@alfadocs/ui-kit-debug 1.109.0 → 1.111.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 (162) hide show
  1. package/dist/_chunks/{anamnesis-summary-rows-QhbDdY7b.js → anamnesis-summary-rows-BrTx7diS.js} +2 -2
  2. package/dist/_chunks/{anamnesis-summary-rows-QhbDdY7b.js.map → anamnesis-summary-rows-BrTx7diS.js.map} +1 -1
  3. package/dist/_chunks/{calculator-dialog-BsVVb8cT.js → calculator-dialog-D4TyJwqy.js} +2 -2
  4. package/dist/_chunks/{calculator-dialog-BsVVb8cT.js.map → calculator-dialog-D4TyJwqy.js.map} +1 -1
  5. package/dist/_chunks/{calendar-appointment-editor-dialog-D8DblVjW.js → calendar-appointment-editor-dialog-Oc3Df8O3.js} +4 -4
  6. package/dist/_chunks/{calendar-appointment-editor-dialog-D8DblVjW.js.map → calendar-appointment-editor-dialog-Oc3Df8O3.js.map} +1 -1
  7. package/dist/_chunks/data-grid.agent-CASyEAnb.js +1773 -0
  8. package/dist/_chunks/data-grid.agent-CASyEAnb.js.map +1 -0
  9. package/dist/_chunks/{dialog-C7RWaH08.js → dialog-BTjSQjdY.js} +17 -16
  10. package/dist/_chunks/dialog-BTjSQjdY.js.map +1 -0
  11. package/dist/_chunks/{file-manager-BDKe59sg.js → file-manager-CVxBE4yy.js} +2 -2
  12. package/dist/_chunks/{file-manager-BDKe59sg.js.map → file-manager-CVxBE4yy.js.map} +1 -1
  13. package/dist/_chunks/{help-me-write-templates-YmceSxf5.js → help-me-write-templates-CWxgjDZl.js} +2 -2
  14. package/dist/_chunks/{help-me-write-templates-YmceSxf5.js.map → help-me-write-templates-CWxgjDZl.js.map} +1 -1
  15. package/dist/_chunks/{notes-panel-SlzUL8in.js → notes-panel-Cz621DWV.js} +3 -3
  16. package/dist/_chunks/{notes-panel-SlzUL8in.js.map → notes-panel-Cz621DWV.js.map} +1 -1
  17. package/dist/_chunks/{public-footer-3Jy1zHSs.js → public-footer-DGjLTtNK.js} +30 -30
  18. package/dist/_chunks/public-footer-DGjLTtNK.js.map +1 -0
  19. package/dist/_chunks/{radiograph-panel.agent-0ngF2sGS.js → radiograph-panel.agent-CXah49KM.js} +2 -2
  20. package/dist/_chunks/{radiograph-panel.agent-0ngF2sGS.js.map → radiograph-panel.agent-CXah49KM.js.map} +1 -1
  21. package/dist/_chunks/{reviews-panel-CtrwZOUS.js → reviews-panel-B03auSpQ.js} +210 -207
  22. package/dist/_chunks/reviews-panel-B03auSpQ.js.map +1 -0
  23. package/dist/_chunks/{rich-text-editor-C3nANKZt.js → rich-text-editor-BLS2cK-u.js} +2 -2
  24. package/dist/_chunks/{rich-text-editor-C3nANKZt.js.map → rich-text-editor-BLS2cK-u.js.map} +1 -1
  25. package/dist/_chunks/{signature-request-opW0fQ5P.js → signature-request-DJwdPji_.js} +2 -2
  26. package/dist/_chunks/{signature-request-opW0fQ5P.js.map → signature-request-DJwdPji_.js.map} +1 -1
  27. package/dist/_chunks/{signed-out-frame-Bs_LSPWe.js → signed-out-frame-CPow3aCZ.js} +2 -2
  28. package/dist/_chunks/{signed-out-frame-Bs_LSPWe.js.map → signed-out-frame-CPow3aCZ.js.map} +1 -1
  29. package/dist/_chunks/{task-editor-CmXcj6cC.js → task-editor-jqRf2JP6.js} +2 -2
  30. package/dist/_chunks/{task-editor-CmXcj6cC.js.map → task-editor-jqRf2JP6.js.map} +1 -1
  31. package/dist/_chunks/{widget-picker-DNYIqRT1.js → widget-picker-U4JsEhQE.js} +2 -2
  32. package/dist/_chunks/{widget-picker-DNYIqRT1.js.map → widget-picker-U4JsEhQE.js.map} +1 -1
  33. package/dist/ag-grid.css +0 -10
  34. package/dist/agent-catalog.json +66 -1
  35. package/dist/components/calculator-dialog/index.js +1 -1
  36. package/dist/components/data-grid/data-grid.agent.d.ts +3 -0
  37. package/dist/components/data-grid/data-grid.agent.d.ts.map +1 -0
  38. package/dist/components/data-grid/data-grid.d.ts +28 -0
  39. package/dist/components/data-grid/data-grid.d.ts.map +1 -0
  40. package/dist/components/data-grid/index.d.ts +4 -0
  41. package/dist/components/data-grid/index.d.ts.map +1 -0
  42. package/dist/components/data-grid/index.js +6 -0
  43. package/dist/components/data-grid/index.js.map +1 -0
  44. package/dist/components/dialog/dialog.d.ts +2 -2
  45. package/dist/components/dialog/dialog.d.ts.map +1 -1
  46. package/dist/components/dialog/index.js +1 -1
  47. package/dist/components/file-manager/index.js +1 -1
  48. package/dist/components/index.d.ts +1 -0
  49. package/dist/components/index.d.ts.map +1 -1
  50. package/dist/components/notes-panel/index.js +1 -1
  51. package/dist/components/public-footer/index.js +1 -1
  52. package/dist/components/radiograph-panel/index.js +1 -1
  53. package/dist/components/reviews-panel/index.js +1 -1
  54. package/dist/components/reviews-panel/reviews-panel.d.ts +7 -0
  55. package/dist/components/reviews-panel/reviews-panel.d.ts.map +1 -1
  56. package/dist/components/rich-text-editor/index.js +1 -1
  57. package/dist/components/task-editor/index.js +1 -1
  58. package/dist/components/widget-picker/index.js +1 -1
  59. package/dist/core.css +1 -1
  60. package/dist/host-legacy.css +17 -1
  61. package/dist/i18n/locales/ar.d.ts +1 -0
  62. package/dist/i18n/locales/ar.d.ts.map +1 -1
  63. package/dist/i18n/locales/ar.js +1 -0
  64. package/dist/i18n/locales/ar.js.map +1 -1
  65. package/dist/i18n/locales/de.d.ts +1 -0
  66. package/dist/i18n/locales/de.d.ts.map +1 -1
  67. package/dist/i18n/locales/de.js +1 -0
  68. package/dist/i18n/locales/de.js.map +1 -1
  69. package/dist/i18n/locales/el.d.ts +1 -0
  70. package/dist/i18n/locales/el.d.ts.map +1 -1
  71. package/dist/i18n/locales/el.js +1 -0
  72. package/dist/i18n/locales/el.js.map +1 -1
  73. package/dist/i18n/locales/en.d.ts +1 -0
  74. package/dist/i18n/locales/en.d.ts.map +1 -1
  75. package/dist/i18n/locales/en.js +1 -0
  76. package/dist/i18n/locales/en.js.map +1 -1
  77. package/dist/i18n/locales/es.d.ts +1 -0
  78. package/dist/i18n/locales/es.d.ts.map +1 -1
  79. package/dist/i18n/locales/es.js +1 -0
  80. package/dist/i18n/locales/es.js.map +1 -1
  81. package/dist/i18n/locales/fr.d.ts +1 -0
  82. package/dist/i18n/locales/fr.d.ts.map +1 -1
  83. package/dist/i18n/locales/fr.js +1 -0
  84. package/dist/i18n/locales/fr.js.map +1 -1
  85. package/dist/i18n/locales/hi.d.ts +1 -0
  86. package/dist/i18n/locales/hi.d.ts.map +1 -1
  87. package/dist/i18n/locales/hi.js +1 -0
  88. package/dist/i18n/locales/hi.js.map +1 -1
  89. package/dist/i18n/locales/it.d.ts +1 -0
  90. package/dist/i18n/locales/it.d.ts.map +1 -1
  91. package/dist/i18n/locales/it.js +1 -0
  92. package/dist/i18n/locales/it.js.map +1 -1
  93. package/dist/i18n/locales/ja.d.ts +1 -0
  94. package/dist/i18n/locales/ja.d.ts.map +1 -1
  95. package/dist/i18n/locales/ja.js +1 -0
  96. package/dist/i18n/locales/ja.js.map +1 -1
  97. package/dist/i18n/locales/nl.d.ts +1 -0
  98. package/dist/i18n/locales/nl.d.ts.map +1 -1
  99. package/dist/i18n/locales/nl.js +1 -0
  100. package/dist/i18n/locales/nl.js.map +1 -1
  101. package/dist/i18n/locales/pl.d.ts +1 -0
  102. package/dist/i18n/locales/pl.d.ts.map +1 -1
  103. package/dist/i18n/locales/pl.js +1 -0
  104. package/dist/i18n/locales/pl.js.map +1 -1
  105. package/dist/i18n/locales/pt.d.ts +1 -0
  106. package/dist/i18n/locales/pt.d.ts.map +1 -1
  107. package/dist/i18n/locales/pt.js +1 -0
  108. package/dist/i18n/locales/pt.js.map +1 -1
  109. package/dist/i18n/locales/ro.d.ts +1 -0
  110. package/dist/i18n/locales/ro.d.ts.map +1 -1
  111. package/dist/i18n/locales/ro.js +1 -0
  112. package/dist/i18n/locales/ro.js.map +1 -1
  113. package/dist/i18n/locales/ru.d.ts +1 -0
  114. package/dist/i18n/locales/ru.d.ts.map +1 -1
  115. package/dist/i18n/locales/ru.js +1 -0
  116. package/dist/i18n/locales/ru.js.map +1 -1
  117. package/dist/i18n/locales/sq.d.ts +1 -0
  118. package/dist/i18n/locales/sq.d.ts.map +1 -1
  119. package/dist/i18n/locales/sq.js +1 -0
  120. package/dist/i18n/locales/sq.js.map +1 -1
  121. package/dist/i18n/locales/sv.d.ts +1 -0
  122. package/dist/i18n/locales/sv.d.ts.map +1 -1
  123. package/dist/i18n/locales/sv.js +1 -0
  124. package/dist/i18n/locales/sv.js.map +1 -1
  125. package/dist/i18n/locales/tr.d.ts +1 -0
  126. package/dist/i18n/locales/tr.d.ts.map +1 -1
  127. package/dist/i18n/locales/tr.js +1 -0
  128. package/dist/i18n/locales/tr.js.map +1 -1
  129. package/dist/i18n/locales/zh.d.ts +1 -0
  130. package/dist/i18n/locales/zh.d.ts.map +1 -1
  131. package/dist/i18n/locales/zh.js +1 -0
  132. package/dist/i18n/locales/zh.js.map +1 -1
  133. package/dist/index.js +982 -979
  134. package/dist/index.js.map +1 -1
  135. package/dist/locales/ar.json +1 -0
  136. package/dist/locales/de.json +1 -0
  137. package/dist/locales/el.json +1 -0
  138. package/dist/locales/en.json +1 -0
  139. package/dist/locales/es.json +1 -0
  140. package/dist/locales/fr.json +1 -0
  141. package/dist/locales/hi.json +1 -0
  142. package/dist/locales/it.json +1 -0
  143. package/dist/locales/ja.json +1 -0
  144. package/dist/locales/nl.json +1 -0
  145. package/dist/locales/pl.json +1 -0
  146. package/dist/locales/pt.json +1 -0
  147. package/dist/locales/ro.json +1 -0
  148. package/dist/locales/ru.json +1 -0
  149. package/dist/locales/sq.json +1 -0
  150. package/dist/locales/sv.json +1 -0
  151. package/dist/locales/tr.json +1 -0
  152. package/dist/locales/zh.json +1 -0
  153. package/dist/patterns/anamnesis/index.js +1 -1
  154. package/dist/patterns/calendar/index.js +1 -1
  155. package/dist/patterns/help-me-write-templates/index.js +1 -1
  156. package/dist/patterns/signature-request/index.js +1 -1
  157. package/dist/patterns/signed-out-frame/index.js +1 -1
  158. package/dist/tokens.css +1 -6
  159. package/package.json +6 -1
  160. package/dist/_chunks/dialog-C7RWaH08.js.map +0 -1
  161. package/dist/_chunks/public-footer-3Jy1zHSs.js.map +0 -1
  162. package/dist/_chunks/reviews-panel-CtrwZOUS.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfadocs/ui-kit-debug",
3
- "version": "1.109.0",
3
+ "version": "1.111.0",
4
4
  "type": "module",
5
5
  "description": "AlfaDocs shared design system — tokens, components, patterns, and translations for platform, booking, and alfascribe. (debug build — identical runtime to @alfadocs/ui-kit, ships source maps for symbolication).",
6
6
  "license": "BUSL-1.1",
@@ -287,6 +287,10 @@
287
287
  "types": "./dist/components/data-table/index.d.ts",
288
288
  "import": "./dist/components/data-table/index.js"
289
289
  },
