@alfadocs/ui-kit-debug 1.119.0 → 1.120.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.
Files changed (34) hide show
  1. package/dist/_chunks/{address-autocomplete-zLygkxFc.js → address-autocomplete-D2uNpxcE.js} +2 -2
  2. package/dist/_chunks/{address-autocomplete-zLygkxFc.js.map → address-autocomplete-D2uNpxcE.js.map} +1 -1
  3. package/dist/_chunks/{address-fields-Dl1ALKhP.js → address-fields-BU65pA-V.js} +2 -2
  4. package/dist/_chunks/{address-fields-Dl1ALKhP.js.map → address-fields-BU65pA-V.js.map} +1 -1
  5. package/dist/_chunks/map-view-h6ngp2xi.js +1578 -0
  6. package/dist/_chunks/map-view-h6ngp2xi.js.map +1 -0
  7. package/dist/_chunks/maps-core-gate-DKx96CC_.js +1492 -0
  8. package/dist/_chunks/{maps-core-gate-C4dyo1ic.js.map → maps-core-gate-DKx96CC_.js.map} +1 -1
  9. package/dist/_chunks/{practice-results-b3uchdBn.js → practice-results-yCresv86.js} +759 -750
  10. package/dist/_chunks/practice-results-yCresv86.js.map +1 -0
  11. package/dist/_chunks/{reserved.agent-V2Eqoabh.js → reserved.agent-DRSZ8UcL.js} +2 -2
  12. package/dist/_chunks/{reserved.agent-V2Eqoabh.js.map → reserved.agent-DRSZ8UcL.js.map} +1 -1
  13. package/dist/agent-catalog.json +1 -1
  14. package/dist/browser-support.js +1 -1
  15. package/dist/components/address-autocomplete/index.js +1 -1
  16. package/dist/components/address-fields/index.js +1 -1
  17. package/dist/components/booking/index.js +1 -1
  18. package/dist/components/map-view/index.js +1 -1
  19. package/dist/components/map-view/map-view-pin-geometry.d.ts +39 -0
  20. package/dist/components/map-view/map-view-pin-geometry.d.ts.map +1 -0
  21. package/dist/components/map-view/map-view-pin.d.ts +15 -0
  22. package/dist/components/map-view/map-view-pin.d.ts.map +1 -0
  23. package/dist/components/map-view/map-view.d.ts.map +1 -1
  24. package/dist/components/practice-results/index.js +1 -1
  25. package/dist/components/practice-results/practice-results.d.ts +8 -0
  26. package/dist/components/practice-results/practice-results.d.ts.map +1 -1
  27. package/dist/core.css +1 -1
  28. package/dist/index.js +5 -5
  29. package/dist/tokens.css +1 -1
  30. package/package.json +4 -1
  31. package/dist/_chunks/map-view-CHeGd94F.js +0 -1443
  32. package/dist/_chunks/map-view-CHeGd94F.js.map +0 -1
  33. package/dist/_chunks/maps-core-gate-C4dyo1ic.js +0 -1571
  34. package/dist/_chunks/practice-results-b3uchdBn.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"practice-results-yCresv86.js","sources":["../../node_modules/lucide-react/dist/esm/icons/map.mjs","../../src/components/practice-results/practice-results.agent.ts","../../src/components/practice-results/practice-results.tsx"],"sourcesContent":["/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M14.106 5.553a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619v12.764a1 1 0 0 1-.553.894l-4.553 2.277a2 2 0 0 1-1.788 0l-4.212-2.106a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0z\",\n key: \"169xi5\"\n }\n ],\n [\"path\", { d: \"M15 5.764v15\", key: \"1pn4in\" }],\n [\"path\", { d: \"M9 3.236v15\", key: \"1uimfh\" }]\n];\nconst Map = createLucideIcon(\"map\", __iconNode);\n\nexport { __iconNode, Map as default };\n//# sourceMappingURL=map.mjs.map\n","/* -------------------------------------------------------------------- */\n/* Agent adapter — PracticeResults (0.31.0). */\n/* */\n/* PracticeResults is the SERP primitive — a three-way A/B-testable */\n/* surface (`split-list-map`, `map-first`, `rich-cards`). The adapter */\n/* exposes the active variant + read-only actions for scrolling a card */\n/* into view and focusing a pin. Selection state and filter values are */\n/* consumer-owned via `value` / `onChange` — never read here (the */\n/* result list itself is generally non-sensitive, but follows the same */\n/* pattern as Booking / PatientSearch for consistency). */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { PracticeResultsHandle } from './practice-results';\n\nexport const practiceResultsAgent: AgentAdapter<PracticeResultsHandle> = {\n id: 'practice-results',\n // `scroll` backs scroll_to_result (light highlight, no selection). focus_pin\n // follows the pin-click rules: on a map variant at `lg` and up it chooses the\n // card and the choice persists until another pin, a card or the map is\n // clicked, so it is `select_single`; elsewhere it is a light highlight. The active variant is consumer-owned via props, so there is\n // no `view_change` action — variant is read-only state, not an operation; and\n // there is no in-component routing, so no `navigate`.\n capabilities: ['scroll', 'select_single'],\n state: {\n variant: {\n type: 'string',\n descriptionKey: 'ui.agent.practiceResults.state.variant',\n description:\n 'Active variant — one of `split-list-map`, `map-first`, `rich-cards`.',\n read: (handle) => handle.getVariant(),\n },\n },\n actions: {\n scroll_to_result: {\n safety: 'read',\n argsType: '{ id: string }',\n argsSchema: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'Stable result id (the data-result-id of the card).',\n },\n },\n required: ['id'],\n },\n idempotent: true,\n descriptionKey: 'ui.agent.practiceResults.actions.scrollToResult',\n description:\n 'Scroll the result card with the given id into the visible viewport and give it a light highlight. It does not choose the card: no \"Selected on the map\" tag.',\n examples: [\n { args: { id: 'practice-42' }, note: 'Scroll to a specific result.' },\n ],\n invoke: (handle, args: { id: string }) => {\n handle.scrollToResult(args.id);\n },\n },\n focus_pin: {\n safety: 'read',\n argsType: '{ id: string }',\n argsSchema: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'Stable result id whose map pin to choose.',\n },\n },\n required: ['id'],\n },\n idempotent: true,\n descriptionKey: 'ui.agent.practiceResults.actions.focusPin',\n description:\n 'Choose the result with the given id as a pin click would. On a map variant at lg width and up the card gets the strong highlight and the \"Selected on the map\" tag, kept until another pin, a card or the map is clicked. On rich-cards or a narrow screen it gives a light highlight only.',\n examples: [\n {\n args: { id: 'practice-42' },\n note: 'Choose the matching result and pin, as a pin click does.',\n },\n ],\n invoke: (handle, args: { id: string }) => {\n handle.focusPin(args.id);\n },\n },\n },\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'practice-results',\n description:\n 'Marks the PracticeResults root region. The headingLevel prop accepts 1 to 6 (default 2) for the optional results heading; use 1 when it is the page title. Card names and default error and empty-state titles use the next level, capped at 6.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description: 'Sourced from the id prop.',\n },\n item: {\n attr: 'data-result-id',\n description:\n 'Stable result id emitted on each rendered result card. Used by scroll_to_result / focus_pin to address a single result. The non-focusable card group is labelled by the practice-result-{id}-name heading, which contains data-component=\"practice-result-name-link\": a visible link to result.href that navigates without firing onResultSelect or click telemetry. Each card also carries up to two CTA anchors: data-component=\"practice-result-open-cta\" (practice page; absent when the consumer sets showCardOpenButton=false) and data-component=\"practice-result-book-cta\" (booking flow; data-slot-aware=\"true\" when it deep-links to a next slot). Card content carries its own read-only hooks: data-component=\"practice-result-tagline\", data-component=\"practice-result-description\", data-component=\"practice-result-latest-review\" (the newest approved review as one truncated line under the rating, from latestReview; its text span carries the language of the review as lang; absent without latestReview), and data-component=\"practice-result-price\" (the kit-formatted first-visit cost: firstVisitPrice supports fixed, from and range amounts plus an optional practice-written label; it takes precedence over the older firstVisitPriceCents fixed-price field. Absent when neither price is set).',\n },\n },\n};\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type ComponentPropsWithoutRef,\n type CSSProperties,\n type ReactNode,\n} from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport {\n CalendarClock,\n CalendarPlus,\n ChevronDown,\n Languages,\n Map as MapIcon,\n MapPin,\n RefreshCw,\n ShieldCheck,\n Heart,\n} from 'lucide-react';\nimport { Alert } from '../alert/alert';\nimport { Avatar } from '../avatar/avatar';\nimport { Button } from '../button/button';\nimport { DropdownMenu } from '../dropdown-menu/dropdown-menu';\nimport { EmptyState } from '../empty-state/empty-state';\nimport {\n MapView,\n type MapMarker,\n type MapMarkerHoverSource,\n type MapViewHandle,\n type MapViewProps,\n type MapViewStaticPreview,\n} from '../map-view/map-view';\nimport { Rating } from '../rating/rating';\nimport { Sheet } from '../sheet/sheet';\nimport { Skeleton } from '../skeleton/skeleton';\nimport { Spinner } from '../spinner/spinner';\nimport { Badge } from '../badge/badge';\nimport { useIsomorphicLayoutEffect } from '../../hooks/use-isomorphic-layout-effect';\nimport { useMediaQuery } from '../../hooks/use-media-query';\nimport { motionIsOff } from '../_shared/motion-is-off';\nimport { lengthTokenPx } from '../_shared/length-token';\nimport { isFocusVisible } from '../_shared/focus-visible';\nimport { formatPrice, formatPriceOrFree } from '../_shared/format-price';\nimport { useAgentRegistration } from '../../agent/registry';\nimport { practiceResultsAgent } from './practice-results.agent';\n\n/* -------------------------------------------------------------------- */\n/* PracticeResults */\n/* */\n/* SERP primitive — a three-way A/B-testable surface. One `variant` */\n/* prop selects between Doctolib-style split-list-map (default), */\n/* map-first (map dominant, list rail), and rich-cards (no live map, */\n/* each card carries a static thumbnail + distance + languages + */\n/* insurance chips). Every card in every variant shows the practice's */\n/* tagline + description when provided and ends in the same two */\n/* anchors — Open (practice page) and Book (booking flow, slot-aware). */\n/* Telemetry and the `PracticeResultsValue` contract are identical */\n/* across variants so A/B comparison is like-for-like. */\n/* */\n/* The consumer owns the search backend (results array, totalCount, */\n/* bbox-driven re-queries via `onBoundsChange`, pagination via */\n/* `onLoadMore`) and the routing (`onResultSelect`). The kit owns */\n/* layout, filter/sort logic, pin↔card sync, accessibility, telemetry. */\n/* -------------------------------------------------------------------- */\n\n/* -------------------------------------------------------------------- */\n/* Public types */\n/* -------------------------------------------------------------------- */\n\nexport type PracticeResultsVariant =\n | 'split-list-map'\n | 'map-first'\n | 'rich-cards';\n\n/** Heading levels the results title accepts. */\nexport type PracticeResultsHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;\n\nexport type PracticeResultsSort =\n | 'distance'\n | 'rating'\n | 'next-available'\n | 'price'\n | 'relevance';\n\n/**\n * Where the result-selection event originated.\n *\n * - `card` — patient clicked the card body. The kit's behaviour is\n * **sync only** (centre the map on the matching pin) — it does NOT\n * navigate. The consumer's `onResultSelect` handler should typically\n * no-op or augment with hover-like analytics for this source.\n * - `pin` — patient clicked a map pin. Sync-only (scroll list to the\n * matching card); no navigation.\n * - `open-button` — patient clicked the explicit \"Open\" CTA on a card.\n * Navigates to the practice page. Booking-website's consumer routes\n * to `result.href` on this source value.\n * - `book-button` — patient clicked the always-visible \"Book\" CTA on a\n * card (1.7.0). Navigates to the booking flow: the concrete\n * `nextAvailableSlot.bookingHref` when known, else `bookingHref`,\n * else `href`. Both CTAs are real anchors, so native navigation\n * happens whether or not the consumer handles the source — the value\n * exists so booking intent from the SERP can be attributed.\n *\n * `open-button` and `book-button` are the only sources that should\n * drive navigation.\n */\nexport type PracticeResultsSelectSource =\n | 'card'\n | 'pin'\n | 'open-button'\n | 'book-button';\n\nexport interface PracticeResultsBounds {\n north: number;\n south: number;\n east: number;\n west: number;\n}\n\nexport interface PracticeResult {\n id: string;\n /** Stable URL for the practice or operator detail page. */\n href: string;\n name: string;\n /** Profession or specialty caption. */\n subtitle?: string;\n /**\n * One-line slogan shown under the caption on every card\n * (booking-website maps `bookingSlogan` here). Added in 1.7.0.\n */\n tagline?: string;\n /**\n * Short free-text blurb about the practice. Rendered on every card,\n * clamped to two lines on compact cards and three on rich cards\n * (booking-website maps `bookingDescription` here). Added in 1.7.0.\n */\n description?: string;\n /**\n * Destination of the always-visible \"Book\" CTA. Defaults to `href`\n * (the practice page hosts the booking flow). A\n * `nextAvailableSlot.bookingHref` takes precedence when present so\n * the CTA deep-links to the concrete slot. Added in 1.7.0.\n */\n bookingHref?: string;\n /**\n * Language of this practice's own text (tagline, description, address),\n * as a BCP 47 tag such as `\"it\"`. Sets `lang` on that text so a screen\n * reader on a page in another language pronounces it correctly. Overrides\n * the list-level `contentLang`. Leave it out and no `lang` is written.\n * The kit's own labels keep the page language.\n */\n contentLang?: string;\n /** Avatar / logo URL. */\n imageUrl?: string;\n /**\n * Text alternative for the photo's `<img>`, for image search and crawlers.\n * Pass the practice name. Leave it out and the photo stays decorative\n * (`alt=\"\"`), as before. Added in 1.91.0.\n */\n imageAlt?: string;\n /** Practice location. Required by the two map variants. */\n location?: {\n lat: number;\n lng: number;\n /** Display string e.g. \"Via Brera 14, 20121 Milano\". */\n address?: string;\n /** Distance from search centre, in km. Rendered by rich-cards. */\n distanceKm?: number;\n };\n rating?: {\n value: number;\n count: number;\n };\n /**\n * Newest approved review that has text, shown as one line under the\n * rating: \"Latest review: {text}\". Pass only text that passed moderation.\n * `lang` is the language the review was written in (BCP 47); it is set on\n * the text only, never the label. The text is cut to one line with an\n * ellipsis by CSS, and the full text stays in the DOM. Leave it out, or\n * pass empty text, and no line is drawn.\n */\n latestReview?: {\n text: string;\n lang?: string;\n };\n /** ISO 639-1 codes. Rendered as chips by rich-cards. */\n languages?: string[];\n /** Short brand names. Rendered as chips by rich-cards. */\n insurances?: string[];\n /** Anxious-patient friendly — renders an icon chip on rich-cards. */\n specializedInFearPatients?: boolean;\n /**\n * First-visit price in minor currency units, formatted by the kit.\n * Takes precedence over the older `firstVisitPriceCents` field when\n * `minCents` is finite; otherwise the older field is used.\n * Fixed zero means \"Free\"; from/range zero stays numeric. A range\n * without a finite maximum above its minimum renders as a starting price.\n */\n firstVisitPrice?: {\n kind: 'fixed' | 'from' | 'range';\n minCents: number;\n maxCents?: number;\n /** Short practice-written plain text, without trailing punctuation. */\n label?: string;\n };\n /**\n * Older fixed-price form, kept for booking-website compatibility.\n * Use `firstVisitPrice` for new integrations; a finite minimum wins when\n * both are set.\n * Price in the smallest currency unit (cents / centesimi).\n * Rendered as a labelled \"First visit cost\" chip on every card in both\n * densities (booking-website maps the practice's long-standing\n * first-visit price setting here). Same convention as\n * `ContactProfileCard.firstVisitPriceCents` — the KIT formats, the\n * consumer never hands over a pre-rendered string.\n *\n * `0` is a real value meaning \"free\" and renders the translated word\n * \"Free\" (`common.freeOfCharge`), never a zero amount. Omit the\n * field (or pass `undefined`) when the practice has not set a price —\n * the label and chip disappear completely. Added for HTP-6136.\n */\n firstVisitPriceCents?: number;\n /**\n * ISO-4217 currency code for either first-visit price form. Defaults to\n * `EUR`. Added for HTP-6136.\n */\n currencyCode?: string;\n /**\n * Next available slot — rendered as a \"Next available …\" caption on\n * every card. When present, the card's Book CTA deep-links to\n * `bookingHref` and reads the slot-aware \"Book at {{time}}\" label.\n */\n nextAvailableSlot?: {\n /** ISO datetime, e.g. \"2026-05-23T10:30:00+02:00\". */\n dateTime: string;\n /** Direct deep-link to the kit's Booking with this slot pre-selected. */\n bookingHref: string;\n };\n /**\n * The practice's map thumbnail on rich cards, as an image the host serves\n * from its own domain (HTP-7348). It needs no `googleMapsApiKey` and no\n * consent, because the browser never contacts Google for it. Without it,\n * the card falls back to a Google Static Maps image when a\n * `googleMapsApiKey` is given, else to the address block.\n */\n mapImage?: {\n src: string;\n srcSet?: string;\n };\n /** Opaque payload echoed back in onResultSelect. */\n __payload?: unknown;\n}\n\nexport interface PracticeResultsValue {\n /** Selected facets — keys match consumer's `facets[].key`. */\n facets?: Record<string, string[]>;\n /** Sort key. Default `'distance'` when searchCentre is set, otherwise `'relevance'`. */\n sort?: PracticeResultsSort;\n}\n\n/** Shared facet shape — same as PatientSearch. */\nexport interface FacetOption {\n value: string;\n label: string;\n}\n\nexport interface FacetGroup {\n key: string;\n label: string;\n /** Non-empty = dropdown of selectable options. */\n options: FacetOption[];\n multi?: boolean;\n /**\n * Facet behaviour. `'boolean'` renders a single on/off toggle chip that\n * keeps results whose `field` (default: `key`) is `true`. `'options'`\n * forces dropdown semantics. When omitted, non-empty `options` means\n * dropdown; an empty `options` array falls back to the legacy boolean\n * degrade ONLY when at least one result actually carries a boolean at\n * `field` — otherwise the group is not rendered and not filtered on.\n * (Before 1.6.0 an empty-options group always became a boolean toggle\n * keyed by `key`; with missing data or a mismatched field name a single\n * click filtered out every result.) Added in 1.6.0.\n */\n type?: 'options' | 'boolean';\n /** Result field a boolean facet reads — defaults to `key`. Added in 1.6.0. */\n field?: string;\n}\n\n/**\n * A `FacetGroup` with its boolean-ness resolved against the actual result\n * set. Dead groups (no options, no matching boolean field) are dropped at\n * resolution time so they neither render a chip nor filter anything.\n */\ninterface ResolvedFacetGroup extends FacetGroup {\n /** True when the group filters on a boolean result field. */\n boolean: boolean;\n /** The result field a boolean group reads (`field ?? key`). */\n booleanField: string;\n}\n\nexport interface PracticeResultsHandle {\n getVariant: () => PracticeResultsVariant;\n /**\n * Scroll a specific result into view (useful for deep-link → highlight).\n * Gives the card the light highlight only: nothing was chosen on the map,\n * so no \"Selected on the map\" tag.\n */\n scrollToResult: (id: string) => void;\n /**\n * Imperatively focus a pin on the map. Follows the pin-click rules: on a\n * map variant at `lg` and up it chooses the card (strong highlight and tag,\n * kept until another pin, a card or the map is clicked, never scrolled);\n * elsewhere, including `rich-cards`, it gives the light highlight only.\n */\n focusPin: (id: string) => void;\n}\n\nexport interface PracticeResultsProps\n extends\n Omit<\n ComponentPropsWithoutRef<'div'>,\n 'aria-label' | 'onChange' | 'onSelect' | 'results' | 'defaultValue'\n >,\n VariantProps<typeof rootVariants> {\n /** Default: 'split-list-map'. Pass to override (PostHog / dev-menu). */\n variant?: PracticeResultsVariant;\n\n // Data\n results: PracticeResult[];\n /**\n * Default language of every result's own text, as a BCP 47 tag. A result's\n * own `contentLang` wins. Leave both out and no `lang` is written.\n */\n contentLang?: string;\n /** Total count for the \"{{count}} risultati\" header. May differ from results.length. */\n totalCount?: number;\n /**\n * Visible search location. Drives the distance figures and the header\n * label. On the map it is the starting view, and the view when no\n * result has a location: as soon as results with a location are listed,\n * the map fits them all into view instead (capped at zoom 15).\n */\n searchCentre?: { lat: number; lng: number; label?: string };\n\n // Filters (same shape as PatientSearch)\n facets?: FacetGroup[];\n\n // Map config (used by split + map-first; ignored by rich-cards)\n googleMapsApiKey?: string;\n /**\n * Google Maps Map ID — required when consumers use Google's\n * `AdvancedMarkerElement`. Forwarded to every internal `<MapView>`\n * instance (split-list-map's desktop + mobile-sheet column,\n * map-first's main canvas). `rich-cards` thumbnails are unaffected\n * — they're static `<img>` fetches, not the JS SDK.\n *\n * When omitted, MapView falls back to the kit's inline-style\n * theming (`getGoogleMapsTheme()`), same as before.\n */\n mapId?: string;\n /**\n * Group nearby pins into numbered clusters on the map panels. `'auto'`\n * (default) clusters only large result sets; `true` always clusters, which\n * suits a small set the host wants grouped; `false` never does. Same meaning\n * as `MapView`'s `clusterMarkers`, forwarded to every map panel (HTP-7356).\n * A hovered or chosen practice stays on its own pin.\n */\n clusterMarkers?: boolean | 'auto';\n /**\n * Map zoom before the results are fitted, and with no results to fit.\n * Default 13.\n */\n defaultZoom?: number;\n /** Actual settled viewport, including initial load and refits (split + map-first only). */\n onBoundsChange?: (bounds: PracticeResultsBounds) => void;\n /**\n * GDPR consent gate, forwarded to the embedded MapView. When `false`\n * (default) the kit renders the same consent placeholder MapView ships\n * by default — third-party scripts never load until the consumer\n * captures opt-in. Also gates the rich-cards static thumbnails (which\n * are still IP-disclosing third-party loads).\n */\n consentGranted?: boolean;\n /** Fires when the patient clicks the \"Load map\" CTA in the consent placeholder. */\n onConsentRequest?: () => void;\n /**\n * A map image with the result pins, shown in the map panel while\n * `consentGranted` is false (HTP-7348), with a \"Show interactive map\"\n * button that calls `onConsentRequest`. See `MapView`'s `staticPreview`.\n * Serve it from your own domain so the browser contacts no third party\n * before the patient chooses. Split-list-map and map-first only.\n */\n mapPreview?: MapViewStaticPreview;\n /**\n * @default false\n * 0.31.4: bypass the embedded `<MapView>`'s GDPR consent placeholder\n * entirely. Use when the consumer's page already gates the Google\n * Maps loader script at a higher level (e.g. Usercentrics / OneTrust\n * / any CMP that decides per-cookie-category whether to load Google\n * Maps). Forwarded to every internal `<MapView>` instance —\n * `split-list-map`'s right column and `map-first`'s main canvas.\n * `rich-cards` static thumbnails are unaffected (they dropped the\n * consent gate in 0.31.3 — see CHANGELOG).\n *\n * When `true`, `consentGranted` and `onConsentRequest` are ignored:\n * the kit assumes the CMP at the page level has already made the\n * call and the map loader script is either present or properly\n * blocked. Setting both `consentGranted={false}` and\n * `skipConsentGate={true}` is well-defined — gate skipped, no\n * placeholder rendered.\n */\n skipConsentGate?: boolean;\n\n // Pagination\n /** Fires when the patient scrolls to the bottom of the list. */\n onLoadMore?: () => void;\n /** True while the next page is loading — kit shows a Spinner at the list tail. */\n loadingMore?: boolean;\n\n // State\n value: PracticeResultsValue;\n onChange: (next: PracticeResultsValue) => void;\n /**\n * Sort choices to offer, shown in the kit's own order. Default (left out):\n * all of them. Leave out `'price'` to hide the first-visit price sort.\n *\n * - An empty list, or one with no known sort, fails closed: no sort chip\n * is rendered and the list uses the default sort.\n * - An active sort that is not offered falls back to the default sort\n * (Distance with `searchCentre`, otherwise Relevance), or to the first\n * offered sort when the default is not offered. This also applies to an\n * unknown `value.sort`, with or without `sortOptions`.\n * - The fallback fires no `onChange` and no `serp_sort_changed`. The next\n * `onChange` (for example a filter change) carries the resolved sort.\n * - Drop a hidden sort from your own state (for example an old\n * `?sort=price` URL) so your resolved sort matches the kit's.\n */\n sortOptions?: readonly PracticeResultsSort[];\n onResultSelect: (\n result: PracticeResult,\n source: PracticeResultsSelectSource,\n ) => void;\n\n // UX\n /**\n * Initial loading skeletons. Per SSR-first design, skeletons render\n * only when `results.length === 0 && loading === true` so a server-\n * rendered list isn't flashed away on hydration.\n */\n loading?: boolean;\n /** Custom empty-state slot. Defaults to the localised EmptyState. */\n emptyResultsSlot?: ReactNode;\n /** Custom error slot. Renders when `error` is truthy. */\n errorSlot?: ReactNode;\n /** Truthy = render the error slot. Pair with `onRetry` for the default slot. */\n error?: boolean;\n /** Retry handler for the default error slot's CTA. */\n onRetry?: () => void;\n /** Heading rendered above the results (e.g. \"I migliori Dentisti a Milano\"). */\n heading?: string;\n /** Results heading level (default 2); card names use the next level, capped at 6. */\n headingLevel?: PracticeResultsHeadingLevel;\n /** Intro text below the heading. */\n intro?: string;\n /**\n * Label for the per-card \"Open\" button. Defaults to\n * `t('practiceResults.card.open')` (\"Apri\" / \"Open\"). The Open button\n * is the **only** card affordance that fires `onResultSelect` with\n * `source: 'open-button'`. Card-body clicks and pin clicks fire with\n * `'card'` / `'pin'` and are sync-only.\n */\n cardOpenButtonLabel?: string;\n /**\n * Label for the per-card \"Book\" button (1.7.0). Defaults to\n * `t('practiceResults.card.book')` (\"Prenota\" / \"Book\"). The button\n * renders on every card in every variant and fires `onResultSelect`\n * with `source: 'book-button'`. When the result carries a\n * `nextAvailableSlot` the label switches to the slot-aware\n * `t('practiceResults.nextSlot.cta')` (\"Book at {{time}}\") regardless\n * of this override — the time is the message.\n */\n cardBookButtonLabel?: string;\n /**\n * Whether each card renders the secondary \"Open\" button next to\n * \"Book\". Defaults to `true`. Set `false` on a surface where both\n * buttons would lead to the same page, so the card carries one\n * clear call to action: the booking site's practice page hosts the\n * booking flow, so its cards keep only \"Book\". `onResultSelect`\n * never fires with `source: 'open-button'` while hidden.\n */\n showCardOpenButton?: boolean;\n\n // a11y\n 'aria-label'?: string;\n /** Agent-readiness instance id. */\n id?: string;\n}\n\n/* -------------------------------------------------------------------- */\n/* Constants */\n/* -------------------------------------------------------------------- */\n\nconst DEFAULT_ZOOM = 13;\n\nconst SORT_OPTIONS: readonly PracticeResultsSort[] = [\n 'distance',\n 'rating',\n 'next-available',\n 'price',\n 'relevance',\n] as const;\n\n/* -------------------------------------------------------------------- */\n/* Language label resolution */\n/* */\n/* The kit's i18n bundles ship `languages.<iso>` keys for the 18 */\n/* supported ISO codes (en / it / fr / de / es / pt / nl / pl / ru / */\n/* ja / zh / hi / tr / sq / sv / el / ro / ar) translated into each of */\n/* the 18 locales. Look up the active locale's translation; fall back */\n/* to the uppercase code for unknown / unbundled codes. */\n/* */\n/* Same helper used by ContactProfileCard's inline resolver — keep the */\n/* fallback rules in sync if you touch one. */\n/* -------------------------------------------------------------------- */\n\nfunction resolveLanguageLabel(\n code: string,\n t: ReturnType<typeof useTranslation>['t'],\n i18n: ReturnType<typeof useTranslation>['i18n'],\n): string {\n const key = `languages.${code}`;\n return i18n.exists(key) ? t(key) : code.toUpperCase();\n}\n\n/* -------------------------------------------------------------------- */\n/* Telemetry */\n/* */\n/* Same `window.track` convention as PatientSearch / booking-website. */\n/* Soft-fails outside the browser and when no host has wired `track`. */\n/* -------------------------------------------------------------------- */\n\ninterface TrackFn {\n (event: string, payload?: Record<string, unknown>): void;\n}\n\nfunction track(event: string, payload?: Record<string, unknown>): void {\n if (typeof window === 'undefined') return;\n const fn = (window as unknown as { track?: TrackFn }).track;\n try {\n fn?.(event, payload);\n } catch {\n // Telemetry must never throw into the UI. Swallow.\n }\n}\n\n/* -------------------------------------------------------------------- */\n/* Helpers */\n/* -------------------------------------------------------------------- */\n\n/**\n * Emit `serp_result_clicked` + invoke the consumer's `onResultSelect`\n * with the result's `position` in the currently-rendered (filtered +\n * sorted) list — needed for the PostHog funnel's position-bias analysis\n * (which rank gets clicked across variants).\n */\nfunction emitResultClick(\n results: PracticeResult[],\n id: string,\n source: PracticeResultsSelectSource,\n onResultSelect: PracticeResultsProps['onResultSelect'],\n): void {\n const position = results.findIndex((r) => r.id === id);\n if (position < 0) return;\n const result = results[position];\n if (!result) return;\n track('serp_result_clicked', { resultId: id, source, position });\n onResultSelect(result, source);\n}\n\n/** Haversine distance between two lat/lng points, in km. */\nfunction haversineKm(\n a: { lat: number; lng: number },\n b: { lat: number; lng: number },\n): number {\n const R = 6371; // earth radius in km\n const dLat = ((b.lat - a.lat) * Math.PI) / 180;\n const dLng = ((b.lng - a.lng) * Math.PI) / 180;\n const lat1 = (a.lat * Math.PI) / 180;\n const lat2 = (b.lat * Math.PI) / 180;\n const sinDLat = Math.sin(dLat / 2);\n const sinDLng = Math.sin(dLng / 2);\n const h =\n sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLng * sinDLng;\n return 2 * R * Math.asin(Math.min(1, Math.sqrt(h)));\n}\n\n/**\n * Resolve declared facet groups against the actual result set. Boolean\n * groups (explicit `type: 'boolean'`, or the pre-1.6.0 empty-options\n * degrade when the data really carries a boolean at `field ?? key`) are\n * flagged; option groups keep dropdown semantics; groups that can never\n * match anything — empty options and no boolean field in the data — are\n * dropped entirely so one chip click can't blank the whole list. A group\n * carrying an ACTIVE selection is never dropped: the chip must survive a\n * result-set refresh so the patient can still toggle it off (otherwise a\n * focused chip could unmount mid-interaction and the selection would\n * become unclearable from the kit UI).\n */\nfunction resolveFacetGroups(\n facetGroups: FacetGroup[],\n results: PracticeResult[],\n selections: PracticeResultsValue['facets'],\n): ResolvedFacetGroup[] {\n const resolved: ResolvedFacetGroup[] = [];\n for (const group of facetGroups) {\n const booleanField = group.field ?? group.key;\n const hasActiveSelection = (selections?.[group.key]?.length ?? 0) > 0;\n const isBoolean =\n group.type === 'boolean' ||\n (group.type !== 'options' &&\n group.options.length === 0 &&\n (hasActiveSelection ||\n results.some(\n (result) =>\n typeof (result as unknown as Record<string, unknown>)[\n booleanField\n ] === 'boolean',\n )));\n if (!isBoolean && group.options.length === 0) continue;\n resolved.push({ ...group, boolean: isBoolean, booleanField });\n }\n return resolved;\n}\n\n/** Apply selected facets to the result list — client-side narrowing. */\nfunction applyFilters(\n results: PracticeResult[],\n facets: PracticeResultsValue['facets'],\n facetGroups: ResolvedFacetGroup[],\n): PracticeResult[] {\n if (!facets || Object.keys(facets).length === 0) return results;\n return results.filter((result) => {\n for (const group of facetGroups) {\n const selected = facets[group.key];\n if (!selected || selected.length === 0) continue;\n if (group.boolean) {\n // Boolean toggle — keep results whose `field ?? key` is `true`.\n const value = (result as unknown as Record<string, unknown>)[\n group.booleanField\n ];\n if (value !== true) return false;\n continue;\n }\n const field =\n group.key === 'insurance'\n ? result.insurances\n : group.key === 'language'\n ? result.languages\n : (result as unknown as Record<string, unknown>)[group.key];\n if (!Array.isArray(field)) return false;\n const hit = group.multi\n ? selected.some((v) => field.includes(v))\n : field.includes(selected[0]);\n if (!hit) return false;\n }\n return true;\n });\n}\n\n/** Follow the card's structured-price precedence; unknown amounts sort last. */\nfunction firstVisitSortAmount(result: PracticeResult): number {\n const amount =\n result.firstVisitPrice && Number.isFinite(result.firstVisitPrice.minCents)\n ? result.firstVisitPrice.minCents\n : result.firstVisitPriceCents;\n return amount != null && Number.isFinite(amount) && amount >= 0\n ? amount\n : Infinity;\n}\n\n/** True when the card shows an exact price (structured fixed or legacy). */\nfunction isFixedFirstVisitPrice(result: PracticeResult): boolean {\n return result.firstVisitPrice &&\n Number.isFinite(result.firstVisitPrice.minCents)\n ? result.firstVisitPrice.kind === 'fixed'\n : true;\n}\n\n/** Known distances first; equal or absent distances retain the host order. */\nfunction compareDistance(a?: number, b?: number): number {\n if (a == null && b == null) return 0;\n if (a == null) return 1;\n if (b == null) return -1;\n return a - b;\n}\n\n/** Sort results by the active sort key. Mutation-free. */\nfunction applySort(\n results: PracticeResult[],\n sort: PracticeResultsSort,\n searchCentre?: { lat: number; lng: number },\n): PracticeResult[] {\n const decorated = results.map((result) => {\n const distanceKm = result.location\n ? (result.location.distanceKm ??\n (searchCentre ? haversineKm(searchCentre, result.location) : undefined))\n : undefined;\n return {\n result,\n distanceKm,\n price: sort === 'price' ? firstVisitSortAmount(result) : undefined,\n fixed: sort === 'price' ? isFixedFirstVisitPrice(result) : false,\n };\n });\n const compare = (\n a: (typeof decorated)[number],\n b: (typeof decorated)[number],\n ): number => {\n switch (sort) {\n case 'distance': {\n return compareDistance(a.distanceKm, b.distanceKm);\n }\n case 'price': {\n const av = a.price ?? Infinity;\n const bv = b.price ?? Infinity;\n // Check equality before subtracting: Infinity - Infinity is NaN.\n if (av !== bv) return av - bv;\n // At equal amounts an exact price (\"Gratuito\" at 0) beats \"from\".\n // No usable price on both sides: distance only.\n if (av !== Infinity && a.fixed !== b.fixed) return a.fixed ? -1 : 1;\n return compareDistance(a.distanceKm, b.distanceKm);\n }\n case 'rating': {\n const av = a.result.rating?.value ?? -1;\n const bv = b.result.rating?.value ?? -1;\n return bv - av;\n }\n case 'next-available': {\n const av = a.result.nextAvailableSlot?.dateTime ?? '';\n const bv = b.result.nextAvailableSlot?.dateTime ?? '';\n if (!av && !bv) return 0;\n if (!av) return 1;\n if (!bv) return -1;\n return av.localeCompare(bv);\n }\n case 'relevance':\n default:\n return 0;\n }\n };\n return [...decorated].sort(compare).map(({ result, distanceKm }) =>\n distanceKm != null && result.location\n ? {\n ...result,\n location: { ...result.location, distanceKm },\n }\n : result,\n );\n}\n\n/** Compute the bounding box of an array of lat/lng pairs. */\nfunction boundsForResults(\n results: PracticeResult[],\n): PracticeResultsBounds | undefined {\n let north = -Infinity;\n let south = Infinity;\n let east = -Infinity;\n let west = Infinity;\n let anyPoint = false;\n for (const result of results) {\n if (!result.location) continue;\n anyPoint = true;\n const { lat, lng } = result.location;\n if (lat > north) north = lat;\n if (lat < south) south = lat;\n if (lng > east) east = lng;\n if (lng < west) west = lng;\n }\n if (!anyPoint) return undefined;\n return { north, south, east, west };\n}\n\n/* -------------------------------------------------------------------- */\n/* Root CVA */\n/* -------------------------------------------------------------------- */\n\nconst rootVariants = cva(\n 'ds:flex ds:w-full ds:flex-col ds:text-[var(--foreground)] ds:gap-[var(--spacing-md)]',\n {\n variants: {\n variant: {\n 'split-list-map': '',\n 'map-first': '',\n 'rich-cards': '',\n },\n },\n defaultVariants: { variant: 'split-list-map' },\n },\n);\n\n/* -------------------------------------------------------------------- */\n/* HeaderBlock — heading + intro + totalCount summary */\n/* -------------------------------------------------------------------- */\n\ninterface HeaderBlockProps {\n heading?: string;\n headingLevel: PracticeResultsHeadingLevel;\n intro?: string;\n totalCount?: number;\n searchCentreLabel?: string;\n}\n\nfunction HeaderBlock({\n heading,\n headingLevel,\n intro,\n totalCount,\n searchCentreLabel,\n}: HeaderBlockProps) {\n const { t } = useTranslation('ui');\n const Heading = `h${headingLevel}` as const;\n if (!heading && !intro && totalCount == null) return null;\n const summary =\n totalCount != null\n ? searchCentreLabel\n ? t('practiceResults.results.heading', {\n count: totalCount,\n place: searchCentreLabel,\n })\n : t('practiceResults.results.summary', { count: totalCount })\n : undefined;\n // Plain <div> rather than <header>: the surrounding region landmark\n // already names the section, and <header> inside a landmark can trip\n // axe's `landmark-banner-is-top-level` when the rule's heuristic\n // walks the DOM. The visual rendering is unchanged.\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-xs)]\">\n {heading ? (\n <Heading className=\"type-title-section ds:m-0 ds:text-[var(--foreground)]\">\n {heading}\n </Heading>\n ) : null}\n {intro ? (\n <p className=\"type-body ds:m-0 ds:text-[var(--muted-foreground)]\">\n {intro}\n </p>\n ) : null}\n {summary ? (\n <p\n className=\"type-body-sm ds:m-0 ds:text-[var(--muted-foreground)]\"\n aria-live=\"polite\"\n >\n {summary}\n </p>\n ) : null}\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* FilterChipRow */\n/* */\n/* Shared chip rail — same shape as PatientSearch's FacetChipRow, lifted */\n/* here so the SERP can carry the same set of filters without depending */\n/* on PatientSearch internals. */\n/* -------------------------------------------------------------------- */\n\nconst facetChipVariants = cva(\n [\n 'ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]',\n 'ds:min-h-[var(--min-target-size)]',\n 'ds:ps-[var(--spacing-md)] ds:pe-[var(--spacing-md)]',\n 'ds:pt-[var(--spacing-xs)] ds:pb-[var(--spacing-xs)]',\n 'ds:rounded-[var(--radius-full)]',\n 'type-label',\n 'ds:transition-colors ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-[color:var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:forced-colors:focus-visible:outline-[CanvasText]',\n ].join(' '),\n {\n variants: {\n state: {\n idle: 'ds:bg-[var(--secondary)] ds:text-[var(--foreground)] ds:border ds:border-[color:var(--border)] ds:hover:bg-[color-mix(in_srgb,var(--primary)_8%,var(--secondary))]',\n active:\n 'ds:bg-[var(--primary)] ds:text-[var(--primary-foreground)] ds:border ds:border-transparent ds:font-[var(--font-weight-semibold)]',\n },\n },\n defaultVariants: { state: 'idle' },\n },\n);\n\nconst filterChipRowVariants = cva(\n 'ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-xs)]',\n {\n variants: {\n placement: {\n inline: '',\n // Floating chrome on map-first: soft `--popover` surface so chips\n // read over the map without losing contrast. Same surface as\n // dropdowns / sheets — themed and forced-colors safe.\n floating: [\n 'ds:rounded-[var(--radius-full)]',\n 'ds:bg-[var(--popover)] ds:shadow-[var(--shadow-md)]',\n 'ds:ps-[var(--spacing-sm)] ds:pe-[var(--spacing-sm)]',\n 'ds:pt-[var(--spacing-2xs)] ds:pb-[var(--spacing-2xs)]',\n ].join(' '),\n },\n },\n defaultVariants: { placement: 'inline' },\n },\n);\n\nconst clearFiltersButtonVariants = cva(\n [\n 'ds:ms-[var(--spacing-xs)] ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]',\n 'ds:min-h-[var(--min-target-size)] ds:ps-[var(--spacing-xs)] ds:pe-[var(--spacing-xs)]',\n 'type-label ds:text-[color:var(--muted-foreground)]',\n 'ds:rounded-[var(--radius-sm)]',\n 'ds:hover:text-[color:var(--foreground)]',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-[color:var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n ].join(' '),\n);\n\ninterface FilterChipRowProps {\n facets: ResolvedFacetGroup[];\n value: PracticeResultsValue['facets'];\n onChange: (next: Record<string, string[]>) => void;\n /** Visual placement — floating overlay on map-first; inline elsewhere. */\n floating?: boolean;\n}\n\nfunction FilterChipRow({\n facets,\n value,\n onChange,\n floating,\n}: FilterChipRowProps) {\n const { t } = useTranslation('ui');\n const selections = useMemo(() => value ?? {}, [value]);\n\n const hasAnyActive = useMemo(\n () => Object.values(selections).some((arr) => arr && arr.length > 0),\n [selections],\n );\n\n const updateFacet = (key: string, values: string[]) => {\n const next = { ...selections };\n if (values.length === 0) delete next[key];\n else next[key] = values;\n onChange(next);\n track('serp_filter_changed', { key, values });\n };\n\n const toggleBoolean = (key: string) => {\n const isActive = (selections[key]?.length ?? 0) > 0;\n updateFacet(key, isActive ? [] : ['true']);\n };\n\n const toggleOption = (group: FacetGroup, option: string) => {\n const current = selections[group.key] ?? [];\n const active = current.includes(option);\n let next: string[];\n if (group.multi) {\n next = active\n ? current.filter((v) => v !== option)\n : [...current, option];\n } else {\n next = active ? [] : [option];\n }\n updateFacet(group.key, next);\n };\n\n if (facets.length === 0) return null;\n\n return (\n <div\n data-component=\"practice-results-filters\"\n role=\"group\"\n aria-label={t('practiceResults.filters.label')}\n className={filterChipRowVariants({\n placement: floating ? 'floating' : 'inline',\n })}\n >\n {facets.map((group) => {\n const isBoolean = group.boolean;\n const current = selections[group.key] ?? [];\n const isActive = current.length > 0;\n\n if (isBoolean) {\n return (\n <button\n key={group.key}\n type=\"button\"\n aria-pressed={isActive}\n aria-label={group.label}\n onClick={() => toggleBoolean(group.key)}\n className={facetChipVariants({\n state: isActive ? 'active' : 'idle',\n })}\n >\n <span>{group.label}</span>\n </button>\n );\n }\n\n const countSuffix = isActive ? ` (${current.length})` : '';\n return (\n <DropdownMenu.Root key={group.key}>\n <DropdownMenu.Trigger asChild>\n <button\n type=\"button\"\n aria-label={`${group.label}${countSuffix}`}\n className={facetChipVariants({\n state: isActive ? 'active' : 'idle',\n })}\n >\n <span>\n {group.label}\n {countSuffix}\n </span>\n <ChevronDown\n aria-hidden=\"true\"\n className=\"ds:size-3.5 ds:shrink-0\"\n />\n </button>\n </DropdownMenu.Trigger>\n <DropdownMenu.Content sideOffset={8} align=\"start\">\n {group.options.map((option) => (\n <DropdownMenu.CheckboxItem\n key={option.value}\n checked={current.includes(option.value)}\n onSelect={(event) => {\n if (group.multi) event.preventDefault();\n toggleOption(group, option.value);\n }}\n >\n {option.label}\n </DropdownMenu.CheckboxItem>\n ))}\n </DropdownMenu.Content>\n </DropdownMenu.Root>\n );\n })}\n {hasAnyActive ? (\n <button\n type=\"button\"\n onClick={() => {\n onChange({});\n track('serp_filter_changed', { key: '*', values: [] });\n }}\n className={clearFiltersButtonVariants()}\n >\n {t('practiceResults.filters.clear')}\n </button>\n ) : null}\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* SortControl — DropdownMenu chip for the active sort key. */\n/* -------------------------------------------------------------------- */\n\ninterface SortControlProps {\n value: PracticeResultsSort;\n options: readonly PracticeResultsSort[];\n onChange: (next: PracticeResultsSort) => void;\n}\n\nfunction SortControl({ value, options, onChange }: SortControlProps) {\n const { t } = useTranslation('ui');\n const labelFor = (sort: PracticeResultsSort): string => {\n switch (sort) {\n case 'distance':\n return t('practiceResults.sort.distance');\n case 'rating':\n return t('practiceResults.sort.rating');\n case 'next-available':\n return t('practiceResults.sort.nextAvailable');\n case 'price':\n return t('practiceResults.sort.price');\n case 'relevance':\n default:\n return t('practiceResults.sort.relevance');\n }\n };\n return (\n <DropdownMenu.Root>\n <DropdownMenu.Trigger asChild>\n <button\n type=\"button\"\n aria-label={`${t('practiceResults.sort.label')}: ${labelFor(value)}`}\n className={facetChipVariants({ state: 'idle' })}\n >\n <span>\n {t('practiceResults.sort.label')}: {labelFor(value)}\n </span>\n <ChevronDown aria-hidden=\"true\" className=\"ds:size-3.5 ds:shrink-0\" />\n </button>\n </DropdownMenu.Trigger>\n <DropdownMenu.Content sideOffset={8} align=\"end\">\n <DropdownMenu.RadioGroup\n value={value}\n onValueChange={(next) => {\n const sort = next as PracticeResultsSort;\n if (sort !== value) {\n onChange(sort);\n track('serp_sort_changed', { sort });\n }\n }}\n >\n {options.map((sort) => (\n <DropdownMenu.RadioItem key={sort} value={sort}>\n {labelFor(sort)}\n </DropdownMenu.RadioItem>\n ))}\n </DropdownMenu.RadioGroup>\n </DropdownMenu.Content>\n </DropdownMenu.Root>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* DistanceChip — formatted \"{km} km\" or \"< 1 km\" reading. */\n/* -------------------------------------------------------------------- */\n\ninterface DistanceChipProps {\n distanceKm: number;\n}\n\nfunction DistanceChip({ distanceKm }: DistanceChipProps) {\n const { t, i18n } = useTranslation('ui');\n const label = useMemo(() => {\n if (distanceKm < 1) return t('practiceResults.distance.below');\n const formatted = new Intl.NumberFormat(i18n.language, {\n maximumFractionDigits: 1,\n }).format(distanceKm);\n return t('practiceResults.distance.km', { distance: formatted });\n }, [distanceKm, t, i18n.language]);\n // 0.31.3: Tag → Badge for visual parity with the rest of the kit's\n // chip language (hero badges, practice-profile badges, PatientSearch\n // facet chips). Same `leading` + `variant` semantics — no consumer\n // API change, purely a primitive swap.\n return (\n <Badge variant=\"neutral\" leading={<MapPin aria-hidden=\"true\" />}>\n {label}\n </Badge>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* StaticMapThumb — Google Static Maps `<img>`. Consent-gated. */\n/* -------------------------------------------------------------------- */\n\ninterface StaticMapThumbProps {\n /** The host's own map image (HTP-7348). Wins over `apiKey`. */\n image?: PracticeResult['mapImage'];\n apiKey?: string;\n center: { lat: number; lng: number };\n zoom?: number;\n /** Address line shown in the tonal placeholder when no `apiKey` is\n * available. Renders next to a MapPin glyph as a multi-line block. */\n address?: string;\n /** Language of `address` (BCP 47). Written as `lang` on the address. */\n addressLang?: string;\n}\n\nconst staticMapThumbVariants = cva(\n [\n 'ds:w-[12rem] ds:h-[8rem] ds:shrink-0',\n 'ds:rounded-[var(--radius-md)] ds:overflow-hidden',\n ].join(' '),\n {\n variants: {\n state: {\n // Tonal address-block placeholder — renders the MapPin glyph\n // + address text on a `--muted` surface. Used when the consumer\n // hasn't provided `googleMapsApiKey`; carries the spatial cue\n // (location is here, even if we can't draw the map).\n placeholder:\n 'ds:flex ds:flex-col ds:items-start ds:justify-end ds:gap-[var(--spacing-2xs)] ds:bg-[var(--muted)] ds:text-[var(--muted-foreground)] ds:p-[var(--spacing-sm)]',\n image: 'ds:[object-fit:cover]',\n },\n },\n defaultVariants: { state: 'placeholder' },\n },\n);\n\n/**\n * Resolve the kit's `--map-marker-color` token to a six-character hex\n * string (no leading `#`) for Google Static Maps' `markers=color:` URL\n * parameter, which requires a literal hex value at fetch time.\n *\n * The token is defined as a direct hex literal in `src/tokens/index.css`\n * (no `var()` indirection), so `getPropertyValue` returns the hex\n * verbatim and we strip the `#`. SSR-safe: when `document` is undefined\n * we fall back to the kit's brand violet (`--color-violet-500`).\n */\nfunction getStaticMapMarkerHex(): string {\n if (typeof document === 'undefined') return '6761e5';\n const raw = getComputedStyle(document.documentElement)\n .getPropertyValue('--map-marker-color')\n .trim();\n const match = /^#?([0-9a-fA-F]{6})$/.exec(raw);\n return match ? match[1] : '6761e5';\n}\n\n/** The thumbnail with no map: a pin and the address on a muted block. */\nfunction StaticMapAddressBlock({\n address,\n addressLang,\n}: {\n address?: string;\n addressLang?: string;\n}) {\n return (\n <div\n aria-hidden={address ? undefined : 'true'}\n className={staticMapThumbVariants({ state: 'placeholder' })}\n >\n <MapPin aria-hidden=\"true\" className=\"ds:size-5 ds:shrink-0\" />\n {address ? (\n <span\n lang={addressLang}\n dir=\"auto\"\n className=\"type-meta ds:[overflow-wrap:anywhere]\"\n >\n {address}\n </span>\n ) : null}\n </div>\n );\n}\n\nfunction StaticMapThumb({\n image,\n apiKey,\n center,\n zoom = 14,\n address,\n addressLang,\n}: StaticMapThumbProps) {\n const { t } = useTranslation('ui');\n const [failedSrc, setFailedSrc] = useState<string | null>(null);\n // HTP-7348: the host's own image. The browser contacts no third party. It\n // is decorative (alt=\"\"): the card already gives the address. If it fails,\n // the address block shows instead of a broken image.\n if (image && failedSrc !== image.src) {\n return (\n <img\n src={image.src}\n srcSet={image.srcSet}\n alt=\"\"\n loading=\"lazy\"\n decoding=\"async\"\n width={240}\n height={160}\n onError={() => setFailedSrc(image.src)}\n className={staticMapThumbVariants({ state: 'image' })}\n />\n );\n }\n if (image) {\n return (\n <StaticMapAddressBlock address={address} addressLang={addressLang} />\n );\n }\n // 0.31.3: dropped the consent gate. Static maps are a single\n // one-shot `<img>` fetch, not a dynamic tile loader — they're\n // categorically different from the interactive MapView (which\n // still respects `consentGranted` on the same root). Without a\n // consumer-supplied `apiKey`, fall back to a TONAL ADDRESS BLOCK\n // (MapPin + address text on `--muted`), not a bare pin glyph —\n // the address carries the spatial cue even without the map.\n if (!apiKey) {\n return (\n <StaticMapAddressBlock address={address} addressLang={addressLang} />\n );\n }\n // Static Maps URL. 240×160 keeps the asset under the 1MP free tier\n // and looks crisp on 2× displays at the rendered 12rem × 8rem cell.\n // Marker colour is sourced from the `--map-marker-color` token at\n // runtime so the value stays in `src/tokens/**` per the closed-palette\n // constraint (Google's URL parameter can't read a CSS custom prop).\n const markerHex = getStaticMapMarkerHex();\n const src =\n `https://maps.googleapis.com/maps/api/staticmap` +\n `?center=${center.lat},${center.lng}` +\n `&zoom=${zoom}` +\n `&size=240x160&scale=2` +\n `&markers=color:0x${markerHex}%7C${center.lat},${center.lng}` +\n `&key=${encodeURIComponent(apiKey)}`;\n return (\n <img\n src={src}\n alt={t('practiceResults.thumbnail.alt')}\n loading=\"lazy\"\n decoding=\"async\"\n width={240}\n height={160}\n className={staticMapThumbVariants({ state: 'image' })}\n />\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* CardTagline / CardDescription — the practice's own words (1.7.0). */\n/* */\n/* Both are optional on `PracticeResult` and render on every card in */\n/* every variant. The description is CSS-clamped rather than truncated */\n/* server-side so the full text stays in the DOM for assistive tech and */\n/* the clamp follows the card's actual width. */\n/* */\n/* Every piece of the practice's own text on a card (name, tagline, */\n/* description, address) carries `dir=\"auto\"`, so it keeps the direction */\n/* it was typed in rather than the page's. */\n/* -------------------------------------------------------------------- */\n\nfunction CardTagline({ tagline, lang }: { tagline?: string; lang?: string }) {\n if (!tagline) return null;\n return (\n <span\n data-component=\"practice-result-tagline\"\n lang={lang}\n dir=\"auto\"\n className=\"type-body-sm ds:font-[var(--font-weight-medium)] ds:text-[var(--foreground)]\"\n >\n {tagline}\n </span>\n );\n}\n\n/** A BCP 47 tag for the review text, or undefined: \"it_IT\" becomes \"it-IT\"; empty or invalid writes no lang (an empty lang means \"unknown\", not \"page language\"). */\nfunction reviewLang(lang: unknown): string | undefined {\n if (typeof lang !== 'string') return undefined;\n const tag = lang.trim().replace(/_/g, '-');\n if (!tag) return undefined;\n try {\n return Intl.getCanonicalLocales(tag)[0] ? tag : undefined;\n } catch {\n return undefined;\n }\n}\n\n// The line is a flex row, so the label and the text are two flex items: each truncates in its own\n// direction (an Italian review on an Arabic page keeps its start visible). The trade-off is that\n// some screen readers may read the label and the text as two stops, not one sentence.\nfunction CardLatestReview({\n review,\n}: {\n review?: PracticeResult['latestReview'];\n}) {\n const { t } = useTranslation('ui');\n const text = typeof review?.text === 'string' ? review.text.trim() : '';\n if (!review || !text) return null;\n return (\n <p\n data-component=\"practice-result-latest-review\"\n className=\"type-body-sm ds:m-0 ds:flex ds:min-w-0 ds:gap-[var(--spacing-xs)] ds:text-[var(--muted-foreground)]\"\n >\n <span className=\"ds:shrink-0\">\n {t('practiceResults.card.latestReview')}\n </span>{' '}\n <span\n lang={reviewLang(review.lang)}\n dir=\"auto\"\n className=\"ds:min-w-0 ds:truncate\"\n >\n {text}\n </span>\n </p>\n );\n}\n\nconst cardDescriptionVariants = cva(\n 'type-body-sm ds:m-0 ds:text-[var(--muted-foreground)] ds:whitespace-pre-line ds:break-words',\n {\n variants: {\n density: {\n compact: 'ds:line-clamp-2',\n rich: 'ds:line-clamp-3',\n },\n },\n defaultVariants: { density: 'compact' },\n },\n);\n\nfunction CardDescription({\n description,\n density,\n lang,\n}: {\n description?: string;\n density: 'compact' | 'rich';\n lang?: string;\n}) {\n if (!description) return null;\n return (\n <p\n data-component=\"practice-result-description\"\n lang={lang}\n dir=\"auto\"\n className={cardDescriptionVariants({ density })}\n >\n {description}\n </p>\n );\n}\n\n/** One price convention for compact cards, rich cards and the phone sheet. */\nfunction formatFirstVisitPrice(\n price: NonNullable<PracticeResult['firstVisitPrice']>,\n currencyCode: string,\n locale: string,\n t: ReturnType<typeof useTranslation>['t'],\n): string {\n if (price.kind === 'fixed') {\n return formatPriceOrFree(\n price.minCents,\n currencyCode,\n locale,\n t('common.freeOfCharge'),\n );\n }\n const min = formatPrice(price.minCents, currencyCode, locale);\n if (\n price.kind === 'range' &&\n price.maxCents != null &&\n Number.isFinite(price.maxCents) &&\n price.maxCents > price.minCents\n ) {\n return t('practiceResults.card.priceRange', {\n min,\n max: formatPrice(price.maxCents, currencyCode, locale),\n });\n }\n return t('practiceResults.card.priceFrom', { price: min });\n}\n\nconst cardPriceLabelVariants = cva(\n 'ds:min-w-0 ds:max-w-full ds:[overflow-wrap:anywhere]',\n {\n variants: {\n practiceWritten: { true: 'ds:truncate' },\n },\n },\n);\n\n/* -------------------------------------------------------------------- */\n/* CardFirstVisitPrice (HTP-6136, extended by HTP-6752). */\n/* */\n/* MioDottore and Doctolib surface a price on the SERP card. AlfaDocs' */\n/* practice-level first-visit price has existed in settings since 2020 */\n/* and already appears on the detail page; the result card was the */\n/* missing link. */\n/* */\n/* The default label deliberately reuses ContactProfileCard's \"First */\n/* visit cost\" wording: practices opt in by filling the field, so it */\n/* imposes nothing on professions where a first visit makes no sense. */\n/* They can leave it empty or now supply their own price label. */\n/* */\n/* After rejecting an invalid structured minimum and falling back to */\n/* the older field, only null / undefined means no price and hides the */\n/* entire row. Zero is a real price: fixed zero reads \"Free\", while */\n/* from/range zero stays numeric. Use a nullish guard, never a falsy */\n/* check, so an unset price keeps the original card appearance. */\n/* -------------------------------------------------------------------- */\n\nfunction CardFirstVisitPrice({\n price,\n cents,\n currencyCode = 'EUR',\n}: {\n price?: PracticeResult['firstVisitPrice'];\n cents?: number;\n currencyCode?: string;\n}) {\n const { t, i18n } = useTranslation('ui');\n const resolvedPrice =\n price && Number.isFinite(price.minCents) ? price : undefined;\n const kind = resolvedPrice?.kind ?? 'fixed';\n const minCents = resolvedPrice?.minCents ?? cents;\n const maxCents = resolvedPrice?.maxCents;\n const formatted = useMemo(\n () =>\n minCents == null\n ? undefined\n : formatFirstVisitPrice(\n { kind, minCents, maxCents },\n currencyCode,\n i18n.language ?? 'en',\n t,\n ),\n [kind, minCents, maxCents, currencyCode, i18n.language, t],\n );\n if (formatted === undefined) return null;\n const practiceLabel = resolvedPrice?.label?.trim().replace(/:$/, '').trim();\n const label = practiceLabel || t('practiceResults.card.firstVisitPrice');\n return (\n <span\n data-component=\"practice-result-price\"\n className=\"type-body-sm ds:flex ds:min-w-0 ds:max-w-full ds:flex-col ds:items-start ds:gap-[var(--spacing-xs)] ds:text-start ds:text-[var(--muted-foreground)] ds:sm:@min-[280px]/result-heading:max-w-[45%] ds:sm:@min-[280px]/result-heading:items-end ds:sm:@min-[280px]/result-heading:text-end\"\n >\n <span\n dir=\"auto\"\n title={label}\n className={cardPriceLabelVariants({\n practiceWritten: Boolean(practiceLabel),\n })}\n >\n {label}\n </span>\n {/* Keep the label/value relationship in the reading order without\n repeating the visible text through an accessible-name override. */}\n <span className=\"ds:sr-only\">: </span>\n <Badge\n variant=\"accentSoft\"\n size=\"lg\"\n dir=\"auto\"\n className=\"ds:h-auto ds:min-h-6 ds:min-w-0 ds:max-w-full ds:whitespace-normal ds:[overflow-wrap:anywhere]\"\n >\n {formatted}\n </Badge>\n </span>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* NextSlotCaption — \"Next available {{time}}\" reading. */\n/* -------------------------------------------------------------------- */\n\nfunction NextSlotCaption({ time }: { time: string }) {\n const { t } = useTranslation('ui');\n return (\n <span className=\"type-body-sm ds:text-[var(--muted-foreground)] ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]\">\n <CalendarClock\n aria-hidden=\"true\"\n className=\"ds:size-4 ds:text-[var(--primary)]\"\n />\n {t('practiceResults.nextSlot.summary', { time })}\n </span>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* CardActions — Open (secondary) + always-visible Book (primary). */\n/* */\n/* 1.7.0: every card in every variant ends in the same two anchors. Open */\n/* goes to the practice page; Book goes to the booking flow — the */\n/* concrete slot when `nextAvailableSlot` is known, else `bookingHref`, */\n/* else `href`. Before 1.7.0 the booking CTA only existed on cards with */\n/* a known next slot (`practice-result-next-slot-cta`); that hook is */\n/* retired in favour of `practice-result-book-cta` + `data-slot-aware`. */\n/* Both are real anchors so middle-click / ctrl-click work natively. The */\n/* card-body sync handler ignores them while clicks bubble to the host. */\n/* Each anchor carries the practice name visually hidden, so a screen- */\n/* reader links list reads \"Book Studio Brera\" rather than N identical */\n/* \"Book\" entries; the visible label stays a prefix of the accessible */\n/* name (WCAG 2.5.3). */\n/* -------------------------------------------------------------------- */\n\ninterface CardActionsProps {\n result: PracticeResult;\n /** Localised \"Open\" CTA label; `undefined` renders no Open button. */\n openLabel: string | undefined;\n /** Localised \"Book\" CTA label — used when the result has no next slot. */\n bookLabel: string;\n /**\n * Pre-formatted next-slot time. When present the Book CTA reads the\n * slot-aware \"Book at {{time}}\" copy and links to the slot.\n */\n nextSlotText?: string;\n onOpenClick: () => void;\n onBookClick: () => void;\n}\n\nfunction CardActions({\n result,\n openLabel,\n bookLabel,\n nextSlotText,\n onOpenClick,\n onBookClick,\n}: CardActionsProps) {\n const { t } = useTranslation('ui');\n const slot = result.nextAvailableSlot;\n const bookHref = slot?.bookingHref ?? result.bookingHref ?? result.href;\n const resolvedBookLabel =\n slot && nextSlotText\n ? t('practiceResults.nextSlot.cta', { time: nextSlotText })\n : bookLabel;\n return (\n <div className=\"ds:mt-[var(--spacing-xs)] ds:flex ds:flex-wrap ds:items-center ds:justify-end ds:gap-[var(--spacing-xs)]\">\n {openLabel !== undefined ? (\n <Button intent=\"secondary\" size=\"sm\" asChild onClick={onOpenClick}>\n <a href={result.href} data-component=\"practice-result-open-cta\">\n {openLabel} <span className=\"ds:sr-only\">{result.name}</span>\n </a>\n </Button>\n ) : null}\n <Button\n intent=\"primary\"\n size=\"sm\"\n asChild\n startIcon={<CalendarPlus aria-hidden=\"true\" />}\n onClick={onBookClick}\n >\n <a\n href={bookHref}\n data-component=\"practice-result-book-cta\"\n data-slot-aware={slot ? 'true' : undefined}\n >\n {resolvedBookLabel} <span className=\"ds:sr-only\">{result.name}</span>\n </a>\n </Button>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* ResultCardBase */\n/* */\n/* Compact card used by `split-list-map` left list and `map-first` rail. */\n/* Avatar + name + subtitle + tagline + rating + optional chip row + */\n/* description + first-visit price + next-slot caption + Open / Book */\n/* CTAs. */\n/* -------------------------------------------------------------------- */\n\nconst resultCardBaseVariants = cva(\n [\n 'ds:@container/result-card ds:relative ds:flex ds:w-full ds:cursor-pointer',\n 'ds:rounded-[var(--radius-lg)]',\n 'ds:bg-[var(--card)] ds:text-[var(--card-foreground)]',\n 'ds:border ds:border-[color:var(--card-border)]',\n 'ds:p-[var(--spacing-md)]',\n 'ds:gap-[var(--spacing-md)]',\n 'ds:transition-[box-shadow,border-color] ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none',\n 'ds:hover:shadow-[var(--shadow-md)]',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-[color:var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n // Use `data-highlighted` for the pin↔card sync visual so consumers\n // can override (e.g. dim siblings) without fighting the kit.\n 'ds:data-[highlighted=true]:border-[color:var(--primary)]',\n 'ds:data-[highlighted=true]:shadow-[var(--shadow-md)]',\n // HTP-7323: the card chosen by a pin click. Stronger than the hover\n // highlight: a brand border (one extra ring, no layout shift), a tinted\n // surface and the \"Selected on the map\" tag.\n 'ds:data-[selected=true]:border-[color:var(--primary)]',\n 'ds:data-[selected=true]:bg-[var(--primary-soft)]',\n 'ds:data-[selected=true]:shadow-[0_0_0_1px_var(--primary),var(--shadow-md)]',\n // Extra room above the card when a pin click scrolls it to the top, for a\n // sticky header other than PublicHeader (which already sets the page's\n // `scroll-padding-block-start`). Default 0, so nothing is counted twice.\n 'ds:[scroll-margin-block-start:var(--practice-results-scroll-offset,0)]',\n 'ds:forced-colors:focus-visible:outline-[CanvasText]',\n ].join(' '),\n {\n variants: {\n density: {\n compact: '',\n rich: 'ds:p-[var(--spacing-lg)] ds:gap-[var(--spacing-lg)]',\n },\n },\n defaultVariants: { density: 'compact' },\n },\n);\n\nconst resultNameLinkVariants = cva([\n 'ds:text-[var(--foreground)] ds:no-underline ds:hover:underline',\n 'ds:rounded-[var(--radius-xs)]',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-[color:var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:forced-colors:focus-visible:outline-[CanvasText]',\n]);\n\nfunction ResultNameHeading({\n result,\n headingLevel,\n}: {\n result: PracticeResult;\n headingLevel: PracticeResultsHeadingLevel;\n}) {\n const Heading = `h${nextHeadingLevel(headingLevel)}` as const;\n return (\n <Heading\n id={`practice-result-${result.id}-name`}\n dir=\"auto\"\n className=\"type-title-card ds:m-0 ds:min-w-0 ds:[overflow-wrap:anywhere] ds:text-[var(--foreground)]\"\n >\n <a\n href={result.href}\n dir=\"auto\"\n data-component=\"practice-result-name-link\"\n className={resultNameLinkVariants()}\n >\n {result.name}\n </a>\n </Heading>\n );\n}\n\n/** Size the price against the available heading space, including narrow map rails. */\nfunction CardHeading({\n result,\n headingLevel,\n selected = false,\n}: {\n result: PracticeResult;\n headingLevel: PracticeResultsHeadingLevel;\n /** HTP-7323: show the \"Selected on the map\" tag. */\n selected?: boolean;\n}) {\n const { t } = useTranslation('ui');\n return (\n <div\n data-component=\"practice-result-heading\"\n className=\"ds:@container/result-heading ds:min-w-0 ds:flex-1\"\n >\n <div className=\"ds:flex ds:min-w-0 ds:flex-col ds:items-start ds:gap-[var(--spacing-xs)] ds:sm:@min-[280px]/result-heading:flex-row ds:sm:@min-[280px]/result-heading:gap-[var(--spacing-md)]\">\n <div className=\"ds:flex ds:min-w-0 ds:w-full ds:flex-1 ds:flex-col ds:gap-[var(--spacing-2xs)]\">\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-xs)]\">\n <ResultNameHeading result={result} headingLevel={headingLevel} />\n {result.location?.distanceKm != null ? (\n <DistanceChip distanceKm={result.location.distanceKm} />\n ) : null}\n {selected ? (\n <Badge\n variant=\"brand\"\n data-component=\"practice-result-selected-tag\"\n leading={<MapPin aria-hidden=\"true\" />}\n >\n {t('practiceResults.selectedOnMap')}\n </Badge>\n ) : null}\n </div>\n {result.subtitle ? (\n <span className=\"type-body-sm ds:text-[var(--muted-foreground)]\">\n {result.subtitle}\n </span>\n ) : null}\n <CardTagline tagline={result.tagline} lang={result.contentLang} />\n {result.location?.address ? (\n <span\n data-component=\"practice-result-address\"\n className=\"type-meta ds:text-[var(--muted-foreground)] ds:inline-flex ds:items-center ds:gap-[var(--spacing-2xs)]\"\n >\n <MapPin aria-hidden=\"true\" className=\"ds:size-3.5 ds:shrink-0\" />\n <span\n lang={result.contentLang}\n dir=\"auto\"\n className=\"ds:min-w-0 ds:[overflow-wrap:anywhere]\"\n >\n {result.location.address}\n </span>\n </span>\n ) : null}\n </div>\n <CardFirstVisitPrice\n price={result.firstVisitPrice}\n cents={result.firstVisitPriceCents}\n currencyCode={result.currencyCode}\n />\n </div>\n </div>\n );\n}\n\n/** Leave descendant controls to the browser and host click listeners. */\nfunction isInteractiveCardClick(event: React.MouseEvent<HTMLElement>): boolean {\n const interactive = (event.target as Element).closest(\n 'a, button, [role=\"button\"], input, select, textarea, label',\n );\n return (\n interactive !== null &&\n interactive !== event.currentTarget &&\n event.currentTarget.contains(interactive)\n );\n}\n\n/**\n * The visual hover state (map mini card, hover pin) follows a pointer only\n * where the device can hover. A tap on a touch screen fires `mouseenter` and\n * no `mouseleave`, which would leave the map card up until the next tap. The\n * telemetry trigger is not gated: it fires on every `mouseenter`, as before.\n */\nfunction canHover(): boolean {\n return !(\n typeof window.matchMedia === 'function' &&\n !window.matchMedia('(hover: hover)').matches\n );\n}\n\n/**\n * Mouse left the card. Focus drives the visual state only when it is\n * keyboard-style (`isFocusVisible`): a mouse click on the name link, Open or\n * Book leaves focus in the card, and that must not keep the map card up after\n * the pointer has left. Keep the visual hover only while keyboard-style focus\n * (`:focus-visible`) is still inside: after a mouse click or a Cmd-click on a\n * link focus stays in the card, but the pointer has gone, so the card goes.\n */\nfunction leaveOnMouse(onLeave?: () => void) {\n if (!onLeave) return undefined;\n return (event: React.MouseEvent<HTMLElement>): void => {\n const active = document.activeElement;\n if (\n active &&\n event.currentTarget.contains(active) &&\n isFocusVisible(active)\n ) {\n return;\n }\n onLeave();\n };\n}\n\n/** Focus moved out of the card (not to something inside it). */\nfunction leaveOnBlur(onLeave?: () => void) {\n if (!onLeave) return undefined;\n return (event: React.FocusEvent<HTMLElement>): void => {\n if (event.currentTarget.contains(event.relatedTarget as Node | null))\n return;\n onLeave();\n };\n}\n\ninterface ResultCardBaseProps {\n result: PracticeResult;\n headingLevel: PracticeResultsHeadingLevel;\n highlighted: boolean;\n /** HTP-7323: chosen by a pin click. Strong highlight + tag. */\n selected?: boolean;\n /**\n * Card-body click handler. Fires `onResultSelect(result, 'card')` —\n * sync-only by the kit's contract. The consumer typically no-ops or\n * augments with analytics; do NOT navigate from this source.\n */\n onSelect: () => void;\n /**\n * Open-button click handler. Fires `onResultSelect(result,\n * 'open-button')` — the navigation primitive. The button is a real\n * anchor so middle-click / ctrl-click \"open in new tab\" work\n * natively.\n */\n onOpenClick: () => void;\n /** `visual` is false for a touch `mouseenter` or a focus that is not keyboard-style: telemetry only. */\n onHover: (visual: boolean) => void;\n /** Pointer or focus left the card (HTP-7322: hides the map mini card). */\n onLeave?: () => void;\n /** Used for layout density (compact vs rich). */\n density?: 'compact' | 'rich';\n /** Localised \"Open\" CTA label; `undefined` renders no Open button. */\n openLabel: string | undefined;\n /** Localised \"Book\" CTA label (used when the result has no next slot). */\n bookLabel: string;\n /**\n * Book-button click handler. Fires `onResultSelect(result,\n * 'book-button')`. Like Open, the button is a real anchor.\n */\n onBookClick: () => void;\n /**\n * Pre-formatted next-available-slot text — when present, the kit\n * renders a \"Next available …\" caption and the Book CTA deep-links to\n * the slot. The string is locale-formatted by the parent (which has\n * access to `i18n.language`).\n */\n nextSlotText?: string;\n /** Truncate the insurance chip row at this many entries. */\n maxInsuranceChips?: number;\n /** Truncate the language chip row at this many entries. */\n maxLanguageChips?: number;\n children?: ReactNode;\n}\n\nfunction ResultCardBase({\n result,\n headingLevel,\n highlighted,\n selected = false,\n onSelect,\n onOpenClick,\n onHover,\n onLeave,\n density = 'compact',\n openLabel,\n bookLabel,\n onBookClick,\n nextSlotText,\n maxInsuranceChips = 3,\n maxLanguageChips = 4,\n children,\n}: ResultCardBaseProps) {\n const { t, i18n } = useTranslation('ui');\n const ratingLabel = result.rating;\n const showLanguages = (result.languages?.length ?? 0) > 0;\n const showInsurances = (result.insurances?.length ?? 0) > 0;\n const showFearPatient = result.specializedInFearPatients;\n const showChipRow = showLanguages || showInsurances || showFearPatient;\n const languageChips = (result.languages ?? []).slice(0, maxLanguageChips);\n const languageOverflow =\n (result.languages?.length ?? 0) - languageChips.length;\n const insuranceChips = (result.insurances ?? []).slice(0, maxInsuranceChips);\n const insuranceOverflow =\n (result.insurances?.length ?? 0) - insuranceChips.length;\n // `role=\"group\"` — the card is a discoverable SR landmark grouping\n // the avatar / name / chips / CTAs. It is NOT a single interactive\n // control: axe's `nested-interactive` forbids focusable descendants\n // (the inner Open / Book anchors) inside an interactive parent.\n //\n // Mouse users get a click handler on the outer container — clicking\n // anywhere except an interactive descendant\n // triggers the sync action (pan the map to the matching pin).\n // Keyboard users tab through the inner anchors directly; the Open\n // button is the canonical kbd path. This matches Doctolib's card\n // pattern.\n //\n // jsx-a11y/no-noninteractive-element-interactions + click-events-\n // have-key-events disabled here: the card is deliberately non-\n // interactive at the SR layer (role=\"group\") to avoid axe's\n // nested-interactive violation against the inner Open / Book\n // anchors. The onClick is a mouse-only sync affordance; keyboard\n // users get equivalent navigation via the inner anchors.\n return (\n // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events -- card is role=\"group\" by design (axe nested-interactive); click handler is mouse-only sync.\n <div\n data-component=\"practice-result-card\"\n data-result-id={result.id}\n data-highlighted={highlighted ? 'true' : 'false'}\n data-selected={selected ? 'true' : 'false'}\n role=\"group\"\n aria-labelledby={`practice-result-${result.id}-name`}\n onClick={(event) => {\n if (isInteractiveCardClick(event)) return;\n onSelect();\n }}\n onMouseEnter={() => onHover(canHover())}\n onFocus={(event) => onHover(isFocusVisible(event.target))}\n onMouseLeave={leaveOnMouse(onLeave)}\n onBlur={leaveOnBlur(onLeave)}\n className={resultCardBaseVariants({ density })}\n >\n {/* HTP-6712: the practice photo, 4:3 and rounded, at the old height. */}\n <Avatar\n src={result.imageUrl}\n alt={result.imageAlt}\n name={result.name}\n size=\"lg\"\n shape=\"rounded\"\n ratio=\"4:3\"\n width={64}\n height={48}\n />\n <div className=\"ds:flex ds:min-w-0 ds:flex-1 ds:flex-col ds:gap-[var(--spacing-xs)]\">\n <CardHeading\n result={result}\n headingLevel={headingLevel}\n selected={selected}\n />\n {ratingLabel && ratingLabel.count > 0 ? (\n <Rating\n value={ratingLabel.value}\n reviews={{ count: ratingLabel.count }}\n size=\"sm\"\n />\n ) : null}\n <CardLatestReview review={result.latestReview} />\n {showChipRow ? (\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-xs)]\">\n {languageChips.map((lang) => (\n <Badge\n key={`lang-${lang}`}\n variant=\"neutral\"\n leading={<Languages aria-hidden=\"true\" />}\n >\n {resolveLanguageLabel(lang, t, i18n)}\n </Badge>\n ))}\n {languageOverflow > 0 ? (\n <Badge variant=\"neutral\">{`+${languageOverflow}`}</Badge>\n ) : null}\n {insuranceChips.map((ins) => (\n <Badge\n key={`ins-${ins}`}\n variant=\"info\"\n leading={<ShieldCheck aria-hidden=\"true\" />}\n >\n {ins}\n </Badge>\n ))}\n {insuranceOverflow > 0 ? (\n <Badge variant=\"neutral\">{`+${insuranceOverflow}`}</Badge>\n ) : null}\n {showFearPatient ? (\n <Badge variant=\"info\" leading={<Heart aria-hidden=\"true\" />}>\n {t('practiceResults.fearPatient.label')}\n </Badge>\n ) : null}\n </div>\n ) : null}\n <CardDescription\n description={result.description}\n density=\"compact\"\n lang={result.contentLang}\n />\n {result.nextAvailableSlot && nextSlotText ? (\n <NextSlotCaption time={nextSlotText} />\n ) : null}\n {children}\n <CardActions\n result={result}\n openLabel={openLabel}\n bookLabel={bookLabel}\n nextSlotText={nextSlotText}\n onOpenClick={onOpenClick}\n onBookClick={onBookClick}\n />\n </div>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* RichResultCard */\n/* */\n/* Full-fat card for `rich-cards`. Thumbnail map + distance + language + */\n/* insurance chips + anxious-patient badge + tagline / description + */\n/* first-visit price + next-slot caption + Open / Book CTAs. */\n/* -------------------------------------------------------------------- */\n\ninterface RichResultCardProps {\n result: PracticeResult;\n headingLevel: PracticeResultsHeadingLevel;\n apiKey?: string;\n highlighted: boolean;\n selected?: boolean;\n onSelect: () => void;\n onOpenClick: () => void;\n onBookClick: () => void;\n /** `visual` is false for a touch `mouseenter` or a focus that is not keyboard-style: telemetry only. */\n onHover: (visual: boolean) => void;\n onLeave?: () => void;\n /** Localised \"Open\" CTA label; `undefined` renders no Open button. */\n openLabel: string | undefined;\n bookLabel: string;\n}\n\nfunction RichResultCard({\n result,\n headingLevel,\n apiKey,\n highlighted,\n selected = false,\n onSelect,\n onOpenClick,\n onBookClick,\n onHover,\n onLeave,\n openLabel,\n bookLabel,\n}: RichResultCardProps) {\n const { t, i18n } = useTranslation('ui');\n const slotLabel = useMemo(() => {\n if (!result.nextAvailableSlot) return undefined;\n try {\n return new Intl.DateTimeFormat(i18n.language, {\n weekday: 'short',\n hour: 'numeric',\n minute: 'numeric',\n }).format(new Date(result.nextAvailableSlot.dateTime));\n } catch {\n return result.nextAvailableSlot.dateTime;\n }\n }, [result.nextAvailableSlot, i18n.language]);\n const showLanguages = (result.languages?.length ?? 0) > 0;\n const showInsurances = (result.insurances?.length ?? 0) > 0;\n const showFearPatient = result.specializedInFearPatients;\n const showChipRow = showLanguages || showInsurances || showFearPatient;\n\n return (\n // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events -- card is role=\"group\" by design (axe nested-interactive); click handler is mouse-only sync.\n <div\n data-component=\"practice-result-card\"\n data-result-id={result.id}\n data-density=\"rich\"\n data-highlighted={highlighted ? 'true' : 'false'}\n data-selected={selected ? 'true' : 'false'}\n role=\"group\"\n aria-labelledby={`practice-result-${result.id}-name`}\n onClick={(event) => {\n if (isInteractiveCardClick(event)) return;\n onSelect();\n }}\n onMouseEnter={() => onHover(canHover())}\n onFocus={(event) => onHover(isFocusVisible(event.target))}\n onMouseLeave={leaveOnMouse(onLeave)}\n onBlur={leaveOnBlur(onLeave)}\n className={resultCardBaseVariants({ density: 'rich' })}\n >\n {result.location ? (\n <div className=\"ds:hidden ds:@min-[640px]/result-card:block\">\n <StaticMapThumb\n image={result.mapImage}\n apiKey={apiKey}\n center={{\n lat: result.location.lat,\n lng: result.location.lng,\n }}\n address={result.location.address}\n addressLang={result.contentLang}\n />\n </div>\n ) : null}\n <div className=\"ds:flex ds:min-w-0 ds:flex-1 ds:flex-col ds:gap-[var(--spacing-sm)]\">\n <div className=\"ds:flex ds:items-start ds:gap-[var(--spacing-md)]\">\n <Avatar\n src={result.imageUrl}\n alt={result.imageAlt}\n name={result.name}\n size=\"md\"\n shape=\"rounded\"\n ratio=\"4:3\"\n width={53}\n height={40}\n />\n <CardHeading\n result={result}\n headingLevel={headingLevel}\n selected={selected}\n />\n </div>\n {result.rating && result.rating.count > 0 ? (\n <Rating\n value={result.rating.value}\n reviews={{ count: result.rating.count }}\n size=\"md\"\n />\n ) : null}\n <CardLatestReview review={result.latestReview} />\n {showChipRow ? (\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-xs)]\">\n {showLanguages\n ? (result.languages ?? []).map((lang) => (\n <Badge\n key={`lang-${lang}`}\n variant=\"neutral\"\n leading={<Languages aria-hidden=\"true\" />}\n >\n {resolveLanguageLabel(lang, t, i18n)}\n </Badge>\n ))\n : null}\n {showInsurances\n ? (result.insurances ?? []).map((ins) => (\n <Badge\n key={`ins-${ins}`}\n variant=\"info\"\n leading={<ShieldCheck aria-hidden=\"true\" />}\n >\n {ins}\n </Badge>\n ))\n : null}\n {showFearPatient ? (\n <Badge variant=\"info\" leading={<Heart aria-hidden=\"true\" />}>\n {t('practiceResults.fearPatient.label')}\n </Badge>\n ) : null}\n </div>\n ) : null}\n <CardDescription\n description={result.description}\n density=\"rich\"\n lang={result.contentLang}\n />\n {result.nextAvailableSlot && slotLabel ? (\n <NextSlotCaption time={slotLabel} />\n ) : null}\n <CardActions\n result={result}\n openLabel={openLabel}\n bookLabel={bookLabel}\n nextSlotText={slotLabel}\n onOpenClick={onOpenClick}\n onBookClick={onBookClick}\n />\n </div>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* ResultsList — scroll container + infinite-scroll observer + skeletons. */\n/* -------------------------------------------------------------------- */\n\ninterface ResultsListProps {\n results: PracticeResult[];\n headingLevel: PracticeResultsHeadingLevel;\n /** Card under the pointer or focus (HTP-7322). */\n hoveredId: string | null;\n /** Card chosen by a pin click (HTP-7323). */\n selectedId: string | null;\n /** Light highlight with no tag: a phone pin click, or the imperative handle. */\n softId: string | null;\n onCardHover: (id: string, visual: boolean) => void;\n onCardLeave: () => void;\n onCardSelect: (id: string) => void;\n onCardOpenClick: (id: string) => void;\n onCardBookClick: (id: string) => void;\n /** Localised \"Open\" CTA label; `undefined` renders no Open button. */\n openLabel: string | undefined;\n bookLabel: string;\n /**\n * Formatter for the next-available-slot text — locale-formatted by\n * the variant body (which has `i18n.language` in scope). Returns\n * undefined when the result has no slot.\n */\n formatNextSlot: (result: PracticeResult) => string | undefined;\n onLoadMore?: () => void;\n loadingMore?: boolean;\n loadingSkeletons?: boolean;\n density?: 'compact' | 'rich';\n apiKey?: string;\n /** Ref to the scrollable container so the parent can imperatively\n * scroll a card into view (handle.scrollToResult). */\n containerRef?: React.RefObject<HTMLDivElement | null>;\n}\n\nfunction ResultsList({\n results,\n headingLevel,\n hoveredId,\n selectedId,\n softId,\n onCardHover,\n onCardLeave,\n onCardSelect,\n onCardOpenClick,\n onCardBookClick,\n openLabel,\n bookLabel,\n formatNextSlot,\n onLoadMore,\n loadingMore,\n loadingSkeletons,\n density = 'compact',\n apiKey,\n containerRef,\n}: ResultsListProps) {\n const { t } = useTranslation('ui');\n const sentinelRef = useRef<HTMLDivElement | null>(null);\n\n // IntersectionObserver-driven `onLoadMore`. The kit fires once per\n // \"intersection\" — the consumer is responsible for guarding against\n // double-fires while a request is in-flight (`loadingMore`).\n useEffect(() => {\n if (!onLoadMore) return;\n const node = sentinelRef.current;\n if (!node) return;\n if (typeof IntersectionObserver === 'undefined') return;\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n track('serp_load_more', {\n currentCount: results.length,\n });\n onLoadMore();\n }\n }\n },\n { root: null, rootMargin: '200px' },\n );\n observer.observe(node);\n return () => observer.disconnect();\n // `results.length` is a snapshot at observe-time — re-bind so the\n // telemetry payload reflects the count at firing.\n }, [onLoadMore, results.length]);\n\n if (loadingSkeletons) {\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-label={t('practiceResults.loading')}\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\"\n >\n {Array.from({ length: 4 }).map((_, i) => (\n <Skeleton\n key={`pr-skel-${i}`}\n variant=\"rounded\"\n height={density === 'rich' ? '11rem' : '7rem'}\n />\n ))}\n </div>\n );\n }\n\n return (\n <div\n ref={containerRef}\n data-component=\"practice-results-list\"\n role=\"list\"\n aria-label={t('practiceResults.list.label')}\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)] ds:overflow-y-auto\"\n >\n {results.map((result) => (\n <div key={result.id} role=\"listitem\">\n {density === 'rich' ? (\n <RichResultCard\n result={result}\n headingLevel={headingLevel}\n apiKey={apiKey}\n highlighted={\n hoveredId === result.id ||\n selectedId === result.id ||\n softId === result.id\n }\n selected={selectedId === result.id}\n onSelect={() => onCardSelect(result.id)}\n onOpenClick={() => onCardOpenClick(result.id)}\n onBookClick={() => onCardBookClick(result.id)}\n onHover={(visual) => onCardHover(result.id, visual)}\n onLeave={onCardLeave}\n openLabel={openLabel}\n bookLabel={bookLabel}\n />\n ) : (\n <ResultCardBase\n result={result}\n headingLevel={headingLevel}\n highlighted={\n hoveredId === result.id ||\n selectedId === result.id ||\n softId === result.id\n }\n selected={selectedId === result.id}\n onSelect={() => onCardSelect(result.id)}\n onOpenClick={() => onCardOpenClick(result.id)}\n onBookClick={() => onCardBookClick(result.id)}\n onHover={(visual) => onCardHover(result.id, visual)}\n onLeave={onCardLeave}\n openLabel={openLabel}\n bookLabel={bookLabel}\n nextSlotText={formatNextSlot(result)}\n />\n )}\n </div>\n ))}\n {onLoadMore ? (\n <div\n ref={sentinelRef}\n aria-hidden=\"true\"\n className=\"ds:flex ds:items-center ds:justify-center ds:py-[var(--spacing-md)]\"\n >\n {loadingMore ? (\n <Spinner size=\"md\" variant=\"pulse\" />\n ) : (\n <span className=\"ds:size-1\" />\n )}\n </div>\n ) : null}\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* MapMiniCard — the small card above a hovered pin (HTP-7322). */\n/* Photo, name, rating, next free time, all from the result. Purely */\n/* visual: MapView hides it from assistive technology and the tab order. */\n/* -------------------------------------------------------------------- */\n\nexport function MapMiniCard({\n result,\n nextSlotText,\n}: {\n result: PracticeResult;\n nextSlotText?: string;\n}) {\n const rating = result.rating;\n return (\n <div\n data-component=\"practice-result-map-card\"\n className=\"ds:flex ds:[inline-size:16rem] ds:max-w-full ds:items-center ds:gap-[var(--spacing-sm)] ds:rounded-[var(--radius-lg)] ds:border ds:border-[color:var(--card-border)] ds:bg-[var(--card)] ds:p-[var(--spacing-sm)] ds:text-[var(--card-foreground)] ds:shadow-[var(--shadow-lg)]\"\n >\n <Avatar\n src={result.imageUrl}\n name={result.name}\n size=\"lg\"\n shape=\"rounded\"\n ratio=\"4:3\"\n width={64}\n height={48}\n />\n <div className=\"ds:flex ds:min-w-0 ds:flex-1 ds:flex-col ds:gap-[var(--spacing-2xs)]\">\n <span\n dir=\"auto\"\n className=\"type-title-card ds:min-w-0 ds:[overflow-wrap:anywhere] ds:text-[var(--foreground)]\"\n >\n {result.name}\n </span>\n {rating && rating.count > 0 ? (\n <Rating\n value={rating.value}\n reviews={{ count: rating.count }}\n size=\"sm\"\n />\n ) : null}\n {result.nextAvailableSlot && nextSlotText ? (\n <NextSlotCaption time={nextSlotText} />\n ) : null}\n </div>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* MapPanel — wraps MapView with markers + bounds-change tracking. */\n/* -------------------------------------------------------------------- */\n\ninterface MapPanelProps {\n results: PracticeResult[];\n searchCentre?: { lat: number; lng: number };\n zoom: number;\n apiKey?: string;\n mapId?: string;\n clusterMarkers?: boolean | 'auto';\n /** Pin that stands out, with its mini card (HTP-7322). */\n hoveredId: string | null;\n /** Pin chosen by a click (HTP-7323). */\n selectedId: string | null;\n /** Map background click. */\n onMapClick: () => void;\n /**\n * Draw the mini card above the hovered pin (HTP-7322). On at `lg` and up;\n * off in the phone sheet, where nothing hovers.\n */\n showMarkerCard?: boolean;\n /**\n * Locale-aware next-slot formatter for the mini card. Without it, or\n * without a slot, the card has no next-free-time line.\n */\n formatNextSlot?: (result: PracticeResult) => string | undefined;\n /**\n * The pointer or keyboard focus moved onto or off a pin. Drives the hover\n * state, so the pin's mini card shows as for a hovered result card.\n */\n onPinHoverChange?: MapViewProps['onMarkerHover'];\n onPinClick: (id: string) => void;\n onPinHover: (id: string) => void;\n /**\n * Fires once per pin per session (de-duped at the root). Use to emit\n * the `serp_result_hovered_pin` telemetry without re-firing on every\n * click of the same pin. The kit's MapView wrapper only surfaces\n * click events from vis.gl AdvancedMarker (no native hover), so this\n * approximates \"the patient noticed a pin\" rather than a true mouse\n * hover.\n */\n onPinFirstInteract: (id: string) => void;\n onBoundsChange?: (bounds: PracticeResultsBounds) => void;\n consentGranted?: boolean;\n onConsentRequest?: () => void;\n /** The host's map image until the interactive map may load (HTP-7348). */\n staticPreview?: MapViewStaticPreview;\n /**\n * Forwards the embedded MapView's imperative handle to the root so\n * card-body clicks can pan the map to the matching pin. Pass a\n * mutable ref from the variant body.\n */\n mapHandleRef?: React.MutableRefObject<MapViewHandle | null>;\n}\n\nfunction MapPanel({\n results,\n searchCentre,\n zoom,\n apiKey,\n mapId,\n clusterMarkers,\n hoveredId,\n selectedId,\n onMapClick,\n showMarkerCard = false,\n formatNextSlot,\n onPinHoverChange,\n onPinClick,\n onPinHover,\n onPinFirstInteract,\n onBoundsChange,\n consentGranted,\n onConsentRequest,\n staticPreview,\n mapHandleRef,\n}: MapPanelProps) {\n const { t } = useTranslation('ui');\n // Filter to results that carry a location — pins-without-coordinates\n // would land at 0,0.\n const markers = useMemo<MapMarker[]>(\n () =>\n results\n .filter(\n (\n result,\n ): result is PracticeResult & {\n location: { lat: number; lng: number };\n } => Boolean(result.location),\n )\n .map((result) => ({\n id: result.id,\n position: {\n lat: result.location.lat,\n lng: result.location.lng,\n },\n label: result.name,\n })),\n [results],\n );\n\n // The map opens on every listed practice (HTP-6705): MapView fits its\n // camera to the pins once the map has loaded, capped at zoom 15, and\n // refits only when the SET of listed practices changes. The key is the\n // sorted ids, so a sort alone never refits and a map the patient has\n // moved stays put until a filter changes the list.\n // Each point carries its result id: two practices can share an\n // address, and \"only more results\" is decided by id, not coordinates.\n const fitPoints = useMemo(\n () => markers.map((marker) => ({ ...marker.position, id: marker.id })),\n [markers],\n );\n const fitKey = useMemo(\n () =>\n markers\n .map((marker) => marker.id)\n .sort()\n .join('|'),\n [markers],\n );\n\n // Starting view, and the view with no results to fit: prefer the\n // explicit `searchCentre`; otherwise the centre of the results' box;\n // otherwise a safe default (Milano).\n const center = useMemo(() => {\n if (searchCentre) return searchCentre;\n const bounds = boundsForResults(results);\n if (bounds) {\n return {\n lat: (bounds.north + bounds.south) / 2,\n lng: (bounds.east + bounds.west) / 2,\n };\n }\n return { lat: 45.4642, lng: 9.19 };\n }, [results, searchCentre]);\n\n // HTP-7322: hovering a result card or a pin shows a small card above the\n // pin. It repeats the list card, so MapView hides it from screen readers.\n // The next-free-time line needs a formatter and a slot; the card does not.\n const renderMarkerCard = useMemo(\n () =>\n showMarkerCard\n ? (markerId: string) => {\n const result = results.find((r) => r.id === markerId);\n return result ? (\n <MapMiniCard\n result={result}\n nextSlotText={formatNextSlot?.(result)}\n />\n ) : null;\n }\n : undefined,\n [showMarkerCard, formatNextSlot, results],\n );\n\n const handleBoundsChange = useCallback<\n NonNullable<MapViewProps['onBoundsChange']>\n >(\n (bounds, { userInteraction }) => {\n onBoundsChange?.(bounds);\n if (userInteraction) track('serp_map_panned', { bounds });\n },\n [onBoundsChange],\n );\n\n // Pin hover and keyboard focus reach the root through `onMarkerHover`\n // (HTP-7322). `focusPin` stays the imperative way to highlight a pin\n // from a deep link or an agent.\n //\n // TODO(`data-component=\"practice-result-pin\"`): The 0.31.0 prompt asks\n // for a per-pin `data-component` marker so agents / consumers can\n // target individual pins. The MapView's AdvancedMarker wrapper\n // doesn't currently expose a custom-attribute prop — adding it\n // requires changes to `src/components/map-view/map-view.tsx`. Until\n // that lands, agents use `handle.focusPin(id)` for pin-level\n // imperative access.\n return (\n <div\n data-component=\"practice-results-map-panel\"\n className=\"ds:relative ds:flex ds:size-full ds:min-h-[24rem] ds:flex-col\"\n >\n <MapView\n ref={mapHandleRef}\n apiKey={apiKey ?? ''}\n mapId={mapId}\n clusterMarkers={clusterMarkers}\n center={center}\n zoom={zoom}\n markers={markers}\n fitToPoints={fitPoints}\n fitKey={fitKey}\n selectedMarkerId={selectedId}\n hoveredMarkerId={hoveredId}\n renderMarkerCard={renderMarkerCard}\n onMarkerHover={showMarkerCard ? onPinHoverChange : undefined}\n consentGranted={Boolean(consentGranted)}\n staticPreview={staticPreview}\n onConsentRequest={onConsentRequest}\n onBoundsChange={handleBoundsChange}\n onClusterClick={({ count }) =>\n track('serp_map_cluster_clicked', { count })\n }\n onMarkerClick={(id) => {\n if (id == null) {\n onMapClick();\n return;\n }\n onPinHover(id);\n onPinFirstInteract(id);\n onPinClick(id);\n }}\n ariaLabel={t('practiceResults.map.label')}\n // `size=\"fill\"` lets MapView absorb the height of the\n // viewport-locked map column instead of capping at the\n // `size=\"lg\"` 28-rem inner. Without this the map-first\n // variant rendered a muted-grey gap below the Google Maps\n // canvas (parent column 737 px tall, MapView inner stuck at\n // 448 px). split-list-map's column hits the same bug at\n // smaller scale (1040→640 px clip on `lg:max-h-[40rem]`).\n size=\"fill\"\n surface=\"bordered\"\n className=\"ds:size-full\"\n />\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* SplitListMapBody — desktop two-column, mobile list + \"Vedi mappa\". */\n/* -------------------------------------------------------------------- */\n\ninterface VariantBodyProps {\n results: PracticeResult[];\n totalCount?: number;\n searchCentre?: PracticeResultsProps['searchCentre'];\n facets: ResolvedFacetGroup[];\n value: PracticeResultsValue;\n sortOptions: readonly PracticeResultsSort[];\n onChange: (next: PracticeResultsValue) => void;\n onResultSelect: PracticeResultsProps['onResultSelect'];\n onLoadMore?: PracticeResultsProps['onLoadMore'];\n loadingMore?: boolean;\n apiKey?: string;\n mapId?: string;\n clusterMarkers?: boolean | 'auto';\n zoom: number;\n consentGranted?: boolean;\n onConsentRequest?: () => void;\n /** The host's map image until the interactive map may load (HTP-7348). */\n mapPreview?: MapViewStaticPreview;\n onBoundsChange?: PracticeResultsProps['onBoundsChange'];\n loadingSkeletons: boolean;\n emptyResultsSlot?: ReactNode;\n errorSlot?: ReactNode;\n error?: boolean;\n onRetry?: () => void;\n heading?: string;\n headingLevel: PracticeResultsHeadingLevel;\n intro?: string;\n /** Hover state: the card under the pointer or focus (HTP-7322). */\n hoveredId: string | null;\n /** Selection state: the card chosen by a pin click (HTP-7323). */\n selectedId: string | null;\n /** Light highlight with no tag (phone pin click, imperative handle). */\n softId: string | null;\n /**\n * What the map shows. At `lg` and up: the hovered pin and the chosen pin.\n * Below it, as before this feature: no hover pin, and the last highlighted\n * card's pin as `data-selected`.\n */\n mapHoveredId: string | null;\n mapSelectedId: string | null;\n onCardHover: (id: string, visual: boolean) => void;\n onCardLeave: () => void;\n /** Hover telemetry for a pin click. Does not set hover state. */\n onPinHover: (id: string) => void;\n /**\n * The pointer or keyboard focus moved onto or off a pin (HTP-7322). Sets\n * the hover state at `lg` and up, by the same rules as a card hover.\n */\n onPinHoverChange: NonNullable<MapViewProps['onMarkerHover']>;\n /** Map background click: clears the pin selection. */\n onMapClick: () => void;\n onPinClick: (id: string) => void;\n onPinFirstInteract: (id: string) => void;\n /**\n * Card-body click: sync (pan map to pin if a map is mounted) +\n * emit `serp_result_clicked` source='card'. Does NOT navigate.\n */\n onCardSelect: (id: string) => void;\n /**\n * Open-button click: emit `serp_result_clicked` source='open-button'.\n * The button is a real `<a href>` so middle-click works; navigation\n * is the consumer's responsibility (typically via window.location).\n */\n onCardOpenClick: (id: string) => void;\n /**\n * Book-button click: emit `serp_result_clicked` source='book-button'.\n * Same anchor semantics as Open; the href is the slot deep-link when\n * known, else `bookingHref`, else `href`.\n */\n onCardBookClick: (id: string) => void;\n /** Localised \"Open\" CTA label, resolved once at the root; `undefined` renders no Open button. */\n openLabel: string | undefined;\n /** Localised \"Book\" CTA label, resolved once at the root. */\n bookLabel: string;\n /** Locale-aware next-slot formatter. */\n formatNextSlot: (result: PracticeResult) => string | undefined;\n /** Imperative ref to the scrollable list container. */\n listContainerRef: React.RefObject<HTMLDivElement | null>;\n /** Imperative ref to the MapView — used by card-body click → panTo. */\n mapHandleRef: React.MutableRefObject<MapViewHandle | null>;\n}\n\nfunction HeaderRow({\n variantBodyProps,\n}: {\n variantBodyProps: VariantBodyProps;\n}) {\n const {\n facets,\n value,\n sortOptions,\n onChange,\n heading,\n intro,\n totalCount,\n searchCentre,\n } = variantBodyProps;\n // Nothing to show: render no wrapper, so the parent's flex gap leaves no\n // blank space.\n if (\n !heading &&\n !intro &&\n totalCount == null &&\n facets.length === 0 &&\n sortOptions.length === 0\n ) {\n return null;\n }\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)]\">\n <HeaderBlock\n heading={heading}\n headingLevel={variantBodyProps.headingLevel}\n intro={intro}\n totalCount={totalCount}\n searchCentreLabel={searchCentre?.label}\n />\n {facets.length > 0 ? (\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:justify-between ds:gap-[var(--spacing-sm)]\">\n <FilterChipRow\n facets={facets}\n value={value.facets}\n onChange={(next) => onChange({ ...value, facets: next })}\n />\n {sortOptions.length > 0 ? (\n <SortControl\n options={sortOptions}\n value={value.sort ?? 'relevance'}\n onChange={(next) => onChange({ ...value, sort: next })}\n />\n ) : null}\n </div>\n ) : sortOptions.length > 0 ? (\n <div className=\"ds:flex ds:justify-end\">\n <SortControl\n options={sortOptions}\n value={value.sort ?? 'relevance'}\n onChange={(next) => onChange({ ...value, sort: next })}\n />\n </div>\n ) : null}\n </div>\n );\n}\n\n/** The level below the results heading, capped at 6. */\nfunction nextHeadingLevel(\n level: PracticeResultsHeadingLevel,\n): PracticeResultsHeadingLevel {\n return Math.min(level + 1, 6) as PracticeResultsHeadingLevel;\n}\n\nfunction DefaultEmpty({\n headingLevel,\n}: {\n headingLevel: PracticeResultsHeadingLevel;\n}) {\n const { t } = useTranslation('ui');\n return (\n <EmptyState\n variant=\"no-results\"\n headingLevel={nextHeadingLevel(headingLevel)}\n title={t('practiceResults.empty.title')}\n description={t('practiceResults.empty.description')}\n />\n );\n}\n\nfunction DefaultError({\n onRetry,\n headingLevel,\n}: {\n onRetry?: () => void;\n headingLevel: PracticeResultsHeadingLevel;\n}) {\n const { t } = useTranslation('ui');\n const titleLevel = nextHeadingLevel(headingLevel);\n return (\n <Alert variant=\"error\">\n <Alert.Title as={`h${titleLevel}` as const}>\n {t('practiceResults.error.title')}\n </Alert.Title>\n <Alert.Description>\n {t('practiceResults.error.description')}\n </Alert.Description>\n {onRetry ? (\n <Alert.Action>\n <Button\n intent=\"secondary\"\n size=\"sm\"\n startIcon={<RefreshCw aria-hidden=\"true\" />}\n onClick={onRetry}\n >\n {t('practiceResults.error.retry')}\n </Button>\n </Alert.Action>\n ) : null}\n </Alert>\n );\n}\n\nfunction SplitListMapBody(props: VariantBodyProps) {\n const {\n results,\n apiKey,\n mapId,\n clusterMarkers,\n zoom,\n searchCentre,\n onLoadMore,\n loadingMore,\n loadingSkeletons,\n emptyResultsSlot,\n errorSlot,\n error,\n onRetry,\n hoveredId,\n selectedId,\n onCardHover,\n onPinClick,\n consentGranted,\n onConsentRequest,\n onBoundsChange,\n listContainerRef,\n } = props;\n const { t } = useTranslation('ui');\n const [mobileMapOpen, setMobileMapOpen] = useState(false);\n const isDesktop = useMediaQuery('(min-width: 1024px)');\n\n const body = error ? (\n (errorSlot ?? (\n <DefaultError onRetry={onRetry} headingLevel={props.headingLevel} />\n ))\n ) : results.length === 0 && !loadingSkeletons ? (\n (emptyResultsSlot ?? <DefaultEmpty headingLevel={props.headingLevel} />)\n ) : (\n <ResultsList\n results={results}\n headingLevel={props.headingLevel}\n hoveredId={hoveredId}\n selectedId={selectedId}\n softId={props.softId}\n onCardHover={onCardHover}\n onCardLeave={props.onCardLeave}\n onCardSelect={props.onCardSelect}\n onCardOpenClick={props.onCardOpenClick}\n onCardBookClick={props.onCardBookClick}\n openLabel={props.openLabel}\n bookLabel={props.bookLabel}\n formatNextSlot={props.formatNextSlot}\n onLoadMore={onLoadMore}\n loadingMore={loadingMore}\n loadingSkeletons={loadingSkeletons}\n density=\"compact\"\n containerRef={listContainerRef}\n />\n );\n\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n <HeaderRow variantBodyProps={props} />\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)] ds:lg:grid ds:lg:grid-cols-[55fr_45fr] ds:lg:gap-[var(--spacing-lg)] ds:lg:items-start\">\n {/*\n 0.33.8: dropped the capped `lg:max-h-[40rem] lg:overflow-y-auto`\n on the list column. The internal scroll capped the visible\n results to roughly 40rem while the page itself also scrolled\n — a double-scroll experience. The list now renders at its\n natural height; the page is the single scroll container,\n and the map column's `lg:sticky` keeps the map visible as\n the patient scrolls through the cards.\n */}\n <div className=\"ds:min-w-0\">{body}</div>\n <div className=\"ds:hidden ds:lg:block ds:sticky ds:top-[var(--spacing-md)] ds:h-[40rem]\">\n <MapPanel\n results={results}\n searchCentre={searchCentre}\n zoom={zoom}\n apiKey={apiKey}\n mapId={mapId}\n clusterMarkers={clusterMarkers}\n hoveredId={props.mapHoveredId}\n selectedId={props.mapSelectedId}\n onMapClick={props.onMapClick}\n showMarkerCard={isDesktop}\n formatNextSlot={props.formatNextSlot}\n onPinHoverChange={props.onPinHoverChange}\n onPinClick={(id) => {\n emitResultClick(results, id, 'pin', props.onResultSelect);\n onPinClick(id);\n }}\n onPinHover={props.onPinHover}\n onPinFirstInteract={props.onPinFirstInteract}\n onBoundsChange={onBoundsChange}\n consentGranted={consentGranted}\n staticPreview={props.mapPreview}\n onConsentRequest={onConsentRequest}\n mapHandleRef={props.mapHandleRef}\n />\n </div>\n </div>\n {/* Mobile: sticky \"Vedi mappa\" CTA — bottom-of-viewport. */}\n <div className=\"ds:flex ds:lg:hidden ds:sticky ds:bottom-[var(--spacing-md)] ds:justify-center\">\n <Button\n intent=\"primary\"\n size=\"md\"\n startIcon={<MapIcon aria-hidden=\"true\" />}\n onClick={() => setMobileMapOpen(true)}\n >\n {t('practiceResults.viewMap')}\n </Button>\n </div>\n <Sheet.Root open={mobileMapOpen} onOpenChange={setMobileMapOpen}>\n <Sheet.Content side=\"bottom\" size=\"lg\">\n <Sheet.Header>\n <Sheet.Title>{t('practiceResults.map.label')}</Sheet.Title>\n </Sheet.Header>\n <Sheet.Body>\n <Sheet.Description className=\"ds:sr-only\">\n {t('practiceResults.sheet.description')}\n </Sheet.Description>\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n {/* As tall as the map panel's own 24rem floor, so the list never\n overlaps the bottom of the map (Google's logo, the preview). */}\n <div className=\"ds:h-[24rem]\">\n <MapPanel\n results={results}\n searchCentre={searchCentre}\n zoom={zoom}\n apiKey={apiKey}\n mapId={mapId}\n clusterMarkers={clusterMarkers}\n hoveredId={props.mapHoveredId}\n selectedId={props.mapSelectedId}\n onMapClick={props.onMapClick}\n onPinClick={(id) => {\n emitResultClick(results, id, 'pin', props.onResultSelect);\n onPinClick(id);\n }}\n onPinHover={props.onPinHover}\n onPinFirstInteract={props.onPinFirstInteract}\n onBoundsChange={onBoundsChange}\n consentGranted={consentGranted}\n staticPreview={props.mapPreview}\n onConsentRequest={onConsentRequest}\n mapHandleRef={props.mapHandleRef}\n />\n </div>\n {body}\n </div>\n </Sheet.Body>\n </Sheet.Content>\n </Sheet.Root>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* MapFirstBody — map dominant, list rail. */\n/* -------------------------------------------------------------------- */\n\ntype MapFirstSheetState = 'peek' | 'half' | 'full';\n\nconst SHEET_STATE_SIZE: Record<MapFirstSheetState, 'sm' | 'md' | 'lg'> = {\n peek: 'sm',\n half: 'md',\n full: 'lg',\n};\n\nconst SNAP_STATES: readonly MapFirstSheetState[] = [\n 'peek',\n 'half',\n 'full',\n] as const;\n\ninterface SheetSnapRadioGroupProps {\n value: MapFirstSheetState;\n onChange: (next: MapFirstSheetState) => void;\n}\n\n/**\n * WAI-ARIA radiogroup for the mobile bottom-sheet's snap state. Only\n * the checked option lives in the tab stop; arrow keys cycle through\n * options and update the checked state in one motion (per the radio\n * pattern). RTL inverts the horizontal arrow keys so ArrowRight always\n * means \"logical next\".\n */\nfunction SheetSnapRadioGroup({ value, onChange }: SheetSnapRadioGroupProps) {\n const { t, i18n } = useTranslation('ui');\n const isRTL = i18n.dir() === 'rtl';\n const buttonRefs = useRef<Map<MapFirstSheetState, HTMLButtonElement>>(\n new Map(),\n );\n const focusAt = useCallback((state: MapFirstSheetState) => {\n buttonRefs.current.get(state)?.focus();\n }, []);\n const handleKeyDown = useCallback(\n (\n event: React.KeyboardEvent<HTMLButtonElement>,\n current: MapFirstSheetState,\n ) => {\n const idx = SNAP_STATES.indexOf(current);\n const total = SNAP_STATES.length;\n const forwardKey = isRTL ? 'ArrowLeft' : 'ArrowRight';\n const backwardKey = isRTL ? 'ArrowRight' : 'ArrowLeft';\n let next: MapFirstSheetState | null = null;\n switch (event.key) {\n case forwardKey:\n case 'ArrowDown':\n next = SNAP_STATES[(idx + 1) % total];\n break;\n case backwardKey:\n case 'ArrowUp':\n next = SNAP_STATES[(idx - 1 + total) % total];\n break;\n case 'Home':\n next = SNAP_STATES[0];\n break;\n case 'End':\n next = SNAP_STATES[total - 1];\n break;\n default:\n return;\n }\n event.preventDefault();\n if (next && next !== current) onChange(next);\n if (next) focusAt(next);\n },\n [focusAt, isRTL, onChange],\n );\n return (\n <div\n role=\"radiogroup\"\n aria-label={t('practiceResults.sheet.snap.label')}\n className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-2xs)]\"\n >\n {SNAP_STATES.map((state) => {\n const isChecked = value === state;\n return (\n <button\n key={state}\n ref={(node) => {\n if (node) buttonRefs.current.set(state, node);\n else buttonRefs.current.delete(state);\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={isChecked}\n aria-label={t(`practiceResults.sheet.snap.${state}`)}\n tabIndex={isChecked ? 0 : -1}\n onClick={() => onChange(state)}\n onKeyDown={(event) => handleKeyDown(event, state)}\n className={facetChipVariants({\n state: isChecked ? 'active' : 'idle',\n })}\n >\n {t(`practiceResults.sheet.snap.${state}`)}\n </button>\n );\n })}\n </div>\n );\n}\n\n/**\n * Map-first on phones while the host's map image shows (HTP-7348): the\n * preview's plate goes below the floating filters at their measured height,\n * and the list sheet, fixed to the bottom of the screen, stops below the\n * consent gate while it is on screen: the plate, or the text placeholder's\n * buttons if the image failed. Both are runtime measurements, passed as CSS\n * variables, taken again when the map, the filters, the gate or the page\n * change size. Inactive (no styles) on desktop and with no preview.\n */\nfunction useMapFirstPreviewLayout(\n mapRef: React.RefObject<HTMLDivElement | null>,\n filtersRef: React.RefObject<HTMLDivElement | null>,\n active: boolean,\n): { mapStyle?: CSSProperties; sheetStyle?: CSSProperties } {\n const [filtersHeight, setFiltersHeight] = useState<number | null>(null);\n const [sheetMax, setSheetMax] = useState<number | null>(null);\n useIsomorphicLayoutEffect(() => {\n const map = mapRef.current;\n if (!active || !map || typeof window === 'undefined') {\n setFiltersHeight(null);\n setSheetMax(null);\n return undefined;\n }\n let frame = 0;\n let resize: ResizeObserver | null = null;\n let observedGate: HTMLElement | null = null;\n const measure = (): void => {\n const filters = filtersRef.current;\n setFiltersHeight(filters ? filters.offsetHeight : null);\n const gate =\n map.querySelector<HTMLElement>(\n '[data-testid=\"map-view-preview-plate\"]',\n ) ??\n map.querySelector<HTMLElement>(\n '[data-testid=\"map-view-consent-actions\"]',\n );\n // The gate comes and goes (image failure, the interactive map): watch\n // the one on screen, so a change in its size moves the sheet too.\n if (gate !== observedGate) {\n if (observedGate) resize?.unobserve(observedGate);\n if (gate) resize?.observe(gate, { box: 'border-box' });\n observedGate = gate;\n }\n if (!gate) {\n setSheetMax(null);\n return;\n }\n const bottom = gate.getBoundingClientRect().bottom;\n // Off screen above (the page scrolled): the sheet may grow again.\n if (bottom <= 0) {\n setSheetMax(null);\n return;\n }\n const room =\n window.innerHeight - bottom - lengthTokenPx('--spacing-sm', map, 8);\n // Never smaller than the sheet's own header row.\n setSheetMax(\n Math.max(\n Math.round(room),\n lengthTokenPx('--min-target-size', map, 44) * 2,\n ),\n );\n };\n const schedule = (): void => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(measure);\n };\n resize =\n typeof ResizeObserver === 'undefined'\n ? null\n : new ResizeObserver(schedule);\n measure();\n resize?.observe(map);\n if (filtersRef.current) resize?.observe(filtersRef.current);\n // Content above the map that grows or shrinks moves the gate (on a page\n // longer than the screen, the body's size follows its content).\n resize?.observe(document.body);\n // The plate leaves when the visitor opens the interactive map.\n const mutations = new MutationObserver(schedule);\n mutations.observe(map, { childList: true, subtree: true });\n window.addEventListener('resize', schedule);\n window.addEventListener('scroll', schedule, { passive: true });\n return () => {\n cancelAnimationFrame(frame);\n resize?.disconnect();\n mutations.disconnect();\n window.removeEventListener('resize', schedule);\n window.removeEventListener('scroll', schedule);\n };\n }, [active, mapRef, filtersRef]);\n if (!active) return {};\n return {\n mapStyle:\n filtersHeight === null\n ? undefined\n : ({\n '--map-preview-inset-block-start': `calc(var(--spacing-md) + ${filtersHeight}px + var(--spacing-xs))`,\n } as CSSProperties),\n sheetStyle:\n sheetMax === null\n ? undefined\n : ({\n '--map-first-sheet-max-block-size': `${sheetMax}px`,\n } as CSSProperties),\n };\n}\n\nfunction MapFirstBody(props: VariantBodyProps) {\n const {\n results,\n apiKey,\n mapId,\n clusterMarkers,\n zoom,\n searchCentre,\n onLoadMore,\n loadingMore,\n loadingSkeletons,\n emptyResultsSlot,\n errorSlot,\n error,\n onRetry,\n hoveredId,\n selectedId,\n onCardHover,\n onPinClick,\n consentGranted,\n onConsentRequest,\n onBoundsChange,\n listContainerRef,\n facets,\n value,\n onChange,\n } = props;\n const { t } = useTranslation('ui');\n // Bottom-sheet snap state. The kit's Sheet ships three discrete\n // size tokens (sm/md/lg) — until drag-snap lands as a primitive, we\n // cycle between them on the snap radiogroup. Default is `'half'` so\n // the map remains the dominant visual on mobile (per the followup\n // spec) — 0.31.0 defaulted to 'peek' which buried the map under a\n // covering sheet on small viewports.\n //\n // 0.31.1: gate the Sheet on viewport. Radix Dialog portals to\n // `document.body`, so the previous `lg:hidden` wrapper around the\n // Sheet trigger didn't stop the portal from rendering on desktop —\n // patients saw the mobile bottom-sheet covering the entire desktop\n // map. Conditional render guarded by `useMediaQuery` is the only\n // robust gate.\n // HTP-7348: while the host's map image shows, the sheet opens at Peek, so\n // the image's consent plate is in view on a phone.\n const [sheetState, setSheetState] = useState<MapFirstSheetState>(() =>\n props.mapPreview && !consentGranted ? 'peek' : 'half',\n );\n const sheetMovedByVisitor = useRef(false);\n const moveSheet = useCallback((next: MapFirstSheetState) => {\n sheetMovedByVisitor.current = true;\n setSheetState(next);\n }, []);\n // Consent can arrive after mount (the booking site grants it once the Maps\n // library loads): the sheet then opens to Half, as it does with consent from\n // the start, unless the visitor has moved it.\n useIsomorphicLayoutEffect(() => {\n if (consentGranted && !sheetMovedByVisitor.current) {\n setSheetState((current) => (current === 'peek' ? 'half' : current));\n }\n }, [consentGranted]);\n const sheetSize = SHEET_STATE_SIZE[sheetState];\n const isDesktop = useMediaQuery('(min-width: 1024px)');\n const phoneMapRef = useRef<HTMLDivElement>(null);\n const filtersRef = useRef<HTMLDivElement>(null);\n const previewLayout = useMapFirstPreviewLayout(\n phoneMapRef,\n filtersRef,\n !isDesktop && Boolean(props.mapPreview),\n );\n\n // Rail cards on map-first use the `compact` density — at 35%\n // viewport width the rich layout's static thumbnail (12rem × 8rem)\n // would push the card content off-screen. Compact still surfaces\n // distance / languages / insurance / fear / next-slot per the new\n // card-density spec (item 3) — only the thumbnail is dropped.\n const listContent = error ? (\n (errorSlot ?? (\n <DefaultError onRetry={onRetry} headingLevel={props.headingLevel} />\n ))\n ) : results.length === 0 && !loadingSkeletons ? (\n (emptyResultsSlot ?? <DefaultEmpty headingLevel={props.headingLevel} />)\n ) : (\n <ResultsList\n results={results}\n headingLevel={props.headingLevel}\n hoveredId={hoveredId}\n selectedId={selectedId}\n softId={props.softId}\n onCardHover={onCardHover}\n onCardLeave={props.onCardLeave}\n onCardSelect={props.onCardSelect}\n onCardOpenClick={props.onCardOpenClick}\n onCardBookClick={props.onCardBookClick}\n openLabel={props.openLabel}\n bookLabel={props.bookLabel}\n formatNextSlot={props.formatNextSlot}\n onLoadMore={onLoadMore}\n loadingMore={loadingMore}\n loadingSkeletons={loadingSkeletons}\n density=\"compact\"\n apiKey={apiKey}\n containerRef={listContainerRef}\n />\n );\n\n const mapPanel = (\n <MapPanel\n results={results}\n searchCentre={searchCentre}\n zoom={zoom}\n apiKey={apiKey}\n mapId={mapId}\n clusterMarkers={clusterMarkers}\n hoveredId={props.mapHoveredId}\n selectedId={props.mapSelectedId}\n onMapClick={props.onMapClick}\n showMarkerCard={isDesktop}\n formatNextSlot={props.formatNextSlot}\n onPinHoverChange={props.onPinHoverChange}\n onPinClick={(id) => {\n emitResultClick(results, id, 'pin', props.onResultSelect);\n onPinClick(id);\n }}\n onPinHover={props.onPinHover}\n onPinFirstInteract={props.onPinFirstInteract}\n onBoundsChange={onBoundsChange}\n consentGranted={consentGranted}\n staticPreview={props.mapPreview}\n onConsentRequest={onConsentRequest}\n mapHandleRef={props.mapHandleRef}\n />\n );\n\n const floatingFilters =\n facets.length > 0 ? (\n <div\n ref={filtersRef}\n className=\"ds:absolute ds:start-[var(--spacing-md)] ds:top-[var(--spacing-md)] ds:max-w-[calc(100%-var(--spacing-lg))]\"\n >\n <FilterChipRow\n facets={facets}\n value={value.facets}\n onChange={(next) => onChange({ ...value, facets: next })}\n floating\n />\n </div>\n ) : null;\n\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n {isDesktop ? (\n // Desktop: 65/35 map left + cards rail right. NO Sheet (the\n // Sheet would portal to body and occlude the desktop layout\n // regardless of the parent's `lg:hidden` class).\n //\n // 0.33.8: filters move from floating-over-the-map to an\n // inline `<HeaderRow>` above the grid (parity with\n // split-list-map). Floating chips read as ad-hoc on a real\n // Google tile and are easy to miss; an inline row sits in\n // the visual flow as a deliberate refinement control.\n //\n // 0.33.8: dropped `h-[calc(100dvh - chrome)] min-h-[40rem]`\n // on both columns + `overflow-y-auto` on the list rail. The\n // viewport-fill + internal scroll created a double-scroll\n // experience — patients scrolled the page AND the rail. Now\n // the map column is `lg:sticky lg:h-[44rem]` (stays visible\n // as the patient scrolls cards) and the rail renders at\n // natural height. Single page scroll.\n <>\n <HeaderRow variantBodyProps={props} />\n <div className=\"ds:grid ds:grid-cols-[65fr_35fr] ds:gap-[var(--spacing-lg)] ds:items-start\">\n <div className=\"ds:sticky ds:top-[var(--spacing-md)] ds:h-[44rem]\">\n {mapPanel}\n </div>\n <div className=\"ds:min-w-0\">{listContent}</div>\n </div>\n </>\n ) : (\n // Mobile: full-bleed map + persistent bottom sheet defaulting to\n // 'half' state. No X close button — the sheet is core to the\n // layout, not dismissible. Patient cycles peek / half / full\n // via the snap radiogroup at the top of the sheet body. Filters\n // stay floating on mobile — the full-bleed map IS the viewport\n // chrome, so floating chips are the only place for them.\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n <HeaderBlock\n heading={props.heading}\n headingLevel={props.headingLevel}\n intro={props.intro}\n totalCount={props.totalCount}\n searchCentreLabel={searchCentre?.label}\n />\n {/* The map preview's plate sits below the floating filters, at\n their measured height (they wrap on phones). HTP-7348. */}\n <div\n ref={phoneMapRef}\n className=\"ds:relative ds:h-[60vh] ds:min-h-[20rem]\"\n // eslint-disable-next-line react/forbid-dom-props -- the filters' measured height, passed as a CSS variable\n style={previewLayout.mapStyle}\n >\n {mapPanel}\n {floatingFilters}\n </div>\n {/* Not modal: the map above the sheet stays usable, so a tap on the\n map preview's button (or the live map) reaches it. */}\n <Sheet.Root open={true} modal={false}>\n <Sheet.Content\n side=\"bottom\"\n size={sheetSize}\n // While the map preview's consent gate is on screen, the sheet\n // stops below it, whatever its snap (HTP-7348). The page behind\n // a non-modal sheet scrolls: the list's scroller (the Radix\n // viewport) stops at its ends instead of moving the page.\n className=\"ds:max-h-[var(--map-first-sheet-max-block-size,100dvh)] ds:[&_[data-radix-scroll-area-viewport]]:overscroll-contain\"\n // eslint-disable-next-line react/forbid-component-props -- the room measured below the plate, passed as a CSS variable\n style={previewLayout.sheetStyle}\n // The sheet is non-dismissible on map-first; intercept\n // every dismiss path (Escape, outside click, drag-down)\n // since the layout depends on it.\n onEscapeKeyDown={(event) => event.preventDefault()}\n onPointerDownOutside={(event) => event.preventDefault()}\n onInteractOutside={(event) => event.preventDefault()}\n // It is open from the start and never closes: focus stays on\n // the page, so keyboard and screen-reader users meet the\n // heading, the filters and the map before the list.\n onOpenAutoFocus={(event) => event.preventDefault()}\n >\n <Sheet.Body>\n <Sheet.Description className=\"ds:sr-only\">\n {t('practiceResults.sheet.descriptionMapFirst')}\n </Sheet.Description>\n {/* Custom header in place of Sheet.Header — drops the X\n close button which would be misleading here (the\n sheet is not dismissible). The title still uses\n Radix's `Sheet.Title` so a11y label resolution\n works. */}\n <div className=\"ds:flex ds:items-start ds:justify-between ds:gap-[var(--spacing-sm)] ds:pb-[var(--spacing-sm)]\">\n <Sheet.Title className=\"type-title-card ds:m-0 ds:text-[var(--foreground)]\">\n {t('practiceResults.sheet.title', {\n count: props.totalCount ?? results.length,\n })}\n </Sheet.Title>\n <SheetSnapRadioGroup\n value={sheetState}\n onChange={moveSheet}\n />\n </div>\n {props.sortOptions.length > 0 ? (\n <div className=\"ds:pb-[var(--spacing-sm)]\">\n <SortControl\n options={props.sortOptions}\n value={value.sort ?? 'relevance'}\n onChange={(next) => onChange({ ...value, sort: next })}\n />\n </div>\n ) : null}\n {listContent}\n </Sheet.Body>\n </Sheet.Content>\n </Sheet.Root>\n </div>\n )}\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* RichCardsBody — vertical column of rich cards. */\n/* -------------------------------------------------------------------- */\n\nfunction RichCardsBody(props: VariantBodyProps) {\n const {\n results,\n apiKey,\n onLoadMore,\n loadingMore,\n loadingSkeletons,\n emptyResultsSlot,\n errorSlot,\n error,\n onRetry,\n hoveredId,\n selectedId,\n onCardHover,\n listContainerRef,\n } = props;\n\n const body = error ? (\n (errorSlot ?? (\n <DefaultError onRetry={onRetry} headingLevel={props.headingLevel} />\n ))\n ) : results.length === 0 && !loadingSkeletons ? (\n (emptyResultsSlot ?? <DefaultEmpty headingLevel={props.headingLevel} />)\n ) : (\n <ResultsList\n results={results}\n headingLevel={props.headingLevel}\n hoveredId={hoveredId}\n selectedId={selectedId}\n softId={props.softId}\n onCardHover={onCardHover}\n onCardLeave={props.onCardLeave}\n onCardSelect={props.onCardSelect}\n onCardOpenClick={props.onCardOpenClick}\n onCardBookClick={props.onCardBookClick}\n openLabel={props.openLabel}\n bookLabel={props.bookLabel}\n formatNextSlot={props.formatNextSlot}\n onLoadMore={onLoadMore}\n loadingMore={loadingMore}\n loadingSkeletons={loadingSkeletons}\n density=\"rich\"\n apiKey={apiKey}\n containerRef={listContainerRef}\n />\n );\n\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n <HeaderRow variantBodyProps={props} />\n {body}\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* PracticeResults */\n/* -------------------------------------------------------------------- */\n\nexport const PracticeResults = forwardRef<\n PracticeResultsHandle,\n PracticeResultsProps\n>(function PracticeResults(\n {\n variant = 'split-list-map',\n results,\n contentLang,\n totalCount,\n searchCentre,\n facets,\n googleMapsApiKey,\n mapId,\n clusterMarkers = 'auto',\n defaultZoom = DEFAULT_ZOOM,\n onBoundsChange,\n consentGranted,\n onConsentRequest,\n skipConsentGate = false,\n mapPreview,\n onLoadMore,\n loadingMore,\n value,\n onChange,\n sortOptions,\n onResultSelect,\n loading,\n emptyResultsSlot,\n errorSlot,\n error,\n onRetry,\n heading,\n headingLevel = 2,\n intro,\n cardOpenButtonLabel,\n cardBookButtonLabel,\n showCardOpenButton = true,\n 'aria-label': ariaLabel,\n id,\n className,\n ...rest\n },\n ref,\n) {\n const { t, i18n } = useTranslation('ui');\n\n // Two separate states, both at the root so the list and the map see them.\n // - hoveredId (HTP-7322): the card under the pointer or focus. Drives the\n // map mini card and the enlarged hover pin. Cleared on leave.\n // - selectedId (HTP-7323): the card a pin click chose. Drives the strong\n // card highlight, the tag and the map pin's `data-selected`. Hovering\n // other cards never clears it.\n const [hoveredId, setHoveredId] = useState<string | null>(null);\n const [selectedId, setSelectedId] = useState<string | null>(null);\n // A light highlight with no tag: a pin click on a phone, and the imperative\n // handle where nothing was chosen on a map (deep links, agents).\n const [softId, setSoftId] = useState<string | null>(null);\n // A pin click asks for its card to be scrolled to the top. The scroll runs\n // in a layout effect, after the previous card has dropped its tag, so the\n // target position is measured on the final layout.\n const [scrollRequest, setScrollRequest] = useState<{\n id: string;\n n: number;\n } | null>(null);\n // The map and its mini card only sit beside the list from `lg`. Below\n // that the map is in a sheet and pin clicks keep their earlier behaviour.\n const isDesktop = useMediaQuery('(min-width: 1024px)');\n // Once-per-card hover telemetry de-dupe. PostHog charges per event;\n // we don't want to flood it with re-hovers on the same card.\n const hoveredCardIdsRef = useRef<Set<string>>(new Set());\n const hoveredPinIdsRef = useRef<Set<string>>(new Set());\n\n // Imperative handle — `scrollToResult` finds the card by id within the\n // currently-rendered list and scrolls it into view; `focusPin` updates\n // the highlight state which feeds the embedded MapView's\n // `selectedMarkerId`.\n const listContainerRef = useRef<HTMLDivElement>(null);\n const rootRef = useRef<HTMLDivElement>(null);\n\n const handle = useMemo<PracticeResultsHandle>(\n () => ({\n getVariant: () => variant,\n scrollToResult: (resultId: string) => {\n const root = rootRef.current;\n if (!root) return;\n const card = root.querySelector<HTMLElement>(\n `[data-component=\"practice-result-card\"][data-result-id=\"${CSS.escape(resultId)}\"]`,\n );\n if (!card) return;\n card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n // Light highlight only: nothing was chosen on the map.\n if (!isDesktop) setHoveredId(null);\n setSoftId(resultId);\n },\n focusPin: (resultId: string) => {\n // Follows the pin-click rules: it chooses the card only where a pin\n // click does (a map variant, `lg` and up). Elsewhere, light highlight.\n if (isDesktop && variant !== 'rich-cards') {\n setSoftId(null);\n setSelectedId(resultId);\n } else {\n if (!isDesktop) setHoveredId(null);\n setSoftId(resultId);\n }\n },\n }),\n [variant, isDesktop],\n );\n useImperativeHandle(ref, () => handle, [handle]);\n useAgentRegistration(practiceResultsAgent, handle, id);\n\n // Resolved at the root so every variant body sees the same string\n // and i18n re-renders cascade once. Consumer can override via the\n // `cardOpenButtonLabel` prop.\n // `undefined` is the \"no Open button\" signal down the card tree, so a\n // hidden button costs no extra prop on five intermediate components.\n const openLabel = showCardOpenButton\n ? (cardOpenButtonLabel ?? t('practiceResults.card.open'))\n : undefined;\n const bookLabel = cardBookButtonLabel ?? t('practiceResults.card.book');\n const formatNextSlot = useCallback(\n (result: PracticeResult): string | undefined => {\n if (!result.nextAvailableSlot) return undefined;\n try {\n return new Intl.DateTimeFormat(i18n.language, {\n weekday: 'short',\n hour: 'numeric',\n minute: 'numeric',\n }).format(new Date(result.nextAvailableSlot.dateTime));\n } catch {\n return result.nextAvailableSlot.dateTime;\n }\n },\n [i18n.language],\n );\n\n // `serp_variant_exposed` — fire once per variant assignment.\n const exposedRef = useRef<PracticeResultsVariant | null>(null);\n useEffect(() => {\n if (exposedRef.current === variant) return;\n exposedRef.current = variant;\n track('serp_variant_exposed', {\n variant,\n totalCount: totalCount ?? results.length,\n });\n // Reset per-card de-dupe so the new variant doesn't share state.\n hoveredCardIdsRef.current = new Set();\n hoveredPinIdsRef.current = new Set();\n // Intentionally omit totalCount / results.length so the event fires\n // once per variant, not once per render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [variant]);\n\n // `serp_empty_results` — fire when the visible (filtered) list drops\n // to zero. Compared against the most-recent emit so consecutive\n // empties don't flood telemetry.\n const lastEmptyEmitRef = useRef<boolean>(false);\n\n // Resolved values: facets default to []; sort defaults to 'distance'\n // when searchCentre is set, otherwise 'relevance'. `resolvedFacets`\n // is memoised so the `processedResults` useMemo below doesn't\n // re-invalidate on every render when the consumer passes `undefined`\n // and `?? []` allocates a fresh array.\n const resolvedFacets = useMemo(\n () => resolveFacetGroups(facets ?? [], results, value.facets),\n [facets, results, value.facets],\n );\n const offeredSorts = useMemo(\n () =>\n sortOptions\n ? SORT_OPTIONS.filter((option) => sortOptions.includes(option))\n : SORT_OPTIONS,\n [sortOptions],\n );\n const defaultSort: PracticeResultsSort = searchCentre\n ? 'distance'\n : 'relevance';\n // A sort the host does not offer falls back to the default (or the first\n // offered sort). With nothing offered the default is used and no chip is\n // drawn. No onChange and no serp_sort_changed fire for the fallback.\n const requestedSort = value.sort ?? defaultSort;\n const resolvedSort = offeredSorts.includes(requestedSort)\n ? requestedSort\n : offeredSorts.includes(defaultSort)\n ? defaultSort\n : (offeredSorts[0] ?? defaultSort);\n\n // Apply filters + sort. The kit does this client-side so consumers\n // get the right ordering without re-querying — the consumer's backend\n // is still free to pre-sort, but the kit's `sort` switch never\n // requires a network round-trip.\n const processedResults = useMemo(() => {\n const filtered = applyFilters(results, value.facets, resolvedFacets);\n return applySort(filtered, resolvedSort, searchCentre);\n }, [results, value.facets, resolvedFacets, resolvedSort, searchCentre]);\n\n // The list-level default is written onto each result for drawing only;\n // callbacks (card and pin clicks) never see the injected value.\n const displayResults = useMemo(\n () =>\n contentLang\n ? processedResults.map((r) =>\n r.contentLang ? r : { ...r, contentLang },\n )\n : processedResults,\n [processedResults, contentLang],\n );\n\n useEffect(() => {\n const isEmpty = processedResults.length === 0;\n if (isEmpty && !lastEmptyEmitRef.current) {\n track('serp_empty_results', {\n appliedFacets: value.facets ?? {},\n });\n lastEmptyEmitRef.current = true;\n } else if (!isEmpty) {\n lastEmptyEmitRef.current = false;\n }\n }, [processedResults.length, value.facets]);\n\n // A result that leaves the list (a filter, new results) or a variant change\n // drops its hover, selection and highlight, so the tag does not come back\n // when the filter is removed, and never shows on a variant with no map.\n useEffect(() => {\n const ids = new Set(processedResults.map((r) => r.id));\n const keep = (prev: string | null): string | null =>\n prev !== null && ids.has(prev) ? prev : null;\n setHoveredId(keep);\n setSelectedId(keep);\n setSoftId(keep);\n }, [processedResults]);\n useEffect(() => {\n setHoveredId(null);\n setSelectedId(null);\n setSoftId(null);\n }, [variant]);\n\n // Hover debounce — `serp_result_hovered_card` only fires after 500ms\n // of continuous hover on the same card AND only once per card per\n // session. Per the prompt's instrumentation spec — distinguishes\n // genuine intent signals from accidental drag-throughs.\n const hoverTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const lastHoverIdRef = useRef<string | null>(null);\n useEffect(\n () => () => {\n if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);\n },\n [],\n );\n const trackCardHover = useCallback((resultId: string) => {\n // De-dupe: already-tracked-this-session cards skip the debounce\n // entirely so we don't queue a no-op timer.\n if (hoveredCardIdsRef.current.has(resultId)) return;\n if (lastHoverIdRef.current === resultId && hoverTimerRef.current) {\n return;\n }\n if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);\n lastHoverIdRef.current = resultId;\n hoverTimerRef.current = setTimeout(() => {\n if (!hoveredCardIdsRef.current.has(resultId)) {\n hoveredCardIdsRef.current.add(resultId);\n track('serp_result_hovered_card', { resultId });\n }\n hoverTimerRef.current = null;\n }, 500);\n }, []);\n const onCardHover = useCallback(\n (resultId: string, visual: boolean) => {\n // Telemetry as on main: every card mouseenter (touch too) and every\n // focus inside the card queues it. Only the visual state is gated.\n trackCardHover(resultId);\n if (!visual) return;\n setHoveredId(resultId);\n setSoftId(null);\n },\n [trackCardHover],\n );\n const onCardLeave = useCallback(() => setHoveredId(null), []);\n // HTP-7322: a pin under the pointer, or keyboard focus on a pin, shows its\n // mini card by the card-hover rules: only beside the list (`lg` and up),\n // and the pointer only where the device can hover. Telemetry is not\n // touched: `serp_result_hovered_pin` still fires on the first pin click.\n // Escape hides the map card without moving the pointer or focus (WCAG\n // 1.4.13), whether a result card or a pin raised it.\n useEffect(() => {\n if (hoveredId === null) return undefined;\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') setHoveredId(null);\n };\n document.addEventListener('keydown', onKeyDown);\n return () => document.removeEventListener('keydown', onKeyDown);\n }, [hoveredId]);\n // Only \"on\" is gated: a leave always clears the pin it holds.\n const onPinHoverChange = useCallback(\n (resultId: string, hovered: boolean, source: MapMarkerHoverSource) => {\n if (!hovered) {\n setHoveredId((prev) => (prev === resultId ? null : prev));\n return;\n }\n if (!isDesktop) return;\n if (source === 'pointer' && !canHover()) return;\n setHoveredId(resultId);\n setSoftId(null);\n },\n [isDesktop],\n );\n // The map moves into a sheet below `lg`, where its pins send no leave: a\n // hover from the wider layout must not stay behind as the selected pin.\n const wasDesktop = useRef(isDesktop);\n useEffect(() => {\n if (wasDesktop.current && !isDesktop) setHoveredId(null);\n wasDesktop.current = isDesktop;\n }, [isDesktop]);\n const onMapClick = useCallback(() => {\n setSelectedId(null);\n setSoftId(null);\n }, []);\n const onPinClick = useCallback(\n (resultId: string) => {\n if (!isDesktop) {\n // Phones: unchanged. Light highlight, nearest scroll.\n const card = rootRef.current?.querySelector<HTMLElement>(\n `[data-component=\"practice-result-card\"][data-result-id=\"${CSS.escape(resultId)}\"]`,\n );\n setHoveredId(null);\n setSoftId(resultId);\n card?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n return;\n }\n // HTP-7323: choose the card and bring it to the top of the list (see\n // the layout effect below). The list order never changes.\n setSoftId(null);\n setSelectedId(resultId);\n setScrollRequest((prev) => ({ id: resultId, n: (prev?.n ?? 0) + 1 }));\n },\n [isDesktop],\n );\n useIsomorphicLayoutEffect(() => {\n if (!scrollRequest) return;\n const root = rootRef.current;\n const card = root?.querySelector<HTMLElement>(\n `[data-component=\"practice-result-card\"][data-result-id=\"${CSS.escape(scrollRequest.id)}\"]`,\n );\n // Instant when the patient asks for less motion or the theme turns\n // motion off (`.theme-accessible`, a zero `--animation-duration`).\n card?.scrollIntoView({\n behavior: motionIsOff(card) ? 'instant' : 'smooth',\n block: 'start',\n });\n }, [scrollRequest]);\n // Once-per-pin telemetry de-dupe. `onPinFirstInteract` fires on the\n // first marker click per pin per session; subsequent clicks update\n // the highlight but don't re-fire `serp_result_hovered_pin`.\n const onPinFirstInteract = useCallback((resultId: string) => {\n if (hoveredPinIdsRef.current.has(resultId)) return;\n hoveredPinIdsRef.current.add(resultId);\n track('serp_result_hovered_pin', { resultId });\n }, []);\n // Map imperative ref — populated by MapPanel via its MapView. Card-\n // body click pans the map to the matching pin (split-list-map +\n // map-first only; no-op on rich-cards since there's no live map).\n const mapHandleRef = useRef<MapViewHandle | null>(null);\n const onCardSelect = useCallback(\n (resultId: string) => {\n // A card click replaces any pin selection: the tag says \"on the map\".\n setSelectedId(null);\n if (canHover()) {\n setSoftId(null);\n setHoveredId(resultId);\n } else {\n // A tap sends no mouseleave, so a hover state would stick: use the\n // light highlight, which is what phones had before.\n setHoveredId(null);\n setSoftId(resultId);\n }\n const result = processedResults.find((r) => r.id === resultId);\n if (!result) return;\n // Sync-only — pan the map to centre on the matching pin. The\n // existing pin-click flow already handles the inverse (pin →\n // scroll list to card).\n if (result.location) {\n const handle = mapHandleRef.current;\n handle?.panTo({\n lat: result.location.lat,\n lng: result.location.lng,\n });\n handle?.setZoom(16);\n }\n emitResultClick(processedResults, resultId, 'card', onResultSelect);\n },\n [processedResults, onResultSelect],\n );\n const onCardOpenClick = useCallback(\n (resultId: string) => {\n // Open and Book are the only sources that should drive navigation.\n // The underlying `<a href>` handles the native navigation; the kit\n // just fires telemetry + the consumer's hook.\n emitResultClick(\n processedResults,\n resultId,\n 'open-button',\n onResultSelect,\n );\n },\n [processedResults, onResultSelect],\n );\n const onCardBookClick = useCallback(\n (resultId: string) => {\n emitResultClick(\n processedResults,\n resultId,\n 'book-button',\n onResultSelect,\n );\n },\n [processedResults, onResultSelect],\n );\n\n // SSR-first: only render skeletons when the consumer is actively\n // loading AND has nothing to show. A server-rendered list keeps its\n // bones during hydration even if the consumer flips `loading` on its\n // background re-fetch.\n const loadingSkeletons = Boolean(loading) && results.length === 0;\n\n // Pin clicks inside the variant bodies hold the display copies; hand the\n // consumer the undecorated result, as the card handlers above do.\n const onBodyResultSelect = useCallback<\n PracticeResultsProps['onResultSelect']\n >(\n (picked, source) => {\n const original = processedResults.find((r) => r.id === picked.id);\n onResultSelect(original ?? picked, source);\n },\n [processedResults, onResultSelect],\n );\n\n const bodyProps: VariantBodyProps = {\n results: displayResults,\n totalCount,\n searchCentre,\n facets: resolvedFacets,\n value: { ...value, sort: resolvedSort },\n sortOptions: offeredSorts,\n onChange,\n onResultSelect: onBodyResultSelect,\n onLoadMore,\n loadingMore,\n apiKey: googleMapsApiKey,\n mapId,\n clusterMarkers,\n zoom: defaultZoom,\n // 0.31.4: when `skipConsentGate` is true the kit synthesises\n // `consentGranted=true` for every internal MapView; the consumer's\n // CMP at the page level is responsible for whether the Google Maps\n // loader script is present at all. `onConsentRequest` is dropped\n // because no placeholder renders — nothing to fire it from.\n consentGranted: skipConsentGate ? true : consentGranted,\n onConsentRequest: skipConsentGate ? undefined : onConsentRequest,\n mapPreview,\n onBoundsChange,\n loadingSkeletons,\n emptyResultsSlot,\n errorSlot,\n error,\n onRetry,\n heading,\n headingLevel,\n intro,\n hoveredId,\n selectedId,\n softId,\n mapHoveredId: isDesktop ? hoveredId : null,\n mapSelectedId: isDesktop ? (selectedId ?? softId) : (hoveredId ?? softId),\n onCardHover,\n onCardLeave,\n onPinHover: trackCardHover,\n onPinHoverChange,\n onMapClick,\n onPinClick,\n onPinFirstInteract,\n onCardSelect,\n onCardOpenClick,\n onCardBookClick,\n openLabel,\n bookLabel,\n formatNextSlot,\n listContainerRef,\n mapHandleRef,\n };\n\n const body = (() => {\n switch (variant) {\n case 'map-first':\n return <MapFirstBody {...bodyProps} />;\n case 'rich-cards':\n return <RichCardsBody {...bodyProps} />;\n case 'split-list-map':\n default:\n return <SplitListMapBody {...bodyProps} />;\n }\n })();\n\n const resolvedAriaLabel = ariaLabel ?? t('practiceResults.regionLabel');\n\n return (\n <div\n ref={rootRef}\n role=\"region\"\n aria-label={resolvedAriaLabel}\n id={id}\n data-component=\"practice-results\"\n data-component-id={id}\n data-variant={variant}\n className={rootVariants({ variant, className })}\n {...rest}\n >\n {body}\n </div>\n );\n});\n\nPracticeResults.displayName = 'PracticeResults';\n"],"names":["__iconNode","Map","createLucideIcon","practiceResultsAgent","handle","args","DEFAULT_ZOOM","SORT_OPTIONS","resolveLanguageLabel","code","i18n","key","track","event","payload","fn","emitResultClick","results","id","source","onResultSelect","position","r","result","haversineKm","a","b","dLat","dLng","lat1","lat2","sinDLat","sinDLng","h","resolveFacetGroups","facetGroups","selections","resolved","group","booleanField","hasActiveSelection","_a","isBoolean","applyFilters","facets","selected","field","v","firstVisitSortAmount","amount","isFixedFirstVisitPrice","compareDistance","applySort","sort","searchCentre","decorated","distanceKm","compare","av","bv","_b","_c","_d","boundsForResults","north","south","east","west","anyPoint","lat","lng","rootVariants","cva","HeaderBlock","heading","headingLevel","intro","totalCount","searchCentreLabel","t","useTranslation","Heading","summary","jsxs","jsx","facetChipVariants","filterChipRowVariants","clearFiltersButtonVariants","FilterChipRow","value","onChange","floating","useMemo","hasAnyActive","arr","updateFacet","values","next","toggleBoolean","isActive","toggleOption","option","current","active","countSuffix","DropdownMenu","ChevronDown","SortControl","options","labelFor","DistanceChip","label","formatted","Badge","MapPin","staticMapThumbVariants","getStaticMapMarkerHex","raw","match","StaticMapAddressBlock","address","addressLang","StaticMapThumb","image","apiKey","center","zoom","failedSrc","setFailedSrc","useState","markerHex","src","CardTagline","tagline","lang","reviewLang","tag","CardLatestReview","review","text","cardDescriptionVariants","CardDescription","description","density","formatFirstVisitPrice","price","currencyCode","locale","formatPriceOrFree","min","formatPrice","cardPriceLabelVariants","CardFirstVisitPrice","cents","resolvedPrice","kind","minCents","maxCents","practiceLabel","NextSlotCaption","time","CalendarClock","CardActions","openLabel","bookLabel","nextSlotText","onOpenClick","onBookClick","slot","bookHref","resolvedBookLabel","Button","CalendarPlus","resultCardBaseVariants","resultNameLinkVariants","ResultNameHeading","nextHeadingLevel","CardHeading","isInteractiveCardClick","interactive","canHover","leaveOnMouse","onLeave","isFocusVisible","leaveOnBlur","ResultCardBase","highlighted","onSelect","onHover","maxInsuranceChips","maxLanguageChips","children","ratingLabel","showLanguages","showInsurances","showFearPatient","showChipRow","languageChips","languageOverflow","insuranceChips","insuranceOverflow","Avatar","Rating","Languages","ins","ShieldCheck","Heart","RichResultCard","slotLabel","ResultsList","hoveredId","selectedId","softId","onCardHover","onCardLeave","onCardSelect","onCardOpenClick","onCardBookClick","formatNextSlot","onLoadMore","loadingMore","loadingSkeletons","containerRef","sentinelRef","useRef","useEffect","node","observer","entries","entry","_","i","Skeleton","visual","Spinner","MapMiniCard","rating","MapPanel","mapId","clusterMarkers","onMapClick","showMarkerCard","onPinHoverChange","onPinClick","onPinHover","onPinFirstInteract","onBoundsChange","consentGranted","onConsentRequest","staticPreview","mapHandleRef","markers","fitPoints","marker","fitKey","bounds","renderMarkerCard","markerId","handleBoundsChange","useCallback","userInteraction","MapView","count","HeaderRow","variantBodyProps","sortOptions","level","DefaultEmpty","EmptyState","DefaultError","onRetry","titleLevel","Alert","RefreshCw","SplitListMapBody","props","emptyResultsSlot","errorSlot","error","listContainerRef","mobileMapOpen","setMobileMapOpen","isDesktop","useMediaQuery","body","MapIcon","Sheet","SHEET_STATE_SIZE","SNAP_STATES","SheetSnapRadioGroup","isRTL","buttonRefs","focusAt","state","handleKeyDown","idx","total","forwardKey","backwardKey","isChecked","useMapFirstPreviewLayout","mapRef","filtersRef","filtersHeight","setFiltersHeight","sheetMax","setSheetMax","useIsomorphicLayoutEffect","map","frame","resize","observedGate","measure","filters","gate","bottom","room","lengthTokenPx","schedule","mutations","MapFirstBody","sheetState","setSheetState","sheetMovedByVisitor","moveSheet","sheetSize","phoneMapRef","previewLayout","listContent","mapPanel","floatingFilters","Fragment","RichCardsBody","PracticeResults","forwardRef","variant","contentLang","googleMapsApiKey","defaultZoom","skipConsentGate","mapPreview","loading","cardOpenButtonLabel","cardBookButtonLabel","showCardOpenButton","ariaLabel","className","rest","ref","setHoveredId","setSelectedId","setSoftId","scrollRequest","setScrollRequest","hoveredCardIdsRef","hoveredPinIdsRef","rootRef","resultId","root","card","useImperativeHandle","useAgentRegistration","exposedRef","lastEmptyEmitRef","resolvedFacets","offeredSorts","defaultSort","requestedSort","resolvedSort","processedResults","filtered","displayResults","isEmpty","ids","keep","prev","hoverTimerRef","lastHoverIdRef","trackCardHover","onKeyDown","hovered","wasDesktop","motionIsOff","onBodyResultSelect","picked","original","bodyProps","resolvedAriaLabel"],"mappings":"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","x_google_ignoreList":[0]}
@@ -15,7 +15,7 @@ import { F as Je } from "./form-field-CyHfOVKF.js";
15
15
  import { P as wr } from "./phone-input-BL8Sj6OG.js";
16
16
  import { T as Cr } from "./text-area-COMVJ1yE.js";
17
17
  import { T as Yt } from "./text-input-B6iAgCr3.js";
18
- import { A as Tr } from "./address-fields-Dl1ALKhP.js";
18
+ import { A as Tr } from "./address-fields-BU65pA-V.js";
19
19
  import { S as qn } from "./spinner-CoflYlEx.js";
20
20
  import { a as on } from "./format-price-CIX0YJZn.js";
21
21
  import { C as Ar } from "./credit-card-D9XZsHGA.js";
@@ -4597,4 +4597,4 @@ export {
4597
4597
  bs as b,
4598
4598
  Mo as c
4599
4599
  };
4600
- //# sourceMappingURL=reserved.agent-V2Eqoabh.js.map
4600
+ //# sourceMappingURL=reserved.agent-DRSZ8UcL.js.map