290
+ "./data-grid": {
291
+ "types": "./dist/components/data-grid/index.d.ts",
292
+ "import": "./dist/components/data-grid/index.js"
293
+ },
290
294
  "./data-table-tabs": {
291
295
  "types": "./dist/components/data-table-tabs/index.d.ts",
292
296
  "import": "./dist/components/data-table-tabs/index.js"
@@ -1238,6 +1242,7 @@
1238
1242
  "@radix-ui/react-switch": "^1.1.3",
1239
1243
  "@radix-ui/react-tabs": "^1.1.12",
1240
1244
  "@radix-ui/react-tooltip": "^1.2.8",
1245
+ "@tanstack/react-table": "9.3.1",
1241
1246
  "@tanstack/react-virtual": "^3.13.24",
1242
1247
  "class-variance-authority": "^0.7.1",
1243
1248
  "cmdk": "^1.1.1",
@@ -1 +0,0 @@
1
- {"version":3,"file":"dialog-C7RWaH08.js","sources":["../../src/components/dialog/dialog.agent.ts","../../src/components/dialog/dialog.tsx"],"sourcesContent":["import type { AgentAdapter } from '../../agent/types';\nimport type { DialogHandle } from './dialog';\n\n/**\n * Dialog wraps Radix Dialog. The curated handle (`DialogHandle`) bridges the\n * Radix controlled-or-uncontrolled `open` state so an agent can observe and\n * drive the dialog without owning the React state itself.\n */\nexport const dialogAgent: AgentAdapter<DialogHandle> = {\n id: 'dialog',\n capabilities: ['open', 'close', 'dismiss'],\n state: {\n isOpen: {\n type: 'boolean',\n descriptionKey: 'ui.agent.dialog.state.isOpen',\n description: 'True when the dialog is currently open.',\n read: (handle) => handle.getIsOpen(),\n },\n },\n actions: {\n open: {\n safety: 'read',\n idempotent: true,\n descriptionKey: 'ui.agent.dialog.actions.open',\n description: 'Open the dialog.',\n invoke: (handle) => {\n handle.open();\n },\n },\n close: {\n safety: 'read',\n idempotent: true,\n descriptionKey: 'ui.agent.dialog.actions.close',\n description: 'Close the dialog. Reversible by reopening.',\n invoke: (handle) => {\n handle.close();\n },\n },\n dismiss: {\n safety: 'destructive',\n idempotent: true,\n descriptionKey: 'ui.agent.dialog.actions.dismiss',\n description:\n 'Dismiss the dialog. Loses any in-progress input that has not been committed.',\n invoke: (handle) => {\n handle.close();\n },\n },\n },\n domHooks: {\n root: { attr: 'data-component', value: 'dialog' },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description: 'Sourced from the id prop on Dialog.Root.',\n },\n },\n};\n","import {\n createContext,\n forwardRef,\n useContext,\n useEffect,\n useMemo,\n useRef,\n type ComponentPropsWithoutRef,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\nimport * as RadixDialog from '@radix-ui/react-dialog';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { X } from 'lucide-react';\nimport { useControllableState } from '../../hooks/use-controllable-state';\nimport { useAgentRegistration } from '../../agent';\nimport { useNonModalTabExit } from '../_shared/non-modal-tab-exit';\nimport { dialogAgent } from './dialog.agent';\n\n// The `ds-dialog__*` class names below are the framework-agnostic contract\n// defined in src/styles.css (@layer ui-kit-components). The React component\n// renders the SAME classes a vanilla (non-React) host hand-authors — the\n// surface (`ds-dialog__surface` + its size modifiers), the close button\n// (`ds-dialog__close`), and the body slot (`ds-dialog__body`) — so there is\n// ONE visual skin (scrim, radius, elevation, bg, padding, centering, sizing,\n// close-button glyph + hover/focus) and the two surfaces can't drift. The few\n// Tailwind utilities that remain are NOT skin: they are the Radix animation\n// hooks (`data-[state=…]:animate-*`) and the surface focus-suppression chrome\n// — neither lives in the vanilla CSS contract. Layer order\n// (`…, utilities, ui-kit-tokens, ui-kit-components`) puts the `ds-dialog__*`\n// rules after Tailwind's utilities, so the contract owns every visual\n// property and the animation utilities only touch `animation` / `outline`.\nconst OVERLAY_CLASSES = [\n 'ds-dialog__backdrop',\n 'ds:backdrop-blur-sm',\n 'ds:data-[state=open]:animate-in ds:data-[state=open]:fade-in-0',\n 'ds:data-[state=closed]:animate-out ds:data-[state=closed]:fade-out-0',\n 'ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none',\n].join(' ');\n\n// Animation + focus chrome only — every visual property (position, centering,\n// bg, radius, elevation, padding, sizing) comes from `.ds-dialog__surface`.\nconst SURFACE_CHROME = [\n 'ds:focus-visible:outline-none',\n 'ds:data-[state=open]:animate-in ds:data-[state=open]:fade-in-0 ds:data-[state=open]:zoom-in-95',\n 'ds:data-[state=closed]:animate-out ds:data-[state=closed]:fade-out-0 ds:data-[state=closed]:zoom-out-95',\n 'ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none',\n].join(' ');\n\n// CVA drives only the contract class names per size. `ds-dialog` is the host\n// marker (cascade-reset boundary); `ds-dialog__surface` carries the panel skin;\n// the `--sm` / `--lg` / `--fullscreen` modifiers shift only `max-inline-size`\n// (or the fullscreen geometry) and are defined alongside the base rule in\n// src/styles.css — so size, like the skin, lives in one place.\nconst contentVariants = cva(`ds-dialog ds-dialog__surface ${SURFACE_CHROME}`, {\n variants: {\n size: {\n sm: 'ds-dialog__surface--sm',\n md: '',\n lg: 'ds-dialog__surface--lg',\n fullscreen: 'ds-dialog__surface--fullscreen',\n },\n // `body` pins the header and footer and scrolls only `Dialog.Body` once\n // the content passes the height cap. `surface` (default) scrolls the\n // whole panel as one block.\n scroll: {\n surface: '',\n body: 'ds-dialog__surface--scroll-body',\n },\n // `flush` drops the surface padding for content that brings its own\n // header, scroll box and footer (a docked panel thrown in a dialog).\n flush: {\n true: 'ds-dialog__surface--flush',\n false: '',\n },\n },\n defaultVariants: { size: 'md', scroll: 'surface', flush: false },\n});\n\n// Curated agent-readiness handle — see dialog.agent.ts.\nexport interface DialogHandle {\n getIsOpen: () => boolean;\n open: () => void;\n close: () => void;\n}\n\nexport interface DialogRootProps extends ComponentPropsWithoutRef<\n typeof RadixDialog.Root\n> {\n /** Opaque instance id — forwarded to Dialog.Content as `data-component-id`. */\n id?: string;\n}\n\ninterface DialogContextValue {\n id?: string;\n modal: boolean;\n}\n\nconst DialogContext = createContext<DialogContextValue>({ modal: true });\n\nconst DialogRoot = ({\n children,\n id,\n open: openProp,\n defaultOpen,\n onOpenChange,\n modal = true,\n ...props\n}: DialogRootProps) => {\n const [openRaw, setOpen] = useControllableState<boolean>({\n value: openProp,\n defaultValue: defaultOpen ?? false,\n onChange: onOpenChange,\n });\n const open = openRaw ?? false;\n\n const openRef = useRef(open);\n useEffect(() => {\n openRef.current = open;\n }, [open]);\n\n const handle = useMemo<DialogHandle>(\n () => ({\n getIsOpen: () => openRef.current,\n open: () => setOpen(true),\n close: () => setOpen(false),\n }),\n [setOpen],\n );\n useAgentRegistration(dialogAgent, handle, id);\n\n const context = useMemo(() => ({ id, modal }), [id, modal]);\n\n return (\n <DialogContext.Provider value={context}>\n <RadixDialog.Root\n open={open}\n onOpenChange={setOpen}\n modal={modal}\n {...props}\n >\n {children}\n </RadixDialog.Root>\n </DialogContext.Provider>\n );\n};\nDialogRoot.displayName = 'Dialog.Root';\n\nconst DialogTrigger = forwardRef<\n HTMLButtonElement,\n ComponentPropsWithoutRef<typeof RadixDialog.Trigger>\n>(({ children, ...props }, ref) => (\n <RadixDialog.Trigger ref={ref} {...props}>\n {children}\n </RadixDialog.Trigger>\n));\nDialogTrigger.displayName = 'Dialog.Trigger';\n\nconst DialogClose = forwardRef<\n HTMLButtonElement,\n ComponentPropsWithoutRef<typeof RadixDialog.Close>\n>(({ children, ...props }, ref) => (\n <RadixDialog.Close ref={ref} {...props}>\n {children}\n </RadixDialog.Close>\n));\nDialogClose.displayName = 'Dialog.Close';\n\nexport interface DialogContentProps\n extends\n Omit<ComponentPropsWithoutRef<typeof RadixDialog.Content>, 'role'>,\n VariantProps<typeof contentVariants> {\n size?: 'sm' | 'md' | 'lg' | 'fullscreen';\n variant?: 'default' | 'alert' | 'confirmation';\n /**\n * Hide the top-right dismiss \"X\". The default is **variant-aware**: the X is\n * shown for `variant=\"default\"` and hidden for `variant=\"alert\"` /\n * `\"confirmation\"` (forced-choice surfaces, where the decision is made via\n * the explicit footer actions). An explicit value always wins — pass `false`\n * to force the X onto an alert, or `true` to suppress it on a default dialog.\n */\n hideCloseButton?: boolean;\n /**\n * What scrolls once the content passes the height cap (85dvh). `surface`\n * (default) scrolls the whole panel, title and footer included. `body`\n * keeps `Dialog.Header` and `Dialog.Footer` in view and scrolls only\n * `Dialog.Body`, so the content must sit in a `Dialog.Body`. Below 480px\n * of viewport height it falls back to scrolling the whole panel.\n */\n scroll?: 'surface' | 'body';\n /**\n * No surface padding, and the surface becomes a column that does not\n * scroll itself: for one child that brings its own header, scroll box\n * and footer, such as a side panel shown in a dialog. That child must\n * fill the column (`flex: 1; min-block-size: 0`) and scroll inside.\n */\n flush?: boolean;\n className?: string;\n children: ReactNode;\n}\n\nconst DialogContent = forwardRef<HTMLDivElement, DialogContentProps>(\n (\n {\n size = 'md',\n variant = 'default',\n scroll = 'surface',\n flush = false,\n hideCloseButton,\n className,\n children,\n onKeyDown,\n ...props\n },\n ref,\n ) => {\n const { t } = useTranslation('ui');\n const { id: instanceId, modal } = useContext(DialogContext);\n const exitOnTab = useNonModalTabExit(!modal);\n // Only forward `role` when we actually need to override Radix's default\n // (`role=\"dialog\"`). Passing `role={undefined}` explicitly wins over\n // Radix's internal assignment and strips the attribute entirely.\n const roleOverride =\n variant === 'alert' ? { role: 'alertdialog' as const } : {};\n\n // Variant-aware close default: plain dialogs expose the dismiss X; alert\n // and confirmation dialogs are forced-choice (no X) so the decision is\n // made via the explicit footer actions. An explicit `hideCloseButton`\n // always wins.\n const closeHidden = hideCloseButton ?? variant !== 'default';\n\n return (\n <RadixDialog.Portal>\n <RadixDialog.Overlay\n data-component=\"dialog-overlay\"\n className={OVERLAY_CLASSES}\n />\n <RadixDialog.Content\n ref={ref}\n {...roleOverride}\n data-component=\"dialog\"\n data-component-id={instanceId}\n className={contentVariants({ size, scroll, flush, className })}\n {...props}\n onKeyDown={(event) => {\n onKeyDown?.(event);\n exitOnTab(event);\n }}\n >\n {children}\n\n {!closeHidden && (\n // The close is a plain button carrying the `ds-dialog__close`\n // contract class — the SAME class a vanilla host renders. The CSS\n // (src/styles.css) owns its position, sizing, glyph size, hover\n // wash, and focus ring from tokens, so the React and non-React\n // close buttons are identical and can't drift.\n <RadixDialog.Close\n type=\"button\"\n className=\"ds-dialog__close\"\n aria-label={t('common.close', 'Close')}\n >\n <X aria-hidden=\"true\" />\n </RadixDialog.Close>\n )}\n </RadixDialog.Content>\n </RadixDialog.Portal>\n );\n },\n);\nDialogContent.displayName = 'Dialog.Content';\n\nconst DialogHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={['ds-dialog__header', className].filter(Boolean).join(' ')}\n {...props}\n />\n ),\n);\nDialogHeader.displayName = 'Dialog.Header';\n\n// Optional body slot. Carries the `ds-dialog__body` contract class — the SAME\n// class a vanilla host renders — so content / iframe slots match between the\n// React and non-React surfaces and can't drift. Additive: content placed\n// directly in Dialog.Content (without this wrapper) still works as before.\nconst DialogBody = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={['ds-dialog__body', className].filter(Boolean).join(' ')}\n {...props}\n />\n ),\n);\nDialogBody.displayName = 'Dialog.Body';\n\nconst DialogTitle = forwardRef<\n HTMLHeadingElement,\n ComponentPropsWithoutRef<typeof RadixDialog.Title>\n>(({ className, ...props }, ref) => (\n <RadixDialog.Title\n ref={ref}\n className={['ds-dialog__title type-title-card ds:break-words', className]\n .filter(Boolean)\n .join(' ')}\n {...props}\n />\n));\nDialogTitle.displayName = 'Dialog.Title';\n\nconst DialogDescription = forwardRef<\n HTMLParagraphElement,\n ComponentPropsWithoutRef<typeof RadixDialog.Description>\n>(({ className, ...props }, ref) => (\n <RadixDialog.Description\n ref={ref}\n className={['type-body-sm ds:text-[var(--muted-foreground)]', className]\n .filter(Boolean)\n .join(' ')}\n {...props}\n />\n));\nDialogDescription.displayName = 'Dialog.Description';\n\ninterface DialogFooterProps extends HTMLAttributes<HTMLDivElement> {\n divider?: boolean;\n}\n\n// Below 384px of footer width (a phone, whatever the dialog size) every\n// action goes full-width on its own line, in DOM order, so each label fits on\n// one line. The footer is its own container; px so a host root cannot move it.\nconst footerVariants = cva(\n [\n 'ds:@container/dialog-footer',\n 'ds:flex ds:flex-wrap ds:items-center ds:justify-end ds:gap-[var(--spacing-sm)]',\n 'ds:@max-[384px]/dialog-footer:[&>*]:w-full',\n 'ds:mt-[var(--spacing-md)]',\n ].join(' '),\n {\n variants: {\n divider: {\n true: 'ds:border-t ds:border-[color:var(--border)] ds:pt-[var(--spacing-md)]',\n false: '',\n },\n },\n defaultVariants: { divider: false },\n },\n);\n\nconst DialogFooter = forwardRef<HTMLDivElement, DialogFooterProps>(\n ({ className, divider = false, ...props }, ref) => (\n <div\n ref={ref}\n className={footerVariants({ divider, className })}\n {...props}\n />\n ),\n);\nDialogFooter.displayName = 'Dialog.Footer';\n\nexport const Dialog = Object.assign(DialogRoot, {\n Root: DialogRoot,\n Trigger: DialogTrigger,\n Content: DialogContent,\n Header: DialogHeader,\n Title: DialogTitle,\n Description: DialogDescription,\n Body: DialogBody,\n Footer: DialogFooter,\n Close: DialogClose,\n});\n\nexport type { DialogFooterProps };\n"],"names":["dialogAgent","handle","OVERLAY_CLASSES","SURFACE_CHROME","contentVariants","cva","DialogContext","createContext","DialogRoot","children","id","openProp","defaultOpen","onOpenChange","modal","props","openRaw","setOpen","useControllableState","open","openRef","useRef","useEffect","useMemo","useAgentRegistration","context","jsx","RadixDialog","DialogTrigger","forwardRef","ref","DialogClose","DialogContent","size","variant","scroll","flush","hideCloseButton","className","onKeyDown","t","useTranslation","instanceId","useContext","exitOnTab","useNonModalTabExit","roleOverride","closeHidden","jsxs","event","X","DialogHeader","DialogBody","DialogTitle","DialogDescription","footerVariants","DialogFooter","divider","Dialog"],"mappings":";;;;;;;;;AAQO,MAAMA,IAA0C;AAAA,EACrD,IAAI;AAAA,EACJ,cAAc,CAAC,QAAQ,SAAS,SAAS;AAAA,EACzC,OAAO;AAAA,IACL,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,gBAAgB;AAAA,MAChB,aAAa;AAAA,MACb,MAAM,CAACC,MAAWA,EAAO,UAAA;AAAA,IAAU;AAAA,EACrC;AAAA,EAEF,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,aAAa;AAAA,MACb,QAAQ,CAACA,MAAW;AAClB,QAAAA,EAAO,KAAA;AAAA,MACT;AAAA,IAAA;AAAA,IAEF,OAAO;AAAA,MACL,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,aAAa;AAAA,MACb,QAAQ,CAACA,MAAW;AAClB,QAAAA,EAAO,MAAA;AAAA,MACT;AAAA,IAAA;AAAA,IAEF,SAAS;AAAA,MACP,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,aACE;AAAA,MACF,QAAQ,CAACA,MAAW;AAClB,QAAAA,EAAO,MAAA;AAAA,MACT;AAAA,IAAA;AAAA,EACF;AAAA,EAEF,UAAU;AAAA,IACR,MAAM,EAAE,MAAM,kBAAkB,OAAO,SAAA;AAAA,IACvC,YAAY;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,aAAa;AAAA,IAAA;AAAA,EACf;AAEJ,GCxBMC,IAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG,GAIJC,IAAiB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG,GAOJC,IAAkBC,EAAI,gCAAgCF,CAAc,IAAI;AAAA,EAC5E,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,YAAY;AAAA,IAAA;AAAA;AAAA;AAAA;AAAA,IAKd,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,MAAM;AAAA,IAAA;AAAA;AAAA;AAAA,IAIR,OAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,IAAA;AAAA,EACT;AAAA,EAEF,iBAAiB,EAAE,MAAM,MAAM,QAAQ,WAAW,OAAO,GAAA;AAC3D,CAAC,GAqBKG,IAAgBC,EAAkC,EAAE,OAAO,IAAM,GAEjEC,IAAa,CAAC;AAAA,EAClB,UAAAC;AAAA,EACA,IAAAC;AAAA,EACA,MAAMC;AAAA,EACN,aAAAC;AAAA,EACA,cAAAC;AAAA,EACA,OAAAC,IAAQ;AAAA,EACR,GAAGC;AACL,MAAuB;AACrB,QAAM,CAACC,GAASC,CAAO,IAAIC,EAA8B;AAAA,IACvD,OAAOP;AAAA,IACP,cAAcC,KAAe;AAAA,IAC7B,UAAUC;AAAA,EAAA,CACX,GACKM,IAAOH,KAAW,IAElBI,IAAUC,EAAOF,CAAI;AAC3B,EAAAG,EAAU,MAAM;AACd,IAAAF,EAAQ,UAAUD;AAAA,EACpB,GAAG,CAACA,CAAI,CAAC;AAET,QAAMlB,IAASsB;AAAA,IACb,OAAO;AAAA,MACL,WAAW,MAAMH,EAAQ;AAAA,MACzB,MAAM,MAAMH,EAAQ,EAAI;AAAA,MACxB,OAAO,MAAMA,EAAQ,EAAK;AAAA,IAAA;AAAA,IAE5B,CAACA,CAAO;AAAA,EAAA;AAEV,EAAAO,EAAqBxB,GAAaC,GAAQS,CAAE;AAE5C,QAAMe,IAAUF,EAAQ,OAAO,EAAE,IAAAb,GAAI,OAAAI,MAAU,CAACJ,GAAII,CAAK,CAAC;AAE1D,SACE,gBAAAY,EAACpB,EAAc,UAAd,EAAuB,OAAOmB,GAC7B,UAAA,gBAAAC;AAAA,IAACC,EAAY;AAAA,IAAZ;AAAA,MACC,MAAAR;AAAA,MACA,cAAcF;AAAA,MACd,OAAAH;AAAA,MACC,GAAGC;AAAA,MAEH,UAAAN;AAAA,IAAA;AAAA,EAAA,GAEL;AAEJ;AACAD,EAAW,cAAc;AAEzB,MAAMoB,IAAgBC,EAGpB,CAAC,EAAE,UAAApB,GAAU,GAAGM,EAAA,GAASe,MACzB,gBAAAJ,EAACC,EAAY,SAAZ,EAAoB,KAAAG,GAAW,GAAGf,GAChC,UAAAN,GACH,CACD;AACDmB,EAAc,cAAc;AAE5B,MAAMG,IAAcF,EAGlB,CAAC,EAAE,UAAApB,GAAU,GAAGM,EAAA,GAASe,MACzB,gBAAAJ,EAACC,EAAY,OAAZ,EAAkB,KAAAG,GAAW,GAAGf,GAC9B,UAAAN,GACH,CACD;AACDsB,EAAY,cAAc;AAmC1B,MAAMC,IAAgBH;AAAA,EACpB,CACE;AAAA,IACE,MAAAI,IAAO;AAAA,IACP,SAAAC,IAAU;AAAA,IACV,QAAAC,IAAS;AAAA,IACT,OAAAC,IAAQ;AAAA,IACR,iBAAAC;AAAA,IACA,WAAAC;AAAA,IACA,UAAA7B;AAAA,IACA,WAAA8B;AAAA,IACA,GAAGxB;AAAA,EAAA,GAELe,MACG;AACH,UAAM,EAAE,GAAAU,EAAA,IAAMC,EAAe,IAAI,GAC3B,EAAE,IAAIC,GAAY,OAAA5B,EAAA,IAAU6B,EAAWrC,CAAa,GACpDsC,IAAYC,EAAmB,CAAC/B,CAAK,GAIrCgC,IACJZ,MAAY,UAAU,EAAE,MAAM,cAAA,IAA2B,CAAA,GAMrDa,IAAcV,KAAmBH,MAAY;AAEnD,WACE,gBAAAc,EAACrB,EAAY,QAAZ,EACC,UAAA;AAAA,MAAA,gBAAAD;AAAA,QAACC,EAAY;AAAA,QAAZ;AAAA,UACC,kBAAe;AAAA,UACf,WAAWzB;AAAA,QAAA;AAAA,MAAA;AAAA,MAEb,gBAAA8C;AAAA,QAACrB,EAAY;AAAA,QAAZ;AAAA,UACC,KAAAG;AAAA,UACC,GAAGgB;AAAA,UACJ,kBAAe;AAAA,UACf,qBAAmBJ;AAAA,UACnB,WAAWtC,EAAgB,EAAE,MAAA6B,GAAM,QAAAE,GAAQ,OAAAC,GAAO,WAAAE,GAAW;AAAA,UAC5D,GAAGvB;AAAA,UACJ,WAAW,CAACkC,MAAU;AACpB,YAAAV,KAAA,QAAAA,EAAYU,IACZL,EAAUK,CAAK;AAAA,UACjB;AAAA,UAEC,UAAA;AAAA,YAAAxC;AAAA,YAEA,CAACsC;AAAA;AAAA;AAAA;AAAA;AAAA,YAMA,gBAAArB;AAAA,cAACC,EAAY;AAAA,cAAZ;AAAA,gBACC,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,cAAYa,EAAE,gBAAgB,OAAO;AAAA,gBAErC,UAAA,gBAAAd,EAACwB,GAAA,EAAE,eAAY,OAAA,CAAO;AAAA,cAAA;AAAA,YAAA;AAAA,UACxB;AAAA,QAAA;AAAA,MAAA;AAAA,IAEJ,GACF;AAAA,EAEJ;AACF;AACAlB,EAAc,cAAc;AAE5B,MAAMmB,IAAetB;AAAA,EACnB,CAAC,EAAE,WAAAS,GAAW,GAAGvB,EAAA,GAASe,MACxB,gBAAAJ;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAAI;AAAA,MACA,WAAW,CAAC,qBAAqBQ,CAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,MACnE,GAAGvB;AAAA,IAAA;AAAA,EAAA;AAGV;AACAoC,EAAa,cAAc;AAM3B,MAAMC,IAAavB;AAAA,EACjB,CAAC,EAAE,WAAAS,GAAW,GAAGvB,EAAA,GAASe,MACxB,gBAAAJ;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAAI;AAAA,MACA,WAAW,CAAC,mBAAmBQ,CAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,MACjE,GAAGvB;AAAA,IAAA;AAAA,EAAA;AAGV;AACAqC,EAAW,cAAc;AAEzB,MAAMC,IAAcxB,EAGlB,CAAC,EAAE,WAAAS,GAAW,GAAGvB,EAAA,GAASe,MAC1B,gBAAAJ;AAAA,EAACC,EAAY;AAAA,EAAZ;AAAA,IACC,KAAAG;AAAA,IACA,WAAW,CAAC,mDAAmDQ,CAAS,EACrE,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,IACV,GAAGvB;AAAA,EAAA;AACN,CACD;AACDsC,EAAY,cAAc;AAE1B,MAAMC,IAAoBzB,EAGxB,CAAC,EAAE,WAAAS,GAAW,GAAGvB,EAAA,GAASe,MAC1B,gBAAAJ;AAAA,EAACC,EAAY;AAAA,EAAZ;AAAA,IACC,KAAAG;AAAA,IACA,WAAW,CAAC,kDAAkDQ,CAAS,EACpE,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,IACV,GAAGvB;AAAA,EAAA;AACN,CACD;AACDuC,EAAkB,cAAc;AAShC,MAAMC,IAAiBlD;AAAA,EACrB;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,EACA,KAAK,GAAG;AAAA,EACV;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,MAAM;AAAA,QACN,OAAO;AAAA,MAAA;AAAA,IACT;AAAA,IAEF,iBAAiB,EAAE,SAAS,GAAA;AAAA,EAAM;AAEtC,GAEMmD,IAAe3B;AAAA,EACnB,CAAC,EAAE,WAAAS,GAAW,SAAAmB,IAAU,IAAO,GAAG1C,EAAA,GAASe,MACzC,gBAAAJ;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAAI;AAAA,MACA,WAAWyB,EAAe,EAAE,SAAAE,GAAS,WAAAnB,GAAW;AAAA,MAC/C,GAAGvB;AAAA,IAAA;AAAA,EAAA;AAGV;AACAyC,EAAa,cAAc;AAEpB,MAAME,KAAS,OAAO,OAAOlD,GAAY;AAAA,EAC9C,MAAMA;AAAA,EACN,SAASoB;AAAA,EACT,SAASI;AAAA,EACT,QAAQmB;AAAA,EACR,OAAOE;AAAA,EACP,aAAaC;AAAA,EACb,MAAMF;AAAA,EACN,QAAQI;AAAA,EACR,OAAOzB;AACT,CAAC;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"public-footer-3Jy1zHSs.js","sources":["../../src/components/public-footer/legal-urls.ts","../../src/components/public-footer/socials.ts","../../src/components/public-footer/public-footer.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* PublicFooter — legally-required URL composition. */\n/* */\n/* GDPR + Italian/German commercial law require Privacy, Terms-of-use, */\n/* entity name and VAT to be visible on every public AlfaDocs surface. */\n/* The kit owns the slug table so a privacy-policy URL change is a single */\n/* kit bump rather than three repo edits. */\n/* */\n/* For locales without an explicit canonical slug, we fall back to the */\n/* Italian slugs — alfadocs.com defaults to Italian and unlocalised pages */\n/* render the IT version. Marketing supplies updates over time. */\n/* -------------------------------------------------------------------- */\n\nexport const DEFAULT_MARKETING_BASE_URL = 'https://www.alfadocs.com';\n\nexport type LegalUrlKey = 'privacy' | 'terms' | 'contacts';\n\nexport type LegalSlugMap = Record<LegalUrlKey, string>;\n\n/** Per-locale slug override; missing locales fall back to the kit default. */\nexport type LegalUrlOverride = Record<string, string>;\n\nexport interface LegalUrlOverrides {\n privacy?: LegalUrlOverride;\n terms?: LegalUrlOverride;\n contacts?: LegalUrlOverride;\n}\n\n/* Italian default — also used as the fallback for any locale without an\n * explicit entry below. */\nconst ITALIAN_SLUGS: LegalSlugMap = {\n privacy: '/privacy-prenotazione-online',\n terms: '/it/condizioni-di-utilizzo',\n contacts: '/it/contattaci',\n};\n\n/* Canonical per-locale slug table. Explicit entries come from marketing;\n * everything else inherits ITALIAN_SLUGS via the resolver below. */\nconst SLUGS: Record<string, LegalSlugMap> = {\n it: ITALIAN_SLUGS,\n en: {\n privacy: '/en/privacy',\n terms: '/en/terms-of-use',\n contacts: '/en/contact-us',\n },\n de: {\n privacy: '/de/datenschutz',\n terms: '/de/nutzungsbedingungen',\n contacts: '/de/kontakt',\n },\n fr: {\n privacy: '/fr/confidentialite',\n terms: '/fr/conditions-utilisation',\n contacts: '/fr/contact',\n },\n es: {\n privacy: '/es/privacidad',\n terms: '/es/condiciones-uso',\n contacts: '/es/contacto',\n },\n};\n\nconst HTTPS_PREFIX = 'https://';\n\nfunction normaliseBaseUrl(input: string | undefined): string {\n if (!input) return DEFAULT_MARKETING_BASE_URL;\n if (!input.startsWith(HTTPS_PREFIX)) {\n if (import.meta.env.DEV) {\n // eslint-disable-next-line no-console\n console.warn(\n `[PublicFooter] marketingBaseUrl must start with \"https://\" — falling back to ${DEFAULT_MARKETING_BASE_URL}. Received: \"${input}\"`,\n );\n }\n return DEFAULT_MARKETING_BASE_URL;\n }\n // Strip trailing slash so composed URLs don't double up.\n return input.replace(/\\/+$/, '');\n}\n\nfunction pickSlug(\n locale: string,\n key: LegalUrlKey,\n overrides: LegalUrlOverrides | undefined,\n): string {\n const override = overrides?.[key]?.[locale];\n if (override) return override;\n const localeSlug = SLUGS[locale]?.[key];\n if (localeSlug) return localeSlug;\n return ITALIAN_SLUGS[key];\n}\n\n/** Resolved legal URLs for the active locale + baseUrl + overrides. */\nexport interface ResolvedLegalUrls {\n privacy: string;\n terms: string;\n contacts: string;\n}\n\nexport function composeLegalUrls(\n locale: string,\n marketingBaseUrl: string | undefined,\n overrides?: LegalUrlOverrides,\n): ResolvedLegalUrls {\n const base = normaliseBaseUrl(marketingBaseUrl);\n const lang = (locale || 'it').split('-')[0]; // strip region — 'pt-BR' → 'pt'\n return {\n privacy: `${base}${pickSlug(lang, 'privacy', overrides)}`,\n terms: `${base}${pickSlug(lang, 'terms', overrides)}`,\n contacts: `${base}${pickSlug(lang, 'contacts', overrides)}`,\n };\n}\n","/* -------------------------------------------------------------------- */\n/* PublicFooter — default social-network URLs + brand icon components. */\n/* */\n/* AlfaDocs's four canonical social profiles. Consumers can subset via */\n/* `socials={[...]}` or override individual `href` values. */\n/* */\n/* Icons are shared inline SVG assets (paths from simpleicons.org) */\n/* rendered with `currentColor` so they inherit the footer's */\n/* `--muted-foreground` ink. Lucide-react does not ship brand glyphs, and */\n/* pulling a separate brand-icon package for four logos is not worth the */\n/* bundle cost. */\n/* -------------------------------------------------------------------- */\n\nimport { SOCIAL_LINK_ICONS } from '../_shared/social-brand-icons';\n\nexport const SOCIAL_NETWORKS = [\n 'linkedin',\n 'youtube',\n 'instagram',\n 'facebook',\n] as const;\n\nexport type SocialNetwork = (typeof SOCIAL_NETWORKS)[number];\n\nexport const DEFAULT_SOCIAL_URLS: Record<SocialNetwork, string> = {\n linkedin: 'https://www.linkedin.com/company/alfadocs',\n youtube: 'https://www.youtube.com/@alfadocs',\n instagram: 'https://www.instagram.com/alfadocs',\n facebook: 'https://www.facebook.com/alfadocs',\n};\n\nexport const SOCIAL_ICONS = {\n linkedin: SOCIAL_LINK_ICONS.linkedin,\n youtube: SOCIAL_LINK_ICONS.youtube,\n instagram: SOCIAL_LINK_ICONS.instagram,\n facebook: SOCIAL_LINK_ICONS.facebook,\n};\n","import {\n forwardRef,\n useImperativeHandle,\n useMemo,\n type ReactNode,\n} from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { Link } from '../link';\nimport { Separator } from '../separator';\nimport { Logo } from '../logo';\nimport {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from '../accordion';\nimport {\n composeLegalUrls,\n type LegalUrlOverrides,\n type ResolvedLegalUrls,\n} from './legal-urls';\nimport {\n DEFAULT_SOCIAL_URLS,\n SOCIAL_ICONS,\n SOCIAL_NETWORKS,\n type SocialNetwork,\n} from './socials';\n\n/* ------------------------------------------------------------------ */\n/* Constants */\n/* ------------------------------------------------------------------ */\n\nconst ENTITY_NAME = 'AlfaDocs GmbH';\nconst ENTITY_VAT = 'P.IVA DE301955405';\n\n/* ------------------------------------------------------------------ */\n/* CVA */\n/* ------------------------------------------------------------------ */\n\nconst rootVariants = cva(\n [\n 'ds:w-full',\n 'ds:text-[color:var(--muted-foreground)]',\n 'ds:text-[length:var(--font-size-sm)]',\n 'ds:bg-[var(--background)]',\n ].join(' '),\n {\n variants: {\n variant: {\n minimal: [\n 'ds:py-[var(--spacing-sm)]',\n 'ds:ps-[var(--spacing-md)] ds:pe-[var(--spacing-md)]',\n 'ds:border-block-start ds:border-[var(--border)]',\n ].join(' '),\n // 0.30.5: compact got a polish pass — bumped vertical padding to\n // `lg` (~24px) so the footer reads as a real landmark, not a\n // one-line afterthought, plus a stronger top border in\n // accessible mode (the default `--border` clears 3:1 in light /\n // dark; accessible bumps to a heavier weight).\n //\n // 0.31.2: forced-colors top-border fallback. Windows High\n // Contrast strips `--border` to whatever the system maps it\n // to — which is often invisible against `Canvas`. Pinning to\n // `CanvasText` keeps the landmark edge visible for users on\n // strict HC themes.\n compact: [\n 'ds:py-[var(--spacing-lg)]',\n 'ds:ps-[var(--spacing-md)] ds:pe-[var(--spacing-md)]',\n 'ds:border-block-start ds:border-[var(--border)]',\n // 2px in the accessible themes, none elsewhere (--accessible-on).\n 'ds:[border-block-start-width:calc(var(--border-width-md)*var(--accessible-on,0))]',\n 'ds:forced-colors:[border-block-start-color:CanvasText]',\n ].join(' '),\n full: [\n 'ds:py-[var(--spacing-xl)]',\n 'ds:ps-[var(--spacing-lg)] ds:pe-[var(--spacing-lg)]',\n 'ds:bg-[var(--muted)]',\n ].join(' '),\n },\n },\n defaultVariants: { variant: 'compact' },\n },\n);\n\n/* ------------------------------------------------------------------ */\n/* Public types */\n/* ------------------------------------------------------------------ */\n\nexport type PublicFooterVariant = NonNullable<\n VariantProps<typeof rootVariants>['variant']\n>;\n\nexport interface PublicFooterLink {\n href: string;\n label: string;\n /** When true, link opens in a new tab with the localised aria-label suffix. */\n external?: boolean;\n}\n\nexport interface PublicFooterLinkGroup {\n /** Stable key — used to look up the column heading via `t('ui.footer.group.<key>')`. */\n key: 'product' | 'resources' | 'company' | 'legal' | (string & {});\n links: PublicFooterLink[];\n}\n\nexport interface PublicFooterLinkSectionLink {\n /** Visible link text. The consumer supplies it, already translated. */\n label: string;\n href: string;\n}\n\nexport interface PublicFooterLinkSection {\n /** Stable key for the group. Also builds the heading `id`. */\n id: string;\n /** Group heading, already translated. */\n title: string;\n links: PublicFooterLinkSectionLink[];\n /** Optional \"see all\" link, shown under the group's links. */\n more?: PublicFooterLinkSectionLink;\n}\n\nexport interface PublicFooterSocial {\n network: SocialNetwork;\n /** Override the kit's default URL. */\n href?: string;\n}\n\nexport interface PublicFooterProps {\n variant: PublicFooterVariant;\n /** Origin for legally-required URLs. Default: 'https://www.alfadocs.com'. */\n marketingBaseUrl?: string;\n /** Override individual legal-link slugs per locale. Rare. */\n legalUrlOverrides?: LegalUrlOverrides;\n /** `full` variant: consumer-supplied link columns. Legal column is always rendered. */\n linkGroups?: PublicFooterLinkGroup[];\n /**\n * A visible block of plain link groups above the footer's own content, in\n * every variant. Each group is a heading and a list of real `<a href>`\n * links (two columns on md and up), with an optional \"more\" link under it;\n * two groups sit side by side from md, stacked on phones. For SEO: it puts\n * the speciality and city pages one hop from every page. The consumer\n * supplies every text, so the kit adds no strings. Without it the footer\n * renders as before.\n */\n linkSections?: PublicFooterLinkSection[];\n /**\n * Accessible name of the `linkSections` navigation landmark, for example\n * \"Explore by speciality and city\". Already translated by the consumer.\n */\n linkSectionsLabel?: string;\n /** `full` variant: subset of social networks. Default: all four. Pass `[]` to hide the row. */\n socials?: PublicFooterSocial[];\n /** `full` variant: custom logo node. Default: kit's wordmark Logo. */\n logoSlot?: ReactNode;\n /**\n * Extra surface-specific links, appended after the standard legal links in\n * every variant. A `<Link>`, an `<a>`, a `<button>` or the kit's\n * `<Button intent=\"link\">` all work: the slot gives anchors the footer link\n * look (muted text, underline on hover, focus ring) in every variant, and in\n * `minimal` bare buttons and `Button intent=\"link\"` too, so a host \"Cookie\n * settings\" button that reopens its consent manager sits in line with\n * Privacy and Terms. In `compact` and `full` host buttons keep their own\n * look. In `minimal` the slot comes after Terms and before\n * the copyright line, so tab order follows reading order, in RTL too. The\n * host owns the label and the click handler. Minimal ignored this slot\n * before 1.91.0.\n */\n extraNavSlot?: ReactNode;\n /** Trust badges (HIPAA, ISO). Rendered at the start of `minimal`'s row, or in the bottom strip of `full`. */\n trustBadgesSlot?: ReactNode;\n /**\n * @default false\n * Render the brand tagline (\"German technology, Italian design\") in\n * the bottom microcopy row of `compact` and `full` variants.\n *\n * 0.32.4: default flipped to `false`. The tagline was loud on\n * booking-website's deployed footer and didn't carry product\n * meaning for end-users; marketing surfaces can opt back in\n * explicitly with `showTagline`. The `footer.tagline` i18n key\n * stays available so the opt-in path still works.\n */\n showTagline?: boolean;\n /**\n * The line with the year, the company name and the VAT number. On by\n * default: a public page must carry it. Turn it off only on a screen\n * that sits inside the app, such as sign-in or registration, where the\n * privacy and terms links are all the footer needs.\n * @default true\n */\n showCopyright?: boolean;\n 'aria-label'?: string;\n id?: string;\n}\n\nexport interface PublicFooterHandle {\n getVariant: () => PublicFooterVariant;\n /** Resolved legal URLs for the current locale + marketingBaseUrl + overrides. */\n getLegalUrls: () => ResolvedLegalUrls;\n}\n\n/* ------------------------------------------------------------------ */\n/* Helpers */\n/* ------------------------------------------------------------------ */\n\n/** Allowlist consumer-supplied URLs — public marketing surface, defence in depth. */\nconst SAFE_HREF_RE = /^(https?:\\/\\/|mailto:|tel:|\\/(?!\\/))/i;\nfunction safeHref(href: string): string {\n return SAFE_HREF_RE.test(href) ? href : '#';\n}\n\n/**\n * Vertical divider between inline links. Hidden while the footer's own\n * width (container `pfoot`) is under 1024px, where the rows wrap and a\n * divider could end up first or last on a line (HTP-7153). The switch\n * follows the container, not the viewport, so narrow embedded footers\n * are covered too. Purely decorative: `aria-hidden`, never announced.\n */\nfunction FooterDivider() {\n return (\n <Separator\n orientation=\"vertical\"\n decorative\n data-slot=\"footer-divider\"\n className=\"ds:hidden ds:@[1024px]/pfoot:block ds:h-4\"\n />\n );\n}\n\n/**\n * Visible link groups above the footer body (HTP-7240). Plain kit Links in\n * lists under an `h2` per group, so crawlers and screen readers read real\n * anchors with real names. Two groups side by side from md, each group's\n * links in two columns; one column of groups on phones.\n */\nfunction LinkSections({\n sections,\n label,\n footerId,\n}: {\n sections: PublicFooterLinkSection[];\n label?: string;\n footerId?: string;\n}) {\n const base = footerId ? `${footerId}-` : '';\n return (\n <nav\n aria-label={label}\n data-slot=\"link-sections\"\n className=\"ds:mb-[var(--spacing-lg)] ds:pb-[var(--spacing-lg)]\"\n >\n <div className=\"ds:mx-auto ds:grid ds:max-w-[64rem] ds:grid-cols-1 ds:gap-[var(--spacing-lg)] ds:md:grid-cols-2\">\n {sections.map((section) => (\n <div\n key={section.id}\n className=\"ds:flex ds:min-w-0 ds:flex-col ds:gap-[var(--spacing-sm)]\"\n >\n <h2\n id={`${base}link-section-${section.id}`}\n className=\"ds:m-0 ds:text-[length:var(--font-size-sm)] ds:font-semibold ds:text-[color:var(--foreground)]\"\n >\n {section.title}\n </h2>\n <ul className=\"ds:m-0 ds:grid ds:list-none ds:grid-cols-2 ds:gap-x-[var(--spacing-md)] ds:gap-y-[var(--spacing-xs)] ds:p-0\">\n {section.links.map((link) => (\n <li key={`${link.href}|${link.label}`} className=\"ds:min-w-0\">\n <Link href={safeHref(link.href)}>{link.label}</Link>\n </li>\n ))}\n </ul>\n {section.more ? (\n <div>\n <Link href={safeHref(section.more.href)}>\n {section.more.label}\n </Link>\n </div>\n ) : null}\n </div>\n ))}\n </div>\n </nav>\n );\n}\n\nfunction CopyrightLine({\n copyright,\n align = 'center',\n}: {\n copyright: string;\n align?: 'start' | 'center';\n}) {\n return (\n <p\n className={[\n 'ds:m-0 ds:text-[color:var(--muted-foreground)]',\n 'ds:text-[length:var(--font-size-sm)]',\n align === 'center' ? 'ds:text-center' : 'ds:text-start',\n ].join(' ')}\n >\n {copyright}\n </p>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* PublicFooter */\n/* ------------------------------------------------------------------ */\n\nexport const PublicFooter = forwardRef<PublicFooterHandle, PublicFooterProps>(\n function PublicFooter(\n {\n variant,\n marketingBaseUrl,\n legalUrlOverrides,\n linkGroups,\n linkSections,\n linkSectionsLabel,\n socials,\n logoSlot,\n extraNavSlot,\n trustBadgesSlot,\n showTagline = false,\n showCopyright = true,\n id,\n 'aria-label': ariaLabel,\n },\n ref,\n ) {\n const { t, i18n } = useTranslation('ui');\n\n const legalUrls = useMemo(\n () =>\n composeLegalUrls(i18n.language, marketingBaseUrl, legalUrlOverrides),\n [i18n.language, marketingBaseUrl, legalUrlOverrides],\n );\n\n useImperativeHandle(\n ref,\n () => ({\n getVariant: () => variant,\n getLegalUrls: () => legalUrls,\n }),\n [variant, legalUrls],\n );\n\n const year = new Date().getFullYear();\n const copyright = showCopyright\n ? t('footer.copyright', {\n year,\n entity: ENTITY_NAME,\n vat: ENTITY_VAT,\n })\n : undefined;\n const tagline = showTagline ? t('footer.tagline') : undefined;\n const landmarkLabel = ariaLabel ?? t('footer.nav.label');\n\n const labels = {\n privacy: t('footer.nav.privacy'),\n terms: t('footer.nav.terms'),\n contacts: t('footer.nav.contacts'),\n };\n\n const resolvedSocials = useMemo<PublicFooterSocial[]>(() => {\n if (socials !== undefined) return socials;\n return SOCIAL_NETWORKS.map((network) => ({ network }));\n }, [socials]);\n\n return (\n <footer\n id={id}\n role=\"contentinfo\"\n aria-label={landmarkLabel}\n data-component=\"public-footer\"\n data-component-id={id}\n data-variant={variant}\n className={rootVariants({ variant })}\n >\n {linkSections && linkSections.length > 0 ? (\n <LinkSections\n sections={linkSections}\n label={linkSectionsLabel}\n footerId={id}\n />\n ) : null}\n {variant === 'minimal' && (\n <MinimalBody\n legalUrls={legalUrls}\n labels={labels}\n copyright={copyright}\n trustBadgesSlot={trustBadgesSlot}\n extraNavSlot={extraNavSlot}\n />\n )}\n {variant === 'compact' && (\n <CompactBody\n legalUrls={legalUrls}\n labels={labels}\n copyright={copyright}\n tagline={tagline}\n socials={resolvedSocials}\n logoSlot={logoSlot}\n extraNavSlot={extraNavSlot}\n t={t}\n />\n )}\n {variant === 'full' && (\n <FullBody\n legalUrls={legalUrls}\n labels={labels}\n copyright={copyright}\n tagline={tagline}\n linkGroups={linkGroups}\n socials={resolvedSocials}\n logoSlot={logoSlot}\n extraNavSlot={extraNavSlot}\n trustBadgesSlot={trustBadgesSlot}\n t={t}\n />\n )}\n </footer>\n );\n },\n);\n\n/* ------------------------------------------------------------------ */\n/* Minimal */\n/* ------------------------------------------------------------------ */\n\n// The minimal row's links are one line of small text, 21px tall: under the\n// 24px WCAG 2.5.8 asks of a target. A transparent `::before` stretches the\n// hit area to 24px (--spacing-lg) in the block direction without moving or\n// resizing anything the eye sees, and to --min-target-size (48px) in the\n// accessible themes, through --accessible-target-floor. Once a link is as\n// tall as that the inset turns positive and the box sits inside the link,\n// which is harmless.\nconst MINIMAL_LINK_HIT_AREA =\n 'ds:relative ds:before:absolute ds:before:inset-x-0 ds:before:inset-y-[calc((var(--accessible-target-floor,var(--spacing-lg))-100%)/-2)] ds:before:content-[\"\"]';\n\n// A host control in `extraNavSlot` looks like the kit's own footer links\n// (`Link intent=\"subtle\"`): muted, no underline until hover, the kit focus\n// ring. These are descendant rules because the host, not the footer, renders\n// the control. Class names are written out in full so Tailwind can see them.\n// On a plain anchor they win over a default `Link`'s own colour.\n\n// Anchors get the look in every variant, but only plain anchors and default\n// or subtle kit Links: anything else the kit renders carries its own\n// `data-intent` (a `Button asChild intent=\"primary\"`, a primary `Link`) and\n// keeps its own colours.\nconst EXTRA_NAV_ANCHORS = [\n 'ds:[&_a:is(:not([data-intent]),[data-intent=default],[data-intent=subtle]):not([data-component=button])]:text-muted-foreground ds:[&_a:is(:not([data-intent]),[data-intent=default],[data-intent=subtle]):not([data-component=button])]:no-underline ds:[&_a:is(:not([data-intent]),[data-intent=default],[data-intent=subtle]):not([data-component=button])]:underline-offset-4',\n 'ds:[&_a:is(:not([data-intent]),[data-intent=default],[data-intent=subtle]):not([data-component=button])]:hover:text-foreground ds:[&_a:is(:not([data-intent]),[data-intent=default],[data-intent=subtle]):not([data-component=button])]:hover:underline ds:[&_a:is(:not([data-intent]),[data-intent=default],[data-intent=subtle]):not([data-component=button])]:active:opacity-80',\n 'ds:[&_a:is(:not([data-intent]),[data-intent=default],[data-intent=subtle]):not([data-component=button])]:focus-visible:outline-[length:var(--focus-ring-width)] ds:[&_a:is(:not([data-intent]),[data-intent=default],[data-intent=subtle]):not([data-component=button])]:focus-visible:outline-solid',\n 'ds:[&_a:is(:not([data-intent]),[data-intent=default],[data-intent=subtle]):not([data-component=button])]:focus-visible:outline-ring ds:[&_a:is(:not([data-intent]),[data-intent=default],[data-intent=subtle]):not([data-component=button])]:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:[&_a:is(:not([data-intent]),[data-intent=default],[data-intent=subtle]):not([data-component=button])]:forced-colors:focus-visible:outline-[CanvasText]',\n].join(' ');\n\n// Buttons are reset and restyled in `minimal` only. In `compact` and `full`\n// the slot has always rendered host buttons untouched, so a kit `Button` or a\n// Radix trigger there keeps its own chrome. In `minimal` a bare `<button>` is\n// reset first (no chrome, inherited font) and a kit `<Button intent=\"link\">`\n// is pulled to the same colour and size.\nconst EXTRA_NAV_BUTTONS = [\n 'ds:[&_button]:m-0 ds:[&_button]:inline ds:[&_button]:h-auto ds:[&_button]:min-h-0 ds:[&_button]:p-0',\n 'ds:[&_button]:cursor-pointer ds:[&_button]:appearance-none ds:[&_button]:border-0 ds:[&_button]:bg-transparent',\n 'ds:[&_button]:rounded-[var(--radius-xs)] ds:[&_button]:font-[inherit] ds:[&_button]:font-normal ds:[&_button]:text-[length:inherit]',\n 'ds:[&_button]:text-muted-foreground ds:[&_button]:no-underline ds:[&_button]:underline-offset-4',\n 'ds:[&_button]:hover:text-foreground ds:[&_button]:hover:underline ds:[&_button]:active:opacity-80',\n 'ds:[&_button]:focus-visible:outline-[length:var(--focus-ring-width)] ds:[&_button]:focus-visible:outline-solid',\n 'ds:[&_button]:focus-visible:outline-ring ds:[&_button]:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:[&_button]:forced-colors:focus-visible:outline-[CanvasText]',\n].join(' ');\n\nconst EXTRA_NAV_MINIMAL = `${EXTRA_NAV_ANCHORS} ${EXTRA_NAV_BUTTONS}`;\n\n// The same 24px (48px accessible) hit area as the legal links, for the\n// anchors and buttons a host puts in the minimal row.\nconst MINIMAL_EXTRA_HIT_AREA =\n 'ds:[&_:is(a,button)]:relative ds:[&_:is(a,button)]:before:absolute ds:[&_:is(a,button)]:before:inset-x-0 ds:[&_:is(a,button)]:before:inset-y-[calc((var(--accessible-target-floor,var(--spacing-lg))-100%)/-2)] ds:[&_:is(a,button)]:before:block ds:[&_:is(a,button)]:before:content-[\"\"]';\n\nfunction MinimalBody({\n legalUrls,\n labels,\n copyright,\n trustBadgesSlot,\n extraNavSlot,\n}: {\n legalUrls: ResolvedLegalUrls;\n labels: { privacy: string; terms: string; contacts: string };\n copyright?: string;\n trustBadgesSlot?: ReactNode;\n extraNavSlot?: ReactNode;\n}) {\n return (\n <div className=\"ds:@container/pfoot ds:w-full\">\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:justify-center ds:gap-x-[var(--spacing-md)] ds:@[1024px]/pfoot:gap-x-[var(--spacing-sm)] ds:gap-y-[var(--spacing-sm)]\">\n {trustBadgesSlot && (\n <span\n data-slot=\"trust-badges\"\n className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-sm)] ds:me-[var(--spacing-sm)]\"\n >\n {trustBadgesSlot}\n </span>\n )}\n <Link\n href={legalUrls.privacy}\n external\n externalIcon={false}\n intent=\"subtle\"\n className={MINIMAL_LINK_HIT_AREA}\n >\n {labels.privacy}\n </Link>\n <FooterDivider />\n <Link\n href={legalUrls.terms}\n external\n externalIcon={false}\n intent=\"subtle\"\n className={MINIMAL_LINK_HIT_AREA}\n >\n {labels.terms}\n </Link>\n {extraNavSlot ? (\n <>\n <FooterDivider />\n <span\n data-slot=\"extra-nav\"\n className={[\n 'ds:inline-flex ds:flex-wrap ds:items-center ds:gap-x-[var(--spacing-sm)]',\n EXTRA_NAV_MINIMAL,\n MINIMAL_EXTRA_HIT_AREA,\n ].join(' ')}\n >\n {extraNavSlot}\n </span>\n </>\n ) : null}\n {copyright ? (\n <>\n <FooterDivider />\n <span className=\"ds:basis-full ds:text-center ds:@[1024px]/pfoot:basis-auto\">\n {copyright}\n </span>\n </>\n ) : null}\n </div>\n </div>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Compact */\n/* ------------------------------------------------------------------ */\n\n// Shared social-icon row — used by Compact and Full. Renders an icon\n// per enabled network; the parent decides where to drop it.\nfunction SocialIconList({\n socials,\n t,\n size,\n}: {\n socials: PublicFooterSocial[];\n t: TFunction;\n size: 'sm' | 'md';\n}) {\n if (socials.length === 0) return null;\n const iconClass = size === 'sm' ? 'ds:size-4' : 'ds:size-5';\n return (\n <ul\n data-slot=\"socials\"\n className=\"ds:list-none ds:m-0 ds:p-0 ds:flex ds:items-center ds:gap-[var(--spacing-xs)]\"\n >\n {socials.map((s) => {\n const Icon = SOCIAL_ICONS[s.network];\n const href = s.href ?? DEFAULT_SOCIAL_URLS[s.network];\n const label = t(`footer.social.aria.${s.network}`);\n return (\n <li key={s.network}>\n <Link\n href={safeHref(href)}\n external\n externalIcon={false}\n intent=\"subtle\"\n aria-label={label}\n className=\"ds:inline-flex ds:items-center ds:justify-center ds:min-w-[var(--min-target-size)] ds:min-h-[var(--min-target-size)]\"\n >\n <Icon\n aria-hidden=\"true\"\n className={iconClass}\n focusable=\"false\"\n />\n </Link>\n </li>\n );\n })}\n </ul>\n );\n}\n\nfunction CompactBody({\n legalUrls,\n labels,\n copyright,\n tagline,\n socials,\n logoSlot,\n extraNavSlot,\n t,\n}: {\n legalUrls: ResolvedLegalUrls;\n labels: { privacy: string; terms: string; contacts: string };\n copyright?: string;\n tagline?: string;\n socials: PublicFooterSocial[];\n logoSlot?: ReactNode;\n extraNavSlot?: ReactNode;\n t: TFunction;\n}) {\n // 0.30.5: compact polish — defaults to a social-icon row + (small,\n // muted) wordmark at the start of the link row. Consumers who want\n // the lean treatment can opt out via `socials={[]}` (kills the row)\n // and `logoSlot={<></>}` (kills the wordmark). The lean fallback is\n // still a useful surface for embedded widgets (the booking aside).\n return (\n <div className=\"ds:flex ds:flex-col ds:items-center ds:gap-[var(--spacing-md)]\">\n {socials.length > 0 ? (\n <SocialIconList socials={socials} t={t} size=\"sm\" />\n ) : null}\n <div className=\"ds:@container/pfoot ds:w-full\">\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:justify-center ds:gap-x-[var(--spacing-md)] ds:@[1024px]/pfoot:gap-x-[var(--spacing-sm)] ds:gap-y-[var(--spacing-sm)]\">\n {logoSlot !== undefined ? (\n logoSlot !== null && logoSlot !== false && logoSlot !== '' ? (\n <>\n <span\n data-slot=\"compact-logo\"\n className=\"ds:inline-flex ds:items-center ds:opacity-70\"\n >\n {logoSlot}\n </span>\n <FooterDivider />\n </>\n ) : null\n ) : (\n // No consumer override — render the kit's default small\n // wordmark, muted so it reads as marker not headline.\n <>\n <span\n data-slot=\"compact-logo\"\n className=\"ds:inline-flex ds:items-center ds:opacity-70\"\n >\n <Logo variant=\"wordmark\" tone=\"auto\" aria-label=\"Alfadocs\" />\n </span>\n <FooterDivider />\n </>\n )}\n <Link\n href={legalUrls.privacy}\n external\n externalIcon={false}\n intent=\"subtle\"\n >\n {labels.privacy}\n </Link>\n <FooterDivider />\n <Link\n href={legalUrls.terms}\n external\n externalIcon={false}\n intent=\"subtle\"\n >\n {labels.terms}\n </Link>\n <FooterDivider />\n <Link\n href={legalUrls.contacts}\n external\n externalIcon={false}\n intent=\"subtle\"\n >\n {labels.contacts}\n </Link>\n {extraNavSlot && (\n <>\n <FooterDivider />\n <span data-slot=\"extra-nav\" className={EXTRA_NAV_ANCHORS}>\n {extraNavSlot}\n </span>\n </>\n )}\n </div>\n </div>\n {copyright ? <CopyrightLine copyright={copyright} /> : null}\n {tagline ? (\n <p\n data-slot=\"tagline\"\n className=\"ds:m-0 ds:text-center ds:text-[color:var(--muted-foreground)] ds:text-[length:var(--font-size-xs)] ds:italic\"\n >\n {tagline}\n </p>\n ) : null}\n </div>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Full */\n/* ------------------------------------------------------------------ */\n\ntype TFunction = ReturnType<typeof useTranslation>['t'];\n\nfunction buildLegalColumn(\n legalUrls: ResolvedLegalUrls,\n labels: { privacy: string; terms: string; contacts: string },\n): PublicFooterLinkGroup {\n return {\n key: 'legal',\n links: [\n { href: legalUrls.privacy, label: labels.privacy, external: true },\n { href: legalUrls.terms, label: labels.terms, external: true },\n { href: legalUrls.contacts, label: labels.contacts, external: true },\n ],\n };\n}\n\nfunction FullBody({\n legalUrls,\n labels,\n copyright,\n tagline,\n linkGroups,\n socials,\n logoSlot,\n extraNavSlot,\n trustBadgesSlot,\n t,\n}: {\n legalUrls: ResolvedLegalUrls;\n labels: { privacy: string; terms: string; contacts: string };\n copyright?: string;\n tagline?: string;\n linkGroups?: PublicFooterLinkGroup[];\n socials: PublicFooterSocial[];\n logoSlot?: ReactNode;\n extraNavSlot?: ReactNode;\n trustBadgesSlot?: ReactNode;\n t: TFunction;\n}) {\n // The kit-built Legal column is always appended — Privacy / Terms /\n // Contacts must render in every variant per the legal brief.\n const columns: PublicFooterLinkGroup[] = [\n ...(linkGroups ?? []),\n buildLegalColumn(legalUrls, labels),\n ];\n\n const brand = (\n <div\n data-slot=\"brand\"\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\"\n >\n <div className=\"ds:inline-flex\">\n {logoSlot ?? (\n <Logo variant=\"wordmark\" tone=\"auto\" aria-label=\"Alfadocs\" />\n )}\n </div>\n <SocialIconList socials={socials} t={t} size=\"md\" />\n </div>\n );\n\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-xl)]\">\n {/* Top: brand block on inline-start + link columns to inline-end */}\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-xl)] ds:lg:flex-row ds:lg:items-start ds:lg:gap-[var(--spacing-xl)]\">\n <div className=\"ds:lg:w-[15rem] ds:lg:shrink-0\">{brand}</div>\n\n <div className=\"ds:flex-1 ds:min-w-0\">\n {/* md+ — grid of columns */}\n <div\n className=\"ds:hidden ds:md:grid ds:md:grid-cols-2 ds:lg:grid-cols-4 ds:gap-[var(--spacing-lg)]\"\n data-slot=\"link-columns-grid\"\n data-columns={columns.length}\n >\n <FullColumnsGrid columns={columns} t={t} />\n </div>\n\n {/* below md — accordion */}\n <div className=\"ds:md:hidden\" data-slot=\"link-columns-accordion\">\n <Accordion type=\"multiple\">\n {columns.map((group, i) => (\n <AccordionItem\n key={`${i}-${group.key}`}\n value={`${i}-${group.key}`}\n >\n <AccordionTrigger>\n {t(`footer.group.${group.key}`, {\n defaultValue: group.key,\n })}\n </AccordionTrigger>\n <AccordionContent>\n <ul className=\"ds:list-none ds:m-0 ds:p-0 ds:flex ds:flex-col ds:gap-[var(--spacing-sm)]\">\n {group.links.map((link) => (\n <li key={`${group.key}-${link.href}-${link.label}`}>\n <Link\n href={safeHref(link.href)}\n external={link.external}\n externalIcon={false}\n intent=\"subtle\"\n >\n {link.label}\n </Link>\n </li>\n ))}\n </ul>\n </AccordionContent>\n </AccordionItem>\n ))}\n </Accordion>\n </div>\n </div>\n </div>\n\n <Separator orientation=\"horizontal\" />\n\n {/* Bottom strip: copyright inline-start, mini legal links inline-end\n on the same row. Stacks under sm. */}\n <div className=\"ds:@container/pfoot ds:flex ds:w-full ds:flex-col ds:gap-[var(--spacing-sm)] ds:sm:flex-row ds:sm:items-center ds:sm:justify-between\">\n {copyright ? (\n <CopyrightLine copyright={copyright} align=\"start\" />\n ) : null}\n <div\n data-slot=\"bottom-legal\"\n className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-x-[var(--spacing-md)] ds:@[1024px]/pfoot:gap-x-[var(--spacing-sm)] ds:gap-y-[var(--spacing-sm)] ds:sm:justify-end\"\n >\n <Link\n href={legalUrls.privacy}\n external\n externalIcon={false}\n intent=\"subtle\"\n >\n {labels.privacy}\n </Link>\n <FooterDivider />\n <Link\n href={legalUrls.terms}\n external\n externalIcon={false}\n intent=\"subtle\"\n >\n {labels.terms}\n </Link>\n {(trustBadgesSlot || extraNavSlot) && (\n <>\n <FooterDivider />\n {trustBadgesSlot && (\n <span\n data-slot=\"trust-badges\"\n className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-sm)]\"\n >\n {trustBadgesSlot}\n </span>\n )}\n {extraNavSlot && (\n <span\n data-slot=\"extra-nav\"\n className={`ds:inline-flex ds:items-center ${EXTRA_NAV_ANCHORS}`}\n >\n {extraNavSlot}\n </span>\n )}\n </>\n )}\n </div>\n </div>\n\n {tagline ? (\n <p\n data-slot=\"tagline\"\n className=\"ds:m-0 ds:text-center ds:text-[color:var(--muted-foreground)] ds:text-[length:var(--font-size-xs)] ds:italic\"\n >\n {tagline}\n </p>\n ) : null}\n </div>\n );\n}\n\nfunction FullColumnsGrid({\n columns,\n t,\n}: {\n columns: PublicFooterLinkGroup[];\n t: TFunction;\n}) {\n return (\n <>\n {columns.map((group, i) => (\n <section\n key={`${i}-${group.key}`}\n aria-labelledby={`public-footer-group-${i}-${group.key}`}\n >\n {/* h3 so the footer's column headings don't collide with the host\n page's section h2 outline. */}\n <h3\n id={`public-footer-group-${i}-${group.key}`}\n className=\"ds:m-0 ds:mb-[var(--spacing-sm)] ds:text-[color:var(--foreground)] ds:text-[length:var(--font-size-sm)] ds:font-medium\"\n >\n {t(`footer.group.${group.key}`, { defaultValue: group.key })}\n </h3>\n <ul className=\"ds:list-none ds:m-0 ds:p-0 ds:flex ds:flex-col ds:gap-[var(--spacing-sm)]\">\n {group.links.map((link) => (\n <li key={`${group.key}-${link.href}-${link.label}`}>\n <Link\n href={safeHref(link.href)}\n external={link.external}\n externalIcon={false}\n intent=\"subtle\"\n >\n {link.label}\n </Link>\n </li>\n ))}\n </ul>\n </section>\n ))}\n </>\n );\n}\n\nPublicFooter.displayName = 'PublicFooter';\n"],"names":["DEFAULT_MARKETING_BASE_URL","ITALIAN_SLUGS","SLUGS","HTTPS_PREFIX","normaliseBaseUrl","input","pickSlug","locale","key","overrides","override","_a","localeSlug","_b","composeLegalUrls","marketingBaseUrl","base","lang","SOCIAL_NETWORKS","DEFAULT_SOCIAL_URLS","SOCIAL_ICONS","SOCIAL_LINK_ICONS","ENTITY_NAME","ENTITY_VAT","rootVariants","cva","SAFE_HREF_RE","safeHref","href","FooterDivider","jsx","Separator","LinkSections","sections","label","footerId","section","jsxs","link","Link","CopyrightLine","copyright","align","PublicFooter","forwardRef","variant","legalUrlOverrides","linkGroups","linkSections","linkSectionsLabel","socials","logoSlot","extraNavSlot","trustBadgesSlot","showTagline","showCopyright","id","ariaLabel","ref","t","i18n","useTranslation","legalUrls","useMemo","useImperativeHandle","year","tagline","landmarkLabel","labels","resolvedSocials","network","MinimalBody","CompactBody","FullBody","MINIMAL_LINK_HIT_AREA","EXTRA_NAV_ANCHORS","EXTRA_NAV_BUTTONS","EXTRA_NAV_MINIMAL","MINIMAL_EXTRA_HIT_AREA","Fragment","SocialIconList","size","iconClass","Icon","Logo","buildLegalColumn","columns","FullColumnsGrid","Accordion","group","i","AccordionItem","AccordionTrigger","AccordionContent"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reviews-panel-CtrwZOUS.js","sources":["../../src/components/reviews-panel/reviews-panel.agent.ts","../../src/components/reviews-panel/reviews-panel.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* Agent adapter — ReviewsPanel. */\n/* */\n/* Replaces the legacy patient-reviews Carousel with a single panel that */\n/* surfaces an aggregate score, a star-bucket filter, and a vertical list */\n/* of compact review cards. The adapter exposes the active filter and the */\n/* visible review count so a host agent can drive the panel and observe */\n/* what the user actually sees after the filter is applied. The reviews */\n/* themselves remain owned by the consumer — they never flow through the */\n/* adapter (PHI: pseudonyms + free-text comments stay on the page). */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { ReviewFilter, ReviewsPanelHandle } from './reviews-panel';\n\nexport const reviewsPanelAgent: AgentAdapter<ReviewsPanelHandle> = {\n id: 'reviews-panel',\n capabilities: ['filter'],\n state: {\n filter: {\n type: 'string',\n descriptionKey: 'ui.agent.reviewsPanel.state.filter',\n description:\n 'Active star-bucket filter — `\"all\"` or an integer 1..5. Reads the controlled `filter` prop.',\n read: (handle) => handle.getFilter(),\n },\n visibleCount: {\n type: 'number',\n descriptionKey: 'ui.agent.reviewsPanel.state.visibleCount',\n description:\n 'Number of reviews currently rendered after the filter is applied.',\n read: (handle) => handle.getVisibleCount(),\n },\n },\n actions: {\n set_filter: {\n // Pure ephemeral narrowing of the visible list — no data loss.\n safety: 'read',\n argsType: '{ filter: \"all\" | 1 | 2 | 3 | 4 | 5 }',\n argsSchema: {\n type: 'object',\n properties: {\n filter: {\n description: 'Star bucket to show, or \"all\" to clear the filter.',\n oneOf: [\n { type: 'string', enum: ['all'] },\n { type: 'integer', enum: [1, 2, 3, 4, 5] },\n ],\n },\n },\n required: ['filter'],\n },\n idempotent: true,\n descriptionKey: 'ui.agent.reviewsPanel.actions.setFilter',\n description:\n 'Narrow the visible review list to a star bucket, or `\"all\"` to clear the filter.',\n examples: [\n { args: { filter: 5 }, note: 'Show only 5-star reviews.' },\n { args: { filter: 'all' }, note: 'Clear the star filter.' },\n ],\n invoke: (handle, args: { filter: ReviewFilter }) => {\n handle.setFilter(args.filter);\n },\n },\n },\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'reviews-panel',\n description: 'Marks the ReviewsPanel root region.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description: 'Sourced from the id prop.',\n },\n },\n};\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type ComponentPropsWithoutRef,\n type KeyboardEvent,\n type ReactNode,\n} from 'react';\nimport { cva } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { ChevronDown, Star } from 'lucide-react';\nimport { useAgentRegistration } from '../../agent/registry';\nimport { reviewsPanelAgent } from './reviews-panel.agent';\nimport { Rating } from '../rating/rating';\nimport { Card } from '../card/card';\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from '../collapsible/collapsible';\nimport { DescriptionList } from '../description-list/description-list';\nimport { EmptyState } from '../empty-state/empty-state';\nimport { Skeleton } from '../skeleton/skeleton';\nimport { Button } from '../button/button';\nimport { PanelHeader } from '../panel-header';\n\n/* -------------------------------------------------------------------- */\n/* ReviewsPanel */\n/* */\n/* Replaces the legacy patient-reviews Carousel with a two-column panel */\n/* — a star-bucket filter sidebar + a vertical list of compact review */\n/* cards — that surfaces the aggregate score, the volume, and (via a */\n/* per-card Collapsible) the rich per-axis breakdown alfaDocs already */\n/* collects but the carousel buried. */\n/* -------------------------------------------------------------------- */\n\n/* -------------------------------------------------------------------- */\n/* Public types */\n/* -------------------------------------------------------------------- */\n\nexport interface ReviewItem {\n id: string;\n /** When the review was authored (ISO 8601). */\n authoredAt: string;\n /** Anonymised author display (\"Marco T.\", \"Anna_M\"). Falls back to a localised \"Anonymous\". */\n pseudonym?: string;\n /** 0..max — drives the visible star summary on the compact card. */\n overallScore: number;\n /** Short free-text comment shown on the compact card. */\n comment?: string;\n /**\n * Language of this review's own text (comment and practice reply), as a\n * BCP 47 tag such as `\"it\"`. Sets `lang` on that text so a screen reader on\n * a page in another language pronounces it correctly. Overrides the\n * panel-level `contentLang`. Leave it out and no `lang` is written.\n * The pseudonym, date, rating and the kit's labels keep the page language.\n */\n contentLang?: string;\n /** Practice reply (revealed in the expanded panel only). */\n reply?: string;\n /** Optional per-axis breakdown — when present, the card grows an\n * \"Expand\" affordance that reveals these in a DescriptionList. */\n breakdown?: {\n appointmentScore?: number;\n communicationScore?: number;\n staffScore?: number;\n overallScore?: number;\n };\n /** Optional categorical follow-ups, surfaced inside the breakdown. */\n futureVisit?: 'yes' | 'no' | 'unsure';\n recommendation?: 'yes' | 'no' | 'unsure';\n}\n\nexport interface ReviewsAggregate {\n /** Total review count across all star buckets (may exceed the loaded set when paginated). */\n count: number;\n /** Average score across the full population (drives the header `Rating`). */\n averageScore: number;\n /**\n * Optional histogram — per-bucket count keyed by integer star value 1..5.\n * When omitted, the panel derives buckets from the loaded `reviews` array\n * (best-effort: paginated consumers SHOULD provide this explicitly).\n */\n histogram?: Record<1 | 2 | 3 | 4 | 5, number>;\n}\n\nexport type ReviewFilter = 'all' | 1 | 2 | 3 | 4 | 5;\n\nexport interface ReviewsPanelHandle {\n getFilter: () => ReviewFilter;\n setFilter: (next: ReviewFilter) => void;\n /** Total visible after filter is applied. */\n getVisibleCount: () => number;\n}\n\nexport interface ReviewsPanelProps extends Omit<\n ComponentPropsWithoutRef<'section'>,\n 'aria-label' | 'children'\n> {\n reviews: ReviewItem[];\n aggregate: ReviewsAggregate;\n /**\n * Default language of every review's own text, as a BCP 47 tag. A review's\n * own `contentLang` wins. Leave both out and no `lang` is written.\n */\n contentLang?: string;\n\n /**\n * Active star filter. Controlled — consumer owns it for shareability via\n * URL query strings. Omit for uncontrolled (panel-managed) filtering.\n */\n filter?: ReviewFilter;\n onFilterChange?: (next: ReviewFilter) => void;\n\n /**\n * Optional intro callout shown above the list (e.g. \"Riepilogo delle\n * opinioni — i pazienti raccontano…\"). Render-prop slot so the consumer\n * can drop in `<Alert>` / `<Callout>` / plain `<p>`.\n */\n introSlot?: ReactNode;\n\n /** Optional header right-end action (e.g. a \"Leave a review\" button). */\n headerActionSlot?: ReactNode;\n\n /** Override the default region aria-label. */\n 'aria-label'?: string;\n\n /** Agent-readiness id. */\n id?: string;\n\n className?: string;\n\n /**\n * Hide the filter sidebar — useful when the panel mounts under ~640px\n * width or inside a narrow column. Defaults to false.\n */\n hideFilter?: boolean;\n\n /**\n * @default 'flat'\n * `'elevated'` wraps the panel in the equivalent of `Card variant=\"elevated\"`\n * — same tokens (`--card`, `--card-border`, `--shadow-card`,\n * `--radius-lg`), same padding. Use to drop the consumer-side\n * `<Card><Card.Body>` wrap.\n */\n surface?: 'flat' | 'elevated';\n\n /** Render skeletons for the header and the list. */\n loading?: boolean;\n}\n\n/* -------------------------------------------------------------------- */\n/* Constants & helpers */\n/* -------------------------------------------------------------------- */\n\nconst FILTER_VALUES: readonly ReviewFilter[] = ['all', 5, 4, 3, 2, 1] as const;\n\nfunction bucketOf(score: number): 1 | 2 | 3 | 4 | 5 {\n const n = Math.min(5, Math.max(1, Math.floor(score)));\n return n as 1 | 2 | 3 | 4 | 5;\n}\n\nfunction deriveHistogram(\n reviews: ReviewItem[],\n): Record<1 | 2 | 3 | 4 | 5, number> {\n const out: Record<1 | 2 | 3 | 4 | 5, number> = {\n 1: 0,\n 2: 0,\n 3: 0,\n 4: 0,\n 5: 0,\n };\n for (const r of reviews) out[bucketOf(r.overallScore)] += 1;\n return out;\n}\n\n/* -------------------------------------------------------------------- */\n/* FilterBar — hand-rolled radiogroup with roving tabindex */\n/* */\n/* Lives in the shared `PanelHeader` filters row, so it lays out */\n/* HORIZONTALLY (a wrapping bar of star-bucket chips) rather than the */\n/* legacy vertical sidebar column. Still the WAI-ARIA RadioGroup pattern */\n/* — each chip is a full click target with a non-text leading glyph (the */\n/* star ramp) and a trailing count. NOT Radix `RadioGroup` because we */\n/* need that glyph-plus-count chip shape. */\n/* */\n/* Activation is automatic — Arrow keys move BOTH focus and selection, */\n/* so a keyboard user feels the same responsiveness as a mouse user. */\n/* That's the right call here because filter activation is cheap (re- */\n/* renders the visible list); contrast with the booking ServiceGrid */\n/* where activation cascades through downstream selection state. */\n/* -------------------------------------------------------------------- */\n\nconst filterRowVariants = cva(\n [\n 'ds:relative ds:inline-flex ds:items-center ds:justify-center',\n 'ds:gap-[var(--spacing-xs)]',\n 'ds:min-h-[var(--min-target-size)]',\n 'ds:rounded-[var(--radius-md)]',\n 'ds:ps-[var(--spacing-sm)] ds:pe-[var(--spacing-sm)]',\n 'ds:pt-[var(--spacing-2xs)] ds:pb-[var(--spacing-2xs)]',\n 'ds:text-start',\n 'ds:transition-[background-color,color]',\n 'ds:duration-[var(--animation-duration)]',\n 'ds:motion-reduce:transition-none',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)]',\n 'ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-[var(--ring)]',\n 'ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:forced-colors:focus-visible:outline-[CanvasText]',\n 'ds:cursor-pointer',\n ].join(' '),\n {\n variants: {\n state: {\n idle: 'ds:hover:bg-[var(--muted)]/30 ds:text-[var(--foreground)]',\n selected: [\n 'ds:bg-[var(--primary)]',\n 'ds:text-[var(--primary-foreground)]',\n 'ds:font-[var(--font-weight-semibold)]',\n ].join(' '),\n },\n },\n defaultVariants: { state: 'idle' },\n },\n);\n\ninterface FilterBarProps {\n filter: ReviewFilter;\n histogram: Record<1 | 2 | 3 | 4 | 5, number>;\n totalCount: number;\n onChange: (next: ReviewFilter) => void;\n}\n\nfunction FilterBar({\n filter,\n histogram,\n totalCount,\n onChange,\n}: FilterBarProps) {\n const { t } = useTranslation('ui');\n const labelId = useId();\n\n // Roving tabindex tracks where keyboard focus is. We use AUTOMATIC\n // activation: arrow keys move focus AND fire onChange (see the rationale\n // above the component). To keep the visible aria-checked row and the\n // tab-stop aligned when the parent changes `filter` externally (e.g.\n // from a `?filter=4` URL parameter on page load), sync focusIndex to\n // the current filter via the effect below — without it, the tab stop\n // could land on `All` while the highlight shows `4`.\n const [focusIndex, setFocusIndex] = useState<number>(() =>\n Math.max(0, FILTER_VALUES.indexOf(filter)),\n );\n useEffect(() => {\n const i = FILTER_VALUES.indexOf(filter);\n if (i >= 0) setFocusIndex(i);\n }, [filter]);\n\n const rowRefs = useRef<Map<number, HTMLButtonElement>>(new Map());\n const focusAt = useCallback((index: number) => {\n rowRefs.current.get(index)?.focus();\n }, []);\n\n const total = FILTER_VALUES.length;\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLButtonElement>, index: number) => {\n switch (event.key) {\n case 'ArrowDown':\n case 'ArrowRight': {\n event.preventDefault();\n const next = (index + 1) % total;\n setFocusIndex(next);\n focusAt(next);\n onChange(FILTER_VALUES[next]);\n break;\n }\n case 'ArrowUp':\n case 'ArrowLeft': {\n event.preventDefault();\n const prev = (index - 1 + total) % total;\n setFocusIndex(prev);\n focusAt(prev);\n onChange(FILTER_VALUES[prev]);\n break;\n }\n case 'Home': {\n event.preventDefault();\n setFocusIndex(0);\n focusAt(0);\n onChange(FILTER_VALUES[0]);\n break;\n }\n case 'End': {\n event.preventDefault();\n const last = total - 1;\n setFocusIndex(last);\n focusAt(last);\n onChange(FILTER_VALUES[last]);\n break;\n }\n case 'Enter':\n case ' ': {\n event.preventDefault();\n onChange(FILTER_VALUES[index]);\n break;\n }\n default:\n break;\n }\n },\n [total, focusAt, onChange],\n );\n\n return (\n <>\n <h3\n id={labelId}\n className=\"type-label ds:font-[var(--font-weight-semibold)] ds:text-[var(--foreground)] ds:m-0\"\n >\n {t('reviewsPanel.filterTitle')}\n </h3>\n <div\n role=\"radiogroup\"\n aria-labelledby={labelId}\n className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-2xs)]\"\n >\n {FILTER_VALUES.map((val, index) => {\n const isAll = val === 'all';\n const stars = isAll ? 0 : (val as 1 | 2 | 3 | 4 | 5);\n const count = isAll\n ? totalCount\n : histogram[stars as 1 | 2 | 3 | 4 | 5];\n const isSelected = val === filter;\n const isFocused = focusIndex === index;\n const ariaLabel = isAll\n ? t('reviewsPanel.filter.allLabel', { count })\n : t('reviewsPanel.filter.starsLabel', { stars, count });\n\n return (\n <button\n key={String(val)}\n ref={(node) => {\n if (node) rowRefs.current.set(index, node);\n else rowRefs.current.delete(index);\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n aria-label={ariaLabel}\n tabIndex={isFocused ? 0 : -1}\n onClick={() => {\n setFocusIndex(index);\n onChange(val);\n }}\n onFocus={() => setFocusIndex(index)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n data-filter-bucket={String(val)}\n className={filterRowVariants({\n state: isSelected ? 'selected' : 'idle',\n })}\n >\n <span\n aria-hidden=\"true\"\n className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]\"\n >\n {isAll ? (\n <span className=\"type-label\">\n {t('reviewsPanel.filter.allShort')}\n </span>\n ) : (\n <Rating value={stars} size=\"sm\" decorative />\n )}\n </span>\n <span\n aria-hidden=\"true\"\n className={[\n 'type-meta',\n // Idle row: count is dimmed (--muted-foreground over\n // --background ≈ 4.5:1, AA passes). Selected row\n // paints violet bg + white text on the BUTTON, but\n // the count's own explicit color override would\n // otherwise win the cascade and leave dark navy-grey\n // text on the violet pill (~3:1, AA-fail). Match the\n // row's foreground when selected; slight opacity\n // keeps the count secondary to the label.\n isSelected\n ? 'ds:text-[color:var(--primary-foreground)]'\n : 'ds:text-[var(--muted-foreground)]',\n ].join(' ')}\n >\n {count}\n </span>\n </button>\n );\n })}\n </div>\n </>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* ReviewCard — compact card with optional Collapsible breakdown */\n/* -------------------------------------------------------------------- */\n\ninterface ReviewCardProps {\n review: ReviewItem;\n locale: string;\n /** Resolved language of the review's text (item override, else panel default). */\n textLang?: string;\n}\n\nfunction ReviewCard({ review, locale, textLang }: ReviewCardProps) {\n const { t } = useTranslation('ui');\n const triggerId = useId();\n const contentId = useId();\n const [open, setOpen] = useState(false);\n\n const breakdownHasValues = Boolean(\n review.breakdown && Object.values(review.breakdown).some((v) => v != null),\n );\n const hasDetails =\n Boolean(review.reply) ||\n breakdownHasValues ||\n Boolean(review.futureVisit) ||\n Boolean(review.recommendation);\n\n const formattedDate = useMemo(() => {\n if (!review.authoredAt) return '';\n try {\n const parsed = new Date(review.authoredAt);\n if (Number.isNaN(parsed.getTime())) return review.authoredAt;\n return new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(\n parsed,\n );\n } catch {\n return review.authoredAt;\n }\n }, [review.authoredAt, locale]);\n\n const pseudonym = review.pseudonym ?? t('reviewsPanel.anonymous');\n\n // Compose the full Card body. The Collapsible wraps the footer + the\n // hidden content so both share the same Radix Root and the Trigger's\n // `data-state` reaches the Content automatically (used for the height\n // animation). The Footer + Content are direct Card children so the\n // Card root's flex-col places them in order.\n const footerAndDetails = (\n <Collapsible\n open={hasDetails ? open : false}\n onOpenChange={hasDetails ? setOpen : undefined}\n >\n <Card.Footer>\n <span className=\"type-meta ds:text-[var(--muted-foreground)]\">\n {formattedDate}\n </span>\n {hasDetails ? (\n <CollapsibleTrigger asChild>\n <button\n id={triggerId}\n type=\"button\"\n data-review-trigger\n aria-controls={contentId}\n className=\"ds:ms-auto ds:inline-flex ds:items-center ds:gap-[var(--spacing-2xs)] ds:rounded-[var(--radius-sm)] ds:ps-[var(--spacing-2xs)] ds:pe-[var(--spacing-2xs)] ds:pt-[var(--spacing-2xs)] ds:pb-[var(--spacing-2xs)] ds:bg-transparent ds:border-0 ds:text-[length:var(--font-size-sm)] ds:text-[var(--review-details-foreground)] ds:min-w-[var(--min-target-size)] ds:min-h-[var(--min-target-size)] ds:hover:underline ds:cursor-pointer ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid ds:focus-visible:outline-[var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)] ds:forced-colors:focus-visible:outline-[CanvasText]\"\n >\n {open\n ? t('reviewsPanel.card.collapse')\n : t('reviewsPanel.card.expand')}\n <ChevronDown\n aria-hidden=\"true\"\n className={[\n 'ds:size-4 ds:transition-transform ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none',\n open ? 'ds:rotate-180' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n />\n </button>\n </CollapsibleTrigger>\n ) : null}\n </Card.Footer>\n {hasDetails ? (\n <CollapsibleContent id={contentId} aria-labelledby={triggerId}>\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)] ds:ps-[var(--spacing-md)] ds:pe-[var(--spacing-md)] ds:pb-[var(--spacing-md)]\">\n {breakdownHasValues ||\n review.futureVisit ||\n review.recommendation ? (\n <DescriptionList layout=\"inline\" density=\"compact\">\n {review.breakdown?.appointmentScore != null ? (\n <DescriptionList.Row>\n <DescriptionList.Term>\n {t('reviewsPanel.breakdown.appointment')}\n </DescriptionList.Term>\n <DescriptionList.Detail>\n <Rating\n value={review.breakdown.appointmentScore}\n size=\"sm\"\n />\n </DescriptionList.Detail>\n </DescriptionList.Row>\n ) : null}\n {review.breakdown?.communicationScore != null ? (\n <DescriptionList.Row>\n <DescriptionList.Term>\n {t('reviewsPanel.breakdown.communication')}\n </DescriptionList.Term>\n <DescriptionList.Detail>\n <Rating\n value={review.breakdown.communicationScore}\n size=\"sm\"\n />\n </DescriptionList.Detail>\n </DescriptionList.Row>\n ) : null}\n {review.breakdown?.staffScore != null ? (\n <DescriptionList.Row>\n <DescriptionList.Term>\n {t('reviewsPanel.breakdown.staff')}\n </DescriptionList.Term>\n <DescriptionList.Detail>\n <Rating value={review.breakdown.staffScore} size=\"sm\" />\n </DescriptionList.Detail>\n </DescriptionList.Row>\n ) : null}\n {review.breakdown?.overallScore != null ? (\n <DescriptionList.Row>\n <DescriptionList.Term>\n {t('reviewsPanel.breakdown.overall')}\n </DescriptionList.Term>\n <DescriptionList.Detail>\n <Rating value={review.breakdown.overallScore} size=\"sm\" />\n </DescriptionList.Detail>\n </DescriptionList.Row>\n ) : null}\n {review.futureVisit ? (\n <DescriptionList.Row>\n <DescriptionList.Term>\n {t('reviewsPanel.breakdown.futureVisit')}\n </DescriptionList.Term>\n <DescriptionList.Detail>\n {t(`reviewsPanel.answer.${review.futureVisit}`)}\n </DescriptionList.Detail>\n </DescriptionList.Row>\n ) : null}\n {review.recommendation ? (\n <DescriptionList.Row>\n <DescriptionList.Term>\n {t('reviewsPanel.breakdown.recommendation')}\n </DescriptionList.Term>\n <DescriptionList.Detail>\n {t(`reviewsPanel.answer.${review.recommendation}`)}\n </DescriptionList.Detail>\n </DescriptionList.Row>\n ) : null}\n </DescriptionList>\n ) : null}\n {review.reply ? (\n <div className=\"ds:rounded-[var(--radius-sm)] ds:bg-[var(--muted)]/40 ds:ps-[var(--spacing-sm)] ds:pe-[var(--spacing-sm)] ds:pt-[var(--spacing-sm)] ds:pb-[var(--spacing-sm)]\">\n <p className=\"type-label ds:m-0 ds:mb-[var(--spacing-2xs)] ds:text-[var(--muted-foreground)] ds:font-[var(--font-weight-semibold)]\">\n {t('reviewsPanel.replyTitle')}\n </p>\n <p\n lang={textLang}\n dir=\"auto\"\n className=\"type-body-sm ds:m-0 ds:text-[var(--foreground)] ds:[white-space:pre-line]\"\n >\n {review.reply}\n </p>\n </div>\n ) : null}\n </div>\n </CollapsibleContent>\n ) : null}\n </Collapsible>\n );\n\n return (\n <Card variant=\"default\" className=\"ds:shadow-[var(--shadow-sm)]\">\n <Card.Header>\n <div className=\"ds:flex ds:items-start ds:justify-between ds:gap-[var(--spacing-sm)]\">\n {/* The patient's pseudonym and comment, and the practice's reply,\n keep the direction they were typed in. The kit's own\n \"Anonymous\" follows the page. */}\n <h4\n dir={review.pseudonym ? 'auto' : undefined}\n className=\"type-title-item ds:m-0 ds:text-[var(--foreground)]\"\n >\n {pseudonym}\n </h4>\n <Rating value={review.overallScore} size=\"sm\" />\n </div>\n </Card.Header>\n {review.comment ? (\n <Card.Body>\n <p\n lang={textLang}\n dir=\"auto\"\n className=\"type-body ds:m-0 ds:text-[var(--foreground)] ds:line-clamp-2\"\n >\n {review.comment}\n </p>\n </Card.Body>\n ) : null}\n {footerAndDetails}\n </Card>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* PanelSkeleton — loading state */\n/* -------------------------------------------------------------------- */\n\nfunction PanelSkeleton({ hideFilter }: { hideFilter: boolean }) {\n // The parent <section> already carries `aria-busy=\"true\"`, which is the\n // canonical SR signal for \"this region is loading\". Don't add `role=status`\n // + `aria-live=polite` here too — pairing both would double-announce on\n // mount. Skeleton itself is `aria-hidden`.\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n <div className=\"ds:flex ds:items-center ds:justify-between ds:gap-[var(--spacing-sm)]\">\n <Skeleton variant=\"text\" width=\"40%\" />\n <Skeleton variant=\"text\" width=\"20%\" />\n </div>\n {!hideFilter ? (\n <div className=\"ds:flex ds:flex-wrap ds:gap-[var(--spacing-xs)]\">\n {Array.from({ length: 6 }, (_, i) => (\n <Skeleton\n key={`rp-fs-${i}`}\n variant=\"rounded\"\n width=\"4rem\"\n height=\"2.5rem\"\n />\n ))}\n </div>\n ) : null}\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)]\">\n {Array.from({ length: 3 }, (_, i) => (\n <Skeleton key={`rp-rs-${i}`} variant=\"rounded\" height=\"6rem\" />\n ))}\n </div>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* Root variants */\n/* -------------------------------------------------------------------- */\n\nconst rootVariants = cva(\n // No root padding: the shared `PanelHeader` owns the header's edge\n // padding (px/pt/pb) and the body wrapper below owns the content\n // padding, so the header divider can span flush to the panel edge.\n 'ds:flex ds:w-full ds:flex-col ds:text-[var(--foreground)]',\n {\n variants: {\n // `surface=\"elevated\"` matches `Card variant=\"elevated\"` — same\n // chrome tokens — so consumers can drop their own Card wraps\n // without producing a double-card look. Padding is supplied by the\n // header + body, not the root, so the header divider reaches the\n // card edge. Default `'flat'` adds no chrome.\n surface: {\n flat: '',\n elevated: [\n 'ds:overflow-hidden',\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:shadow-[var(--shadow-card)]',\n ].join(' '),\n },\n },\n defaultVariants: { surface: 'flat' },\n },\n);\n\n/* -------------------------------------------------------------------- */\n/* ReviewsPanel */\n/* -------------------------------------------------------------------- */\n\nexport const ReviewsPanel = forwardRef<HTMLElement, ReviewsPanelProps>(\n (\n {\n reviews,\n aggregate,\n contentLang,\n filter: filterProp,\n onFilterChange,\n introSlot,\n headerActionSlot,\n 'aria-label': ariaLabel,\n id,\n className,\n hideFilter = false,\n surface = 'flat',\n loading = false,\n ...rest\n },\n ref,\n ) => {\n const { t, i18n } = useTranslation('ui');\n const locale = i18n.language ?? 'en';\n\n // Controlled / uncontrolled filter handling. When `filterProp` is\n // undefined the panel manages the filter internally — the consumer can\n // mount the panel and never wire `filter` / `onFilterChange` for the\n // common case where the URL doesn't carry the filter state. When the\n // prop IS provided, the consumer owns the truth.\n const [internalFilter, setInternalFilter] = useState<ReviewFilter>('all');\n const filter = filterProp ?? internalFilter;\n\n const handleFilterChange = useCallback(\n (next: ReviewFilter) => {\n if (filterProp === undefined) setInternalFilter(next);\n onFilterChange?.(next);\n },\n [filterProp, onFilterChange],\n );\n\n // Prefer the consumer-provided histogram; fall back to deriving from the\n // loaded reviews. Pagination consumers SHOULD pass an explicit histogram\n // so the per-bucket counts reflect server-side totals rather than only\n // what's loaded on this page.\n const histogram = useMemo(\n () => aggregate.histogram ?? deriveHistogram(reviews),\n [aggregate.histogram, reviews],\n );\n\n const visibleReviews = useMemo(() => {\n if (filter === 'all') return reviews;\n return reviews.filter((r) => bucketOf(r.overallScore) === filter);\n }, [reviews, filter]);\n\n /* Agent registration — exposes filter + visible count via refs so the\n imperative handle returns the latest values without re-creating the\n handle on every render. */\n const latestFilterRef = useRef(filter);\n latestFilterRef.current = filter;\n const latestVisibleCountRef = useRef(visibleReviews.length);\n latestVisibleCountRef.current = visibleReviews.length;\n\n const agentHandle = useMemo<ReviewsPanelHandle>(\n () => ({\n getFilter: () => latestFilterRef.current,\n setFilter: (next) => handleFilterChange(next),\n getVisibleCount: () => latestVisibleCountRef.current,\n }),\n [handleFilterChange],\n );\n\n const rootRef = useRef<HTMLElement>(null);\n useImperativeHandle(ref, () => rootRef.current as HTMLElement, []);\n useAgentRegistration(reviewsPanelAgent, agentHandle, id);\n\n const resolvedAriaLabel = ariaLabel ?? t('reviewsPanel.regionLabel');\n const dataFilter = String(filter);\n\n /* ---------------------------- Loading ---------------------------- */\n if (loading) {\n return (\n <section\n ref={rootRef as React.Ref<HTMLElement>}\n aria-label={resolvedAriaLabel}\n aria-busy=\"true\"\n id={id}\n data-component=\"reviews-panel\"\n data-component-id={id}\n data-filter={dataFilter}\n data-state=\"loading\"\n className={rootVariants({ surface, className })}\n {...rest}\n >\n <div className=\"ds:p-[var(--spacing-md)]\">\n <PanelSkeleton hideFilter={hideFilter} />\n </div>\n </section>\n );\n }\n\n /* ----------------------- Zero-total empty ------------------------ */\n // No header, no filter — just the EmptyState. The region landmark is\n // still emitted so an agent can locate the empty panel by its\n // data-component marker.\n if (aggregate.count === 0 && reviews.length === 0) {\n return (\n <section\n ref={rootRef as React.Ref<HTMLElement>}\n aria-label={resolvedAriaLabel}\n id={id}\n data-component=\"reviews-panel\"\n data-component-id={id}\n data-filter={dataFilter}\n data-state=\"empty\"\n className={rootVariants({ surface, className })}\n {...rest}\n >\n <div className=\"ds:p-[var(--spacing-md)]\">\n <EmptyState\n variant=\"no-results\"\n title={t('reviewsPanel.empty.title')}\n description={t('reviewsPanel.empty.description')}\n />\n </div>\n </section>\n );\n }\n\n const showFilter = !hideFilter;\n\n return (\n <section\n ref={rootRef as React.Ref<HTMLElement>}\n aria-label={resolvedAriaLabel}\n id={id}\n data-component=\"reviews-panel\"\n data-component-id={id}\n data-filter={dataFilter}\n className={rootVariants({ surface, className })}\n {...rest}\n >\n {/* Shared rail header — the panel root is a region landmark with an\n accessible name, so this nested <header> is a generic element\n (not a banner) and stays within the region. The rail injects the\n favourite / close actions via PanelHeaderActionsProvider; the\n panel's own aggregate Rating + headerActionSlot lead the cluster.\n The rating summary is wide (about 260px), so on a phone it moves\n under the heading rather than clipping it to a letter or two. */}\n <PanelHeader\n title={t('reviewsPanel.title')}\n icon={<Star aria-hidden=\"true\" />}\n titleScale=\"section\"\n sticky\n wrapActions\n actions={\n <>\n {headerActionSlot}\n <Rating\n value={aggregate.averageScore}\n reviews={{ count: aggregate.count }}\n size=\"md\"\n />\n </>\n }\n filters={\n showFilter ? (\n <FilterBar\n filter={filter}\n histogram={histogram}\n totalCount={aggregate.count}\n onChange={handleFilterChange}\n />\n ) : undefined\n }\n />\n\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)] ds:min-w-0 ds:p-[var(--spacing-md)]\">\n {introSlot\n ? // Render the slot raw — the consumer's introSlot owns\n // its own visual chrome (Alert, Infotip, plain <p>, …).\n // 0.31.x wrapped the slot in a `bg-info/8` tinted box,\n // but when the consumer passed an Alert with its own\n // tinted surface the visual stack rendered TWO concentric\n // tinted boxes (\"double effect\"). The wrapper added no\n // semantic value either (no role, no aria), so dropping\n // it is purely visual.\n introSlot\n : null}\n\n {visibleReviews.length === 0 ? (\n // `role=\"status\"` already implies `aria-live=\"polite\"` —\n // declaring both would queue two announcements when the\n // filter changes (the radio's aria-checked flip + this\n // container's text). Keeping `role` only.\n <div\n role=\"status\"\n data-state=\"no-match\"\n className=\"ds:flex ds:flex-col ds:items-center ds:gap-[var(--spacing-sm)] ds:rounded-[var(--radius-md)] ds:border ds:border-dashed ds:border-[var(--border)] ds:p-[var(--spacing-lg)] ds:text-center\"\n >\n <p className=\"type-body ds:m-0 ds:text-[var(--muted-foreground)]\">\n {filter === 'all'\n ? t('reviewsPanel.empty.title')\n : t('reviewsPanel.empty.filterDescription', {\n stars: filter,\n })}\n </p>\n {filter !== 'all' ? (\n <Button\n intent=\"secondary\"\n size=\"sm\"\n onClick={() => handleFilterChange('all')}\n >\n {t('reviewsPanel.empty.resetFilter')}\n </Button>\n ) : null}\n </div>\n ) : (\n <ol\n aria-label={t('reviewsPanel.listLabel')}\n className=\"ds:list-none ds:m-0 ds:p-0 ds:flex ds:flex-col ds:gap-[var(--spacing-sm)]\"\n >\n {visibleReviews.map((review) => (\n <li key={review.id} data-review-id={review.id}>\n <ReviewCard\n review={review}\n locale={locale}\n textLang={review.contentLang ?? contentLang}\n />\n </li>\n ))}\n </ol>\n )}\n </div>\n </section>\n );\n },\n);\n\nReviewsPanel.displayName = 'ReviewsPanel';\n"],"names":["reviewsPanelAgent","handle","args","FILTER_VALUES","bucketOf","score","deriveHistogram","reviews","out","r","filterRowVariants","cva","FilterBar","filter","histogram","totalCount","onChange","t","useTranslation","labelId","useId","focusIndex","setFocusIndex","useState","useEffect","i","rowRefs","useRef","focusAt","useCallback","index","_a","total","handleKeyDown","event","next","prev","last","jsxs","Fragment","jsx","val","isAll","stars","count","isSelected","isFocused","ariaLabel","node","e","Rating","ReviewCard","review","locale","textLang","triggerId","contentId","open","setOpen","breakdownHasValues","v","hasDetails","formattedDate","useMemo","parsed","pseudonym","footerAndDetails","Collapsible","Card","CollapsibleTrigger","ChevronDown","CollapsibleContent","DescriptionList","_b","_c","_d","PanelSkeleton","hideFilter","Skeleton","_","rootVariants","ReviewsPanel","forwardRef","aggregate","contentLang","filterProp","onFilterChange","introSlot","headerActionSlot","id","className","surface","loading","rest","ref","i18n","internalFilter","setInternalFilter","handleFilterChange","visibleReviews","latestFilterRef","latestVisibleCountRef","agentHandle","rootRef","useImperativeHandle","useAgentRegistration","resolvedAriaLabel","dataFilter","EmptyState","showFilter","PanelHeader","Star","Button"],"mappings":";;;;;;;;;;;;;;;AAeO,MAAMA,KAAsD;AAAA,EACjE,IAAI;AAAA,EACJ,cAAc,CAAC,QAAQ;AAAA,EACvB,OAAO;AAAA,IACL,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,gBAAgB;AAAA,MAChB,aACE;AAAA,MACF,MAAM,CAACC,MAAWA,EAAO,UAAA;AAAA,IAAU;AAAA,IAErC,cAAc;AAAA,MACZ,MAAM;AAAA,MACN,gBAAgB;AAAA,MAChB,aACE;AAAA,MACF,MAAM,CAACA,MAAWA,EAAO,gBAAA;AAAA,IAAgB;AAAA,EAC3C;AAAA,EAEF,SAAS;AAAA,IACP,YAAY;AAAA;AAAA,MAEV,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,YAAY;AAAA,QACV,MAAM;AAAA,QACN,YAAY;AAAA,UACV,QAAQ;AAAA,YACN,aAAa;AAAA,YACb,OAAO;AAAA,cACL,EAAE,MAAM,UAAU,MAAM,CAAC,KAAK,EAAA;AAAA,cAC9B,EAAE,MAAM,WAAW,MAAM,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,EAAA;AAAA,YAAE;AAAA,UAC3C;AAAA,QACF;AAAA,QAEF,UAAU,CAAC,QAAQ;AAAA,MAAA;AAAA,MAErB,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,aACE;AAAA,MACF,UAAU;AAAA,QACR,EAAE,MAAM,EAAE,QAAQ,EAAA,GAAK,MAAM,4BAAA;AAAA,QAC7B,EAAE,MAAM,EAAE,QAAQ,MAAA,GAAS,MAAM,yBAAA;AAAA,MAAyB;AAAA,MAE5D,QAAQ,CAACA,GAAQC,MAAmC;AAClD,QAAAD,EAAO,UAAUC,EAAK,MAAM;AAAA,MAC9B;AAAA,IAAA;AAAA,EACF;AAAA,EAEF,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IAAA;AAAA,IAEf,YAAY;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,aAAa;AAAA,IAAA;AAAA,EACf;AAEJ,GCmFMC,IAAyC,CAAC,OAAO,GAAG,GAAG,GAAG,GAAG,CAAC;AAEpE,SAASC,EAASC,GAAkC;AAElD,SADU,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,MAAMA,CAAK,CAAC,CAAC;AAEtD;AAEA,SAASC,GACPC,GACmC;AACnC,QAAMC,IAAyC;AAAA,IAC7C,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EAAA;AAEL,aAAWC,KAAKF,EAAS,CAAAC,EAAIJ,EAASK,EAAE,YAAY,CAAC,KAAK;AAC1D,SAAOD;AACT;AAmBA,MAAME,KAAoBC;AAAA,EACxB;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,EACA,KAAK,GAAG;AAAA,EACV;AAAA,IACE,UAAU;AAAA,MACR,OAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU;AAAA,UACR;AAAA,UACA;AAAA,UACA;AAAA,QAAA,EACA,KAAK,GAAG;AAAA,MAAA;AAAA,IACZ;AAAA,IAEF,iBAAiB,EAAE,OAAO,OAAA;AAAA,EAAO;AAErC;AASA,SAASC,GAAU;AAAA,EACjB,QAAAC;AAAA,EACA,WAAAC;AAAA,EACA,YAAAC;AAAA,EACA,UAAAC;AACF,GAAmB;AACjB,QAAM,EAAE,GAAAC,EAAA,IAAMC,EAAe,IAAI,GAC3BC,IAAUC,EAAA,GASV,CAACC,GAAYC,CAAa,IAAIC;AAAA,IAAiB,MACnD,KAAK,IAAI,GAAGpB,EAAc,QAAQU,CAAM,CAAC;AAAA,EAAA;AAE3C,EAAAW,EAAU,MAAM;AACd,UAAMC,IAAItB,EAAc,QAAQU,CAAM;AACtC,IAAIY,KAAK,KAAGH,EAAcG,CAAC;AAAA,EAC7B,GAAG,CAACZ,CAAM,CAAC;AAEX,QAAMa,IAAUC,EAAuC,oBAAI,KAAK,GAC1DC,IAAUC,EAAY,CAACC,MAAkB;;AAC7C,KAAAC,IAAAL,EAAQ,QAAQ,IAAII,CAAK,MAAzB,QAAAC,EAA4B;AAAA,EAC9B,GAAG,CAAA,CAAE,GAECC,IAAQ7B,EAAc,QACtB8B,IAAgBJ;AAAA,IACpB,CAACK,GAAyCJ,MAAkB;AAC1D,cAAQI,EAAM,KAAA;AAAA,QACZ,KAAK;AAAA,QACL,KAAK,cAAc;AACjB,UAAAA,EAAM,eAAA;AACN,gBAAMC,KAAQL,IAAQ,KAAKE;AAC3B,UAAAV,EAAca,CAAI,GAClBP,EAAQO,CAAI,GACZnB,EAASb,EAAcgC,CAAI,CAAC;AAC5B;AAAA,QACF;AAAA,QACA,KAAK;AAAA,QACL,KAAK,aAAa;AAChB,UAAAD,EAAM,eAAA;AACN,gBAAME,KAAQN,IAAQ,IAAIE,KAASA;AACnC,UAAAV,EAAcc,CAAI,GAClBR,EAAQQ,CAAI,GACZpB,EAASb,EAAciC,CAAI,CAAC;AAC5B;AAAA,QACF;AAAA,QACA,KAAK,QAAQ;AACX,UAAAF,EAAM,eAAA,GACNZ,EAAc,CAAC,GACfM,EAAQ,CAAC,GACTZ,EAASb,EAAc,CAAC,CAAC;AACzB;AAAA,QACF;AAAA,QACA,KAAK,OAAO;AACV,UAAA+B,EAAM,eAAA;AACN,gBAAMG,IAAOL,IAAQ;AACrB,UAAAV,EAAce,CAAI,GAClBT,EAAQS,CAAI,GACZrB,EAASb,EAAckC,CAAI,CAAC;AAC5B;AAAA,QACF;AAAA,QACA,KAAK;AAAA,QACL,KAAK,KAAK;AACR,UAAAH,EAAM,eAAA,GACNlB,EAASb,EAAc2B,CAAK,CAAC;AAC7B;AAAA,QACF;AAAA,MAEE;AAAA,IAEN;AAAA,IACA,CAACE,GAAOJ,GAASZ,CAAQ;AAAA,EAAA;AAG3B,SACE,gBAAAsB,EAAAC,GAAA,EACE,UAAA;AAAA,IAAA,gBAAAC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,IAAIrB;AAAA,QACJ,WAAU;AAAA,QAET,YAAE,0BAA0B;AAAA,MAAA;AAAA,IAAA;AAAA,IAE/B,gBAAAqB;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,mBAAiBrB;AAAA,QACjB,WAAU;AAAA,QAET,UAAAhB,EAAc,IAAI,CAACsC,GAAKX,MAAU;AACjC,gBAAMY,IAAQD,MAAQ,OAChBE,IAAQD,IAAQ,IAAKD,GACrBG,IAAQF,IACV3B,IACAD,EAAU6B,CAA0B,GAClCE,IAAaJ,MAAQ5B,GACrBiC,IAAYzB,MAAeS,GAC3BiB,IAAYL,IACdzB,EAAE,gCAAgC,EAAE,OAAA2B,GAAO,IAC3C3B,EAAE,kCAAkC,EAAE,OAAA0B,GAAO,OAAAC,GAAO;AAExD,iBACE,gBAAAN;AAAA,YAAC;AAAA,YAAA;AAAA,cAEC,KAAK,CAACU,MAAS;AACb,gBAAIA,IAAMtB,EAAQ,QAAQ,IAAII,GAAOkB,CAAI,IACpCtB,EAAQ,QAAQ,OAAOI,CAAK;AAAA,cACnC;AAAA,cACA,MAAK;AAAA,cACL,MAAK;AAAA,cACL,gBAAce;AAAA,cACd,cAAYE;AAAA,cACZ,UAAUD,IAAY,IAAI;AAAA,cAC1B,SAAS,MAAM;AACb,gBAAAxB,EAAcQ,CAAK,GACnBd,EAASyB,CAAG;AAAA,cACd;AAAA,cACA,SAAS,MAAMnB,EAAcQ,CAAK;AAAA,cAClC,WAAW,CAACmB,MAAMhB,EAAcgB,GAAGnB,CAAK;AAAA,cACxC,sBAAoB,OAAOW,CAAG;AAAA,cAC9B,WAAW/B,GAAkB;AAAA,gBAC3B,OAAOmC,IAAa,aAAa;AAAA,cAAA,CAClC;AAAA,cAED,UAAA;AAAA,gBAAA,gBAAAL;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,eAAY;AAAA,oBACZ,WAAU;AAAA,oBAET,cACC,gBAAAA,EAAC,QAAA,EAAK,WAAU,cACb,YAAE,8BAA8B,EAAA,CACnC,IAEA,gBAAAA,EAACU,KAAO,OAAOP,GAAO,MAAK,MAAK,YAAU,GAAA,CAAC;AAAA,kBAAA;AAAA,gBAAA;AAAA,gBAG/C,gBAAAH;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,eAAY;AAAA,oBACZ,WAAW;AAAA,sBACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBASAK,IACI,8CACA;AAAA,oBAAA,EACJ,KAAK,GAAG;AAAA,oBAET,UAAAD;AAAA,kBAAA;AAAA,gBAAA;AAAA,cACH;AAAA,YAAA;AAAA,YAnDK,OAAOH,CAAG;AAAA,UAAA;AAAA,QAsDrB,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,EACH,GACF;AAEJ;AAaA,SAASU,GAAW,EAAE,QAAAC,GAAQ,QAAAC,GAAQ,UAAAC,KAA6B;;AACjE,QAAM,EAAE,GAAArC,EAAA,IAAMC,EAAe,IAAI,GAC3BqC,IAAYnC,EAAA,GACZoC,IAAYpC,EAAA,GACZ,CAACqC,GAAMC,CAAO,IAAInC,EAAS,EAAK,GAEhCoC,IAAqB,GACzBP,EAAO,aAAa,OAAO,OAAOA,EAAO,SAAS,EAAE,KAAK,CAACQ,MAAMA,KAAK,IAAI,IAErEC,IACJ,EAAQT,EAAO,SACfO,KACA,EAAQP,EAAO,eACf,EAAQA,EAAO,gBAEXU,IAAgBC,EAAQ,MAAM;AAClC,QAAI,CAACX,EAAO,WAAY,QAAO;AAC/B,QAAI;AACF,YAAMY,IAAS,IAAI,KAAKZ,EAAO,UAAU;AACzC,aAAI,OAAO,MAAMY,EAAO,SAAS,IAAUZ,EAAO,aAC3C,IAAI,KAAK,eAAeC,GAAQ,EAAE,WAAW,SAAA,CAAU,EAAE;AAAA,QAC9DW;AAAA,MAAA;AAAA,IAEJ,QAAQ;AACN,aAAOZ,EAAO;AAAA,IAChB;AAAA,EACF,GAAG,CAACA,EAAO,YAAYC,CAAM,CAAC,GAExBY,IAAYb,EAAO,aAAanC,EAAE,wBAAwB,GAO1DiD,IACJ,gBAAA5B;AAAA,IAAC6B;AAAA,IAAA;AAAA,MACC,MAAMN,IAAaJ,IAAO;AAAA,MAC1B,cAAcI,IAAaH,IAAU;AAAA,MAErC,UAAA;AAAA,QAAA,gBAAApB,EAAC8B,EAAK,QAAL,EACC,UAAA;AAAA,UAAA,gBAAA5B,EAAC,QAAA,EAAK,WAAU,+CACb,UAAAsB,GACH;AAAA,UACCD,IACC,gBAAArB,EAAC6B,IAAA,EAAmB,SAAO,IACzB,UAAA,gBAAA/B;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,IAAIiB;AAAA,cACJ,MAAK;AAAA,cACL,uBAAmB;AAAA,cACnB,iBAAeC;AAAA,cACf,WAAU;AAAA,cAET,UAAA;AAAA,gBACGvC,EADHwC,IACK,+BACA,0BAD4B;AAAA,gBAElC,gBAAAjB;AAAA,kBAAC8B;AAAA,kBAAA;AAAA,oBACC,eAAY;AAAA,oBACZ,WAAW;AAAA,sBACT;AAAA,sBACAb,IAAO,kBAAkB;AAAA,oBAAA,EAExB,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,kBAAA;AAAA,gBAAA;AAAA,cACb;AAAA,YAAA;AAAA,UAAA,GAEJ,IACE;AAAA,QAAA,GACN;AAAA,QACCI,IACC,gBAAArB,EAAC+B,IAAA,EAAmB,IAAIf,GAAW,mBAAiBD,GAClD,UAAA,gBAAAjB,EAAC,OAAA,EAAI,WAAU,gIACZ,UAAA;AAAA,UAAAqB,KACDP,EAAO,eACPA,EAAO,mCACJoB,GAAA,EAAgB,QAAO,UAAS,SAAQ,WACtC,UAAA;AAAA,cAAAzC,IAAAqB,EAAO,cAAP,gBAAArB,EAAkB,qBAAoB,OACrC,gBAAAO,EAACkC,EAAgB,KAAhB,EACC,UAAA;AAAA,cAAA,gBAAAhC,EAACgC,EAAgB,MAAhB,EACE,UAAAvD,EAAE,oCAAoC,GACzC;AAAA,cACA,gBAAAuB,EAACgC,EAAgB,QAAhB,EACC,UAAA,gBAAAhC;AAAA,gBAACU;AAAA,gBAAA;AAAA,kBACC,OAAOE,EAAO,UAAU;AAAA,kBACxB,MAAK;AAAA,gBAAA;AAAA,cAAA,EACP,CACF;AAAA,YAAA,EAAA,CACF,IACE;AAAA,cACHqB,IAAArB,EAAO,cAAP,gBAAAqB,EAAkB,uBAAsB,OACvC,gBAAAnC,EAACkC,EAAgB,KAAhB,EACC,UAAA;AAAA,cAAA,gBAAAhC,EAACgC,EAAgB,MAAhB,EACE,UAAAvD,EAAE,sCAAsC,GAC3C;AAAA,cACA,gBAAAuB,EAACgC,EAAgB,QAAhB,EACC,UAAA,gBAAAhC;AAAA,gBAACU;AAAA,gBAAA;AAAA,kBACC,OAAOE,EAAO,UAAU;AAAA,kBACxB,MAAK;AAAA,gBAAA;AAAA,cAAA,EACP,CACF;AAAA,YAAA,EAAA,CACF,IACE;AAAA,cACHsB,IAAAtB,EAAO,cAAP,gBAAAsB,EAAkB,eAAc,OAC/B,gBAAApC,EAACkC,EAAgB,KAAhB,EACC,UAAA;AAAA,cAAA,gBAAAhC,EAACgC,EAAgB,MAAhB,EACE,UAAAvD,EAAE,8BAA8B,GACnC;AAAA,cACA,gBAAAuB,EAACgC,EAAgB,QAAhB,EACC,UAAA,gBAAAhC,EAACU,GAAA,EAAO,OAAOE,EAAO,UAAU,YAAY,MAAK,KAAA,CAAK,EAAA,CACxD;AAAA,YAAA,EAAA,CACF,IACE;AAAA,cACHuB,IAAAvB,EAAO,cAAP,gBAAAuB,EAAkB,iBAAgB,OACjC,gBAAArC,EAACkC,EAAgB,KAAhB,EACC,UAAA;AAAA,cAAA,gBAAAhC,EAACgC,EAAgB,MAAhB,EACE,UAAAvD,EAAE,gCAAgC,GACrC;AAAA,cACA,gBAAAuB,EAACgC,EAAgB,QAAhB,EACC,UAAA,gBAAAhC,EAACU,GAAA,EAAO,OAAOE,EAAO,UAAU,cAAc,MAAK,KAAA,CAAK,EAAA,CAC1D;AAAA,YAAA,EAAA,CACF,IACE;AAAA,YACHA,EAAO,cACN,gBAAAd,EAACkC,EAAgB,KAAhB,EACC,UAAA;AAAA,cAAA,gBAAAhC,EAACgC,EAAgB,MAAhB,EACE,UAAAvD,EAAE,oCAAoC,GACzC;AAAA,cACA,gBAAAuB,EAACgC,EAAgB,QAAhB,EACE,YAAE,uBAAuBpB,EAAO,WAAW,EAAE,EAAA,CAChD;AAAA,YAAA,EAAA,CACF,IACE;AAAA,YACHA,EAAO,iBACN,gBAAAd,EAACkC,EAAgB,KAAhB,EACC,UAAA;AAAA,cAAA,gBAAAhC,EAACgC,EAAgB,MAAhB,EACE,UAAAvD,EAAE,uCAAuC,GAC5C;AAAA,cACA,gBAAAuB,EAACgC,EAAgB,QAAhB,EACE,YAAE,uBAAuBpB,EAAO,cAAc,EAAE,EAAA,CACnD;AAAA,YAAA,EAAA,CACF,IACE;AAAA,UAAA,EAAA,CACN,IACE;AAAA,UACHA,EAAO,QACN,gBAAAd,EAAC,OAAA,EAAI,WAAU,iKACb,UAAA;AAAA,YAAA,gBAAAE,EAAC,KAAA,EAAE,WAAU,wHACV,UAAAvB,EAAE,yBAAyB,GAC9B;AAAA,YACA,gBAAAuB;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,MAAMc;AAAA,gBACN,KAAI;AAAA,gBACJ,WAAU;AAAA,gBAET,UAAAF,EAAO;AAAA,cAAA;AAAA,YAAA;AAAA,UACV,EAAA,CACF,IACE;AAAA,QAAA,EAAA,CACN,GACF,IACE;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIR,SACE,gBAAAd,EAAC8B,GAAA,EAAK,SAAQ,WAAU,WAAU,gCAChC,UAAA;AAAA,IAAA,gBAAA5B,EAAC4B,EAAK,QAAL,EACC,UAAA,gBAAA9B,EAAC,OAAA,EAAI,WAAU,wEAIb,UAAA;AAAA,MAAA,gBAAAE;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,KAAKY,EAAO,YAAY,SAAS;AAAA,UACjC,WAAU;AAAA,UAET,UAAAa;AAAA,QAAA;AAAA,MAAA;AAAA,wBAEFf,GAAA,EAAO,OAAOE,EAAO,cAAc,MAAK,KAAA,CAAK;AAAA,IAAA,EAAA,CAChD,EAAA,CACF;AAAA,IACCA,EAAO,UACN,gBAAAZ,EAAC4B,EAAK,MAAL,EACC,UAAA,gBAAA5B;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAMc;AAAA,QACN,KAAI;AAAA,QACJ,WAAU;AAAA,QAET,UAAAF,EAAO;AAAA,MAAA;AAAA,IAAA,GAEZ,IACE;AAAA,IACHc;AAAA,EAAA,GACH;AAEJ;AAMA,SAASU,GAAc,EAAE,YAAAC,KAAuC;AAK9D,SACE,gBAAAvC,EAAC,OAAA,EAAI,WAAU,kDACb,UAAA;AAAA,IAAA,gBAAAA,EAAC,OAAA,EAAI,WAAU,yEACb,UAAA;AAAA,MAAA,gBAAAE,EAACsC,GAAA,EAAS,SAAQ,QAAO,OAAM,OAAM;AAAA,MACrC,gBAAAtC,EAACsC,GAAA,EAAS,SAAQ,QAAO,OAAM,MAAA,CAAM;AAAA,IAAA,GACvC;AAAA,IACED,IAWE,OAVF,gBAAArC,EAAC,OAAA,EAAI,WAAU,mDACZ,UAAA,MAAM,KAAK,EAAE,QAAQ,EAAA,GAAK,CAACuC,GAAGtD,MAC7B,gBAAAe;AAAA,MAACsC;AAAA,MAAA;AAAA,QAEC,SAAQ;AAAA,QACR,OAAM;AAAA,QACN,QAAO;AAAA,MAAA;AAAA,MAHF,SAASrD,CAAC;AAAA,IAAA,CAKlB,GACH;AAAA,IAEF,gBAAAe,EAAC,SAAI,WAAU,kDACZ,gBAAM,KAAK,EAAE,QAAQ,EAAA,GAAK,CAACuC,GAAGtD,MAC7B,gBAAAe,EAACsC,GAAA,EAA4B,SAAQ,WAAU,QAAO,UAAvC,SAASrD,CAAC,EAAoC,CAC9D,EAAA,CACH;AAAA,EAAA,GACF;AAEJ;AAMA,MAAMuD,IAAerE;AAAA;AAAA;AAAA;AAAA,EAInB;AAAA,EACA;AAAA,IACE,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMR,SAAS;AAAA,QACP,MAAM;AAAA,QACN,UAAU;AAAA,UACR;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA,EACA,KAAK,GAAG;AAAA,MAAA;AAAA,IACZ;AAAA,IAEF,iBAAiB,EAAE,SAAS,OAAA;AAAA,EAAO;AAEvC,GAMasE,KAAeC;AAAA,EAC1B,CACE;AAAA,IACE,SAAA3E;AAAA,IACA,WAAA4E;AAAA,IACA,aAAAC;AAAA,IACA,QAAQC;AAAA,IACR,gBAAAC;AAAA,IACA,WAAAC;AAAA,IACA,kBAAAC;AAAA,IACA,cAAczC;AAAA,IACd,IAAA0C;AAAA,IACA,WAAAC;AAAA,IACA,YAAAb,IAAa;AAAA,IACb,SAAAc,IAAU;AAAA,IACV,SAAAC,IAAU;AAAA,IACV,GAAGC;AAAA,EAAA,GAELC,MACG;AACH,UAAM,EAAE,GAAA7E,GAAG,MAAA8E,MAAS7E,EAAe,IAAI,GACjCmC,IAAS0C,EAAK,YAAY,MAO1B,CAACC,GAAgBC,CAAiB,IAAI1E,EAAuB,KAAK,GAClEV,IAASwE,KAAcW,GAEvBE,IAAqBrE;AAAA,MACzB,CAACM,MAAuB;AACtB,QAAIkD,MAAe,UAAWY,EAAkB9D,CAAI,GACpDmD,KAAA,QAAAA,EAAiBnD;AAAA,MACnB;AAAA,MACA,CAACkD,GAAYC,CAAc;AAAA,IAAA,GAOvBxE,IAAYiD;AAAA,MAChB,MAAMoB,EAAU,aAAa7E,GAAgBC,CAAO;AAAA,MACpD,CAAC4E,EAAU,WAAW5E,CAAO;AAAA,IAAA,GAGzB4F,IAAiBpC,EAAQ,MACzBlD,MAAW,QAAcN,IACtBA,EAAQ,OAAO,CAACE,MAAML,EAASK,EAAE,YAAY,MAAMI,CAAM,GAC/D,CAACN,GAASM,CAAM,CAAC,GAKduF,IAAkBzE,EAAOd,CAAM;AACrC,IAAAuF,EAAgB,UAAUvF;AAC1B,UAAMwF,IAAwB1E,EAAOwE,EAAe,MAAM;AAC1D,IAAAE,EAAsB,UAAUF,EAAe;AAE/C,UAAMG,IAAcvC;AAAA,MAClB,OAAO;AAAA,QACL,WAAW,MAAMqC,EAAgB;AAAA,QACjC,WAAW,CAACjE,MAAS+D,EAAmB/D,CAAI;AAAA,QAC5C,iBAAiB,MAAMkE,EAAsB;AAAA,MAAA;AAAA,MAE/C,CAACH,CAAkB;AAAA,IAAA,GAGfK,IAAU5E,EAAoB,IAAI;AACxC,IAAA6E,EAAoBV,GAAK,MAAMS,EAAQ,SAAwB,CAAA,CAAE,GACjEE,EAAqBzG,IAAmBsG,GAAab,CAAE;AAEvD,UAAMiB,IAAoB3D,KAAa9B,EAAE,0BAA0B,GAC7D0F,IAAa,OAAO9F,CAAM;AAGhC,QAAI+E;AACF,aACE,gBAAApD;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,KAAK+D;AAAA,UACL,cAAYG;AAAA,UACZ,aAAU;AAAA,UACV,IAAAjB;AAAA,UACA,kBAAe;AAAA,UACf,qBAAmBA;AAAA,UACnB,eAAakB;AAAA,UACb,cAAW;AAAA,UACX,WAAW3B,EAAa,EAAE,SAAAW,GAAS,WAAAD,GAAW;AAAA,UAC7C,GAAGG;AAAA,UAEJ,4BAAC,OAAA,EAAI,WAAU,4BACb,UAAA,gBAAArD,EAACoC,IAAA,EAAc,YAAAC,GAAwB,EAAA,CACzC;AAAA,QAAA;AAAA,MAAA;AASN,QAAIM,EAAU,UAAU,KAAK5E,EAAQ,WAAW;AAC9C,aACE,gBAAAiC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,KAAK+D;AAAA,UACL,cAAYG;AAAA,UACZ,IAAAjB;AAAA,UACA,kBAAe;AAAA,UACf,qBAAmBA;AAAA,UACnB,eAAakB;AAAA,UACb,cAAW;AAAA,UACX,WAAW3B,EAAa,EAAE,SAAAW,GAAS,WAAAD,GAAW;AAAA,UAC7C,GAAGG;AAAA,UAEJ,UAAA,gBAAArD,EAAC,OAAA,EAAI,WAAU,4BACb,UAAA,gBAAAA;AAAA,YAACoE;AAAA,YAAA;AAAA,cACC,SAAQ;AAAA,cACR,OAAO3F,EAAE,0BAA0B;AAAA,cACnC,aAAaA,EAAE,gCAAgC;AAAA,YAAA;AAAA,UAAA,EACjD,CACF;AAAA,QAAA;AAAA,MAAA;AAKN,UAAM4F,IAAa,CAAChC;AAEpB,WACE,gBAAAvC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAKiE;AAAA,QACL,cAAYG;AAAA,QACZ,IAAAjB;AAAA,QACA,kBAAe;AAAA,QACf,qBAAmBA;AAAA,QACnB,eAAakB;AAAA,QACb,WAAW3B,EAAa,EAAE,SAAAW,GAAS,WAAAD,GAAW;AAAA,QAC7C,GAAGG;AAAA,QASJ,UAAA;AAAA,UAAA,gBAAArD;AAAA,YAACsE;AAAA,YAAA;AAAA,cACC,OAAO7F,EAAE,oBAAoB;AAAA,cAC7B,MAAM,gBAAAuB,EAACuE,IAAA,EAAK,eAAY,OAAA,CAAO;AAAA,cAC/B,YAAW;AAAA,cACX,QAAM;AAAA,cACN,aAAW;AAAA,cACX,SACE,gBAAAzE,EAAAC,GAAA,EACG,UAAA;AAAA,gBAAAiD;AAAA,gBACD,gBAAAhD;AAAA,kBAACU;AAAA,kBAAA;AAAA,oBACC,OAAOiC,EAAU;AAAA,oBACjB,SAAS,EAAE,OAAOA,EAAU,MAAA;AAAA,oBAC5B,MAAK;AAAA,kBAAA;AAAA,gBAAA;AAAA,cACP,GACF;AAAA,cAEF,SACE0B,IACE,gBAAArE;AAAA,gBAAC5B;AAAA,gBAAA;AAAA,kBACC,QAAAC;AAAA,kBACA,WAAAC;AAAA,kBACA,YAAYqE,EAAU;AAAA,kBACtB,UAAUe;AAAA,gBAAA;AAAA,cAAA,IAEV;AAAA,YAAA;AAAA,UAAA;AAAA,UAIR,gBAAA5D,EAAC,OAAA,EAAI,WAAU,sFACZ,UAAA;AAAA,YAAAiD,KAUG;AAAA,YAEHY,EAAe,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,cAKzB,gBAAA7D;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,MAAK;AAAA,kBACL,cAAW;AAAA,kBACX,WAAU;AAAA,kBAEV,UAAA;AAAA,oBAAA,gBAAAE,EAAC,KAAA,EAAE,WAAU,sDACV,UAAA3B,MAAW,QACRI,EAAE,0BAA0B,IAC5BA,EAAE,wCAAwC;AAAA,sBACxC,OAAOJ;AAAA,oBAAA,CACR,GACP;AAAA,oBACCA,MAAW,QACV,gBAAA2B;AAAA,sBAACwE;AAAA,sBAAA;AAAA,wBACC,QAAO;AAAA,wBACP,MAAK;AAAA,wBACL,SAAS,MAAMd,EAAmB,KAAK;AAAA,wBAEtC,YAAE,gCAAgC;AAAA,sBAAA;AAAA,oBAAA,IAEnC;AAAA,kBAAA;AAAA,gBAAA;AAAA,cAAA;AAAA,gBAGN,gBAAA1D;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,cAAYvB,EAAE,wBAAwB;AAAA,gBACtC,WAAU;AAAA,gBAET,UAAAkF,EAAe,IAAI,CAAC/C,wBAClB,MAAA,EAAmB,kBAAgBA,EAAO,IACzC,UAAA,gBAAAZ;AAAA,kBAACW;AAAA,kBAAA;AAAA,oBACC,QAAAC;AAAA,oBACA,QAAAC;AAAA,oBACA,UAAUD,EAAO,eAAegC;AAAA,kBAAA;AAAA,gBAAA,EAClC,GALOhC,EAAO,EAMhB,CACD;AAAA,cAAA;AAAA,YAAA;AAAA,UACH,EAAA,CAEJ;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGN;AACF;AAEA6B,GAAa,cAAc;"}