inline-chat-kit 0.54.2 → 0.54.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"inline-chat-kit.js","names":[],"sources":["../src/Button/Button.module.css","../src/Button/Button.tsx","../src/Attachments/Attachments.module.css","../src/Attachments/Attachments.tsx","../src/ChatInput/MorphGlyph.tsx","../src/ChatInput/ChatInput.module.css","../src/ChatInput/HoverActionsRow.tsx","../src/ChatInput/AddCardsOverlay.tsx","../src/announce/announce.ts","../src/reducedMotion/reducedMotion.ts","../src/voice/useVoiceInput.ts","../src/ChatInput/ChatInput.tsx","../src/turnParts/turnParts.ts","../src/useChatTurns/useChatTurns.ts","../src/ChatHeader/ChatHeader.module.css","../src/ChatHeader/ChatHeader.tsx","../src/AnswerActions/AnswerActions.module.css","../src/AnswerActions/AnswerActions.tsx","../src/Branch/Branch.module.css","../src/Branch/Branch.tsx","../src/Loader/Loader.module.css","../src/Loader/Loader.tsx","../src/disclosure/useDisclosure.ts","../src/disclosure/DisclosureHeader.module.css","../src/disclosure/DisclosureHeader.tsx","../src/disclosure/DisclosureBody.module.css","../src/disclosure/DisclosureBody.tsx","../src/duration/formatDuration.ts","../src/Reasoning/Reasoning.module.css","../src/Reasoning/Reasoning.tsx","../src/CodeBlock/highlight.ts","../src/CodeBlock/CodeBlock.module.css","../src/CodeBlock/CodeBlock.tsx","../src/stateGlyph/StateGlyph.module.css","../src/stateGlyph/StateGlyph.tsx","../src/Tool/Tool.module.css","../src/Tool/Tool.tsx","../src/Approval/Approval.module.css","../src/Approval/Approval.tsx","../src/TaskList/TaskList.module.css","../src/TaskList/TaskList.tsx","../src/ChainOfThought/ChainOfThought.module.css","../src/ChainOfThought/ChainOfThought.tsx","../src/Sources/Sources.module.css","../src/Sources/Sources.tsx","../src/Chip/Chip.module.css","../src/Chip/Chip.tsx","../src/QuestionCard/QuestionCard.module.css","../src/QuestionCard/parts.tsx","../src/radiusCorrection/useCorrectedRadius.ts","../src/QuestionCard/QuestionCard.tsx","../src/QuestionGroup/QuestionGroup.module.css","../src/QuestionGroup/QuestionGroup.tsx","../src/SystemMessage/SystemMessage.module.css","../src/SystemMessage/SystemMessage.tsx","../src/Artifact/ArtifactCard.module.css","../src/Artifact/ArtifactCard.tsx","../src/markdown/parse.ts","../src/markdown/parseMarkdown.ts","../src/InlineCitation/InlineCitation.module.css","../src/InlineCitation/InlineCitation.tsx","../src/TextHighlighter/TextHighlighter.module.css","../src/TextHighlighter/TextHighlighter.tsx","../src/ChatTurnRow/ChatTurnRow.module.css","../src/ChatTurnRow/ChatTurnRow.tsx","../src/Context/Context.module.css","../src/Context/Context.tsx","../src/Artifact/ArtifactPane.module.css","../src/Artifact/ArtifactPane.tsx","../src/Artifact/ChatLayout.module.css","../src/Artifact/ChatLayout.tsx","../src/Artifact/useArtifacts.ts","../src/EmptyState/EmptyState.module.css","../src/EmptyState/EmptyState.tsx","../src/Conversation/Conversation.module.css","../src/Conversation/Conversation.tsx","../src/ReplyThreadPopup/placePanel.ts","../src/ReplyThreadPopup/ReplyThreadPopup.module.css","../src/ReplyThreadPopup/ReplyThreadPopup.tsx","../src/CustomCursor/CustomCursor.tsx","../src/GlassButton/GlassButton.tsx"],"sourcesContent":["/* ─────────────────────────────────────────────\n Button\n\n One component for what used to be two. `Button` was a 28px round icon\n button; `GlassButton` was a 710-line pill that did the same job in a\n different material. They shared a size range, a set of states and most of\n their behaviour, and disagreed about all of it.\n\n Not one literal colour in this file. Everything comes from the token layer,\n which is also where the dark theme lives — so there is no dark block here.\n ───────────────────────────────────────────── */\n\n.btn {\n all: unset;\n box-sizing: border-box;\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n border-radius: var(--ick-radius-pill);\n cursor: pointer;\n white-space: nowrap;\n -webkit-tap-highlight-color: transparent;\n font-family: var(--ick-font-sans);\n font-weight: var(--ick-weight-medium);\n color: var(--ick-ink);\n transition:\n box-shadow var(--ick-duration-fast) ease,\n background var(--ick-duration-fast) ease,\n outline-color var(--ick-duration-fast) ease,\n color var(--ick-duration-fast) ease,\n transform 120ms var(--ick-ease);\n}\n\n.btn:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: var(--ick-space-1);\n}\n\n/* The 44px rule, hung off the first child rather than the button.\n\n `overflow: hidden` used to sit on `.btn` and was the whole obstacle: a\n clipping box clips hit-testing too, so any box reaching past the button's\n edge was unreachable. Nothing needed it — everything absolutely positioned\n in here is inset within the button — except a label too long for its\n button, and that belongs to the label. It is on `.label` now, with an\n ellipsis, which is a better answer to that case anyway.\n\n The second obstacle is that `::after` is taken: the glass material draws its\n rim highlight with it. Every button renders at least one of `.icon` or\n `.label`, and an absolute box inside one of those still resolves against\n `.btn` — so `> :first-child` gives the same box from an anchor that is free.\n\n See `styles/touch.module.css` for what this is and what it costs. */\n@media (pointer: coarse) {\n .btn > :first-child::after {\n content: \"\";\n position: absolute;\n top: 50%;\n left: 50%;\n width: max(100%, var(--ick-touch-target));\n height: max(100%, var(--ick-touch-target));\n transform: translate(-50%, -50%);\n }\n}\n\n/* ── Sizes ──────────────────────────────────────────────────────────────────\n Square while it holds only an icon; grows to fit once it has words. The\n type scale is non-decreasing rather than strictly increasing — 28 and 32\n both read at 13px, which is what both of the old components used at those\n heights. */\n\n/* Heights come from tokens so a touch device can have roomier ones without\n this file knowing — see the `pointer: coarse` block in `tokens.css`. The\n numbers that used to live here flattered 13px text and are tight around 14. */\n.xs { height: var(--ick-control-xs); min-width: var(--ick-control-xs); font-size: var(--ick-text-xs); gap: var(--ick-space-2); }\n.s { height: var(--ick-control-s); min-width: var(--ick-control-s); font-size: var(--ick-text-md); gap: var(--ick-space-3); }\n.m { height: var(--ick-control-m); min-width: var(--ick-control-m); font-size: var(--ick-text-md); gap: var(--ick-space-3); }\n.l { height: var(--ick-control-l); min-width: var(--ick-control-l); font-size: var(--ick-text-lg); gap: var(--ick-space-4); }\n.xl { height: var(--ick-control-xl); min-width: var(--ick-control-xl); font-size: 1rem; gap: var(--ick-space-4); }\n\n/* And the icon inside, from the text it sits beside. `auto` on a pointer is\n the SVG's own width and height attributes, so this is inert there. */\n.btn svg {\n width: var(--ick-icon);\n height: var(--ick-icon);\n}\n\n.hasText.xs { padding: 0 var(--ick-space-3); }\n.hasText.s { padding: 0 var(--ick-space-5) 0 var(--ick-space-4); }\n.hasText.m { padding: 0 var(--ick-space-6); }\n.hasText.l { padding: 0 var(--ick-space-7); }\n.hasText.xl { padding: 0 var(--ick-space-8); }\n\n.icon {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n/* The clipping that used to be on the button, on the only thing that can\n spill — and as an ellipsis rather than a hard cut, which is what a label too\n long for its button should have been doing all along. */\n.label {\n position: relative;\n z-index: var(--ick-z-base);\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ── Primary: glassy at rest ────────────────────────────────────────────── */\n\n.primary {\n background: var(--ick-glass-gradient);\n box-shadow: var(--ick-shadow-1);\n outline: 0.5px solid var(--ick-border-bright);\n}\n\n.primary:hover:not(:disabled) {\n box-shadow: var(--ick-shadow-2);\n}\n\n.primary:active:not(:disabled) {\n box-shadow: var(--ick-shadow-inset);\n transform: scale(0.94);\n transition-duration: var(--ick-duration-press);\n}\n\n/* ── Secondary: naked until touched ─────────────────────────────────────── */\n\n.secondary {\n background: transparent;\n outline: 0.5px solid transparent;\n}\n\n.secondary:hover:not(:disabled) {\n background: var(--ick-glass-gradient);\n box-shadow: var(--ick-shadow-2);\n outline-color: var(--ick-border-bright);\n}\n\n.secondary:active:not(:disabled) {\n background: var(--ick-glass-gradient);\n box-shadow: var(--ick-shadow-inset);\n outline-color: var(--ick-border-bright);\n transform: scale(0.94);\n transition-duration: var(--ick-duration-press);\n}\n\n/* ── Outline: an edge at rest ────────────────────────────────────────────────\n The gap between `primary`, which is filled, and `secondary`, which is naked\n until touched. A button that looks like one before you reach for it, without\n competing with a filled one beside it.\n\n `secondary` reading as bold text has caught this kit twice — once on the\n empty state's openers, once on an approval's Deny. The second time it was a\n permission control that did not look like a control, which is worse than any\n colour being wrong, so the gap is filled rather than worked around again. */\n\n.outline {\n background: transparent;\n outline: 1px solid var(--ick-border);\n}\n\n.outline:hover:not(:disabled) {\n background: var(--ick-surface-hover);\n outline-color: var(--ick-ink-faint);\n}\n\n.outline:active:not(:disabled) {\n background: var(--ick-surface-press);\n transform: scale(0.94);\n transition-duration: var(--ick-duration-press);\n}\n\n/* ── Ghost: flat ────────────────────────────────────────────────────────── */\n\n.ghost {\n background: transparent;\n color: var(--ick-ink-soft);\n}\n\n.ghost:hover:not(:disabled) {\n background: var(--ick-surface-active);\n color: var(--ick-ink);\n}\n\n.ghost:active:not(:disabled) {\n background: var(--ick-surface-press);\n color: var(--ick-ink);\n transform: scale(0.92);\n transition-duration: var(--ick-duration-press);\n}\n\n/* ── Glass: the concave-glass material ──────────────────────────────────────\n A gradient border through ::before and a top rim highlight through ::after,\n over a stack of thirteen shadow layers. The layers are tokens because they\n are the one part that has to be restated for the dark theme. */\n\n.glass {\n font-family: var(--ick-font-mono);\n letter-spacing: -0.01em;\n color: var(--ick-ink-glass);\n background: var(--ick-glass-fill);\n box-shadow: var(--ick-shadow-glass);\n backdrop-filter: blur(var(--ick-glass-blur));\n -webkit-backdrop-filter: blur(var(--ick-glass-blur));\n /* Named, not `all`, and this is the button the composer is made of — mic,\n plus, send — plus the X on an attachment. Pressed constantly.\n\n `all` animates whatever happens to change, and what changes here includes\n `backdrop-filter`: a blur re-composited every frame for 400ms, which is\n the expensive one in Safari and buys nothing, because the blur is the\n same before and after. Colour and shadow are the state; the lift is the\n movement, and it is quicker, because a press is not a journey. */\n transition:\n background var(--ick-duration-base) var(--ick-ease),\n color var(--ick-duration-base) var(--ick-ease),\n box-shadow var(--ick-duration-base) var(--ick-ease),\n transform var(--ick-duration-fast) var(--ick-ease);\n}\n\n.glass::before {\n content: \"\";\n position: absolute;\n inset: 0;\n border-radius: inherit;\n padding: var(--ick-glass-border-width);\n background: var(--ick-glass-edge);\n /* The only literal colours in this file, and they are not colours: a mask\n needs an opaque stencil, and any opaque value does. */\n mask:\n linear-gradient(#fff 0 0) content-box,\n linear-gradient(#fff 0 0);\n -webkit-mask:\n linear-gradient(#fff 0 0) content-box,\n linear-gradient(#fff 0 0);\n mask-composite: exclude;\n -webkit-mask-composite: xor;\n pointer-events: none;\n transition: background var(--ick-duration-slow) var(--ick-ease);\n}\n\n.glass::after {\n content: \"\";\n position: absolute;\n top: 0;\n left: 15%;\n right: 15%;\n height: var(--ick-glass-border-width);\n background: var(--ick-glass-highlight);\n border-radius: var(--ick-radius-pill);\n pointer-events: none;\n opacity: 0.85;\n transition: opacity var(--ick-duration-slow) var(--ick-ease);\n}\n\n.glass:hover:not(:disabled) {\n background: var(--ick-glass-fill-hover);\n color: var(--ick-ink-glass-strong);\n box-shadow: var(--ick-shadow-glass-hover);\n}\n\n/* The lift only where there is something to hover with.\n \n A touchscreen fires a hover on tap and leaves it there until the next tap\n somewhere else, so ungated this left the composer's buttons standing a\n pixel high for as long as you did not touch anything — a state the design\n has no name for. The colour and shadow above stay: those read as \"this is\n the one under your finger\" and are right either way. */\n@media (hover: hover) and (pointer: fine) {\n .glass:hover:not(:disabled) {\n transform: translateY(-1px);\n }\n}\n\n.glass:hover:not(:disabled)::after {\n opacity: 0.6;\n}\n\n.glass:active:not(:disabled) {\n background: var(--ick-glass-fill-press);\n box-shadow: var(--ick-shadow-glass-press);\n transform: translateY(1px) scale(0.99);\n transition-duration: 80ms;\n}\n\n.glass:active:not(:disabled)::before {\n background: var(--ick-glass-edge-press);\n}\n\n.glass:active:not(:disabled)::after {\n opacity: 0.3;\n}\n\n.glass:disabled {\n box-shadow: var(--ick-shadow-glass-disabled);\n}\n\n.glass:disabled::after {\n opacity: 0.15;\n}\n\n/* ── Disabled ───────────────────────────────────────────────────────────── */\n\n/* No `pointer-events: none` here, though the glass button used to have it.\n `disabled` already refuses activation; switching off hit-testing on top of\n that only removes the hover feedback and any tooltip explaining why the\n button cannot be used. */\n.btn:disabled {\n cursor: not-allowed;\n opacity: 0.45;\n}\n\n/* ── Loading ────────────────────────────────────────────────────────────────\n Hidden with opacity, not visibility. `visibility: hidden` takes the label\n out of the accessibility tree, and the spinner is aria-hidden, so the button\n announced as one with no name at all. */\n\n.loading .label,\n.loading .icon {\n opacity: 0;\n}\n\n.spinner {\n position: absolute;\n z-index: var(--ick-z-base);\n width: 1.25em;\n height: 1.25em;\n border: 2px solid var(--ick-border);\n border-top-color: var(--ick-ink-soft);\n border-radius: 50%;\n animation: spin 0.7s linear infinite;\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n/* ── Reduced motion ─────────────────────────────────────────────────────────\n The colour, shadow and outline fades stay — nothing about them moves, and\n dropping them would take the interactive states with them. Only transform\n goes, so the press snaps rather than travelling. The spinner keeps turning:\n it is the only sign that anything is happening. */\n\n@media (prefers-reduced-motion: reduce) {\n .btn,\n .glass {\n transition-property: box-shadow, background, outline-color, color;\n }\n}\n","\"use client\";\n\nimport { forwardRef, type ButtonHTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./Button.module.css\";\n\nexport type ButtonVariant = \"primary\" | \"secondary\" | \"outline\" | \"ghost\" | \"glass\";\n\n/**\n * Heights: xs 24, s 28, m 32, l 40, xl 48.\n *\n * One scale, from two that disagreed — the round icon button was 28 with no\n * say in the matter, and the glass pill offered 32/40/48 under the names\n * s/m/l. Both sets of values survive; only the glass names moved up one step,\n * which is what the deprecated `GlassButton` wrapper exists to absorb.\n */\nexport type ButtonSize = \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\";\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n /** Leading icon. On its own it makes a square button; give it an aria-label. */\n icon?: ReactNode;\n /** Trailing icon. */\n iconRight?: ReactNode;\n /** Replaces the content with a spinner and refuses interaction. */\n loading?: boolean;\n children?: ReactNode;\n}\n\nconst variantClass: Record<ButtonVariant, string> = {\n primary: styles.primary,\n secondary: styles.secondary,\n outline: styles.outline,\n ghost: styles.ghost,\n glass: styles.glass,\n};\n\nconst sizeClass: Record<ButtonSize, string> = {\n xs: styles.xs,\n s: styles.s,\n m: styles.m,\n l: styles.l,\n xl: styles.xl,\n};\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n {\n variant = \"primary\",\n size = \"s\",\n icon,\n iconRight,\n loading = false,\n children,\n className,\n disabled,\n ...rest\n },\n ref\n) {\n const classes = [\n styles.btn,\n variantClass[variant],\n sizeClass[size],\n children ? styles.hasText : \"\",\n loading ? styles.loading : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <button\n ref={ref}\n className={classes}\n disabled={disabled || loading}\n /* Stated either way rather than only when true: \"not busy\" is a fact\n worth having, and it is the contract the glass button already had. */\n aria-busy={loading}\n {...rest}\n >\n {icon && <span className={styles.icon}>{icon}</span>}\n {children && <span className={styles.label}>{children}</span>}\n {iconRight && <span className={styles.icon}>{iconRight}</span>}\n {loading && <span className={styles.spinner} aria-hidden=\"true\" />}\n </button>\n );\n});\n","/* ─────────────────────────────────────────────\n Attachments\n\n What is going along with the message. One row, drawn the same in the\n composer and under the message once it has been sent — the only difference\n is whether it carries a remove button, which is the difference between a\n control and a record.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.list {\n box-sizing: border-box;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--ick-space-3);\n width: 100%;\n margin: 0;\n padding: 0;\n list-style: none;\n font-family: var(--ick-font-sans);\n}\n\n/* A named file: a glyph, its name, its size. An inset panel, the same thing an\n option row is inside a question card. */\n.item {\n position: relative;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--ick-space-3);\n max-width: var(--ick-attachment-max);\n min-width: 0;\n padding: var(--ick-space-3) var(--ick-space-4);\n border-radius: var(--ick-attachment-radius);\n background: var(--ick-attachment-fill);\n}\n\n/* A picture is its own panel: it fills the square and needs no padding around\n itself to say what it is. */\n.item[data-picture] {\n width: var(--ick-attachment-size);\n height: var(--ick-attachment-size);\n padding: 0;\n overflow: hidden;\n /* Against a picture that happens to end in the surface's own colour, and\n lifted a little off the section it sits in. */\n outline: 1px solid var(--ick-border-bright);\n outline-offset: -1px;\n box-shadow: 0 2px 3px -2px rgb(var(--ick-ink-rgb) / 0.2);\n}\n\n.picture {\n display: block;\n width: 100%;\n height: 100%;\n background-position: 50%;\n background-size: cover;\n transition: filter var(--ick-duration-fast) ease;\n}\n\n.glyph {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n color: var(--ick-ink-faint);\n}\n\n.body {\n box-sizing: border-box;\n display: flex;\n align-items: baseline;\n gap: var(--ick-space-3);\n min-width: 0;\n}\n\n/* One line, ellipsised. A filename that wraps is a paragraph. */\n.name {\n font-size: var(--ick-text-sm);\n color: var(--ick-ink);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.size {\n flex-shrink: 0;\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n color: var(--ick-ink-faint);\n}\n\n/* ── Removing one ───────────────────────────────────────────────────────── */\n\n/* On the picture, over a scrim, because there is nowhere beside it to put a\n button without making the square bigger. Beside a named file, where there\n is. */\n.remove {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.item[data-picture] .remove {\n position: absolute;\n inset: 0;\n background: var(--ick-scrim);\n opacity: 0;\n transition: opacity var(--ick-duration-fast) ease;\n}\n\n.item[data-picture]:hover .remove,\n.item[data-picture]:focus-within .remove {\n opacity: 1;\n}\n\n.item[data-picture]:hover .picture {\n filter: blur(2px);\n}\n\n/* Reaching for the button must not be a race against the reveal. */\n@media (prefers-reduced-motion: reduce) {\n .item[data-picture] .remove,\n .picture {\n transition: none;\n }\n}\n\n/* ── Where there is no hover ─────────────────────────────────────────────\n A control that only appears on hover does not exist on a phone.\n\n The wash above is a good answer for a pointer: the button has the whole\n square to be pressed in, and the square is only obscured while somebody is\n pointing at it. Neither half survives the move. There is no pointing, so\n the wash would have to be permanent — a scrim over every picture for as\n long as it is attached, which is the picture you attached, hidden. And a\n full-square target that is always armed is a picture you cannot touch\n without deleting it.\n\n So on touch it stops being a wash and becomes a button, in the corner,\n always there. Nothing is covered but the corner it sits in, and the rest of\n the square goes back to being a picture. `hover: none` rather than\n `pointer: coarse`: the reason is the missing hover, not the finger — and\n the two come apart on a touchscreen laptop, where hovering still works and\n this rule should not apply. */\n@media (hover: none) {\n .item[data-picture] .remove {\n inset: var(--ick-attachment-remove-inset) var(--ick-attachment-remove-inset) auto auto;\n background: none;\n opacity: 1;\n }\n\n /* The picture is the thing you attached. Leave it alone. */\n .item[data-picture]:hover .picture {\n filter: none;\n }\n}\n","\"use client\";\n\nimport { X } from \"lucide-react\";\nimport { FileText, Film, Music, Image as ImageIcon, File } from \"lucide-react\";\nimport type { HTMLAttributes } from \"react\";\nimport { Button } from \"../Button/Button\";\nimport styles from \"./Attachments.module.css\";\n\n/** Something sent along with a message. */\nexport interface Attachment {\n id: string;\n /** The filename, shown when there is no picture to show instead. */\n name: string;\n /**\n * What to draw for it. An object URL, a data URL, or a remote one.\n *\n * Only used when `type` is an image. Anything else gets its name and a\n * glyph, because a thumbnail of a PDF at 64px is a grey rectangle.\n */\n url?: string;\n /** The MIME type. Decides between a picture and a name. */\n type?: string;\n /** In bytes. Shown beside the name. */\n size?: number;\n}\n\ntype Labels = { remove: string };\nconst LABELS: Labels = { remove: \"Remove\" };\n\nexport interface AttachmentsProps extends Omit<HTMLAttributes<HTMLUListElement>, \"onRemove\"> {\n attachments: Attachment[];\n /**\n * Leave it out and they are a record rather than a control — which is what\n * they are once the message has been sent.\n */\n onRemove?: (id: string) => void;\n labels?: Partial<Labels>;\n}\n\n/**\n * `1.2 MB`, and nothing for a size nobody supplied.\n *\n * Powers of two, because that is what a file browser says and this number is\n * only ever compared to one of those.\n */\nexport function formatSize(bytes?: number): string {\n if (bytes === undefined || !Number.isFinite(bytes) || bytes < 0) return \"\";\n if (bytes < 1024) return `${bytes} B`;\n const units = [\"KB\", \"MB\", \"GB\"];\n let value = bytes / 1024;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;\n}\n\nconst isImage = (a: Attachment) => Boolean(a.url) && (a.type ?? \"\").startsWith(\"image/\");\n\n/**\n * A URL that survives being written into `url(\"…\")`.\n *\n * Unquoted, a bracket inside the URL closes the `url()` early — the rest is\n * garbage, the browser throws the whole declaration away, and the element\n * gets `background-image: none`. No error anywhere; the picture is simply not\n * there. Which is what the stories showed for as long as they have existed: a\n * row of blank squares under the heading \"pictures — they show themselves\",\n * because an SVG data URL says `fill=\"url(#g)\"` and those brackets are not\n * touched by `encodeURIComponent`.\n *\n * So it is quoted, and the two characters that could close the quote are\n * escaped. Everything else is a URL's business.\n */\nconst cssUrl = (url: string) => url.replace(/[\"\\\\]/g, encodeURIComponent);\n\n/** A picture of the kind of thing it is, for the ones with no picture. */\nfunction Glyph({ type }: { type?: string }) {\n const kind = (type ?? \"\").split(\"/\")[0];\n const props = { size: 16, \"aria-hidden\": true } as const;\n if (kind === \"image\") return <ImageIcon {...props} />;\n if (kind === \"video\") return <Film {...props} />;\n if (kind === \"audio\") return <Music {...props} />;\n if (type === \"application/pdf\" || kind === \"text\") return <FileText {...props} />;\n return <File {...props} />;\n}\n\n/**\n * What is going along with the message.\n *\n * An image shows itself; everything else shows its name, because a thumbnail\n * of a PDF at this size is a grey rectangle with a corner turned down and\n * tells you less than the filename does.\n *\n * The same row draws them in the composer and under the message once it has\n * been sent — with `onRemove` in the first case and without in the second,\n * which is the whole difference between a control and a record.\n */\nexport function Attachments({\n attachments,\n onRemove,\n labels,\n className,\n ...rest\n}: AttachmentsProps) {\n const label = { ...LABELS, ...labels };\n if (attachments.length === 0) return null;\n\n return (\n <ul className={[styles.list, className ?? \"\"].filter(Boolean).join(\" \")} {...rest}>\n {attachments.map((file) => {\n const size = formatSize(file.size);\n return (\n <li\n key={file.id}\n className={styles.item}\n data-picture={isImage(file) || undefined}\n >\n {isImage(file) ? (\n /* A background rather than an `<img>`: it has to fill a square\n crop, and the name is already on the element for anybody who\n is not looking at it. */\n <span\n className={styles.picture}\n style={{ backgroundImage: `url(\"${cssUrl(file.url!)}\")` }}\n role=\"img\"\n aria-label={file.name}\n />\n ) : (\n <>\n <span className={styles.glyph}>\n <Glyph type={file.type} />\n </span>\n <span className={styles.body}>\n <span className={styles.name}>{file.name}</span>\n {size && <span className={styles.size}>{size}</span>}\n </span>\n </>\n )}\n\n {onRemove && (\n <span className={styles.remove}>\n <Button\n variant=\"glass\"\n size=\"s\"\n onClick={() => onRemove(file.id)}\n /* In the composer this sits on the highlighter's surface,\n where a pointerdown starts drawing a marker. */\n onPointerDown={(event) => event.stopPropagation()}\n aria-label={`${label.remove} ${file.name}`}\n icon={<X size={14} aria-hidden />}\n />\n </span>\n )}\n </li>\n );\n })}\n </ul>\n );\n}\n","\"use client\";\n\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { Check, CornerDownLeft, Mic, Square } from \"lucide-react\";\n\n/**\n * Glyph slot for the send/stop/check action. Static Lucide icons swap\n * with a vertical slide + fade — outgoing slides up and out, incoming\n * slides up from below. Single slot, single transition.\n */\n\nexport interface MorphGlyphProps {\n mode: \"send\" | \"stop\" | \"check\" | \"mic\";\n /** Icon colour. Inherits from whatever contains it unless set. */\n color?: string;\n /** Slide duration in seconds. */\n duration?: number;\n /** Icon pixel size. */\n size?: number;\n}\n\nconst SLIDE_DISTANCE = 10;\n\nexport function MorphGlyph({\n mode,\n color = \"currentColor\",\n duration = 0.22,\n size = 14,\n}: MorphGlyphProps) {\n return (\n <span\n style={{\n position: \"relative\",\n display: \"inline-flex\",\n width: size,\n height: size,\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n aria-hidden\n >\n <AnimatePresence initial={false} mode=\"popLayout\">\n <motion.span\n key={mode}\n initial={{ y: SLIDE_DISTANCE, opacity: 0 }}\n animate={{ y: 0, opacity: 1 }}\n exit={{ y: -SLIDE_DISTANCE, opacity: 0 }}\n transition={{ duration, ease: [0.32, 0.72, 0, 1] }}\n style={{\n position: \"absolute\",\n inset: 0,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n {mode === \"send\" ? (\n <CornerDownLeft\n size={size}\n color={color}\n strokeWidth={1.8}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n ) : mode === \"mic\" ? (\n <Mic\n size={size}\n color={color}\n strokeWidth={1.8}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n ) : mode === \"check\" ? (\n <Check\n size={size}\n color={color}\n strokeWidth={2.2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n ) : (\n <Square\n size={size}\n color={color}\n fill={color}\n strokeWidth={1.8}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n )}\n </motion.span>\n </AnimatePresence>\n </span>\n );\n}\n","/* ─────────────────────────────────────────────\n ChatInput — single morphing element\n States: idle | typing | responding | resting\n ───────────────────────────────────────────── */\n\n.root {\n /* The component tier: named here, derived from the semantic layer, and\n overridable by a host that wants a taller or rounder input without\n touching anything else. `--slot` went with this rewrite — declared, never\n read, by either this file or any other. */\n --ick-input-h: 36px;\n --ick-input-radius: var(--ick-radius-xl);\n --ick-input-fill: rgb(var(--ick-ink-rgb) / 0.03);\n --ick-input-fill-hover: rgb(var(--ick-ink-rgb) / 0.05);\n --ick-input-text: var(--ick-ink-soft);\n\n display: inline-flex;\n align-items: center;\n gap: var(--ick-space-2);\n font-family: var(--ick-font-sans);\n font-size: var(--ick-composer-size);\n letter-spacing: 0.03em;\n /* Fixed width when typing */\n width: 320px;\n}\n\n.root.isGlassRoot {\n width: auto;\n max-width: 320px;\n}\n\n/* ── Surface — the morphing pill ── */\n.surface {\n position: relative;\n display: flex;\n align-items: center;\n flex: 1;\n min-height: var(--ick-input-h);\n min-width: 0;\n padding: var(--ick-space-4);\n border-radius: var(--ick-input-radius);\n background: var(--ick-input-fill);\n color: var(--ick-input-text);\n cursor: text;\n transition:\n background var(--ick-duration-slow) var(--ick-ease),\n box-shadow var(--ick-duration-slow) var(--ick-ease),\n color var(--ick-duration-slow) var(--ick-ease);\n /* keep the morph performant */\n will-change: transform, background, box-shadow, padding;\n}\n\n/* Idle/typing hover — reads as clickable but stays subtle. */\n.surface:not(.glass):hover {\n --ick-input-fill: var(--ick-input-fill-hover);\n}\n\n.surface:not(.glass):focus-within {\n --ick-input-fill: var(--ick-input-fill-hover);\n --ick-input-text: var(--ick-ink);\n}\n\n/* ── Glass surface variant (responding / resting bubble) ──\n Mirrors GlassButton.module.css concave-glass shadow stack so\n the morph is purely a CSS state change on the SAME element. */\n.surface.glass {\n --ick-input-fill: var(--ick-glass-fill);\n --ick-input-text: var(--ick-ink-glass);\n cursor: default;\n backdrop-filter: blur(var(--ick-glass-blur));\n -webkit-backdrop-filter: blur(var(--ick-glass-blur));\n box-shadow: var(--ick-shadow-glass);\n}\n\n.surface.glass::before {\n content: \"\";\n position: absolute;\n inset: 0;\n border-radius: inherit;\n padding: var(--ick-glass-border-width);\n background: var(--ick-glass-edge);\n /* Stencils, not colours: a mask needs an opaque value and any one does. */\n mask:\n linear-gradient(#fff 0 0) content-box,\n linear-gradient(#fff 0 0);\n -webkit-mask:\n linear-gradient(#fff 0 0) content-box,\n linear-gradient(#fff 0 0);\n mask-composite: exclude;\n -webkit-mask-composite: xor;\n pointer-events: none;\n}\n\n.surface.glass::after {\n content: \"\";\n position: absolute;\n top: 0;\n left: 15%;\n right: 15%;\n height: var(--ick-glass-border-width);\n background: var(--ick-glass-highlight);\n border-radius: var(--ick-radius-pill);\n pointer-events: none;\n opacity: 0.85;\n transition: opacity var(--ick-duration-slow) var(--ick-ease);\n}\n\n/* ── Hover ──\n The bubble is made of the same glass as the buttons, so it wears the same\n stack. It used to be a copy of it, pasted, and the two drifted. */\n.surface.glass.hovered {\n --ick-input-fill: var(--ick-glass-fill-hover);\n color: var(--ick-ink);\n box-shadow: var(--ick-shadow-glass-hover);\n}\n\n.surface.glass.hovered::after {\n opacity: 0.6;\n}\n\n/* ── Impact pulse ──\n Fires once, the moment the action button is absorbed. Not a brighter hover:\n it is about the event, not the pointer. */\n.surface.glass.pulsed {\n box-shadow: var(--ick-shadow-glass-pulse);\n transition:\n background var(--ick-duration-slow) var(--ick-ease),\n box-shadow 0.18s var(--ick-ease-overshoot-strong),\n color var(--ick-duration-slow) var(--ick-ease);\n}\n\n.surface.glass.pulsed::after {\n opacity: 1;\n}\n\n\n/* ── Editor clip — non-scrolling wrapper that holds the fade mask ── */\n.editorClip {\n flex: 1;\n min-width: 0;\n}\n\n.editorClip[data-fade=\"top\"] {\n mask-image: linear-gradient(to bottom, transparent 0, black 48px 100%);\n -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 48px 100%);\n}\n\n.editorClip[data-fade=\"bottom\"] {\n mask-image: linear-gradient(to top, transparent 0, black 48px 100%);\n -webkit-mask-image: linear-gradient(to top, transparent 0, black 48px 100%);\n}\n\n.editorClip[data-fade=\"both\"] {\n mask-image: linear-gradient(to bottom, transparent 0, black 48px calc(100% - 48px), transparent 100%);\n -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 48px calc(100% - 48px), transparent 100%);\n}\n\n/* ── Editor wrapper — scroll container ── */\n.editorWrap {\n overflow-x: hidden;\n overflow-y: scroll;\n max-height: 240px;\n scrollbar-width: none;\n}\n\n.editorWrap::-webkit-scrollbar {\n display: none;\n}\n\n/* In glass (bubble) mode: disable scroll, clip text at max-height */\n.editorWrapGlass {\n overflow: hidden;\n}\n\n/* ── Editor area (contenteditable div) ── */\n.editor {\n /* This kit has no global `box-sizing` reset — a reset inside a CSS module\n leaks into the host page — so every padded box states its own. This one\n did not: `width: 100%` plus 8px of padding either side made its border box\n 16px wider than the wrapper that clips it, and the right edge of the text\n was cut off. Found by the visual QA pass, which reported it as\n \"runs 16px past the right of a box that clips\" on 25 stories. */\n box-sizing: border-box;\n width: 100%;\n outline: none;\n font: inherit;\n letter-spacing: inherit;\n color: inherit;\n line-height: 1.45;\n padding: var(--ick-space-2) var(--ick-space-4);\n caret-color: var(--ick-ink);\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.editor:empty::before {\n content: attr(data-placeholder);\n color: var(--ick-ink-faint);\n pointer-events: none;\n}\n\n\n/* In glass mode the text is inked, not muted, and the caret is hidden */\n.surface.glass .editor {\n color: var(--ick-ink);\n caret-color: transparent;\n}\n\n/* ─────────────────────────────────────────────\n Bubble wrapper + hover-revealed actions row\n ───────────────────────────────────────────── */\n\n/* The hover wrapper holds the bubble + an absolutely positioned actions row\n so that revealing the actions does NOT push the bubble upward. */\n.wrap {\n /* Where the words inside the pill start, so anything drawn under the pill\n can line up with them rather than with its edge — this kit's one alignment\n rule. Read from the surface's own padding. */\n --ick-input-text-inset: var(--ick-space-4);\n\n position: relative;\n display: inline-flex;\n justify-content: flex-end;\n /* So a second item can drop below the pill instead of standing beside it.\n The only one there has ever been is the microphone's refusal note, which\n landed in a column to the right of the composer the first time it was\n photographed — the row was the whole layout and nothing had needed a\n second line before. The two overlays here are absolutely positioned and\n are not items at all, so this changes nothing that already existed. */\n flex-wrap: wrap;\n}\n\n.wrap::after {\n content: '';\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n height: 16px;\n}\n\n.buttonGroup {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.actionsRow {\n position: absolute;\n top: calc(100% + 8px);\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 4px;\n padding: 0 8px;\n}\n\n/* ─────────────────────────────────────────────\n Add cards overlay\n ───────────────────────────────────────────── */\n\n.addBackdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: var(--ick-z-raised);\n cursor: default;\n}\n\n/* ─────────────────────────────────────────────\n Attached Image Preview\n ───────────────────────────────────────────── */\n\n/* Holds an attached image above the input. Opaque against the page, and\n transparent once the bubble it sits in is made of glass. */\n/* A section at the top of the composer, above the line you type on. Its own\n surface, because what is going with the message is a different thing from\n the message — and this is how it has looked since it was one image. */\n.attachedTray {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n min-height: 80px;\n padding: var(--ick-space-4);\n margin-bottom: var(--ick-space-4);\n border-radius: var(--ick-radius-lg);\n background-color: var(--ick-surface);\n}\n\n/* Sent: the bubble is glass and the section stops being a panel inside it. */\n.attachedTray[data-glass] {\n background-color: transparent;\n}\n\n.addOverlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: var(--ick-z-sticky);\n}\n\n.addCardsContainer {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n/* The overlay's own close button: a filled circle rather than the naked ghost,\n so it reads against the fanned cards behind it. */\n.closeAdd {\n flex-shrink: 0;\n width: 28px;\n background-color: var(--ick-surface-active);\n color: var(--ick-ink);\n}\n\n.addCardFan {\n all: unset;\n box-sizing: border-box;\n position: absolute;\n width: 160px;\n height: 42px;\n border-radius: var(--ick-radius-xl);\n background-color: var(--ick-surface);\n box-shadow: var(--ick-shadow-float);\n display: flex;\n align-items: center;\n padding: 0 var(--ick-space-6);\n gap: 10px;\n font-family: var(--ick-font-sans);\n font-size: var(--ick-text-md);\n font-weight: var(--ick-weight-medium);\n color: var(--ick-ink);\n cursor: pointer;\n pointer-events: auto;\n transition: background-color 150ms ease;\n}\n\n/* ── Dictation ──────────────────────────────────────────────────────────────\n The microphone sits in the send glyph's slot, so it needs nothing of its\n own except the ring that shows the level of what it is hearing. */\n\n/* Driven by `useVoiceInput`, which writes `--ick-voice-level` here once a\n frame and never through React. A number this component re-rendered on would\n be sixty renders a second for a decoration — the same arithmetic that took\n the highlighter's hover out of React after it cost 28 mutations a crossing.\n\n `scale` and `opacity` only: both are composited, so the ring costs no\n layout and no paint however loudly somebody talks. */\n.voiceMeter {\n --ick-voice-level: 0;\n\n position: absolute;\n inset: 0;\n border-radius: inherit;\n pointer-events: none;\n background: var(--ick-voice-meter);\n transform: scale(calc(1 + var(--ick-voice-level) * var(--ick-voice-meter-spread)));\n opacity: 0;\n transition: opacity var(--ick-duration-fast) var(--ick-ease);\n}\n\n.voiceMeter[data-live] {\n opacity: calc(0.35 + var(--ick-voice-level) * 0.65);\n}\n\n/* Under reduced motion the level is never written, so the ring would sit at\n its resting scale and read as nothing at all. A steady halo instead: the\n state is still visible, it just does not move. */\n@media (prefers-reduced-motion: reduce) {\n .voiceMeter[data-live] {\n transform: scale(calc(1 + var(--ick-voice-meter-spread) * 0.5));\n opacity: 0.6;\n }\n}\n\n/* Lined up with the words in the composer rather than with its edge — the\n kit's one alignment rule, stated the same way everywhere. */\n.voiceNote {\n /* Its own line, the full width of the composer. */\n flex: 0 0 100%;\n /* `margin-right: auto` because the wrap justifies to the end: without it a\n capped width is pushed to the right edge and the note reads as belonging\n to nothing. */\n margin: var(--ick-space-2) auto 0 0;\n padding-inline: var(--ick-input-text-inset);\n max-width: 46ch;\n font-size: var(--ick-text-sm);\n line-height: 1.5;\n color: var(--ick-ink-soft);\n}\n\n.voiceNote[data-tone=\"denied\"] {\n color: var(--ick-danger);\n}\n\n/* The microphone's slot is a Motion child now, like the plus and the send\n glyph, so its size and entrance live in `ChatInput.tsx` beside theirs. What\n is left here is the ring that shows the level of what it is hearing. */\n\n/* Lined up with the words in the composer rather than with its edge — the\n kit's one alignment rule, stated the same way everywhere. */\n.voiceNote {\n /* Its own line, the full width of the composer. */\n flex: 0 0 100%;\n /* `margin-right: auto` because the wrap justifies to the end: without it a\n capped width is pushed to the right edge and the note reads as belonging\n to nothing. */\n margin: var(--ick-space-2) auto 0 0;\n padding-inline: var(--ick-input-text-inset);\n max-width: 46ch;\n font-size: var(--ick-text-sm);\n line-height: 1.5;\n color: var(--ick-ink-soft);\n}\n\n.voiceNote[data-tone=\"denied\"] {\n color: var(--ick-danger);\n}\n\n","\"use client\";\n\nimport React from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { Copy, Pencil } from \"lucide-react\";\nimport { Button } from \"../Button/Button\";\nimport styles from \"./ChatInput.module.css\";\nimport type { InlineAnimConfig } from \"./ChatInput\";\n\nexport interface HoverActionsRowProps {\n showActions: boolean;\n showReadMore: boolean;\n isExpanded: boolean;\n setIsExpanded: React.Dispatch<React.SetStateAction<boolean>>;\n onCopy?: (value: string) => void;\n onEdit?: (value: string) => void;\n value: string;\n ac: InlineAnimConfig | undefined;\n}\n\nexport function HoverActionsRow({\n showActions,\n showReadMore,\n isExpanded,\n setIsExpanded,\n onCopy,\n onEdit,\n value,\n ac,\n}: HoverActionsRowProps) {\n return (\n <AnimatePresence initial={false}>\n {showActions && (\n <motion.div\n key=\"actions\"\n className={styles.actionsRow}\n variants={{\n hidden: { scale: 0.85, opacity: 0 },\n visible: { scale: 1, opacity: 1, transition: { stiffness: 460, damping: 38, staggerChildren: ac?.actions?.staggerDelay ?? 0.07 } },\n exit: { scale: 0.85, opacity: 0, transition: { duration: 0.12 } },\n }}\n initial=\"hidden\"\n animate=\"visible\"\n exit=\"exit\"\n >\n {showReadMore && (\n <motion.div\n key=\"read-more\"\n style={{ marginRight: \"auto\" }}\n variants={{\n hidden: { opacity: 0, scale: 0.8 },\n visible: { opacity: 1, scale: 1, transition: { type: \"spring\", stiffness: ac?.actions?.stiffness ?? 400, damping: ac?.actions?.damping ?? 22 } },\n exit: {},\n }}\n >\n <Button\n variant=\"ghost\"\n icon={isExpanded ? \"Read less\" : \"Read more\"}\n onClick={(e) => {\n e.stopPropagation();\n setIsExpanded((v) => !v);\n }}\n aria-label={isExpanded ? \"Read less\" : \"Read more\"}\n style={{ width: \"auto\", padding: \"4px 10px\", fontSize: 10, letterSpacing: \"0.03em\" }}\n />\n </motion.div>\n )}\n {([\n { icon: <Copy size={14} aria-hidden />, onClick: () => onCopy?.(value), label: \"Copy\" },\n { icon: <Pencil size={14} aria-hidden />, onClick: () => onEdit?.(value), label: \"Edit\" },\n ] as const).map(({ icon, onClick, label }) => (\n <motion.div\n key={label}\n variants={{\n hidden: { opacity: 0, scale: 0.8 },\n visible: { opacity: 1, scale: 1, transition: { type: \"spring\", stiffness: ac?.actions?.stiffness ?? 400, damping: ac?.actions?.damping ?? 22 } },\n exit: {},\n }}\n >\n <Button variant=\"ghost\" icon={icon} onClick={onClick} aria-label={label} title={label} />\n </motion.div>\n ))}\n </motion.div>\n )}\n </AnimatePresence>\n );\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { Paperclip, Palette, Link2, X } from \"lucide-react\";\nimport { Button } from \"../Button/Button\";\nimport styles from \"./ChatInput.module.css\";\nimport type { InlineAnimConfig } from \"./ChatInput\";\n\nconst ADD_CARDS = [\n { Icon: Paperclip, label: \"Add\" },\n { Icon: Palette, label: \"Design\" },\n { Icon: Link2, label: \"Connectors\" },\n] as const;\n\nexport interface AddCardsOverlayProps {\n isAddOpen: boolean;\n setIsAddOpen: React.Dispatch<React.SetStateAction<boolean>>;\n onAdd?: (label?: string) => void;\n showInlineGlyph: boolean;\n showButtons: boolean;\n ac: InlineAnimConfig | undefined;\n}\n\nexport function AddCardsOverlay({\n isAddOpen,\n setIsAddOpen,\n onAdd,\n showInlineGlyph,\n showButtons,\n ac,\n}: AddCardsOverlayProps) {\n return (\n <AnimatePresence>\n {isAddOpen && (\n <>\n <motion.div\n key=\"backdrop\"\n className={styles.addBackdrop}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n onClick={(e) => {\n e.stopPropagation();\n setIsAddOpen(false);\n }}\n />\n <motion.div\n key=\"add-overlay\"\n className={styles.addOverlay}\n >\n <div className={styles.addCardsContainer}>\n {ADD_CARDS.map(({ Icon, label }, i) => {\n const sd = ac?.addCards?.staggerDelay ?? 0.04;\n const enterDelay = i * sd;\n const exitDelay = (ADD_CARDS.length - 1 - i) * sd;\n const angles = [\n ac?.addCards?.angle1 ?? -26,\n ac?.addCards?.angle2 ?? -2,\n ac?.addCards?.angle3 ?? 22,\n ];\n const hoverPull = ac?.addCards?.hoverPull ?? 8;\n\n return (\n <motion.button\n key={label}\n className={styles.addCardFan}\n style={{\n right: showInlineGlyph && showButtons ? 36 : 0,\n bottom: 1,\n transformOrigin: \"calc(100% - 22px) 50%\",\n zIndex: 3 - i,\n }}\n initial={{ opacity: 0, scale: 0.95, rotate: 0, width: 160 }}\n animate={{\n opacity: 1, scale: 1, rotate: angles[i], width: 160,\n transition: {\n type: \"spring\", stiffness: ac?.addCards?.stiffness ?? 350, damping: ac?.addCards?.damping ?? 25, delay: enterDelay,\n opacity: { duration: 0.1, delay: enterDelay },\n },\n }}\n whileHover={{\n width: 160 + hoverPull,\n transition: { type: \"spring\", stiffness: 400, damping: 25 },\n }}\n exit={{\n opacity: 0, scale: 0.5, rotate: 0, width: 160,\n transition: { duration: 0.15, delay: exitDelay, ease: \"easeIn\" },\n }}\n onClick={(e) => {\n e.stopPropagation();\n setIsAddOpen(false);\n onAdd?.(label);\n }}\n aria-label={label}\n >\n <Icon size={16} aria-hidden />\n <span>{label}</span>\n </motion.button>\n );\n })}\n </div>\n\n <motion.div\n key=\"x-btn-wrap\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={{ duration: 0.12, ease: \"easeOut\" }}\n style={{\n position: \"absolute\",\n right: showInlineGlyph && showButtons ? 44 : 8,\n bottom: 8,\n width: 28,\n height: 28,\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n pointerEvents: \"auto\",\n zIndex: 10,\n }}\n >\n <Button\n variant=\"ghost\"\n icon={<X size={14} aria-hidden />}\n onClick={(e) => {\n e.stopPropagation();\n setIsAddOpen(false);\n }}\n aria-label=\"Close\"\n title=\"Close\"\n className={styles.closeAdd}\n />\n </motion.div>\n </motion.div>\n </>\n )}\n </AnimatePresence>\n );\n}\n","/**\n * A shared live region, for telling a screen reader what just happened.\n *\n * Three things about live regions decide the shape of this file:\n *\n * 1. The region has to be in the document *before* the text goes into it.\n * Adding a region and its content in the same commit is, to most screen\n * readers, an ordinary DOM insertion — nothing is spoken. So the node is\n * created first and written to on a later tick.\n * 2. Writing the same string twice is not a change, and is not announced.\n * Clearing first makes a repeated answer speak again.\n * 3. One region per politeness, shared by every component on the page. Several\n * regions competing is how announcements get dropped or interleaved.\n *\n * This is deliberately not a React component: it must not be something a\n * consumer can forget to render.\n */\n\nexport type Politeness = \"polite\" | \"assertive\";\n\n/** Long enough for the region to be registered before it is written to. */\nconst WRITE_DELAY = 100;\n\nconst regions = new Map<Politeness, HTMLElement>();\nconst pending = new Map<Politeness, ReturnType<typeof setTimeout>>();\n\nconst visuallyHidden = {\n position: \"absolute\",\n width: \"1px\",\n height: \"1px\",\n margin: \"-1px\",\n padding: \"0\",\n border: \"0\",\n overflow: \"hidden\",\n clip: \"rect(0 0 0 0)\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n} as const;\n\nfunction getRegion(politeness: Politeness): HTMLElement | null {\n // Server render, or a test environment without a DOM.\n if (typeof document === \"undefined\") return null;\n\n const existing = regions.get(politeness);\n if (existing?.isConnected) return existing;\n\n const node = document.createElement(\"div\");\n node.setAttribute(\"role\", politeness === \"assertive\" ? \"alert\" : \"status\");\n node.setAttribute(\"aria-live\", politeness);\n // Answers are read as a whole. Without this a screen reader may read only\n // the part that changed, which for a replaced answer is meaningless.\n node.setAttribute(\"aria-atomic\", \"true\");\n node.dataset.inlineChatKit = \"live-region\";\n Object.assign(node.style, visuallyHidden);\n document.body.appendChild(node);\n regions.set(politeness, node);\n return node;\n}\n\n/**\n * Speak a message. Empty strings are ignored — clearing is not an announcement.\n */\nexport function announce(message: string, politeness: Politeness = \"polite\"): void {\n // Checked before the region is created, so nothing to say leaves no trace.\n if (!message) return;\n const node = getRegion(politeness);\n if (!node) return;\n\n const queued = pending.get(politeness);\n if (queued) clearTimeout(queued);\n\n node.textContent = \"\";\n pending.set(\n politeness,\n setTimeout(() => {\n pending.delete(politeness);\n node.textContent = message;\n }, WRITE_DELAY)\n );\n}\n\n/**\n * Drop the regions and anything queued. For tests, so one case cannot hear\n * what a previous one said.\n */\nexport function resetAnnouncer(): void {\n pending.forEach((timer) => clearTimeout(timer));\n pending.clear();\n regions.forEach((node) => node.remove());\n regions.clear();\n}\n","/**\n * Whether this reader has asked their system to reduce motion.\n *\n * Motion's own `useReducedMotion` covers rendering. This exists for the places\n * that are not rendering — the reveal in `useChatTurns` decides inside a\n * callback, where a hook's value would have to be captured in a closure and\n * kept fresh in a ref for no gain.\n *\n * The query is matched once and kept. `matchMedia` is not free, and the\n * decision is read on every answer.\n */\nconst QUERY = \"(prefers-reduced-motion: reduce)\";\n\nlet query: MediaQueryList | null | undefined;\n\nexport function prefersReducedMotion(): boolean {\n if (query === undefined) {\n // Server render, or a test environment without matchMedia. Assume motion\n // is fine: the client will correct it on the first real render.\n query = typeof window !== \"undefined\" && window.matchMedia ? window.matchMedia(QUERY) : null;\n }\n return query?.matches ?? false;\n}\n\n/** For tests, which change what `matchMedia` returns between cases. */\nexport function resetReducedMotionCache(): void {\n query = undefined;\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState, useSyncExternalStore, type RefObject } from \"react\";\nimport { announce } from \"../announce/announce\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\n\n/**\n * The microphone, and everything around turning it into text except the part\n * that is a service.\n *\n * The division is the same one the artifact pane settled: the kit owns what is\n * the same in every product and the host owns what differs. Permission,\n * recording, the level of the incoming signal and the states in between are\n * identical wherever this ships — sixty lines every host would otherwise write\n * again, slightly differently. Turning audio into words is a choice between\n * Whisper, Deepgram, a local model and somebody's own endpoint, and a kit that\n * chose one for its consumers would be wrong for most of them.\n *\n * So this records and hands over a `Blob`. What comes back is text.\n *\n * **What that costs, stated rather than discovered:** the browser's own\n * `SpeechRecognition` cannot be reached through this, because it insists on\n * holding the microphone itself and will not take a recording. It is the only\n * free transcriber there is, and this shuts the door on it. The door stays\n * shut on purpose: a kit that let each host drive the microphone would ship\n * four chats that behave differently and call it flexibility.\n */\n\nexport type VoiceState =\n /** No `getUserMedia` — an insecure context, or a browser without it. */\n | \"unsupported\"\n | \"idle\"\n /** The browser's permission prompt is up. It owns that dialog, not us. */\n | \"requesting\"\n | \"listening\"\n /** Recording is over and the host is working on it. */\n | \"transcribing\"\n /** Refused. Not an error: the browser will not ask again on its own. */\n | \"denied\"\n | \"failed\";\n\nexport interface TranscribeContext {\n /** Aborts when the reader cancels, or when the component goes away. */\n signal: AbortSignal;\n /** What the recorder actually produced, which is not the same everywhere. */\n mimeType: string;\n}\n\n/**\n * Audio in, words out.\n *\n * Return a string, a promise of one, or an async iterable of **deltas** — the\n * same three shapes `onSend` takes, and deltas accumulate the same way, so a\n * long dictation can appear as it is recognised rather than all at the end.\n */\nexport type TranscribeHandler = (\n audio: Blob,\n context: TranscribeContext\n) => string | Promise<string> | AsyncIterable<string>;\n\nexport interface UseVoiceInputOptions {\n onTranscribe?: TranscribeHandler;\n /**\n * Called with the transcript so far, every time it grows. Cumulative rather\n * than per-delta: the caller is putting this inside a string it already\n * holds, and giving it the whole run means it does not have to remember\n * where the last one ended.\n */\n onTranscript?: (text: string) => void;\n /** Called once when a recording has been fully transcribed. */\n onDone?: (text: string) => void;\n /**\n * The element the level is written onto, as `--ick-voice-level`, from 0 to 1.\n *\n * A number this component re-rendered on would be sixty renders a second for\n * a decoration. This is the same decision the highlighter's hover made after\n * it cost 28 DOM mutations per crossing: the value the browser needs goes\n * straight to the browser.\n */\n meterRef?: RefObject<HTMLElement | null>;\n}\n\nexport interface VoiceInput {\n state: VoiceState;\n /** Whether a microphone can be asked for at all. */\n supported: boolean;\n /** Why it failed, when it did. Safe to show. */\n error: string | null;\n start: () => void;\n stop: () => void;\n toggle: () => void;\n /** Forget a refusal, so the button can be offered again. */\n reset: () => void;\n}\n\n/** Level meter smoothing. Raw RMS jitters enough to read as noise. */\nconst SMOOTHING = 0.6;\n/** Quietest signal that still moves the meter, so a silent room reads as still. */\nconst FLOOR = 0.02;\n\n/** Support cannot change while the page is open, so there is nothing to watch. */\nconst subscribeNever = () => () => {};\n\nconst canRecord = () =>\n typeof window !== \"undefined\" &&\n typeof navigator !== \"undefined\" &&\n !!navigator.mediaDevices?.getUserMedia &&\n typeof window.MediaRecorder !== \"undefined\";\n\n/**\n * The first container the recorder will admit to supporting.\n *\n * Left to itself Safari produces mp4 and Chrome webm, and a host posting the\n * blob somewhere has to be told which arrived — that is what `mimeType` in the\n * context is for.\n */\nconst pickMimeType = (): string | undefined => {\n const wanted = [\"audio/webm;codecs=opus\", \"audio/webm\", \"audio/mp4\", \"audio/ogg;codecs=opus\"];\n const supported = window.MediaRecorder?.isTypeSupported;\n if (!supported) return undefined;\n return wanted.find((type) => window.MediaRecorder.isTypeSupported(type));\n};\n\nexport function useVoiceInput({\n onTranscribe,\n onTranscript,\n onDone,\n meterRef,\n}: UseVoiceInputOptions): VoiceInput {\n /**\n * Whether a microphone can be asked for, answered once and never again.\n *\n * `useSyncExternalStore` rather than an effect that corrects itself: a\n * server render has no `navigator`, so it must say no, and the client says\n * whatever is true — which is exactly the split this hook is for, and it\n * costs no second render. Written first as `useState(\"unsupported\")` plus an\n * effect, which lint stopped on the right grounds: setting state inside an\n * effect body is a cascading render, and here it was one on every mount.\n */\n const supported = useSyncExternalStore(subscribeNever, canRecord, () => false);\n const [phase, setPhase] = useState<Exclude<VoiceState, \"unsupported\">>(\"idle\");\n const [error, setError] = useState<string | null>(null);\n // Support is not a phase the machine moves through; it decides whether the\n // machine is on the page at all.\n const state: VoiceState = supported ? phase : \"unsupported\";\n\n const recorderRef = useRef<MediaRecorder | null>(null);\n const streamRef = useRef<MediaStream | null>(null);\n const audioRef = useRef<AudioContext | null>(null);\n const frameRef = useRef<number | null>(null);\n const chunksRef = useRef<BlobPart[]>([]);\n const abortRef = useRef<AbortController | null>(null);\n /** Set while stopping, so the recorder's own stop event knows why. */\n const cancelledRef = useRef(false);\n\n // Callbacks are read through a ref so that starting the microphone does not\n // depend on the identity of a function the host recreates every render.\n const handlers = useRef({ onTranscribe, onTranscript, onDone });\n useEffect(() => {\n handlers.current = { onTranscribe, onTranscript, onDone };\n });\n\n /** Everything that holds hardware or a frame loop, released in one place. */\n const teardown = useCallback(() => {\n if (frameRef.current !== null) {\n cancelAnimationFrame(frameRef.current);\n frameRef.current = null;\n }\n streamRef.current?.getTracks().forEach((track) => track.stop());\n streamRef.current = null;\n // Suspended rather than closed on older Safari, which throws on a second\n // close. Either way the graph stops running.\n void audioRef.current?.close().catch(() => {});\n audioRef.current = null;\n recorderRef.current = null;\n meterRef?.current?.style.setProperty(\"--ick-voice-level\", \"0\");\n }, [meterRef]);\n\n useEffect(\n () => () => {\n cancelledRef.current = true;\n abortRef.current?.abort();\n teardown();\n },\n [teardown]\n );\n\n /**\n * Writes the incoming level onto the meter element, once per frame.\n *\n * Skipped entirely under reduced motion. The meter is the only thing moving,\n * and a reader who asked for stillness gets a state that says \"listening\"\n * rather than one that pulses at them.\n */\n const runMeter = useCallback(\n (analyser: AnalyserNode) => {\n if (prefersReducedMotion()) return;\n const buffer = new Uint8Array(analyser.fftSize);\n let smoothed = 0;\n const tick = () => {\n analyser.getByteTimeDomainData(buffer);\n let sum = 0;\n for (const sample of buffer) {\n const centred = (sample - 128) / 128;\n sum += centred * centred;\n }\n const rms = Math.sqrt(sum / buffer.length);\n const level = rms < FLOOR ? 0 : Math.min(1, rms * 3);\n smoothed = smoothed * SMOOTHING + level * (1 - SMOOTHING);\n meterRef?.current?.style.setProperty(\"--ick-voice-level\", smoothed.toFixed(3));\n frameRef.current = requestAnimationFrame(tick);\n };\n frameRef.current = requestAnimationFrame(tick);\n },\n [meterRef]\n );\n\n /** Drain whatever the host returned, in any of its three shapes. */\n const consume = useCallback(async (result: ReturnType<TranscribeHandler>) => {\n if (typeof result === \"string\") return result;\n if (result && typeof (result as AsyncIterable<string>)[Symbol.asyncIterator] === \"function\") {\n let text = \"\";\n for await (const delta of result as AsyncIterable<string>) {\n text += delta;\n handlers.current.onTranscript?.(text);\n }\n return text;\n }\n return await (result as Promise<string>);\n }, []);\n\n const transcribe = useCallback(\n async (audio: Blob, mimeType: string) => {\n const handler = handlers.current.onTranscribe;\n if (!handler) {\n setPhase(\"idle\");\n return;\n }\n const controller = new AbortController();\n abortRef.current = controller;\n setPhase(\"transcribing\");\n announce(\"Transcribing.\");\n try {\n const text = await consume(handler(audio, { signal: controller.signal, mimeType }));\n if (controller.signal.aborted) return;\n // A recording with nothing in it is not a failure. Saying so out loud\n // is the difference between \"it is broken\" and \"say something\".\n if (!text) {\n announce(\"Nothing was heard.\");\n setPhase(\"idle\");\n return;\n }\n handlers.current.onTranscript?.(text);\n handlers.current.onDone?.(text);\n announce(\"Transcript added.\");\n setPhase(\"idle\");\n } catch (cause) {\n if (controller.signal.aborted) return;\n setError(cause instanceof Error ? cause.message : \"The transcript could not be made.\");\n setPhase(\"failed\");\n announce(\"The transcript could not be made.\", \"assertive\");\n } finally {\n abortRef.current = null;\n }\n },\n [consume]\n );\n\n const start = useCallback(async () => {\n // Support is not a phase any more — it is read straight from the platform\n // and gates whether this is reachable at all. Nothing to set here.\n if (!canRecord()) return;\n setError(null);\n cancelledRef.current = false;\n setPhase(\"requesting\");\n let stream: MediaStream;\n try {\n stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch (cause) {\n const name = (cause as DOMException)?.name;\n // A refusal is a durable state, not a passing error: the browser will\n // not raise its prompt again, so telling the reader to try again would\n // be sending them to a button that can no longer work.\n if (name === \"NotAllowedError\" || name === \"SecurityError\") {\n setPhase(\"denied\");\n announce(\"Microphone access was refused.\", \"assertive\");\n } else {\n setError(name === \"NotFoundError\" ? \"No microphone was found.\" : \"The microphone could not be opened.\");\n setPhase(\"failed\");\n announce(\"The microphone could not be opened.\", \"assertive\");\n }\n return;\n }\n\n streamRef.current = stream;\n chunksRef.current = [];\n\n try {\n const Ctor = window.AudioContext ?? (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;\n const context = new Ctor();\n audioRef.current = context;\n const analyser = context.createAnalyser();\n analyser.fftSize = 512;\n context.createMediaStreamSource(stream).connect(analyser);\n runMeter(analyser);\n } catch {\n // No meter is a lesser thing than no recording. Carry on without it.\n }\n\n const mimeType = pickMimeType();\n const recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);\n recorderRef.current = recorder;\n recorder.ondataavailable = (event) => {\n if (event.data.size > 0) chunksRef.current.push(event.data);\n };\n recorder.onstop = () => {\n const type = recorder.mimeType || mimeType || \"audio/webm\";\n const audio = new Blob(chunksRef.current, { type });\n chunksRef.current = [];\n teardown();\n if (cancelledRef.current || audio.size === 0) {\n setPhase(\"idle\");\n return;\n }\n void transcribe(audio, type);\n };\n recorder.start();\n setPhase(\"listening\");\n announce(\"Listening.\");\n }, [runMeter, teardown, transcribe]);\n\n const stop = useCallback(() => {\n const recorder = recorderRef.current;\n if (recorder && recorder.state !== \"inactive\") {\n announce(\"Stopped listening.\");\n recorder.stop();\n return;\n }\n // Nothing recording, so this is a cancel of whatever came after it.\n abortRef.current?.abort();\n abortRef.current = null;\n teardown();\n setPhase(\"idle\");\n }, [teardown]);\n\n const toggle = useCallback(() => {\n if (state === \"listening\" || state === \"requesting\" || state === \"transcribing\") stop();\n else if (state === \"idle\" || state === \"failed\") void start();\n }, [state, start, stop]);\n\n const reset = useCallback(() => {\n setError(null);\n if (canRecord()) setPhase(\"idle\");\n }, []);\n\n return {\n state,\n supported,\n error,\n start: () => void start(),\n stop,\n toggle,\n reset,\n };\n}\n","\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useId,\n useImperativeHandle,\n useRef,\n useState,\n forwardRef,\n} from \"react\";\n\nfunction placeCursorAtEnd(el: HTMLElement) {\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(el);\n range.collapse(false);\n sel?.removeAllRanges();\n sel?.addRange(range);\n}\n\n/**\n * How many characters sit before the caret, or the whole length when the caret\n * is somewhere else on the page.\n *\n * Dictation goes in where the cursor was, which is the difference between\n * finishing a half-typed sentence out loud and losing it. Measured by the\n * length of a range rather than by a node offset: the editor holds one text\n * node most of the time and several after a paste, and only the first of those\n * makes an offset mean anything.\n */\nfunction caretOffset(el: HTMLElement): number {\n const sel = window.getSelection();\n const text = el.textContent ?? \"\";\n if (!sel || sel.rangeCount === 0) return text.length;\n const range = sel.getRangeAt(0);\n if (!el.contains(range.endContainer)) return text.length;\n const upTo = range.cloneRange();\n upTo.selectNodeContents(el);\n upTo.setEnd(range.endContainer, range.endOffset);\n return upTo.toString().length;\n}\n\n/** Put the caret `offset` characters into `el`, clamped to what is there. */\nfunction placeCursorAt(el: HTMLElement, offset: number) {\n const node = el.firstChild;\n if (!node || node.nodeType !== Node.TEXT_NODE) {\n placeCursorAtEnd(el);\n return;\n }\n const range = document.createRange();\n const sel = window.getSelection();\n range.setStart(node, Math.max(0, Math.min(offset, node.textContent?.length ?? 0)));\n range.collapse(true);\n sel?.removeAllRanges();\n sel?.addRange(range);\n}\nimport { AnimatePresence, LayoutGroup, MotionConfig, animate, motion, useAnimationControls, type Transition } from \"motion/react\";\nimport { Plus, X } from \"lucide-react\";\nimport { Attachments, type Attachment } from \"../Attachments/Attachments\";\nimport { Button } from \"../Button/Button\";\nimport { MorphGlyph } from \"./MorphGlyph\";\nimport { HoverActionsRow } from \"./HoverActionsRow\";\nimport { AddCardsOverlay } from \"./AddCardsOverlay\";\nimport { useVoiceInput, type TranscribeHandler } from \"../voice/useVoiceInput\";\nimport styles from \"./ChatInput.module.css\";\n\n\nexport type ChatInputState = \"idle\" | \"typing\" | \"responding\" | \"resting\";\n\nexport interface InlineAnimConfig {\n bubble: { stiffness: number; damping: number; mass: number };\n button: { stiffness: number; damping: number; mass: number; staggerEnter: number; staggerExit: number };\n addButton: { duration: number; visualDuration: number; bounce: number };\n enterButton: { duration: number; visualDuration: number; bounce: number };\n ripple: { scaleX: number; duration: number; pulseDuration: number };\n wrap: {\n nearThreshold: number;\n exitThreshold: number;\n preExpandHeight: number;\n slideInDelay: number;\n /**\n * How long the controls take to leave when the composer grows into a\n * second line, in seconds.\n *\n * It was a literal `0.15` in three places, which is why the exit could not\n * be tuned while everything around it could: the dial reached the springs\n * that bring the row back and nothing that takes it away.\n */\n exitDuration: number;\n };\n actions: { staggerDelay: number; duration: number; stiffness: number; damping: number };\n addCards: { staggerDelay: number; stiffness: number; damping: number; inputScale: number; inputBlur: number; angle1: number; angle2: number; angle3: number; hoverPull: number };\n}\n\nexport const defaultInlineAnimConfig: InlineAnimConfig = {\n bubble: { stiffness: 600, damping: 22, mass: 0.3 },\n button: { stiffness: 500, damping: 50, mass: 0.2, staggerEnter: 0.06, staggerExit: 0.06 },\n addButton: { duration: 0.2, visualDuration: 0.2, bounce: 0.2 },\n enterButton: { duration: 0.2, visualDuration: 0.2, bounce: 0.2 },\n ripple: { scaleX: 1.000, duration: 0.19, pulseDuration: 140 },\n wrap: { nearThreshold: 0.92, exitThreshold: 0.75, preExpandHeight: 16, slideInDelay: 120, exitDuration: 0.15 },\n actions: { staggerDelay: 0.07, duration: 0.12, stiffness: 400, damping: 22 },\n addCards: { staggerDelay: 0.06, stiffness: 800, damping: 41, inputScale: 0.95, inputBlur: 2, angle1: 0, angle2: -25, angle3: -50, hoverPull: 12 },\n};\n\nexport interface ChatInputHandle {\n focus: (options?: FocusOptions) => void;\n setValue: (value: string) => void;\n getValue: () => string;\n}\n\nexport interface ChatInputProps {\n state: ChatInputState;\n value: string;\n onChange: (value: string) => void;\n /**\n * What was typed, and what is going with it.\n *\n * The second argument is the whole reason attachments exist: before it, one\n * could be picked and shown in the composer and then quietly dropped on\n * send, which is worse than not offering it.\n */\n onSubmit: (value: string, attachments: Attachment[]) => void;\n onStop?: () => void;\n onAdd?: () => void;\n /**\n * What is attached, controlled. Leave it out and the composer keeps its own\n * — the same rule `useDisclosure` follows.\n */\n attachments?: Attachment[];\n /**\n * Files somebody picked. Given, this is called instead of the composer\n * attaching them itself — for a host that wants to upload first and attach\n * the URL it gets back.\n */\n onAttach?: (files: File[]) => void;\n onRemoveAttachment?: (id: string) => void;\n /** Passed to the file picker. Defaults to images. */\n accept?: string;\n /** More than one at a time. Off by default. */\n multiple?: boolean;\n /**\n * Audio in, words out. Given, the composer offers a microphone where the\n * send glyph would be while there is nothing to send; left out, there is no\n * microphone at all — the same rule `onRemove` follows on `<Attachments>`.\n *\n * The kit records and hands over a `Blob`; what it does with the audio is\n * the half a host owns. See `useVoiceInput`.\n */\n onTranscribe?: TranscribeHandler;\n onCopy?: (value: string) => void;\n onEdit?: (value: string) => void;\n onCancelEdit?: () => void;\n isEditing?: boolean;\n placeholder?: string;\n animationConfig?: InlineAnimConfig;\n style?: React.CSSProperties;\n}\n\n/** Bubble spring used when no animationConfig override is supplied. */\nconst defaultBubbleSpring: Transition = {\n type: \"spring\",\n stiffness: 600,\n damping: 21.5,\n mass: 0.2,\n};\n\n/**\n * Insert text at the caret of a contenteditable.\n *\n * `execCommand` is deprecated but remains the only way to insert into a\n * contenteditable while keeping the browser's native undo stack, so it stays\n * as the preferred path. It is absent in some environments and can refuse the\n * command, hence the range-based fallback — which the browser does not follow\n * with an `input` event, so the caller is told to sync state itself.\n *\n * Returns true when the browser handled it and an input event is coming.\n */\nconst insertTextAtCaret = (text: string): boolean => {\n if (typeof document.execCommand === \"function\") {\n try {\n if (document.execCommand(\"insertText\", false, text)) return true;\n } catch {\n // Fall through to the manual path.\n }\n }\n\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return false;\n\n const range = selection.getRangeAt(0);\n range.deleteContents();\n const node = document.createTextNode(text);\n range.insertNode(node);\n range.setStartAfter(node);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n return false;\n};\n\nexport const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(\n function ChatInput(\n {\n state,\n value,\n onChange,\n onSubmit,\n onStop,\n onAdd,\n attachments,\n onAttach,\n onRemoveAttachment,\n accept = \"image/*\",\n multiple = false,\n onTranscribe,\n onCopy,\n onEdit,\n onCancelEdit,\n isEditing = false,\n /* A default that can ship. It was \"Placeholder text...\", a stand-in\n nobody replaced, and a default is what every consumer who has not\n thought about it yet gets in production — which is most of them on\n their first afternoon. Found by writing the getting-started page and\n looking at what it drew. */\n placeholder = \"Ask anything…\",\n animationConfig,\n style,\n },\n ref\n ) {\n const instanceId = useId();\n const editorRef = useRef<HTMLDivElement>(null);\n const editorWrapRef = useRef<HTMLDivElement>(null);\n const editorClipRef = useRef<HTMLDivElement>(null);\n const textScrollHeightRef = useRef(0);\n const internalChangeRef = useRef(false);\n const measureSpanRef = useRef<HTMLSpanElement>(null);\n const [hovered, setHovered] = useState(false);\n const [pulsing, setPulsing] = useState(false);\n const [expandedMode, setExpandedMode] = useState(false);\n const [showButtons, setShowButtons] = useState(true);\n const [isExpanded, setIsExpanded] = useState(false);\n const [isOverflowing, setIsOverflowing] = useState(false);\n const [isAddOpen, setIsAddOpen] = useState(false);\n const singleLineHeight = useRef(0);\n const expandedModeRef = useRef(false);\n const compactAvailWidthRef = useRef(0);\n const pendingExpansion = useRef(false);\n const buttonsTimerRef = useRef<number | null>(null);\n\n // The timer below outlives the component otherwise. In a browser React\n // shrugs at a setState on an unmounted component; in a test environment\n // that has already been torn down it throws, which made the suite fail at\n // random depending on how long the last render happened to take.\n useEffect(\n () => () => {\n if (buttonsTimerRef.current) clearTimeout(buttonsTimerRef.current);\n },\n []\n );\n const surfaceControls = useAnimationControls();\n const prevState = useRef(state);\n const animCfgRef = useRef(animationConfig);\n animCfgRef.current = animationConfig;\n\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [own, setOwn] = useState<Attachment[]>([]);\n const attached = attachments ?? own;\n /* Read through a ref by the keydown handler, which is memoised on things\n that do not change per keystroke. Putting the list in its deps would\n rebuild the handler on every attach for no gain. */\n const attachedRef = useRef(attached);\n attachedRef.current = attached;\n\n /* Every object URL this composer made, so it can hand them back.\n `URL.createObjectURL` pins the file in memory until it is revoked, and\n nothing was revoking: attaching and removing an image ten times leaked\n ten of them, and so did navigating away with one attached. */\n const madeUrls = useRef<string[]>([]);\n useEffect(\n () => () => {\n madeUrls.current.forEach((url) => URL.revokeObjectURL(url));\n madeUrls.current = [];\n },\n []\n );\n\n const forget = useCallback((url?: string) => {\n if (!url) return;\n const at = madeUrls.current.indexOf(url);\n // Only ours. A URL the host passed in is the host's to revoke.\n if (at === -1) return;\n URL.revokeObjectURL(url);\n madeUrls.current.splice(at, 1);\n }, []);\n\n const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(e.target.files ?? []);\n // Cleared, so picking the same file twice in a row still fires.\n e.target.value = \"\";\n if (files.length === 0) return;\n\n if (onAttach) {\n onAttach(files);\n return;\n }\n\n const picked = files.map((file) => {\n const url = URL.createObjectURL(file);\n madeUrls.current.push(url);\n return {\n id: `${file.name}-${file.size}-${file.lastModified}`,\n name: file.name,\n type: file.type,\n size: file.size,\n url,\n };\n });\n setOwn((current) => {\n if (multiple) return [...current, ...picked];\n // Replacing drops the old one, so its URL goes with it. Picking six\n // images in a row leaked five otherwise.\n const kept = picked.slice(-1);\n current.forEach((a) => forget(a.url));\n picked.slice(0, -1).forEach((a) => forget(a.url));\n return kept;\n });\n };\n\n const removeAttachment = useCallback(\n (id: string) => {\n if (onRemoveAttachment) {\n onRemoveAttachment(id);\n return;\n }\n setOwn((current) => {\n forget(current.find((a) => a.id === id)?.url);\n return current.filter((a) => a.id !== id);\n });\n },\n [onRemoveAttachment, forget]\n );\n\n useImperativeHandle(ref, () => ({\n focus: (options) => editorRef.current?.focus(options),\n setValue: (v) => {\n const el = editorRef.current;\n if (el) { el.textContent = v; placeCursorAtEnd(el); }\n onChange(v);\n },\n getValue: () => value,\n }), [onChange, value]);\n\n /**\n * Dictation.\n *\n * The microphone takes the send glyph's slot, and it can, because that slot\n * is empty exactly when it is wanted: `showSend` is content-gated, so an\n * empty composer draws nothing there. One control that means \"say it\" until\n * there is something to send and \"send it\" afterwards, rather than a third\n * button pushed into a pill that has room for two.\n */\n const meterRef = useRef<HTMLSpanElement>(null);\n /** Where the transcript goes, fixed when recording starts. */\n const dictation = useRef<{ base: string; at: number } | null>(null);\n /**\n * Where the caret last was inside the editor, or null if it has never been\n * there.\n *\n * Read when recording starts rather than measured then, because by then it\n * is gone: pressing the microphone moves focus to a button, and a\n * contenteditable's selection does not survive that. Reading it in the\n * click handler put every transcript at the end of the field — which is\n * the behaviour \"insert at the caret\" exists to avoid, and it was a test\n * that noticed rather than a screenshot.\n */\n const lastCaret = useRef<number | null>(null);\n\n useEffect(() => {\n if (!onTranscribe) return;\n const el = editorRef.current;\n if (!el) return;\n const remember = () => {\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0) return;\n if (!el.contains(sel.getRangeAt(0).endContainer)) return;\n lastCaret.current = caretOffset(el);\n };\n // On the document, because that is where the event is: a selection\n // inside a contenteditable does not raise one on the element.\n document.addEventListener(\"selectionchange\", remember);\n return () => document.removeEventListener(\"selectionchange\", remember);\n }, [onTranscribe]);\n\n const putTranscript = useCallback(\n (text: string) => {\n const where = dictation.current;\n const el = editorRef.current;\n if (!where || !el) return;\n // The whole run every time rather than the last delta, so a partial\n // transcript that grows replaces itself instead of stacking up.\n const next = where.base.slice(0, where.at) + text + where.base.slice(where.at);\n el.textContent = next;\n placeCursorAt(el, where.at + text.length);\n onChange(next);\n },\n [onChange]\n );\n\n const voice = useVoiceInput({\n onTranscribe,\n onTranscript: putTranscript,\n onDone: () => {\n dictation.current = null;\n editorRef.current?.focus();\n },\n meterRef,\n });\n\n const isGlass = state === \"responding\" || state === \"resting\";\n const isReadOnly = isGlass;\n const hasContent = value.trim().length > 0 || attached.length > 0;\n const isInputting = state === \"typing\" || state === \"idle\";\n const showSend = isInputting && hasContent;\n const showStop = state === \"responding\";\n const isRestingHovered = state === \"resting\" && hovered;\n const showActions = isRestingHovered;\n const showReadMore = state === \"resting\" && isOverflowing;\n const voiceBusy = voice.state === \"listening\" || voice.state === \"requesting\" || voice.state === \"transcribing\";\n /**\n * The microphone stands beside the plus, not in the send glyph's slot.\n *\n * The slot was the first idea and it was wrong for a reason a test found\n * rather than a screenshot: that slot is empty exactly while the composer\n * is, so the microphone would disappear the moment anything was in the\n * field — including the moment the first dictated word landed. Dictating\n * into a half-typed sentence is the case that made \"insert at the caret\"\n * worth building, and it needs a control that is still there once there is\n * something to insert into.\n *\n * Beside the plus it also reads better: both are ways of putting something\n * into the message that is not typing.\n *\n * A refusal keeps it on screen, disabled. The browser will not raise its\n * prompt a second time, so a control that vanished at that moment would\n * leave nothing to explain why — and no way back once the reader has\n * changed it in site settings.\n */\n const showMic = !!onTranscribe && voice.supported && !isEditing;\n const showInlineGlyph = showSend || showStop;\n\n const ac = animationConfig;\n const bubbleSpring: Transition = ac\n ? { type: \"spring\", stiffness: ac.bubble.stiffness, damping: ac.bubble.damping, mass: ac.bubble.mass }\n : defaultBubbleSpring;\n\n const updateFade = useCallback(() => {\n const wrap = editorWrapRef.current;\n const clip = editorClipRef.current;\n if (!wrap || !clip) return;\n const hasOverflow = wrap.scrollHeight > wrap.clientHeight;\n if (!hasOverflow) { clip.removeAttribute(\"data-fade\"); return; }\n const atTop = wrap.scrollTop <= 1;\n const atBottom = wrap.scrollTop + wrap.clientHeight >= wrap.scrollHeight - 1;\n if (!atTop && !atBottom) clip.dataset.fade = \"both\";\n else if (!atTop) clip.dataset.fade = \"top\";\n else if (!atBottom) clip.dataset.fade = \"bottom\";\n else clip.removeAttribute(\"data-fade\");\n }, []);\n\n useEffect(() => {\n const wrap = editorWrapRef.current;\n if (!wrap) return;\n wrap.addEventListener(\"scroll\", updateFade, { passive: true });\n return () => wrap.removeEventListener(\"scroll\", updateFade);\n }, [updateFade]);\n\n useEffect(() => {\n const id = requestAnimationFrame(() => {\n const wrap = editorWrapRef.current;\n if (wrap && !isReadOnly) {\n wrap.scrollTop = wrap.scrollHeight;\n }\n updateFade();\n });\n return () => cancelAnimationFrame(id);\n }, [value, isReadOnly, updateFade]);\n\n /**\n * The composer takes focus when it becomes one, and **does not scroll**.\n *\n * `focus()` scrolls the element into view. That is usually a kindness and\n * here it is a fault: when an answer settles, the next turn is appended\n * and this runs on a composer that has just mounted at the bottom of the\n * conversation — so the browser scrolled to it instantly, and the message\n * that had been sent leapt off the top of the view. Measured on the demo\n * at 1100×700: `scrollTop` 0 → 175 in a single frame at the moment the\n * turn count went from one to two.\n *\n * It took three traces to find, because every instrument pointed at the\n * scroll logic — which was innocent. `Conversation` made exactly one\n * correction in the whole session, and it was a smooth one arriving after\n * the jump had already happened.\n *\n * Where the view goes belongs to `Conversation`, which knows about\n * anchors, tails and whether a reader has scrolled away. Focus is about\n * the keyboard.\n */\n useEffect(() => {\n if (state === \"idle\" || state === \"typing\") {\n editorRef.current?.focus({ preventScroll: true });\n }\n }, [state]);\n\n useEffect(() => {\n const el = editorRef.current;\n if (el) singleLineHeight.current = el.scrollHeight || Math.round(12 * 1.45);\n }, []);\n\n useEffect(() => {\n const el = editorRef.current;\n const span = measureSpanRef.current;\n if (!el || !singleLineHeight.current) return;\n\n requestAnimationFrame(() => {\n const textW = span ? span.offsetWidth : 0;\n const contentW = el.clientWidth - 16;\n const wrap = editorWrapRef.current;\n const isMultiline = (wrap?.scrollHeight ?? 0) > singleLineHeight.current + 2;\n\n const ac = animCfgRef.current;\n if (!expandedModeRef.current) {\n compactAvailWidthRef.current = contentW;\n if (isMultiline || textW >= compactAvailWidthRef.current * (ac?.wrap?.nearThreshold ?? 0.92)) {\n expandedModeRef.current = true;\n pendingExpansion.current = true;\n setShowButtons(false);\n }\n } else {\n const wrappedNow = isMultiline || textW >= contentW - 2;\n if (!wrappedNow && textW < compactAvailWidthRef.current * (ac?.wrap?.exitThreshold ?? 0.75)) {\n expandedModeRef.current = false;\n pendingExpansion.current = false;\n setExpandedMode(false);\n setShowButtons(true);\n if (buttonsTimerRef.current) {\n clearTimeout(buttonsTimerRef.current);\n buttonsTimerRef.current = null;\n }\n }\n }\n });\n }, [value]);\n\n useEffect(() => {\n if (!isGlass) return;\n const wrap = editorWrapRef.current;\n if (!wrap || wrap.scrollTop === 0) return;\n const from = wrap.scrollTop;\n animate(from, 0, {\n type: \"spring\",\n stiffness: 280,\n damping: 28,\n mass: 0.6,\n onUpdate: (v) => { wrap.scrollTop = v; },\n });\n }, [isGlass]);\n\n useEffect(() => {\n if (!isGlass) {\n setIsExpanded(false);\n setIsOverflowing(false);\n } else {\n setIsAddOpen(false);\n }\n }, [isGlass]);\n\n useEffect(() => {\n if (!isGlass) return;\n const clip = editorClipRef.current;\n if (!clip) return;\n if (isOverflowing && !isExpanded) {\n clip.dataset.fade = \"bottom\";\n } else {\n clip.removeAttribute(\"data-fade\");\n }\n }, [isGlass, isOverflowing, isExpanded]);\n\n useEffect(() => {\n const prev = prevState.current;\n prevState.current = state;\n if (prev !== \"responding\" || state !== \"resting\") return;\n\n requestAnimationFrame(() => {\n const wrap = editorWrapRef.current;\n if (wrap) {\n textScrollHeightRef.current = wrap.scrollHeight;\n setIsOverflowing(wrap.scrollHeight > 241);\n updateFade();\n }\n });\n\n setPulsing(true);\n const ac = animCfgRef.current;\n surfaceControls.start({\n scaleX: [1, ac?.ripple?.scaleX ?? 1.018, 1],\n transition: {\n duration: ac?.ripple?.duration ?? 0.38,\n times: [0, 0.4, 1],\n ease: [0.25, 1, 0.5, 1],\n },\n });\n const id = window.setTimeout(() => setPulsing(false), ac?.ripple?.pulseDuration ?? 260);\n return () => clearTimeout(id);\n }, [state, surfaceControls, updateFade]);\n\n const handleInput = useCallback(() => {\n const el = editorRef.current;\n if (!el) return;\n internalChangeRef.current = true;\n onChange(el.textContent ?? \"\");\n }, [onChange]);\n\n const handlePaste = useCallback(\n (e: React.ClipboardEvent<HTMLDivElement>) => {\n e.preventDefault();\n const text = e.clipboardData.getData(\"text/plain\");\n if (!insertTextAtCaret(text)) handleInput();\n },\n [handleInput]\n );\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n if (!e.shiftKey && (value.trim().length > 0 || attachedRef.current.length > 0)) {\n onSubmit(value, attachedRef.current);\n } else if (e.shiftKey && !insertTextAtCaret(\"\\n\")) handleInput();\n }\n },\n [value, onSubmit, handleInput]\n );\n\n useEffect(() => {\n const el = editorRef.current;\n if (!el) return;\n if (internalChangeRef.current) { internalChangeRef.current = false; return; }\n if (el.textContent !== value) {\n el.textContent = value;\n if (document.activeElement === el) placeCursorAtEnd(el);\n }\n }, [value]);\n\n return (\n // `reducedMotion=\"user\"` is set here rather than left to the host app,\n // because a host that never sets it leaves every reader with the full\n // morph. Motion snaps transforms and layout to their final values and\n // keeps opacity and colour, which is the right split: the input still\n // reads as changing state, it just stops travelling to get there.\n <MotionConfig reducedMotion=\"user\">\n <LayoutGroup id={instanceId}>\n <div\n className={styles.wrap}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n style={style}\n >\n <motion.div\n className={`${styles.root} ${isGlass ? styles.isGlassRoot : \"\"}`}\n layout\n animate={{\n filter: isAddOpen ? `blur(${ac?.addCards?.inputBlur ?? 2}px)` : \"blur(0px)\",\n }}\n style={{\n pointerEvents: isAddOpen ? \"none\" : \"auto\",\n width: style?.width === \"100%\" ? \"100%\" : undefined,\n maxWidth: style?.maxWidth,\n }}\n transition={{\n ...bubbleSpring,\n filter: { type: \"tween\", duration: 0.2, ease: \"easeOut\" },\n }}\n >\n <motion.div\n className={`${styles.surface} ${isGlass ? styles.glass : \"\"} ${isRestingHovered ? styles.hovered : \"\"} ${pulsing ? styles.pulsed : \"\"}`}\n style={{\n alignItems: \"stretch\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n }}\n transition={bubbleSpring}\n animate={surfaceControls}\n onClick={() => editorRef.current?.focus()}\n >\n <input\n type=\"file\"\n ref={fileInputRef}\n accept={accept}\n multiple={multiple}\n style={{ display: \"none\" }}\n onChange={handleFileChange}\n />\n\n <AnimatePresence>\n {attached.length > 0 && (\n <motion.div\n initial={{ opacity: 0, height: 0 }}\n animate={{ opacity: 1, height: \"auto\" }}\n exit={{ opacity: 0, height: 0 }}\n transition={{ type: \"spring\", stiffness: 300, damping: 28 }}\n style={{ overflow: \"hidden\", width: \"100%\", flexShrink: 0 }}\n >\n <div className={styles.attachedTray} data-glass={isGlass || undefined}>\n {/* Read-only while the bubble is glass: the message has\n been sent, so these are a record of what went with\n it rather than something still being assembled. */}\n <Attachments\n attachments={attached}\n onRemove={isGlass ? undefined : removeAttachment}\n />\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n\n\n\n <div style={{ display: \"flex\", flexDirection: \"row\", alignItems: \"center\", flexWrap: expandedMode ? \"wrap\" : \"nowrap\", justifyContent: expandedMode ? \"flex-end\" : \"flex-start\", width: \"100%\", minWidth: 0, flex: 1 }}>\n <span\n ref={measureSpanRef}\n aria-hidden=\"true\"\n style={{\n position: \"absolute\",\n visibility: \"hidden\",\n pointerEvents: \"none\",\n whiteSpace: \"nowrap\",\n font: \"inherit\",\n letterSpacing: \"inherit\",\n }}\n >\n {value}\n </span>\n\n <div\n ref={editorClipRef}\n className={styles.editorClip}\n style={{ flexBasis: expandedMode ? \"100%\" : undefined }}\n >\n <motion.div\n ref={editorWrapRef}\n className={`${styles.editorWrap} ${isGlass ? styles.editorWrapGlass : \"\"}`}\n layout=\"position\"\n animate={isGlass && isOverflowing\n ? { maxHeight: isExpanded ? textScrollHeightRef.current : 240 }\n : undefined\n }\n // The text has to travel on the same spring as the box around\n // it. Given its own — this used to be a slower, heavier one —\n // it lags the morph and visibly slides outside the bubble\n // before catching up, for about half a second after the\n // bubble itself has settled. Height is a separate concern and\n // keeps its own easing.\n transition={{\n ...bubbleSpring,\n maxHeight: { type: \"spring\", stiffness: 300, damping: 35, mass: 0.8 },\n }}\n >\n <div\n ref={editorRef}\n className={styles.editor}\n contentEditable={isReadOnly ? \"false\" : \"plaintext-only\"}\n suppressContentEditableWarning\n onInput={handleInput}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n spellCheck={false}\n role=\"textbox\"\n aria-multiline=\"true\"\n aria-label={placeholder}\n data-placeholder={isReadOnly ? \"\" : placeholder}\n />\n </motion.div>\n </div>\n\n <div className={styles.buttonGroup}>\n {/* A keyed child of the row's own presence, like the other two.\n\n It was outside Motion for a while, because inside this\n presence its props appeared to stop reaching the DOM. That\n was measured in jsdom, where Motion's animations never\n complete — an exit that never finishes leaves\n `AnimatePresence` rendering its stored children forever.\n In a real browser the exits do finish, and the props do\n land; verified before moving it back.\n\n Outside, it could not leave: the other two unmount for the\n wrap, so the layout cannot drag them anywhere, while this\n one stayed mounted and got carried into the second line\n with the text. */}\n {!isGlass && showButtons && showMic && (\n <motion.div\n key=\"mic\"\n initial={{ opacity: 0, scale: 0, width: 0, height: 0, marginLeft: 0 }}\n animate={{ opacity: 1, scale: 1, width: 28, height: 28, marginLeft: 2 }}\n exit={{\n opacity: 0, scale: 0, width: 0, height: 0, marginLeft: 0,\n transition: pendingExpansion.current\n ? { type: \"tween\", duration: ac?.wrap?.exitDuration ?? 0.15, ease: \"easeOut\", delay: (ac?.button?.staggerExit ?? 0.055) * 2 }\n : undefined\n }}\n transition={{\n type: \"spring\",\n visualDuration: ac?.addButton?.visualDuration ?? 0.4,\n bounce: ac?.addButton?.bounce ?? 0.5,\n /* The row assembles right to left — the send glyph has\n no delay and the plus waits one stagger — so the\n microphone, leftmost, waits two. */\n delay: (ac?.button?.staggerEnter ?? 0.055) * 2,\n opacity: { type: \"tween\", duration: 0.15 }\n }}\n style={{ display: \"flex\", justifyContent: \"center\", alignItems: \"center\", flexShrink: 0, position: \"relative\" }}\n >\n <Button\n variant=\"ghost\"\n data-voice={voice.state}\n icon={\n <>\n {/* The level of the incoming signal. `useVoiceInput`\n writes `--ick-voice-level` here once a frame and\n nothing reads it but CSS — a number this\n component re-rendered on would be sixty renders a\n second for a decoration. */}\n <span\n ref={meterRef}\n className={styles.voiceMeter}\n data-live={voice.state === \"listening\" || undefined}\n aria-hidden\n />\n {/* A square means \"press to stop what is running\".\n Nothing is running while the browser's prompt is\n up, so `requesting` keeps the microphone and says\n it is busy instead — a stop button over a\n permission dialog claims a recording that has not\n started. */}\n <MorphGlyph\n mode={voice.state === \"listening\" || voice.state === \"transcribing\" ? \"stop\" : \"mic\"}\n size={14}\n />\n </>\n }\n onClick={(e) => {\n e.stopPropagation();\n if (voice.state === \"denied\") return;\n if (!voiceBusy) {\n // Fixed here rather than read when the transcript\n // arrives: by then focus has been on a button for\n // however long somebody spoke, and the caret is\n // wherever that left it.\n // Never seen a caret in the editor means nobody\n // has been typing in it, and the end is where a\n // first sentence belongs.\n const at = Math.min(lastCaret.current ?? value.length, value.length);\n dictation.current = { base: value, at };\n }\n voice.toggle();\n }}\n aria-busy={voice.state === \"requesting\" || voice.state === \"transcribing\"}\n aria-pressed={voice.state === \"listening\"}\n disabled={voice.state === \"denied\"}\n aria-label={\n voice.state === \"listening\"\n ? \"Stop listening\"\n : voice.state === \"requesting\"\n ? \"Waiting for microphone access\"\n : voice.state === \"transcribing\"\n ? \"Cancel transcription\"\n : voice.state === \"denied\"\n ? \"Microphone blocked\"\n : \"Dictate a message\"\n }\n style={{ width: \"100%\", height: \"100%\", position: \"relative\" }}\n />\n </motion.div>\n )}\n <AnimatePresence\n initial={false}\n onExitComplete={() => {\n if (!pendingExpansion.current) return;\n pendingExpansion.current = false;\n setExpandedMode(true);\n if (buttonsTimerRef.current) clearTimeout(buttonsTimerRef.current);\n buttonsTimerRef.current = window.setTimeout(() => {\n setShowButtons(true);\n buttonsTimerRef.current = null;\n }, animCfgRef.current?.wrap?.slideInDelay ?? 120);\n }}\n >\n {!isGlass && showButtons && (\n <motion.div\n key=\"lead\"\n initial={{ opacity: 0, scale: 0, width: 0, height: 0 }}\n animate={{ opacity: 1, scale: 1, width: 28, height: 28 }}\n exit={{\n opacity: 0, scale: 0, width: 0, height: 0,\n transition: pendingExpansion.current\n ? { type: \"tween\", duration: ac?.wrap?.exitDuration ?? 0.15, ease: \"easeOut\", delay: ac?.button?.staggerExit ?? 0.055 }\n : undefined\n }}\n transition={{\n type: \"spring\",\n visualDuration: ac?.addButton?.visualDuration ?? 0.4,\n bounce: ac?.addButton?.bounce ?? 0.5,\n delay: ac?.button?.staggerEnter ?? 0.055,\n opacity: { type: \"tween\", duration: 0.15 }\n }}\n style={{ display: \"flex\", justifyContent: \"center\", alignItems: \"center\", flexShrink: 0, position: \"relative\" }}\n >\n <AnimatePresence mode=\"popLayout\" initial={false}>\n {isEditing ? (\n <motion.div\n key=\"cancel-btn\"\n initial={{ opacity: 0, scale: 0.5, rotate: -90 }}\n animate={{ opacity: 1, scale: 1, rotate: 0 }}\n exit={{ opacity: 0, scale: 0.5, rotate: -90 }}\n transition={{ duration: 0.14, ease: \"easeOut\" }}\n style={{ position: \"absolute\", inset: 0 }}\n >\n <Button\n variant=\"ghost\"\n icon={<X size={14} aria-hidden />}\n onClick={(e) => {\n e.stopPropagation();\n onCancelEdit?.();\n }}\n aria-label=\"Cancel edit\"\n title=\"Cancel edit\"\n style={{ width: \"100%\", height: \"100%\" }}\n />\n </motion.div>\n ) : !isAddOpen ? (\n <motion.div\n key=\"plus-btn\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={{ duration: 0.12, ease: \"easeOut\" }}\n style={{ position: \"absolute\", inset: 0 }}\n >\n <Button\n variant=\"ghost\"\n icon={<Plus size={14} aria-hidden />}\n onClick={(e) => {\n e.stopPropagation();\n setIsAddOpen(true);\n }}\n aria-label=\"Add\"\n title=\"Add\"\n style={{ width: \"100%\", height: \"100%\" }}\n />\n </motion.div>\n ) : null}\n </AnimatePresence>\n </motion.div>\n )}\n {showInlineGlyph && showButtons && (\n <motion.div\n key=\"inline-action\"\n initial={{ opacity: 0, scale: 0, width: 0, height: 0, marginLeft: 0 }}\n animate={{ opacity: 1, scale: 1, width: 28, height: 28, marginLeft: 8 }}\n exit={{\n opacity: 0,\n scale: 0,\n width: 0,\n height: 0,\n marginLeft: 0,\n transition: pendingExpansion.current\n ? { type: \"tween\", duration: ac?.wrap?.exitDuration ?? 0.15, ease: \"easeOut\" }\n : {\n type: \"spring\",\n visualDuration: ac?.enterButton?.visualDuration ?? 0.18,\n bounce: ac?.enterButton?.bounce ?? 0.3,\n opacity: { type: \"tween\", duration: 0.15 },\n height: bubbleSpring,\n width: bubbleSpring,\n marginLeft: bubbleSpring\n }\n }}\n transition={{\n type: \"spring\",\n visualDuration: ac?.enterButton?.visualDuration ?? 0.4,\n bounce: ac?.enterButton?.bounce ?? 0.5,\n opacity: { type: \"tween\", duration: 0.15 }\n }}\n style={{ display: \"flex\", justifyContent: \"center\", alignItems: \"center\", flexShrink: 0, overflow: \"visible\", transformOrigin: \"right\" }}\n >\n <Button\n variant=\"primary\"\n icon={<MorphGlyph mode={showStop ? \"stop\" : isEditing ? \"check\" : \"send\"} />}\n onClick={(e) => {\n e.stopPropagation();\n if (showStop) onStop?.();\n else onSubmit(value, attached);\n }}\n aria-label={showStop ? \"Stop response\" : isEditing ? \"Save edits\" : \"Send message\"}\n style={{ flexShrink: 0, width: 28 }}\n />\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n </div>\n </motion.div>\n\n </motion.div>\n\n <AddCardsOverlay\n isAddOpen={isAddOpen}\n setIsAddOpen={setIsAddOpen}\n onAdd={(label) => {\n if (!label || label === \"Add\") {\n fileInputRef.current?.click();\n }\n onAdd?.();\n }}\n showInlineGlyph={showInlineGlyph}\n showButtons={showButtons}\n ac={ac}\n />\n\n <HoverActionsRow\n showActions={showActions}\n showReadMore={showReadMore}\n isExpanded={isExpanded}\n setIsExpanded={setIsExpanded}\n onCopy={onCopy}\n onEdit={onEdit}\n value={value}\n ac={ac}\n />\n\n {/* A refusal is the one voice state that has to say something in\n words. Every other one is legible from the button: a microphone\n means \"not yet\", a square means \"recording, press to stop\". A\n blocked microphone looks exactly like an idle one, and the\n browser has already stopped offering its prompt, so without this\n line the reader presses a dead control and learns nothing. */}\n {showMic && (voice.state === \"denied\" || voice.state === \"failed\") && (\n <p className={styles.voiceNote} data-tone={voice.state}>\n {voice.state === \"denied\"\n ? \"Microphone access is blocked. Allow it for this site in your browser settings, then reload.\"\n : (voice.error ?? \"The microphone could not be opened.\")}\n </p>\n )}\n </div>\n </LayoutGroup>\n </MotionConfig>\n );\n }\n);\n","import type { ReasoningState } from \"../Reasoning/Reasoning\";\nimport type { Thought } from \"../ChainOfThought/ChainOfThought\";\nimport type { Source } from \"../Sources/Sources\";\nimport type { Task } from \"../TaskList/TaskList\";\nimport type { ToolState } from \"../Tool/Tool\";\nimport type { Decision } from \"../Approval/Approval\";\nimport type { Answer, Question } from \"../QuestionCard/types\";\nimport type { SystemTone } from \"../SystemMessage/SystemMessage\";\nimport type { ArtifactKind, ArtifactState } from \"../Artifact/ArtifactCard\";\n\n/**\n * The parts of a turn that are not the answer's prose.\n *\n * An answer used to be one string, and everything the agent tier draws had\n * nowhere to live: a tool call, a plan and a block of reasoning all flattened\n * into the same text or did not arrive at all. These are what a turn carries\n * alongside `ai`.\n *\n * Every part has an `id` and parts are **merged by it**, which is what makes\n * streaming one of them bearable: send `{ kind: \"reasoning\", id: \"r1\", text }`\n * as it grows, then `{ kind: \"reasoning\", id: \"r1\", state: \"done\" }` when it\n * stops, and the text is still there. Only the fields you send change.\n */\nexport type TurnPart =\n | {\n kind: \"reasoning\";\n id: string;\n text?: string;\n state?: ReasoningState;\n /** In ms. Left out, the block times itself. */\n duration?: number;\n }\n | {\n kind: \"tool\";\n id: string;\n name: string;\n state?: ToolState;\n summary?: string;\n input?: unknown;\n output?: unknown;\n error?: string;\n duration?: number;\n }\n | {\n kind: \"tasks\";\n id: string;\n title?: string;\n tasks: Task[];\n collapsible?: boolean;\n }\n | {\n /**\n * Reasoning that has structure — steps that follow from one another,\n * rather than one block of prose. `reasoning` is the block; this is the\n * chain. Sending both for the same stretch of thinking says it twice.\n */\n kind: \"chain\";\n id: string;\n steps: Thought[];\n state?: \"thinking\" | \"done\";\n /** In ms. Left out, the block times itself. */\n duration?: number;\n }\n | {\n /**\n * What the answer stands on.\n *\n * Also what an inline `[^1]` in the prose resolves against: the citation\n * takes the source at that position, so the list is ordered by the order\n * the answer cites them, not by rank. Send it **before** the prose that\n * cites it and the markers come up already knowing what they point at.\n */\n kind: \"sources\";\n id: string;\n sources: Source[];\n title?: string;\n collapsible?: boolean;\n }\n | {\n /**\n * Something the agent wants to do, and has not done yet.\n *\n * Data rather than JSX, like every other part: a part is what a stream\n * can send, and a stream cannot send a component. The tool it names is\n * drawn for it, unrun.\n */\n kind: \"approval\";\n id: string;\n title: string;\n description?: string;\n tool?: { name: string; input?: unknown };\n /** `null` or absent while it is still being asked. */\n decision?: Decision | null;\n }\n | {\n kind: \"question\";\n id: string;\n /** What this step is about, at the top of the group. */\n title?: string;\n questions: Question[];\n /** Owned by the host: the row reports an answer, it does not keep one. */\n answers?: Record<string, Answer | undefined>;\n activeIndex?: number | null;\n collapsible?: boolean;\n readOnly?: boolean;\n }\n | {\n /**\n * Something that happened to the conversation rather than in it — the\n * window filling, the model changing partway through, a connection\n * going. A part rather than a component the host drops between rows,\n * because those are the two ways anything reaches a conversation here\n * and only one of them can be sent down a stream.\n */\n kind: \"notice\";\n id: string;\n text: string;\n tone?: SystemTone;\n }\n | {\n /**\n * Something the answer produced that is bigger than the answer — a plan,\n * a document, a file. The card in the transcript is a window onto it;\n * pressing that card is what opens the pane.\n *\n * `content` is absent while it is being written, which is the usual way\n * one arrives: the title comes first and the card shimmers it.\n */\n kind: \"artifact\";\n id: string;\n title: string;\n /** What it is, in a word or two — \"8 weeks\", \"Python\". */\n meta?: string;\n /** How the preview is drawn. Defaults to code. */\n preview?: ArtifactKind;\n lang?: string;\n content?: string;\n state?: ArtifactState;\n };\n\n/**\n * An update to a part, which is a part with everything optional but the two\n * fields that say which one it is.\n *\n * This is what a stream and a host actually send. Once a tool call is on\n * screen, saying it finished should be `{ kind: \"tool\", id, state: \"done\" }`\n * and nothing else — repeating the name to satisfy a type is how a field that\n * was not meant to change gets overwritten with whatever was easiest to type.\n */\nexport type TurnPartUpdate = {\n [K in TurnPart[\"kind\"]]: { kind: K; id: string } & Partial<\n Omit<Extract<TurnPart, { kind: K }>, \"kind\" | \"id\">\n >;\n}[TurnPart[\"kind\"]];\n\n/**\n * Fold one update into a turn's list, by id.\n *\n * A shallow merge, deliberately: the fields left out keep the values they had.\n * Sending a state change should not wipe the text that arrived before it.\n */\nexport function mergeParts(parts: TurnPart[], incoming: TurnPartUpdate): TurnPart[] {\n const at = parts.findIndex((part) => part.id === incoming.id);\n if (at === -1) return [...parts, incoming as TurnPart];\n\n const next = parts.slice();\n next[at] = { ...next[at], ...incoming } as TurnPart;\n return next;\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { Attachment } from \"../Attachments/Attachments\";\nimport type { ChatInputState } from \"../ChatInput/ChatInput\";\nimport { announce } from \"../announce/announce\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport { mergeParts, type TurnPart, type TurnPartUpdate } from \"../turnParts/turnParts\";\n\n/** One answer this turn has had. */\nexport interface TurnVersion {\n id: string;\n ai: string;\n parts: TurnPart[];\n}\n\nexport interface ChatTurn {\n id: string;\n /** What the person asked. */\n user: string;\n /** What went with it. */\n attachments?: Attachment[];\n /**\n * Every answer this turn has had, oldest first.\n *\n * Regenerating used to overwrite the answer, which threw away the one being\n * compared against — the reason anybody presses regenerate is to see whether\n * a second attempt is better, and there is no \"better\" once the first is\n * gone. Each attempt is kept and `versionIndex` says which is on screen.\n *\n * Empty until the first answer starts, and absent on a turn a host built by\n * hand — the same tolerance `parts` has.\n */\n versions?: TurnVersion[];\n /** Which of `versions` is in `ai` and `parts`. */\n versionIndex?: number;\n /** What has arrived of the answer so far. */\n ai: string;\n /**\n * Everything the answer is made of that is not its prose — reasoning, tool\n * calls, a plan, a question being asked. Merged by id as they arrive. See\n * `TurnPart`.\n */\n parts: TurnPart[];\n state: ChatInputState;\n}\n\nexport interface SendContext {\n /** Aborted when the reader presses stop, or the component unmounts. */\n signal: AbortSignal;\n turnId: string;\n /**\n * What was sent along with the message. Empty when nothing was.\n *\n * On the context rather than as a second argument: the signature already has\n * a place for \"everything about this send that is not the message\", and a\n * handler that does not care never has to mention it.\n */\n attachments: Attachment[];\n}\n\n/**\n * Produce the reply.\n *\n * Return a string for a complete answer, or an async iterable to stream one.\n * Whatever an API hands back — an SSE reader, an SDK's stream, a plain fetch —\n * fits one of those two shapes.\n *\n * A streamed item is either a **delta of the answer's prose** (a string, which\n * is appended) or a **`TurnPart`** (which is merged into the turn by its id).\n * That is what carries a model's thinking, its tool calls and its plan through\n * to the components that draw them; a stream of strings alone has nowhere to\n * put any of it.\n */\nexport type SendHandler = (\n message: string,\n context: SendContext\n) => AsyncIterable<string | TurnPartUpdate> | Promise<string> | string;\n\n/**\n * What a screen reader is told, and in which language.\n *\n * An answer that appears silently is an answer a blind reader never learns\n * about, so this is on by default. It is spoken once, when the answer settles\n * — never per character. A live region updated on every frame makes a screen\n * reader restart the whole answer on every frame, which is worse than silence.\n */\nexport interface ChatAnnouncements {\n /** Spoken when a request starts. `null` for silence. */\n responding?: string | null;\n /** Spoken when the answer settles. Return `null` for silence. */\n answer?: (text: string) => string | null;\n}\n\nexport interface UseChatTurnsOptions {\n onSend: SendHandler;\n /**\n * Reveal rate for non-streaming replies, in characters per second. Streamed\n * replies are paced by whatever produced them and ignore this.\n */\n revealSpeed?: number;\n /** Pause after a full stop, in ms. Gives read-aloud rhythm to the reveal. */\n sentencePause?: number;\n /** Override the spoken strings, or pass `false` to say nothing at all. */\n announcements?: ChatAnnouncements | false;\n}\n\nexport interface UseChatTurnsResult {\n turns: ChatTurn[];\n /** Report the person's editing of a turn's input. */\n setDraft: (id: string, value: string) => void;\n submit: (id: string, value?: string, attachments?: Attachment[]) => void;\n /**\n * Show another of a turn's answers. Out-of-range is ignored rather than\n * clamped: a caller asking for version 7 of a turn that has three has a bug,\n * and quietly showing them version 3 hides it.\n */\n showVersion: (id: string, index: number) => void;\n /** Abort the answer in flight and settle the turn where it stands. */\n stop: () => void;\n beginEdit: (id: string) => void;\n cancelEdit: (id: string) => void;\n isStreaming: boolean;\n /**\n * Merge a part into a turn from outside the stream.\n *\n * The stream is not the only thing that changes a part: a question the\n * assistant asked is answered by the person reading it, and that answer has\n * to land somewhere. Same merge-by-id as a streamed part.\n */\n updatePart: (turnId: string, part: TurnPartUpdate) => void;\n}\n\nconst DEFAULT_REVEAL_SPEED = 260;\nconst DEFAULT_SENTENCE_PAUSE = 220;\nconst DEFAULT_RESPONDING = \"Generating response\";\n\nconst newId = () =>\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : Math.random().toString(36).slice(2, 11);\n\nconst emptyTurn = (): ChatTurn => ({\n id: newId(),\n user: \"\",\n ai: \"\",\n parts: [],\n versions: [],\n versionIndex: 0,\n state: \"idle\",\n});\n\n/**\n * File the answer on screen under the version it belongs to.\n *\n * `ai` and `parts` are what is rendered; `versions[versionIndex]` is the same\n * answer kept so an earlier one can come back. Two writers for one fact is how\n * they drift, so this is the only place that copies between them — and both\n * writers, the batched stream flush and `patchTurn`, go through it.\n *\n * Returns the turn unchanged when there is nothing filed yet or nothing has\n * moved, so it never makes a new object for no reason.\n */\nconst fileAnswer = (turn: ChatTurn): ChatTurn => {\n const versions = turn.versions;\n if (!versions?.length) return turn;\n\n const at = turn.versionIndex ?? 0;\n const filed = versions[at];\n if (!filed || (filed.ai === turn.ai && filed.parts === turn.parts)) return turn;\n\n return {\n ...turn,\n versions: versions.map((version, i) =>\n i === at ? { ...version, ai: turn.ai, parts: turn.parts } : version\n ),\n };\n};\n\nconst isAsyncIterable = (value: unknown): value is AsyncIterable<string | TurnPartUpdate> =>\n typeof value === \"object\" && value !== null && Symbol.asyncIterator in value;\n\n/**\n * Timing for a typewriter reveal, precomputed once per answer.\n *\n * Index i holds the moment character i should appear. Deriving visible length\n * from elapsed time rather than stepping a timer per character means the\n * reveal runs at the same speed on any display and survives a dropped frame\n * without falling behind.\n */\nconst revealSchedule = (text: string, speed: number, pause: number): number[] => {\n const perChar = 1000 / speed;\n const times = new Array<number>(text.length);\n let t = 0;\n for (let i = 0; i < text.length; i++) {\n t += perChar;\n const c = text[i];\n if (c === \".\" || c === \"!\" || c === \"?\") t += pause;\n else if (c === \",\" || c === \";\" || c === \":\") t += pause * 0.35;\n times[i] = t;\n }\n return times;\n};\n\n/**\n * Owns the turn list, the request in flight, and the reveal.\n *\n * The reason this is a hook in the package rather than an example in the\n * README: writing it correctly means never updating state faster than the\n * display can show it, and leaving finished turns referentially untouched so\n * they can bail out of rendering. Both are easy to get wrong, and getting them\n * wrong is invisible until a conversation grows long.\n */\nexport function useChatTurns({\n onSend,\n revealSpeed = DEFAULT_REVEAL_SPEED,\n sentencePause = DEFAULT_SENTENCE_PAUSE,\n announcements,\n}: UseChatTurnsOptions): UseChatTurnsResult {\n const [turns, setTurns] = useState<ChatTurn[]>(() => [emptyTurn()]);\n const [isStreaming, setIsStreaming] = useState(false);\n\n const abortRef = useRef<AbortController | null>(null);\n const rafRef = useRef<number | null>(null);\n // Text that has arrived but not yet been shown. Flushed once per frame.\n const pendingRef = useRef<Map<string, string>>(new Map());\n /* Parts wait for the same animation frame the text does. A stream that sends\n a part per token would otherwise re-render the row per token, which is the\n cost the frame batching exists to avoid. A queue rather than a map: two\n updates to one part in a single frame both have to be folded, in order. */\n const pendingPartsRef = useRef<Map<string, TurnPartUpdate[]>>(new Map());\n const editingRef = useRef<{ id: string; user: string } | null>(null);\n const onSendRef = useRef(onSend);\n // Consumers write this as an object literal, so it is a new object on every\n // render. Held in a ref rather than a dependency, or every callback below\n // would be rebuilt each render and the memoised rows would stop skipping.\n const announcementsRef = useRef(announcements);\n // A live mirror of `turns`. Reading state inside a setState updater and\n // acting on it there makes the updater impure: React is free to call it\n // twice — StrictMode does, in development — and any work scheduled from\n // inside lands a render late. Both matter here. The morph out of the input\n // is driven by `state`, so a render's delay shows up as the text lagging\n // behind the transition.\n const turnsRef = useRef(turns);\n\n useEffect(() => {\n onSendRef.current = onSend;\n turnsRef.current = turns;\n announcementsRef.current = announcements;\n });\n\n /**\n * Rewrite exactly one turn. Every other turn keeps its object identity, so a\n * memoised row sees the same props and skips rendering entirely — which is\n * what keeps the cost of streaming flat as the conversation grows.\n */\n /**\n * The one writer, which is what keeps `ai`/`parts` and `versions` from\n * disagreeing.\n *\n * `ai` and `parts` are the answer on screen; `versions[versionIndex]` is the\n * same answer in the archive. Every patch that touches one writes through to\n * the other — except a patch that moves `versionIndex`, which is a *switch*\n * and carries both halves itself. Two writers for one fact is how they drift.\n */\n const patchTurn = useCallback((id: string, patch: Partial<ChatTurn>) => {\n setTurns((current) => {\n let changed = false;\n const next = current.map((turn) => {\n if (turn.id !== id) return turn;\n changed = true;\n const merged = { ...turn, ...patch };\n /* A patch that moves `versionIndex` or replaces `versions` is a\n *switch* and carries both halves itself; anything else that touches\n the answer gets filed. */\n const isSwitch = \"versionIndex\" in patch || \"versions\" in patch;\n return isSwitch ? merged : fileAnswer(merged);\n });\n if (!changed) return current;\n turnsRef.current = next;\n return next;\n });\n }, []);\n\n const flush = useCallback(() => {\n rafRef.current = null;\n const pending = pendingRef.current;\n const pendingParts = pendingPartsRef.current;\n if (pending.size === 0 && pendingParts.size === 0) return;\n\n const entries = [...pending];\n const partEntries = [...pendingParts];\n pending.clear();\n pendingParts.clear();\n\n setTurns((current) => {\n const next = current.map((turn) => {\n const text = entries.find(([id]) => id === turn.id)?.[1];\n const incoming = partEntries.find(([id]) => id === turn.id)?.[1];\n if (text === undefined && !incoming) return turn;\n\n return fileAnswer({\n ...turn,\n ...(text === undefined ? null : { ai: text }),\n ...(incoming ? { parts: incoming.reduce(mergeParts, turn.parts) } : null),\n });\n });\n turnsRef.current = next;\n return next;\n });\n }, []);\n\n const schedule = useCallback(() => {\n if (rafRef.current !== null) return;\n rafRef.current = requestAnimationFrame(flush);\n }, [flush]);\n\n const publish = useCallback(\n (id: string, text: string) => {\n pendingRef.current.set(id, text);\n schedule();\n },\n [schedule]\n );\n\n const publishPart = useCallback(\n (id: string, part: TurnPartUpdate) => {\n const queue = pendingPartsRef.current.get(id);\n if (queue) queue.push(part);\n else pendingPartsRef.current.set(id, [part]);\n schedule();\n },\n [schedule]\n );\n\n const updatePart = useCallback(\n (turnId: string, part: TurnPartUpdate) => {\n publishPart(turnId, part);\n },\n [publishPart]\n );\n\n useEffect(\n () => () => {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);\n abortRef.current?.abort();\n },\n []\n );\n\n const settle = useCallback(\n (id: string, wasEdit: boolean) => {\n setIsStreaming(false);\n abortRef.current = null;\n patchTurn(id, { state: \"resting\" });\n // An edited turn already has an input beneath it; a fresh answer needs one.\n if (!wasEdit) {\n setTurns((current) => {\n if (current.some((t) => t.state === \"idle\" && t.ai === \"\" && t.user === \"\")) {\n return current;\n }\n const next = [...current, emptyTurn()];\n turnsRef.current = next;\n return next;\n });\n }\n },\n [patchTurn]\n );\n\n const reveal = useCallback(\n (id: string, text: string, signal: AbortSignal) =>\n new Promise<void>((resolve) => {\n // A typewriter is an animation, and this reader has asked for fewer of\n // them. The answer arrives whole.\n if (prefersReducedMotion()) {\n publish(id, text);\n resolve();\n return;\n }\n\n const times = revealSchedule(text, revealSpeed, sentencePause);\n const start = performance.now();\n let shown = 0;\n\n const step = () => {\n if (signal.aborted) {\n // Stopping shows everything received rather than truncating it —\n // the answer arrived, only the theatre was cut short.\n publish(id, text);\n resolve();\n return;\n }\n const elapsed = performance.now() - start;\n while (shown < text.length && times[shown] <= elapsed) shown++;\n publish(id, text.slice(0, shown));\n if (shown >= text.length) {\n resolve();\n return;\n }\n requestAnimationFrame(step);\n };\n\n requestAnimationFrame(step);\n }),\n [publish, revealSpeed, sentencePause]\n );\n\n /** Route one of the two spoken moments through the live region. */\n const speak = useCallback((moment: \"responding\" | \"answer\", text = \"\") => {\n const config = announcementsRef.current;\n if (config === false) return;\n\n if (moment === \"responding\") {\n // `in` rather than `??`, so an explicit `null` can mean silence.\n const message = config && \"responding\" in config ? config.responding : DEFAULT_RESPONDING;\n if (message) announce(message);\n return;\n }\n\n const message = config?.answer ? config.answer(text) : text;\n if (message) announce(message);\n }, []);\n\n const run = useCallback(\n async (id: string, message: string, wasEdit: boolean, attachments: Attachment[] = []) => {\n const controller = new AbortController();\n abortRef.current = controller;\n setIsStreaming(true);\n /* A new version rather than an overwrite. Parts go with it: the tool\n calls that produced the old answer are not evidence for the new one,\n and they stay filed with the answer they belong to. */\n const before = turnsRef.current.find((t) => t.id === id);\n const versions = [\n ...(before?.versions ?? []),\n { id: `${id}-v${(before?.versions?.length ?? 0) + 1}`, ai: \"\", parts: [] },\n ];\n patchTurn(id, {\n user: message,\n state: \"responding\",\n ai: \"\",\n parts: [],\n versions,\n versionIndex: versions.length - 1,\n });\n speak(\"responding\");\n\n // Held out here so the announcement can read it in `finally`, whether\n // the answer completed, was stopped, or threw partway through.\n let answer = \"\";\n try {\n const result = onSendRef.current(message, {\n signal: controller.signal,\n turnId: id,\n attachments,\n });\n\n if (isAsyncIterable(result)) {\n for await (const chunk of result) {\n if (controller.signal.aborted) break;\n if (typeof chunk === \"string\") {\n answer += chunk;\n publish(id, answer);\n } else {\n publishPart(id, chunk);\n }\n }\n } else {\n answer = (await result) ?? \"\";\n await reveal(id, answer, controller.signal);\n }\n } catch (error) {\n if (!controller.signal.aborted) throw error;\n } finally {\n flush();\n settle(id, wasEdit);\n speak(\"answer\", answer);\n }\n },\n [flush, patchTurn, publish, publishPart, reveal, settle, speak]\n );\n\n const setDraft = useCallback(\n (id: string, value: string) => {\n patchTurn(id, { user: value, state: value.length > 0 ? \"typing\" : \"idle\" });\n },\n [patchTurn]\n );\n\n const submit = useCallback(\n (id: string, value?: string, attachments?: Attachment[]) => {\n const turn = turnsRef.current.find((t) => t.id === id);\n if (!turn) return;\n const message = (value ?? turn.user).trim();\n const going = attachments ?? turn.attachments ?? [];\n /* A picture on its own is a message. Blocking on empty text would offer\n an attach button and then refuse to send what it attached. */\n if (!message && going.length === 0) return;\n if (going.length > 0 || turn.attachments) patchTurn(id, { attachments: going });\n\n // Re-submitting a turn that already has an answer regenerates it in\n // place; it must not spawn a second input below.\n const wasEdit = turn.ai.length > 0;\n editingRef.current = null;\n // Synchronous, so the turn enters `responding` in the same update as the\n // keystroke that sent it and the morph starts with the text already set.\n void run(id, message, wasEdit, going);\n },\n [run, patchTurn]\n );\n\n const showVersion = useCallback(\n (id: string, index: number) => {\n const turn = turnsRef.current.find((t) => t.id === id);\n const version = turn?.versions?.[index];\n if (!version) return;\n /* Carries both halves, which is what tells `patchTurn` this is a switch\n and not an edit of the answer on screen. */\n patchTurn(id, { versionIndex: index, ai: version.ai, parts: version.parts });\n },\n [patchTurn]\n );\n\n const stop = useCallback(() => {\n abortRef.current?.abort();\n }, []);\n\n const beginEdit = useCallback(\n (id: string) => {\n const turn = turnsRef.current.find((t) => t.id === id);\n if (turn) editingRef.current = { id, user: turn.user };\n patchTurn(id, { state: \"typing\" });\n },\n [patchTurn]\n );\n\n const cancelEdit = useCallback(\n (id: string) => {\n const snapshot = editingRef.current;\n editingRef.current = null;\n patchTurn(id, {\n state: \"resting\",\n ...(snapshot && snapshot.id === id ? { user: snapshot.user } : {}),\n });\n },\n [patchTurn]\n );\n\n return {\n turns,\n setDraft,\n submit,\n showVersion,\n stop,\n beginEdit,\n cancelEdit,\n isStreaming,\n updatePart,\n };\n}\n","/* ─────────────────────────────────────────────\n ChatHeader\n\n Two regions and a rule about which one owns what. `start` holds identity —\n back, avatar, title, subtitle — and is the part allowed to shrink, because\n a title can always be cut short. `end` holds actions and never shrinks: an\n action that is half visible is worse than one folded into the menu.\n\n No literal colours. Everything is a token, and the dark theme lives with\n the tokens rather than in a block down here.\n ───────────────────────────────────────────── */\n\n.header {\n box-sizing: border-box;\n position: relative;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--ick-header-gap);\n width: 100%;\n padding-inline: var(--ick-header-pad);\n font-family: var(--ick-font-sans);\n color: var(--ick-ink);\n transition:\n background var(--ick-duration-base) ease,\n box-shadow var(--ick-duration-base) ease,\n border-color var(--ick-duration-base) ease,\n backdrop-filter var(--ick-duration-base) ease;\n}\n\n/* ── Sizes ──────────────────────────────────────────────────────────────── */\n\n.s { min-height: var(--ick-header-height-s); }\n.m { min-height: var(--ick-header-height-m); }\n.l { min-height: var(--ick-header-height-l); }\n\n.s .title { font-size: var(--ick-text-md); }\n.m .title { font-size: var(--ick-text-lg); }\n.l .title { font-size: 1rem; }\n\n/* ── Materials ──────────────────────────────────────────────────────────── */\n\n.plain {\n background: transparent;\n border-bottom: 1px solid transparent;\n}\n\n/* Opaque, which is the whole difference between this and `glass`: a panel\n with a line under it rather than a frosted pane. It was transparent, and\n sticky content scrolled straight through the title — found the moment the\n story was made to actually scroll rather than posing. */\n.bordered {\n background: var(--ick-surface);\n border-bottom: 1px solid var(--ick-header-border);\n}\n\n.glass {\n background: var(--ick-header-bg);\n backdrop-filter: blur(var(--ick-header-blur));\n -webkit-backdrop-filter: blur(var(--ick-header-blur));\n border-bottom: 1px solid var(--ick-header-border);\n}\n\n/* Nothing until the conversation has moved under it. A border drawn over\n untouched content is a line for its own sake; a border that appears at the\n moment content slides beneath says what it is for. */\n.plain[data-scrolled] {\n background: var(--ick-header-bg);\n backdrop-filter: blur(var(--ick-header-blur));\n -webkit-backdrop-filter: blur(var(--ick-header-blur));\n border-bottom-color: var(--ick-header-border);\n}\n\n.bordered[data-scrolled],\n.glass[data-scrolled] {\n box-shadow: var(--ick-shadow-float);\n}\n\n.sticky {\n position: sticky;\n top: 0;\n z-index: var(--ick-z-sticky);\n}\n\n/* ── Regions ────────────────────────────────────────────────────────────── */\n\n.start {\n display: flex;\n align-items: center;\n gap: var(--ick-header-gap);\n /* Without this a flex item refuses to go below its content's width, and the\n ellipsis never appears however narrow the header gets. */\n min-width: 0;\n}\n\n.end {\n display: flex;\n align-items: center;\n /* Its own token rather than the header's, because the two sides are not the\n same problem: the left is a logo beside a title, the right is a row of\n square controls that read as one lump when they are 4px apart. */\n gap: var(--ick-header-action-gap);\n flex-shrink: 0;\n}\n\n.avatar {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.titles {\n display: flex;\n flex-direction: column;\n justify-content: center;\n min-width: 0;\n}\n\n.title {\n display: flex;\n align-items: center;\n gap: var(--ick-space-3);\n margin: 0;\n font-weight: var(--ick-weight-medium);\n font-size: var(--ick-text-lg);\n line-height: 1.3;\n color: var(--ick-header-title);\n}\n\n.subtitle {\n font-size: var(--ick-text-sm);\n line-height: 1.4;\n color: var(--ick-header-subtitle);\n}\n\n.truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n /* `display: flex` on the title beats `text-overflow`, which only applies to\n a block container. The title keeps its dot beside it by making the text\n itself the thing that overflows. */\n display: block;\n}\n\n.title.truncate {\n display: block;\n}\n\n/* Centred: the native arrangement. The title leaves the flow so the two side\n regions can be different widths without pushing it off centre. */\n.center .titles {\n position: absolute;\n left: 50%;\n transform: translateX(-50%);\n max-width: 50%;\n text-align: center;\n align-items: center;\n}\n\n.center .title {\n justify-content: center;\n}\n\n/* ── Links styled as buttons ────────────────────────────────────────────────\n Back and any action carrying an `href`. An anchor rather than a button\n because it navigates, and the browser's own affordances — middle click,\n copy link, open in a tab — only exist on an anchor. It borrows the\n `secondary` button's material so the row reads as one set of controls. */\n\n.back {\n box-sizing: border-box;\n composes: target from \"../styles/touch.module.css\";\n display: inline-flex;\n align-items: center;\n justify-content: center;\n position: relative;\n flex-shrink: 0;\n height: 32px;\n min-width: 32px;\n border-radius: var(--ick-radius-pill);\n color: var(--ick-ink);\n text-decoration: none;\n outline: 0.5px solid transparent;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n background var(--ick-duration-fast) ease,\n box-shadow var(--ick-duration-fast) ease,\n outline-color var(--ick-duration-fast) ease,\n transform 120ms var(--ick-ease);\n}\n\n.s .back { height: 28px; min-width: 28px; }\n.l .back { height: 40px; min-width: 40px; }\n\n.back:hover {\n background: var(--ick-glass-gradient);\n box-shadow: var(--ick-shadow-2);\n outline-color: var(--ick-border-bright);\n}\n\n.back:active {\n background: var(--ick-glass-gradient);\n box-shadow: var(--ick-shadow-inset);\n outline-color: var(--ick-border-bright);\n transform: scale(0.94);\n transition-duration: var(--ick-duration-press);\n}\n\n.back:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: var(--ick-space-1);\n}\n\n/* ── Badge ──────────────────────────────────────────────────────────────────\n Decoration. The number it shows is in the button's accessible name, so a\n reader who never sees this still hears the count. */\n\n.actionWrap {\n position: relative;\n display: inline-flex;\n}\n\n.badge {\n position: absolute;\n top: -2px;\n right: -4px;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 15px;\n height: 15px;\n padding: 0 var(--ick-space-2);\n box-sizing: border-box;\n border-radius: var(--ick-radius-pill);\n background: var(--ick-header-badge-bg);\n color: var(--ick-header-badge-ink);\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n font-weight: var(--ick-weight-semibold);\n line-height: 1;\n pointer-events: none;\n}\n\n/* ── Overflow menu ──────────────────────────────────────────────────────── */\n\n.overflow {\n position: relative;\n display: inline-flex;\n}\n\n.menu {\n position: absolute;\n top: calc(100% + var(--ick-space-3));\n right: 0;\n z-index: var(--ick-z-menu);\n display: flex;\n flex-direction: column;\n /* Wide enough that an ordinary label — \"Switch to the dark theme\" — sits on\n one line, capped so an unusually long one wraps instead of running off\n the edge of a phone. */\n min-width: 210px;\n max-width: 280px;\n padding: var(--ick-space-2);\n border-radius: var(--ick-radius-lg);\n background: var(--ick-surface-raised);\n border: 1px solid var(--ick-border);\n box-shadow: var(--ick-shadow-float);\n}\n\n.menuItem {\n all: unset;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--ick-space-4);\n padding: var(--ick-space-3) var(--ick-space-4);\n border-radius: var(--ick-radius-md);\n font-family: var(--ick-font-sans);\n font-size: var(--ick-text-md);\n color: var(--ick-ink);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n}\n\n.menuItem:hover:not(:disabled) {\n background: var(--ick-surface-active);\n}\n\n.menuItem:active:not(:disabled) {\n background: var(--ick-surface-press);\n}\n\n/* The ring is worn by the item an arrow key would move from, and the menu\n says when that is the case — see `byKeyboard` in `ChatHeader.tsx`.\n\n Not `:focus-visible`, which is what this used and which the engines do not\n agree on. A menu focuses an item as it opens; Chromium reads that as\n programmatic and shows nothing, WebKit shows a ring. On a phone that meant\n every menu opened with a thumb came up with its first item ringed, looking\n chosen. Measured in both engines rather than argued about. */\n.menu[data-keyboard] .menuItem:focus {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n.menuItem:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n\n.menuItem[aria-checked=\"true\"] {\n background: var(--ick-surface-active);\n}\n\n.menuIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--ick-ink-soft);\n}\n\n.menuCount {\n margin-left: auto;\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n color: var(--ick-ink-faint);\n}\n\n/* ── Reduced motion ─────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .back {\n transition-property: background, box-shadow, outline-color;\n }\n}\n","\"use client\";\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { ArrowLeft, MoreHorizontal } from \"lucide-react\";\nimport { Button, type ButtonSize } from \"../Button/Button\";\nimport styles from \"./ChatHeader.module.css\";\n\nexport type ChatHeaderVariant = \"plain\" | \"glass\" | \"bordered\";\nexport type ChatHeaderSize = \"s\" | \"m\" | \"l\";\n\n/**\n * One action in the end region.\n *\n * Described rather than handed over as a node, and the reason is not tidiness:\n * a header that is given arbitrary children cannot fold them into an overflow\n * menu when the space runs out, because it has no idea what any of them are —\n * no label to list, no icon to draw. Everything `collapseActionsAt` does\n * depends on this shape.\n */\nexport interface ChatHeaderAction {\n /** Stable across renders. Also the React key and the menu item's id. */\n id: string;\n /**\n * The accessible name, and the text used in the overflow menu.\n *\n * Not optional. An icon button has no name of its own, and a header full of\n * unlabelled glyphs is a header a screen reader reads as \"button, button,\n * button\".\n */\n label: string;\n icon: ReactNode;\n onClick?: () => void;\n /** Renders an anchor rather than a button. Navigation is not a click. */\n href?: string;\n /**\n * A number worth showing on the glyph — saved bookmarks, unread replies.\n * It is folded into the accessible name too: the badge is decorative, and a\n * reader who cannot see it would otherwise lose the count entirely.\n */\n count?: number;\n /** A toggle rather than a command. Renders `aria-pressed`. */\n active?: boolean;\n disabled?: boolean;\n /** Stays visible when the rest collapse. For the one action that matters. */\n pinned?: boolean;\n}\n\nexport interface ChatHeaderProps\n extends Omit<HTMLAttributes<HTMLElement>, \"title\" | \"children\"> {\n /** What the conversation is about, so the reader can see where they are. */\n title?: ReactNode;\n /** The second line: the model, a count, a state. */\n subtitle?: ReactNode;\n /** Logo or agent avatar, drawn before the title. */\n avatar?: ReactNode;\n /**\n * How the title is exposed to assistive technology. A heading is what makes\n * the conversation reachable by heading navigation; the level belongs to the\n * host's document, not to us, so it is a prop. `false` renders plain text.\n */\n headingLevel?: 1 | 2 | 3 | 4 | 5 | 6 | false;\n /** Long titles get an ellipsis rather than a second line. */\n truncate?: boolean;\n\n /** Renders a back button. */\n onBack?: () => void;\n /** Renders a back link instead — navigation belongs in an anchor. */\n backHref?: string;\n backLabel?: string;\n\n /** The managed actions. These are what collapse. */\n actions?: ChatHeaderAction[];\n overflowLabel?: string;\n\n variant?: ChatHeaderVariant;\n size?: ChatHeaderSize;\n /** `center` is the native/mobile arrangement: title centred, actions apart. */\n align?: \"start\" | \"center\";\n sticky?: boolean;\n /** Grow a border and a backdrop once the conversation has scrolled under it. */\n elevateOnScroll?: boolean;\n /**\n * Width, in pixels, below which unpinned actions fold into an overflow menu.\n * `false` never folds. Measured on the header itself, not the viewport, so\n * a header in a narrow panel collapses on its own terms.\n */\n collapseActionsAt?: number | false;\n /**\n * Render as `<header>`, which is a `banner` landmark at the top level of a\n * page. Set `false` for a header inside a panel or a card, where claiming\n * the page's banner would be a lie.\n */\n landmark?: boolean;\n\n /**\n * Anything the kit should not manage — a segmented control, a model picker.\n * Sits in the end region before the actions, and never collapses: the kit\n * cannot summarise what it cannot read.\n */\n children?: ReactNode;\n}\n\nconst BUTTON_SIZE: Record<ChatHeaderSize, ButtonSize> = { s: \"s\", m: \"m\", l: \"l\" };\n/**\n * How big an icon in this header should be, per header size.\n *\n * Exported because the actions are the caller's: the header draws its own back\n * button and overflow menu from this, and everything beside them is a\n * `ReactNode` somebody else made at whatever size they guessed. A segmented\n * toggle in the demo came out at 15 against everything else's 16 — a pixel,\n * and visible, because the row is four icons in a line and one of them was\n * smaller.\n *\n * `<Highlighter size={headerIconSize.m} />` rather than a number, and the row\n * stays even when the header's size changes.\n */\nexport const headerIconSize: Record<ChatHeaderSize, number> = { s: 14, m: 16, l: 18 };\n\nconst ICON_SIZE = headerIconSize;\n\nconst sizeClass: Record<ChatHeaderSize, string> = {\n s: styles.s,\n m: styles.m,\n l: styles.l,\n};\n\nconst variantClass: Record<ChatHeaderVariant, string> = {\n plain: styles.plain,\n glass: styles.glass,\n bordered: styles.bordered,\n};\n\n/** The name a reader hears, which has to carry what the badge shows. */\nconst actionName = (action: ChatHeaderAction): string =>\n action.count === undefined ? action.label : `${action.label}, ${action.count}`;\n\n/**\n * The nearest ancestor that actually scrolls, or the window.\n *\n * `elevateOnScroll` cannot just listen to the window: a chat is very often a\n * panel with its own overflow, and in that case the window never scrolls at\n * all — the header would stay flat forever.\n */\nconst scrollParent = (node: HTMLElement | null): HTMLElement | Window => {\n let current = node?.parentElement ?? null;\n while (current) {\n const { overflowY } = getComputedStyle(current);\n if (/(auto|scroll|overlay)/.test(overflowY) && current.scrollHeight > current.clientHeight) {\n return current;\n }\n current = current.parentElement;\n }\n return window;\n};\n\nexport const ChatHeader = forwardRef<HTMLElement, ChatHeaderProps>(function ChatHeader(\n {\n title,\n subtitle,\n avatar,\n headingLevel = 2,\n truncate = true,\n onBack,\n backHref,\n backLabel = \"Back\",\n actions = [],\n overflowLabel = \"More actions\",\n variant = \"plain\",\n size = \"m\",\n align = \"start\",\n sticky = false,\n elevateOnScroll = sticky,\n collapseActionsAt = 520,\n landmark = true,\n children,\n className,\n ...rest\n },\n ref\n) {\n const rootRef = useRef<HTMLElement | null>(null);\n const [collapsed, setCollapsed] = useState(false);\n const [scrolled, setScrolled] = useState(false);\n\n const setRefs = useCallback(\n (node: HTMLElement | null) => {\n rootRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n },\n [ref]\n );\n\n /* ── Collapsing ──────────────────────────────────────────────────────────\n Measured on the element, not the viewport. A width of zero means nobody\n has laid it out yet — jsdom, a display:none ancestor — and guessing\n \"collapsed\" from an unmeasurable element would hide the actions in every\n test and every hidden tab. */\n useEffect(() => {\n const node = rootRef.current;\n if (!node || collapseActionsAt === false || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(([entry]) => {\n const width = entry.contentRect.width;\n setCollapsed(width > 0 && width < collapseActionsAt);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, [collapseActionsAt]);\n\n /* ── Elevation ───────────────────────────────────────────────────────── */\n useEffect(() => {\n if (!elevateOnScroll) return;\n const target = scrollParent(rootRef.current);\n const read = () => {\n // Identity, not `instanceof Window` and not a property check. The first\n // is a realm test — already false under jsdom, and false in a browser\n // the moment the node lives in an iframe. The second is worse than it\n // looks: `\"scrollTop\" in window` is true under jsdom even though the\n // value is undefined. This is the one thing that cannot be wrong, since\n // `scrollParent` returned this very object a moment ago.\n const top = target === window ? window.scrollY : (target as HTMLElement).scrollTop;\n setScrolled(top > 0);\n };\n read();\n target.addEventListener(\"scroll\", read, { passive: true });\n return () => target.removeEventListener(\"scroll\", read);\n }, [elevateOnScroll]);\n\n /* Both flags are read through their prop rather than cleared when it turns\n off. An effect that resets state on the way out leaves a render where the\n old value is still on screen, and needs the state to be written twice for\n what is really one derived fact. */\n const isCollapsed = collapseActionsAt !== false && collapsed;\n const visible = isCollapsed ? actions.filter((a) => a.pinned) : actions;\n const folded = isCollapsed ? actions.filter((a) => !a.pinned) : [];\n\n const Heading = (headingLevel ? (`h${headingLevel}` as const) : \"span\") as \"span\";\n\n const Root = (landmark ? \"header\" : \"div\") as \"header\";\n\n return (\n <Root\n ref={setRefs}\n className={[\n styles.header,\n sizeClass[size],\n variantClass[variant],\n align === \"center\" ? styles.center : \"\",\n sticky ? styles.sticky : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n data-scrolled={(elevateOnScroll && scrolled) || undefined}\n {...rest}\n >\n <div className={styles.start}>\n {backHref ? (\n <a href={backHref} className={styles.back} aria-label={backLabel}>\n <ArrowLeft size={ICON_SIZE[size]} aria-hidden />\n </a>\n ) : onBack ? (\n <Button\n variant=\"secondary\"\n size={BUTTON_SIZE[size]}\n icon={<ArrowLeft size={ICON_SIZE[size]} aria-hidden />}\n aria-label={backLabel}\n onClick={onBack}\n />\n ) : null}\n\n {avatar && <span className={styles.avatar}>{avatar}</span>}\n\n {(title || subtitle) && (\n <div className={styles.titles}>\n {title && (\n <Heading\n className={[styles.title, truncate ? styles.truncate : \"\"].filter(Boolean).join(\" \")}\n title={typeof title === \"string\" && truncate ? title : undefined}\n >\n {title}\n </Heading>\n )}\n {subtitle && (\n <span\n className={[styles.subtitle, truncate ? styles.truncate : \"\"]\n .filter(Boolean)\n .join(\" \")}\n >\n {subtitle}\n </span>\n )}\n </div>\n )}\n </div>\n\n <div className={styles.end}>\n {children}\n {visible.map((action) => (\n <HeaderAction key={action.id} action={action} size={size} />\n ))}\n {folded.length > 0 && (\n <OverflowMenu actions={folded} size={size} label={overflowLabel} />\n )}\n </div>\n </Root>\n );\n});\n\n/** One managed action: a button, or an anchor when it navigates. */\nfunction HeaderAction({ action, size }: { action: ChatHeaderAction; size: ChatHeaderSize }) {\n const name = actionName(action);\n const badge =\n action.count !== undefined && action.count > 0 ? (\n <span className={styles.badge} aria-hidden=\"true\">\n {action.count > 99 ? \"99+\" : action.count}\n </span>\n ) : null;\n\n if (action.href) {\n return (\n <a href={action.href} className={styles.back} aria-label={name} title={action.label}>\n {action.icon}\n {badge}\n </a>\n );\n }\n\n return (\n <span className={styles.actionWrap}>\n <Button\n variant=\"secondary\"\n size={BUTTON_SIZE[size]}\n icon={action.icon}\n aria-label={name}\n title={action.label}\n aria-pressed={action.active}\n disabled={action.disabled}\n onClick={action.onClick}\n />\n {badge}\n </span>\n );\n}\n\n/**\n * The folded actions.\n *\n * A real menu, not a list of buttons in a box: one tab stop, arrows to move,\n * Escape to leave, and focus handed back to the trigger on the way out. The\n * pattern is the same one the highlighter's menu uses.\n */\nfunction OverflowMenu({\n actions,\n size,\n label,\n}: {\n actions: ChatHeaderAction[];\n size: ChatHeaderSize;\n label: string;\n}) {\n const [open, setOpen] = useState(false);\n const [index, setIndex] = useState(0);\n /**\n * Whether the reader is working this menu by keyboard, which decides whether\n * the focused item wears a ring.\n *\n * `:focus-visible` was supposed to decide it and cannot, because the two\n * engines disagree about what it means. A menu moves focus to an item the\n * moment it opens — that is the pattern, and it is what lets an arrow key\n * take over. Chromium sees programmatic focus after a tap and correctly\n * shows no ring; WebKit shows one. Measured in both: on a tap Chromium says\n * `:focus-visible` does not match and Safari says it does, so on a phone the\n * first item of every menu opened with a thumb came up ringed as though it\n * had been chosen.\n *\n * So the question is answered here rather than guessed at by the engine. It\n * is also the more honest question: what the ring is for is *\"an arrow key\n * will move this\"*, and only this component knows whether an arrow key is\n * in play.\n */\n const [byKeyboard, setByKeyboard] = useState(false);\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n const menuRef = useRef<HTMLDivElement | null>(null);\n const menuId = useId();\n\n const close = useCallback(\n (returnFocus: boolean) => {\n setOpen(false);\n if (returnFocus) triggerRef.current?.focus();\n },\n []\n );\n\n /* Clamped here rather than corrected by an effect. A menu left open while\n the list shrinks under it would otherwise point past the end for one\n render, and that render is the one that moves focus. */\n const active = Math.min(index, Math.max(actions.length - 1, 0));\n\n // Focus lands on the item the roving index names, once the menu exists.\n useEffect(() => {\n if (!open) return;\n const items = menuRef.current?.querySelectorAll<HTMLElement>(\n '[role=\"menuitem\"], [role=\"menuitemcheckbox\"]'\n );\n items?.[active]?.focus();\n }, [open, active]);\n\n const onKeyDown = (event: React.KeyboardEvent) => {\n if (event.key === \"Escape\") {\n event.stopPropagation();\n close(true);\n return;\n }\n /* Any of the keys below means the reader has taken the keyboard, whatever\n opened the menu — so the ring appears from here on even if a thumb\n opened it. */\n if ([\"ArrowDown\", \"ArrowUp\", \"Home\", \"End\"].includes(event.key)) setByKeyboard(true);\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n setIndex((i) => (i + 1) % actions.length);\n return;\n }\n if (event.key === \"ArrowUp\") {\n event.preventDefault();\n setIndex((i) => (i - 1 + actions.length) % actions.length);\n return;\n }\n if (event.key === \"Home\") {\n event.preventDefault();\n setIndex(0);\n return;\n }\n if (event.key === \"End\") {\n event.preventDefault();\n setIndex(actions.length - 1);\n }\n };\n\n return (\n <span className={styles.overflow}>\n <Button\n ref={triggerRef}\n variant=\"secondary\"\n size={BUTTON_SIZE[size]}\n icon={<MoreHorizontal size={ICON_SIZE[size]} aria-hidden />}\n aria-label={label}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n aria-controls={open ? menuId : undefined}\n onClick={(event) => {\n /* `detail` is the click count, and a click synthesised from Enter or\n Space reports 0. Which makes it the one reliable way to tell how a\n button was pressed from inside its own handler — a keydown\n listener would have to duplicate what the browser already does\n with activation behaviour. */\n setByKeyboard(event.detail === 0);\n setIndex(0);\n setOpen((v) => !v);\n }}\n />\n {open && (\n <div\n ref={menuRef}\n id={menuId}\n role=\"menu\"\n aria-label={label}\n className={styles.menu}\n data-keyboard={byKeyboard || undefined}\n onKeyDown={onKeyDown}\n onBlur={(event) => {\n // Tab away and the menu goes with you, rather than being left\n // open behind the focus ring.\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {\n close(false);\n }\n }}\n >\n {actions.map((action, i) => (\n <button\n key={action.id}\n type=\"button\"\n /* A toggle inside a menu is a `menuitemcheckbox`. `menuitem`\n does not take a pressed or checked state, so saying it that\n way is an ARIA error, not a shortcut. */\n role={action.active === undefined ? \"menuitem\" : \"menuitemcheckbox\"}\n aria-checked={action.active}\n tabIndex={active === i ? 0 : -1}\n className={styles.menuItem}\n disabled={action.disabled}\n onClick={() => {\n action.onClick?.();\n close(true);\n }}\n >\n <span className={styles.menuIcon}>{action.icon}</span>\n <span>{action.label}</span>\n {action.count !== undefined && action.count > 0 && (\n <span className={styles.menuCount}>{action.count}</span>\n )}\n </button>\n ))}\n </div>\n )}\n </span>\n );\n}\n","/* ─────────────────────────────────────────────\n AnswerActions\n ───────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--ick-space-1);\n /* Pulled left by the button's own padding, so the first glyph lines up with\n the text above it rather than sitting a few pixels in. */\n margin-left: calc(var(--ick-space-2) * -1);\n}\n\n/* Four 28px buttons two pixels apart: on a pointer that is a tidy cluster; on\n a finger it is four 44px hit areas overlapping by fourteen, and a press\n between two of them goes to whichever was painted last. The row spreads only\n where that is true. */\n@media (pointer: coarse) {\n .row {\n gap: var(--ick-touch-gap);\n }\n}\n\n/* Invisible, not absent. It keeps its space so nothing shifts when it appears,\n and it still hit-tests at zero opacity — which is what makes hovering it\n work at all. `:focus-within` is what saves anyone arriving by keyboard from\n tabbing into something they cannot see. */\n.reveal {\n opacity: 0;\n transition: opacity var(--ick-duration-fast) ease;\n}\n\n.reveal:hover,\n.reveal:focus-within {\n opacity: 1;\n}\n\n/* The verdict that was given stays lit. `aria-pressed` is already the state;\n this is the same fact, drawn. */\n.row :global(button[aria-pressed=\"true\"]) {\n color: var(--ick-ink);\n background: var(--ick-surface-active);\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { Check, Copy, RotateCcw, ThumbsDown, ThumbsUp } from \"lucide-react\";\nimport { Button } from \"../Button/Button\";\nimport { announce } from \"../announce/announce\";\nimport styles from \"./AnswerActions.module.css\";\n\nexport type Verdict = \"up\" | \"down\";\n\nexport interface AnswerActionsProps\n // Ours hands over the answer; the DOM's hands over a ClipboardEvent.\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onCopy\"> {\n /** The answer these act on. Copy takes it; the callbacks receive it. */\n text: string;\n /** Defaults to the clipboard. */\n onCopy?: (text: string) => void;\n /** Omit and no regenerate button is drawn. */\n onRegenerate?: () => void;\n /**\n * Omit and no thumbs are drawn. Called with `null` when a verdict is taken\n * back — pressing the one already given is how somebody undoes it.\n */\n onFeedback?: (verdict: Verdict | null) => void;\n /** Controlled, for a host that stores the verdict. */\n feedback?: Verdict | null;\n /** Anything else, after the built-in controls. */\n children?: ReactNode;\n /**\n * Invisible until hovered or focused. It still occupies its space and still\n * hit-tests, so nothing shifts and nothing is unreachable — `:focus-within`\n * brings it back for anyone arriving by keyboard.\n */\n reveal?: boolean;\n /** While an answer is being regenerated. */\n busy?: boolean;\n labels?: Partial<Record<\"copy\" | \"copied\" | \"regenerate\" | \"up\" | \"down\", string>>;\n}\n\nconst DEFAULT_LABELS = {\n copy: \"Copy answer\",\n copied: \"Copied\",\n regenerate: \"Regenerate\",\n up: \"Good answer\",\n down: \"Bad answer\",\n} as const;\n\nconst COPIED_FOR = 1600;\n\n/**\n * What you can do to an answer once it has arrived.\n *\n * The input has had a hover row since the beginning; the answer had nothing,\n * which is backwards — the answer is the part worth keeping.\n */\nexport function AnswerActions({\n text,\n onCopy,\n onRegenerate,\n onFeedback,\n feedback = null,\n children,\n reveal = false,\n busy = false,\n labels,\n className,\n ...rest\n}: AnswerActionsProps) {\n const [copied, setCopied] = useState(false);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const label = { ...DEFAULT_LABELS, ...labels };\n\n useEffect(() => () => { if (timer.current) clearTimeout(timer.current); }, []);\n\n const copy = useCallback(() => {\n if (onCopy) onCopy(text);\n else void navigator.clipboard?.writeText(text);\n\n setCopied(true);\n // The tick is a picture. Without this a reader is told nothing happened,\n // which is indistinguishable from the copy having failed.\n announce(label.copied);\n if (timer.current) clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopied(false), COPIED_FOR);\n }, [label.copied, onCopy, text]);\n\n /** Pressing the verdict already given takes it back. */\n const vote = (verdict: Verdict) => onFeedback?.(feedback === verdict ? null : verdict);\n\n return (\n <div\n className={[styles.row, reveal ? styles.reveal : \"\", className ?? \"\"].filter(Boolean).join(\" \")}\n {...rest}\n >\n <Button\n variant=\"ghost\"\n icon={copied ? <Check size={14} aria-hidden /> : <Copy size={14} aria-hidden />}\n onClick={copy}\n aria-label={copied ? label.copied : label.copy}\n title={copied ? label.copied : label.copy}\n />\n\n {onRegenerate && (\n <Button\n variant=\"ghost\"\n icon={<RotateCcw size={14} aria-hidden />}\n onClick={onRegenerate}\n disabled={busy}\n loading={busy}\n aria-label={label.regenerate}\n title={label.regenerate}\n />\n )}\n\n {onFeedback && (\n <>\n <Button\n variant=\"ghost\"\n icon={<ThumbsUp size={14} aria-hidden />}\n onClick={() => vote(\"up\")}\n aria-pressed={feedback === \"up\"}\n aria-label={label.up}\n title={label.up}\n />\n <Button\n variant=\"ghost\"\n icon={<ThumbsDown size={14} aria-hidden />}\n onClick={() => vote(\"down\")}\n aria-pressed={feedback === \"down\"}\n aria-label={label.down}\n title={label.down}\n />\n </>\n )}\n\n {children}\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n Branch\n\n Which answer you are looking at, and how to reach the others. It sits in the\n row of answer actions and is built like them — a quiet control that only\n asks for attention when the pointer is on it.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.branch {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n gap: var(--ick-space-1);\n font-family: var(--ick-font-sans);\n}\n\n.step {\n all: unset;\n composes: target from \"../styles/touch.module.css\";\n /* A positioned box for it to centre on. */\n position: relative;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--ick-branch-size);\n height: var(--ick-branch-size);\n border-radius: var(--ick-radius-sm);\n color: var(--ick-branch-ink);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n background var(--ick-duration-fast) ease,\n color var(--ick-duration-fast) ease;\n}\n\n.step:hover:not(:disabled) {\n background: var(--ick-surface-hover);\n color: var(--ick-ink);\n}\n\n.step:active:not(:disabled) {\n background: var(--ick-surface-active);\n}\n\n.step:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n/* At an end. Still drawn, so the control keeps its shape and the count does\n not slide sideways as you walk to the last one. */\n.step:disabled {\n cursor: default;\n color: var(--ick-branch-ink-off);\n}\n\n.count {\n display: inline-flex;\n align-items: center;\n gap: 0.15em;\n /* Tabular, so the count does not jog as the numbers change width. */\n font-family: var(--ick-font-mono);\n font-variant-numeric: tabular-nums;\n font-size: var(--ick-text-xs);\n color: var(--ick-branch-ink);\n user-select: none;\n}\n\n.of {\n color: var(--ick-branch-ink-off);\n}\n","\"use client\";\n\nimport type { HTMLAttributes } from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport styles from \"./Branch.module.css\";\n\ntype Labels = {\n previous: string;\n next: string;\n /** `{index}` and `{total}` are filled in. */\n position: string;\n};\n\nconst LABELS: Labels = {\n previous: \"Previous answer\",\n next: \"Next answer\",\n position: \"Answer {index} of {total}\",\n};\n\nexport interface BranchProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** How many answers this turn has had. */\n total: number;\n /** Which one is on screen, from zero. */\n index: number;\n onSelect: (index: number) => void;\n labels?: Partial<Labels>;\n}\n\n/**\n * Which answer you are looking at, and how to reach the others.\n *\n * Regenerating used to overwrite: the answer you were comparing against was\n * gone the moment the second one started. But the reason anybody presses\n * regenerate is to find out whether a second attempt is better, and there is\n * no better once the first has been thrown away.\n *\n * So it draws nothing at all for a turn with one answer. A control that says\n * \"1 of 1\" is a control offering to take you nowhere.\n */\nexport function Branch({ total, index, onSelect, labels, className, ...rest }: BranchProps) {\n const label = { ...LABELS, ...labels };\n if (total < 2) return null;\n\n const at = Math.min(Math.max(index, 0), total - 1);\n const position = label.position\n .replace(\"{index}\", String(at + 1))\n .replace(\"{total}\", String(total));\n\n return (\n <div\n className={[styles.branch, className ?? \"\"].filter(Boolean).join(\" \")}\n /* A group rather than a toolbar: two buttons and the count between them\n are one thing, and the count is the group's name. */\n role=\"group\"\n aria-label={position}\n {...rest}\n >\n <button\n type=\"button\"\n className={styles.step}\n onClick={() => onSelect(at - 1)}\n /* Inside an answer this sits on the highlighter's surface, where a\n pointerdown starts drawing a marker. The click is for the button. */\n onPointerDown={(event) => event.stopPropagation()}\n disabled={at === 0}\n aria-label={label.previous}\n >\n <ChevronLeft size={14} aria-hidden />\n </button>\n\n {/* Not `aria-live`: moving between answers already replaces the answer\n itself, which is what a reader wants read out — not the counter. */}\n <span className={styles.count}>\n {at + 1}\n <span className={styles.of}>/</span>\n {total}\n </span>\n\n <button\n type=\"button\"\n className={styles.step}\n onClick={() => onSelect(at + 1)}\n onPointerDown={(event) => event.stopPropagation()}\n disabled={at === total - 1}\n aria-label={label.next}\n >\n <ChevronRight size={14} aria-hidden />\n </button>\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n Loader\n ───────────────────────────────────────────── */\n\n.loader {\n display: inline-flex;\n align-items: center;\n gap: var(--ick-space-2);\n}\n\n/* ── Dots ───────────────────────────────────────────────────────────────── */\n\n.dot {\n width: var(--ick-loader-dot);\n height: var(--ick-loader-dot);\n border-radius: var(--ick-radius-pill);\n background: var(--ick-ink-faint);\n animation: blink 1.2s var(--ick-ease) infinite;\n}\n\n.dot:nth-child(2) { animation-delay: 0.15s; }\n.dot:nth-child(3) { animation-delay: 0.3s; }\n\n/* `.srOnly` is the first child when there is a label, which would push the\n delays onto the wrong dots. Keyed off the dots themselves instead. */\n.dot + .dot { animation-delay: 0.15s; }\n.dot + .dot + .dot { animation-delay: 0.3s; }\n\n@keyframes blink {\n 0%, 100% { opacity: 0.25; }\n 50% { opacity: 1; }\n}\n\n/* ── Shimmer ────────────────────────────────────────────────────────────────\n A gradient the width of the text, slid across it and clipped to the glyphs.\n It reads as \"these words are provisional\" without anything moving. */\n\n.text {\n font-size: var(--ick-text-md);\n color: var(--ick-ink-faint);\n background: linear-gradient(\n 90deg,\n var(--ick-ink-faint) 0%,\n var(--ick-ink-faint) 40%,\n var(--ick-ink) 50%,\n var(--ick-ink-faint) 60%,\n var(--ick-ink-faint) 100%\n );\n background-size: 200% 100%;\n -webkit-background-clip: text;\n background-clip: text;\n -webkit-text-fill-color: transparent;\n animation: sweep 1.8s linear infinite;\n}\n\n/* See the note in `Reasoning.module.css`: the same numbers, and the same fix.\n The bright point used to spend two thirds of every cycle off the words. */\n@keyframes sweep {\n from { background-position: 110% 0; }\n to { background-position: -10% 0; }\n}\n\n/* ── Reduced motion ─────────────────────────────────────────────────────────\n Both stop, and both stay legible standing still. A loader that vanishes\n when the animation does has been carrying its meaning in the movement. */\n\n@media (prefers-reduced-motion: reduce) {\n .dot {\n animation: none;\n opacity: 0.6;\n }\n\n .text {\n animation: none;\n background: none;\n -webkit-text-fill-color: currentColor;\n color: var(--ick-ink-faint);\n }\n}\n\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n","\"use client\";\n\nimport type { HTMLAttributes } from \"react\";\nimport styles from \"./Loader.module.css\";\n\nexport type LoaderVariant = \"dots\" | \"shimmer\";\n\nexport interface LoaderProps extends HTMLAttributes<HTMLDivElement> {\n variant?: LoaderVariant;\n /** The words the shimmer runs through. Ignored by `dots`. */\n children?: React.ReactNode;\n /**\n * What a screen reader is told, if anything.\n *\n * `null` by default, and that is deliberate: `useChatTurns` already\n * announces that a response is coming, and a second live region saying the\n * same thing means hearing it twice. Pass a string only when nothing else\n * is speaking for you.\n */\n label?: string | null;\n}\n\n/**\n * The gap between sending and the first word arriving.\n *\n * `dots` for that gap, when there is nothing to show yet. `shimmer` for words\n * that are standing in for something — a status line that has not settled.\n */\nexport function Loader({\n variant = \"dots\",\n children,\n label = null,\n className,\n ...rest\n}: LoaderProps) {\n const classes = [styles.loader, styles[variant], className ?? \"\"].filter(Boolean).join(\" \");\n\n return (\n <div\n className={classes}\n // Decorative unless it is given something to say. See `label`.\n role={label ? \"status\" : undefined}\n aria-hidden={label ? undefined : true}\n {...rest}\n >\n {label && <span className={styles.srOnly}>{label}</span>}\n {variant === \"dots\" ? (\n <>\n <span className={styles.dot} />\n <span className={styles.dot} />\n <span className={styles.dot} />\n </>\n ) : (\n <span className={styles.text}>{children}</span>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback, useState } from \"react\";\n\nexport interface DisclosureOptions {\n /** Controlled. Leave it out and the row keeps its own. */\n open?: boolean;\n /** Where it starts, when the row's own preference is not what you want. */\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /**\n * What the row would do if nobody had an opinion.\n *\n * Read every render, so it follows the state: a tool call that fails later\n * opens itself, a reasoning block folds away when the answer starts.\n */\n preferOpen: boolean;\n}\n\n/**\n * Open or shut, decided by three sources in order of who gets the last word:\n * the host if it is controlling the row, then whoever clicked it, then the\n * row's own preference.\n *\n * That order is the whole point, and it is why this is not an effect. An\n * effect forcing the row open on a state change would also reopen one that\n * somebody had deliberately shut — showing a reader again what they have\n * already dismissed is not help. Derived, a reader's decision simply outlives\n * every state change after it, and a row nobody has touched still follows\n * along.\n */\nexport function useDisclosure({ open, defaultOpen, onOpenChange, preferOpen }: DisclosureOptions) {\n const [toggled, setToggled] = useState<boolean | null>(null);\n const isOpen = open ?? toggled ?? defaultOpen ?? preferOpen;\n\n const toggle = useCallback(() => {\n const next = !isOpen;\n // Controlled: report, and wait to be told. Setting our own would make the\n // row move before the host agreed to it.\n if (open === undefined) setToggled(next);\n onOpenChange?.(next);\n }, [isOpen, onOpenChange, open]);\n\n return { isOpen, toggle };\n}\n","/* ─────────────────────────────────────────────\n DisclosureHeader\n\n The row you click to open something, written once. Five components had one\n — `Tool`, `Reasoning`, `ChainOfThought`, `TaskList`, `Sources` — and the CSS\n was written out five times. Two pairs of them were byte-for-byte identical.\n\n What is **not** unified is the shape, because the difference is real. A\n `band` is a full-width row: it has a right edge, so meta and the chevron are\n pushed to it. An `inline` header is a label that hugs its own words — it\n sits in the flow of an answer as an aside, and pushing its chevron to a\n right edge 500px away would leave it floating alone in white space. One\n attribute, two layouts, and everything else shared.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.header {\n all: unset;\n composes: target from \"../styles/touch.module.css\";\n /* A positioned box for it to centre on. */\n position: relative;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--ick-space-4);\n /* The box is the container's content column widened by its own padding on\n both sides, so the label lands exactly on the left edge and the chevron\n on the right edge, with only the hover wash reaching past them. It used\n to be `width: 100%` with a pull-back on the left alone, which left the\n chevron six pixels shy of an edge every other row in the kit sits on.\n\n `--ick-disclosure-inset` then moves both ends inward together — for a\n section title standing over cards, whose words start a card's padding in\n from the cards' edges. The widening is the same either way: the inset\n eats into the content column, not into the box. */\n width: calc(100% + var(--ick-space-3) * 2);\n margin-inline: calc(var(--ick-space-3) * -1);\n padding: var(--ick-space-2) calc(var(--ick-space-3) + var(--ick-disclosure-inset));\n /* Stated rather than inherited. The row sits inside the prose, so without\n this it takes the prose's size — which on a phone is 16px, and the icons\n beside the label are then sized against a number the row does not use. */\n font-size: var(--ick-disclosure-size);\n border-radius: var(--ick-radius-sm);\n font-family: var(--ick-font-sans);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition: background var(--ick-duration-fast) ease;\n}\n\n/* A label, not a band: it takes the width of its own words. */\n.header[data-fit=\"inline\"] {\n display: inline-flex;\n gap: var(--ick-space-3);\n width: auto;\n max-width: 100%;\n /* It hugs its words, so there is no right edge to reach — and a negative\n margin there would pull whatever follows in over it. */\n margin-right: 0;\n}\n\n\n.header:hover {\n background: var(--ick-disclosure-hover);\n}\n\n/* The response when there is no wash to give one — and an improvement where\n there is, since the chevron is the part that says what the row does. */\n.header:hover .chevron {\n color: var(--ick-ink);\n}\n\n.header:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n/* Nothing to fold: a heading, not a control. */\n.static,\n.static:hover {\n cursor: default;\n background: none;\n}\n\n.static:hover .chevron,\n.header:disabled:hover .chevron {\n color: var(--ick-disclosure-meta);\n}\n\n/* Nothing to open yet — no input, no output, nothing to say. It is still a row\n worth reading, so it stays put and stops being a control. */\n.header:disabled {\n cursor: default;\n}\n\n.header:disabled:hover {\n background: none;\n}\n\n/* Icons from the text they sit beside. `auto` on a pointer is the SVG's own\n width and height attributes, so this changes nothing there. */\n.header svg {\n width: var(--ick-icon);\n height: var(--ick-icon);\n}\n\n.glyph {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n color: var(--ick-disclosure-glyph);\n}\n\n\n/* Set from tokens rather than overridden by a class the caller passes in:\n two `.label`s from two stylesheets have the same specificity, so which one\n wins is import order — which is not a thing to build on. A component that\n means something different by its label repoints the token from its own\n root, the way `Tool` already repoints the code block's fill. */\n.label {\n font-family: var(--ick-disclosure-font);\n font-size: var(--ick-disclosure-size);\n font-weight: var(--ick-disclosure-weight);\n color: var(--ick-disclosure-label);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* A gradient the width of the word, slid across it and clipped to the glyphs —\n the kit's way of saying \"provisional\". Its own element rather than a\n `<Loader variant=\"shimmer\">`: the loader is decorative and marks itself\n `aria-hidden`, and this word is the button's name. Hiding it would leave a\n control with nothing to call it.\n\n No counter either. A number ticking up while somebody waits is a stopwatch\n pointed at them. */\n.label[data-pending] {\n background: linear-gradient(\n 90deg,\n var(--ick-disclosure-label) 0%,\n var(--ick-disclosure-label) 40%,\n var(--ick-ink) 50%,\n var(--ick-disclosure-label) 60%,\n var(--ick-disclosure-label) 100%\n );\n background-size: 200% 100%;\n -webkit-background-clip: text;\n background-clip: text;\n -webkit-text-fill-color: transparent;\n animation: sweep 1.8s linear infinite;\n}\n\n/* The geometry, because the numbers are not obvious and the first version got\n them wrong.\n\n With `background-size: 200%` the image is twice the element, so the bright\n stop at 50% of the image sits one element-width in from its left edge. At\n `background-position: 100%` that lands on the element's left edge and at\n `0%` on its right — so the highlight crosses the whole word and nothing\n else. The overshoot either side keeps it from arriving and leaving on the\n letters themselves.\n\n It was 250% swept from 150% to -50%, which sends the bright point from one\n width left of the word to two widths right of it: on the text for a third of\n the cycle and off it for the rest. Measured over one period, frame by frame\n — two of every three frames were a static grey word, which is the opposite\n of what a shimmer is for. */\n@keyframes sweep {\n from { background-position: 110% 0; }\n to { background-position: -10% 0; }\n}\n\n/* Standing still it is still the word \"Thinking\", which was always the part\n carrying the meaning. */\n@media (prefers-reduced-motion: reduce) {\n .label[data-pending] {\n animation: none;\n background: none;\n -webkit-text-fill-color: currentColor;\n }\n}\n\n/* Hard against the right end of a band, whatever else the row carries. In an\n inline header there is no right end to be against, so it simply follows. */\n.meta {\n flex-shrink: 0;\n margin-left: auto;\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n color: var(--ick-disclosure-meta);\n}\n\n.chevron {\n flex-shrink: 0;\n margin-left: auto;\n color: var(--ick-disclosure-meta);\n transition: transform var(--ick-duration-fast) var(--ick-ease);\n}\n\n/* Something ahead of it already did the pushing; a second `auto` would split\n the free space between them instead of closing the pair up. */\n.meta ~ .chevron {\n margin-left: 0;\n}\n\n.header[data-fit=\"inline\"] .meta,\n.header[data-fit=\"inline\"] .chevron {\n margin-left: 0;\n}\n\n.header[aria-expanded=\"true\"] .chevron {\n transform: rotate(180deg);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .chevron {\n transition: none;\n }\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { ChevronDown } from \"lucide-react\";\nimport styles from \"./DisclosureHeader.module.css\";\n\nexport interface DisclosureHeaderProps {\n /**\n * `band` is a full-width row — it has a right edge, so the meta and the\n * chevron are pushed to it. `inline` is a label that hugs its own words,\n * for a header that sits in the flow of an answer as an aside.\n *\n * The two are not a style choice. An inline header's chevron pushed to a\n * right edge 500px away floats alone in white space with nothing beside it.\n */\n fit?: \"band\" | \"inline\";\n open: boolean;\n /** Leave it out and this is a heading rather than a control — no chevron,\n no `aria-expanded`, nothing to press. */\n onToggle?: () => void;\n /** The id of the box it opens. Always present, so this always points at\n something even while the box is empty. */\n controls: string;\n /** A control with nothing to open yet. Still a row worth reading. */\n disabled?: boolean;\n glyph?: ReactNode;\n /** The control's name. */\n label: ReactNode;\n /** Shimmers the label: the kit's way of saying \"still happening\". */\n pending?: boolean;\n /** A duration, a count — pushed to the right end of a band. */\n meta?: ReactNode;\n /** Anything between the label and the meta. */\n children?: ReactNode;\n className?: string;\n}\n\nconst join = (...names: (string | undefined | false)[]) => names.filter(Boolean).join(\" \");\n\n/**\n * The row you click to open something.\n *\n * Five components had one of these and the markup was written out five times,\n * with two pairs byte-for-byte identical and the others differing in ways\n * nobody had decided on. The shimmer under the label was in two of them, with\n * the same twenty lines of gradient in each.\n *\n * What it does not do is force one shape on all five — see `fit`.\n */\nexport function DisclosureHeader({\n fit = \"band\",\n open,\n onToggle,\n controls,\n disabled,\n glyph,\n label,\n pending,\n meta,\n children,\n className,\n}: DisclosureHeaderProps) {\n const inside = (\n <>\n {glyph && <span className={styles.glyph}>{glyph}</span>}\n <span className={styles.label} data-pending={pending || undefined}>\n {label}\n </span>\n {children}\n {meta ? <span className={styles.meta}>{meta}</span> : null}\n {onToggle && <ChevronDown className={styles.chevron} size={14} aria-hidden />}\n </>\n );\n\n const shared = {\n className: join(styles.header, className),\n \"data-fit\": fit,\n };\n\n if (!onToggle) {\n return (\n <div {...shared} className={join(shared.className, styles.static)}>\n {inside}\n </div>\n );\n }\n\n return (\n <button\n type=\"button\"\n {...shared}\n onClick={onToggle}\n /* Inside an answer this sits on the highlighter's surface, where a\n pointerdown starts drawing a marker. The click is for the row. */\n onPointerDown={(event) => event.stopPropagation()}\n aria-expanded={open}\n aria-controls={controls}\n disabled={disabled}\n >\n {inside}\n </button>\n );\n}\n","/* ─────────────────────────────────────────────\n DisclosureBody\n ───────────────────────────────────────────── */\n\n/* Always present, so the header's `aria-controls` always points at something.\n Empty and flat when the row is shut. */\n.outer {\n box-sizing: border-box;\n width: 100%;\n}\n\n.wrap {\n box-sizing: border-box;\n width: 100%;\n overflow: hidden;\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport styles from \"./DisclosureBody.module.css\";\n\nexport interface DisclosureBodyProps {\n /** What the header's `aria-controls` points at. */\n id: string;\n open: boolean;\n /** On the animated box, so a caller can pad or lay out what is revealed. */\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * What a `DisclosureHeader` opens, and how it opens.\n *\n * The outer box is **always rendered**, empty and flat when shut, so the\n * header's `aria-controls` always points at something — a control that names\n * an id nothing has is a control that says nothing.\n *\n * The reveal was ten lines of identical `motion` props in five components. The\n * height animation is what makes growing read as a reveal rather than as\n * content arriving before its container has caught up, and `overflow: hidden`\n * on the moving box is what keeps it from spilling on the way.\n */\nexport function DisclosureBody({ id, open, className, children }: DisclosureBodyProps) {\n const still = prefersReducedMotion();\n\n return (\n <div id={id} className={styles.outer}>\n <AnimatePresence initial={false}>\n {open && children ? (\n <motion.div\n key=\"body\"\n className={styles.wrap}\n initial={still ? { opacity: 0 } : { height: 0, opacity: 0 }}\n animate={still ? { opacity: 1 } : { height: \"auto\", opacity: 1 }}\n exit={still ? { opacity: 0 } : { height: 0, opacity: 0 }}\n transition={{ duration: still ? 0.12 : 0.22, ease: [0.32, 0.72, 0, 1] }}\n >\n <div className={className}>{children}</div>\n </motion.div>\n ) : null}\n </AnimatePresence>\n </div>\n );\n}\n","/**\n * How long something took, in words somebody reads rather than in the number\n * the clock handed over.\n *\n * `840ms` under a second, `1.2s` over it, whole seconds past ten. Nobody reads\n * `1173ms`, and the tenth stops mattering once there are two digits in front\n * of it.\n */\nexport function formatDuration(ms: number): string {\n if (!Number.isFinite(ms) || ms < 0) return \"\";\n if (ms < 1000) return `${Math.round(ms)}ms`;\n const seconds = ms / 1000;\n return `${seconds < 10 ? seconds.toFixed(1) : Math.round(seconds)}s`;\n}\n","/* ─────────────────────────────────────────────\n Reasoning\n\n Prose, not a panel — which is the difference between this and `Tool`. A tool\n call is a record of something that ran and it gets a box; thinking is the\n model talking to itself on the way to the answer, and boxing it gives it a\n weight it has not earned. A rule down the side is enough to say \"this is an\n aside\", and it stays out of the way of the answer underneath.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.reasoning {\n box-sizing: border-box;\n width: 100%;\n /* It carries no surface of its own — no ground, no card — so it sits in the\n answer's flow, and everything in that flow starts on the answer's column.\n See `--ick-answer-column`. */\n padding-left: var(--ick-answer-column);\n font-family: var(--ick-font-sans);\n}\n\n.bodyInner {\n box-sizing: border-box;\n width: 100%;\n margin-top: var(--ick-space-3);\n /* The rule is the whole container. An aside, marked as one. */\n padding-left: var(--ick-reasoning-indent);\n border-left: 1px solid var(--ick-reasoning-rule);\n font-size: var(--ick-reasoning-size);\n line-height: var(--ick-reasoning-leading);\n color: var(--ick-reasoning-ink);\n /* Thinking arrives with its own line breaks and they carry the shape of it. */\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n/* Paragraphs a host passes in, rather than a string. */\n.bodyInner > :first-child {\n margin-top: 0;\n}\n\n.bodyInner > :last-child {\n margin-bottom: 0;\n}\n","\"use client\";\n\nimport { useId, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { Brain } from \"lucide-react\";\nimport { useDisclosure } from \"../disclosure/useDisclosure\";\nimport { DisclosureHeader } from \"../disclosure/DisclosureHeader\";\nimport { DisclosureBody } from \"../disclosure/DisclosureBody\";\nimport { formatDuration } from \"../duration/formatDuration\";\nimport styles from \"./Reasoning.module.css\";\n\n/** Still working it out, or finished. */\nexport type ReasoningState = \"thinking\" | \"done\";\n\ntype Labels = Record<\"thinking\" | \"thought\" | \"thoughtFor\", string>;\n\nconst LABELS: Labels = {\n thinking: \"Thinking\",\n thought: \"Thought\",\n /** Followed by the duration: \"Thought for 12s\". */\n thoughtFor: \"Thought for\",\n};\n\nexport interface ReasoningProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** The thinking itself. Prose, not structure — see `Tool` for that. */\n children: ReactNode;\n state?: ReasoningState;\n /**\n * How long it took, in ms.\n *\n * Left out, the block times itself: it starts a clock when it begins\n * thinking and reads it when it stops. Pass one when you already know —\n * replaying a transcript, where the thinking did not happen just now.\n */\n duration?: number;\n /** Controlled. Leave it out and the block looks after itself. */\n open?: boolean;\n /** Where it starts, overruling the block's own preference. */\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n labels?: Partial<Labels>;\n}\n\n/**\n * What the model worked through before it answered.\n *\n * **Open while it thinks, folded away once the answer starts.** That is the\n * one detail every kit that ships this has converged on, and it is right:\n * thinking is worth watching while it is happening and worth almost nothing\n * afterwards — but it has to stay reachable, because the times it matters are\n * exactly the times the answer looks wrong.\n *\n * Folding is the block's *preference*, not something done to the reader. Open\n * it and it stays open, however many times the state changes underneath.\n */\nexport function Reasoning({\n children,\n state = \"done\",\n duration,\n open,\n defaultOpen,\n onOpenChange,\n labels,\n className,\n ...rest\n}: ReasoningProps) {\n const label = { ...LABELS, ...labels };\n const bodyId = useId();\n const thinking = state === \"thinking\";\n\n const { isOpen, toggle } = useDisclosure({\n open,\n defaultOpen,\n onOpenChange,\n preferOpen: thinking,\n });\n\n /* Timed here rather than asked of the host, which would only be reading the\n same clock. Adjusted during render on a change of state rather than in an\n effect: an effect would mean a second pass every time the thinking stops,\n and React documents this as the way to derive state from a prop that\n changed. `was` is what makes it run once per change rather than once per\n render. */\n const [was, setWas] = useState(thinking);\n const [clock, setClock] = useState<{ from: number | null; took: number | null }>(() => ({\n from: thinking ? Date.now() : null,\n took: null,\n }));\n\n if (was !== thinking) {\n setWas(thinking);\n setClock((c) =>\n thinking\n ? { from: Date.now(), took: null }\n : { from: null, took: c.from === null ? null : Date.now() - c.from }\n );\n }\n\n const took = duration ?? clock.took;\n const time = thinking || took === null ? \"\" : formatDuration(took);\n\n return (\n <div\n className={[styles.reasoning, className ?? \"\"].filter(Boolean).join(\" \")}\n data-state={state}\n {...rest}\n >\n {/* An aside in the flow of an answer, so the header takes the width of\n its own words rather than a band's. See `DisclosureHeader`'s `fit`. */}\n <DisclosureHeader\n fit=\"inline\"\n open={isOpen}\n onToggle={toggle}\n controls={bodyId}\n glyph={<Brain size={14} aria-hidden />}\n label={thinking ? label.thinking : time ? `${label.thoughtFor} ${time}` : label.thought}\n pending={thinking}\n />\n\n <DisclosureBody id={bodyId} open={isOpen} className={styles.bodyInner}>\n {children}\n </DisclosureBody>\n </div>\n );\n}\n","/**\n * Syntax highlighting, loaded only once something needs it.\n *\n * `lowlight` and eleven grammars are **22.4 kB gzipped** — more than a third\n * of this package, for a thing most conversations never show. Imported at the\n * top of the module they were in everybody's bundle whether or not an answer\n * ever contained a fence.\n *\n * So the grammars sit behind a dynamic `import()` and the module itself keeps\n * only the names. A block renders its code unhighlighted on the first paint\n * and colours in when the chunk lands; every block after that is highlighted\n * from the first paint, because the loaded highlighter is kept.\n *\n * What is *not* deferred is knowing whether a language can be highlighted at\n * all — `canHighlight` answers off the list below, so a caller can decide what\n * to draw without pulling 22 kB to find out.\n */\n\n/**\n * Eleven languages, chosen and measured rather than taken wholesale.\n *\n * `lowlight/common` is 37 languages and 51.6 kB gzipped — nearly the size of\n * everything else in this package put together, for grammars a chat will\n * almost never show. These cost a quarter of that and cover what actually\n * turns up in an answer. A language outside the list renders unhighlighted\n * rather than throwing; nobody loses their code over a missing grammar.\n *\n * Written out rather than read off the registry, because reading the registry\n * means loading it. A test holds this list against what `load()` registers.\n */\nconst LANGUAGES = [\n \"bash\",\n \"css\",\n \"diff\",\n \"javascript\",\n \"json\",\n \"markdown\",\n \"python\",\n \"sql\",\n \"typescript\",\n \"xml\",\n \"yaml\",\n] as const;\n\n/** What the fences people actually write map to. */\nconst ALIASES: Record<string, string> = {\n js: \"javascript\",\n jsx: \"javascript\",\n mjs: \"javascript\",\n cjs: \"javascript\",\n ts: \"typescript\",\n tsx: \"typescript\",\n html: \"xml\",\n svg: \"xml\",\n vue: \"xml\",\n sh: \"bash\",\n shell: \"bash\",\n zsh: \"bash\",\n console: \"bash\",\n py: \"python\",\n md: \"markdown\",\n postgres: \"sql\",\n psql: \"sql\",\n patch: \"diff\",\n yml: \"yaml\",\n yamllint: \"yaml\",\n};\n\nconst KNOWN = new Set<string>(LANGUAGES);\n\n/** A run of code with one highlight class, or none. */\nexport interface CodeToken {\n value: string;\n /** The `hljs-` class, without the prefix. `null` for plain text. */\n kind: string | null;\n}\n\n/** The whole block, one run, no colour. What a block shows before the chunk\n arrives and what it keeps for a language nothing here can read. */\nexport const plain = (code: string): CodeToken[] => [{ value: code, kind: null }];\n\nconst resolve = (lang?: string): string | null => {\n if (!lang) return null;\n const name = ALIASES[lang.toLowerCase()] ?? lang.toLowerCase();\n return KNOWN.has(name) ? name : null;\n};\n\n/** Whether the fence's language is one we can actually colour. */\nexport const canHighlight = (lang?: string): boolean => resolve(lang) !== null;\n\nexport type Highlighter = (code: string, lang?: string) => CodeToken[];\n\n/**\n * Flatten highlight.js's tree into a list of runs.\n *\n * The tree nests — a string inside a template inside a function — and only the\n * innermost class is worth drawing, so the walk carries the nearest one down\n * and emits a flat list. Flat is also what renders without a recursive\n * component, and a code block is the one place in an answer where the DOM is\n * worth keeping shallow: a long file is thousands of nodes either way.\n */\ntype Node = {\n type: string;\n value?: string;\n children?: Node[];\n properties?: { className?: string[] };\n};\n\nfunction flatten(tree: Node): CodeToken[] {\n const tokens: CodeToken[] = [];\n const push = (value: string, kind: string | null) => {\n if (!value) return;\n const last = tokens[tokens.length - 1];\n // Runs of the same kind are joined, which is most of them.\n if (last && last.kind === kind) last.value += value;\n else tokens.push({ value, kind });\n };\n\n const walk = (nodes: Node[], inherited: string | null) => {\n for (const node of nodes) {\n if (node.type === \"text\") {\n push(node.value ?? \"\", inherited);\n continue;\n }\n const own = node.properties?.className?.find((c) => c.startsWith(\"hljs-\"));\n walk(node.children ?? [], own ? own.slice(5) : inherited);\n }\n };\n\n walk(tree.children ?? [], null);\n return tokens;\n}\n\n/** Set once the chunk has landed, so every block after the first is coloured\n on its first paint rather than flashing plain and correcting itself. */\nlet ready: Highlighter | null = null;\nlet loading: Promise<Highlighter> | null = null;\n\n/** The highlighter, if some block has already paid for it. */\nexport const loaded = (): Highlighter | null => ready;\n\n/**\n * Fetch the grammars, once.\n *\n * Concurrent callers share the one promise: three code blocks in an answer\n * arrive together, and three requests for the same chunk is two too many.\n */\nexport function loadHighlighter(): Promise<Highlighter> {\n if (ready) return Promise.resolve(ready);\n if (loading) return loading;\n\n loading = (async () => {\n /* One module, not thirteen `import()`s: `lowlight`'s own entry re-exports\n `all` and `common` beside `createLowlight`, so importing the package\n dynamically pulls 190 grammars that nothing can shake back out. The\n static imports live in `./grammars`, which is the chunk. */\n const { createHighlighter } = await import(\"./grammars\");\n const lowlight = createHighlighter();\n\n ready = (code, lang) => {\n const name = resolve(lang);\n if (!name) return plain(code);\n try {\n const tokens = flatten(lowlight.highlight(name, code) as unknown as Node);\n return tokens.length > 0 ? tokens : plain(code);\n } catch {\n // A grammar can throw on input it cannot make sense of. Unhighlighted\n // code is a small loss; a chat that crashes on one answer is not.\n return plain(code);\n }\n };\n return ready;\n })();\n\n return loading;\n}\n","/* ─────────────────────────────────────────────\n CodeBlock\n\n Restrained on purpose. This kit is ink, paper and one acid yellow, and a\n twelve-colour syntax theme dropped into it reads as somebody else's\n component. So the scheme is ink at four weights: comments recede, keywords\n carry weight, strings and numbers sit between.\n\n It is all tokens, though, and that is the point — six values turn it into\n whatever palette a brand already owns.\n ───────────────────────────────────────────── */\n\n.block {\n position: relative;\n /* A token rather than the scale directly: a block nested inside another box\n has to take a smaller corner than one standing on its own, or the two\n curves sit at different insets with the same radius — which is the thing\n that reads as not fitting. `Tool` repoints this, the way it already\n repoints the fill. */\n border-radius: var(--ick-code-radius);\n background: var(--ick-md-code-fill);\n overflow: hidden;\n font-family: var(--ick-font-mono);\n}\n\n/* ── The bar ────────────────────────────────────────────────────────────── */\n\n.bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--ick-space-4);\n padding: var(--ick-space-3) var(--ick-space-3) 0 var(--ick-code-pad);\n /* A label over code, not a header with a rule under it. See the note in\n `Tool.module.css`: this kit separates by surface and gap. */\n min-height: 26px;\n box-sizing: border-box;\n}\n\n.lang {\n font-size: var(--ick-text-xs);\n letter-spacing: 0.04em;\n text-transform: lowercase;\n color: var(--ick-ink-faint);\n}\n\n.copy {\n all: unset;\n composes: target from \"../styles/touch.module.css\";\n /* A positioned box for it to centre on. */\n position: relative;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n gap: var(--ick-space-2);\n padding: 0 var(--ick-space-3);\n height: 22px;\n border-radius: var(--ick-radius-sm);\n cursor: pointer;\n flex-shrink: 0;\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n color: var(--ick-ink-faint);\n -webkit-tap-highlight-color: transparent;\n transition:\n background var(--ick-duration-fast) ease,\n color var(--ick-duration-fast) ease;\n}\n\n.copy:hover {\n background: var(--ick-surface-hover);\n color: var(--ick-ink);\n}\n\n.copy:active {\n background: var(--ick-surface-press);\n}\n\n.copy:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n/* The word is what makes the glyph mean something the first time. It goes\n when there is no room, and the button keeps its `aria-label` either way. */\n@media (max-width: 420px) {\n .copyLabel {\n display: none;\n }\n}\n\n/* ── The code ───────────────────────────────────────────────────────────── */\n\n.pre {\n margin: 0;\n padding: var(--ick-code-pad);\n overflow-x: auto;\n /* A long line scrolls inside the block rather than widening the answer. */\n white-space: pre;\n font-size: var(--ick-code-size);\n line-height: var(--ick-code-leading);\n color: var(--ick-ink);\n tab-size: 2;\n\n /* Stated here rather than inherited from `.block`.\n\n An inherited value loses to any direct declaration, and `pre` and `code`\n are two of the elements a host app or docs tool will style without\n thinking about it — Storybook puts a Fira Code stack on both. Declared on\n the parent alone, the kit's mono font would simply not arrive. Verified\n the other way round too: with `pre { font-family: … }` on the page, the\n block keeps its own.\n\n Ligatures off because `--ick-font-mono` is a token, and a brand is free\n to set Fira Code or JetBrains Mono, both of which draw `--` as one long\n dash and `!==` as a single glyph. What gets copied is unaffected, which\n makes it worse rather than better: the block would show one thing and\n hand over another. Code is the one place a character has to be the\n character. (Geist Mono, the default, has no such ligatures — this is for\n whoever changes it.) */\n font-family: var(--ick-font-mono);\n font-variant-ligatures: none;\n font-feature-settings: \"liga\" 0, \"calt\" 0;\n}\n\n.pre code {\n /* Same again: `code` is styled by hosts as reliably as `pre` is. */\n font-family: var(--ick-font-mono);\n font-variant-ligatures: none;\n font-feature-settings: \"liga\" 0, \"calt\" 0;\n font-size: inherit;\n background: none;\n padding: 0;\n}\n\n/* ── The scheme ─────────────────────────────────────────────────────────────\n `data-hl` carries highlight.js's class without its prefix. Grouped by what\n a reader is actually looking for, not by what the grammar calls it. */\n\n[data-hl=\"comment\"],\n[data-hl=\"quote\"] {\n color: var(--ick-code-comment);\n font-style: italic;\n}\n\n[data-hl=\"keyword\"],\n[data-hl=\"built_in\"],\n[data-hl=\"literal\"],\n[data-hl=\"type\"],\n[data-hl=\"meta\"],\n[data-hl=\"selector-tag\"] {\n color: var(--ick-code-keyword);\n font-weight: var(--ick-weight-medium);\n}\n\n[data-hl=\"string\"],\n[data-hl=\"regexp\"],\n[data-hl=\"symbol\"],\n[data-hl=\"char\"] {\n color: var(--ick-code-string);\n}\n\n[data-hl=\"number\"],\n[data-hl=\"bullet\"] {\n color: var(--ick-code-number);\n}\n\n[data-hl=\"title\"],\n[data-hl=\"name\"],\n[data-hl=\"section\"],\n[data-hl=\"function\"],\n[data-hl=\"class\"] {\n color: var(--ick-code-name);\n}\n\n[data-hl=\"attr\"],\n[data-hl=\"attribute\"],\n[data-hl=\"property\"],\n[data-hl=\"variable\"],\n[data-hl=\"params\"],\n[data-hl=\"tag\"] {\n color: var(--ick-code-attr);\n}\n\n/* A diff is the one place a whole line means something. Bands rather than\n text colour, so the `+` and `-` still read as the characters they are. */\n[data-hl=\"addition\"] {\n display: inline-block;\n width: 100%;\n background: var(--ick-code-addition);\n}\n\n[data-hl=\"deletion\"] {\n display: inline-block;\n width: 100%;\n background: var(--ick-code-deletion);\n color: var(--ick-ink-soft);\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState, type HTMLAttributes } from \"react\";\nimport { Check, Copy } from \"lucide-react\";\nimport { announce } from \"../announce/announce\";\nimport {\n canHighlight,\n loadHighlighter,\n loaded,\n plain,\n type Highlighter,\n} from \"./highlight\";\nimport styles from \"./CodeBlock.module.css\";\n\nexport interface CodeBlockProps\n // `onCopy` is a DOM clipboard handler as well as a prop here, and the two\n // signatures do not agree. Ours wins: a copy button's callback should hand\n // over the code, not a ClipboardEvent nobody asked for.\n extends Omit<HTMLAttributes<HTMLDivElement>, \"children\" | \"onCopy\"> {\n code: string;\n /** The fence's language. Unknown ones render unhighlighted. */\n lang?: string;\n /** Shown top-left. Defaults to the language; pass `false` for no bar at all. */\n label?: string | false;\n /** Set false for a block nobody is meant to take away. */\n copyable?: boolean;\n /** Defaults to the clipboard. */\n onCopy?: (code: string) => void;\n /** How long the button stays confirmed, in ms. */\n copiedFor?: number;\n}\n\nconst COPIED_FOR = 1600;\n\n/**\n * A fenced block: language, a copy button, and code that scrolls sideways\n * rather than widening the answer.\n *\n * Deliberately not markable. The rest of an answer is split into word tokens\n * so a marker can be drawn over it; preformatted text split on whitespace\n * stops being preformatted, so this renders whole and the highlighter skips\n * it. Copy is what people want from code anyway.\n */\nexport function CodeBlock({\n code,\n lang,\n label,\n copyable = true,\n onCopy,\n copiedFor = COPIED_FOR,\n className,\n ...rest\n}: CodeBlockProps) {\n const [copied, setCopied] = useState(false);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n /* The grammars are a dynamic import — 22.4 kB that most conversations never\n need — so the first block on a page paints plain and colours in when the\n chunk lands. `loaded()` is what stops that from being a flash on every\n block after it: once something has paid, the highlighter is there on the\n first render and the initial state is already right. */\n const [highlighter, setHighlighter] = useState<Highlighter | null>(loaded);\n\n useEffect(() => {\n if (highlighter || !canHighlight(lang)) return;\n let alive = true;\n void loadHighlighter().then((h) => {\n // The block can be gone by then — in a chat they unmount all the time.\n if (alive) setHighlighter(() => h);\n });\n return () => {\n alive = false;\n };\n }, [highlighter, lang]);\n\n const tokens = useMemo(\n () => (highlighter ? highlighter(code, lang) : plain(code)),\n [highlighter, code, lang]\n );\n\n // A block that unmounts while confirmed would otherwise set state on a gone\n // component, and in a chat they unmount all the time.\n useEffect(() => () => { if (timer.current) clearTimeout(timer.current); }, []);\n\n const copy = useCallback(() => {\n if (onCopy) onCopy(code);\n else void navigator.clipboard?.writeText(code);\n\n setCopied(true);\n // The tick is a picture. Without this a screen reader is told nothing\n // happened at all, which is the same as the copy having failed.\n announce(\"Copied to clipboard\");\n if (timer.current) clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopied(false), copiedFor);\n }, [code, copiedFor, onCopy]);\n\n const caption = label === false ? null : (label ?? lang ?? null);\n const showBar = caption !== null || copyable;\n\n return (\n <div className={[styles.block, className ?? \"\"].filter(Boolean).join(\" \")} {...rest}>\n {showBar && (\n <div className={styles.bar}>\n <span className={styles.lang}>{caption}</span>\n {copyable && (\n <button\n type=\"button\"\n className={styles.copy}\n onClick={copy}\n /* Inside an answer this sits on the highlighter's surface, where\n a pointerdown starts drawing a marker. The click is for the\n button, not the paragraph under it. */\n onPointerDown={(event) => event.stopPropagation()}\n aria-label={copied ? \"Copied\" : \"Copy code\"}\n >\n {copied ? <Check size={13} aria-hidden /> : <Copy size={13} aria-hidden />}\n <span className={styles.copyLabel}>{copied ? \"Copied\" : \"Copy\"}</span>\n </button>\n )}\n </div>\n )}\n\n <pre className={styles.pre}>\n <code>\n {tokens.map((token, i) =>\n token.kind ? (\n <span key={i} data-hl={token.kind}>\n {token.value}\n </span>\n ) : (\n token.value\n )\n )}\n </code>\n </pre>\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n StateGlyph\n\n Shared by `Tool` and `TaskList`, which is the point: it lived in one of them\n first, and a second copy is how two drawings of the same thing start to\n disagree. The shimmer in `Loader` and `Reasoning` had already done exactly\n that once.\n ───────────────────────────────────────────── */\n\n.glyph {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--ick-state-size);\n height: var(--ick-state-size);\n color: var(--ick-state-pending);\n}\n\n.glyph[data-state=\"running\"] {\n color: var(--ick-state-running);\n}\n\n.glyph[data-state=\"done\"] {\n color: var(--ick-state-done);\n}\n\n.glyph[data-state=\"error\"] {\n color: var(--ick-state-error);\n}\n\n/* Queued: an outline, because nothing has happened to fill it in yet. */\n.queued {\n width: 9px;\n height: 9px;\n border: 1.5px solid currentColor;\n border-radius: var(--ick-radius-pill);\n}\n\n/* Running: a ring with one quarter in ink, turning. */\n.spinner {\n width: 12px;\n height: 12px;\n border: 1.5px solid var(--ick-state-track);\n border-top-color: currentColor;\n border-radius: var(--ick-radius-pill);\n animation: spin 0.7s linear infinite;\n}\n\n@keyframes spin {\n to { transform: rotate(360deg); }\n}\n\n/* Standing still it is still a ring that is not a tick and not a triangle,\n which is the whole job. A spinner that vanishes with its animation was\n carrying its meaning in the movement. */\n@media (prefers-reduced-motion: reduce) {\n .spinner {\n animation: none;\n }\n}\n","\"use client\";\n\nimport { Check, TriangleAlert } from \"lucide-react\";\nimport styles from \"./StateGlyph.module.css\";\n\n/**\n * Queued, working, finished, failed.\n *\n * One vocabulary across the kit rather than one per component — a tool call\n * and a task in a list are in the same four states, and calling the third one\n * `active` in one place and `running` in another buys nothing and costs a\n * mental translation every time somebody reads both.\n */\nexport type WorkState = \"pending\" | \"running\" | \"done\" | \"error\";\n\n/**\n * The 16px square that says which of the four states something is in.\n *\n * Four shapes, not four colours. Colour is the second thing it says, and a\n * status carried only by colour is a status half the people reading it do not\n * have — so a queued ring, a turning ring, a tick and a triangle are different\n * before anything is tinted.\n *\n * Not exported from the package. Two components draw it and neither wants a\n * consumer's version of it; if that changes it is one line in `index.ts`.\n */\nexport function StateGlyph({ state }: { state: WorkState }) {\n return (\n <span className={styles.glyph} data-state={state} aria-hidden>\n {state === \"running\" ? (\n <span className={styles.spinner} />\n ) : state === \"done\" ? (\n <Check size={13} />\n ) : state === \"error\" ? (\n <TriangleAlert size={13} />\n ) : (\n <span className={styles.queued} />\n )}\n </span>\n );\n}\n","/* ─────────────────────────────────────────────\n Tool\n\n One tool call, in the same three surfaces a question is: a **ground**, a\n **card** on it, and what the card holds **inset** into it. It used to be one\n box, and the box was the grey one — the same three stacked backwards, which\n beside a question card read as a different system.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n/* The ground. What the card stands on, and what the header stands on above\n it — the same two the question group has, in the same order. */\n.tool {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-5);\n width: 100%;\n padding: var(--ick-tool-ground-pad);\n border-radius: var(--ick-tool-ground-radius);\n background: var(--ick-tool-ground);\n font-family: var(--ick-font-sans);\n\n /* An identifier, not prose — so the header's label is set as one. These live\n on the ground rather than on the card now, because that is where the\n header is. */\n --ick-disclosure-font: var(--ick-font-mono);\n --ick-disclosure-size: var(--ick-text-sm);\n --ick-disclosure-label: var(--ick-tool-name);\n --ick-disclosure-meta: var(--ick-tool-meta);\n /* And the header's own content sits on the column the card's content sits\n on: the glyph where a panel's label starts, the chevron where they end.\n Measured against the card's edges instead, the chevron sat hard in the\n corner with nothing under it agreeing. */\n --ick-disclosure-inset: var(--ick-tool-pad);\n /* And no wash, for the reason a question group has none: the hover is a\n rounded box the width of the header, and on a ground there is nothing\n under it for that shape to agree with. The chevron lighting up is the\n response. */\n --ick-disclosure-hover: transparent;\n}\n\n.card {\n box-sizing: border-box;\n width: 100%;\n border-radius: var(--ick-tool-radius);\n background: var(--ick-tool-surface);\n box-shadow: var(--ick-tool-shadow);\n /* Clips the body to the box while it is opening, so growing reads as a\n reveal rather than as content arriving before its container has. */\n overflow: hidden;\n}\n\n/* ── What the card says when it is shut ─────────────────────────────────── */\n\n/* One row, on the card's own column — a folded question group's summary row in\n everything but its contents. It stays when the call opens, which is where\n this parts company with the question group: nothing swaps, so nothing can be\n drawn over anything.\n\n The line box takes the badge's height for the same reason every label beside\n a badge does, and it is what makes this row 40 tall — a folded question's\n height, so a stack of the two lines up. */\n.overview {\n box-sizing: border-box;\n width: 100%;\n margin: 0;\n padding: var(--ick-space-4) var(--ick-tool-pad);\n font-size: var(--ick-text-sm);\n line-height: var(--ick-badge-size);\n color: var(--ick-tool-meta);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.bodyInner {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-5);\n width: 100%;\n padding: 0 var(--ick-tool-inner-gap) var(--ick-tool-inner-gap);\n /* The overview above is the separation, so the body starts right under it. */\n /* No rule under the header.\n\n The question card separates things by **surface and gap** — a row is an\n inset panel with space around it, and nothing is underlined. This drew a\n line instead, and with two sections open that made three stacked rules in\n a component the size of a paragraph. The panels already sit on the tool's\n ground with space between them; the line was saying again what the surface\n had already said. */\n margin-top: var(--ick-space-2);\n /* Repointed rather than restyled: the code block reads its own tokens, so\n this moves its surface and its corner without knowing anything about how a\n code block is built. */\n --ick-md-code-fill: var(--ick-tool-code-fill);\n --ick-code-radius: var(--ick-tool-inner-radius);\n /* 8, so a panel's label sits at 8 + 8 = 16 from the card's edge — the column\n the header's glyph is in. At 12 it landed at 20, which is the kind of\n four-pixel near-miss that reads as wrong without being nameable. */\n --ick-code-pad: var(--ick-space-4);\n}\n\n/* An inset panel, exactly like the code block beside it.\n\n It was a bare stack: no surface, no padding of its own, so its label and its\n text sat flush at the body's 8px while a fenced value's label and code sat\n at 12px inside the block's own panel. Two sections of one tool call at two\n left edges — which is what \"the padding ran away at the bottom\" is, when the\n bottom section happens to be the plain-text one.\n\n The numbers are `CodeBlock`'s, deliberately: its bar puts a label 12px in\n and its `pre` puts the code 12px in, so matching that is what makes the two\n line up rather than nearly line up. */\n.section {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-3);\n width: 100%;\n min-width: 0;\n padding: var(--ick-space-3) var(--ick-code-pad) var(--ick-code-pad);\n border-radius: var(--ick-tool-inner-radius);\n background: var(--ick-tool-code-fill);\n}\n\n.sectionLabel {\n font-size: var(--ick-text-xs);\n letter-spacing: 0.04em;\n text-transform: lowercase;\n color: var(--ick-tool-meta);\n}\n\n.text {\n margin: 0;\n font-size: var(--ick-text-md);\n line-height: 1.55;\n color: var(--ick-ink);\n /* What came back keeps its own line breaks; it is a value, not a paragraph\n somebody wrote. */\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n/* ── When it failed ─────────────────────────────────────────────────────── */\n\n/* The same panel, tinted. It used to be a box *inside* the section's box —\n two panels for one thing, and the inner one at a different inset again. */\n.section[data-tone=\"error\"] {\n background: var(--ick-tool-error-surface);\n color: var(--ick-danger);\n}\n\n.section[data-tone=\"error\"] .text,\n.section[data-tone=\"error\"] .sectionLabel {\n color: inherit;\n}\n\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n","\"use client\";\n\nimport { isValidElement, useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { CodeBlock } from \"../CodeBlock/CodeBlock\";\nimport { StateGlyph, type WorkState } from \"../stateGlyph/StateGlyph\";\nimport { useDisclosure } from \"../disclosure/useDisclosure\";\nimport { DisclosureHeader } from \"../disclosure/DisclosureHeader\";\nimport { DisclosureBody } from \"../disclosure/DisclosureBody\";\nimport { formatDuration } from \"../duration/formatDuration\";\nimport { Loader } from \"../Loader/Loader\";\nimport styles from \"./Tool.module.css\";\n\n/** Queued, working, finished, or failed — the kit's four, shared with `TaskList`. */\nexport type ToolState = WorkState;\n\ntype Labels = Record<\"input\" | \"output\" | \"error\" | ToolState, string>;\n\nexport interface ToolProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\" | \"onToggle\"> {\n /** What was called. Set in mono: it is an identifier, not prose. */\n name: string;\n state?: ToolState;\n /** A sentence for what it did — \"Searched the web\", \"Read 3 files\". */\n summary?: ReactNode;\n /**\n * What it was called with.\n *\n * A string is shown as text and an object as JSON. Anything you want drawn\n * some other way, pass as an element and it is rendered untouched.\n */\n input?: unknown;\n /** What came back. Read the same way as `input`. */\n output?: unknown;\n /** What went wrong. Drawn instead of the output, and opens the row. */\n error?: ReactNode;\n /** How long it took, in ms. Shown once it has finished. */\n duration?: number;\n /** Open, controlled. Leave out and the row keeps its own. */\n open?: boolean;\n /** Where it starts. Defaults to open only if it failed. */\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n labels?: Partial<Labels>;\n}\n\nconst LABELS: Labels = {\n input: \"Input\",\n output: \"Output\",\n error: \"Error\",\n pending: \"Queued\",\n running: \"Running\",\n done: \"Done\",\n};\n\n/**\n * A value, and how it wants to be drawn.\n *\n * A string is what came back rather than a program, so it is text. An object\n * is JSON, which is what it already was on the wire. An element is somebody\n * having decided for themselves, so it is left alone.\n */\ntype Shown =\n | { kind: \"node\"; node: ReactNode }\n | { kind: \"text\"; text: string }\n | { kind: \"json\"; code: string }\n | null;\n\nfunction show(value: unknown): Shown {\n if (value === null || value === undefined) return null;\n if (isValidElement(value)) return { kind: \"node\", node: value };\n if (typeof value === \"string\") return value.trim() ? { kind: \"text\", text: value } : null;\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return { kind: \"text\", text: String(value) };\n }\n try {\n const code = JSON.stringify(value, null, 2);\n return code ? { kind: \"json\", code } : null;\n } catch {\n // Circular, or a BigInt. A row that throws while rendering a tool call is\n // worse than one that prints something unhelpful.\n return { kind: \"text\", text: String(value) };\n }\n}\n\nfunction Section({\n label,\n value,\n tone,\n}: {\n label: string;\n value: Shown;\n /** `error` puts the section on its own ground. */\n tone?: \"error\";\n}) {\n if (!value) return null;\n\n /* A fenced value carries its heading in its own bar. Stacking a title above\n a bar that held nothing but a copy button was two rows doing one row's\n work, and the empty strip between them was the first thing you saw. */\n if (value.kind === \"json\") {\n return <CodeBlock code={value.code} lang=\"json\" label={label} />;\n }\n\n const body =\n value.kind === \"text\" ? <p className={styles.text}>{value.text}</p> : value.node;\n\n return (\n <div className={styles.section} data-tone={tone}>\n <span className={styles.sectionLabel}>{label}</span>\n {body}\n </div>\n );\n}\n\n/**\n * One tool call: what was run, what with, what came back.\n *\n * Collapsed, because most of the time nobody cares — and open when it failed,\n * because an error nobody can see has not been reported. The glyph says which\n * state it is in and the row says so in words too; a status carried only by\n * colour is a status half the people reading it do not have.\n */\nexport function Tool({\n name,\n state = \"done\",\n summary,\n input,\n output,\n error,\n duration,\n open,\n defaultOpen,\n onOpenChange,\n labels,\n className,\n ...rest\n}: ToolProps) {\n const label = { ...LABELS, ...labels };\n const bodyId = useId();\n\n const failed = state === \"error\";\n\n /* Open when it failed, because an error nobody can see has not been\n reported — but only as the row's own preference, which anybody reading it\n can overrule. See `useDisclosure`. */\n const { isOpen, toggle } = useDisclosure({\n open,\n defaultOpen,\n onOpenChange,\n preferOpen: failed,\n });\n const shownInput = show(input);\n const shownOutput = failed ? null : show(output);\n const shownError = failed ? show(error) : null;\n const hasBody = Boolean(shownInput || shownOutput || shownError || state === \"running\");\n\n const time = state === \"done\" || failed ? formatDuration(duration ?? NaN) : \"\";\n\n return (\n <div\n className={[styles.tool, className ?? \"\"].filter(Boolean).join(\" \")}\n data-state={state}\n {...rest}\n >\n {/* The name of the call, on the ground, above the card — which is where\n a question group puts its section title, and for the same reason. A\n header that is the card's top edge has to move when the card grows;\n one on the ground does not, so opening the call is the body changing\n under a row that stays put.\n\n It also settles the alignment: on the ground the header takes\n `--ick-disclosure-inset`, so the glyph starts on the column the card's\n panels start on and the chevron ends where they end. Filled, it was\n measured against the *card's* edges, which is what put the chevron\n hard into the corner. */}\n <DisclosureHeader\n open={isOpen}\n onToggle={toggle}\n controls={bodyId}\n disabled={!hasBody}\n glyph={<StateGlyph state={state} />}\n label={name}\n meta={time || undefined}\n >\n {/* The glyph is a picture. This is the same thing in words, for\n anybody the picture is not reaching. */}\n <span className={styles.srOnly}>{label[state]}</span>\n </DisclosureHeader>\n\n {/* The card, and the overview stays in it whether or not the call is\n open — which is where this deliberately parts company with the\n question group. There the two bodies swap, and because both are\n anchored to the same top edge they want the same band and cannot be\n crossfaded without drawing one over the other. Here nothing swaps:\n the summary is the card's first row in both states and the detail\n opens underneath it. Same shape, one fewer thing to go wrong. */}\n <div className={styles.card}>\n <p className={styles.overview}>{summary ?? label[state]}</p>\n\n <DisclosureBody\n id={bodyId}\n open={isOpen && hasBody}\n className={styles.bodyInner}\n >\n <Section label={label.input} value={shownInput} />\n {state === \"running\" && !shownOutput && (\n <div className={styles.section}>\n <span className={styles.sectionLabel}>{label.output}</span>\n <Loader />\n </div>\n )}\n <Section label={label.output} value={shownOutput} />\n <Section label={label.error} value={shownError} tone=\"error\" />\n </DisclosureBody>\n </div>\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n Approval\n\n The one component here whose whole job is to slow somebody down for a\n moment, so it is the one thing in an answer that is allowed to look like a\n box that stops you. Everything else in the agent tier is quiet on purpose.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n/* The ground — the same one a question group stands on. An approval is a\n question: it asks, it blocks, it has an answer. See `--ick-approval-surface`\n for why it stopped being tinted. */\n.approval {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-5);\n width: 100%;\n padding: var(--ick-approval-pad);\n border-radius: var(--ick-approval-radius);\n background: var(--ick-approval-surface);\n font-family: var(--ick-font-sans);\n\n /* The subject brings the *contents* of the card; the approval draws the card\n itself. So a tool call standing in one brings no surfaces of its own — not\n its ground, not its card, not its shadow.\n\n Two arrangements were tried before this. The approval's card wrapping all\n three (title, subject, buttons) made an approval holding a tool call two\n papers for one thing. Letting the tool bring its own card then left its\n header stranded on the ground beside the title — two headers over one\n card, and the second of them a stray row.\n\n One card, and everything in it belongs to the subject. The title above and\n the buttons below are on the ground, because they are the asking and the\n answering rather than part of what is being approved. */\n --ick-tool-ground: transparent;\n /* Vertical only. Sideways it has to be nothing, or the tool's header is\n pushed off the column the title and the buttons stand on; downwards and\n upwards it is what keeps the header off the card's top edge — which is\n what zeroing all four sides did, and it showed. The bottom is the tool\n body's own, so this side gives nothing back. */\n --ick-tool-ground-pad: var(--ick-nest-pad) 0 0;\n --ick-tool-surface: transparent;\n --ick-tool-shadow: none;\n --ick-tool-radius: 0;\n /* And a subject that is not a tool call still lands on the column: a bare\n code block's own padding is 12, which would sit four pixels inside it. */\n --ick-code-pad: var(--ick-approval-column);\n}\n\n/* Nothing here changes when it settles. It used to shed its tint and pick up a\n border, because the tint was the thing saying \"unanswered\" and it had to\n stop saying it. The buttons say that now, and they leave on their own — the\n way a question card keeps its paper and its shadow when it folds into an\n answered row. */\n\n\n/* ── What is being asked, on the ground above the card ──────────────────── */\n\n.head {\n box-sizing: border-box;\n width: 100%;\n min-width: 0;\n /* On the card's column, so the shield starts where the subject's own content\n starts. It used to be 8 plus the card's 8, which was the same arithmetic\n from inside the card; from out here there is no card padding to add to, so\n the column is stated. */\n padding: var(--ick-space-2) 0 0 var(--ick-approval-column);\n}\n\n\n\n.title {\n margin: 0;\n font-size: var(--ick-approval-size);\n font-weight: var(--ick-weight-medium);\n color: var(--ick-ink);\n}\n\n.description {\n margin: var(--ick-space-1) 0 0;\n font-size: var(--ick-text-sm);\n line-height: 1.5;\n color: var(--ick-ink-soft);\n}\n\n/* The card, and the only thing on paper: what is actually being decided about.\n\n No padding of its own, deliberately. Whatever is inside brings it — a tool\n call's rows are padded to the column already, a code block's panel likewise\n — and adding a gap here would push all of it eight pixels off the line the\n title and the buttons stand on. */\n.subject {\n box-sizing: border-box;\n width: 100%;\n min-width: 0;\n border-radius: var(--ick-nest-card);\n background: var(--ick-card);\n box-shadow: var(--ick-shadow-float);\n /* Clips whatever is inside to the corner, since nothing in there knows it is\n against the card's edge. */\n overflow: hidden;\n}\n\n/* ── The decision ───────────────────────────────────────────────────────── */\n\n.actions {\n box-sizing: border-box;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--ick-space-3);\n width: 100%;\n /* The footer, on the ground under the card. Deny sits on the column the\n shield above it sits on; the two that say yes end where the card's content\n ends, which is the same column mirrored. */\n padding: var(--ick-space-2) var(--ick-approval-column) 0;\n}\n\n/* Alone at the left end, with the two that say yes pushed to the right. A\n destructive choice sitting flush against an affirmative one is a mis-click\n waiting to happen, and the gap is what stops it.\n\n Neutral at rest and only red under the pointer: a permanently red button is\n the first thing the eye lands on, and the destructive choice is not the one\n to draw somebody towards. */\n.deny {\n margin-right: auto;\n /* Pulled back by its own padding, so the *word* lands on the column rather\n than the invisible box around it. A ghost button is text — what you see is\n \"Deny\", and it sat 16px inside the line the shield above it is on. The two\n that say yes are the other way round: they are filled, so the box is the\n visible thing and its edge is what belongs on the column.\n\n `size=\"m\"`, hence `--ick-space-6`. Sharing a token with `Button` would\n couple this to a size it does not choose. */\n margin-left: calc(var(--ick-space-6) * -1);\n}\n\n.deny:hover:not(:disabled),\n.deny:focus-visible {\n color: var(--ick-danger);\n}\n\n.settled {\n display: flex;\n padding: var(--ick-space-2) var(--ick-approval-column) 0;\n align-items: center;\n gap: var(--ick-space-3);\n margin: 0;\n font-size: var(--ick-text-sm);\n color: var(--ick-ink-soft);\n}\n\n.settled[data-decision=\"denied\"] {\n color: var(--ick-danger);\n}\n","\"use client\";\n\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { Check, X } from \"lucide-react\";\nimport { Button } from \"../Button/Button\";\nimport styles from \"./Approval.module.css\";\n\n/** Once, every time from now on, or not at all. */\nexport type Decision = \"once\" | \"always\" | \"denied\";\n\ntype Labels = Record<\"once\" | \"always\" | \"deny\", string> &\n Record<\"allowedOnce\" | \"allowedAlways\" | \"wasDenied\" | \"pending\", string>;\n\nconst LABELS: Labels = {\n once: \"Allow once\",\n always: \"Always allow\",\n deny: \"Deny\",\n allowedOnce: \"Allowed once\",\n allowedAlways: \"Allowed from now on\",\n wasDenied: \"Denied\",\n pending: \"Waiting for you\",\n};\n\nexport interface ApprovalProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n /** What is being asked. One line: \"Run a command in your shell\". */\n title: ReactNode;\n /** Why it is being asked, or what it will touch. */\n description?: ReactNode;\n /**\n * The thing itself — usually a `<Tool>` or a `<CodeBlock>`.\n *\n * Somebody deciding needs to see what they are deciding about, and a name is\n * not that. An approval with nothing under it is asking for a signature on a\n * blank page.\n */\n children?: ReactNode;\n /** `null` while it is still being asked. */\n decision?: Decision | null;\n onDecide?: (decision: Decision) => void;\n /** Nothing can be decided from here — a record of a decision already made. */\n readOnly?: boolean;\n labels?: Partial<Labels>;\n}\n\nconst SETTLED: Record<Decision, keyof Labels> = {\n once: \"allowedOnce\",\n always: \"allowedAlways\",\n denied: \"wasDenied\",\n};\n\n/**\n * \"It wants to do this. Is that all right?\"\n *\n * The one pattern from a coding agent that generalises to any agent that acts,\n * and the only component in this kit whose whole job is to slow somebody down\n * for a moment.\n *\n * Three decisions rather than two, because \"yes\" and \"yes forever\" are not the\n * same answer and a UI offering one button for both collects the wrong one.\n * **Allow once is the primary**: the narrow permission is the one that should\n * be easiest to give, and the standing one should cost a moment's thought.\n *\n * Decided, it stops being a set of buttons and becomes a record of what was\n * decided. Leaving live controls under a decision already made invites a\n * second, contradictory one.\n */\nexport function Approval({\n title,\n description,\n children,\n decision = null,\n onDecide,\n readOnly = false,\n labels,\n className,\n ...rest\n}: ApprovalProps) {\n const label = { ...LABELS, ...labels };\n const titleId = useId();\n const settled = decision !== null;\n\n return (\n <section\n className={[styles.approval, className ?? \"\"].filter(Boolean).join(\" \")}\n data-decision={decision ?? undefined}\n aria-labelledby={titleId}\n {...rest}\n >\n {/* Ground, then what is being asked over the card, then the answer under\n it — a question group's three, in a question group's order.\n\n The title and the buttons are *on the ground*, not on the card. They\n are not part of the thing being approved; they are the asking and the\n answering, and the card between them is the subject. It used to wrap\n all three, which meant an approval holding a tool call was a card\n inside a card — two papers, two shadows, one thing. */}\n {/* No shield. It said \"this is about permission\" in a picture, next to a\n title that says it in words, above a card that shows the thing and\n three buttons that are visibly a decision. Every other glyph in the\n kit carries *state* — queued, running, failed, allowed, denied — and\n pairs with a word for anybody the picture is not reaching. This one\n carried a category nothing else could be mistaken for.\n\n It was also drawn at 24 against everything else's 14, because the\n badge box it was meant to sit *in* was applied to the icon itself. */}\n <div className={styles.head}>\n <p className={styles.title} id={titleId}>\n {title}\n </p>\n {description && <p className={styles.description}>{description}</p>}\n </div>\n\n {/* What is actually being asked for, and the only thing on paper. */}\n {children && <div className={styles.subject}>{children}</div>}\n\n {settled ? (\n /* A record, not a control. Live buttons under a decision already made\n invite a second one that contradicts the first. */\n <p className={styles.settled} data-decision={decision}>\n {decision === \"denied\" ? (\n <X size={13} aria-hidden />\n ) : (\n <Check size={13} aria-hidden />\n )}\n {label[SETTLED[decision]]}\n </p>\n ) : (\n /* Three weights for three answers, in the order they should be reached\n for: filled for the narrow yes, outlined for the standing one, flat\n for no. Two outlined buttons beside each other said the last two were\n equals, which they are not.\n\n Deny stays first in the DOM, so a keyboard lands on the safe answer\n without tabbing past two that say yes. */\n !readOnly && (\n <div className={styles.actions}>\n <Button\n variant=\"ghost\"\n size=\"m\"\n className={styles.deny}\n onClick={() => onDecide?.(\"denied\")}\n >\n {label.deny}\n </Button>\n <Button variant=\"outline\" size=\"m\" onClick={() => onDecide?.(\"always\")}>\n {label.always}\n </Button>\n <Button variant=\"primary\" size=\"m\" onClick={() => onDecide?.(\"once\")}>\n {label.once}\n </Button>\n </div>\n )\n )}\n </section>\n );\n}\n","/* ─────────────────────────────────────────────\n TaskList\n\n A list, not a box. `Tool` is a record of one thing that ran and gets a\n surface; this is a sequence, and the sequence is what it is saying — so the\n rows read down the page with nothing drawn around them.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.tasks {\n box-sizing: border-box;\n width: 100%;\n /* It carries no surface of its own — no ground, no card — so it sits in the\n answer's flow, and everything in that flow starts on the answer's column.\n See `--ick-answer-column`. */\n padding-left: var(--ick-answer-column);\n font-family: var(--ick-font-sans);\n\n /* A title, not a label: this heads a plan rather than describing an aside.\n See the same note in `Sources`. */\n --ick-disclosure-label: var(--ick-ink);\n --ick-disclosure-weight: var(--ick-weight-medium);\n}\n\n.list {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-task-gap);\n width: 100%;\n margin: var(--ick-space-3) 0 0;\n padding: 0;\n list-style: none;\n}\n\n.item {\n box-sizing: border-box;\n display: flex;\n align-items: flex-start;\n gap: var(--ick-space-4);\n width: 100%;\n}\n\n.body {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-1);\n /* The glyph is 16 and the text is smaller, so the first line is nudged onto\n the glyph's centre rather than its top. */\n padding-top: 1px;\n}\n\n.label {\n font-size: var(--ick-task-size);\n line-height: 1.45;\n color: var(--ick-task-ink);\n}\n\n/* Finished work recedes. It is still there to read, and it is no longer what\n anybody is looking for. */\n.item[data-state=\"done\"] .label {\n color: var(--ick-task-ink-done);\n}\n\n.item[data-state=\"pending\"] .label {\n color: var(--ick-task-ink-pending);\n}\n\n/* The one being worked on is the answer to \"where is it up to\". */\n.item[data-state=\"running\"] .label {\n color: var(--ick-ink);\n font-weight: var(--ick-weight-medium);\n}\n\n.detail {\n font-size: var(--ick-text-sm);\n line-height: 1.5;\n color: var(--ick-ink-faint);\n}\n\n.item[data-state=\"error\"] .detail {\n color: var(--ick-danger);\n}\n\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n","\"use client\";\n\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { StateGlyph, type WorkState } from \"../stateGlyph/StateGlyph\";\nimport { useDisclosure } from \"../disclosure/useDisclosure\";\nimport { DisclosureHeader } from \"../disclosure/DisclosureHeader\";\nimport { DisclosureBody } from \"../disclosure/DisclosureBody\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport styles from \"./TaskList.module.css\";\n\n/** The same four as a tool call. See `StateGlyph`. */\nexport type TaskState = WorkState;\n\nexport interface Task {\n id: string;\n label: ReactNode;\n state?: TaskState;\n /** A line under the label — what it found, or why it failed. */\n detail?: ReactNode;\n}\n\ntype Labels = Record<TaskState, string> & {\n /** `{done}` and `{total}` are filled in. */\n progress: string;\n};\n\nconst LABELS: Labels = {\n pending: \"Queued\",\n running: \"In progress\",\n done: \"Done\",\n error: \"Failed\",\n progress: \"{done} of {total}\",\n};\n\nexport interface TaskListProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n tasks: Task[];\n /** Names the list, and gives it a row to fold into. */\n title?: ReactNode;\n /**\n * Let the whole list fold away.\n *\n * It folds itself once every task is done — a plan is worth watching while\n * it runs and worth little afterwards — and anybody reading it can overrule\n * that, in either direction, for good.\n */\n collapsible?: boolean;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n labels?: Partial<Labels>;\n}\n\n/**\n * What the agent means to do, what it is doing, and what it has finished.\n *\n * The order never changes. A list that sorted itself as work progressed would\n * move the line somebody is reading out from under them, and the sequence is\n * half of what the list is saying — these steps, in this order. Only the\n * glyphs change.\n */\nexport function TaskList({\n tasks,\n title,\n collapsible = false,\n open,\n defaultOpen,\n onOpenChange,\n labels,\n className,\n ...rest\n}: TaskListProps) {\n const label = { ...LABELS, ...labels };\n const listId = useId();\n const still = prefersReducedMotion();\n\n const done = tasks.filter((t) => (t.state ?? \"pending\") === \"done\").length;\n const finished = tasks.length > 0 && done === tasks.length;\n\n const disclosure = useDisclosure({\n open,\n defaultOpen,\n onOpenChange,\n preferOpen: !finished,\n });\n // Without a row to fold into there is nothing to fold with.\n const isOpen = collapsible ? disclosure.isOpen : true;\n\n const progress = label.progress\n .replace(\"{done}\", String(done))\n .replace(\"{total}\", String(tasks.length));\n\n\n return (\n <section\n className={[styles.tasks, className ?? \"\"].filter(Boolean).join(\" \")}\n {...rest}\n >\n {title && (\n <DisclosureHeader\n open={isOpen}\n /* No handler when it cannot fold, which is what makes it a heading\n rather than a control that does nothing. */\n onToggle={collapsible ? disclosure.toggle : undefined}\n controls={listId}\n label={title}\n meta={progress}\n />\n )}\n\n <DisclosureBody id={listId} open={isOpen}>\n <ol className={styles.list}>\n <AnimatePresence initial={false}>\n {tasks.map((task) => {\n const state = task.state ?? \"pending\";\n return (\n <motion.li\n key={task.id}\n className={styles.item}\n data-state={state}\n /* The one being worked on, said in a way a screen\n reader can jump to. `step` rather than `true`: this\n is a sequence, and which step it is on is the\n question somebody is asking. */\n aria-current={state === \"running\" ? \"step\" : undefined}\n initial={still ? { opacity: 0 } : { opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={still ? { opacity: 0 } : { opacity: 0, y: -4 }}\n transition={{ duration: still ? 0.12 : 0.2 }}\n >\n <StateGlyph state={state} />\n <span className={styles.body}>\n <span className={styles.label}>{task.label}</span>\n {/* The glyph is a picture. This is the same thing in\n words, for anybody it is not reaching. */}\n <span className={styles.srOnly}>{label[state]}</span>\n {task.detail && <span className={styles.detail}>{task.detail}</span>}\n </span>\n </motion.li>\n );\n })}\n </AnimatePresence>\n </ol>\n </DisclosureBody>\n </section>\n );\n}\n","/* ─────────────────────────────────────────────\n ChainOfThought\n\n A derivation, and the line down the glyph column is the point: each step\n follows from the one above it. `TaskList` is a plan and draws no line\n between its items, because a plan's items do not follow from each other —\n they are a set, in an order somebody chose.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.chain {\n box-sizing: border-box;\n width: 100%;\n /* It carries no surface of its own — no ground, no card — so it sits in the\n answer's flow, and everything in that flow starts on the answer's column.\n See `--ick-answer-column`. */\n padding-left: var(--ick-answer-column);\n font-family: var(--ick-font-sans);\n}\n\n.list {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n width: 100%;\n margin: var(--ick-space-3) 0 0;\n padding: 0;\n list-style: none;\n}\n\n.step {\n box-sizing: border-box;\n display: flex;\n align-items: flex-start;\n gap: var(--ick-space-4);\n width: 100%;\n padding-bottom: var(--ick-chain-gap);\n}\n\n/* Nothing follows the last one, so nothing should suggest it does. */\n.step:last-child {\n padding-bottom: 0;\n}\n\n/* The glyph, and the line to the step under it. Drawn on the rail rather than\n between the rows, so it stretches with however tall a step's working is. */\n.rail {\n position: relative;\n flex-shrink: 0;\n /* The line runs from under this glyph to the next one, so the rail has to be\n as tall as the step is. Left to itself it is as tall as the glyph, and the\n line came out a nine-pixel stub — measured, after the first screenshot. */\n align-self: stretch;\n display: flex;\n flex-direction: column;\n align-items: center;\n width: var(--ick-state-size);\n /* One pixel down, so the glyph sits on the label's first line rather than\n above it — the text is smaller than the glyph's box. */\n padding-top: 1px;\n}\n\n.step:not(:last-child) .rail::after {\n content: \"\";\n position: absolute;\n top: calc(var(--ick-state-size) + var(--ick-space-2));\n bottom: calc(var(--ick-chain-gap) * -1);\n left: 50%;\n width: 1px;\n margin-left: -0.5px;\n background: var(--ick-chain-connector);\n}\n\n.body {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-2);\n padding-top: 1px;\n}\n\n.stepLabel {\n font-size: var(--ick-chain-size);\n line-height: 1.45;\n font-weight: var(--ick-weight-medium);\n color: var(--ick-chain-label);\n}\n\n/* Still to come, so it should not read as something that happened. */\n.step[data-state=\"pending\"] .stepLabel {\n font-weight: var(--ick-weight-regular);\n color: var(--ick-ink-faint);\n}\n\n/* The working behind the step. Prose, so it is set like the answer it belongs\n to and coloured like an aside. */\n.working {\n font-size: var(--ick-chain-size);\n line-height: var(--ick-chain-leading);\n color: var(--ick-chain-ink);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n.working > :first-child {\n margin-top: 0;\n}\n\n.working > :last-child {\n margin-bottom: 0;\n}\n","\"use client\";\n\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\n\nimport { StateGlyph, type WorkState } from \"../stateGlyph/StateGlyph\";\nimport { useDisclosure } from \"../disclosure/useDisclosure\";\nimport { DisclosureHeader } from \"../disclosure/DisclosureHeader\";\nimport { DisclosureBody } from \"../disclosure/DisclosureBody\";\nimport { formatDuration } from \"../duration/formatDuration\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport styles from \"./ChainOfThought.module.css\";\n\n/** One step of a derivation: what was worked out, and the working. */\nexport interface Thought {\n id: string;\n /** What this step established. One line. */\n label: ReactNode;\n /** The working behind it. Prose, or anything you want drawn — a `Tool`, say. */\n body?: ReactNode;\n /** Defaults to `done`: a step you can read is a step that happened. */\n state?: WorkState;\n}\n\ntype Labels = {\n /** Followed by the count: \"Thought through 4 steps\". */\n through: string;\n step: string;\n steps: string;\n thinking: string;\n};\n\nconst LABELS: Labels = {\n through: \"Thought through\",\n step: \"step\",\n steps: \"steps\",\n thinking: \"Thinking\",\n};\n\nexport interface ChainOfThoughtProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n steps: Thought[];\n /** The chain as a whole. `thinking` holds it open and narrates the last step. */\n state?: \"thinking\" | \"done\";\n /** How long the whole chain took, in ms. */\n duration?: number;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n labels?: Partial<Labels>;\n}\n\n/**\n * How the answer was arrived at, step by step.\n *\n * The third thing in this kit that draws a sequence, and the line between them\n * is worth stating because it is the only reason there are three:\n *\n * - **`Reasoning`** is prose. The model talking to itself, unstructured.\n * - **`TaskList`** is a plan. Known up front, forward-looking, fixed order,\n * items changing state as work happens.\n * - **`ChainOfThought`** is a derivation. Backward-looking, and it *grows* —\n * each step follows from the one above it, which is what the line down the\n * side is drawing. A step carries its own working, not a footnote.\n *\n * Open while it thinks and folded away once the answer starts, on the same\n * terms as `Reasoning`: the chain's preference, overruled for good by anybody\n * who touches it.\n */\nexport function ChainOfThought({\n steps,\n state = \"done\",\n duration,\n open,\n defaultOpen,\n onOpenChange,\n labels,\n className,\n ...rest\n}: ChainOfThoughtProps) {\n const label = { ...LABELS, ...labels };\n const listId = useId();\n const still = prefersReducedMotion();\n const thinking = state === \"thinking\";\n\n const { isOpen, toggle } = useDisclosure({\n open,\n defaultOpen,\n onOpenChange,\n preferOpen: thinking,\n });\n\n /* While it is running the header narrates the step it is on — which is the\n one question somebody watching is asking, and the reason to look at a\n folded chain at all. */\n const running = thinking\n ? steps.find((s) => (s.state ?? \"done\") === \"running\") ?? steps[steps.length - 1]\n : undefined;\n\n const time = thinking ? \"\" : formatDuration(duration ?? NaN);\n const counted = `${label.through} ${steps.length} ${steps.length === 1 ? label.step : label.steps}`;\n\n return (\n <section\n className={[styles.chain, className ?? \"\"].filter(Boolean).join(\" \")}\n data-state={state}\n {...rest}\n >\n {/* An aside in the flow of an answer, like `Reasoning`'s — it takes the\n width of its own words rather than a band's. */}\n <DisclosureHeader\n fit=\"inline\"\n open={isOpen}\n onToggle={toggle}\n controls={listId}\n glyph={<StateGlyph state={thinking ? \"running\" : \"done\"} />}\n /* While it runs the header narrates the step it is on, which is the\n one question somebody watching a folded chain is asking. */\n label={thinking ? (running?.label ?? label.thinking) : counted}\n pending={thinking}\n meta={time || undefined}\n />\n\n <DisclosureBody id={listId} open={isOpen && steps.length > 0}>\n <ol className={styles.list}>\n <AnimatePresence initial={false}>\n {steps.map((step) => {\n const stepState = step.state ?? \"done\";\n return (\n <motion.li\n key={step.id}\n className={styles.step}\n data-state={stepState}\n aria-current={stepState === \"running\" ? \"step\" : undefined}\n initial={still ? { opacity: 0 } : { opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={still ? { opacity: 0 } : { opacity: 0, y: -4 }}\n transition={{ duration: still ? 0.12 : 0.2 }}\n >\n <span className={styles.rail} aria-hidden>\n <StateGlyph state={stepState} />\n </span>\n <span className={styles.body}>\n <span className={styles.stepLabel}>{step.label}</span>\n {step.body && <span className={styles.working}>{step.body}</span>}\n </span>\n </motion.li>\n );\n })}\n </AnimatePresence>\n </ol>\n </DisclosureBody>\n </section>\n );\n}\n","/* ─────────────────────────────────────────────\n Sources\n\n What the answer was drawn from. Numbered to match the markers in the text,\n and wearing the same square, so the two read as one thing counted twice\n rather than as two numbering schemes that happen to agree.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.sources {\n box-sizing: border-box;\n width: 100%;\n /* It carries no surface of its own — no ground, no card — so it sits in the\n answer's flow, and everything in that flow starts on the answer's column.\n See `--ick-answer-column`. */\n padding-left: var(--ick-answer-column);\n font-family: var(--ick-font-sans);\n\n /* A title, not a label: this heads a list rather than describing an aside,\n so it is ink at medium against `Reasoning`'s soft regular. Repointed from\n here rather than overridden with a class passed into the header — two\n `.label` rules from two stylesheets have equal specificity, and which one\n wins is import order. */\n --ick-disclosure-label: var(--ick-ink);\n --ick-disclosure-weight: var(--ick-weight-medium);\n}\n\n.list {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-2);\n width: 100%;\n margin: var(--ick-space-3) 0 0;\n padding: 0;\n list-style: none;\n}\n\n.item {\n box-sizing: border-box;\n width: 100%;\n border-radius: var(--ick-radius-md);\n transition: background var(--ick-duration-fast) ease;\n}\n\n/* Arrived at from a marker in the text. Marked rather than scrolled to: the\n list is already under the answer, and moving the page under somebody who\n clicked a marker in a sentence loses them the sentence. */\n.item[data-active] {\n background: var(--ick-cite-active);\n}\n\n.entry {\n box-sizing: border-box;\n display: flex;\n align-items: flex-start;\n gap: var(--ick-space-4);\n width: 100%;\n padding: var(--ick-space-3) var(--ick-space-4);\n border-radius: var(--ick-radius-md);\n color: inherit;\n text-decoration: none;\n}\n\na.entry {\n cursor: pointer;\n}\n\na.entry:hover {\n background: var(--ick-surface-hover);\n}\n\na.entry:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n.number {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: var(--ick-cite-size-marker);\n height: var(--ick-cite-size-marker);\n padding: 0 var(--ick-space-1);\n border-radius: var(--ick-radius-sm);\n background: var(--ick-cite-fill);\n color: var(--ick-cite-ink);\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n line-height: 1;\n}\n\n.number[data-active] {\n background: var(--ick-marker);\n color: var(--ick-marker-ink);\n}\n\n.body {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-1);\n}\n\n.name {\n font-size: var(--ick-cite-size);\n line-height: 1.45;\n color: var(--ick-ink);\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.origin {\n font-size: var(--ick-text-sm);\n color: var(--ick-ink-faint);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* The passage in the source that carries the claim. Quoted, so it is visibly\n somebody else's words and not the answer's. */\n.quote {\n margin-top: var(--ick-space-1);\n padding-left: var(--ick-space-4);\n border-left: 1px solid var(--ick-border);\n font-size: var(--ick-text-sm);\n line-height: 1.5;\n color: var(--ick-ink-soft);\n}\n","\"use client\";\n\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { useDisclosure } from \"../disclosure/useDisclosure\";\nimport { DisclosureHeader } from \"../disclosure/DisclosureHeader\";\nimport { DisclosureBody } from \"../disclosure/DisclosureBody\";\nimport styles from \"./Sources.module.css\";\n\n/** Where a claim came from. */\nexport interface Source {\n id: string;\n title: string;\n /** Opened when the entry is a link. Without one it is a row, not a link. */\n url?: string;\n /** A domain, a filename, a page — whatever names where this is. */\n origin?: string;\n /** The passage in the source that carries the claim. */\n quote?: string;\n}\n\ntype Labels = { title: string; one: string; many: string };\n\nconst LABELS: Labels = { title: \"Sources\", one: \"source\", many: \"sources\" };\n\nexport interface SourcesProps extends Omit<HTMLAttributes<HTMLElement>, \"title\" | \"onSelect\"> {\n sources: Source[];\n /** Heads the list. Defaults to `labels.title`. */\n title?: ReactNode;\n /** Offer to fold the list to its own row. */\n collapsible?: boolean;\n /**\n * The one just arrived at, from a citation in the text.\n *\n * Marked rather than scrolled to: the list is directly under the answer, and\n * moving the page under somebody who clicked a marker in a sentence they\n * were reading loses them the sentence.\n */\n activeId?: string | null;\n onSelect?: (source: Source, index: number) => void;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n labels?: Partial<Labels>;\n}\n\n/**\n * What the answer was drawn from, numbered to match the markers in it.\n *\n * Open by default. Sources are the difference between an answer somebody can\n * check and one they have to trust, and folding that away by default says the\n * opposite of what a citation is for.\n */\nexport function Sources({\n sources,\n title,\n collapsible = false,\n activeId = null,\n onSelect,\n open,\n defaultOpen,\n onOpenChange,\n labels,\n className,\n ...rest\n}: SourcesProps) {\n const label = { ...LABELS, ...labels };\n const listId = useId();\n\n const disclosure = useDisclosure({ open, defaultOpen, onOpenChange, preferOpen: true });\n const isOpen = collapsible ? disclosure.isOpen : true;\n\n const count = `${sources.length} ${sources.length === 1 ? label.one : label.many}`;\n\n return (\n <section className={[styles.sources, className ?? \"\"].filter(Boolean).join(\" \")} {...rest}>\n <DisclosureHeader\n open={isOpen}\n /* No handler when it cannot fold, which is what makes it a heading\n rather than a control that does nothing. */\n onToggle={collapsible ? disclosure.toggle : undefined}\n controls={listId}\n label={title ?? label.title}\n meta={count}\n />\n\n <DisclosureBody id={listId} open={isOpen && sources.length > 0}>\n <ol className={styles.list}>\n {sources.map((source, i) => {\n const number = i + 1;\n const active = source.id === activeId;\n const body = (\n <>\n {/* The same square the marker in the text wears, so the\n two read as one thing counted twice. */}\n <span className={styles.number} data-active={active || undefined}>\n {number}\n </span>\n <span className={styles.body}>\n <span className={styles.name}>{source.title}</span>\n {source.origin && <span className={styles.origin}>{source.origin}</span>}\n {source.quote && <span className={styles.quote}>{source.quote}</span>}\n </span>\n </>\n );\n\n return (\n <li\n key={source.id}\n className={styles.item}\n data-active={active || undefined}\n id={`${listId}-${source.id}`}\n >\n {source.url ? (\n <a\n className={styles.entry}\n href={source.url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n onClick={() => onSelect?.(source, number)}\n onPointerDown={(event) => event.stopPropagation()}\n >\n {body}\n </a>\n ) : (\n <div className={styles.entry}>{body}</div>\n )}\n </li>\n );\n })}\n </ol>\n </DisclosureBody>\n </section>\n );\n}\n",".chip {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n gap: var(--ick-space-2);\n height: var(--ick-chip-height);\n padding: 0 var(--ick-space-4);\n border-radius: var(--ick-chip-radius);\n background: var(--ick-chip-fill);\n color: var(--ick-ink);\n font-family: var(--ick-font-sans);\n font-size: var(--ick-text-sm);\n line-height: 1;\n white-space: nowrap;\n max-width: var(--ick-chip-max);\n overflow: hidden;\n text-overflow: ellipsis;\n}\n","\"use client\";\n\nimport { motion } from \"motion/react\";\nimport type { ReactNode } from \"react\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport styles from \"./Chip.module.css\";\n\nexport interface ChipProps {\n children: ReactNode;\n className?: string;\n}\n\n/** A small, self-contained value — an answer given, a count, a tag. */\nexport function Chip({ children, className }: ChipProps) {\n const still = prefersReducedMotion();\n return (\n <motion.span\n className={[styles.chip, className ?? \"\"].filter(Boolean).join(\" \")}\n initial={still ? { opacity: 0 } : { opacity: 0, scale: 0.85 }}\n animate={still ? { opacity: 1 } : { opacity: 1, scale: 1 }}\n transition={still ? { duration: 0.15 } : { type: \"spring\", stiffness: 500, damping: 30 }}\n >\n {children}\n </motion.span>\n );\n}\n","/* ─────────────────────────────────────────────\n QuestionCard\n\n One question in three states, and the card morphs between them. Everything\n here is a token — the original was built on another product's palette, and\n the point of bringing it across was that it stops being that product's.\n ───────────────────────────────────────────── */\n\n/* `box-sizing` is stated on every box here that has padding and a width.\n This package has no global reset — a reset in a CSS module leaks into the\n host's page — so each rule has to say it. Left off `.active`, its 8px of\n padding was added *outside* a 100% width and the card clipped 16px of every\n row and the Next button with it. */\n\n.item {\n box-sizing: border-box;\n position: relative;\n width: 100%;\n border-radius: var(--ick-question-radius-card);\n /* Never shrink. The list around it is a flex column, and without this every\n card is squashed the moment one of them is tall. */\n flex-shrink: 0;\n /* Clips the counter-scaled content to the morphing box, so growing reads as\n a reveal rather than as content spilling out of a box that has not caught\n up with it. */\n overflow: hidden;\n}\n\n.item[data-card] {\n background: var(--ick-question-card);\n box-shadow: var(--ick-shadow-float);\n}\n\n/* ── Upcoming ───────────────────────────────────────────────────────────── */\n\n.upcoming {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--ick-space-4);\n /* The card's own column on both sides, because this row *is* the card rather\n than a row inside one: there is no inner padding to make up the difference,\n so the badge takes it here. It was 8 on the right against 16 on the left —\n the pencil sat half a column nearer its edge than the badge sat to its\n own, which is the sort of four-pixel thing that reads as wrong long before\n anybody can say why. */\n padding: var(--ick-space-3) var(--ick-question-pad);\n width: 100%;\n white-space: nowrap;\n overflow: hidden;\n}\n\n\n/* The badge's own line box, which is the rule `.title` already states: a badge\n is 24 tall with `line-height: 1`, and text beside it inherits whatever line\n height the type gives it. Two line boxes of different heights, both centred,\n land half a pixel apart — measured, the label sat at 86.5 against the badge's\n 87. Same box, same centre, and nothing to re-guess when the type changes. */\n.upcomingLabel {\n font-size: var(--ick-text-md);\n line-height: var(--ick-badge-size);\n color: var(--ick-ink-soft);\n}\n\n/* ── Collapsed ──────────────────────────────────────────────────────────── */\n\n.collapsed {\n all: unset;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: space-between;\n flex-wrap: wrap;\n row-gap: var(--ick-space-4);\n gap: var(--ick-space-4);\n width: 100%;\n padding: var(--ick-space-4) var(--ick-question-pad);\n font-family: var(--ick-font-sans);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition: background var(--ick-duration-fast) ease;\n}\n\n.collapsed:hover {\n background: var(--ick-surface-hover);\n}\n\n.collapsed:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n.static {\n cursor: default;\n}\n\n.collapsedLabel {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--ick-space-4);\n min-width: 0;\n}\n\n/* The badge's line box. See the note over `.upcomingLabel`. */\n.collapsedTitle {\n font-size: var(--ick-text-md);\n line-height: var(--ick-badge-size);\n color: var(--ick-ink);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.collapsedAnswer {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--ick-space-2);\n flex: 1;\n min-width: 0;\n}\n\n.pencil {\n flex-shrink: 0;\n margin-left: var(--ick-space-2);\n color: var(--ick-ink-faint);\n}\n\n.collapsed:hover .pencil {\n color: var(--ick-ink);\n}\n\n/* ── Active ─────────────────────────────────────────────────────────────── */\n\n.active {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-5);\n width: 100%;\n padding: var(--ick-space-4);\n}\n\n.header {\n box-sizing: border-box;\n display: flex;\n align-items: flex-start;\n gap: var(--ick-space-4);\n /* 8 here plus the card's own 8 puts this badge on the same vertical line\n as the badges in the rows below it. See the note over `.upcoming`. */\n padding: var(--ick-space-2) 0 0 var(--ick-space-4);\n}\n\n.headerText {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n}\n\n/* The badge is 24 tall and this line was 16, both top-aligned — which puts the\n number four pixels below the middle of the question it belongs to. Measured,\n because at four pixels it reads as wrong long before anybody can say why.\n\n The line box takes the badge's height rather than the badge taking a margin:\n a margin is a guess that has to be re-guessed whenever the type changes, and\n this way the two boxes are the same box. It is also what `.fieldLabel` has\n always done, which is why field rows were the only ones already right.\n\n On the first line, deliberately. A question long enough to wrap keeps its\n badge on the line it starts on, which is where a number belongs. */\n.title {\n margin: 0;\n font-size: var(--ick-text-md);\n font-weight: var(--ick-weight-medium);\n line-height: var(--ick-badge-size);\n color: var(--ick-ink);\n}\n\n.subtitle {\n margin: var(--ick-space-1) 0 0;\n font-size: var(--ick-text-sm);\n line-height: 1.45;\n color: var(--ick-ink-soft);\n}\n\n.rows {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-3);\n width: 100%;\n}\n\n.footer {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding-top: var(--ick-space-2);\n}\n\n/* ── The badge ──────────────────────────────────────────────────────────── */\n\n.badge {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--ick-badge-size);\n height: var(--ick-badge-size);\n border-radius: var(--ick-question-radius-badge);\n background: var(--ick-badge-fill);\n color: var(--ick-ink);\n font-family: var(--ick-font-sans);\n font-size: var(--ick-text-sm);\n line-height: 1;\n transition:\n background var(--ick-duration-fast) ease,\n color var(--ick-duration-fast) ease;\n}\n\n.badge[data-on-card] {\n background: var(--ick-badge-fill-on-card);\n}\n\n/* The signature colour, doing the one job an accent is for: saying which one. */\n.badge[data-selected] {\n background: var(--ick-marker);\n color: var(--ick-marker-ink);\n}\n\n/* Except on a row already wearing it, where the accent at full strength is a\n shade off the tint under it — two yellows that close are not a badge on a\n row, they are one yellow with a rounded hole in it. Paper was the first\n answer and it was no better: pale on pale. This is the marker taken down\n towards ink, so the badge is still the accent and still separates. */\n.option[data-selected] .badge[data-selected] {\n background: var(--ick-marker-deep);\n color: var(--ick-marker-deep-ink);\n}\n\n/* ── Option and field rows ──────────────────────────────────────────────── */\n\n.option {\n all: unset;\n box-sizing: border-box;\n display: flex;\n align-items: flex-start;\n gap: var(--ick-space-4);\n width: 100%;\n padding: var(--ick-space-4);\n border-radius: var(--ick-question-radius-row);\n background: var(--ick-question-row);\n font-family: var(--ick-font-sans);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition: background var(--ick-duration-fast) ease;\n}\n\n.option:hover {\n background: var(--ick-question-row-hover);\n}\n\n.option[data-selected],\n.option[data-selected]:hover {\n background: var(--ick-question-row-selected);\n}\n\n/* `:focus-within` only on the row that has something inside it to focus.\n Unqualified it caught the option button as well, and a button *is* focused\n by a mouse click — so picking one drew a focus ring around it, which is the\n exact thing `:focus-visible` exists to avoid. The \"something else\" row is a\n label around an input: the label never takes focus itself, so it has no\n `:focus-visible` of its own to use. */\n.option:focus-visible,\n.option[data-other]:focus-within {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n.option[data-other] {\n cursor: text;\n}\n\n/* No padding: the 2px here was nudging the title towards the badge's middle\n and landing two pixels short. The line box does it exactly now. */\n.optionBody {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n/* Same again, two pixels' worth. See the note over `.title`. */\n.optionTitle {\n font-size: var(--ick-text-md);\n font-weight: var(--ick-weight-medium);\n line-height: var(--ick-badge-size);\n color: var(--ick-ink);\n}\n\n.optionDescription {\n margin-top: var(--ick-space-1);\n font-size: var(--ick-text-sm);\n line-height: 1.45;\n color: var(--ick-ink-soft);\n}\n\n.otherInput {\n all: unset;\n width: 100%;\n font-family: var(--ick-font-sans);\n /* Not `--ick-text-md`: this is text somebody types into, and on a touch\n device that is a different size. See `--ick-field-size`. */\n font-size: var(--ick-field-size);\n color: var(--ick-ink);\n caret-color: var(--ick-ink);\n}\n\n.otherInput::placeholder {\n color: var(--ick-ink-faint);\n}\n\n.field {\n box-sizing: border-box;\n display: flex;\n align-items: flex-start;\n gap: var(--ick-space-4);\n width: 100%;\n padding: var(--ick-space-4);\n border-radius: var(--ick-question-radius-row);\n background: var(--ick-question-row);\n cursor: text;\n transition: box-shadow var(--ick-duration-fast) ease;\n}\n\n/* The row lights up rather than the input, because the row is what somebody\n is looking at — the field inside it has no border of its own to notice.\n\n An inset shadow rather than a border: a border is part of the box, and a\n 1px one on the field rows and none on the option rows put the two kinds of\n badge a pixel apart. This paints the same edge and takes up no room. */\n.field[data-focused] {\n box-shadow: inset 0 0 0 1px var(--ick-question-row-focus);\n}\n\n.fieldBody {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.fieldLabel {\n display: flex;\n align-items: center;\n height: var(--ick-badge-size);\n font-size: var(--ick-text-sm);\n font-weight: var(--ick-weight-medium);\n color: var(--ick-ink);\n}\n\n.fieldInput {\n all: unset;\n width: 100%;\n font-family: var(--ick-font-sans);\n /* Not `--ick-text-md`: this is text somebody types into, and on a touch\n device that is a different size. See `--ick-field-size`. */\n font-size: var(--ick-field-size);\n color: var(--ick-ink);\n caret-color: var(--ick-ink);\n}\n\n.fieldInput::placeholder {\n color: var(--ick-ink-faint);\n}\n\n/* The card box, for a shell standing on its own rather than inside a\n QuestionCard — which brings its own, and would otherwise nest two. */\n.shellCard {\n border-radius: var(--ick-question-radius-card);\n background: var(--ick-question-card);\n box-shadow: var(--ick-shadow-float);\n}\n","\"use client\";\n\nimport {\n forwardRef,\n useId,\n useState,\n type ComponentPropsWithoutRef,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { motion } from \"motion/react\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport styles from \"./QuestionCard.module.css\";\n\n/* ─────────────────────────────────────────────\n The parts a QuestionCard is built from.\n\n Exported, so a fourth kind of question is a composition rather than a fork.\n The kit ships three shapes; a product that needs a fifth should not have to\n reimplement the badge, the row, the focus behaviour and the tokens to get\n there.\n\n Being public API is what decides how they look from outside: named for what\n they are rather than for where they sit (`QuestionOptionRow`, not\n `OptionCard`), each taking a `className` and the DOM props of the element it\n ends in, and each forwarding its ref to the thing you would actually want\n one for — the input, in the two rows that have one.\n ───────────────────────────────────────────── */\n\n/* ── The badge ──────────────────────────────────────────────────────────── */\n\nexport interface QuestionBadgeProps extends Omit<HTMLAttributes<HTMLSpanElement>, \"children\"> {\n /** The number or the letter. */\n children: ReactNode;\n /** Wearing the accent, because this is the one being answered or chosen. */\n selected?: boolean;\n /** Sitting on a raised card rather than on the group's own surface. */\n onCard?: boolean;\n}\n\n/** The 24px square carrying a number or a letter. */\nexport const QuestionBadge = forwardRef<HTMLSpanElement, QuestionBadgeProps>(\n function QuestionBadge({ children, selected, onCard, className, ...rest }, ref) {\n return (\n <span\n ref={ref}\n className={[styles.badge, className ?? \"\"].filter(Boolean).join(\" \")}\n /* Hidden from assistive technology, because the letter is a visual\n index and not information — it exists so somebody can say \"the third\n one\". Left in the tree it joins the accessible name of whatever\n contains it, and a field called \"a Their name\" is worse than one\n called \"Their name\".\n\n Said before the spread, so somebody using the badge for something\n that *is* information can pass `aria-hidden={false}`. */\n aria-hidden=\"true\"\n data-selected={selected || undefined}\n data-on-card={(!selected && onCard) || undefined}\n {...rest}\n >\n {children}\n </span>\n );\n }\n);\n\n/* ── The option row ─────────────────────────────────────────────────────── */\n\n/* Motion declares its own `onAnimationStart` and drag handlers with different\n shapes to the DOM's, so the DOM's are dropped rather than fought with. */\ntype OptionBase = Omit<\n ComponentPropsWithoutRef<\"button\">,\n \"title\" | \"onAnimationStart\" | \"onAnimationEnd\" | \"onDrag\" | \"onDragStart\" | \"onDragEnd\"\n>;\n\nexport interface QuestionOptionRowProps extends OptionBase {\n /** The a/b/c badge. Left out, the row starts at its title. */\n letter?: ReactNode;\n /** Wins the name from the button's own `title` attribute, which is a tooltip. */\n title: ReactNode;\n description?: ReactNode;\n selected?: boolean;\n}\n\n/** One option, in a single- or multi-select question. */\nexport const QuestionOptionRow = forwardRef<HTMLButtonElement, QuestionOptionRowProps>(\n function QuestionOptionRow(\n { letter, title, description, selected = false, className, ...rest },\n ref\n ) {\n const still = prefersReducedMotion();\n return (\n <motion.button\n ref={ref}\n type=\"button\"\n className={[styles.option, className ?? \"\"].filter(Boolean).join(\" \")}\n data-selected={selected || undefined}\n /* A toggle, said plainly. Not a radio: a single-select here commits and\n moves on, so it behaves like a button that picks rather than like one\n of a set you arrow between. */\n aria-pressed={selected}\n whileTap={still ? undefined : { scale: 0.99 }}\n {...rest}\n >\n {letter !== undefined && <QuestionBadge selected={selected}>{letter}</QuestionBadge>}\n <span className={styles.optionBody}>\n <span className={styles.optionTitle}>{title}</span>\n {description && <span className={styles.optionDescription}>{description}</span>}\n </span>\n </motion.button>\n );\n }\n);\n\n/* ── The field row ──────────────────────────────────────────────────────── */\n\n/** `className` styles the row; everything else lands on the input. */\ntype FieldBase = Omit<ComponentPropsWithoutRef<\"input\">, \"value\" | \"onChange\" | \"className\">;\n\nexport interface QuestionFieldRowProps extends FieldBase {\n /** The a/b/c badge. Left out, the row starts at its label. */\n letter?: ReactNode;\n label: ReactNode;\n value: string;\n /** The value, not the event — the row exists to be typed into. */\n onChange: (value: string) => void;\n /** Enter, which in a question nearly always means \"the next one\", not \"send\". */\n onEnter?: () => void;\n /** On the row. Every other prop goes to the input. */\n className?: string;\n}\n\n/** A free-text row: letter, label, and the field itself. */\nexport const QuestionFieldRow = forwardRef<HTMLInputElement, QuestionFieldRowProps>(\n function QuestionFieldRow(\n { letter, label, value, onChange, onEnter, className, id, onFocus, onBlur, onKeyDown, ...rest },\n ref\n ) {\n const [focused, setFocused] = useState(false);\n /* React's own, rather than one built out of the letter and the label. Two\n cards on a page can hold the same label, and two elements sharing an id\n means the label focuses the wrong input. */\n const auto = useId();\n const inputId = id ?? auto;\n\n return (\n /* A label, so the whole row focuses the input — which is what the\n original achieved with a click handler on a div, and this does without\n asking a pointer to be involved. */\n <label\n className={[styles.field, className ?? \"\"].filter(Boolean).join(\" \")}\n data-focused={focused || undefined}\n htmlFor={inputId}\n >\n {letter !== undefined && <QuestionBadge selected={focused}>{letter}</QuestionBadge>}\n <span className={styles.fieldBody}>\n <span className={styles.fieldLabel}>{label}</span>\n <input\n id={inputId}\n ref={ref}\n type=\"text\"\n className={styles.fieldInput}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n onFocus={(event) => {\n setFocused(true);\n onFocus?.(event);\n }}\n onBlur={(event) => {\n setFocused(false);\n onBlur?.(event);\n }}\n onKeyDown={(event) => {\n /* Theirs first: a handler that calls preventDefault is saying it\n has dealt with this key, and Enter is the one somebody is most\n likely to want for themselves. */\n onKeyDown?.(event);\n if (event.key === \"Enter\" && !event.defaultPrevented) {\n event.preventDefault();\n onEnter?.();\n }\n }}\n {...rest}\n />\n </span>\n </label>\n );\n }\n);\n\n/* ── The \"something else\" row ───────────────────────────────────────────── */\n\ntype OtherBase = Omit<\n ComponentPropsWithoutRef<\"input\">,\n \"value\" | \"onChange\" | \"className\" | \"placeholder\"\n>;\n\nexport interface QuestionOtherRowProps extends OtherBase {\n /** The a/b/c badge. Left out, the row starts at the field. */\n letter?: ReactNode;\n value: string;\n /** Also the accessible name: the row has no visible label of its own. */\n placeholder: string;\n onChange: (value: string) => void;\n onEnter?: () => void;\n /** On the row. Every other prop goes to the input. */\n className?: string;\n}\n\n/**\n * The \"something else\" row: reads as one more option, but it is a text field.\n *\n * A label rather than a button, and that is not a detail — an input inside a\n * button is not reliably focusable, and the whole row exists to be typed into.\n */\nexport const QuestionOtherRow = forwardRef<HTMLInputElement, QuestionOtherRowProps>(\n function QuestionOtherRow(\n { letter, value, placeholder, onChange, onEnter, className, onKeyDown, ...rest },\n ref\n ) {\n const filled = value.trim().length > 0;\n return (\n <label\n className={[styles.option, className ?? \"\"].filter(Boolean).join(\" \")}\n data-selected={filled || undefined}\n data-other=\"\"\n >\n {letter !== undefined && <QuestionBadge selected={filled}>{letter}</QuestionBadge>}\n <span className={styles.optionBody}>\n <input\n ref={ref}\n type=\"text\"\n className={styles.otherInput}\n value={value}\n placeholder={placeholder}\n aria-label={placeholder}\n onChange={(event) => onChange(event.target.value)}\n onKeyDown={(event) => {\n onKeyDown?.(event);\n if (event.key === \"Enter\" && !event.defaultPrevented) {\n event.preventDefault();\n onEnter?.();\n }\n }}\n {...rest}\n />\n </span>\n </label>\n );\n }\n);\n\n/* ── The shell they sit in ──────────────────────────────────────────────── */\n\nexport interface QuestionShellProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** The badge over the question. Left out, the header starts at the title. */\n number?: ReactNode;\n title: ReactNode;\n subtitle?: ReactNode;\n /** The rows this question is made of. */\n children: ReactNode;\n /** Right-aligned under the rows — usually the button that commits. */\n footer?: ReactNode;\n /**\n * Paint the card box: background, radius, shadow.\n *\n * On by default, because a shell used on its own is the reason this is\n * exported. `QuestionCard` turns it off — it brings its own box, which is\n * the one that morphs between the three states.\n */\n card?: boolean;\n}\n\n/**\n * A question laid out: header, rows, footer.\n *\n * The scaffolding, so composing a fourth kind of question is a matter of\n * choosing the rows rather than rebuilding the box, the spacing and the type\n * around them out of numbers that were tokens ten minutes ago.\n */\nexport const QuestionShell = forwardRef<HTMLDivElement, QuestionShellProps>(\n function QuestionShell(\n { number, title, subtitle, children, footer, card = true, className, ...rest },\n ref\n ) {\n return (\n <div\n ref={ref}\n className={[styles.active, card ? styles.shellCard : \"\", className ?? \"\"]\n .filter(Boolean)\n .join(\" \")}\n {...rest}\n >\n <div className={styles.header}>\n {number !== undefined && <QuestionBadge selected>{number}</QuestionBadge>}\n <div className={styles.headerText}>\n <p className={styles.title}>{title}</p>\n {subtitle && <p className={styles.subtitle}>{subtitle}</p>}\n </div>\n </div>\n <div className={styles.rows}>{children}</div>\n {footer && <div className={styles.footer}>{footer}</div>}\n </div>\n );\n }\n);\n","\"use client\";\n\nimport { useLayoutEffect, useState, type RefObject } from \"react\";\n\n/**\n * A box's corner in pixels, so Motion can keep it round while it scales it.\n *\n * A `layout` animation does not resize a box — it **scales** one, and a browser\n * scaling a box scales the corner with it. Measured on the question group\n * opening a card: `scaleY` runs from 0.932 to 1 while `border-radius` stays a\n * flat 40px, which paints a 40 × 37 ellipse and eases back to a circle. That is\n * the stretching and squashing everyone can see and nobody can name.\n *\n * Motion has a corrector for exactly this — it rewrites the radius as\n * `x% y%` against the projected box every frame — but it only runs on values\n * **Motion is managing**. A radius that lives in a CSS class is invisible to\n * it, so the whole morph goes uncorrected.\n *\n * So the number is read off the element and handed back through `style`, where\n * Motion can see it. Read rather than hard-coded, because the corner is a\n * token and a host may retune it; `var(--…)` is no use here, since the\n * corrector parses pixels and returns anything else untouched.\n *\n * `undefined` until the element exists, which is one render with the class's\n * own corner and no animation running yet — nothing to see.\n */\nexport function useCorrectedRadius(ref: RefObject<HTMLElement | null>): number | undefined {\n const [radius, setRadius] = useState<number | undefined>(undefined);\n\n useLayoutEffect(() => {\n const element = ref.current;\n if (!element) return;\n /* Before paint, so the first frame is already the corrected value rather\n than the class's and then a swap. */\n const px = Number.parseFloat(getComputedStyle(element).borderTopLeftRadius);\n setRadius((current) => (Number.isFinite(px) && px !== current ? px : current));\n }, [ref]);\n\n return radius;\n}\n","\"use client\";\n\nimport { useEffect, useMemo, useRef, useState, type ReactNode, type RefObject } from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { Pencil } from \"lucide-react\";\nimport { Button } from \"../Button/Button\";\nimport { Chip } from \"../Chip/Chip\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport {\n QuestionBadge,\n QuestionFieldRow,\n QuestionOptionRow,\n QuestionOtherRow,\n QuestionShell,\n} from \"./parts\";\nimport type { Answer, Question, QuestionState } from \"./types\";\nimport { useCorrectedRadius } from \"../radiusCorrection/useCorrectedRadius\";\nimport styles from \"./QuestionCard.module.css\";\n\nexport interface QuestionCardProps {\n question: Question;\n /** Shown in the badge. 1-based. */\n number: number;\n state: QuestionState;\n answer?: Answer;\n onCommit?: (answer: Answer) => void;\n /** Called when somebody asks to change an answer already given. */\n onEdit?: () => void;\n /** Answers can be read but not changed. */\n readOnly?: boolean;\n labels?: Partial<Record<\"next\" | \"none\" | \"edit\", string>>;\n}\n\ntype Labels = Record<\"next\" | \"none\" | \"edit\", string>;\n\nconst DEFAULT_LABELS: Labels = { next: \"Next\", none: \"None of these\", edit: \"Edit answer\" };\n\nconst SPRING = { type: \"spring\", stiffness: 260, damping: 30 } as const;\n\n/** a, b, c … — the option's letter. */\nconst letterFor = (i: number) => String.fromCharCode(97 + i);\n\n/** How long a single-select stays visibly picked before it moves on. */\nconst PICK_SETTLE = 300;\n/** Long enough for the card to have finished growing before focus lands. */\nconst FOCUS_AFTER = 380;\n\n/**\n * What an answer looks like once folded into a row.\n *\n * Two at most, then a count. Three chips of unpredictable width in a row that\n * also holds a title is a row that wraps, and a summary that wraps is not a\n * summary.\n */\nexport function answerChips(question: Question, answer?: Answer): string[] {\n if (!answer) return [];\n\n if (question.type === \"inputs\" && \"values\" in answer) {\n const values = question.fields.map((f) => answer.values?.[f.id]).filter(Boolean) as string[];\n return values.length <= 2 ? values : [...values.slice(0, 2), `+${values.length - 2}`];\n }\n\n if (question.type === \"single\" && \"optionId\" in answer) {\n const option = question.options.find((o) => o.id === answer.optionId);\n return option ? [option.title] : [];\n }\n\n if (question.type === \"multi\" && \"optionIds\" in answer) {\n const picked = question.options.filter((o) => answer.optionIds?.includes(o.id));\n const other = answer.other?.trim();\n // Several short ones read better joined than stacked.\n if (!other && picked.length > 1 && picked.every((o) => o.short)) {\n return [picked.map((o) => o.short).join(\", \")];\n }\n const labels = [...picked.map((o) => o.title), ...(other ? [other] : [])];\n return labels.length <= 2 ? labels : [...labels.slice(0, 2), `+${labels.length - 2}`];\n }\n\n return [];\n}\n\n/** The body of a question being answered. */\nfunction ActiveBody({\n question,\n number,\n answer,\n onCommit,\n labels,\n}: {\n question: Question;\n number: number;\n answer?: Answer;\n onCommit?: (answer: Answer) => void;\n labels: Labels;\n}) {\n const [values, setValues] = useState<Record<string, string>>(() =>\n answer && \"values\" in answer ? { ...answer.values } : {}\n );\n const [ids, setIds] = useState<string[]>(() =>\n answer && \"optionIds\" in answer ? [...(answer.optionIds ?? [])] : []\n );\n const [other, setOther] = useState(() => (answer && \"optionIds\" in answer ? answer.other ?? \"\" : \"\"));\n const [picked, setPicked] = useState<string | null>(\n answer && \"optionId\" in answer ? answer.optionId : null\n );\n\n const firstField = useRef<HTMLInputElement | null>(null);\n /* Built once per question rather than filled in during the map. Writing to a\n ref while rendering is the kind of thing that works until React renders\n twice, and it renders twice in development on purpose. */\n const fields = useMemo<RefObject<HTMLInputElement | null>[]>(\n () => (question.type === \"inputs\" ? question.fields.map(() => ({ current: null })) : []),\n [question]\n );\n\n useEffect(() => {\n if (question.type !== \"inputs\") return;\n // After the card has finished growing, or focus lands mid-animation and\n // the browser scrolls to where the field was rather than where it is.\n const t = setTimeout(\n () => firstField.current?.focus({ preventScroll: true }),\n prefersReducedMotion() ? 0 : FOCUS_AFTER\n );\n return () => clearTimeout(t);\n }, [question.type]);\n\n const complete = useMemo(\n () =>\n question.type !== \"inputs\" ||\n question.fields.every((f) => f.optional || (values[f.id] ?? \"\").trim()),\n [question, values]\n );\n\n /* The same in all three branches. An object rather than a local component —\n a component declared inside a render is a new type every render, and the\n rows would remount on every keystroke, taking the focus with them. The\n card around this one is the one that morphs, so this shell draws no box. */\n const shell = {\n number,\n title: question.title,\n subtitle: question.subtitle,\n card: false,\n } as const;\n\n if (question.type === \"inputs\") {\n const commit = () => complete && onCommit?.({ values });\n return (\n <QuestionShell\n {...shell}\n footer={\n <Button variant=\"secondary\" size=\"m\" disabled={!complete} onClick={commit}>\n {labels.next}\n </Button>\n }\n >\n {question.fields.map((field, i) => (\n <QuestionFieldRow\n key={field.id}\n letter={letterFor(i)}\n label={field.label}\n placeholder={field.placeholder}\n value={values[field.id] ?? \"\"}\n onChange={(v) => setValues((all) => ({ ...all, [field.id]: v }))}\n ref={i === 0 ? firstField : fields[i]}\n /* Enter moves on rather than submitting the lot: the next field\n is nearly always what somebody means by it. */\n onEnter={() => {\n const next = i === 0 ? fields[1]?.current : fields[i + 1]?.current;\n if (next) next.focus();\n else commit();\n }}\n />\n ))}\n </QuestionShell>\n );\n }\n\n if (question.type === \"single\") {\n return (\n <QuestionShell {...shell}>\n {question.options.map((option, i) => (\n <QuestionOptionRow\n key={option.id}\n letter={letterFor(i)}\n title={option.title}\n description={option.description}\n selected={picked === option.id}\n /* Picked, then a beat, then on. Committing instantly means the\n card is gone before anyone sees which one they chose. */\n onClick={() => {\n setPicked(option.id);\n setTimeout(() => onCommit?.({ optionId: option.id }), PICK_SETTLE);\n }}\n />\n ))}\n </QuestionShell>\n );\n }\n\n const trimmed = other.trim();\n const empty = ids.length === 0 && !trimmed;\n const commitMulti = () => {\n if (!question.allowEmpty && empty) return;\n onCommit?.({ optionIds: ids, ...(trimmed ? { other: trimmed } : {}) });\n };\n\n return (\n <QuestionShell\n {...shell}\n footer={\n <Button\n variant=\"secondary\"\n size=\"m\"\n disabled={!question.allowEmpty && empty}\n onClick={commitMulti}\n >\n {question.allowEmpty && empty ? labels.none : labels.next}\n </Button>\n }\n >\n {question.options.map((option, i) => (\n <QuestionOptionRow\n key={option.id}\n letter={letterFor(i)}\n title={option.title}\n description={option.description}\n selected={ids.includes(option.id)}\n onClick={() =>\n setIds((all) =>\n all.includes(option.id) ? all.filter((x) => x !== option.id) : [...all, option.id]\n )\n }\n />\n ))}\n {question.allowOther && (\n <QuestionOtherRow\n letter={letterFor(question.options.length)}\n value={other}\n placeholder={question.otherPlaceholder ?? \"Something else\"}\n onChange={setOther}\n onEnter={commitMulti}\n />\n )}\n </QuestionShell>\n );\n}\n\n/**\n * One question, in whichever of its three states it is in.\n *\n * The card morphs between them rather than swapping: the box animates with\n * FLIP, and the content inside counter-scales so text keeps its real size\n * instead of stretching on the way. `overflow: hidden` is what makes growing\n * read as a reveal rather than as content spilling out of a box that has not\n * caught up.\n */\nexport function QuestionCard({\n question,\n number,\n state,\n answer,\n onCommit,\n onEdit,\n readOnly = false,\n labels,\n}: QuestionCardProps) {\n const label = { ...DEFAULT_LABELS, ...labels };\n const isCard = state !== \"upcoming\";\n const still = prefersReducedMotion();\n const cardRef = useRef<HTMLDivElement>(null);\n const cardRadius = useCorrectedRadius(cardRef);\n\n let body: ReactNode;\n if (state === \"active\") {\n body = (\n <ActiveBody\n question={question}\n number={number}\n answer={answer}\n onCommit={onCommit}\n labels={label}\n />\n );\n } else if (state === \"collapsed\") {\n const chips = answerChips(question, answer);\n const summary = (\n <>\n <span className={styles.collapsedLabel}>\n <QuestionBadge onCard>{number}</QuestionBadge>\n <span className={styles.collapsedTitle}>{question.shortTitle}</span>\n </span>\n <span className={styles.collapsedAnswer}>\n {chips.map((chip, i) => (\n <Chip key={`${chip}-${i}`}>{chip}</Chip>\n ))}\n {!readOnly && <Pencil className={styles.pencil} size={14} aria-hidden />}\n </span>\n </>\n );\n\n /* One control for the whole row rather than a click handler on a div with\n a button inside it. The original could be clicked but not tabbed to, and\n a nested button inside a clickable row is a second tab stop for the same\n action. Read-only, it is not a control at all. */\n body = readOnly ? (\n <div className={`${styles.collapsed} ${styles.static}`}>{summary}</div>\n ) : (\n <button\n type=\"button\"\n className={styles.collapsed}\n onClick={onEdit}\n aria-label={`${label.edit}: ${question.shortTitle}`}\n >\n {summary}\n </button>\n );\n } else {\n body = (\n <div className={styles.upcoming}>\n {/* The number, not a mark meaning \"one of these\" — the row is question\n three whether or not it has been reached yet, and saying so is what\n makes a list of them read as a list. White, because an upcoming\n card has no card under it: the badge is what stands off the\n group's own surface. */}\n <QuestionBadge>{number}</QuestionBadge>\n <span className={styles.upcomingLabel}>{question.shortTitle}</span>\n </div>\n );\n }\n\n return (\n <motion.div\n ref={cardRef}\n layout={!still}\n data-active={state === \"active\" || undefined}\n className={styles.item}\n data-card={isCard || undefined}\n transition={{ layout: SPRING }}\n /* Handed to Motion so it can keep the corner round while it scales the\n box. This one morphs between a tall card and a 40px row, so it is the\n one that scales furthest. See `useCorrectedRadius`. */\n style={{ borderRadius: cardRadius }}\n >\n {/* The wrapper resizes with FLIP, which scales everything inside it.\n `layout=\"position\"` makes Motion counter-scale the content, so text\n and controls keep their real size instead of stretching. `popLayout`\n takes the outgoing state out of flow so it is not squashed on its\n way out. */}\n <AnimatePresence mode=\"popLayout\" initial={false}>\n <motion.div\n key={state}\n layout={still ? false : \"position\"}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1, transition: { duration: 0.2, delay: still ? 0 : 0.12 } }}\n exit={{ opacity: 0, transition: { duration: 0.12 } }}\n >\n {body}\n </motion.div>\n </AnimatePresence>\n </motion.div>\n );\n}\n","/* ─────────────────────────────────────────────\n QuestionGroup\n ───────────────────────────────────────────── */\n\n.group {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-5);\n\n /* A section's name, not an aside's label. See `Sources` for the same note. */\n --ick-disclosure-label: var(--ick-ink);\n --ick-disclosure-weight: var(--ick-weight-medium);\n /* And a name stands over the words it names, not over the cards' edges: the\n title starts on the column the badges and the numbers start on, and the\n chevron ends on the column the rows' pencils end on. */\n --ick-disclosure-inset: var(--ick-question-pad);\n /* No wash. It is a rounded box the width of the header, and there is no box\n under it for that shape to agree with — it read as a stray highlight. */\n --ick-disclosure-hover: transparent;\n width: 100%;\n padding: var(--ick-space-6);\n border-radius: var(--ick-question-radius-group);\n background: var(--ick-question-surface);\n font-family: var(--ick-font-sans);\n /* The ground is the edge of this thing, so nothing paints past it.\n `AnimatePresence mode=\"popLayout\"` takes the leaving body out of flow so\n the arriving one can have its place immediately — which means that while\n the ground shrinks, the old list stays pinned where it was and hangs out\n of the bottom, three rows of it, over whatever is underneath. Card shadows\n still have room: the ground's own 16 of padding is more than the float\n shadow reaches. */\n overflow: hidden;\n}\n\n/* What the header opens. One slot: the questions, or the one row they fold\n into. The header above it does not move either way, which is what makes the\n swap read as content changing rather than a control changing places. */\n.body {\n box-sizing: border-box;\n position: relative;\n width: 100%;\n}\n\n/* The negative margin and matching padding keep the cards' shadows out of the\n clip that the layout animation puts on this box. */\n.list {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-5);\n margin: 0 calc(var(--ick-space-4) * -1);\n padding: 0 var(--ick-space-4);\n}\n\n/* One card's slot in the list, and the thing the row variants are applied to.\n It exists because a card arriving has to be a Motion child of the list for\n the stagger to reach it, and `QuestionCard` is not one.\n\n `flex-shrink: 0` for the reason `.item` carries it: this is a flex column,\n and without it every card is squashed the moment one of them is tall. */\n.row {\n box-sizing: border-box;\n flex-shrink: 0;\n width: 100%;\n}\n\n/* Motion animates a row's `y` as an independent transform, and an independent\n transform does not promote the element on its own — sampled through a whole\n fold, every row read `will-change: auto`. Motion's own guidance is to name\n the properties being animated and then take the hint away again, because a\n permanent one is a permanent layer.\n\n Hence the attribute rather than a rule on `.row`: it goes on when the fold\n starts and comes off when the bodies have settled, so the layers exist for\n the three hundred milliseconds that need them. */\n.group[data-moving] .row,\n.group[data-moving] .summary {\n will-change: transform, opacity;\n}\n\n/* ── Folded ─────────────────────────────────────────────────────────────── */\n\n/* Folded: what the step covered, on one card. Not a control any more — the\n header above it is. */\n.summary {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--ick-space-4);\n width: 100%;\n /* On the card's column, like every other row that is a card: the count rides\n where a badge rides, and the line ends where a pencil ends. */\n padding: var(--ick-space-4) var(--ick-question-pad);\n border-radius: var(--ick-question-radius-card);\n background: var(--ick-question-card);\n box-shadow: var(--ick-shadow-float);\n font-family: var(--ick-font-sans);\n}\n\n/* A badge, not a chip. It stands in the badge column on a card, so it is the\n same box the numbers stand in one state over: same height, same corner, same\n fill on a card, same type. Only the width differs, because this one holds\n words rather than a numeral.\n\n It had been 2px of padding, a pill corner and the smallest type in the kit,\n which made the single badge on the folded row about half the size of every\n badge on the open one — and the two states are a fold apart. */\n.count {\n box-sizing: border-box;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n height: var(--ick-badge-size);\n padding: 0 var(--ick-space-4);\n border-radius: var(--ick-question-radius-badge);\n background: var(--ick-badge-fill-on-card);\n font-size: var(--ick-text-sm);\n line-height: 1;\n color: var(--ick-ink);\n}\n\n/* One line, ellipsised. A summary that wraps is not a summary. */\n.summaryList {\n flex: 1;\n min-width: 0;\n font-size: var(--ick-text-sm);\n /* The count beside it is a badge, so this takes the badge's line box — the\n same rule the card's own rows follow. Measured with a `Range` over the\n text itself, it sat at 86.5 against the count's 87: two line boxes of\n different heights, both centred, land half a pixel apart. */\n line-height: var(--ick-badge-size);\n color: var(--ick-ink-soft);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n","\"use client\";\n\nimport { useId, useRef, useState, type ReactNode } from \"react\";\nimport { AnimatePresence, LayoutGroup, motion } from \"motion/react\";\nimport { QuestionCard } from \"../QuestionCard/QuestionCard\";\nimport { DisclosureHeader } from \"../disclosure/DisclosureHeader\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport type { Answer, Question } from \"../QuestionCard/types\";\nimport { useCorrectedRadius } from \"../radiusCorrection/useCorrectedRadius\";\nimport styles from \"./QuestionGroup.module.css\";\n\n/** Below this, folding saves less room than the summary row costs. */\nexport const FOLDABLE_FROM = 3;\n\n/**\n * What the fold is made of, in numbers.\n *\n * Springs for anything that moves, tweens for opacity alone.\n *\n * That split is not a preference. A spring is a description of *travel* —\n * where a thing is going and how it arrives — and opacity has nowhere to\n * travel: it is bounded at 0 and 1, so a spring with any bounce in it\n * overshoots into a clamp and spends the overshoot sitting still. Position and\n * size have no such ceiling, which is exactly why they are worth springing.\n *\n * `visualDuration` rather than stiffness and damping, for the same reason\n * throughout: the two of them describe one spring between them without either\n * answering \"how long is this\", which is the only question anybody tuning it\n * is asking. Motion solves the spring for the duration you name.\n */\nexport interface FoldMotion {\n /** How long the ground looks like it takes to resize, in seconds. */\n visualDuration: number;\n /** The ground's overshoot, 0–1. */\n bounce: number;\n /** How long a row takes to arrive, in seconds. */\n rowDuration: number;\n /** A row's overshoot, 0–1. Enough to read as arriving, not as bouncing. */\n rowBounce: number;\n /** How far above its place a row starts, in pixels. */\n rowOffset: number;\n /** Between one row and the next, in seconds. */\n stagger: number;\n /** A row's fade, in seconds. Tween: see the note above. */\n fadeIn: number;\n /** And the fade of one leaving. */\n fadeOut: number;\n /**\n * How long the arriving body waits, in seconds.\n *\n * Not a stylistic pause — it is what stops the two bodies from being in the\n * same pixels at once. They are anchored to the same top edge, so the folded\n * row and the first card always want that same band; crossfading them put\n * two different sentences on top of each other at half opacity, which is\n * mush rather than motion. At `fadeOut` the one is gone before the other\n * starts, and the box growing underneath is what carries the eye across.\n */\n fadeInDelay: number;\n}\n\nexport const defaultFoldMotion: FoldMotion = {\n visualDuration: 0.34,\n bounce: 0.1,\n rowDuration: 0.3,\n rowBounce: 0.12,\n rowOffset: -10,\n stagger: 0.045,\n fadeIn: 0.16,\n fadeOut: 0.08,\n fadeInDelay: 0.08,\n};\n\nexport interface QuestionGroupProps {\n /** Distinguishes one group's layout animations from another's. */\n id: string;\n /**\n * What this step is about, at the top of the group.\n *\n * Also the fold control when there is one — which is the point of putting it\n * there. A control at the top does not move when the thing under it opens,\n * so folding stops being two shapes swapping places and becomes a body\n * changing under a header that stays put.\n *\n * Left out on a foldable group, the count stands in, so the control still\n * has a name.\n */\n title?: ReactNode;\n questions: Question[];\n answers: Record<string, Answer | undefined>;\n /** Index of the question being answered, or `null` when none is. */\n activeIndex?: number | null;\n /** Offer to fold the whole group into one row. */\n collapsible?: boolean;\n onCommit?: (questionId: string, answer: Answer) => void;\n onEdit?: (index: number) => void;\n readOnly?: boolean;\n labels?: Partial<Record<\"answers\", string>>;\n /** Overrides for the fold's timing. See `defaultFoldMotion`. */\n foldMotion?: Partial<FoldMotion>;\n className?: string;\n}\n\n/* `hide` went with the pill that used to say it. The header carries the\n section's name in both states now, so there is nothing left to word. */\nconst DEFAULT_LABELS = { answers: \"answers\" };\n\n/**\n * One step of a questionnaire: the surface holding its questions.\n *\n * Once the conversation has moved past it, it folds down to a single row.\n * Not a peek at the list — a peek costs more height than the answers it shows,\n * and a conversation with four half-open steps above it is unreadable.\n */\nexport function QuestionGroup({\n id,\n title,\n questions,\n answers,\n activeIndex = null,\n collapsible = false,\n onCommit,\n onEdit,\n readOnly = false,\n labels,\n foldMotion,\n className,\n}: QuestionGroupProps) {\n const [expanded, setExpanded] = useState(false);\n /* True for the length of one fold, and off again after. See the note over\n `[data-moving]` in the stylesheet. */\n const [moving, setMoving] = useState(false);\n const bodyId = useId();\n const groundRef = useRef<HTMLDivElement>(null);\n const groundRadius = useCorrectedRadius(groundRef);\n const label = { ...DEFAULT_LABELS, ...labels };\n const still = prefersReducedMotion();\n const folded = collapsible && !expanded;\n\n const summary = questions.map((q) => q.shortTitle).join(\" · \");\n\n const beat = { ...defaultFoldMotion, ...foldMotion };\n\n /* The box's own resize. Handed to every element that carries `layout` here,\n so the ground and the things inside it move on one clock — a header\n counter-scaling on a different spring to the box scaling it is a header\n that visibly lags. */\n const resize = still\n ? { duration: 0 }\n : { type: \"spring\" as const, visualDuration: beat.visualDuration, bounce: beat.bounce };\n\n /* What a body does, one row at a time.\n \n This was a crossfade between two blocks: the summary dissolving into the\n list and back. A dissolve is what you reach for when two things are\n unrelated, and these are not — the row and the stack are the same answers\n in two states. It read as the box moving while the content sat there\n bleeding through itself.\n \n Rows arrive instead, each one a little above its place and settling into\n it, one after the next, and leave the same way. The ground follows them\n rather than the other way round. */\n const bodyVariants = {\n /* Both directions stagger the same way round. The conventional mirror —\n last in, first out — is right when a thing is being *dismissed*, because\n it unwinds the way it was built. This is not a dismissal: it is one body\n being replaced by another on the same edge, and the two are the same\n answers. Read forwards both ways, leaving and arriving are one gesture\n passing through the row rather than two gestures meeting in the middle. */\n hidden: { transition: { staggerChildren: still ? 0 : beat.stagger } },\n shown: { transition: { staggerChildren: still ? 0 : beat.stagger } },\n };\n\n /* One spring, both directions. A row leaving used to be a plain tween while a\n row arriving was sprung, which made collapsing a different gesture from\n expanding rather than the same one run backwards. */\n const travel = (fade: number, delay = 0) =>\n still\n ? { duration: 0 }\n : {\n type: \"spring\" as const,\n visualDuration: beat.rowDuration,\n bounce: beat.rowBounce,\n delay,\n /* Opacity is bounded, so it gets the tween. See `FoldMotion`. */\n opacity: { duration: fade, delay },\n };\n\n const rowMotion = {\n hidden: { opacity: 0, y: still ? 0 : beat.rowOffset, transition: travel(beat.fadeOut) },\n shown: {\n opacity: 1,\n y: 0,\n transition: travel(beat.fadeIn, still ? 0 : beat.fadeInDelay),\n },\n };\n\n const bodyMotion = {\n variants: bodyVariants,\n initial: \"hidden\" as const,\n animate: \"shown\" as const,\n exit: \"hidden\" as const,\n /* The arriving body finishing is when the fold is over. Both bodies carry\n this, and the leaving one finishes first — clearing on that took the\n layers away at 165ms with the rows still travelling until 300. */\n onAnimationComplete: (definition: unknown) => {\n if (definition === \"shown\") setMoving(false);\n },\n };\n\n return (\n <LayoutGroup id={id}>\n <motion.div\n ref={groundRef}\n layout={!still}\n transition={resize}\n className={[styles.group, className ?? \"\"].filter(Boolean).join(\" \")}\n data-moving={moving || undefined}\n /* Handed to Motion so it can keep the corner round while it scales the\n box. See `useCorrectedRadius`. */\n style={{ borderRadius: groundRadius }}\n >\n {/* The section's name, at the top, and the fold control when there is\n one. At the top because a control that does not move is the whole\n trick: folding stops being two shapes swapping places and becomes a\n body changing under a header that stays put.\n \n Without a title a foldable group falls back to the count, so the\n control still has a name; without either there is no header at all\n and the group is the list, as it was. */}\n {(title || collapsible) && (\n /* `layout=\"position\"` and not decoration.\n\n The ground animates its height the only way Motion animates a\n size: it sets the new one in the DOM and scales the box back. So\n every child that is not itself a layout child rides that scale,\n and this one is a line of type. Measured over one open: the header\n was squashed to 11.67px of its 23 in a single frame and stretched\n back over the next 450ms, the title with it — which is exactly\n what \"it flickers and jumps around\" looks like when you slow it\n down. `position` is the right half of it: the header does not\n move, so all it needs is the scale taken back off. */\n <motion.div layout={still ? false : \"position\"} transition={resize}>\n <DisclosureHeader\n open={!folded}\n onToggle={\n collapsible\n ? () => {\n setMoving(true);\n setExpanded((open) => !open);\n }\n : undefined\n }\n controls={bodyId}\n label={title ?? `${questions.length} ${label.answers}`}\n />\n </motion.div>\n )}\n\n <motion.div\n id={bodyId}\n layout={still ? false : \"position\"}\n transition={resize}\n className={styles.body}\n >\n <AnimatePresence initial={false} mode=\"popLayout\">\n {folded ? (\n /* No `layout` on either body, deliberately. `popLayout` takes\n the leaving one out of flow, and `layout` then animates it\n toward wherever it resolves to once absolute — measured, the\n summary left its own row and travelled 67px down the moment it\n popped, so it faded out somewhere it had never been. Scale\n correction is what these needed and they inherit it from the\n body above them.\n\n Each body is a list of rows even when it holds one, so that\n both sides of the fold arrive by the same rule. */\n <motion.div key=\"summary\" {...bodyMotion}>\n <motion.div variants={rowMotion} className={styles.summary}>\n <span className={styles.count}>\n {questions.length} {label.answers}\n </span>\n <span className={styles.summaryList}>{summary}</span>\n </motion.div>\n </motion.div>\n ) : (\n <motion.div key=\"list\" className={styles.list} {...bodyMotion}>\n {questions.map((question, i) => (\n <motion.div key={question.id} variants={rowMotion} className={styles.row}>\n <QuestionCard\n question={question}\n number={i + 1}\n state={\n i === activeIndex\n ? \"active\"\n : answers[question.id]\n ? \"collapsed\"\n : \"upcoming\"\n }\n answer={answers[question.id]}\n readOnly={readOnly}\n onCommit={(answer) => onCommit?.(question.id, answer)}\n onEdit={() => onEdit?.(i)}\n />\n </motion.div>\n ))}\n </motion.div>\n )}\n </AnimatePresence>\n </motion.div>\n </motion.div>\n </LayoutGroup>\n );\n}\n","/* ─────────────────────────────────────────────\n SystemMessage\n\n A recessed row in the transcript: the surface talking, rather than either of\n the two people in the conversation. It takes the **ground** for that reason\n — a ground is what things sit on, and a note about the conversation is not a\n thing in it.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.message {\n box-sizing: border-box;\n width: 100%;\n /* The column every card's *content* starts on — which from out here is the\n ground's own padding plus that column, because this row stands where a\n ground stands rather than where a card does. Measured with only the column\n it read 32 against a tool call's 48: a note lined up with the cards' edges\n instead of with the words in them, which is the one alignment rule this\n kit has. */\n padding: var(--ick-space-4)\n calc(var(--ick-nest-ground-pad) + var(--ick-nest-column));\n border-radius: var(--ick-system-radius);\n background: var(--ick-system-surface);\n font-family: var(--ick-font-sans);\n font-size: var(--ick-text-sm);\n /* Prose spacing, not the badge's line box.\n \n The badge's box is the rule for a label standing *beside* a badge, so that\n the two centre on one line. Nothing stands beside this. Taking it anyway\n bought a one-line note the height of a folded row and cost every note that\n wraps — 24 over 13px type is 1.85, which on three lines reads as a poster\n rather than a sentence. A note is prose and wraps like prose; the eight\n above and below is what keeps a one-liner close to a folded row. */\n line-height: 1.5;\n color: var(--ick-system-ink);\n}\n\n/* Something went wrong. The tint is the whole signal, and the sentence beside\n it is what anybody can act on — see the note in the component about why\n there is no icon. */\n.message[data-tone=\"danger\"] {\n background: var(--ick-system-surface-danger);\n color: var(--ick-system-ink-danger);\n}\n","\"use client\";\n\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./SystemMessage.module.css\";\n\n/**\n * Two, not three.\n *\n * `Context` settled this already: an amber in the middle makes somebody learn\n * a scale to read a state they can already read in words. Something either\n * went wrong or it did not.\n */\nexport type SystemTone = \"notice\" | \"danger\";\n\nexport interface SystemMessageProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n children: ReactNode;\n tone?: SystemTone;\n}\n\n/**\n * The conversation itself saying something — not the reader, not the agent.\n *\n * \"The oldest messages are dropping out of the window.\" \"The model changed\n * partway through this answer.\" \"You went offline.\" Things that happened *to*\n * the conversation, which until now had nowhere to be said: `Context` warns\n * that the window is nearly full and then nothing speaks when it fills.\n *\n * **No glyph**, deliberately, and for the reason the approval's shield came\n * off an hour before this was written. Every picture in this kit carries a\n * state the words beside it also carry — queued, running, failed, allowed — so\n * that a reader the picture does not reach loses nothing. An icon here would\n * carry \"something is being announced\", next to a sentence announcing it. The\n * tint says which of the two tones it is and the sentence says the rest, which\n * is the only part anybody can act on.\n *\n * **No dismiss, and no action.** It is a line of the transcript rather than a\n * toast: dismissing one would be editing what happened. A host that needs a\n * button under it composes one, which costs them a line and costs this\n * component a whole API.\n *\n * **No live region either.** The kit has one, shared and written to on a later\n * tick — see `announce`. A second one says everything twice, which is the\n * fault that region exists to have fixed.\n */\nexport function SystemMessage({\n children,\n tone = \"notice\",\n className,\n ...rest\n}: SystemMessageProps) {\n return (\n <div\n className={[styles.message, className ?? \"\"].filter(Boolean).join(\" \")}\n data-tone={tone}\n {...rest}\n >\n {children}\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n ArtifactCard\n\n The kit's usual three: a ground, a card on it, and what the card holds inset\n into that. The ground is not decoration — it is what puts the card's words on\n the answer's column, which every other box in a turn reaches the same way.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n\n/* The ground, which is what puts the card's words on the answer's column —\n see the note in the component. */\n.artifact {\n box-sizing: border-box;\n width: 100%;\n padding: var(--ick-artifact-ground-pad);\n border-radius: var(--ick-artifact-ground-radius);\n background: var(--ick-artifact-ground);\n font-family: var(--ick-font-sans);\n}\n\n/* The whole card is the control, because the whole card is the affordance: a\n window onto something with a way in, rather than a panel with a button\n somewhere on it. */\n.card {\n all: unset;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-4);\n width: 100%;\n padding: var(--ick-nest-pad);\n border-radius: var(--ick-artifact-radius);\n background: var(--ick-artifact-surface);\n box-shadow: var(--ick-shadow-float);\n /* Clips the preview at the cut, which is what makes it a preview. */\n overflow: hidden;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n transform var(--ick-duration-fast) var(--ick-ease),\n box-shadow var(--ick-duration-fast) var(--ick-ease);\n}\n\n/* ── Pointed at, and pressed ────────────────────────────────────────────────\n\n The card moves; its surface does not.\n\n It used to take a grey fill on hover, and the fill was the problem. The card\n sits on a ground that is itself ink at 5%, and the hover fill was ink at\n about 4% of the card — so pointing at it slid the card *towards* the colour\n of the thing it is meant to be lifted off. The nearer it got to being\n pressed, the more it looked merged. A card is a physical metaphor and the\n physical answer is the right one: nearer the reader on hover, down under the\n finger on press, and the surface left alone.\n\n Two pixels, not one. One is the right amount of travel for a button in a\n row; a card the width of the answer needs the shadow to carry it, and the\n shadow only reads if the gap under the card actually opens. */\n.artifact:not([data-open]) .card:hover {\n box-shadow: var(--ick-shadow-float-lift);\n}\n\n/* The travel, only where there is something to hover with. A tap on a phone\n fires a hover that stays, so ungated the card sat two pixels up until you\n touched something else. The shadow stays on touch: it says which card you\n are on without claiming the card moved. */\n@media (hover: hover) and (pointer: fine) {\n .artifact:not([data-open]) .card:hover {\n transform: translateY(-2px);\n }\n}\n\n/* Pressed, including the open one — pressing that closes the pane, so it has\n to answer. Faster than the lift, because a press is not a journey.\n\n Written out twice, and it has to be. A press only ever happens under a\n pointer that is already hovering, so this rule is in a fight with the one\n above it and has to win: `.card:active` alone loses on specificity, four\n to two, and the card stayed lifted under the finger. It measured `-2` where\n it should have measured `+1` — caught by `npm run showcase:artifact`, not by\n looking, because a press that does nothing looks exactly like a press\n arriving a frame late. */\n.artifact:not([data-open]) .card:active,\n.artifact[data-open] .card:active {\n transform: translateY(1px) scale(0.997);\n box-shadow: var(--ick-shadow-float-press);\n transition-duration: var(--ick-duration-press);\n}\n\n.card:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n/* Which one the pane is showing — and it is drawn with nothing.\n\n It had a two-pixel ink ring. Beside a whole pane holding the same document,\n a ring on the card was a second answer to a question already answered, and\n it read as a selected list row rather than as an open one. What says it now\n is the absence above: every other card lifts under the pointer, this one\n stays put, because there is nowhere for it to go. The fact itself is not\n left to the eye — the button carries `aria-expanded`, which is the reading\n the ring was never giving anybody. */\n\n/* The reader has asked for less movement, so the card stops moving. The\n shadow still deepens: it is a change of state, not a journey. */\n@media (prefers-reduced-motion: reduce) {\n .card,\n .artifact:not([data-open]) .card:active,\n .artifact[data-open] .card:active,\n .artifact:not([data-open]) .card:hover {\n transform: none;\n }\n}\n\n.head {\n box-sizing: border-box;\n display: flex;\n align-items: baseline;\n gap: var(--ick-space-4);\n width: 100%;\n min-width: 0;\n /* The column, reached from inside the card the way every row here reaches\n it: the card's own gap plus a row's. */\n padding: var(--ick-space-2) var(--ick-nest-pad) 0;\n}\n\n.title {\n font-size: var(--ick-text-md);\n font-weight: var(--ick-weight-medium);\n color: var(--ick-ink);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Still being written. The same shimmer a disclosure header's pending label\n uses, and for the same reason: the word is the name of the thing, so it\n cannot be hidden or replaced by a spinner. */\n.title[data-writing] {\n background: linear-gradient(\n 90deg,\n var(--ick-ink) 0%,\n var(--ick-ink) 40%,\n var(--ick-ink-faint) 50%,\n var(--ick-ink) 60%,\n var(--ick-ink) 100%\n );\n background-size: 200% 100%;\n -webkit-background-clip: text;\n background-clip: text;\n -webkit-text-fill-color: transparent;\n animation: artifact-sweep 1.8s linear infinite;\n}\n\n@keyframes artifact-sweep {\n from { background-position: 110% 0; }\n to { background-position: -10% 0; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .title[data-writing] {\n animation: none;\n background: none;\n -webkit-text-fill-color: currentColor;\n }\n}\n\n.meta {\n flex-shrink: 0;\n margin-left: auto;\n font-size: var(--ick-text-sm);\n color: var(--ick-ink-faint);\n}\n\n/* ── The window ─────────────────────────────────────────────────────────── */\n\n.preview {\n position: relative;\n box-sizing: border-box;\n width: 100%;\n min-width: 0;\n /* Repointed rather than restyled, the way a tool call moves the code block's\n surface: the preview is a row inset into this card. */\n --ick-md-code-fill: var(--ick-artifact-preview-fill);\n --ick-code-radius: var(--ick-nest-row);\n --ick-code-pad: var(--ick-nest-pad);\n}\n\n/* The cut. A hard edge says the file ends there; a fade says it goes on, which\n is the one thing the card has to get across. */\n.preview[data-more]::after {\n content: \"\";\n position: absolute;\n inset-inline: 0;\n bottom: 0;\n height: var(--ick-artifact-fade);\n border-end-start-radius: var(--ick-nest-row);\n border-end-end-radius: var(--ick-nest-row);\n background: linear-gradient(to bottom, transparent, var(--ick-artifact-preview-fill));\n pointer-events: none;\n}\n\n.text {\n box-sizing: border-box;\n margin: 0;\n padding: var(--ick-nest-pad) var(--ick-code-pad);\n border-radius: var(--ick-nest-row);\n background: var(--ick-artifact-preview-fill);\n font-size: var(--ick-text-sm);\n line-height: 1.5;\n color: var(--ick-ink-soft);\n /* What was written keeps its own line breaks; it is a document, not a\n paragraph somebody typed into a box. */\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n/* Nothing to show yet, which is not the same as nothing to say: the title is\n already there and shimmering. */\n.waiting {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n padding: var(--ick-nest-pad) var(--ick-code-pad);\n border-radius: var(--ick-nest-row);\n background: var(--ick-artifact-preview-fill);\n}\n","\"use client\";\n\nimport { type HTMLAttributes, type ReactNode } from \"react\";\nimport { CodeBlock } from \"../CodeBlock/CodeBlock\";\nimport { Loader } from \"../Loader/Loader\";\nimport styles from \"./ArtifactCard.module.css\";\n\n/**\n * Code, or everything else.\n *\n * Two, because they are drawn differently and nothing else is: code goes\n * through the highlighter, prose keeps its line breaks and does not. An\n * `image` kind can be added the day something needs one — it is a third\n * preview, not a third idea.\n */\nexport type ArtifactKind = \"code\" | \"text\";\n\n/** Being written, or written. A file does not fail the way a call does. */\nexport type ArtifactState = \"writing\" | \"done\";\n\nexport interface ArtifactCardProps\n extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"title\" | \"content\" | \"onSelect\"\n > {\n /** What the pane opens. The card and the pane share nothing else. */\n id: string;\n title: ReactNode;\n /** What it is, in a word or two — \"Training plan\", \"12 lines of Python\". */\n meta?: ReactNode;\n kind?: ArtifactKind;\n /** The fence's language, for `kind=\"code\"`. */\n lang?: string;\n /** What was written. Absent while it is still being written. */\n content?: string;\n state?: ArtifactState;\n /**\n * How much of it the card shows before the cut.\n *\n * The whole point of an artifact is that it is bigger than the answer, so\n * the card is a window rather than the thing. Poured out in full it is not\n * an artifact, it is a long message.\n */\n lines?: number;\n /** Open it. Without one the card is a record rather than a control. */\n onOpen?: (id: string) => void;\n /**\n * True while its pane is the one on screen.\n *\n * Nothing is drawn for it. The pane standing beside the conversation is\n * already the answer, and a ring on the card was a second one — so what\n * marks it is that it no longer lifts under the pointer, and\n * `aria-expanded`, which says it outright to anybody not using a pointer.\n */\n open?: boolean;\n}\n\nconst LINES = 8;\n\n/** The first `lines` of it, and whether there was more. */\nfunction clip(content: string, lines: number) {\n const all = content.split(\"\\n\");\n return { shown: all.slice(0, lines).join(\"\\n\"), more: all.length > lines };\n}\n\n/**\n * What the answer produced, in the transcript.\n *\n * A window onto it and a way in — not the thing itself. Press it and the pane\n * beside the conversation shows the whole of it; the card stays where it is,\n * because it is the line of the transcript that says the artifact was made.\n */\nexport function ArtifactCard({\n id,\n title,\n meta,\n kind = \"code\",\n lang,\n content,\n state = \"done\",\n lines = LINES,\n onOpen,\n open,\n className,\n ...rest\n}: ArtifactCardProps) {\n const writing = state === \"writing\";\n const clipped = content ? clip(content, lines) : null;\n\n const body = (\n <div className={styles.preview} data-more={clipped?.more || undefined} aria-hidden>\n {clipped === null ? (\n <div className={styles.waiting}>\n <Loader />\n </div>\n ) : kind === \"code\" ? (\n <CodeBlock code={clipped.shown} lang={lang} label={false} copyable={false} />\n ) : (\n <p className={styles.text}>{clipped.shown}</p>\n )}\n </div>\n );\n\n /* A ground under the card after all, and the reason is the answer's column\n rather than this component.\n\n It was taken away when the prose still began at the turn's own edge: a\n ground under a single card was 32px of indent that put the card's words on\n a different line from the answer's. Now everything in an answer begins on\n one column and the boxes bleed wider than it — so the ground is what puts\n this card's words *on* that column, the same way a tool call's does. See\n `--ick-answer-column`. */\n const inside = (\n <>\n <Head title={title} meta={meta} writing={writing} />\n {body}\n </>\n );\n\n const shared = { className: styles.card };\n\n return (\n <div\n className={[styles.artifact, className ?? \"\"].filter(Boolean).join(\" \")}\n data-state={state}\n data-open={open || undefined}\n {...(rest as HTMLAttributes<HTMLDivElement>)}\n >\n {onOpen ? (\n <button\n type=\"button\"\n {...shared}\n /* The state the ring used to draw, said properly. Omitted rather\n than `false` when the host is not tracking which is open: a\n button that claims to expand nothing is worse than a plain one. */\n aria-expanded={open}\n onClick={() => onOpen(id)}\n >\n {inside}\n </button>\n ) : (\n <div {...shared}>{inside}</div>\n )}\n </div>\n );\n}\n\nfunction Head({\n title,\n meta,\n writing,\n}: {\n title: ReactNode;\n meta?: ReactNode;\n writing: boolean;\n}) {\n return (\n <span className={styles.head}>\n <span className={styles.title} data-writing={writing || undefined}>\n {title}\n </span>\n {meta && <span className={styles.meta}>{meta}</span>}\n </span>\n );\n}\n","/**\n * Markdown, parsed here rather than by `unified` + `remark`.\n *\n * That pipeline was 31.6 KB gzip — a third of the whole package — and the\n * slowest thing in it: 0.9ms per thousand characters, 8.9ms at ten thousand,\n * which is half a frame spent re-parsing an answer that grew by one word.\n *\n * The kit uses a thin slice of it. `parseMarkdown` walks the tree straight\n * into a flat token list and a handful of elements, and it touches nine block\n * types and nine inline ones. This produces exactly those, in the same shape,\n * so nothing downstream changed.\n *\n * **It is not a CommonMark implementation and does not try to be.** It handles\n * what a model actually writes. Where it diverges from `remark` the difference\n * is caught rather than assumed: `remark` stays a dev dependency and a test\n * parses a corpus through both and compares the finished documents.\n */\n\nexport type Phrasing =\n | { type: \"text\"; value: string }\n | { type: \"strong\"; children: Phrasing[] }\n | { type: \"emphasis\"; children: Phrasing[] }\n | { type: \"delete\"; children: Phrasing[] }\n | { type: \"inlineCode\"; value: string }\n | { type: \"link\"; url: string; title?: string; children: Phrasing[] }\n | { type: \"image\"; url: string; alt: string }\n /**\n * `[^1]` — a citation marker, pointing at the nth source of the turn.\n *\n * The kit's one extension to the grammar, and the only place this parser\n * deliberately reads something `remark` does not. GFM spells footnotes the\n * same way but needs a `[^1]: …` definition somewhere in the document to\n * make one; a model streaming an answer emits the marker and sends the\n * sources beside the text, never below it. Without this an `InlineCitation`\n * can only be written by hand in JSX, which a stream cannot do — so the\n * component existed and nothing in a real conversation could reach it. */\n | { type: \"citation\"; index: number }\n | { type: \"break\" }\n /** Recognised so it can be dropped, which is what the renderer does with it. */\n | { type: \"html\"; value: string };\n\nexport interface ListItem {\n type: \"listItem\";\n children: Block[];\n}\n\nexport interface TableCell {\n type: \"tableCell\";\n children: Phrasing[];\n}\n\nexport interface TableRow {\n type: \"tableRow\";\n children: TableCell[];\n}\n\nexport type Block =\n | { type: \"paragraph\"; children: Phrasing[] }\n | { type: \"heading\"; depth: number; children: Phrasing[] }\n | { type: \"list\"; ordered: boolean; start?: number; children: ListItem[] }\n | { type: \"blockquote\"; children: Block[] }\n | { type: \"code\"; lang?: string; value: string }\n | { type: \"thematicBreak\" }\n | { type: \"table\"; children: TableRow[] }\n | { type: \"html\"; value: string };\n\n/* ─────────────────────────────────────────────\n Inline\n ───────────────────────────────────────────── */\n\n/** `\\*` and friends: the next character is itself, not syntax. */\nconst ESCAPABLE = \"\\\\`*_{}[]()#+-.!>~|\";\n\nconst isSpace = (c: string | undefined) => c === undefined || /\\s/.test(c);\n\n/**\n * A run of `*` or `_` that can open or close emphasis.\n *\n * The full CommonMark rule is about flanking runs and unicode punctuation.\n * This is the part of it that matters for prose: a marker opens if what\n * follows is not a space and closes if what precedes is not one, and `_`\n * additionally has to sit at a word boundary — otherwise `snake_case_name`\n * comes back italic, which is the one failure anybody would actually hit.\n */\nfunction canOpen(src: string, at: number, run: number, char: string): boolean {\n const after = src[at + run];\n if (isSpace(after)) return false;\n if (char === \"_\" && /[\\w]/.test(src[at - 1] ?? \"\")) return false;\n return true;\n}\n\nfunction canClose(src: string, at: number, run: number, char: string): boolean {\n const before = src[at - 1];\n if (isSpace(before)) return false;\n if (char === \"_\" && /[\\w]/.test(src[at + run] ?? \"\")) return false;\n return true;\n}\n\n/** How many of `char` start at `at`. */\nfunction runLength(src: string, at: number, char: string): number {\n let n = 0;\n while (src[at + n] === char) n++;\n return n;\n}\n\n/** The index of the `]` matching the `[` at `open`, or -1. */\nfunction closingBracket(src: string, open: number): number {\n let depth = 0;\n for (let i = open; i < src.length; i++) {\n const c = src[i];\n if (c === \"\\\\\") {\n i++;\n continue;\n }\n if (c === \"`\") {\n // A bracket inside a code span is not a bracket.\n const run = runLength(src, i, \"`\");\n const end = src.indexOf(\"`\".repeat(run), i + run);\n i = end === -1 ? src.length : end + run - 1;\n continue;\n }\n if (c === \"[\") depth++;\n else if (c === \"]\") {\n depth--;\n if (depth === 0) return i;\n }\n }\n return -1;\n}\n\n/**\n * `(url \"title\")` after a link's `]`.\n *\n * Returns where it ends, so the caller knows how much to consume. Angle\n * brackets around the url are stripped, which is how a url with a space in it\n * is written.\n */\nfunction destination(src: string, open: number): { url: string; title?: string; end: number } | null {\n if (src[open] !== \"(\") return null;\n let depth = 0;\n let close = -1;\n for (let i = open; i < src.length; i++) {\n if (src[i] === \"\\\\\") {\n i++;\n continue;\n }\n if (src[i] === \"(\") depth++;\n else if (src[i] === \")\") {\n depth--;\n if (depth === 0) {\n close = i;\n break;\n }\n }\n }\n if (close === -1) return null;\n\n const inner = src.slice(open + 1, close).trim();\n const quoted = inner.match(/^(.*?)\\s+(?:\"([^\"]*)\"|'([^']*)'|\\(([^)]*)\\))$/s);\n const rawUrl = (quoted ? quoted[1] : inner).trim();\n const title = quoted ? (quoted[2] ?? quoted[3] ?? quoted[4]) : undefined;\n const url = rawUrl.startsWith(\"<\") && rawUrl.endsWith(\">\") ? rawUrl.slice(1, -1) : rawUrl;\n return { url, ...(title ? { title } : {}), end: close + 1 };\n}\n\n/** Anything shaped like a tag, so it can be recognised and thrown away. */\nconst HTML_TAG = /^<\\/?[A-Za-z][A-Za-z0-9-]*(\\s[^<>]*?)?\\/?>|^<!--[\\s\\S]*?-->/;\n\nexport function parseInline(src: string): Phrasing[] {\n const out: Phrasing[] = [];\n let text = \"\";\n\n const flush = () => {\n if (text) out.push({ type: \"text\", value: text });\n text = \"\";\n };\n\n let i = 0;\n while (i < src.length) {\n const c = src[i];\n\n // A backslash before punctuation, or before a newline: an escape or a break.\n if (c === \"\\\\\") {\n const next = src[i + 1];\n if (next === \"\\n\") {\n flush();\n out.push({ type: \"break\" });\n i += 2;\n continue;\n }\n if (next && ESCAPABLE.includes(next)) {\n text += next;\n i += 2;\n continue;\n }\n text += c;\n i++;\n continue;\n }\n\n // Two spaces at the end of a line is a hard break.\n if (c === \" \" && src[i + 1] === \" \" && /^ *\\n/.test(src.slice(i))) {\n const nl = src.indexOf(\"\\n\", i);\n flush();\n out.push({ type: \"break\" });\n i = nl + 1;\n continue;\n }\n\n if (c === \"`\") {\n const run = runLength(src, i, \"`\");\n const close = src.indexOf(\"`\".repeat(run), i + run);\n // An unmatched run is literal backticks, not the start of anything.\n if (close !== -1 && !src.slice(i + run, close).startsWith(\"`\")) {\n flush();\n let value = src.slice(i + run, close);\n // One space either side is the fence, not the content — it is how you\n // write a span that itself starts with a backtick.\n if (value.length > 2 && value.startsWith(\" \") && value.endsWith(\" \") && value.trim()) {\n value = value.slice(1, -1);\n }\n out.push({ type: \"inlineCode\", value: value.replace(/\\n/g, \" \") });\n i = close + run;\n continue;\n }\n }\n\n if (c === \"!\" && src[i + 1] === \"[\") {\n const close = closingBracket(src, i + 1);\n const dest = close === -1 ? null : destination(src, close + 1);\n if (dest) {\n flush();\n out.push({ type: \"image\", url: dest.url, alt: src.slice(i + 2, close) });\n i = dest.end;\n continue;\n }\n }\n\n /* Before the link branch, which would otherwise take `[^1]` as a label\n and then fail to find a `(url)` after it — leaving the marker as text. */\n if (c === \"[\" && src[i + 1] === \"^\") {\n const close = src.indexOf(\"]\", i + 2);\n const digits = close === -1 ? null : src.slice(i + 2, close);\n if (digits && /^\\d{1,3}$/.test(digits)) {\n flush();\n out.push({ type: \"citation\", index: Number(digits) });\n i = close + 1;\n continue;\n }\n }\n\n if (c === \"[\") {\n const close = closingBracket(src, i);\n const dest = close === -1 ? null : destination(src, close + 1);\n if (dest) {\n flush();\n out.push({\n type: \"link\",\n url: dest.url,\n ...(dest.title ? { title: dest.title } : {}),\n children: parseInline(src.slice(i + 1, close)),\n });\n i = dest.end;\n continue;\n }\n }\n\n if (c === \"~\" && src[i + 1] === \"~\") {\n const close = src.indexOf(\"~~\", i + 2);\n if (close !== -1 && close > i + 2) {\n flush();\n out.push({ type: \"delete\", children: parseInline(src.slice(i + 2, close)) });\n i = close + 2;\n continue;\n }\n }\n\n if (c === \"*\" || c === \"_\") {\n const run = Math.min(runLength(src, i, c), 2);\n if (canOpen(src, i, run, c)) {\n // The first run of the same length that can close it.\n let j = i + run;\n let close = -1;\n while (j < src.length) {\n if (src[j] === \"\\\\\") {\n j += 2;\n continue;\n }\n if (src[j] === c) {\n const there = runLength(src, j, c);\n if (there >= run && canClose(src, j, run, c)) {\n close = j;\n break;\n }\n j += there;\n continue;\n }\n j++;\n }\n if (close !== -1 && close > i + run) {\n flush();\n const children = parseInline(src.slice(i + run, close));\n out.push(run === 2 ? { type: \"strong\", children } : { type: \"emphasis\", children });\n i = close + run;\n continue;\n }\n }\n }\n\n if (c === \"<\") {\n const tag = src.slice(i).match(HTML_TAG);\n if (tag) {\n flush();\n out.push({ type: \"html\", value: tag[0] });\n i += tag[0].length;\n continue;\n }\n }\n\n text += c;\n i++;\n }\n\n flush();\n return out;\n}\n\n/* ─────────────────────────────────────────────\n Blocks\n ───────────────────────────────────────────── */\n\nconst FENCE = /^ {0,3}(`{3,}|~{3,})\\s*([^`\\s]*)[^`]*$/;\nconst HEADING = /^ {0,3}(#{1,6})(?:\\s+(.*?))?\\s*#*\\s*$/;\nconst RULE = /^ {0,3}(?:(?:\\*\\s*){3,}|(?:-\\s*){3,}|(?:_\\s*){3,})$/;\nconst QUOTE = /^ {0,3}>\\s?/;\nconst BULLET = /^(\\s*)([-*+])(\\s+)(.*)$/;\nconst ORDERED = /^(\\s*)(\\d{1,9})([.)])(\\s+)(.*)$/;\nconst DELIMITER = /^ {0,3}\\|?\\s*:?-{1,}:?\\s*(\\|\\s*:?-{1,}:?\\s*)*\\|?\\s*$/;\nconst DELIMITER_CELL = /^:?-+:?$/;\nconst BLANK = /^\\s*$/;\n\n/** `| a | b |` → `[\"a\", \"b\"]`, respecting `\\|`. */\nfunction cells(line: string): string[] {\n const trimmed = line.trim().replace(/^\\|/, \"\").replace(/\\|$/, \"\");\n const out: string[] = [];\n let cell = \"\";\n for (let i = 0; i < trimmed.length; i++) {\n if (trimmed[i] === \"\\\\\" && trimmed[i + 1] === \"|\") {\n cell += \"|\";\n i++;\n continue;\n }\n if (trimmed[i] === \"|\") {\n out.push(cell.trim());\n cell = \"\";\n continue;\n }\n cell += trimmed[i];\n }\n out.push(cell.trim());\n return out;\n}\n\nconst isBlockStart = (line: string) =>\n FENCE.test(line) ||\n HEADING.test(line) ||\n RULE.test(line) ||\n QUOTE.test(line) ||\n BULLET.test(line) ||\n ORDERED.test(line);\n\nexport function parseBlocks(src: string): Block[] {\n const lines = src.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n const out: Block[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n if (BLANK.test(line)) {\n i++;\n continue;\n }\n\n // ── Fenced code ────────────────────────────────────────────────────────\n const fence = line.match(FENCE);\n if (fence) {\n const marker = fence[1][0];\n const width = fence[1].length;\n const body: string[] = [];\n i++;\n while (i < lines.length) {\n const closing = lines[i].match(new RegExp(`^ {0,3}${marker === \"`\" ? \"`\" : \"~\"}{${width},}\\\\s*$`));\n if (closing) {\n i++;\n break;\n }\n body.push(lines[i]);\n i++;\n }\n out.push({ type: \"code\", value: body.join(\"\\n\"), ...(fence[2] ? { lang: fence[2] } : {}) });\n continue;\n }\n\n // ── Heading ────────────────────────────────────────────────────────────\n const heading = line.match(HEADING);\n if (heading) {\n out.push({\n type: \"heading\",\n depth: heading[1].length,\n children: parseInline(heading[2] ?? \"\"),\n });\n i++;\n continue;\n }\n\n // ── Thematic break ─────────────────────────────────────────────────────\n if (RULE.test(line)) {\n out.push({ type: \"thematicBreak\" });\n i++;\n continue;\n }\n\n // ── Blockquote ─────────────────────────────────────────────────────────\n if (QUOTE.test(line)) {\n const quoted: string[] = [];\n while (i < lines.length && !BLANK.test(lines[i])) {\n // Lazy continuation: a line inside a quote need not repeat the marker.\n quoted.push(lines[i].replace(QUOTE, \"\"));\n i++;\n }\n out.push({ type: \"blockquote\", children: parseBlocks(quoted.join(\"\\n\")) });\n continue;\n }\n\n // ── Table ──────────────────────────────────────────────────────────────\n /* A table needs a delimiter row with **as many cells as the header**, which\n is the rule that keeps a half-typed one from becoming a table a row\n early. Streaming parses every prefix of an answer, so `| --- ` arrives\n on its own for a frame or two and it is still a paragraph. */\n const delimiter =\n line.includes(\"|\") && i + 1 < lines.length && DELIMITER.test(lines[i + 1])\n ? cells(lines[i + 1])\n : null;\n if (delimiter && delimiter.length === cells(line).length && delimiter.every((c) => DELIMITER_CELL.test(c))) {\n const header = cells(line);\n const rows: TableRow[] = [\n { type: \"tableRow\", children: header.map((c) => ({ type: \"tableCell\" as const, children: parseInline(c) })) },\n ];\n i += 2;\n while (i < lines.length && !BLANK.test(lines[i]) && lines[i].includes(\"|\")) {\n const row = cells(lines[i]);\n rows.push({\n type: \"tableRow\",\n children: header.map((_, at) => ({\n type: \"tableCell\" as const,\n children: parseInline(row[at] ?? \"\"),\n })),\n });\n i++;\n }\n out.push({ type: \"table\", children: rows });\n continue;\n }\n\n // ── List ───────────────────────────────────────────────────────────────\n const bullet = line.match(BULLET);\n const ordered = line.match(ORDERED);\n if (bullet || ordered) {\n const isOrdered = Boolean(ordered);\n const start = ordered ? Number(ordered[2]) : undefined;\n const items: ListItem[] = [];\n\n while (i < lines.length) {\n const b = lines[i].match(BULLET);\n const o = lines[i].match(ORDERED);\n const m = isOrdered ? o : b;\n if (!m || Boolean(o) !== isOrdered) break;\n\n const indent = (isOrdered ? m[1].length + m[2].length + 1 + m[4].length : m[1].length + 1 + m[3].length);\n const body = [isOrdered ? m[5] : m[4]];\n i++;\n\n // Everything indented under the marker, and lazy lines that continue\n // the item's paragraph.\n while (i < lines.length) {\n const next = lines[i];\n if (BLANK.test(next)) {\n const after = lines[i + 1];\n if (after !== undefined && new RegExp(`^ {${indent},}\\\\S`).test(after)) {\n body.push(\"\");\n i++;\n continue;\n }\n break;\n }\n if (new RegExp(`^ {${indent},}`).test(next)) {\n body.push(next.slice(indent));\n i++;\n continue;\n }\n if (BULLET.test(next) || ORDERED.test(next) || isBlockStart(next)) break;\n body.push(next.trim());\n i++;\n }\n\n items.push({ type: \"listItem\", children: parseBlocks(body.join(\"\\n\")) });\n }\n\n out.push({\n type: \"list\",\n ordered: isOrdered,\n ...(isOrdered && start !== undefined ? { start } : {}),\n children: items,\n });\n continue;\n }\n\n // ── Paragraph ──────────────────────────────────────────────────────────\n const para: string[] = [];\n while (i < lines.length && !BLANK.test(lines[i])) {\n if (para.length && isBlockStart(lines[i])) break;\n if (\n para.length &&\n lines[i].includes(\"|\") &&\n i + 1 < lines.length &&\n DELIMITER.test(lines[i + 1])\n ) {\n break;\n }\n para.push(lines[i]);\n i++;\n }\n /* A paragraph's own indentation is not content: the leading whitespace of\n every line goes, and so does the trailing whitespace of the last one.\n Interior trailing spaces are left alone — two of them before a newline\n is a hard break, which is the one place whitespace means something. */\n const text = para.map((l) => l.replace(/^[ \\t]+/, \"\")).join(\"\\n\").replace(/\\s+$/, \"\");\n out.push({ type: \"paragraph\", children: parseInline(text) });\n }\n\n return out;\n}\n","import { parseBlocks, type Block, type Phrasing } from \"./parse\";\n\n/**\n * Markdown, in the one shape the highlighter can work with.\n *\n * The highlighter's whole model is a **flat array of word and space tokens**,\n * addressed by index. Hit-testing reads `data-index` off whatever is under the\n * pointer; the keyboard cursor walks the indices; the text of a highlight is\n * rebuilt by joining a run of them. Markdown is a tree, and the obvious move —\n * render markdown and highlight the result — breaks every one of those.\n *\n * So the tree and the indices are separated. Parsing produces both:\n *\n * - `tokens`, still flat, still in reading order, exactly as before\n * - `nodes`, a small tree that says where each token sits\n *\n * Nothing downstream changes. A stroke across `**bold**` is a run of indices\n * like any other, even though the words live in different elements.\n */\nexport type MdNode =\n /** One entry of `tokens`, by index. */\n | { type: \"token\"; index: number }\n | { type: \"el\"; tag: string; props?: Record<string, string>; children: MdNode[] }\n /** A fenced block. Preformatted, so it is not tokenised — see below. */\n | { type: \"code\"; lang?: string; value: string };\n\nexport interface MarkdownDoc {\n /** Flat, in reading order. The bridge to every existing mechanism. */\n tokens: string[];\n nodes: MdNode[];\n}\n\n/**\n * Separates two blocks in `tokens` without appearing in `nodes`.\n *\n * An index does not have to be rendered. Nothing requires every token to exist\n * in the DOM — the cursor only walks words, and hit-testing only finds what is\n * drawn. What this buys is a sane rebuild: without it, the last word of one\n * paragraph and the first of the next would be joined into one word.\n */\nconst BLOCK_BREAK = \"\\n\\n\";\n\nconst HEADINGS = [\"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\"] as const;\n\nclass Builder {\n tokens: string[] = [];\n\n /** Split a run of text into words and spaces, and record where they went. */\n text(value: string): MdNode[] {\n const out: MdNode[] = [];\n for (const piece of value.split(/(\\s+)/)) {\n if (piece === \"\") continue;\n out.push({ type: \"token\", index: this.tokens.length });\n this.tokens.push(piece);\n }\n return out;\n }\n\n /** A token nobody draws. Keeps the rebuilt text readable across blocks. */\n break(): void {\n this.tokens.push(BLOCK_BREAK);\n }\n}\n\nconst el = (tag: string, children: MdNode[], props?: Record<string, string>): MdNode => ({\n type: \"el\",\n tag,\n children,\n ...(props ? { props } : {}),\n});\n\n/**\n * Map siblings, dropping a separator between each pair.\n *\n * Every place two pieces of text sit side by side with no whitespace of their\n * own needs this — list items, table cells, table rows. Without it the last\n * word of one and the first of the next are rebuilt as a single word, which is\n * how \"one\" and \"two\" first came back as \"onetwo\".\n */\nfunction siblings<T>(items: T[], b: Builder, each: (item: T) => MdNode): MdNode[] {\n return items.map((item, i) => {\n if (i > 0) b.break();\n return each(item);\n });\n}\n\nfunction inline(nodes: Phrasing[], b: Builder): MdNode[] {\n const out: MdNode[] = [];\n for (const node of nodes) {\n switch (node.type) {\n case \"text\":\n out.push(...b.text(node.value));\n break;\n case \"strong\":\n out.push(el(\"strong\", inline(node.children, b)));\n break;\n case \"emphasis\":\n out.push(el(\"em\", inline(node.children, b)));\n break;\n case \"delete\":\n out.push(el(\"del\", inline(node.children, b)));\n break;\n case \"inlineCode\":\n // Tokenised, unlike a fenced block: this is a run of words inside a\n // sentence, and a stroke has to be able to cross it.\n out.push(el(\"code\", b.text(node.value)));\n break;\n case \"link\":\n out.push(\n el(\"a\", inline(node.children, b), {\n href: node.url,\n // Model output is untrusted text. A link in it goes to somebody\n // else's page, so it opens away from the conversation and cannot\n // reach back through `window.opener`.\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n ...(node.title ? { title: node.title } : {}),\n })\n );\n break;\n case \"image\":\n out.push(el(\"img\", [], { src: node.url, alt: node.alt ?? \"\" }));\n break;\n case \"citation\":\n /* Untokenised, like a fenced block and for the same reason: a marker\n is not a word of the answer. Tokenising it would put `[1]` inside a\n highlight and inside the text a thread quotes back, which is not\n what anybody drew a line under. */\n out.push(el(\"cite\", [], { index: String(node.index) }));\n break;\n case \"break\":\n out.push(el(\"br\", []));\n break;\n // `html` is deliberately dropped. Raw HTML from a model is arbitrary\n // markup in the host's page; there is no version of rendering it that is\n // worth the surface it opens.\n default:\n if (\"children\" in node && Array.isArray(node.children)) {\n out.push(...inline(node.children as Phrasing[], b));\n }\n }\n }\n return out;\n}\n\nfunction block(nodes: Block[], b: Builder): MdNode[] {\n const out: MdNode[] = [];\n nodes.forEach((node, i) => {\n if (i > 0) b.break();\n switch (node.type) {\n case \"paragraph\":\n out.push(el(\"p\", inline(node.children, b)));\n break;\n case \"heading\":\n out.push(el(HEADINGS[node.depth - 1] ?? \"h6\", inline(node.children, b)));\n break;\n case \"list\":\n out.push(\n el(\n node.ordered ? \"ol\" : \"ul\",\n siblings(node.children, b, (item) => el(\"li\", block(item.children, b))),\n node.ordered && node.start != null && node.start !== 1\n ? { start: String(node.start) }\n : undefined\n )\n );\n break;\n case \"blockquote\":\n out.push(el(\"blockquote\", block(node.children, b)));\n break;\n case \"code\":\n out.push({ type: \"code\", value: node.value, ...(node.lang ? { lang: node.lang } : {}) });\n break;\n case \"thematicBreak\":\n out.push(el(\"hr\", []));\n break;\n case \"table\": {\n const [head, ...body] = node.children;\n const cells = (row: typeof head, tag: \"th\" | \"td\") =>\n siblings(row.children, b, (cell) => el(tag, inline(cell.children, b)));\n // Built head-first so the indices run in reading order; `siblings`\n // only separates within a run, so the seam between the two needs one\n // of its own.\n const thead = head ? el(\"thead\", [el(\"tr\", cells(head, \"th\"))]) : null;\n if (thead && body.length) b.break();\n const tbody = body.length\n ? el(\"tbody\", siblings(body, b, (row) => el(\"tr\", cells(row, \"td\"))))\n : null;\n out.push(el(\"table\", [thead, tbody].filter((n): n is MdNode => n !== null)));\n break;\n }\n default:\n if (\"children\" in node && Array.isArray(node.children)) {\n out.push(...inline(node.children as Phrasing[], b));\n }\n }\n });\n return out;\n}\n\n/**\n * Parse once per distinct string.\n *\n * Called from a `useMemo` keyed on the text, which during streaming still\n * means once per frame — the text is different every frame. That is measured\n * rather than assumed; see the note in `TextHighlighter`.\n */\nexport function parseMarkdown(text: string): MarkdownDoc {\n const b = new Builder();\n const nodes = block(parseBlocks(text), b);\n return { tokens: b.tokens, nodes };\n}\n","/* ─────────────────────────────────────────────\n InlineCitation\n\n The kit already had a way of saying \"this run of words is picked out\" — the\n marker somebody draws over an answer to ask about it. A citation is the same\n statement made by the answer rather than by the reader, so it is drawn the\n same way rather than in a second visual language nobody has learned.\n ───────────────────────────────────────────── */\n\n.citation {\n /* Inline, so it flows with the sentence it belongs to and wraps with it. */\n display: inline;\n}\n\n/* The passage the citation speaks for. */\n.marked .passage {\n background: var(--ick-cite-passage);\n /* Painted per line rather than as one box, so a passage that wraps is marked\n on every line it covers instead of as a rectangle behind the paragraph. */\n box-decoration-break: clone;\n -webkit-box-decoration-break: clone;\n padding: 0.05em 0.1em;\n border-radius: var(--ick-radius-xs);\n}\n\n/* Sized to the number rather than to a square.\n\n It was `min-width: 16px` with 2px of padding — the square the list uses. In\n a list that is right: the numbers sit in a column and have to line up. In a\n sentence it is not. A single digit is about five pixels wide, so five and a\n half pixels of empty badge sat on either side of it, and whatever followed —\n a comma, most often — came after that gap and read as detached from the\n number it belongs to.\n\n Padding in `em` so it tracks the number rather than a fixed square, and no\n minimum: what a badge needs is a fill and a corner, not a width. */\n.marker {\n all: unset;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: var(--ick-cite-size-marker);\n padding: 0 0.3em;\n margin-left: 0.25em;\n /* Lifted, the way a footnote mark is, without the size loss of a real\n superscript — the number still has to be readable and clickable. */\n vertical-align: 0.25em;\n border-radius: var(--ick-radius-sm);\n background: var(--ick-cite-fill);\n color: var(--ick-cite-ink);\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n line-height: 1;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n background var(--ick-duration-fast) ease,\n color var(--ick-duration-fast) ease;\n}\n\n.marker:hover {\n background: var(--ick-marker);\n color: var(--ick-marker-ink);\n}\n\n.marker:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: 1px;\n}\n\n/* Nowhere to go, so it is not a control and should not offer to be one. */\n.marker[data-static] {\n cursor: default;\n}\n\n.marker[data-static]:hover {\n background: var(--ick-cite-fill);\n color: var(--ick-cite-ink);\n}\n","\"use client\";\n\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport type { Source } from \"../Sources/Sources\";\nimport styles from \"./InlineCitation.module.css\";\n\nexport interface InlineCitationProps\n extends Omit<HTMLAttributes<HTMLElement>, \"children\" | \"onSelect\"> {\n /** 1-based, and it has to match the entry's place in `<Sources>`. */\n index: number;\n /** Named in the marker's label, so the marker says more than a number. */\n source?: Source;\n /**\n * The passage this citation is for.\n *\n * Given one, the passage is **marked** — the same yellow the highlighter\n * draws, because it is the same statement: this run of words is spoken for.\n * Left out, the marker stands on its own after whatever precedes it.\n */\n children?: ReactNode;\n /** Where the reader is taken. Without one the marker is not a control. */\n onSelect?: (index: number, source?: Source) => void;\n labels?: Partial<{ cite: string }>;\n}\n\n/**\n * The numbered marker in the text, and the passage it speaks for.\n *\n * The kit already had a way of saying \"this run of words is picked out\" — the\n * marker somebody draws over an answer to ask about it. A citation is that\n * same statement made by the answer rather than by the reader, so it is drawn\n * the same way rather than in a second visual language nobody has learned.\n */\nexport function InlineCitation({\n index,\n source,\n children,\n onSelect,\n labels,\n className,\n ...rest\n}: InlineCitationProps) {\n const cite = labels?.cite ?? \"Source\";\n const name = source ? `${cite} ${index}: ${source.title}` : `${cite} ${index}`;\n\n const marker = onSelect ? (\n <button\n type=\"button\"\n className={styles.marker}\n onClick={() => onSelect(index, source)}\n /* Inside an answer this sits on the highlighter's surface, where a\n pointerdown starts drawing a marker. The click is for the citation. */\n onPointerDown={(event) => event.stopPropagation()}\n aria-label={name}\n >\n {index}\n </button>\n ) : (\n <span className={styles.marker} data-static=\"\" aria-label={name} role=\"note\">\n {index}\n </span>\n );\n\n if (!children) {\n return (\n <span className={[styles.citation, className ?? \"\"].filter(Boolean).join(\" \")} {...rest}>\n {marker}\n </span>\n );\n }\n\n return (\n <span\n className={[styles.citation, styles.marked, className ?? \"\"].filter(Boolean).join(\" \")}\n {...rest}\n >\n <span className={styles.passage}>{children}</span>\n {marker}\n </span>\n );\n}\n","/* ─────────────────────────────────────────────\n Marker hover, driven by CSS\n\n Hover used to live in React state. Every crossing of the marker's stroke\n re-rendered the whole highlighter — every word span, every path string —\n and started springs that wrote stroke-width, opacity and filter on each\n frame. Tracing a paragraph crosses that boundary hundreds of times, which\n is why input-to-paint stretched past 100ms while the frame rate itself\n looked fine.\n\n The browser already knows what the pointer is over. Nothing here needs\n React to find out.\n ───────────────────────────────────────────── */\n\n.marker {\n --marker-stroke: 20px;\n\n /* Set here rather than as an SVG `stroke` attribute: an attribute cannot\n read a custom property, and the marker colour is the signature of the\n whole feature — it belongs in the token layer, not in a TSX constant. */\n stroke: var(--ick-marker-stroke);\n fill: none;\n stroke-linejoin: round;\n stroke-linecap: butt;\n stroke-width: var(--marker-stroke);\n opacity: 1;\n transition:\n stroke-width var(--ick-duration-base) var(--ick-ease),\n opacity var(--ick-duration-base) var(--ick-ease);\n}\n\n.marker:hover {\n --marker-stroke: 24px;\n opacity: 0.9;\n}\n\n/* Pressed and menu-open are click-driven, so they stay in React — clicks are\n rare and a re-render there costs nothing. They win over hover. */\n.marker[data-pressed=\"true\"],\n.marker[data-pressed=\"true\"]:hover {\n --marker-stroke: 16px;\n opacity: 1;\n}\n\n.marker[data-active=\"true\"],\n.marker[data-active=\"true\"]:hover {\n --marker-stroke: 28px;\n opacity: 1;\n filter: drop-shadow(0 6px 16px var(--ick-marker-glow));\n}\n\n/* Every other marker recedes while one has its menu open. */\n.marker[data-dimmed=\"true\"],\n.marker[data-dimmed=\"true\"]:hover {\n opacity: var(--ick-recede);\n}\n\n/* ── Shimmer ──\n Mounted once per marker rather than added and removed on every crossing.\n Paused costs nothing, so it simply runs while hovered. `pathLength=\"1\"`\n normalises the geometry so the dash values work for any path. */\n\n.shimmer {\n fill: none;\n stroke: var(--ick-marker-sheen);\n stroke-width: 24px;\n stroke-linejoin: round;\n stroke-linecap: butt;\n mix-blend-mode: overlay;\n pointer-events: none;\n\n stroke-dasharray: 1;\n stroke-dashoffset: 1;\n opacity: 0;\n\n animation: shimmer-sweep 2s ease-out infinite;\n animation-play-state: paused;\n}\n\n.marker:hover + .shimmer,\n.marker[data-active=\"true\"] + .shimmer {\n animation-play-state: running;\n}\n\n.marker[data-active=\"true\"] + .shimmer {\n stroke-width: 28px;\n}\n\n@keyframes shimmer-sweep {\n 0% {\n stroke-dashoffset: 1;\n opacity: 0.8;\n }\n 80% {\n stroke-dashoffset: 0;\n opacity: 0;\n }\n 100% {\n stroke-dashoffset: 0;\n opacity: 0;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .marker {\n transition: none;\n }\n .shimmer {\n animation: none;\n }\n}\n\n/* ─────────────────────────────────────────────\n Tokens\n\n The text is split per word because the marker needs to know which words a\n stroke crossed, and because the focus effect blurs everything except them —\n something no range-level API can express. `::highlight()` styles colour and\n shadow but not `filter` or `opacity`, so per-word elements are the only way\n to get this look.\n\n What was wrong was not the splitting. It was giving each of the ~120 words\n its own animation engine: opening one menu wrote 350 inline styles, one word\n at a time, for identical values. Two attributes and a stylesheet do the same\n job, and the browser composites it.\n ───────────────────────────────────────────── */\n\n.tokens {\n --token-ease: var(--ick-ease);\n --token-ease-press: var(--ick-ease-overshoot);\n\n position: relative;\n display: block;\n z-index: var(--ick-z-base);\n}\n\n/* ── Markdown ───────────────────────────────────────────────────────────────\n An answer is markdown now, so the tree under `.tokens` has real structure in\n it. Every rule here is scoped to `.tokens` and reads a token, so a host that\n already styles `p` or `ul` does not fight with this and a brand can move all\n of it.\n\n The outer margins are stripped rather than reduced. A block's margin at the\n very top or bottom of an answer pushes the answer away from the bubble it\n belongs to, and margin collapse makes the amount unpredictable. */\n\n.tokens > :first-child { margin-top: 0; }\n.tokens > :last-child { margin-bottom: 0; }\n\n.tokens p,\n.tokens ul,\n.tokens ol,\n.tokens blockquote,\n.tokens pre,\n.tokens table {\n margin: 0 0 var(--ick-md-gap);\n}\n\n.tokens h1,\n.tokens h2,\n.tokens h3,\n.tokens h4,\n.tokens h5,\n.tokens h6 {\n margin: var(--ick-md-heading-space) 0 var(--ick-md-gap);\n font-weight: var(--ick-weight-semibold);\n line-height: 1.3;\n}\n\n/* A heading in a chat answer is a label on a section, not a page title. The\n scale is compressed hard for that reason: h1 at browser default would be\n twice the size of the answer around it. */\n.tokens h1 { font-size: 1.25em; }\n.tokens h2 { font-size: 1.15em; }\n.tokens h3 { font-size: 1.05em; }\n.tokens h4,\n.tokens h5,\n.tokens h6 { font-size: 1em; }\n\n.tokens ul,\n.tokens ol {\n padding-left: var(--ick-md-indent);\n}\n\n.tokens li {\n margin-bottom: var(--ick-md-item-gap);\n}\n\n.tokens li:last-child {\n margin-bottom: 0;\n}\n\n/* Nested lists close up: the outer item's own gap already separates them. */\n.tokens li > ul,\n.tokens li > ol {\n margin-top: var(--ick-md-item-gap);\n margin-bottom: 0;\n}\n\n.tokens blockquote {\n padding-left: var(--ick-md-quote-indent);\n border-left: 2px solid var(--ick-md-rule);\n color: var(--ick-ink-soft);\n}\n\n.tokens code {\n font-family: var(--ick-font-mono);\n font-size: 0.92em;\n background: var(--ick-md-code-fill);\n border-radius: var(--ick-radius-xs);\n /* Vertical padding only in the box, so an inline code run does not push its\n own line taller than its neighbours. */\n padding: 0.1em var(--ick-space-2);\n}\n\n.tokens pre {\n overflow-x: auto;\n padding: var(--ick-space-5);\n border-radius: var(--ick-radius-md);\n background: var(--ick-md-code-fill);\n}\n\n/* The block already has the fill; the run inside it should not have a second. */\n.tokens pre code {\n display: block;\n padding: 0;\n background: none;\n font-size: 0.92em;\n line-height: 1.5;\n white-space: pre;\n}\n\n.tokens a {\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 0.2em;\n text-decoration-color: var(--ick-md-rule);\n transition: text-decoration-color var(--ick-duration-fast) ease;\n}\n\n.tokens a:hover {\n text-decoration-color: currentColor;\n}\n\n.tokens hr {\n margin: var(--ick-md-gap) 0;\n border: 0;\n border-top: 1px solid var(--ick-md-rule);\n}\n\n.tokens img {\n max-width: 100%;\n height: auto;\n border-radius: var(--ick-radius-md);\n}\n\n/* A wide table scrolls inside itself rather than widening the answer. */\n.tokens table {\n display: block;\n overflow-x: auto;\n border-collapse: collapse;\n font-size: 0.95em;\n}\n\n.tokens th,\n.tokens td {\n padding: var(--ick-space-3) var(--ick-space-5);\n border: 1px solid var(--ick-md-rule);\n text-align: left;\n vertical-align: top;\n}\n\n.tokens th {\n font-weight: var(--ick-weight-semibold);\n}\n\n.token {\n /* `inline-block` so the press transform applies — transforms do nothing to a\n non-replaced inline box. Filters work either way. */\n display: inline-block;\n white-space: pre-wrap;\n /* No `color` here. A token sets no colour of its own at rest — it inherits\n the page's — so transitioning the property meant transitioning whatever\n the host changed it to. On a theme swap the ink inverts, and 320ms of\n travel between the two inks goes straight through the background it is\n sitting on: every word of every answer disappeared for nine frames.\n Measured at 11.96% ink coverage before the swap, 3.56% one frame after,\n back at 13.98% ten frames later. The bullets stayed, because `::marker`\n takes no transition from here, which is what gave it away. */\n transition:\n filter 320ms var(--token-ease),\n opacity 320ms var(--token-ease),\n text-shadow 320ms var(--token-ease),\n transform 240ms var(--token-ease-press);\n}\n\n/* Colour is animated only where a token actually sets one — under an open\n menu — so the property cannot be dragged around by a change to what the\n token inherits. Scoping it here rather than on `.token` is the whole fix\n for the theme swap: the rule does not match at rest, so a swap repaints in\n one frame.\n\n Colouring a marked word at rest was tried and reverted. A stroke is drawn\n by hand and does not stop at word boundaries — it entered \"Higgs\" three\n letters in — so a token painted marker-ink is legible on the part the\n stroke covers and invisible on the part it misses. Ink cannot follow a\n stroke; only the stroke can. */\n.tokens[data-focus=\"true\"] .token {\n transition:\n filter 320ms var(--token-ease),\n opacity 320ms var(--token-ease),\n color 320ms var(--token-ease),\n text-shadow 320ms var(--token-ease),\n transform 240ms var(--token-ease-press);\n}\n\n/* The spaces between words stay inline.\n As `inline-block` each one is an atomic box, and an atomic box cannot hang\n at the end of a line the way a space does — so a line breaking *before* a\n space put that space at the start of the next line and pushed the text one\n character to the right. Only the words need to be transformable. */\n.token[data-space] {\n display: inline;\n}\n\n/* One attribute on the container recedes the whole paragraph. */\n.tokens[data-focus=\"true\"] .token {\n filter: blur(3px);\n opacity: var(--ick-recede);\n}\n\n/* …except the words the open menu belongs to, which come forward. */\n.tokens[data-focus=\"true\"] .token[data-active=\"true\"] {\n filter: blur(0);\n opacity: 1;\n color: var(--ick-marker-active-ink);\n text-shadow: 0 1px 2px rgb(var(--ick-glass-rim-rgb) / 0.4);\n}\n\n.token[data-pressed=\"true\"] {\n transform: scale(0.96);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .token {\n transition: none;\n }\n}\n\n/* ─────────────────────────────────────────────\n The word cursor\n\n The paragraph is focusable, so it needs a focus ring of its own — and the\n cursor and its selection need to be visible to the sighted keyboard user\n driving them, not only to the screen reader.\n ───────────────────────────────────────────── */\n\n/* Layout that used to be an inline style block. The only part that still\n changes per render is whether a menu is open, which is an attribute. */\n.surface {\n position: relative;\n display: block; /* wraps the text tightly */\n /* Suppressed only while a marker is being drawn. It used to be off\n permanently in marker mode, which meant the answer could not be selected\n at all — not to highlight it, not even to copy it. */\n user-select: text;\n -webkit-user-select: text;\n /* `pan-y`, not `none`. `none` meant a finger anywhere on an answer could\n not scroll the page at all — the whole conversation became a dead zone on\n a phone. Marker strokes are horizontal, scrolling is vertical, so the two\n gestures separate cleanly: a sideways drag draws, a downward drag\n scrolls, and the browser cancels the stroke it took over. */\n touch-action: pan-y;\n z-index: var(--ick-z-base);\n}\n\n.surface[data-drawing] {\n user-select: none;\n -webkit-user-select: none;\n}\n\n/* Raised over its neighbours while its menu is open, so the menu is not\n clipped by the next answer. */\n.surface[data-menu] {\n z-index: var(--ick-z-sticky);\n}\n\n/* Widens the pointer's catch area without moving anything.\n\n Up and down only. It used to bleed 20px on all four sides, and the two\n sideways ones were the only part that could hurt: an answer that fills its\n column — which on a phone is the whole screen — put this 20px past the right\n edge and gave the **host's page** a horizontal scrollbar. A component that\n can make a page scroll sideways is a defect wherever it is embedded, not\n only on a phone.\n\n Nothing is lost. The slop that matters is vertical: lines are ~24px apart\n and a stroke is aimed between them, while horizontally the text already runs\n to the surface's own edge, so a drag starting further out starts in the\n margin and snaps to the first word regardless. */\n.hitbox {\n position: absolute;\n inset-block: -20px;\n inset-inline: 0;\n z-index: 0;\n}\n\n.canvas {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n mix-blend-mode: var(--ick-marker-blend);\n z-index: var(--ick-z-raised);\n overflow: visible; /* lets a stroke run slightly outside the box */\n}\n\n.menu {\n position: absolute;\n transform: translate(-50%, calc(-100% - 16px));\n z-index: var(--ick-z-menu);\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: var(--ick-space-4);\n pointer-events: auto;\n}\n\n.surface:focus {\n outline: none; /* replaced by the focus-visible ring below */\n}\n\n.surface:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: 6px;\n border-radius: 6px;\n}\n\n/* Deliberately not the marker yellow. This is a pending selection, not a\n highlight — they should not look like the same thing. */\n.token[data-kbd-selected] {\n background: var(--ick-marker-tint);\n border-radius: 2px;\n}\n\n.token[data-kbd-cursor] {\n outline: 2px solid rgb(var(--ick-ink-rgb) / 0.55);\n outline-offset: 1px;\n border-radius: 2px;\n}\n\n/* Read on focus through aria-describedby, shown to nobody. */\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n overflow: hidden;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n white-space: nowrap;\n}\n","\"use client\";\n\nimport React, { useState, useRef, useEffect, useMemo, useCallback, useId, createElement, type ReactNode } from \"react\";\nimport { motion, AnimatePresence, MotionConfig } from \"motion/react\";\nimport { Button } from \"../Button/Button\";\nimport { CodeBlock } from \"../CodeBlock/CodeBlock\";\nimport { parseMarkdown, type MdNode } from \"../markdown/parseMarkdown\";\nimport { InlineCitation } from \"../InlineCitation/InlineCitation\";\nimport type { Source } from \"../Sources/Sources\";\nimport styles from \"./TextHighlighter.module.css\";\nimport { MessageCircle, Trash2 } from \"lucide-react\";\n\nconst SKEW_ANGLE = -20;\nconst TAN_ANGLE = Math.tan((SKEW_ANGLE * Math.PI) / 180);\n\ninterface PathData {\n id: string;\n points: { x: number; y: number }[];\n highlightedIndices: Set<number>;\n}\n\ninterface SelectionHighlight {\n id: string;\n rects: { x: number; y: number; w: number; h: number }[]; // container-relative\n text: string;\n highlightedIndices: Set<number>;\n}\n\nexport interface TextHighlighterProps {\n text: string;\n selectionMode?: \"marker\" | \"precise\";\n onHighlightComplete?: (highlightedText: string) => void;\n onReplyInThread?: (text: string, rect: DOMRect) => void;\n /**\n * What `[^1]` in the text points at, in the order the answer cites them.\n *\n * Leave it out and a marker still draws — as a number with nothing behind\n * it, which is what it is until the list arrives.\n */\n sources?: Source[];\n onSelectSource?: (index: number, source?: Source) => void;\n}\n\n/* Pure geometry helpers — hoisted so memoised values can depend on them. */\n\nconst makePathString = (points: { x: number; y: number }[]) => {\n if (points.length === 0) return \"\";\n const start = points[0];\n let d = `M ${start.x} ${start.y}`;\n for (let i = 1; i < points.length; i++) {\n d += ` L ${points[i].x} ${points[i].y}`;\n }\n return d;\n};\n// Menu position: horizontal center + top edge of the highlight (in container coords).\n// Points store skew-corrected x, so reapply the skew (visualX = x + TAN_ANGLE * y) to get the rendered x.\nconst getMenuPosition = (points: { x: number; y: number }[]) => {\n let minX = Infinity, maxX = -Infinity, minY = Infinity;\n points.forEach((p) => {\n const visualX = p.x + TAN_ANGLE * p.y;\n if (visualX < minX) minX = visualX;\n if (visualX > maxX) maxX = visualX;\n if (p.y < minY) minY = p.y;\n });\n return { x: (minX + maxX) / 2, y: minY };\n};\n// Same idea for precise-mode rect highlights (container-relative rects).\nconst getRectsMenuPosition = (rects: { x: number; y: number; w: number; h: number }[]) => {\n let minX = Infinity, maxX = -Infinity, minY = Infinity;\n rects.forEach((r) => {\n if (r.x < minX) minX = r.x;\n if (r.x + r.w > maxX) maxX = r.x + r.w;\n if (r.y < minY) minY = r.y;\n });\n return { x: (minX + maxX) / 2, y: minY };\n};\n/**\n * The words a set of token indices covers, gaps included.\n *\n * A fast drag skips tokens — the pointer lands on \"the\" and \"matter\" without\n * ever being over the space between them — so the run is filled in from the\n * first index to the last rather than joining only what was touched.\n */\nconst textFromIndices = (indices: Set<number>, tokens: string[]) => {\n if (indices.size === 0) return \"\";\n const sorted = [...indices].sort((a, b) => a - b);\n let out = \"\";\n for (let i = sorted[0]; i <= sorted[sorted.length - 1]; i++) out += tokens[i];\n return out.trim();\n};\n\n/** Long passages make unusable labels; a screen reader reads every character. */\nconst shorten = (text: string, max = 60) =>\n text.length > max ? `${text.slice(0, max).trimEnd()}…` : text;\n\nconst getSelectionPathString = (rects: { x: number; y: number; w: number; h: number }[]) => {\n let d = \"\";\n rects.forEach(r => {\n const centerY = r.y + r.h / 2;\n const startX = r.x - centerY * TAN_ANGLE;\n const endX = (r.x + r.w) - centerY * TAN_ANGLE;\n d += `M ${startX} ${centerY} L ${endX} ${centerY} `;\n });\n return d.trim();\n};\n\nexport function TextHighlighter({\n text,\n selectionMode = \"marker\",\n onHighlightComplete,\n onReplyInThread,\n sources,\n onSelectSource,\n}: TextHighlighterProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [paths, setPaths] = useState<PathData[]>([]);\n const [selections, setSelections] = useState<SelectionHighlight[]>([]);\n const [currentPath, setCurrentPath] = useState<PathData | null>(null);\n const [isDrawing, setIsDrawing] = useState(false);\n // `viaKeyboard` decides whether focus moves into the menu when it opens.\n // Pulling focus out from under someone who just drew with a mouse would be\n // an interruption; for someone who arrived by keyboard it is the only way in.\n const [menuAnchor, setMenuAnchor] = useState<{\n x: number | string;\n y: number;\n pathId: string;\n kind: \"path\" | \"selection\";\n viaKeyboard?: boolean;\n } | null>(null);\n /** Which action the menu's roving tabindex is currently on. */\n const [menuIndex, setMenuIndex] = useState(0);\n const menuRef = useRef<HTMLDivElement>(null);\n /** Where focus goes when a keyboard-opened menu is dismissed. */\n const returnFocusRef = useRef<HTMLElement | null>(null);\n const [pressedPathId, setPressedPathId] = useState<string | null>(null);\n // Which highlight the keyboard is currently on. Drives the same emphasis the\n // open menu does, so tabbing through them is visible on the page and not\n // only to a screen reader.\n const [focusedMarkerId, setFocusedMarkerId] = useState<string | null>(null);\n\n type MenuAnchor = NonNullable<typeof menuAnchor>;\n\n /** Open the menu, remembering how it was opened and what focus came from. */\n const openMenu = useCallback((anchor: MenuAnchor) => {\n if (anchor.viaKeyboard) {\n returnFocusRef.current = document.activeElement as HTMLElement | null;\n }\n setMenuIndex(0);\n setMenuAnchor(anchor);\n }, []);\n\n /**\n * Close the menu, and decide what focus does next.\n *\n * \"restore\" hands it back to whatever opened the menu — a highlight's button,\n * or the paragraph. \"container\" is for when that thing is about to stop\n * existing, as when the highlight itself is deleted. \"none\" is for the\n * pointer, where moving focus at all would be an interruption.\n */\n const dismissMenu = useCallback((focus: \"restore\" | \"container\" | \"none\" = \"none\") => {\n const target = returnFocusRef.current;\n returnFocusRef.current = null;\n setMenuAnchor(null);\n if (focus === \"none\") return;\n if (focus === \"restore\" && target?.isConnected) target.focus();\n else containerRef.current?.focus();\n }, []);\n\n useEffect(() => {\n const handleGlobalPointerDown = (e: PointerEvent) => {\n if (menuAnchor && containerRef.current && !containerRef.current.contains(e.target as Node)) {\n dismissMenu();\n }\n };\n window.addEventListener(\"pointerdown\", handleGlobalPointerDown);\n return () => window.removeEventListener(\"pointerdown\", handleGlobalPointerDown);\n }, [menuAnchor, dismissMenu]);\n\n /**\n * Markdown, parsed into a tree of elements over a flat array of tokens.\n *\n * The flat array is what everything else here depends on — hit-testing reads\n * an index off the span under the pointer, the keyboard cursor walks the\n * indices, a highlight's text is a run of them joined back together. Keeping\n * it flat is what lets the words live inside `<strong>` and `<li>` without a\n * single one of those mechanisms knowing.\n *\n * Keyed on the text, which during streaming is a new string every frame. See\n * the render below for what that costs.\n */\n const doc = useMemo(() => parseMarkdown(text), [text]);\n const tokens = doc.tokens;\n\n\n\n\n // Live values for the stable document listener (avoids re-registering on every\n // render). Written after commit — the listener only reads them from event\n // handlers, which run well after paint.\n const selectionModeRef = useRef(selectionMode);\n const selectionsRef = useRef(selections);\n const onHighlightCompleteRef = useRef(onHighlightComplete);\n const menuAnchorRef = useRef(menuAnchor);\n\n useEffect(() => {\n selectionModeRef.current = selectionMode;\n selectionsRef.current = selections;\n onHighlightCompleteRef.current = onHighlightComplete;\n menuAnchorRef.current = menuAnchor;\n });\n\n\n // Rebuilding these path strings on every hover was a large part of the cost.\n const allMarkers = useMemo(\n () => [\n ...paths.map((p) => ({\n id: p.id,\n d: makePathString(p.points),\n kind: \"path\" as const,\n item: p,\n text: textFromIndices(p.highlightedIndices, tokens),\n })),\n ...selections.map((sel) => ({\n id: sel.id,\n d: getSelectionPathString(sel.rects),\n kind: \"selection\" as const,\n item: sel,\n text: sel.text,\n })),\n ],\n [paths, selections, tokens]\n );\n\n type Marker = (typeof allMarkers)[number];\n\n /** Where the menu sits for a marker, in container coordinates. */\n const openMenuFor = (marker: Marker, viaKeyboard = false) => {\n const pos =\n marker.kind === \"path\"\n ? getMenuPosition((marker.item as PathData).points)\n : getRectsMenuPosition((marker.item as SelectionHighlight).rects);\n openMenu({ x: pos.x, y: pos.y, pathId: marker.id, kind: marker.kind, viaKeyboard });\n };\n\n /**\n * Turn a DOM Range into a committed highlight.\n *\n * Three routes arrive at a range — a drag in precise mode, a native\n * selection finished with the keyboard, and the word cursor below. They\n * differ only in how the range is arrived at, so everything after it is\n * shared.\n */\n const commitRange = useCallback((range: Range, rawText: string, viaKeyboard = false) => {\n const container = containerRef.current;\n const text = rawText.trim();\n if (!container || !text) return;\n\n const highlightedIndices = new Set<number>();\n container.querySelectorAll(\"span[data-index]\").forEach((span) => {\n if (range.intersectsNode(span)) {\n highlightedIndices.add(parseInt(span.getAttribute(\"data-index\")!, 10));\n }\n });\n\n // Reported before the geometry, and regardless of it. Somebody highlighted\n // this text; whether a marker can be drawn over it is a separate question,\n // and one the host app has no stake in.\n onHighlightCompleteRef.current?.(text);\n\n const cRect = container.getBoundingClientRect();\n const rawRects = Array.from(range.getClientRects())\n .map((r) => ({ x: r.left - cRect.left, y: r.top - cRect.top, w: r.width, h: r.height }))\n .filter((r) => r.w > 0 && r.h > 0);\n if (rawRects.length === 0) return;\n\n // The browser returns a separate rect for each text node. Merge them by\n // line, so the marker is one continuous stroke per line rather than one\n // stroke per word.\n const lines: { [y: string]: { x: number; y: number; w: number; h: number }[] } = {};\n rawRects.forEach((r) => {\n // Group rects that share roughly the same Y coordinate (within 5px)\n const lineY = Object.keys(lines).find((yStr) => Math.abs(parseFloat(yStr) - r.y) < 5);\n if (lineY) lines[lineY].push(r);\n else lines[r.y.toString()] = [r];\n });\n\n const rects = Object.values(lines).map((lineRects) => {\n const minX = Math.min(...lineRects.map((r) => r.x));\n const maxX = Math.max(...lineRects.map((r) => r.x + r.w));\n const minY = Math.min(...lineRects.map((r) => r.y));\n const maxH = Math.max(...lineRects.map((r) => r.h));\n return { x: minX, y: minY, w: maxX - minX, h: maxH };\n });\n\n const id = Date.now().toString();\n setSelections((prev) => [...prev, { id, rects, text, highlightedIndices }]);\n const pos = getRectsMenuPosition(rects);\n openMenu({ x: pos.x, y: pos.y, pathId: id, kind: \"selection\", viaKeyboard });\n }, [openMenu]);\n\n /**\n * A selection the reader made themselves.\n *\n * The pointer half only applies in precise mode, where native selection owns\n * the pointer. The keyboard half applies in both modes: dragging a marker\n * has no keyboard equivalent, so refusing keyboard selection in marker mode\n * would leave that mode with no way in at all.\n */\n useEffect(() => {\n const capture = (fromKeyboard: boolean) => {\n if (menuAnchorRef.current) return;\n const container = containerRef.current;\n if (!container) return;\n if (!fromKeyboard && selectionModeRef.current !== \"precise\") return;\n\n const sel = window.getSelection();\n if (!sel || sel.isCollapsed || sel.rangeCount === 0 || !sel.toString().trim()) return;\n\n const range = sel.getRangeAt(0);\n if (!container.contains(range.commonAncestorContainer)) return; // not this paragraph\n\n commitRange(range, sel.toString(), fromKeyboard);\n // Clear the native selection on the next frame so it doesn't linger as blue.\n requestAnimationFrame(() => window.getSelection()?.removeAllRanges());\n };\n\n const onMouseUp = () => capture(false);\n const onKeyUp = (e: KeyboardEvent) => {\n // Only keys that can have changed a selection. Anything else would\n // re-commit the same range on every keystroke.\n const selectAll = (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === \"a\";\n if (!e.shiftKey && !selectAll) return;\n capture(true);\n };\n\n document.addEventListener(\"mouseup\", onMouseUp);\n document.addEventListener(\"keyup\", onKeyUp);\n return () => {\n document.removeEventListener(\"mouseup\", onMouseUp);\n document.removeEventListener(\"keyup\", onKeyUp);\n };\n }, [commitRange]);\n\n // Close any open menu when switching selection mode.\n const [lastSelectionMode, setLastSelectionMode] = useState(selectionMode);\n if (lastSelectionMode !== selectionMode) {\n setLastSelectionMode(selectionMode);\n setMenuAnchor(null);\n }\n\n /**\n * The word cursor — how this works without a mouse.\n *\n * Dragging a marker has no keyboard equivalent, and native selection is not\n * a way in either: browsers give no caret to non-editable text unless caret\n * browsing is switched on, which almost nobody has. So the paragraph owns a\n * cursor of its own, moving over the word tokens that are already there for\n * hit-testing.\n *\n * Left and right move it, shift extends, Enter commits the same kind of\n * highlight a drag would have made. Up and down are deliberately left alone:\n * moving by line would need to know where the lines break, and guessing that\n * would be worse than letting the page scroll.\n */\n const wordIndices = useMemo(\n () => tokens.map((token, i) => (token.trim() ? i : -1)).filter((i) => i >= 0),\n [tokens]\n );\n const [caret, setCaret] = useState<number | null>(null);\n const [anchorIndex, setAnchorIndex] = useState<number | null>(null);\n const hintId = useId();\n\n const cursorRange = useMemo(() => {\n if (caret === null || anchorIndex === null) return null;\n return { from: Math.min(anchorIndex, caret), to: Math.max(anchorIndex, caret) };\n }, [caret, anchorIndex]);\n\n const clearCursor = () => {\n setCaret(null);\n setAnchorIndex(null);\n };\n\n const commitCursor = () => {\n const container = containerRef.current;\n if (!container || !cursorRange) return;\n const first = container.querySelector(`span[data-index=\"${cursorRange.from}\"]`);\n const last = container.querySelector(`span[data-index=\"${cursorRange.to}\"]`);\n if (!first || !last) return;\n\n const range = document.createRange();\n range.setStartBefore(first);\n range.setEndAfter(last);\n\n let text = \"\";\n for (let i = cursorRange.from; i <= cursorRange.to; i++) text += tokens[i];\n commitRange(range, text, true);\n clearCursor();\n };\n\n const menuItems = () => [\n ...(menuRef.current?.querySelectorAll<HTMLElement>('[role=\"menuitem\"]') ?? []),\n ];\n\n /**\n * Focus follows the roving tabindex — but only once focus is already in the\n * menu, or the menu was opened by someone who has no other way in. A menu\n * that grabs focus from a reader who opened it with a mouse is a menu that\n * interrupts.\n */\n useEffect(() => {\n if (!menuAnchor) return;\n const items = menuItems();\n if (items.length === 0) return;\n const inside = menuRef.current?.contains(document.activeElement);\n if (menuAnchor.viaKeyboard || inside) items[Math.min(menuIndex, items.length - 1)]?.focus();\n }, [menuAnchor, menuIndex]);\n\n const handleMenuKeyDown = (e: React.KeyboardEvent) => {\n const count = menuItems().length;\n if (count === 0) return;\n\n const step = (delta: number) => {\n e.preventDefault();\n // Stopped here so the paragraph's own arrow keys do not also move the\n // word cursor behind the open menu.\n e.stopPropagation();\n setMenuIndex((i) => (i + delta + count) % count);\n };\n\n switch (e.key) {\n case \"ArrowRight\":\n case \"ArrowDown\":\n return step(1);\n case \"ArrowLeft\":\n case \"ArrowUp\":\n return step(-1);\n case \"Home\":\n e.preventDefault();\n return setMenuIndex(0);\n case \"End\":\n e.preventDefault();\n return setMenuIndex(count - 1);\n default:\n return;\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n // Something inside has focus — a highlight's button, or a menu action.\n // Only Escape is still ours; the rest belongs to whatever is focused.\n if (e.target !== e.currentTarget && e.key !== \"Escape\") return;\n if (wordIndices.length === 0) return;\n\n const move = (direction: 1 | -1, extend: boolean) => {\n e.preventDefault();\n const at = caret === null ? -1 : wordIndices.indexOf(caret);\n const next =\n at === -1\n ? wordIndices[direction === 1 ? 0 : wordIndices.length - 1]\n : wordIndices[Math.min(wordIndices.length - 1, Math.max(0, at + direction))];\n setCaret(next);\n // Without shift the selection collapses onto the cursor, which is what\n // makes an unextended move feel like moving rather than selecting.\n if (!extend || anchorIndex === null) setAnchorIndex(next);\n };\n\n const jump = (to: number) => {\n e.preventDefault();\n setCaret(to);\n if (!e.shiftKey || anchorIndex === null) setAnchorIndex(to);\n };\n\n switch (e.key) {\n case \"ArrowRight\":\n return move(1, e.shiftKey);\n case \"ArrowLeft\":\n return move(-1, e.shiftKey);\n case \"Home\":\n return jump(wordIndices[0]);\n case \"End\":\n return jump(wordIndices[wordIndices.length - 1]);\n case \"Enter\":\n case \" \":\n if (!cursorRange) return;\n e.preventDefault();\n return commitCursor();\n case \"Escape\":\n if (!menuAnchor && caret === null) return; // let the host app have it\n e.preventDefault();\n dismissMenu(\"restore\");\n return clearCursor();\n default:\n return;\n }\n };\n\n const checkHighlight = (clientX: number, clientY: number, indices: Set<number>) => {\n const el = document.elementFromPoint(clientX, clientY);\n if (el && el.hasAttribute(\"data-index\")) {\n const idx = parseInt(el.getAttribute(\"data-index\")!, 10);\n indices.add(idx);\n }\n };\n\n const handlePointerDown = (e: React.PointerEvent) => {\n if (menuAnchor) {\n dismissMenu();\n return;\n }\n\n if (selectionMode === \"precise\") return; // native selection handles this mode\n // Only left click / main touch\n if (e.button !== 0 && e.pointerType === \"mouse\") return;\n\n // Text is selectable now, so the drag would otherwise start a native\n // selection underneath the marker. Preventing the default here suppresses\n // the compatibility mouse events that would begin one.\n e.preventDefault();\n\n setIsDrawing(true);\n const target = e.currentTarget as HTMLDivElement;\n target.setPointerCapture(e.pointerId);\n\n const rect = target.getBoundingClientRect();\n const mouseX = e.clientX - rect.left;\n const mouseY = e.clientY - rect.top;\n\n const correctedX = mouseX - mouseY * TAN_ANGLE;\n const newPath: PathData = {\n id: Date.now().toString(),\n points: [{ x: correctedX, y: mouseY }],\n highlightedIndices: new Set<number>(),\n };\n setCurrentPath(newPath);\n dismissMenu(); // Hide menu when starting a new highlight\n checkHighlight(e.clientX, e.clientY, newPath.highlightedIndices);\n };\n\n const handlePointerMove = (e: React.PointerEvent) => {\n if (selectionMode === \"precise\") return;\n if (!currentPath) return;\n\n const rect = containerRef.current!.getBoundingClientRect();\n const mouseX = e.clientX - rect.left;\n const mouseY = e.clientY - rect.top;\n const correctedX = mouseX - mouseY * TAN_ANGLE;\n\n setCurrentPath((prev) => {\n if (!prev) return prev;\n const newIndices = new Set(prev.highlightedIndices);\n checkHighlight(e.clientX, e.clientY, newIndices);\n return {\n ...prev,\n points: [...prev.points, { x: correctedX, y: mouseY }],\n highlightedIndices: newIndices,\n };\n });\n };\n\n const handlePointerUp = (e: React.PointerEvent) => {\n if (selectionMode === \"precise\") return;\n setIsDrawing(false);\n if (!currentPath) return;\n const target = e.currentTarget as HTMLDivElement;\n target.releasePointerCapture(e.pointerId);\n\n setPaths((prev) => [...prev, currentPath]);\n \n if (currentPath.highlightedIndices.size > 0) {\n const pos = getMenuPosition(currentPath.points);\n openMenu({ x: pos.x, y: pos.y, pathId: currentPath.id, kind: \"path\" });\n }\n\n if (onHighlightComplete && currentPath.highlightedIndices.size > 0) {\n onHighlightComplete(textFromIndices(currentPath.highlightedIndices, tokens));\n }\n \n setCurrentPath(null);\n };\n\n /**\n * The stroke was interrupted rather than finished — almost always because\n * the browser decided the gesture was a scroll and took the pointer away.\n *\n * Discarded, not committed. Treating a cancellation like a release meant\n * that on a phone, every scroll that happened to begin on an answer left a\n * highlight behind on whatever word the finger landed on.\n */\n const handlePointerCancel = (e: React.PointerEvent) => {\n if (selectionMode === \"precise\") return;\n setIsDrawing(false);\n if (!currentPath) return;\n (e.currentTarget as HTMLDivElement).releasePointerCapture(e.pointerId);\n setCurrentPath(null);\n };\n\n const getHighlightedText = (pathId: string) => {\n const pathData = paths.find((p) => p.id === pathId);\n return pathData ? textFromIndices(pathData.highlightedIndices, tokens) : \"\";\n };\n\n const removeHighlight = (id: string) => {\n const viaKeyboard = menuAnchor?.viaKeyboard;\n if (menuAnchor?.kind === \"selection\") {\n setSelections(prev => prev.filter(s => s.id !== id));\n } else {\n setPaths(prev => prev.filter(p => p.id !== id));\n }\n // The control focus would return to is the one being deleted, so focus\n // goes back to the paragraph instead of nowhere.\n dismissMenu(viaKeyboard ? \"container\" : \"none\");\n };\n\n const replyInThread = () => {\n // Focus goes back to the highlight *before* the host app opens its thread,\n // so whatever the thread captures as its return target is the highlight\n // and not a menu item that is about to stop existing.\n const viaKeyboard = menuAnchor?.viaKeyboard;\n if (menuAnchor && onReplyInThread) {\n if (menuAnchor.kind === \"selection\") {\n const sel = selections.find(s => s.id === menuAnchor.pathId);\n const container = containerRef.current;\n if (sel && container) {\n // Build a viewport-space union rect from the container-relative rects.\n const cRect = container.getBoundingClientRect();\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n sel.rects.forEach(r => {\n minX = Math.min(minX, r.x); minY = Math.min(minY, r.y);\n maxX = Math.max(maxX, r.x + r.w); maxY = Math.max(maxY, r.y + r.h);\n });\n const rect = new DOMRect(cRect.left + minX, cRect.top + minY, maxX - minX, maxY - minY);\n onReplyInThread(sel.text, rect);\n }\n } else {\n const text = getHighlightedText(menuAnchor.pathId);\n const el = document.getElementById(`highlight-path-${menuAnchor.pathId}`);\n if (el) {\n onReplyInThread(text, el.getBoundingClientRect());\n }\n }\n }\n dismissMenu(viaKeyboard ? \"restore\" : \"none\");\n };\n\n /**\n * One token: a word or the whitespace beside it, addressed by index.\n *\n * Unchanged from before markdown, and that is the point — the attributes\n * here are the entire contract with hit-testing, the keyboard cursor and the\n * dimming. Where the span sits in the tree is not part of it.\n */\n const renderToken = (index: number): ReactNode => {\n const token = tokens[index];\n\n // Both freeform (path) and precise (selection) highlights dim the surrounding tokens.\n let isPartOfActiveHighlight = false;\n if (menuAnchor) {\n const active =\n menuAnchor.kind === \"path\"\n ? paths.find((p) => p.id === menuAnchor.pathId)\n : selections.find((sel) => sel.id === menuAnchor.pathId);\n isPartOfActiveHighlight = active?.highlightedIndices?.has(index) ?? false;\n }\n\n let isPartOfPressed = false;\n if (pressedPathId) {\n const pressed =\n paths.find((p) => p.id === pressedPathId) ??\n selections.find((sel) => sel.id === pressedPathId);\n isPartOfPressed = pressed?.highlightedIndices?.has(index) ?? false;\n }\n\n return (\n <span\n key={index}\n data-index={index}\n className={styles.token}\n data-space={token.trim() ? undefined : true}\n data-active={isPartOfActiveHighlight || undefined}\n data-pressed={isPartOfPressed || undefined}\n data-kbd-cursor={caret === index || undefined}\n data-kbd-selected={\n (cursorRange && index >= cursorRange.from && index <= cursorRange.to) || undefined\n }\n >\n {token}\n </span>\n );\n };\n\n /** Walk the parsed tree, dropping a token span at every text position. */\n const renderNodes = (nodes: MdNode[]): ReactNode =>\n nodes.map((node, i) => {\n if (node.type === \"token\") return renderToken(node.index);\n if (node.type === \"code\") {\n // Not tokenised, so not markable. A fenced block is preformatted —\n // splitting it on whitespace would destroy the one thing it is for.\n // What it gets instead is a copy button, which is what people want\n // from code anyway.\n return <CodeBlock key={`c${i}`} code={node.value} lang={node.lang} />;\n }\n /* `[^1]` in the answer, resolved against the turn's sources. The number\n is what the model wrote and the list is 1-based, which is why this\n subtracts one and does not renumber: a marker pointing at a source\n that has not arrived yet still draws, and starts working the moment\n the list does. */\n if (node.type === \"el\" && node.tag === \"cite\") {\n const at = Number(node.props?.index ?? 0);\n return (\n <InlineCitation\n key={`q${i}`}\n index={at}\n source={sources?.[at - 1]}\n onSelect={onSelectSource ? (n, source) => onSelectSource(n, source) : undefined}\n />\n );\n }\n return createElement(\n node.tag,\n { key: `e${i}`, ...node.props },\n // Void elements take no children at all, not even an empty array.\n node.children.length > 0 ? renderNodes(node.children) : undefined\n );\n });\n\n return (\n // See the note in ChatInput: the kit sets this itself rather than relying\n // on the host app to.\n <MotionConfig reducedMotion=\"user\">\n <motion.div\n ref={containerRef}\n data-cursor={selectionMode === \"precise\" ? \"text\" : \"marker\"}\n data-cursor-active={isDrawing ? \"true\" : \"false\"}\n animate={{\n scale: 1,\n y: 0,\n }}\n transition={{ type: \"spring\", stiffness: 400, damping: 30 }}\n data-drawing={isDrawing || undefined}\n data-menu={!!menuAnchor || undefined}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerCancel}\n className={styles.surface}\n tabIndex={0}\n onKeyDown={handleKeyDown}\n onBlur={clearCursor}\n aria-describedby={hintId}\n >\n {/* Referenced by aria-describedby, so it is read on focus and nowhere\n else — a directly referenced node is used even when hidden. Without\n this, the keys exist but nothing tells anyone they do. */}\n <span id={hintId} className={styles.srOnly} aria-hidden=\"true\">\n Left and right arrow keys move by word. Hold shift to select. Enter\n highlights the selection, Escape clears it.\n </span>\n\n <div className={styles.hitbox} />\n\n {/* Underlying text.\n\n A `div`, not a `span`: markdown brings paragraphs, lists and headings,\n and a span may not contain them. The class already made it a block. */}\n <div className={styles.tokens} data-focus={!!menuAnchor || undefined}>\n {renderNodes(doc.nodes)}\n </div>\n\n {/* SVG Canvas overlay */}\n <svg className={styles.canvas}>\n <g transform={`skewX(${SKEW_ANGLE})`}>\n {allMarkers.map((marker) => (\n <React.Fragment key={marker.id}>\n <path\n id={`highlight-${marker.kind}-${marker.id}`}\n className={styles.marker}\n d={marker.d}\n data-cursor=\"pointer\"\n data-pressed={pressedPathId === marker.id || undefined}\n data-active={\n menuAnchor?.pathId === marker.id || focusedMarkerId === marker.id || undefined\n }\n data-dimmed={(menuAnchor && menuAnchor.pathId !== marker.id) || undefined}\n style={{ pointerEvents: isDrawing ? \"none\" : \"stroke\" }}\n onPointerDown={(e) => {\n if (isDrawing) return;\n e.stopPropagation();\n setPressedPathId(marker.id);\n }}\n onPointerUp={(e) => {\n if (pressedPathId === marker.id) {\n e.stopPropagation();\n openMenuFor(marker);\n setPressedPathId(null);\n }\n }}\n onPointerLeave={() => setPressedPathId(null)}\n onPointerCancel={() => setPressedPathId(null)}\n />\n {/* Sibling of the marker so CSS can drive it off :hover. Mounted\n once and paused, rather than added and removed per crossing. */}\n <path\n className={styles.shimmer}\n d={marker.d}\n pathLength={1}\n data-active={\n menuAnchor?.pathId === marker.id || focusedMarkerId === marker.id || undefined\n }\n />\n </React.Fragment>\n ))}\n {currentPath && (\n <path className={styles.marker} d={makePathString(currentPath.points)} />\n )}\n </g>\n </svg>\n\n {/* Every committed highlight, as something the keyboard can reach.\n Real buttons rather than focusable SVG paths — focus on SVG elements\n behaves differently in every browser and is not a fight worth having.\n They are invisible, but focusing one lights up the marker it belongs\n to, so tabbing through them is visible on the page and not only to a\n screen reader. */}\n {allMarkers.length > 0 && (\n <div\n role=\"group\"\n aria-label={`${allMarkers.length} highlight${allMarkers.length === 1 ? \"\" : \"s\"}`}\n >\n {allMarkers.map((marker) => (\n <button\n key={marker.id}\n type=\"button\"\n className={styles.srOnly}\n onPointerDown={(e) => e.stopPropagation()} // must not start a new drawing\n onFocus={() => setFocusedMarkerId(marker.id)}\n onBlur={() => setFocusedMarkerId(null)}\n onClick={(e) => {\n e.stopPropagation();\n // These are invisible, so they can only have been activated\n // from the keyboard.\n openMenuFor(marker, true);\n }}\n >\n {`Highlight: ${shorten(marker.text)}`}\n </button>\n ))}\n </div>\n )}\n\n {/* Floating Action Menu */}\n <AnimatePresence>\n {menuAnchor && (\n <motion.div\n ref={menuRef}\n role=\"menu\"\n aria-label=\"Highlight actions\"\n onKeyDown={handleMenuKeyDown}\n onBlur={(e) => {\n // Tab out and the menu goes with you. Leaving one open behind\n // the focus ring is how a menu becomes a thing to get lost in.\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) dismissMenu();\n }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1, transition: { staggerChildren: 0.05 } }}\n exit={{ opacity: 0, transition: { staggerChildren: 0.05, staggerDirection: -1 } }}\n className={styles.menu}\n // Where the menu sits is the one thing that cannot be a class.\n style={{ left: menuAnchor.x, top: menuAnchor.y }}\n onPointerDown={(e) => e.stopPropagation()} // Sprečava da klik na dugme započne novo crtananje\n >\n <motion.div\n initial={{ opacity: 0, y: 10, scale: 0.9 }}\n animate={{ opacity: 1, y: 0, scale: 1, transition: { type: \"spring\", stiffness: 400, damping: 25 } }}\n exit={{ opacity: 0, scale: 0.9, transition: { duration: 0.1 } }}\n >\n <Button\n variant=\"primary\"\n role=\"menuitem\"\n tabIndex={menuIndex === 0 ? 0 : -1}\n icon={<MessageCircle size={14} />}\n onClick={(e) => {\n e.stopPropagation();\n replyInThread();\n }}\n title=\"Reply in thread\"\n >\n Reply in thread\n </Button>\n </motion.div>\n\n <motion.div\n initial={{ opacity: 0, y: 10, scale: 0.9 }}\n animate={{ opacity: 1, y: 0, scale: 1, transition: { type: \"spring\", stiffness: 400, damping: 25 } }}\n exit={{ opacity: 0, scale: 0.9, transition: { duration: 0.1 } }}\n >\n <Button\n variant=\"primary\"\n role=\"menuitem\"\n tabIndex={menuIndex === 1 ? 0 : -1}\n icon={<Trash2 size={14} />}\n onClick={(e) => {\n e.stopPropagation();\n removeHighlight(menuAnchor.pathId);\n }}\n title=\"Remove highlight\"\n aria-label=\"Remove highlight\"\n />\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>\n </motion.div>\n </MotionConfig>\n );\n}\n","/* ─────────────────────────────────────────────\n ChatTurnRow\n\n The question sits right, the answer sits left and full width. That asymmetry\n is the only layout opinion in here; everything else is measurement, and\n every measurement is a token.\n ───────────────────────────────────────────── */\n\n.turn {\n display: flex;\n flex-direction: column;\n gap: var(--ick-turn-gap);\n /* Room above a turn scrolled to by id, so it does not land flush against\n whatever is fixed at the top of the page. */\n scroll-margin-top: var(--ick-space-6);\n}\n\n.question {\n display: flex;\n justify-content: flex-end;\n}\n\n/* The opening composer, which is not a message yet. See `questionAlign`. */\n.question[data-align=\"stretch\"] {\n justify-content: stretch;\n}\n\n/* Everything the answer is made of: the reasoning, the tool calls, the plan,\n the question, and the prose. Closer together than the row's own gap, which\n separates a whole turn from the next one. */\n.body {\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-7);\n}\n\n/* Nothing arrived yet, so it should take up no room and add no gap. */\n.body:empty {\n display: none;\n}\n\n.answer {\n margin: 0;\n /* On the answer's column, at both ends. Everything that draws in a turn\n starts here; only the boxes bleed wider. See `--ick-answer-column`.\n\n The right margin was missing, and on a wide screen nothing showed it: the\n measure below runs out first, so the line ends where the measure says and\n the column never comes into it. On a phone the measure is wider than the\n screen, so the prose ran to the container's edge — measured at 390px, the\n text ended at 374 while the card's own words ended at 342, and the answer\n looked ragged against the card above it. Mirroring the column puts it back\n on 342, which is the same 48 in from the other side. */\n margin-left: var(--ick-answer-column);\n margin-right: var(--ick-answer-column);\n font-family: var(--ick-font-sans);\n font-size: var(--ick-answer-size);\n line-height: var(--ick-answer-leading);\n letter-spacing: var(--ick-answer-tracking);\n color: var(--ick-ink);\n /* Measured in characters, not pixels: the comfortable line length is a\n count of letters, so it should follow the font rather than fight it. */\n max-width: var(--ick-answer-measure);\n}\n\n/* Close under the last line, and clear of the next turn. */\n/* The row under an answer: which version you are on, then what you can do\n with it. One row, because they are the same kind of thing — quiet controls\n that belong to the answer above them. */\n.actions {\n display: flex;\n align-items: center;\n gap: var(--ick-space-4);\n /* No column of its own: this row is *inside* `.answer`, so it is already on\n one. Adding the margin here as well put it at 64 — measured, which is the\n only way to notice an indent applied twice. */\n margin-top: var(--ick-space-4);\n}\n\n/* Dragging across the answer with the OS selection still reads as marking it,\n so it is marked in the marker's colour. */\n.answer ::selection {\n background: var(--ick-marker);\n color: var(--ick-marker-ink);\n}\n\n.answer ::-moz-selection {\n background: var(--ick-marker);\n color: var(--ick-marker-ink);\n}\n","\"use client\";\n\nimport { memo, type Ref } from \"react\";\nimport { motion } from \"motion/react\";\nimport { AnswerActions, type Verdict } from \"../AnswerActions/AnswerActions\";\nimport { Branch } from \"../Branch/Branch\";\nimport { ChatInput, type ChatInputHandle, type InlineAnimConfig } from \"../ChatInput/ChatInput\";\nimport type { Attachment } from \"../Attachments/Attachments\";\nimport type { TranscribeHandler } from \"../voice/useVoiceInput\";\nimport { Loader } from \"../Loader/Loader\";\nimport { Reasoning } from \"../Reasoning/Reasoning\";\nimport { Tool } from \"../Tool/Tool\";\nimport { Approval, type Decision } from \"../Approval/Approval\";\nimport { TaskList } from \"../TaskList/TaskList\";\nimport { ChainOfThought } from \"../ChainOfThought/ChainOfThought\";\nimport { Sources } from \"../Sources/Sources\";\nimport { QuestionGroup, type FoldMotion } from \"../QuestionGroup/QuestionGroup\";\nimport { SystemMessage } from \"../SystemMessage/SystemMessage\";\nimport { ArtifactCard } from \"../Artifact/ArtifactCard\";\nimport type { Answer } from \"../QuestionCard/types\";\nimport { TextHighlighter } from \"../TextHighlighter/TextHighlighter\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport type { ChatTurn } from \"../useChatTurns/useChatTurns\";\nimport styles from \"./ChatTurnRow.module.css\";\n\nexport interface ChatTurnRowProps {\n turn: ChatTurn;\n /** This row owns the live composer: the one the reader types the next question into. */\n isActiveInput?: boolean;\n inputRef?: Ref<ChatInputHandle> | null;\n placeholder?: string;\n animationConfig?: InlineAnimConfig;\n /** Overrides for a question group's fold timing. See `defaultFoldMotion`. */\n foldMotion?: Partial<FoldMotion>;\n /**\n * Which artifact the pane is showing, so the card that opened it can say so.\n * Held by `useArtifacts`, above both of them.\n */\n openArtifactId?: string | null;\n onOpenArtifact?: (turnId: string, artifactId: string) => void;\n /** Stagger for the entrance, in seconds. */\n entranceDelay?: number;\n /** Passed through to the highlighter over the answer. */\n selectionMode?: \"marker\" | \"precise\";\n\n /**\n * Passed through to the composer. Given, this row's live input offers a\n * microphone; left out, it does not. See `ChatInput`.\n */\n onTranscribe?: TranscribeHandler;\n\n /**\n * Where the composer sits in the row.\n *\n * `end` by default, because it is about to become the reader's own bubble\n * and those sit right. `stretch` fills the row instead, which is what an\n * *opening* composer wants: on an empty conversation it is not a message\n * yet, it is the thing under the openers — and a pill floating at the right\n * edge of a centred block reads as unrelated to the block.\n */\n questionAlign?: \"end\" | \"stretch\";\n\n /**\n * Every callback takes the turn's id rather than being closed over per row.\n *\n * Not a style choice. An inline arrow is a new function on every render, and\n * a new function prop is what makes `memo` give up — see the note on the\n * component below. Taking the id lets a consumer hoist these once.\n */\n onDraft?: (id: string, value: string) => void;\n onSubmit?: (id: string, value: string, attachments: Attachment[]) => void;\n onStop?: () => void;\n onEdit?: (id: string) => void;\n onCancelEdit?: (id: string) => void;\n /** Defaults to writing to the clipboard. */\n onCopy?: (value: string) => void;\n onHighlight?: (turnId: string, text: string) => void;\n onReplyInThread?: (text: string, rect: DOMRect) => void;\n\n /**\n * The row of actions under a settled answer.\n *\n * Copy is always offered once there is something to copy. Regenerate and the\n * thumbs are drawn only when there is somewhere for them to report to, so a\n * host that has no use for them is not showing a button that does nothing.\n *\n * They appear when the answer *settles*, not while it arrives: offering to\n * copy a half-written answer, or to rate one, is offering the wrong thing.\n */\n onRegenerate?: (id: string) => void;\n /** Show another of this turn's answers. Without it the control is inert, so\n it is not drawn. */\n onShowVersion?: (id: string, index: number) => void;\n onFeedback?: (id: string, verdict: Verdict | null) => void;\n feedback?: Verdict | null;\n /** Leave the row out entirely. */\n answerActions?: boolean;\n\n /**\n * A question the assistant asked, answered.\n *\n * The row does not keep the answer — `turn.parts` is the host's, and this is\n * how it hears that one of them changed. `useChatTurns` gives you\n * `updatePart` to write it back.\n */\n onAnswerQuestion?: (turnId: string, partId: string, questionId: string, answer: Answer) => void;\n /** Somebody asked to change an answer already given. */\n onEditQuestion?: (turnId: string, partId: string, index: number) => void;\n\n /**\n * Something the agent asked to do, decided.\n *\n * Same shape as the question callbacks and for the same reason: the row does\n * not keep the decision, it reports it. Write it back with `updatePart`.\n */\n onDecideApproval?: (turnId: string, partId: string, decision: Decision) => void;\n\n className?: string;\n}\n\nconst copyToClipboard = (value: string) => {\n void navigator.clipboard?.writeText(value);\n};\n\n/**\n * One turn: the question as a composer that has become a bubble, and the\n * answer beneath it.\n *\n * Not `Message`, because it is not one. The user half is a live input that\n * morphs into its own bubble rather than a rendered record of what was typed\n * — which is the whole idea, and the reason the row is a turn.\n *\n * **Memoised, and the memo is load-bearing.** `useChatTurns` already leaves\n * untouched turns referentially identical when it rewrites one of them, but\n * that only pays off if the rows can act on it. Left inline in a `.map()`,\n * every row re-renders on every flush anyway, because the parent re-rendered.\n * Measured before this existed: streaming a second answer produced 366 DOM\n * mutations inside the first, already-finished turn.\n *\n * The two halves have to be in place together. Stable objects give React the\n * grounds to skip; `memo` is what makes it skip.\n */\nexport const ChatTurnRow = memo(function ChatTurnRow({\n turn,\n isActiveInput = false,\n inputRef = null,\n placeholder,\n animationConfig,\n foldMotion,\n openArtifactId = null,\n onOpenArtifact,\n entranceDelay = 0,\n selectionMode = \"marker\",\n questionAlign = \"end\",\n onDraft,\n onSubmit,\n onStop,\n onEdit,\n onCancelEdit,\n onCopy = copyToClipboard,\n onHighlight,\n onReplyInThread,\n onRegenerate,\n onShowVersion,\n onFeedback,\n feedback = null,\n answerActions = true,\n onAnswerQuestion,\n onEditQuestion,\n onDecideApproval,\n className,\n onTranscribe,\n}: ChatTurnRowProps) {\n // A row arriving is travel, and this reader has asked for less of it. The\n // fade stays: without it a turn would appear with no transition at all,\n // which reads as a glitch rather than as calm.\n const still = prefersReducedMotion();\n const offset = still ? 0 : -16;\n // A turn a host built by hand may not have any.\n const parts = turn.parts ?? [];\n const cited = parts.find((part) => part.kind === \"sources\")?.sources;\n\n return (\n <motion.article\n id={`turn-${turn.id}`}\n className={[styles.turn, className ?? \"\"].filter(Boolean).join(\" \")}\n /* Which row is the live composer is something only this component knows,\n and a hashed CSS-module class is not something a host can target. So\n it is stated as an attribute.\n\n The one that needs it: a page with a gradient fading the conversation\n off the bottom edge has no way to exempt the box you type in, and a\n washed-out composer reads as one you are not allowed to use. */\n data-active-input={isActiveInput || undefined}\n aria-busy={turn.state === \"responding\" || undefined}\n initial={{ opacity: 0, y: offset }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: offset }}\n transition={{ duration: still ? 0.15 : 0.4, ease: [0.22, 1, 0.36, 1], delay: entranceDelay }}\n >\n <div className={styles.question} data-align={questionAlign}>\n <ChatInput\n ref={inputRef}\n /* A flex child sizes to its content, and the composer has a width of\n its own — so stretching the row is not enough on its own. */\n style={questionAlign === \"stretch\" ? { width: \"100%\" } : undefined}\n state={turn.state}\n value={turn.user}\n onChange={(v) => onDraft?.(turn.id, v)}\n onSubmit={(v, files) => onSubmit?.(turn.id, v, files)}\n /* The bubble *is* the message, so what went with it is shown by the\n same composer that held it — read-only once it is sent, because a\n sent message is a record. */\n attachments={turn.attachments}\n onStop={onStop}\n onTranscribe={onTranscribe}\n onCopy={onCopy}\n onEdit={() => onEdit?.(turn.id)}\n onCancelEdit={() => onCancelEdit?.(turn.id)}\n // An edit, rather than a first draft: there is already an answer\n // under this input, so the buttons say save and cancel.\n isEditing={turn.ai.length > 0 && turn.state === \"typing\"}\n animationConfig={animationConfig}\n placeholder={placeholder}\n />\n </div>\n\n {/* One block, so the row's generous gap sits between the question and\n everything the answer is made of — and not between a tool call and\n the sentence it produced, which belong together. */}\n <div className={styles.body}>\n {parts.map((part) => {\n switch (part.kind) {\n case \"reasoning\":\n return (\n <Reasoning key={part.id} state={part.state} duration={part.duration}>\n {part.text ?? \"\"}\n </Reasoning>\n );\n case \"tool\":\n return (\n <Tool\n key={part.id}\n name={part.name ?? \"\"}\n state={part.state}\n summary={part.summary}\n input={part.input}\n output={part.output}\n error={part.error}\n duration={part.duration}\n />\n );\n case \"tasks\":\n return (\n <TaskList\n key={part.id}\n title={part.title}\n tasks={part.tasks ?? []}\n collapsible={part.collapsible}\n />\n );\n case \"chain\":\n return (\n <ChainOfThought\n key={part.id}\n steps={part.steps ?? []}\n state={part.state}\n duration={part.duration}\n />\n );\n case \"sources\":\n return (\n <Sources\n key={part.id}\n sources={part.sources ?? []}\n title={part.title}\n collapsible={part.collapsible}\n />\n );\n case \"approval\":\n return (\n <Approval\n key={part.id}\n title={part.title}\n description={part.description}\n decision={part.decision ?? null}\n onDecide={(decision) => onDecideApproval?.(turn.id, part.id, decision)}\n >\n {part.tool && (\n <Tool\n name={part.tool.name}\n state=\"pending\"\n input={part.tool.input}\n defaultOpen\n />\n )}\n </Approval>\n );\n case \"artifact\":\n return (\n <ArtifactCard\n key={part.id}\n id={part.id}\n title={part.title}\n meta={part.meta}\n kind={part.preview}\n lang={part.lang}\n content={part.content}\n state={part.state}\n open={openArtifactId === part.id}\n onOpen={\n onOpenArtifact ? (id) => onOpenArtifact(turn.id, id) : undefined\n }\n />\n );\n case \"notice\":\n return (\n <SystemMessage key={part.id} tone={part.tone}>\n {part.text}\n </SystemMessage>\n );\n case \"question\":\n return (\n <QuestionGroup\n key={part.id}\n id={part.id}\n title={part.title}\n questions={part.questions ?? []}\n answers={part.answers ?? {}}\n activeIndex={part.activeIndex}\n collapsible={part.collapsible}\n readOnly={part.readOnly}\n foldMotion={foldMotion}\n onCommit={(questionId, answer) =>\n onAnswerQuestion?.(turn.id, part.id, questionId, answer)\n }\n onEdit={(index) => onEditQuestion?.(turn.id, part.id, index)}\n />\n );\n }\n })}\n\n {/* Sent, and nothing back yet. Without this the turn is a question with\n a blank space under it, which reads as nothing having happened.\n Silent on purpose: `useChatTurns` has already announced that a\n response is coming, and a second live region says it twice. */}\n {turn.state === \"responding\" && !turn.ai && parts.length === 0 && (\n <div className={styles.answer}>\n <Loader />\n </div>\n )}\n\n {turn.ai && (\n <div className={styles.answer}>\n <TextHighlighter\n text={turn.ai}\n selectionMode={selectionMode}\n /* What `[^1]` in the answer points at. The first sources part in\n the turn, because an answer stands on one list — a second would\n make the numbering ambiguous the moment both are non-empty. */\n sources={cited}\n onHighlightComplete={(text) => onHighlight?.(turn.id, text)}\n onReplyInThread={onReplyInThread}\n />\n\n {answerActions && turn.state === \"resting\" && (\n <div className={styles.actions}>\n {/* Draws nothing until there are two, so a turn answered once\n looks exactly as it did before there were versions at all. */}\n <Branch\n total={turn.versions?.length ?? 0}\n index={turn.versionIndex ?? 0}\n onSelect={(index) => onShowVersion?.(turn.id, index)}\n />\n <AnswerActions\n text={turn.ai}\n onCopy={onCopy}\n onRegenerate={onRegenerate ? () => onRegenerate(turn.id) : undefined}\n onFeedback={onFeedback ? (verdict) => onFeedback(turn.id, verdict) : undefined}\n feedback={feedback}\n />\n </div>\n )}\n </div>\n )}\n </div>\n </motion.article>\n );\n});\n","/* ─────────────────────────────────────────────\n Context\n\n A gauge, not a feature. It sits in a header or over a composer and it should\n be possible to ignore it until it matters — which is the whole design: quiet\n until the window is nearly full, and then saying what happens next rather\n than only that a number is high.\n ───────────────────────────────────────────── */\n\n.context {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n gap: var(--ick-space-3);\n font-family: var(--ick-font-sans);\n color: var(--ick-context-ink);\n}\n\n.context[data-warn] {\n color: var(--ick-context-ink-warn);\n}\n\n.ring {\n flex-shrink: 0;\n width: var(--ick-context-size);\n height: var(--ick-context-size);\n}\n\n.track {\n fill: none;\n stroke: var(--ick-context-track);\n stroke-width: 2;\n}\n\n.fill {\n fill: none;\n stroke: currentColor;\n stroke-width: 2;\n /* Rounded, so a nearly-empty window is still a mark rather than a speck. */\n stroke-linecap: round;\n transition: stroke-dashoffset var(--ick-duration-slow) var(--ick-ease);\n}\n\n/* The number climbing is the information; the sweep animating to it is not. */\n@media (prefers-reduced-motion: reduce) {\n .fill {\n transition: none;\n }\n}\n\n.label {\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n line-height: 1;\n white-space: nowrap;\n}\n","\"use client\";\n\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Context.module.css\";\n\ntype Labels = {\n name: string;\n /** Between the two numbers: \"128k of 1M\". */\n of: string;\n tokens: string;\n /** Said once it is past `warnAt`, and it should say what happens next. */\n nearlyFull: string;\n};\n\nconst LABELS: Labels = {\n name: \"Context used\",\n of: \"of\",\n tokens: \"tokens\",\n nearlyFull: \"Nearly full — the oldest messages will start dropping out\",\n};\n\nexport interface ContextProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n used: number;\n total: number;\n /** Where it stops being quiet. A fraction, default `0.8`. */\n warnAt?: number;\n /**\n * Drawn beside the ring. The percentage by default; `false` for the ring on\n * its own, which is what a header usually wants.\n */\n label?: ReactNode | false;\n labels?: Partial<Labels>;\n}\n\n/** `128000` → `128k`, `1000000` → `1M`. Nobody reads the zeroes. */\nexport function formatCount(n: number): string {\n if (!Number.isFinite(n) || n < 0) return \"\";\n if (n >= 1_000_000) {\n const m = n / 1_000_000;\n return `${m < 10 ? Number(m.toFixed(1)) : Math.round(m)}M`;\n }\n if (n >= 1000) {\n const k = n / 1000;\n return `${k < 10 ? Number(k.toFixed(1)) : Math.round(k)}k`;\n }\n return String(Math.round(n));\n}\n\nconst RADIUS = 6;\nconst CIRCUMFERENCE = 2 * Math.PI * RADIUS;\n\n/**\n * How full the context window is.\n *\n * Small on purpose. It is a gauge, not a feature, and it earns its place for\n * one reason: it is the only honest way to explain why a long conversation\n * starts forgetting. Without it the forgetting looks like the model being\n * stupid rather than the window being full.\n *\n * Which is why the warning says what happens next rather than only that a\n * number is high. \"82%\" tells somebody nothing they can act on.\n */\nexport function Context({\n used,\n total,\n warnAt = 0.8,\n label,\n labels,\n className,\n ...rest\n}: ContextProps) {\n const label_ = { ...LABELS, ...labels };\n\n /* A total of zero is a window that has not been reported yet, not one that\n is full. Clamped, because a host summing its own tokens will overshoot\n before it notices. */\n const fraction = total > 0 ? Math.min(1, Math.max(0, used / total)) : 0;\n const percent = Math.round(fraction * 100);\n const warn = fraction >= warnAt;\n\n const shown = label === false ? null : (label ?? `${percent}%`);\n const spoken = `${label_.name}: ${percent}%, ${formatCount(used)} ${label_.of} ${formatCount(\n total\n )} ${label_.tokens}${warn ? `. ${label_.nearlyFull}` : \"\"}`;\n\n return (\n <div\n className={[styles.context, className ?? \"\"].filter(Boolean).join(\" \")}\n data-warn={warn || undefined}\n /* A gauge within a known range, which is what a meter is for. The\n numbers are on it rather than only in the label, so assistive\n technology can read the value without parsing a sentence. */\n role=\"meter\"\n aria-valuenow={percent}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={spoken}\n // The same sentence for a pointer, since the ring alone says a fraction\n // and not what to do about it.\n title={spoken}\n {...rest}\n >\n <svg className={styles.ring} viewBox=\"0 0 16 16\" aria-hidden focusable=\"false\">\n <circle className={styles.track} cx=\"8\" cy=\"8\" r={RADIUS} />\n <circle\n className={styles.fill}\n cx=\"8\"\n cy=\"8\"\n r={RADIUS}\n strokeDasharray={CIRCUMFERENCE}\n strokeDashoffset={CIRCUMFERENCE * (1 - fraction)}\n /* From the top rather than from three o'clock, because a gauge that\n starts anywhere else reads as decoration. */\n transform=\"rotate(-90 8 8)\"\n />\n </svg>\n {shown !== null && <span className={styles.label}>{shown}</span>}\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n ArtifactPane\n\n A card, at the size of a column. It takes the card surface rather than the\n ground for the reason a tool call's card does: this is the thing itself, and\n the conversation beside it is what it stands on.\n\n It sets no width and no position — `ChatLayout` owns those, because where\n the pane goes is a fact about the layout and not about the pane. What it\n does own is the geometry inside it: a head that stays and a body that\n scrolls.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.pane {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n border-radius: var(--ick-artifact-pane-radius);\n background: var(--ick-artifact-pane-surface);\n box-shadow: var(--ick-shadow-float);\n font-family: var(--ick-font-sans);\n overflow: hidden;\n}\n\n/* The sheet's handle. Drawn, not pressable — what it says is \"this edge\n moves\", and the thing that moves is the whole sheet. */\n.grabber {\n display: block;\n flex-shrink: 0;\n width: var(--ick-sheet-grabber-width);\n height: var(--ick-sheet-grabber-height);\n margin: var(--ick-space-3) auto 0;\n border-radius: var(--ick-radius-pill);\n background: var(--ick-sheet-grabber);\n}\n\n/* Stays while the body moves. A title that scrolls away takes with it the only\n thing saying what is being read. */\n.head {\n box-sizing: border-box;\n display: flex;\n align-items: flex-start;\n gap: var(--ick-space-4);\n flex-shrink: 0;\n padding: var(--ick-artifact-pane-pad) var(--ick-artifact-pane-pad) var(--ick-space-5);\n}\n\n.headText {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n}\n\n.title {\n margin: 0;\n font-size: var(--ick-text-lg);\n font-weight: var(--ick-weight-medium);\n color: var(--ick-ink);\n}\n\n/* Focused on open, so a keyboard reader is put down on what the pane is rather\n than left behind in the conversation — and given no ring for it, because\n nobody pressed anything. */\n.title:focus {\n outline: none;\n}\n\n.meta {\n margin: var(--ick-space-1) 0 0;\n font-size: var(--ick-text-sm);\n color: var(--ick-ink-soft);\n}\n\n.body {\n box-sizing: border-box;\n flex: 1;\n min-height: 0;\n padding: 0 var(--ick-artifact-pane-pad) var(--ick-artifact-pane-pad);\n overflow-y: auto;\n overscroll-behavior: contain;\n color: var(--ick-ink);\n /* The one thing that decides whether a web sheet feels right.\n\n The sheet is dragged with a finger and the body is scrolled with the same\n finger, so something has to say which. `pan-y` here gives the vertical\n gesture to the scroller whenever it starts inside the body — so reading\n never drags the sheet — while the header, the grabber and the margins\n around them have no scroller under them and drag it. Two surfaces, one\n finger, and no arbitration code at all.\n\n `overscroll-behavior: contain` above is the other half: at the top of the\n body a further pull does not become the page's. */\n touch-action: pan-y;\n}\n\n/* ── Before there is anything to read ───────────────────────────────────── */\n\n.skeleton {\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-5);\n padding-top: var(--ick-space-2);\n}\n\n.line {\n height: var(--ick-space-5);\n border-radius: var(--ick-radius-sm);\n background: var(--ick-artifact-skeleton);\n animation: artifact-pulse 1.6s ease-in-out infinite;\n}\n\n.line:nth-child(2n) { animation-delay: 0.12s; }\n.line:nth-child(3n) { animation-delay: 0.24s; }\n\n@keyframes artifact-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.45; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .line { animation: none; }\n}\n","\"use client\";\n\nimport { useEffect, useId, useRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport { Maximize2, Minimize2, X } from \"lucide-react\";\nimport { Button } from \"../Button/Button\";\nimport styles from \"./ArtifactPane.module.css\";\n\nexport interface ArtifactPaneProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n title: ReactNode;\n /** What it is, under the title. */\n meta?: ReactNode;\n /**\n * What the host renders. Absent, the pane shows its own skeleton — an\n * artifact usually arrives before it is finished, and a pane that opens\n * empty reads as broken rather than as early.\n */\n children?: ReactNode;\n onClose?: () => void;\n /**\n * The one prop that changes behaviour, and it is not about position.\n *\n * Below the layout's breakpoint the pane covers the conversation instead of\n * standing beside it, and covering changes what it *is*: focus has to be\n * held inside it and Escape has to close it, because there is nothing usable\n * behind it. Beside the conversation both would be wrong — trapping focus\n * would lock a reader out of the chat they are still reading.\n *\n * `<ChatLayout>` sets it from its own width. Set it yourself only if you are\n * placing the pane without one.\n */\n modal?: boolean;\n /**\n * Widened. The pane draws the control; `ChatLayout` owns the width, because\n * how much room the pane takes is a fact about the layout.\n */\n expanded?: boolean;\n onToggleExpanded?: () => void;\n closeLabel?: string;\n expandLabel?: string;\n collapseLabel?: string;\n}\n\n/**\n * What the answer produced, in full, beside the conversation.\n *\n * The kit decides that it is on the right and that the conversation makes room\n * for it — this is a pattern rather than a slot, and a chat where the pane\n * arrives somewhere different each time is a chat nobody learns. What the kit\n * does **not** decide is the content: a plan, a document, a table, a diagram.\n * That is the host's, and it is why this takes children.\n *\n * The part worth having in a library is not the box. It is what happens on\n * open: focus has to move here or a keyboard reader is left behind in the\n * conversation, and it must **not** be trapped unless the pane is covering\n * that conversation. Every host gets one of those two wrong.\n */\nexport function ArtifactPane({\n title,\n meta,\n children,\n onClose,\n modal = false,\n expanded = false,\n onToggleExpanded,\n closeLabel = \"Close\",\n expandLabel = \"Widen\",\n collapseLabel = \"Narrow\",\n className,\n ...rest\n}: ArtifactPaneProps) {\n const titleId = useId();\n const pane = useRef<HTMLDivElement>(null);\n\n /* Focus moves to the pane's heading, not into it: a heading is where a\n reader wants to be put down, and focusing the first control would skip\n what the thing is. `tabIndex={-1}` makes it a target without making it a\n stop. */\n const heading = useRef<HTMLHeadingElement>(null);\n useEffect(() => {\n heading.current?.focus({ preventScroll: true });\n }, []);\n\n /* Escape closes it only when it is covering the conversation. Beside it,\n Escape belongs to whatever the reader is actually in. */\n useEffect(() => {\n if (!modal || !onClose) return;\n const onKey = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n event.stopPropagation();\n onClose();\n }\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [modal, onClose]);\n\n /* And focus stays inside it, for the same reason and only then. */\n useEffect(() => {\n if (!modal) return;\n const onFocus = (event: FocusEvent) => {\n const root = pane.current;\n if (root && event.target instanceof Node && !root.contains(event.target)) {\n heading.current?.focus({ preventScroll: true });\n }\n };\n document.addEventListener(\"focusin\", onFocus);\n return () => document.removeEventListener(\"focusin\", onFocus);\n }, [modal]);\n\n return (\n /* A `div` with the role written out, not an `<aside>`.\n \n Beside the conversation this is a complementary region and `<aside>`\n would have said so for free — but the same element has to become a\n dialog when it covers the conversation, and `role=\"dialog\"` is not\n allowed on an `<aside>`. One element that is both means neither can be\n implied. Caught by the axe pass rather than by reading, which is what\n that pass is for. */\n <div\n ref={pane}\n className={[styles.pane, className ?? \"\"].filter(Boolean).join(\" \")}\n aria-labelledby={titleId}\n aria-modal={modal || undefined}\n role={modal ? \"dialog\" : \"complementary\"}\n {...rest}\n >\n {/* A `div`, not a `<header>`. Inside a landmark a `<header>` *is* a\n banner landmark, and a banner has to be top-level — nesting one in\n the pane's own region is a second landmark claiming to be the page's.\n It only became a problem when the pane stopped being an `<aside>`,\n which would have scoped it; the axe pass caught the consequence a\n minute after it caught the cause. */}\n {/* The strip that says \"pull me\", and only where pulling does something.\n Not a control: pressing it does nothing, so it is `aria-hidden` and\n the close button beside it is the reachable way out. */}\n {modal && <span className={styles.grabber} aria-hidden />}\n <div className={styles.head}>\n <div className={styles.headText}>\n <h2 className={styles.title} id={titleId} ref={heading} tabIndex={-1}>\n {title}\n </h2>\n {meta && <p className={styles.meta}>{meta}</p>}\n </div>\n {/* Widening is meaningless while it is covering the conversation:\n there is nothing left to take. */}\n {onToggleExpanded && !modal && (\n <Button\n variant=\"ghost\"\n size=\"s\"\n icon={\n expanded ? <Minimize2 size={15} aria-hidden /> : <Maximize2 size={15} aria-hidden />\n }\n aria-label={expanded ? collapseLabel : expandLabel}\n aria-pressed={expanded}\n onClick={onToggleExpanded}\n />\n )}\n {onClose && (\n <Button\n variant=\"ghost\"\n size=\"s\"\n icon={<X size={15} aria-hidden />}\n aria-label={closeLabel}\n onClick={onClose}\n />\n )}\n </div>\n\n <div className={styles.body}>\n {children ?? <Skeleton />}\n </div>\n </div>\n );\n}\n\n/**\n * What a pane looks like before its content exists.\n *\n * Not a spinner. An artifact is written a line at a time and the shape of what\n * is coming is already known — a title, some paragraphs — so the skeleton says\n * that, and the content replaces it in place rather than after a blank.\n */\nfunction Skeleton() {\n return (\n <div className={styles.skeleton} aria-hidden>\n {[68, 100, 92, 100, 46, 100, 84].map((width, i) => (\n <span key={i} className={styles.line} style={{ width: `${width}%` }} />\n ))}\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n ChatLayout\n\n The conversation, and the pane beside it. The kit decides that the pane is\n on the right and that the conversation makes room — a chat where the pane\n arrives somewhere different each time is a chat nobody learns.\n\n A **container** query rather than a media query, deliberately: the pane\n should answer to the width it actually has, not to the width of the window.\n A kit embedded in a 480px column inside a wide page would otherwise put a\n 420px pane beside a 60px conversation, which is the fault a media query\n cannot see.\n\n ── The shape of the opening ────────────────────────────────────────────────\n\n Two boxes, and which one is which is the whole trick.\n\n `.slot` is the room the pane takes. Its width is the only thing that\n animates, and animating it is what moves the conversation — a transform\n cannot push a sibling, so the property has to be the one the layout is made\n of. `.card` is what the host rendered, and it is held at a width the\n animation never touches, so the document inside it is laid out once instead\n of re-wrapping on every frame of its own entrance.\n\n The slot clips. So as it widens, it uncovers a card that is already the\n right size and already in its final place: the pane appears rather than\n inflating. All the spacing lives on the card for the same reason — padding\n on the slot would be a floor under a width that has to reach zero.\n ───────────────────────────────────────────── */\n\n.layout {\n container-type: inline-size;\n box-sizing: border-box;\n position: relative;\n display: flex;\n min-height: 0;\n flex: 1;\n}\n\n/* The conversation. `min-width: 0` because a flex child's default is its\n content, and a long unbroken line in an answer would push the pane off the\n end rather than scroll inside itself.\n\n A column, so a `Conversation` inside it fills the height. Worth knowing if\n you are wrapping an existing centred page in this: a child with\n `margin: 0 auto` is a flex item that has opted out of stretching, so it will\n be sized by its content rather than by its `max-width` until it is given a\n `width` to cap. */\n.chat {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n min-width: 0;\n min-height: 0;\n flex: 1;\n}\n\n/* The room. Nothing is drawn here — no padding, no background, no gap — so\n that its width can be animated to and from zero and mean it. */\n.slot {\n box-sizing: border-box;\n display: flex;\n /* Glued right, so the slot uncovers the card from its left edge inwards:\n the pane comes in from the side it lives on. */\n justify-content: flex-end;\n flex-shrink: 0;\n min-height: 0;\n overflow: hidden;\n}\n\n/* What the host rendered, at a width the animation never touches.\n\n The margins are the layout's spacing, moved here off the slot: the gap from\n the conversation on the left, the card's clearance from the window on the\n other three sides. Which means the slot's natural width — what it animates\n *to* — is already the whole of the room the pane needs. */\n.card {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n flex: 0 0 auto;\n min-height: 0;\n width: var(--ick-artifact-pane-width);\n margin: var(--ick-artifact-pane-inset) var(--ick-artifact-pane-inset)\n var(--ick-artifact-pane-inset) var(--ick-artifact-pane-gap);\n /* Widening is a resize, not an entrance: the document *should* re-wrap, so\n this one animates the card itself and the slot's `auto` follows it. */\n transition: width var(--ick-duration-slow) var(--ick-ease);\n}\n\n.card[data-expanded] {\n width: var(--ick-artifact-pane-width-wide);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .card {\n transition: none;\n }\n}\n\n/* Narrow: the pane covers the conversation instead of standing beside it.\n Which is not a different component — it is the same pane, told that it is\n now modal, so it holds focus and answers Escape. See `ArtifactPane`.\n\n `left: auto` matters. The slot is still the thing whose width animates, and\n a box pinned on both sides would ignore that width; pinned on the right\n only, it grows leftwards out of the edge it belongs to — the same entrance\n as the wide case, at the size of the whole layout. */\n@container (max-width: 760px) {\n /* Pinned to the bottom and short of the top, which is the whole difference\n between a sheet and a cover: a strip of the conversation stays visible, so\n you can still see what you asked while reading what came back. */\n .slot {\n position: absolute;\n inset-inline: 0;\n top: auto;\n bottom: 0;\n height: var(--ick-sheet-height);\n z-index: 1;\n /* The card is dragged past this edge on the way out. */\n overflow: visible;\n }\n\n .card,\n .card[data-expanded] {\n /* Nothing to expand into: a sheet is already as wide as the screen. */\n width: 100%;\n height: 100%;\n margin: 0;\n /* Dragged, so it must not be waiting for the browser to decide whether\n this gesture was a scroll. */\n touch-action: none;\n }\n\n /* Corners at the top only. The bottom runs off the screen, and rounding it\n shows the page through two notches against the bezel. */\n .card > * {\n border-start-start-radius: var(--ick-sheet-radius);\n border-start-end-radius: var(--ick-sheet-radius);\n border-end-start-radius: 0;\n border-end-end-radius: 0;\n }\n}\n\n/* What the conversation goes under while a sheet is over it. Its own element\n rather than a shadow on the sheet, because it has to fade on its own clock:\n the sheet slides and the scrim dims, and a reader reads the second one\n first. */\n.scrim {\n position: absolute;\n inset: 0;\n z-index: 1;\n background: var(--ick-sheet-scrim);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n}\n","\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { AnimatePresence, motion, useReducedMotion, type Variants } from \"motion/react\";\nimport styles from \"./ChatLayout.module.css\";\n\n/** Below this the pane covers the conversation. Kept with the stylesheet. */\nconst NARROW = 760;\n\nexport interface PaneState {\n /** The pane is covering the conversation rather than standing beside it. */\n narrow: boolean;\n /** It has been widened. Meaningless while `narrow`, which is already full. */\n expanded: boolean;\n toggleExpanded: () => void;\n}\n\nexport interface ChatLayoutProps extends HTMLAttributes<HTMLDivElement> {\n /** The conversation, and whatever else belongs above and below it. */\n children: ReactNode;\n /**\n * The pane, when one is open.\n *\n * Given `narrow`, so it can hold focus and answer Escape once it is covering\n * the conversation rather than standing beside it — the one thing about a\n * pane that is not a matter of taste. And given `expanded` with the control\n * for it, because the *width* belongs to the layout while the button that\n * changes it belongs in the pane's own header, where somebody can find it.\n */\n pane?: (state: PaneState) => ReactNode;\n /**\n * Close it — dragged down, or the conversation behind it pressed.\n *\n * Only used on a phone, where the pane is a sheet. Both of those are ways\n * out that belong to the *layout* rather than to the pane: the pane owns its\n * close button, the layout owns the gesture and the ground around it. Leave\n * it out and the sheet has neither, which is a sheet a thumb cannot dismiss.\n */\n onDismiss?: () => void;\n}\n\n/**\n * How the pane arrives, in two parts that are deliberately not the same.\n *\n * The room opens flat and the pane arrives with a little life in it. That\n * split is the whole design: `width` is what the conversation is laid out\n * against, so any overshoot there re-wraps every line of the answer twice on\n * the way past — a bounce nobody asked for, paid for in text. Transform and\n * opacity cost the layout nothing, so that is where the spring goes.\n *\n * Leaving is quicker than arriving, and in the other order: the pane goes\n * first and the room closes behind it. A thing that leaves as slowly as it\n * came reads as reluctant.\n */\nconst room: Variants = {\n closed: {\n width: 0,\n transition: { type: \"spring\", visualDuration: 0.26, bounce: 0, delay: 0.05 },\n },\n open: {\n width: \"auto\",\n transition: { type: \"spring\", visualDuration: 0.42, bounce: 0 },\n },\n};\n\nconst arriving: Variants = {\n /* Held inside the slot's right margin, so what slides is the pane and not\n the clip: it comes from the edge it lives on. */\n closed: { opacity: 0, x: 24, transition: { duration: 0.16, ease: \"easeIn\" } },\n open: {\n opacity: 1,\n x: 0,\n transition: { type: \"spring\", visualDuration: 0.36, bounce: 0.2, delay: 0.06 },\n },\n};\n\n/**\n * And on a phone, where the pane is a sheet rather than a column beside the\n * conversation.\n *\n * Nothing about the wide entrance survives the change. There, the room opening\n * *is* the animation, because the conversation has to make way; here the\n * conversation stays where it is and the sheet comes up over it, so what moves\n * is a transform and the layout never changes. That also makes it cheap: the\n * wide case re-wraps a column on every frame, this one does not touch layout\n * at all.\n *\n * A little bounce, which the width animation could not have. Nothing is laid\n * out against `y`, so an overshoot costs nothing and is what tells a thumb the\n * thing it flicked has weight.\n */\nconst sheet: Variants = {\n closed: {\n y: \"100%\",\n transition: { type: \"spring\", visualDuration: 0.28, bounce: 0 },\n },\n open: {\n y: 0,\n transition: { type: \"spring\", visualDuration: 0.42, bounce: 0.18 },\n },\n};\n\n/** The wash over the conversation, on its own clock. */\nconst scrim: Variants = {\n closed: { opacity: 0, transition: { duration: 0.18 } },\n open: { opacity: 1, transition: { duration: 0.26 } },\n};\n\n/** The same choreography with the time taken out. */\nconst AT_ONCE = { transition: { duration: 0 } };\nconst still = (variants: Variants): Variants => ({\n closed: { ...(variants.closed as object), ...AT_ONCE },\n open: { ...(variants.open as object), ...AT_ONCE },\n});\n\n/**\n * Where the pane goes, decided once.\n *\n * The kit takes this decision rather than handing over a slot. A preview pane\n * is one of the few patterns every AI chat now has, and the value of a pattern\n * is that it is the same every time: ask for a plan, get a card, press it, the\n * plan opens on the right. A kit that let each host place it would be shipping\n * four chats that behave differently and calling it flexibility.\n *\n * What is left open is the part that actually differs — what is *in* the pane.\n * See `ArtifactPane`.\n */\nexport function ChatLayout({\n children,\n pane,\n onDismiss,\n className,\n ...rest\n}: ChatLayoutProps) {\n const root = useRef<HTMLDivElement>(null);\n const [narrow, setNarrow] = useState(false);\n const [expanded, setExpanded] = useState(false);\n const toggleExpanded = useCallback(() => setExpanded((wide) => !wide), []);\n\n /* Read here rather than through `<MotionConfig reducedMotion=\"user\">`, which\n is what the rest of the kit uses and would not be enough on its own: it\n drops transforms and layout animations, and the slot's `width` is neither.\n A pane that still took four hundred milliseconds to unfold would be the\n one animation on the page ignoring the request. */\n const reduce = useReducedMotion();\n const [slot, card, wash] = useMemo<[Variants, Variants | undefined, Variants]>(() => {\n const outer = narrow ? sheet : room;\n /* Inside a sheet the card has no entrance of its own — the sheet coming up\n *is* the arrival, and a second fade within it reads as two things\n happening for one gesture.\n \n `undefined`, not a stilled copy of the wide one. A stilled variant still\n *runs*, at zero duration: on the way out the document blinked away\n instantly and an empty white sheet slid down after it. Which looks\n exactly like the content being deleted rather than put away. */\n const inner = narrow ? undefined : arriving;\n return reduce\n ? [still(outer), inner && still(inner), still(scrim)]\n : [outer, inner, scrim];\n }, [reduce, narrow]);\n\n /* Measured off this element rather than the window, to agree with the\n container query in the stylesheet. A kit embedded in a narrow column\n inside a wide page is the case a media query gets wrong. */\n useEffect(() => {\n const element = root.current;\n if (!element || typeof ResizeObserver === \"undefined\") return;\n const watch = new ResizeObserver(([entry]) => {\n setNarrow(entry.contentRect.width <= NARROW);\n });\n watch.observe(element);\n return () => watch.disconnect();\n }, []);\n\n const shown = pane?.({ narrow, expanded, toggleExpanded });\n\n return (\n <div\n ref={root}\n className={[styles.layout, className ?? \"\"].filter(Boolean).join(\" \")}\n data-pane={shown ? \"\" : undefined}\n {...rest}\n >\n <div className={styles.chat}>{children}</div>\n {/* `initial={false}`: a layout that mounts with a pane already open did\n not just open one. Anything opened afterwards animates. */}\n <AnimatePresence initial={false}>\n {shown && narrow && onDismiss && (\n /* A wash, not a control.\n \n It was a `<button aria-label=\"Close\">`, which put a second thing\n called Close on the page — the pane's own X is the first — and a\n tab stop in front of a sheet that already traps focus. Pressing\n the ground is a convenience for a thumb; the reachable ways out\n are the button and Escape, and both are in the pane. So this is\n hidden from the tree and does nothing a keyboard can reach. */\n <motion.div\n key=\"scrim\"\n className={styles.scrim}\n variants={wash}\n initial=\"closed\"\n animate=\"open\"\n exit=\"closed\"\n aria-hidden\n onClick={onDismiss}\n />\n )}\n {shown && (\n <motion.div\n /* Keyed on the shape, so crossing the breakpoint builds the right\n one rather than leaving an inline `width` from the other mode on\n a box that is now positioned by its edges. */\n key={narrow ? \"sheet\" : \"beside\"}\n className={styles.slot}\n variants={slot}\n initial=\"closed\"\n animate=\"open\"\n exit=\"closed\"\n >\n {/* Variants rather than props on each element: named states are\n what Motion propagates down a tree, so the pane leaving is one\n decision here instead of two that have to agree. */}\n <motion.div\n className={styles.card}\n data-expanded={(expanded && !narrow) || undefined}\n variants={card}\n /* ── Dragged down to dismiss, and only downwards ──────────────\n `dragConstraints` with `top: 0` and no elastic upwards: a\n sheet that can be pulled *above* its own top edge is a sheet\n with a gap under it, and there is nothing up there to show.\n\n `dragPropagation` off so the gesture does not also become a\n scroll of the conversation underneath.\n\n What decides is distance **or** speed. Distance alone loses a\n quick flick, which is how a thumb actually dismisses things;\n speed alone dismisses a slow deliberate nudge that was meant\n to peek. */\n {...(narrow && onDismiss\n ? {\n drag: \"y\" as const,\n dragConstraints: { top: 0, bottom: 0 },\n dragElastic: { top: 0, bottom: 0.6 },\n dragMomentum: false,\n onDragEnd: (\n _e: unknown,\n info: { offset: { y: number }; velocity: { y: number } }\n ) => {\n if (info.offset.y > 120 || info.velocity.y > 600) onDismiss();\n },\n }\n : {})}\n >\n {shown}\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback, useState } from \"react\";\n\nexport interface Artifacts {\n /** The one on screen, or `null`. */\n openId: string | null;\n open: (id: string) => void;\n close: () => void;\n /** Press the card that is already open and it closes, which is what a reader expects. */\n toggle: (id: string) => void;\n}\n\n/**\n * Which artifact is open.\n *\n * A hook rather than state inside the pane, for the reason `useChatTurns` is a\n * hook: the card in the transcript and the pane beside it are in different\n * parts of the tree and both need the answer. A pane that owned it could not\n * tell a card it was the open one, and a card that owned it could not be told\n * the pane had closed.\n *\n * One at a time. A stack of open artifacts is a tab bar, and a tab bar is a\n * product's decision about how many things somebody is holding at once.\n */\nexport function useArtifacts(initial: string | null = null): Artifacts {\n const [openId, setOpenId] = useState<string | null>(initial);\n\n const open = useCallback((id: string) => setOpenId(id), []);\n const close = useCallback(() => setOpenId(null), []);\n const toggle = useCallback((id: string) => setOpenId((at) => (at === id ? null : id)), []);\n\n return { openId, open, close, toggle };\n}\n","/* ─────────────────────────────────────────────\n EmptyState\n ───────────────────────────────────────────── */\n\n.empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--ick-space-5);\n padding: var(--ick-empty-pad) var(--ick-space-6);\n text-align: center;\n font-family: var(--ick-font-sans);\n}\n\n.icon {\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--ick-ink-faint);\n}\n\n.title {\n font-size: var(--ick-text-lg);\n font-weight: var(--ick-weight-medium);\n color: var(--ick-ink);\n}\n\n.description {\n margin: 0;\n max-width: 42ch;\n font-size: var(--ick-text-md);\n line-height: 1.6;\n color: var(--ick-ink-soft);\n}\n\n/* Wrapping rather than scrolling: an opener that runs off the edge is one\n nobody reads, and there are never enough of them to need a rail. */\n.suggestions {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n gap: var(--ick-space-3);\n margin-top: var(--ick-space-2);\n}\n","\"use client\";\n\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { Button } from \"../Button/Button\";\nimport styles from \"./EmptyState.module.css\";\n\nexport interface EmptyStateProps\n // `title` is a DOM attribute as well as a prop here, and the DOM's is a\n // plain string. Ours wins: a heading can be a node.\n extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n icon?: ReactNode;\n title?: ReactNode;\n description?: ReactNode;\n /**\n * Openers. A blank input asks somebody to think of something; these are for\n * the moment before they have.\n */\n suggestions?: string[];\n /** Given the suggestion's text. Without it, none are drawn. */\n onSuggestion?: (text: string) => void;\n suggestionsLabel?: string;\n children?: ReactNode;\n}\n\n/**\n * What is on screen before anybody has asked anything.\n *\n * Not a landmark and not a heading by default — this sits inside a\n * conversation the host already owns, and claiming a level in their document\n * is not ours to do. `title` renders as text; wrap it yourself if it should be\n * a heading.\n */\nexport function EmptyState({\n icon,\n title,\n description,\n suggestions,\n onSuggestion,\n suggestionsLabel = \"Suggestions\",\n children,\n className,\n ...rest\n}: EmptyStateProps) {\n const chips = onSuggestion ? (suggestions ?? []) : [];\n\n return (\n <div className={[styles.empty, className ?? \"\"].filter(Boolean).join(\" \")} {...rest}>\n {icon && <div className={styles.icon}>{icon}</div>}\n {title && <div className={styles.title}>{title}</div>}\n {description && <p className={styles.description}>{description}</p>}\n\n {chips.length > 0 && (\n <div className={styles.suggestions} role=\"group\" aria-label={suggestionsLabel}>\n {chips.map((text) => (\n /* `primary`, not `secondary`. The secondary button is transparent\n until it is hovered, which for an opener means it reads as a\n line of bold text rather than as something to press — and an\n opener nobody recognises as pressable is one nobody presses. */\n <Button key={text} variant=\"primary\" size=\"m\" onClick={() => onSuggestion?.(text)}>\n {text}\n </Button>\n ))}\n </div>\n )}\n\n {children}\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n Conversation\n\n Three layers: a positioning root, the thing that actually scrolls, and a\n content wrapper whose height is what \"the end\" is measured against. The\n wrapper is not decorative — scrollHeight includes the container's own\n padding, and a chat with a screen-height pad below it would be scrolled to\n a blank space rather than to the answer.\n ───────────────────────────────────────────── */\n\n.root {\n position: relative;\n display: flex;\n flex-direction: column;\n min-height: 0;\n flex: 1;\n}\n\n.viewport {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n overscroll-behavior: contain;\n padding: var(--ick-conversation-pad-block) var(--ick-conversation-pad-inline);\n box-sizing: border-box;\n}\n\n.content {\n display: flex;\n flex-direction: column;\n gap: var(--ick-conversation-gap);\n /* Room under the last turn, so a turn brought to the anchor can actually\n reach the top of the view.\n\n **On the wrapper, never on the scroller.** A scroll container is a flex\n item told to be the height of its parent, and `box-sizing: border-box`\n will not let any box be shorter than its own padding — so a screen-height\n `padding-bottom` on the viewport makes the viewport taller than the page\n that holds it. The demo had exactly that: a 680px page with a 773px\n scroller inside it, the extra 93px clipped away by the page, and the\n follow logic reading `clientHeight` 773 and believing a line drawn at 700\n was on screen. It was not; it was under the clip. Answers stopped\n scrolling the moment they reached the real bottom edge.\n\n Here it costs nothing: the wrapper is content, so it lengthens the scroll\n without touching the size of the thing doing the scrolling. */\n padding-bottom: var(--ick-conversation-tail);\n}\n\n/* ── The way back ───────────────────────────────────────────────────────── */\n\n.jump {\n all: unset;\n font-size: var(--ick-text-md);\n box-sizing: border-box;\n position: absolute;\n left: 50%;\n bottom: var(--ick-space-6);\n z-index: var(--ick-z-sticky);\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border-radius: var(--ick-radius-pill);\n cursor: pointer;\n color: var(--ick-ink);\n background: var(--ick-surface-raised);\n box-shadow: var(--ick-shadow-float);\n outline: 0.5px solid var(--ick-border);\n -webkit-tap-highlight-color: transparent;\n\n /* Not `display: none`: the button has to be able to fade rather than blink,\n and a display change cannot be transitioned. `visibility` is what takes\n it out of hit-testing on the way out. */\n opacity: 0;\n visibility: hidden;\n transform: translateX(-50%) translateY(6px) scale(0.9);\n transition:\n opacity var(--ick-duration-fast) ease,\n transform var(--ick-duration-fast) var(--ick-ease),\n visibility 0s linear var(--ick-duration-fast);\n}\n\n/* Sized from the text scale rather than the 15px its call site asks for, so\n it grows with everything else on a touch device. Inert on a pointer. */\n.jump svg {\n width: var(--ick-icon);\n height: var(--ick-icon);\n}\n\n.jump[data-shown] {\n opacity: 1;\n visibility: visible;\n transform: translateX(-50%) translateY(0) scale(1);\n transition-delay: 0s;\n}\n\n.jump:hover {\n background: var(--ick-surface-raised);\n box-shadow: var(--ick-shadow-2);\n}\n\n.jump[data-shown]:active {\n transform: translateX(-50%) translateY(0) scale(0.92);\n transition-duration: var(--ick-duration-press);\n}\n\n.jump:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: var(--ick-space-1);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .jump {\n transition-property: opacity, visibility;\n transform: translateX(-50%);\n }\n\n .jump[data-shown]:active,\n .jump[data-shown] {\n transform: translateX(-50%);\n }\n}\n","\"use client\";\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { ArrowDown } from \"lucide-react\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport styles from \"./Conversation.module.css\";\n\nexport interface ConversationProps extends HTMLAttributes<HTMLDivElement> {\n children?: ReactNode;\n /**\n * How close to the end still counts as following, in pixels. Below this the\n * view keeps up on its own; above it, the reader is reading and is left\n * alone.\n */\n threshold?: number;\n /** The button offering a way back. `false` for none. */\n scrollButton?: boolean;\n scrollButtonLabel?: string;\n /**\n * The id of an element to hold at the top of the view — a turn, usually.\n *\n * Without it the view follows the end of the content, which is what a chat\n * that stacks downwards wants. With it, the named element is brought to the\n * top and **held** there while the answer grows underneath, so a reader sees\n * their question and its answer and nothing else. Change the id and the view\n * moves to the new one.\n *\n * This needs room to scroll into: an element cannot be brought to the top of\n * a container that ends just below it. That is what a large bottom padding\n * on the viewport is for.\n */\n anchorId?: string;\n /**\n * How far below the top edge the anchor sits, in pixels. A fixed header over\n * the conversation is the usual reason — without it the turn is scrolled\n * neatly underneath and out of sight.\n */\n anchorOffset?: number;\n /**\n * How much room is left under the last turn when the view is at the end, in\n * pixels.\n *\n * Zero puts the last line flush with the bottom edge, which is right for a\n * transcript and wrong for this kit: the last turn *is* the composer — the\n * input is the message — so flush means the thing you type into is jammed\n * against the edge of the screen the moment an answer finishes. On a phone\n * that is also where the browser's own chrome lives.\n */\n endOffset?: number;\n /**\n * The empty room under the last turn, which is what lets a turn be brought\n * to the top at all.\n *\n * An element cannot be scrolled to the top of a container that ends just\n * below it — so `anchorId` only works if there is somewhere to scroll into.\n * That room used to be a guess written in the host's stylesheet, `99vh`,\n * and a guess is wrong in both directions at once: too much of it and the\n * reader can scroll a whole screen into nothing (measured: 697px of blank\n * in a 680px view, with the last turn above the top edge), too little and\n * the newest message cannot reach the top.\n *\n * `\"auto\"` measures it instead:\n *\n * screen - anchorOffset - (the anchored turn's top to the end) - padding\n *\n * and the second term is the whole point. Measuring the **last turn's own\n * height** instead — which this did first — leaves everything between the\n * anchor and the end unaccounted for: the last turn is the composer, 44px\n * of it, so an answer that already filled the screen still got 536px of\n * room under it to scroll into. What the room is for is lifting the\n * anchored turn to the top, and what stands between that turn and the\n * bottom edge is the whole stack under it.\n *\n * Sized this way, the end of the scroll lands exactly where the anchored\n * turn sits at the anchor. So the view comes to rest **at** the end of the\n * scroll and there is nowhere further to go — the room is spent holding the\n * turn up, not left over underneath. It shrinks as the answer grows, which\n * is also why an answer does not have to be chased: the content grows and\n * the room shrinks by the same pixel, so the scroll height does not move\n * and neither does the turn at the top.\n *\n * Never below `endOffset`, so a turn taller than the screen still rests\n * with the composer clear of the bottom edge instead of jammed against it.\n *\n * Zero unless this conversation anchors, because the room exists for\n * anchoring. A number takes it over, in pixels; so does the\n * `--ick-conversation-tail` token, which this writes.\n */\n tail?: \"auto\" | number;\n /** Switch the whole thing off and it is a plain scroll container. */\n follow?: boolean;\n /**\n * For the element that actually scrolls, which is not the one `className`\n * lands on.\n *\n * There are three: a root that positions the button, the viewport that\n * scrolls, and a wrapper whose height is what \"the end\" is measured from.\n * `className` goes to the root, because that is the box a consumer lays out.\n * Padding has to go here instead — on the scroller — or it is not padding\n * inside the scroll at all.\n */\n viewportClassName?: string;\n}\n\nconst THRESHOLD = 64;\n\n/**\n * How long a deliberate move is given before the view goes back to tracking.\n *\n * A ceiling, not a duration — the browser owns how long its own smooth scroll\n * takes. Without one, a travel that never quite arrives (because the answer\n * kept growing and moved the target out from under it) would leave the view\n * refusing to track for the rest of the session.\n */\nconst TRAVEL = 500;\n\n/**\n * How close to the end counts as having **arrived** there.\n *\n * Not the same number as `threshold`, and the difference is the whole of a\n * bug. `threshold` is how far you have to go before the view accepts that you\n * have left — generous on purpose, so a small nudge does not abandon the\n * answer you are reading. Arriving is the opposite question, and answering it\n * with the same generosity means the view re-acquires you while you are still\n * on your way out.\n *\n * Measured on a conversation at rest, which since the room under the last turn\n * became measured is exactly the end of the scroll — so every upward scroll\n * now starts inside that band:\n *\n * wheeled up 20px → wanted 134, ended at 154 dragged back\n * wheeled up 40px → wanted 114, ended at 154 dragged back\n * wheeled up 63px → wanted 91, ended at 154 dragged back\n * wheeled up 80px → wanted 74, ended at 74 free\n *\n * The first 64 pixels of reading back through a conversation were spent\n * fighting, and then it let go all at once — which is what \"it jumps at a\n * certain point\" is. Four pixels is a rounding allowance, not an opinion.\n */\nconst ARRIVED = 4;\n\n/**\n * How far down the scroll an element's top sits, in layout coordinates.\n *\n * `offsetTop` is measured from the nearest **positioned** ancestor, which here\n * is the root — the viewport itself is not positioned, so every turn and the\n * wrapper around them all report against the same origin. Adding the\n * wrapper's own `offsetTop` to a turn's, as this used to, counts the\n * viewport's padding twice: measured in the demo, a conversation ending at\n * 729 was read as ending at 829, and the composer came to rest 100px higher\n * than the `endOffset` it was given.\n *\n * `offsetTop` rather than a rect on purpose. Turns animate in, and a rect\n * reads the transform mid-flight; this is layout, which does not move.\n */\nconst flowTop = (el: HTMLElement, view: HTMLElement): number => el.offsetTop - view.offsetTop;\n\n/** The turn the room is measured against, if it is still on the page. */\nconst held0 = (view: HTMLElement, id: string | null): HTMLElement | null =>\n id ? view.querySelector<HTMLElement>(`[id=\"${CSS.escape(id)}\"]`) : null;\n\n/** Written only when it changes, so an answer does not touch the DOM per frame. */\nconst write = (inner: HTMLElement, applied: { current: number }, room: number): void => {\n if (room === applied.current) return;\n applied.current = room;\n inner.style.setProperty(\"--ick-conversation-tail\", `${Math.round(room)}px`);\n};\n\n/**\n * The scroll container: it keeps up with an answer as it arrives, and stops\n * the instant the reader scrolls away.\n *\n * **It follows the end of the content, not the bottom of the container**, and\n * those are only the same thing when nothing is padded below. This kit's demo\n * carries a screen-height bottom padding so a turn can be pulled to the top,\n * and scrolling to the true bottom there would push the answer off the screen\n * to sit in front of a blank space. Measuring the content instead makes one\n * behaviour correct for both.\n *\n * The other half is not fighting the reader. A naive version listens to the\n * scroll event, cannot tell its own scrolling from theirs, and either drags\n * them back down while they are reading or lets go entirely. This reads intent\n * from the input — a wheel, a drag, a page key — and uses the scroll event\n * only to measure where things ended up.\n */\nexport const Conversation = forwardRef<HTMLDivElement, ConversationProps>(function Conversation(\n {\n children,\n anchorId,\n anchorOffset = 0,\n endOffset = 0,\n tail = \"auto\",\n threshold = THRESHOLD,\n scrollButton = true,\n scrollButtonLabel = \"Jump to the latest\",\n follow = true,\n className,\n viewportClassName,\n onScroll,\n ...rest\n },\n ref\n) {\n const viewport = useRef<HTMLDivElement | null>(null);\n /* The forwarded ref is the *viewport*, not the root. Anyone reaching for a\n ref here wants to scroll something, and the root does not scroll. */\n useImperativeHandle(ref, () => viewport.current as HTMLDivElement, []);\n const content = useRef<HTMLDivElement | null>(null);\n const [following, setFollowing] = useState(true);\n /**\n * The tail this component last wrote, so it can tell it apart from content\n * and so an unchanged measurement does not touch the DOM on every frame of\n * an answer. `-1` because no measurement can produce it: a real one of zero\n * still has to be written down the first time.\n */\n const applied = useRef(-1);\n /**\n * The turn the room is measured against: the last one anchored, held on to\n * after the host lets go.\n *\n * A boolean would not do, and neither would reading `anchorId` live. The\n * demo drops the anchor when an answer settles — holding it for ever is\n * what pinned the composer below the fold — so a measurement that needed a\n * live anchor would lose its subject at the end of every answer and the\n * room would collapse under the reader. Which turn was last brought to the\n * top does not stop being true in between messages.\n */\n const anchors = useRef<string | null>(null);\n /**\n * Whether the room was actually measured against a turn that is on the page.\n *\n * Not the same question as whether this conversation anchors. A host can\n * name an anchor that is not there — a turn removed, an id that never\n * matched — and then there was nothing to size the room against and the end\n * of the scroll means nothing in particular. The end of the *content* is\n * the honest answer in that case.\n */\n const sized = useRef(false);\n\n /**\n * Where the last pixel of the last *turn* sits flush with the bottom edge.\n *\n * The last child rather than the wrapper, because the wrapper carries the\n * tail — the empty room under the conversation that lets a turn reach the\n * top. Measuring the wrapper would count that room as content and park the\n * reader a screen below the thing they are reading.\n */\n const endOfContent = useCallback(() => {\n const view = viewport.current;\n const inner = content.current;\n if (!view || !inner) return 0;\n\n /* When the kit sized the room, the end of the scroll is where the room\n was **aimed** — the anchored turn at the top — so that is where coming\n to rest means something.\n \n Measuring the end of the content instead only agrees with it while the\n answer is longer than the screen. Below that the room is bigger than\n the floor, the end of the scroll moves down with it, and resting at the\n end of the *content* leaves the difference unspent: measured in a\n 1400px window, 585px and 551px of it after the second and third\n answers, with the turn that had just been sent left in the middle of\n the view instead of at the top. The first fix for this was checked\n only against answers longer than the screen, where the two numbers are\n the same and nothing shows. */\n if (tail === \"auto\" && sized.current) {\n return Math.max(0, view.scrollHeight - view.clientHeight);\n }\n\n const last = inner.lastElementChild as HTMLElement | null;\n const bottom = last\n ? flowTop(last, view) + last.offsetHeight\n : /* No element to measure — a consumer whose children are bare text.\n The wrapper stands in, less the tail, which is padding this\n component put there itself and is emphatically not content. */\n flowTop(inner, view) + inner.offsetHeight - applied.current;\n return Math.max(0, bottom + endOffset - view.clientHeight);\n }, [endOffset, tail]);\n\n /**\n * The tail, measured rather than guessed. See the `tail` prop.\n *\n * Written as the token the stylesheet already reads, so there is one\n * mechanism rather than two, and written **on the wrapper** — a scroll\n * container cannot be shorter than its own padding, which is how a 99vh pad\n * once made a 773px scroller inside a 680px page.\n */\n const fitTail = useCallback(() => {\n const view = viewport.current;\n const inner = content.current;\n if (!view || !inner) return;\n if (tail !== \"auto\") return write(inner, applied, Math.max(0, tail));\n\n /* The conversation's own bottom padding is room under the last turn too,\n and it is already inside `scrollHeight`. Counting it twice puts the end\n of the scroll below the anchor by exactly that much, which is somewhere\n to scroll and nothing to see there. */\n const pad = parseFloat(getComputedStyle(view).paddingBottom) || 0;\n /* Never less than the end gap, so a turn taller than the screen still\n comes to rest with the composer clear of the bottom edge rather than\n jammed against it. */\n const floor = Math.max(0, endOffset - pad);\n\n const last = inner.lastElementChild as HTMLElement | null;\n const held = held0(view, anchors.current);\n sized.current = !!last && !!held;\n if (!sized.current || !last || !held) return write(inner, applied, floor);\n\n /* **From the anchored turn to the end of the conversation** — not the last\n turn's own height, which is what this measured first and is wrong by\n everything in between. The last turn is the composer, 44px of it, so an\n answer that already filled the screen still got a screen of room under\n it: 536px of nothing to scroll into after every answer.\n \n What the room is for is lifting the anchored turn to the top, and what\n stands between it and the bottom edge is the whole stack under it. So\n that is what comes off. */\n const stack = flowTop(last, view) + last.offsetHeight - flowTop(held, view);\n write(inner, applied, Math.max(floor, view.clientHeight - anchorOffset - stack - pad));\n }, [tail, anchorOffset, endOffset]);\n\n /**\n * Where the view wants to be: the anchor's top — unless holding it there\n * would push the anchored turn's own bottom off the screen.\n *\n * The second half is what this was missing. An anchor pins the turn you sent\n * to the top and holds it there while the answer is written underneath,\n * which is right and is the whole point of anchoring. It stops being right\n * the moment the turn is longer than the screen: the anchor does not move,\n * so the target does not move, and every line after the first screenful is\n * written below the fold with nothing to bring it back.\n *\n * So the anchor holds the top only while its own end is still in view, and\n * gives way to that end when it is not. The handover lands exactly where it\n * should, because the two are the same number at the moment the turn grows\n * to the height of the viewport.\n *\n * Note what this is **not**: the end of the *conversation*. Taking that\n * would follow whatever came after the anchored turn, and anchoring an older\n * turn — a jump to a message further up — would snap straight back to the\n * bottom. Only the anchored turn's own bottom is allowed to overrule its\n * top.\n */\n const target = useCallback(() => {\n const view = viewport.current;\n if (!view) return 0;\n\n if (anchorId) {\n const el = view.querySelector<HTMLElement>(`[id=\"${CSS.escape(anchorId)}\"]`);\n if (el) {\n const max = Math.max(0, view.scrollHeight - view.clientHeight);\n const from = flowTop(el, view);\n const top = from - anchorOffset;\n // Where the anchored turn's last pixel sits flush with the bottom.\n const end = from + el.offsetHeight - view.clientHeight;\n return Math.max(0, Math.min(Math.max(top, end), max));\n }\n }\n return endOfContent();\n }, [anchorId, anchorOffset, endOfContent]);\n\n /**\n * True while the way-back button's own scroll is still travelling.\n *\n * Without it that scroll never happened. `jump` asks for `behavior: smooth`\n * and then sets `following`, which re-runs the effect below, whose first act\n * is `view.scrollTop = target()` — an assignment, which cancels a smooth\n * scroll on the spot. Measured: the view went from 0 to 1088 inside 80ms,\n * so the button teleported instead of travelling and a reader lost their\n * place with nothing to follow.\n *\n * A ref rather than state: nothing renders differently for it, and a render\n * in the middle of a scroll is the last thing this wants.\n */\n const travelling = useRef(0);\n /** The last anchor this has already moved to, so it only moves once. */\n const moved = useRef<string | null>(null);\n\n const jump = useCallback(\n (smooth: boolean) => {\n const view = viewport.current;\n if (!view) return;\n const gentle = smooth && !prefersReducedMotion();\n travelling.current = gentle ? performance.now() + TRAVEL : 0;\n view.scrollTo({ top: target(), behavior: gentle ? \"smooth\" : \"auto\" });\n setFollowing(true);\n },\n [target]\n );\n\n /* ── The room to move into ─────────────────────────────────────────────\n Declared before the effect that keeps up, and that ordering is the point:\n observers fire in registration order, so the tail is the right size\n before anything reads where the end of the scroll now is. */\n useLayoutEffect(() => {\n /* Latched rather than read live. The demo lets go of the anchor once an\n answer settles — holding it for ever is what pinned the composer below\n the fold — so a tail that existed only while `anchorId` was set would\n collapse at the end of every answer and take the view with it. What is\n true is that this conversation *anchors*, and that does not stop being\n true between messages. */\n if (anchorId) anchors.current = anchorId;\n const view = viewport.current;\n const inner = content.current;\n fitTail();\n if (!view || !inner || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(fitTail);\n // The wrapper for the last turn's height, the viewport for the screen's.\n observer.observe(inner);\n observer.observe(view);\n return () => observer.disconnect();\n }, [anchorId, fitTail]);\n\n /* ── Keeping up ────────────────────────────────────────────────────────\n Layout effect, and the reason matters: run after paint and the answer is\n drawn one frame lower before the scroll catches up, which reads as a\n shudder on every frame of every answer. */\n useLayoutEffect(() => {\n /* An anchor is an instruction, not a preference: the host has said which\n turn to hold, so it is honoured whether or not the reader was following\n the end. Gating it on `following` meant that pressing anything — which\n now releases the follow, see below — also stopped the next message from\n being taken to the top. */\n if (!follow || (!following && !anchorId)) return;\n const view = viewport.current;\n const inner = content.current;\n if (!view || !inner || typeof ResizeObserver === \"undefined\") return;\n\n const keepUp = () => {\n const want = target();\n /* Let the way-back button's scroll finish rather than snapping past it —\n and notice here when it has arrived, rather than waiting for a scroll\n event. jsdom fires none, so a flag cleared only by that event stays\n set for ever and the view stops keeping up with the answer entirely.\n A test said so. */\n if (travelling.current) {\n if (performance.now() < travelling.current && Math.abs(view.scrollTop - want) > 1) return;\n travelling.current = 0;\n }\n\n /* ── Growth while nothing is being written ────────────────────────\n Tracking an answer has to be instant: easing the arrival of text\n means the line being read slides for a third of a second. But not\n everything that grows the conversation is an answer arriving.\n\n When one finishes, the next turn — the empty composer — is appended,\n and the content grows by its height in a single frame. Measured on\n the demo at 1100×700, at the moment the turn count went from one to\n two: `scrollTop` 0 → 182 in one frame and the message that had been\n sent leapt from 100 to −82, off the top of the view. Nothing was\n being read at that instant; the answer had just ended.\n\n The component can tell the two apart without being told, because the\n host lets go of the anchor when an answer settles. An anchor held\n means something is still being written, and growth is tracked.\n\n **`anchors.current` and not just `!anchorId`.** A conversation that\n never anchors — a plain transcript following its own end — has no\n anchor to hold at any moment, and there growth really is the answer\n arriving and must stay instant. Two tests said so, immediately. What\n this is about is an anchoring conversation between turns. */\n const gap = want - view.scrollTop;\n if (anchors.current && !anchorId && Math.abs(gap) > threshold && !prefersReducedMotion()) {\n travelling.current = performance.now() + TRAVEL;\n view.scrollTo({ top: want, behavior: \"smooth\" });\n return;\n }\n\n view.scrollTop = want;\n };\n\n /* ── Arriving at a new turn ──────────────────────────────────────────\n A sent message travels to the top rather than appearing there.\n\n Sending one adds a whole turn at once, so the content grows by its\n height in a single frame and the scroll used to be assigned the new\n position in that same frame. Measured across two answers at 1120×680:\n three moves over 24px in the whole session and every one of them a\n single frame — 154px, 416px, 172px — while the reasoning folding away\n at the end was gradual and cost nothing. The sharpness was never the\n settle; it was the send.\n\n So the one move that is a **decision** gets to be a journey, and the\n thousand that are tracking stay instant. Only when it is far enough to\n be worth watching — a short hop reads better snapped than eased. */\n const fresh = anchorId !== null && anchorId !== undefined && anchorId !== moved.current;\n if (anchorId) moved.current = anchorId;\n if (fresh && !prefersReducedMotion() && Math.abs(view.scrollTop - target()) > threshold) {\n travelling.current = performance.now() + TRAVEL;\n view.scrollTo({ top: target(), behavior: \"smooth\" });\n } else {\n keepUp();\n }\n\n const observer = new ResizeObserver(keepUp);\n observer.observe(inner);\n return () => observer.disconnect();\n /* `anchorId` is in here on purpose: a new turn means the view moves to it,\n and it moves whether or not the reader had scrolled away from the last\n one. Sending a message is asking to be taken to it. */\n }, [follow, following, target, anchorId, threshold]);\n\n /* ── Letting go ────────────────────────────────────────────────────────\n Intent, read from the input rather than inferred from the scroll event.\n A wheel or a drag upwards means the reader wants to be somewhere else,\n and that is true before the scroll has even happened. */\n useEffect(() => {\n if (!follow) return;\n const view = viewport.current;\n if (!view) return;\n\n const away = () => {\n // A reader who touches the view during the journey has taken it over.\n travelling.current = 0;\n if (Math.abs(view.scrollTop - target()) > threshold) setFollowing(false);\n };\n\n const onWheel = (event: WheelEvent) => {\n // Upwards means \"not here\" only when the place to be is above. With an\n // anchor held at the top, scrolling up is often how a reader returns to\n // it, so the distance decides rather than the direction.\n if (anchorId) away();\n else if (event.deltaY < 0) setFollowing(false);\n };\n const onKey = (event: KeyboardEvent) => {\n if ([\"ArrowUp\", \"PageUp\", \"Home\", \"ArrowDown\", \"PageDown\", \"End\"].includes(event.key)) {\n // Same reasoning: with an anchor, any key that moves the view is only\n // \"away\" if it lands somewhere else.\n if (anchorId) requestAnimationFrame(away);\n else if ([\"ArrowUp\", \"PageUp\", \"Home\"].includes(event.key)) setFollowing(false);\n }\n };\n\n /**\n * And a press inside the conversation, which is the one this was missing.\n *\n * Everything that folds open — a tool call, the reasoning, the sources —\n * grows the content, and growth is what makes the view keep up. So opening\n * one while sitting at the end pinned the *end* and dragged the thing you\n * had just opened up and off the top: measured, `scrollTop` 29 to 131 and\n * the header from 160px down the view to 58, which reads as the panel\n * opening upwards. With a long panel the header leaves the screen\n * entirely, and the control to shut it again with it.\n *\n * A press on **something that folds** is intent in exactly the way a wheel\n * is — you reached for this, here — so it lets go of the end and the\n * browser's own scroll anchoring keeps what you pressed where it was.\n *\n * Only those. The first cut released on any press at all, and that is too\n * much: clicking into the composer released it too, so when the answer\n * settled there was nothing left to bring the view back — and settling is\n * also when the reasoning block folds itself away, so the content shrank\n * by its height and everything above dropped into view. It read as the\n * conversation jumping to show an older message. A control that folds is\n * the one press that changes the layout under the reader, and it is the\n * only one that has to be answered this way.\n */\n const pressed = (event: PointerEvent) => {\n const on = event.target;\n if (on instanceof Element && on.closest(\"[aria-expanded]\")) setFollowing(false);\n };\n\n view.addEventListener(\"wheel\", onWheel, { passive: true });\n view.addEventListener(\"touchmove\", away, { passive: true });\n view.addEventListener(\"keydown\", onKey);\n view.addEventListener(\"pointerdown\", pressed, { passive: true });\n return () => {\n view.removeEventListener(\"wheel\", onWheel);\n view.removeEventListener(\"touchmove\", away);\n view.removeEventListener(\"keydown\", onKey);\n view.removeEventListener(\"pointerdown\", pressed);\n };\n }, [follow, threshold, target, anchorId]);\n\n /**\n * Back at the end by any route — dragging the bar, momentum, the button.\n *\n * **Arrived, not nearly.** This is the one place the component is allowed to\n * infer intent from the scroll event rather than from the input, and it has\n * to be strict about it: a reader on their way *up* passes through every\n * distance between here and there, so a generous band catches them mid-\n * gesture and hands them back to the bottom. See `ARRIVED`.\n */\n const handleScroll = useCallback(\n (event: React.UIEvent<HTMLDivElement>) => {\n onScroll?.(event);\n if (!follow) return;\n const view = viewport.current;\n if (!view) return;\n if (Math.abs(view.scrollTop - target()) <= ARRIVED) {\n // Arrived. Following resumes and the effect may set the scroll again.\n travelling.current = 0;\n setFollowing(true);\n }\n },\n [follow, onScroll, target]\n );\n\n const detached = follow && !following;\n\n return (\n <div className={[styles.root, className ?? \"\"].filter(Boolean).join(\" \")}>\n <div\n ref={viewport}\n className={[styles.viewport, viewportClassName ?? \"\"].filter(Boolean).join(\" \")}\n onScroll={handleScroll}\n {...rest}\n >\n <div ref={content} className={styles.content}>\n {children}\n </div>\n </div>\n\n {scrollButton && (\n <button\n type=\"button\"\n className={styles.jump}\n data-shown={detached || undefined}\n /* Out of the tab order while it is not offering anything, rather\n than merely invisible: a hidden control that still takes focus is\n a dead stop in the middle of a page. */\n tabIndex={detached ? 0 : -1}\n aria-hidden={detached ? undefined : true}\n onClick={() => jump(true)}\n aria-label={scrollButtonLabel}\n >\n <ArrowDown size={15} aria-hidden />\n </button>\n )}\n </div>\n );\n});\n","/**\n * Where the reply panel goes, as arithmetic.\n *\n * A function rather than ten lines inside the render, and the reason is that\n * it could not otherwise be tested. The panel's geometry reaches the DOM\n * through Motion's `animate` prop, and Motion animations never complete under\n * jsdom — so a test that reads `panel.style.left` reads the `initial` value\n * for ever, which is the anchor. The first guard written for this passed\n * against the broken version because of it.\n */\n\nexport interface Anchor {\n left: number;\n top: number;\n width: number;\n}\n\nexport interface Placement {\n x: number;\n y: number;\n width: number;\n /**\n * The room it has between where it starts and the bottom of the screen.\n *\n * The panel hangs off a phrase and grows downwards as the thread fills, and\n * nothing used to stop it growing past the bottom edge. Measured on a 390px\n * phone with a passage 556px down: after one reply the panel ended at 920\n * in an 844px screen, and every message after that pushed more of it\n * further out of reach. This is what it may use, so the thread scrolls\n * inside it instead of running off the end of the world.\n */\n maxHeight: number;\n}\n\n/** Its clearance from every edge of the screen. */\nconst PADDING = 24;\n/** Narrower than this and a thread is a column of two-word lines. */\nconst IDEAL = 480;\n/** Room either side of the passage it hangs off, when that is the wider one. */\nconst AROUND = 80;\n/**\n * The least it is worth opening at: a heading, a line of the passage, and\n * somewhere to type.\n *\n * A phrase near the bottom of the screen would otherwise be answered with a\n * panel twenty pixels tall. Below this the panel stops following the phrase\n * down and sits where it can still be used — which is the one case where\n * being beside the passage has to give way to being usable at all.\n */\nconst LEAST = 260;\n\n/**\n * **Fit first, then place.** That order is the whole of it.\n *\n * The version before this widened to 480 whatever the screen was, then ran two\n * clamps in a row: push the left edge in to the padding, then push the right\n * edge in to `screenWidth - padding`. For a box that does not fit, the second\n * always undoes the first — on a 390px phone it finished at `390 - 24 - 480`,\n * so the panel sat at **x = −114** with its heading off the side of the world.\n * Clamping a box into a space smaller than itself has no answer; the box has\n * to be made smaller first.\n */\nexport function placePanel(anchor: Anchor, screenWidth: number, screenHeight = Infinity): Placement {\n const room = Math.max(screenWidth - PADDING * 2, 0);\n const width = Math.min(Math.max(anchor.width + AROUND, IDEAL), room);\n\n // Centred on the passage, then pulled inside whichever edge it crossed.\n const centred = anchor.left + anchor.width / 2 - width / 2;\n const x = Math.min(Math.max(centred, PADDING), screenWidth - PADDING - width);\n\n /* Above the passage, but never off the top — a phrase marked in an answer's\n first line would otherwise open its thread where nobody can read it — and\n never so far down that there is no room left underneath. */\n const lowest = Math.max(PADDING, screenHeight - PADDING - LEAST);\n const y = Math.min(\n Math.max(anchor.top - PADDING, Math.min(PADDING, screenHeight)),\n Number.isFinite(lowest) ? lowest : Infinity\n );\n\n /* And what is left between there and the bottom edge. Finite screens only:\n `screenHeight` defaults to `Infinity` for callers that do not care, and a\n panel told it may be infinitely tall is a panel with no constraint, which\n is what it had before. */\n const maxHeight = Number.isFinite(screenHeight)\n ? Math.max(LEAST, screenHeight - y - PADDING)\n : Infinity;\n\n return { x, y, width, maxHeight };\n}\n","/* ─────────────────────────────────────────────\n ReplyThreadPopup\n\n This component had no stylesheet at all — seventeen inline `style={{}}`\n blocks, which meant nothing about it could be themed and nothing about it\n respected the tokens the rest of the kit reads.\n\n What stays inline is only what changes per render: the panel's animated\n geometry, which Motion owns.\n ───────────────────────────────────────────── */\n\n.backdrop {\n position: fixed;\n inset: 0;\n z-index: var(--ick-z-modal);\n display: block;\n pointer-events: auto;\n}\n\n.panel {\n position: absolute;\n box-sizing: border-box;\n /* A column, so the thread inside can be told to take what is left rather\n than a number. The panel's own ceiling comes from `placePanel`, which\n knows how much room there is between where it opens and the bottom of\n the screen — see `maxHeight` there. */\n display: flex;\n padding: var(--ick-space-2);\n overflow: hidden;\n transform-origin: center center;\n z-index: var(--ick-z-raised);\n /* Set here rather than animated. Motion was fading the background and the\n shadow in alongside the panel's own opacity, which is the same picture\n with two more properties to keep in step — and two more colours that\n could not come from a token. */\n background-color: var(--ick-surface-raised);\n box-shadow: var(--ick-shadow-modal);\n}\n\n/* ── On a phone ──────────────────────────────────────────────────────────\n The thread stops hanging off the phrase and comes up from the bottom.\n\n Hanging off a phrase works where there is somewhere to hang: beside the\n passage, above it, with room underneath. A phone has none of that. Measured\n on a 390×844 screen with a passage 556px down, the panel ended at 920 after\n a single reply — 76px past the bottom edge, and every message after it\n pushed more out of reach. Capping the height stops it running off the\n screen and turns it into a letterbox instead: 289px of thread, in the lower\n third, under a phrase you can no longer see.\n\n So it becomes what the artifact pane already becomes here, with the same\n tokens and the same gesture. Geometry is stated here rather than animated,\n because Motion writes `left`/`top`/`width` inline and no rule in this file\n could outrank that — see the note on the variants. */\n.panel[data-sheet] {\n position: fixed;\n inset: auto 0 0 0;\n width: auto;\n /* As tall as the thread, and no taller than the artifact sheet would be.\n \n Fixed at the sheet height it left most of a screen of white under the\n input on a thread with one reply in it — which is what a thread usually\n has, since the whole idea is one question about one passage. A sheet that\n is mostly empty reads as something that failed to load. It grows from the\n bottom edge as the thread fills, which is the direction a sheet grows. */\n height: auto;\n max-height: var(--ick-sheet-height);\n /* Top corners only. The bottom two are off the screen, and rounding them\n puts a sliver of the page under a sheet that is supposed to be resting on\n the edge. */\n border-radius: var(--ick-sheet-radius) var(--ick-sheet-radius) 0 0;\n /* The drag is the sheet's; the thread inside keeps its own scrolling. */\n touch-action: none;\n}\n\n.panel[data-sheet] .feed {\n /* No ceiling of its own here: the sheet's is the one that counts, and two\n would leave the shorter one deciding for reasons nobody can see. */\n max-height: none;\n height: auto;\n overscroll-behavior: contain;\n touch-action: pan-y;\n}\n\n/* Says it can be pulled, and gives a thumb somewhere to pull that is not a\n control. Same shape as the artifact sheet's. */\n.grabber {\n display: block;\n flex-shrink: 0;\n width: var(--ick-sheet-grabber-width);\n height: var(--ick-sheet-grabber-height);\n margin: 0 auto var(--ick-space-2);\n border-radius: var(--ick-radius-pill);\n background: var(--ick-sheet-grabber);\n}\n\n/* The panel is `replyTargetWidth` wide with 4px of padding, so the column is\n simply the space left over. It used to be told `replyTargetWidth - 8` from\n JavaScript. */\n.column {\n width: 100%;\n display: flex;\n flex-direction: column;\n /* `min-height: 0` on every link of the chain, or a flex item refuses to be\n shorter than its content and the panel grows past its own ceiling\n regardless. */\n min-height: 0;\n flex: 1;\n}\n\n/* ── Header ─────────────────────────────────────────────────────────────── */\n\n.header {\n display: flex;\n align-self: stretch;\n align-items: center;\n justify-content: center;\n gap: var(--ick-space-2);\n border-radius: var(--ick-radius-lg);\n padding: 6px var(--ick-space-6) var(--ick-space-5);\n}\n\n.headerLabel {\n display: flex;\n flex: 1;\n align-items: center;\n gap: var(--ick-space-2);\n color: var(--ick-ink-soft);\n}\n\n.headerIcon {\n width: 16px;\n height: auto;\n overflow: visible;\n flex-shrink: 0;\n /* Inherits, so the icon and its label cannot drift apart. */\n fill: currentColor;\n}\n\n.headerText {\n margin: 0;\n font-family: var(--ick-font-sans);\n font-size: var(--ick-text-xs);\n font-weight: var(--ick-weight-medium);\n letter-spacing: 0.01em;\n line-height: 14px;\n}\n\n.headerActions {\n display: flex;\n align-items: center;\n gap: var(--ick-space-4);\n}\n\n/* ── Feed ───────────────────────────────────────────────────────────────── */\n\n.feedClip {\n position: relative;\n border-radius: var(--ick-radius-xl);\n overflow: hidden;\n min-height: 0;\n flex: 1;\n}\n\n/* The fades that say there is more above or below. */\n.fade {\n position: absolute;\n left: 0;\n right: 0;\n height: 48px;\n pointer-events: none;\n z-index: var(--ick-z-sticky);\n opacity: 0;\n transition: opacity var(--ick-duration-base) ease;\n}\n\n.fadeTop {\n top: 0;\n background: linear-gradient(to bottom, var(--ick-surface) 0%, transparent 100%);\n}\n\n.fadeBottom {\n bottom: 0;\n background: linear-gradient(to top, var(--ick-surface) 0%, transparent 100%);\n}\n\n.fade[data-visible] {\n opacity: 1;\n}\n\n.feed {\n background-color: var(--ick-surface);\n border-radius: var(--ick-radius-xl);\n /* Two ceilings, and they mean different things. This one is taste — a\n thread taller than this is a conversation and belongs in the main feed.\n The other is the screen, and it is the panel's. */\n max-height: 640px;\n height: 100%;\n box-sizing: border-box;\n overflow-y: auto;\n scrollbar-width: none;\n}\n\n/* This used to be a `<style>` element rendered inside the component, carrying\n `div::-webkit-scrollbar { display: none }` — an unscoped rule that hid the\n scrollbar on every div in the host application for as long as the thread\n was open. */\n.feed::-webkit-scrollbar {\n display: none;\n}\n\n.feedInner {\n display: flex;\n flex-direction: column;\n align-items: start;\n padding: var(--ick-space-6);\n}\n\n/* ── Text ───────────────────────────────────────────────────────────────── */\n\n.quote,\n.answer {\n display: inline-block;\n color: var(--ick-ink);\n font-family: var(--ick-font-sans);\n font-size: var(--ick-text-md);\n letter-spacing: 0.01em;\n line-height: 150%;\n}\n\n/* ── Turns ──────────────────────────────────────────────────────────────── */\n\n.turn {\n display: flex;\n flex-direction: column;\n width: 100%;\n gap: var(--ick-space-8);\n margin-top: var(--ick-space-8);\n}\n\n/* A settled turn sits to the right, as a bubble; one still being written\n stretches, because it is an input. */\n.turnRow {\n display: flex;\n align-self: flex-end;\n justify-content: flex-end;\n max-width: 100%;\n width: auto;\n}\n\n.turnRow[data-editing] {\n align-self: stretch;\n justify-content: stretch;\n width: 100%;\n}\n\n/* The input has a fixed width of its own, so stretching the row is not enough\n — the child has to be told. Reached as a direct child rather than by class,\n since its class name is hashed and belongs to another module. */\n.turnRow[data-editing] > * {\n width: 100%;\n max-width: 100%;\n}\n\n/* Screen readers only: names the dialog together with the quoted passage. */\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n","\"use client\";\n\nimport { useState, useRef, useEffect, useId } from \"react\";\nimport { motion, MotionConfig } from \"motion/react\";\nimport { X, Save } from \"lucide-react\";\nimport { ChatInput, defaultInlineAnimConfig, type ChatInputHandle } from \"../ChatInput/ChatInput\";\nimport { useChatTurns, type ChatTurn, type SendContext } from \"../useChatTurns/useChatTurns\";\n\n/** @deprecated Use `ChatTurn`. Kept so existing imports keep resolving. */\nexport type Turn = ChatTurn;\nimport { Button } from '../Button/Button';\nimport { placePanel } from \"./placePanel\";\nimport styles from \"./ReplyThreadPopup.module.css\";\n\nexport interface ReplyThreadPopupProps {\n activeReply: { text: string; rect: DOMRect };\n onClose: () => void;\n onSave?: () => void;\n /**\n * Produce the reply to a message sent inside the thread. Receives the\n * message and the passage the thread hangs off. Required — the popup has no\n * answers of its own to fall back on.\n */\n onSendMessage: (\n message: string,\n quotedText: string,\n context: SendContext\n ) => AsyncIterable<string> | Promise<string> | string;\n}\n\n/**\n * Below this the thread stops hanging off the phrase and comes up from the\n * bottom instead. Kept with the stylesheet, and with `ChatLayout`'s own\n * breakpoint — two sheets in one kit that disagree about what a phone is\n * would be worse than either choice.\n */\nconst NARROW = 760;\n\n/** Coming up from the bottom, and going back down. */\nconst sheet = {\n closed: { y: \"100%\", opacity: 1, transition: { type: \"spring\" as const, visualDuration: 0.28, bounce: 0 } },\n open: { y: 0, opacity: 1, transition: { type: \"spring\" as const, visualDuration: 0.42, bounce: 0.18 } },\n};\n\n/** Everything inside the panel that can take focus, in tab order. */\nconst FOCUSABLE =\n 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [contenteditable]:not([contenteditable=\"false\"]), [tabindex]:not([tabindex=\"-1\"])';\n\nexport function ReplyThreadPopup({ activeReply, onClose, onSave, onSendMessage }: ReplyThreadPopupProps) {\n const [threadFade, setThreadFade] = useState<\"none\" | \"top\" | \"bottom\" | \"both\">(\"none\");\n const threadActiveInputRef = useRef<ChatInputHandle>(null);\n const threadFeedRef = useRef<HTMLDivElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n /**\n * Where focus came from. Read during the first render rather than in an\n * effect: a child's effects run before its parent's, and the thread's input\n * focuses itself in one of them — so by the time an effect here could look,\n * the answer would already be \"the input\", and closing would restore focus\n * to an element that no longer exists.\n */\n const [returnFocusTo] = useState<HTMLElement | null>(() =>\n typeof document === \"undefined\" ? null : (document.activeElement as HTMLElement | null)\n );\n const labelId = useId();\n const quoteId = useId();\n const animConfig = defaultInlineAnimConfig;\n\n /**\n * Whether this is a phone, watched rather than read once.\n *\n * `matchMedia` on the window rather than a container query like\n * `ChatLayout` uses, and the difference is real: that pane lives inside the\n * conversation's own column and has to answer to it, while this is fixed\n * over the whole screen. What matters here is the screen.\n */\n /* Guarded on the **result**, not just on the function. `?.()` says the\n method may be missing; it says nothing about what comes back, and a test\n environment that stubs `matchMedia` with something returning nothing\n brought the whole dialog down on `undefined.matches`. An axe test found\n it, which is the sort of thing the accessibility pass is quietly good\n for. */\n const [narrow, setNarrow] = useState(() =>\n typeof window === \"undefined\"\n ? false\n : (window.matchMedia?.(`(max-width: ${NARROW}px)`)?.matches ?? false)\n );\n useEffect(() => {\n const query = window.matchMedia?.(`(max-width: ${NARROW}px)`);\n if (!query?.addEventListener) return;\n const answer = () => setNarrow(query.matches);\n answer();\n query.addEventListener(\"change\", answer);\n return () => query.removeEventListener(\"change\", answer);\n }, []);\n\n // Turn state, streaming and the reveal are the same problem here as in the\n // main feed, so they come from the same place rather than being written\n // twice. The quoted passage is closed over and handed to the host app.\n const quoted = activeReply.text;\n const {\n turns: threadTurns,\n setDraft,\n submit,\n stop,\n beginEdit,\n } = useChatTurns({\n onSend: (message, context) => onSendMessage(message, quoted, context),\n });\n\n /**\n * A dialog owns focus while it is open, and gives it back when it closes.\n *\n * Without the first half, someone who opened this from the keyboard is left\n * behind on a page they can no longer see. Without the second, closing it\n * drops them at the top of the document with no idea where they were.\n */\n useEffect(() => {\n // The input, not the panel: writing a reply is the only reason to be here.\n if (threadActiveInputRef.current) threadActiveInputRef.current.focus();\n else panelRef.current?.focus();\n\n return () => {\n if (returnFocusTo?.isConnected) returnFocusTo.focus();\n };\n }, [returnFocusTo]);\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Escape\") {\n // Something inside may have wanted it first — a highlight menu open on\n // one of the thread's own answers, for instance. Those mark the event as\n // handled; this only acts on what is left.\n if (e.defaultPrevented) return;\n e.preventDefault();\n onClose();\n return;\n }\n\n if (e.key !== \"Tab\") return;\n const items = [...(panelRef.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? [])];\n if (items.length === 0) return;\n\n const first = items[0];\n const last = items[items.length - 1];\n const active = document.activeElement;\n\n // Wrap at both ends, so tab cannot walk out of the dialog onto a page the\n // reader can no longer see.\n if (e.shiftKey && active === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && active === last) {\n e.preventDefault();\n first.focus();\n }\n };\n\n const updateThreadFade = () => {\n const wrap = threadFeedRef.current;\n if (!wrap) return;\n \n /* Whether there is anything out of sight, which is the only question the\n fades answer.\n \n This used to also require `clientHeight >= 630` — the thread's old\n fixed ceiling, written again as a number. The panel takes the room the\n screen leaves it now, so on a phone it is rarely 630 tall and the fades\n would have stopped appearing exactly where they are most needed: the\n short panel is the one whose content does not fit. Overflow is the\n question; height was a proxy for it. */\n const hasOverflow = wrap.scrollHeight - wrap.clientHeight > 2;\n \n if (!hasOverflow) { \n setThreadFade(\"none\"); \n return; \n }\n \n const atTop = wrap.scrollTop <= 2;\n const atBottom = wrap.scrollTop + wrap.clientHeight >= wrap.scrollHeight - 2;\n if (!atTop && !atBottom) setThreadFade(\"both\");\n else if (!atTop) setThreadFade(\"top\");\n else if (!atBottom) setThreadFade(\"bottom\");\n else setThreadFade(\"none\");\n };\n\n useEffect(() => {\n const wrap = threadFeedRef.current;\n if (!wrap) return;\n \n const handleScroll = () => updateThreadFade();\n wrap.addEventListener(\"scroll\", handleScroll, { passive: true });\n \n const resizeObserver = new ResizeObserver(() => {\n updateThreadFade();\n });\n \n resizeObserver.observe(wrap);\n if (wrap.firstElementChild) {\n resizeObserver.observe(wrap.firstElementChild);\n }\n \n updateThreadFade(); \n return () => {\n wrap.removeEventListener(\"scroll\", handleScroll);\n resizeObserver.disconnect();\n };\n }, [threadTurns]);\n\n // Keep the newest turn in view as the answer grows.\n useEffect(() => {\n const feed = threadFeedRef.current;\n if (!feed) return;\n feed.scrollTo({ top: feed.scrollHeight, behavior: \"smooth\" });\n }, [threadTurns]);\n\n /* Fit first, then place — see `placePanel`, which is a function precisely\n so the arithmetic can be tested without a browser. */\n const { x: replyTargetX, y: replyTargetY, width: replyTargetWidth, maxHeight } =\n typeof window === \"undefined\"\n ? { x: 0, y: 0, width: 480, maxHeight: Infinity }\n : placePanel(activeReply.rect, window.innerWidth, window.innerHeight);\n\n\n return (\n // See the note in ChatInput: the kit sets this itself rather than relying\n // on the host app to.\n <MotionConfig reducedMotion=\"user\">\n <div\n className={styles.backdrop}\n onClick={onClose}\n onKeyDown={handleKeyDown}\n >\n <motion.div\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n // Named by both: the phrase gives it meaning, the passage says which\n // thread. `aria-modal` is the whole claim to modality — marking the\n // rest of the page inert would mean reaching outside this component,\n // into a document it does not own.\n aria-labelledby={`${labelId} ${quoteId}`}\n tabIndex={-1}\n onClick={(e) => e.stopPropagation()}\n /* Two shapes, and only one of them is placed.\n \n Beside the phrase, the panel travels from the passage's own box to\n where it is going, so it reads as the passage opening. On a phone\n there is nowhere for it to go — a panel hanging off a phrase near\n the bottom of the screen is a panel mostly below it, and capping\n its height only turns that into a letterbox. So it comes up from\n the bottom instead, and its geometry belongs to the stylesheet:\n Motion writes `left`/`top`/`width` as inline styles, which no media\n query can override, so the sheet's variants must not mention them\n at all. */\n data-sheet={narrow || undefined}\n initial={narrow ? \"closed\" : {\n left: activeReply.rect.left, \n top: activeReply.rect.top, \n width: activeReply.rect.width, \n borderRadius: 12,\n opacity: 0\n }}\n animate={narrow ? \"open\" : { \n left: replyTargetX, \n top: replyTargetY, \n width: replyTargetWidth, \n borderRadius: 28,\n opacity: 1,\n }}\n exit={narrow ? \"closed\" : { \n opacity: 0,\n scale: 0.95,\n filter: \"blur(4px)\"\n }}\n variants={narrow ? sheet : undefined}\n transition={narrow ? undefined : { type: \"spring\", bounce: 0, duration: 0.4 }}\n {...(narrow\n ? {\n /* Dragged down to put away, like the artifact sheet. Distance\n **or** speed: distance alone loses a quick flick, speed alone\n dismisses a slow nudge that was meant to peek. */\n drag: \"y\" as const,\n dragConstraints: { top: 0, bottom: 0 },\n dragElastic: { top: 0, bottom: 0.6 },\n dragMomentum: false,\n onDragEnd: (\n _e: unknown,\n info: { offset: { y: number }; velocity: { y: number } }\n ) => {\n if (info.offset.y > 120 || info.velocity.y > 600) onClose();\n },\n }\n : {})}\n /* A ceiling rather than an animated property: how much room there is\n between the panel and the bottom of the screen is a fact about the\n screen, not a thing to travel to. Motion owns everything in\n `animate`; this sits beside it. */\n style={!narrow && Number.isFinite(maxHeight) ? { maxHeight } : undefined}\n className={styles.panel}\n >\n <div className={styles.column}>\n {/* Says the sheet can be pulled, and gives a thumb somewhere to pull\n it that is not a control. Drawn only where it means something. */}\n {narrow && <div className={styles.grabber} aria-hidden />}\n <div className={styles.header}>\n <div className={styles.headerLabel}>\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\" width=\"100\" height=\"100\" className={styles.headerIcon} aria-hidden>\n <path d=\"m85.1 21c1.5-2.2 4.6-6.7 5.8-11 0.7-2.5 0.3-4.7-1.6-6.1-1.6-1.4-4.3-1.7-7.3-0.4-3.2 1.2-7.3 4.6-12 9.1l-0.2 0.2c-0.9-0.3-1.9-0.6-2.9-0.7-7.9-1.2-14.7 0-20 3.6-6.8 4.6-10.8 12.3-9.5 22.6 0.1 1.4 1.2 2.4 2.7 2.3 1.4 0 2.6-1.4 2.4-2.8-1.1-6.8 1-12.9 6.5-16.9 3.9-2.9 9.1-4.8 16.5-3.9l-35.4 39.5c0 0.3 4.1 2.8 4.2 2.8 1-0.9 4-4.4 5.2-5.7l27.2-30.6c-0.4 3.3 0.6 5.8 5.6 5.1-8.4 9.4-25.2 25.2-33.5 33.7 0.5 0.5 4 2 4.8 2.3 7.8-7.2 25.4-24 34.8-34.5l3.5-4.6c4.1 3.7 11.7 11.9 11.7 24.7 0 9.6-6 20-20 22.7-8.2 1.2-17.3 0.1-26.4-3.5-8.5-3.1-15.7-7.8-22.3-13.1-7-6-13.3-13.5-14.9-21.8-0.9-4.9 0.2-9.7 4.1-12.9 2.8-2.4 6.5-4.2 12-4.3 3.8-0.1 7.3 0.4 11.9 2.1 1.2 0.6 2.8 0.4 3.4-0.8 0.8-1.1 0.2-3.2-1.1-3.6-5.2-1.9-9-2.9-14-2.9-5.7 0.1-10.7 1.5-14.9 4.9-4.7 3.8-8.5 10.3-5.8 20 2.4 9.2 9.5 17.3 19.5 25.4l-20.8 27.8c-1.4 1.7-2.3 2.8-2.3 3.9-0.2 1.5 0.6 2.7 1.6 3.3 1.3 0.8 3.4 0.6 4.7-0.3 3.4-2.2 15.8-13.1 29.7-26.7 6.5 3 16.5 7 27.9 7.8 5.6 0.2 10.7-0.2 15.4-1.9 10.8-3.5 17.2-13 17.1-25.8 0-11.9-6.7-22.3-13.3-29zm-73.3 65.7 17.5-21.9 4.1 2.5c-5.8 5.2-13.6 12.4-21.6 19.4zm64.9-63.3c-1.7 0.7-4.7 2-6.6 1.9-0.7-0.4 1.5-6.5 1.7-6.5 2.1 0.9 4.7 2 6.1 3.1l-1.2 1.5zm4.2-5.5c-1.6-1.1-3.7-2.3-5.5-3.2 2.1-1.9 4.7-4 6.3-5.1 1.4-0.9 3.2-0.5 3 1.4-0.3 2-3 5.8-3.8 6.9z\" />\n </svg>\n <p className={styles.headerText}>Replying in a thread</p>\n </div>\n <div className={styles.headerActions}>\n <Button\n variant=\"primary\"\n icon={<Save size={16} />}\n onClick={onSave}\n >\n Save\n </Button>\n <Button\n variant=\"secondary\"\n icon={<X size={16} />}\n onClick={onClose}\n aria-label=\"Close thread\"\n />\n </div>\n </div>\n\n <div className={styles.feedClip}>\n <div\n className={`${styles.fade} ${styles.fadeTop}`}\n data-visible={threadFade === \"top\" || threadFade === \"both\" || undefined}\n />\n <div\n className={`${styles.fade} ${styles.fadeBottom}`}\n data-visible={threadFade === \"bottom\" || threadFade === \"both\" || undefined}\n />\n <div ref={threadFeedRef} className={styles.feed}>\n <div className={styles.feedInner}>\n <span id={labelId} className={styles.srOnly}>\n Thread on\n </span>\n <div id={quoteId} className={styles.quote}>\n {activeReply.text}\n </div>\n \n {threadTurns.map((turn, i) => {\n const isActiveInput =\n i === threadTurns.length - 1 &&\n (turn.state === \"idle\" || turn.state === \"typing\");\n \n const isInputMode = turn.state === \"idle\" || turn.state === \"typing\";\n \n return (\n <motion.div\n key={turn.id}\n id={`thread-turn-${turn.id}`}\n aria-busy={turn.state === \"responding\" || undefined}\n initial={{ opacity: 0, y: 8, filter: \"blur(4px)\" }}\n animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n transition={{ type: \"spring\", stiffness: 260, damping: 30, mass: 0.8 }}\n className={styles.turn}\n >\n <div className={styles.turnRow} data-editing={isInputMode || undefined}>\n <ChatInput\n ref={isActiveInput ? threadActiveInputRef : null}\n state={turn.state}\n value={turn.user}\n onChange={(v) => setDraft(turn.id, v)}\n onSubmit={(v) => submit(turn.id, v)}\n onStop={stop}\n onCopy={() => navigator.clipboard.writeText(turn.ai)}\n onEdit={() => beginEdit(turn.id)}\n animationConfig={animConfig}\n placeholder=\"Ask me about this text...\"\n />\n </div>\n \n {turn.ai && (\n <div className={styles.answer}>{turn.ai}</div>\n )}\n </motion.div>\n );\n })}\n </div>\n </div>\n </div>\n </div>\n </motion.div>\n </div>\n </MotionConfig>\n );\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { motion, AnimatePresence, useMotionValue } from \"motion/react\";\n\nexport function CustomCursor() {\n const x = useMotionValue(-100);\n const y = useMotionValue(-100);\n const [variant, setVariant] = useState<\"default\" | \"marker\" | \"text\">(\"default\");\n const [isActive, setIsActive] = useState(false);\n const [isVisible, setIsVisible] = useState(false);\n\n useEffect(() => {\n const handleMove = (e: PointerEvent) => {\n if (!isVisible) setIsVisible(true);\n x.set(e.clientX);\n y.set(e.clientY);\n \n const target = e.target as Element | null;\n if (!target) return;\n \n const cursorEl = target.closest('[data-cursor]');\n const activeEl = target.closest('[data-cursor-active=\"true\"]');\n const controlEl = target.closest('a, button, [role=\"button\"], input, select, textarea, [tabindex]');\n\n if (activeEl || e.buttons > 0) {\n setIsActive(true);\n } else {\n setIsActive(false);\n }\n\n // Whichever of the two is nearer to the pointer wins. A button sitting\n // inside a marker surface should still show the arrow — but a marker\n // surface that is itself focusable is not a button, and `[tabindex]`\n // alone cannot tell the two apart.\n const controlWins =\n !!controlEl && (!cursorEl || (controlEl !== cursorEl && cursorEl.contains(controlEl)));\n\n if (controlWins || !cursorEl) {\n setVariant(\"default\");\n } else {\n const customVariant = cursorEl.getAttribute(\"data-cursor\");\n setVariant(customVariant === \"marker\" ? \"marker\" : customVariant === \"text\" ? \"text\" : \"default\");\n }\n };\n \n const handleDown = () => setIsActive(true);\n const handleUp = () => setIsActive(false);\n const handleLeave = () => setIsVisible(false);\n const handleEnter = () => setIsVisible(true);\n\n window.addEventListener(\"pointermove\", handleMove);\n window.addEventListener(\"pointerdown\", handleDown);\n window.addEventListener(\"pointerup\", handleUp);\n document.addEventListener(\"mouseleave\", handleLeave);\n document.addEventListener(\"mouseenter\", handleEnter);\n \n return () => {\n window.removeEventListener(\"pointermove\", handleMove);\n window.removeEventListener(\"pointerdown\", handleDown);\n window.removeEventListener(\"pointerup\", handleUp);\n document.removeEventListener(\"mouseleave\", handleLeave);\n document.removeEventListener(\"mouseenter\", handleEnter);\n };\n }, [isVisible, x, y]);\n\n if (!isVisible) return null;\n\n return (\n <motion.div\n style={{\n position: \"fixed\",\n top: 0,\n left: 0,\n pointerEvents: \"none\",\n zIndex: \"var(--ick-z-cursor)\",\n x,\n y,\n }}\n >\n <motion.div\n animate={{\n scale: isActive ? 0.8 : 1,\n x: variant === \"marker\" ? -2 : variant === \"text\" ? -7 : 0,\n y: variant === \"marker\" ? -12 : variant === \"text\" ? -10 : 0,\n }}\n transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n >\n <AnimatePresence mode=\"popLayout\">\n {variant === \"default\" && (\n <motion.div\n key=\"default\"\n initial={{ scale: 0, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n exit={{ scale: 0, opacity: 0 }}\n transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n >\n <svg width=\"19\" height=\"19\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\" style={{ filter: \"drop-shadow(0 1px 2px rgb(var(--ick-glass-shade-rgb) / 0.15))\" }}>\n <path d=\"M2.691 3.125C2.665 3.065 2.658 2.997 2.67 2.932C2.682 2.867 2.714 2.808 2.761 2.761C2.808 2.714 2.867 2.682 2.932 2.67C2.997 2.658 3.065 2.665 3.125 2.691L13.792 7.025C13.857 7.051 13.912 7.097 13.949 7.157C13.986 7.216 14.003 7.286 13.999 7.355C13.994 7.425 13.968 7.492 13.923 7.546C13.878 7.6 13.818 7.638 13.75 7.656L9.667 8.709C9.437 8.769 9.226 8.889 9.057 9.057C8.889 9.225 8.768 9.435 8.709 9.666L7.656 13.75C7.638 13.818 7.6 13.878 7.546 13.923C7.492 13.968 7.425 13.994 7.355 13.999C7.286 14.003 7.216 13.986 7.157 13.949C7.097 13.912 7.051 13.857 7.025 13.792L2.691 3.125Z\" fill=\"var(--ick-cursor-fill)\" stroke=\"var(--ick-cursor-ink)\" strokeWidth=\"1.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </motion.div>\n )}\n {variant === \"text\" && (\n <motion.div\n key=\"text\"\n initial={{ scale: 0.5, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n exit={{ scale: 0.5, opacity: 0 }}\n transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n >\n <svg width=\"14\" height=\"20\" viewBox=\"0 0 14 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n {/* white halo for visibility on any background */}\n <path d=\"M7 3 V17 M4 3 H10 M4 17 H10\" stroke=\"var(--ick-cursor-fill)\" strokeWidth=\"3.4\" strokeLinecap=\"round\" />\n <path d=\"M7 3 V17 M4 3 H10 M4 17 H10\" stroke=\"var(--ick-cursor-ink)\" strokeWidth=\"1.4\" strokeLinecap=\"round\" />\n </svg>\n </motion.div>\n )}\n {variant === \"marker\" && (\n <motion.div\n key=\"marker\"\n initial={{ scale: 0.5, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n exit={{ scale: 0.5, opacity: 0 }}\n transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n style={{\n filter: isActive \n ? \"drop-shadow(0px 1px 0px rgb(var(--ick-ink-rgb) / 0.2))\" \n : \"drop-shadow(0px 3px 0px rgb(var(--ick-ink-rgb) / 0.2))\"\n }}\n >\n <svg width=\"27\" height=\"16\" viewBox=\"0 0 34 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M9.383 7.767L25.309 1.111C27.347 0.259 29.69 1.22 30.542 3.259L32.085 6.949C32.937 8.987 31.975 11.33 29.937 12.182L14.011 18.839L5.353 17.813L3.251 18.692L1.228 15.821L4.03 14.65L9.383 7.767Z\" fill=\"var(--ick-cursor-fill)\" />\n <path d=\"M4.03 14.65L9.383 7.767L25.309 1.111C27.347 0.259 29.69 1.22 30.542 3.259L32.085 6.949C32.937 8.987 31.975 11.33 29.937 12.182L14.011 18.839L5.353 17.813M9.383 7.767L14.011 18.839M4.03 14.65L1.228 15.821L3.251 18.692L5.353 17.813M4.03 14.65L5.353 17.813\" stroke=\"var(--ick-cursor-ink)\" strokeWidth=\"1.6\" />\n </svg>\n </motion.div>\n )}\n </AnimatePresence>\n </motion.div>\n </motion.div>\n );\n}\n","\"use client\";\n\nimport { forwardRef, type ButtonHTMLAttributes, type ReactNode } from \"react\";\nimport { Button } from \"../Button/Button\";\n\nexport type GlassButtonSize = \"s\" | \"m\" | \"l\";\n\nexport interface GlassButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n children: ReactNode;\n /** Show loading spinner and disable interaction */\n loading?: boolean;\n /** Leading icon element */\n iconLeft?: ReactNode;\n /** Trailing icon element */\n iconRight?: ReactNode;\n /** Button size: s=32px, m=40px, l=48px */\n size?: GlassButtonSize;\n}\n\n/**\n * The old size names, one step below the merged scale.\n *\n * `GlassButton size=\"s\"` was 32px. On the single scale 32px is `m`, because\n * `s` is the 28px the round icon button has always been. Translating here\n * keeps every existing call site rendering exactly what it rendered before.\n */\nconst SIZE: Record<GlassButtonSize, \"m\" | \"l\" | \"xl\"> = { s: \"m\", m: \"l\", l: \"xl\" };\n\n/**\n * @deprecated Use `<Button variant=\"glass\">`. Kept so existing installs keep\n * working; it is a thin wrapper, not a second component.\n */\nconst GlassButton = forwardRef<HTMLButtonElement, GlassButtonProps>(function GlassButton(\n { size = \"m\", iconLeft, ...rest },\n ref\n) {\n return <Button ref={ref} variant=\"glass\" size={SIZE[size]} icon={iconLeft} {...rest} />;\n});\n\nexport default GlassButton;\n"],"mappings":"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"}
1
+ {"version":3,"file":"inline-chat-kit.js","names":[],"sources":["../src/Button/Button.module.css","../src/Button/Button.tsx","../src/Attachments/Attachments.module.css","../src/Attachments/Attachments.tsx","../src/ChatInput/MorphGlyph.tsx","../src/ChatInput/ChatInput.module.css","../src/ChatInput/HoverActionsRow.tsx","../src/ChatInput/AddCardsOverlay.tsx","../src/announce/announce.ts","../src/reducedMotion/reducedMotion.ts","../src/voice/useVoiceInput.ts","../src/ChatInput/ChatInput.tsx","../src/turnParts/turnParts.ts","../src/useChatTurns/useChatTurns.ts","../src/ChatHeader/ChatHeader.module.css","../src/ChatHeader/ChatHeader.tsx","../src/AnswerActions/AnswerActions.module.css","../src/AnswerActions/AnswerActions.tsx","../src/Branch/Branch.module.css","../src/Branch/Branch.tsx","../src/Loader/Loader.module.css","../src/Loader/Loader.tsx","../src/disclosure/useDisclosure.ts","../src/disclosure/DisclosureHeader.module.css","../src/disclosure/DisclosureHeader.tsx","../src/disclosure/DisclosureBody.module.css","../src/disclosure/DisclosureBody.tsx","../src/duration/formatDuration.ts","../src/Reasoning/Reasoning.module.css","../src/Reasoning/Reasoning.tsx","../src/CodeBlock/highlight.ts","../src/CodeBlock/CodeBlock.module.css","../src/CodeBlock/CodeBlock.tsx","../src/stateGlyph/StateGlyph.module.css","../src/stateGlyph/StateGlyph.tsx","../src/Tool/Tool.module.css","../src/Tool/Tool.tsx","../src/Approval/Approval.module.css","../src/Approval/Approval.tsx","../src/TaskList/TaskList.module.css","../src/TaskList/TaskList.tsx","../src/ChainOfThought/ChainOfThought.module.css","../src/ChainOfThought/ChainOfThought.tsx","../src/Sources/Sources.module.css","../src/Sources/Sources.tsx","../src/Chip/Chip.module.css","../src/Chip/Chip.tsx","../src/QuestionCard/QuestionCard.module.css","../src/QuestionCard/parts.tsx","../src/radiusCorrection/useCorrectedRadius.ts","../src/QuestionCard/QuestionCard.tsx","../src/QuestionGroup/QuestionGroup.module.css","../src/QuestionGroup/QuestionGroup.tsx","../src/SystemMessage/SystemMessage.module.css","../src/SystemMessage/SystemMessage.tsx","../src/Artifact/ArtifactCard.module.css","../src/Artifact/ArtifactCard.tsx","../src/markdown/parse.ts","../src/markdown/parseMarkdown.ts","../src/InlineCitation/InlineCitation.module.css","../src/InlineCitation/InlineCitation.tsx","../src/TextHighlighter/TextHighlighter.module.css","../src/TextHighlighter/TextHighlighter.tsx","../src/ChatTurnRow/ChatTurnRow.module.css","../src/ChatTurnRow/ChatTurnRow.tsx","../src/Context/Context.module.css","../src/Context/Context.tsx","../src/Artifact/ArtifactPane.module.css","../src/Artifact/ArtifactPane.tsx","../src/Artifact/ChatLayout.module.css","../src/Artifact/ChatLayout.tsx","../src/Artifact/useArtifacts.ts","../src/EmptyState/EmptyState.module.css","../src/EmptyState/EmptyState.tsx","../src/Conversation/Conversation.module.css","../src/Conversation/Conversation.tsx","../src/ReplyThreadPopup/placePanel.ts","../src/ReplyThreadPopup/ReplyThreadPopup.module.css","../src/ReplyThreadPopup/ReplyThreadPopup.tsx","../src/CustomCursor/CustomCursor.tsx","../src/GlassButton/GlassButton.tsx"],"sourcesContent":["/* ─────────────────────────────────────────────\n Button\n\n One component for what used to be two. `Button` was a 28px round icon\n button; `GlassButton` was a 710-line pill that did the same job in a\n different material. They shared a size range, a set of states and most of\n their behaviour, and disagreed about all of it.\n\n Not one literal colour in this file. Everything comes from the token layer,\n which is also where the dark theme lives — so there is no dark block here.\n ───────────────────────────────────────────── */\n\n.btn {\n all: unset;\n box-sizing: border-box;\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n border-radius: var(--ick-radius-pill);\n cursor: pointer;\n white-space: nowrap;\n -webkit-tap-highlight-color: transparent;\n font-family: var(--ick-font-sans);\n font-weight: var(--ick-weight-medium);\n color: var(--ick-ink);\n transition:\n box-shadow var(--ick-duration-fast) ease,\n background var(--ick-duration-fast) ease,\n outline-color var(--ick-duration-fast) ease,\n color var(--ick-duration-fast) ease,\n transform 120ms var(--ick-ease);\n}\n\n.btn:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: var(--ick-space-1);\n}\n\n/* The 44px rule, hung off the first child rather than the button.\n\n `overflow: hidden` used to sit on `.btn` and was the whole obstacle: a\n clipping box clips hit-testing too, so any box reaching past the button's\n edge was unreachable. Nothing needed it — everything absolutely positioned\n in here is inset within the button — except a label too long for its\n button, and that belongs to the label. It is on `.label` now, with an\n ellipsis, which is a better answer to that case anyway.\n\n The second obstacle is that `::after` is taken: the glass material draws its\n rim highlight with it. Every button renders at least one of `.icon` or\n `.label`, and an absolute box inside one of those still resolves against\n `.btn` — so `> :first-child` gives the same box from an anchor that is free.\n\n See `styles/touch.module.css` for what this is and what it costs. */\n@media (pointer: coarse) {\n .btn > :first-child::after {\n content: \"\";\n position: absolute;\n top: 50%;\n left: 50%;\n width: max(100%, var(--ick-touch-target));\n height: max(100%, var(--ick-touch-target));\n transform: translate(-50%, -50%);\n }\n}\n\n/* ── Sizes ──────────────────────────────────────────────────────────────────\n Square while it holds only an icon; grows to fit once it has words. The\n type scale is non-decreasing rather than strictly increasing — 28 and 32\n both read at 13px, which is what both of the old components used at those\n heights. */\n\n/* Heights come from tokens so a touch device can have roomier ones without\n this file knowing — see the `pointer: coarse` block in `tokens.css`. The\n numbers that used to live here flattered 13px text and are tight around 14. */\n.xs { height: var(--ick-control-xs); min-width: var(--ick-control-xs); font-size: var(--ick-text-xs); gap: var(--ick-space-2); }\n.s { height: var(--ick-control-s); min-width: var(--ick-control-s); font-size: var(--ick-text-md); gap: var(--ick-space-3); }\n.m { height: var(--ick-control-m); min-width: var(--ick-control-m); font-size: var(--ick-text-md); gap: var(--ick-space-3); }\n.l { height: var(--ick-control-l); min-width: var(--ick-control-l); font-size: var(--ick-text-lg); gap: var(--ick-space-4); }\n.xl { height: var(--ick-control-xl); min-width: var(--ick-control-xl); font-size: 1rem; gap: var(--ick-space-4); }\n\n/* And the icon inside, from the text it sits beside. `auto` on a pointer is\n the SVG's own width and height attributes, so this is inert there. */\n.btn svg {\n width: var(--ick-icon);\n height: var(--ick-icon);\n}\n\n.hasText.xs { padding: 0 var(--ick-space-3); }\n.hasText.s { padding: 0 var(--ick-space-5) 0 var(--ick-space-4); }\n.hasText.m { padding: 0 var(--ick-space-6); }\n.hasText.l { padding: 0 var(--ick-space-7); }\n.hasText.xl { padding: 0 var(--ick-space-8); }\n\n.icon {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n/* The clipping that used to be on the button, on the only thing that can\n spill — and as an ellipsis rather than a hard cut, which is what a label too\n long for its button should have been doing all along. */\n.label {\n position: relative;\n z-index: var(--ick-z-base);\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ── Primary: glassy at rest ────────────────────────────────────────────── */\n\n.primary {\n background: var(--ick-glass-gradient);\n box-shadow: var(--ick-shadow-1);\n outline: 0.5px solid var(--ick-border-bright);\n}\n\n.primary:hover:not(:disabled) {\n box-shadow: var(--ick-shadow-2);\n}\n\n.primary:active:not(:disabled) {\n box-shadow: var(--ick-shadow-inset);\n transform: scale(0.94);\n transition-duration: var(--ick-duration-press);\n}\n\n/* ── Secondary: naked until touched ─────────────────────────────────────── */\n\n.secondary {\n background: transparent;\n outline: 0.5px solid transparent;\n}\n\n.secondary:hover:not(:disabled) {\n background: var(--ick-glass-gradient);\n box-shadow: var(--ick-shadow-2);\n outline-color: var(--ick-border-bright);\n}\n\n.secondary:active:not(:disabled) {\n background: var(--ick-glass-gradient);\n box-shadow: var(--ick-shadow-inset);\n outline-color: var(--ick-border-bright);\n transform: scale(0.94);\n transition-duration: var(--ick-duration-press);\n}\n\n/* ── Outline: an edge at rest ────────────────────────────────────────────────\n The gap between `primary`, which is filled, and `secondary`, which is naked\n until touched. A button that looks like one before you reach for it, without\n competing with a filled one beside it.\n\n `secondary` reading as bold text has caught this kit twice — once on the\n empty state's openers, once on an approval's Deny. The second time it was a\n permission control that did not look like a control, which is worse than any\n colour being wrong, so the gap is filled rather than worked around again. */\n\n.outline {\n background: transparent;\n outline: 1px solid var(--ick-border);\n}\n\n.outline:hover:not(:disabled) {\n background: var(--ick-surface-hover);\n outline-color: var(--ick-ink-faint);\n}\n\n.outline:active:not(:disabled) {\n background: var(--ick-surface-press);\n transform: scale(0.94);\n transition-duration: var(--ick-duration-press);\n}\n\n/* ── Ghost: flat ────────────────────────────────────────────────────────── */\n\n.ghost {\n background: transparent;\n color: var(--ick-ink-soft);\n}\n\n.ghost:hover:not(:disabled) {\n background: var(--ick-surface-active);\n color: var(--ick-ink);\n}\n\n.ghost:active:not(:disabled) {\n background: var(--ick-surface-press);\n color: var(--ick-ink);\n transform: scale(0.92);\n transition-duration: var(--ick-duration-press);\n}\n\n/* ── Glass: the concave-glass material ──────────────────────────────────────\n A gradient border through ::before and a top rim highlight through ::after,\n over a stack of thirteen shadow layers. The layers are tokens because they\n are the one part that has to be restated for the dark theme. */\n\n.glass {\n font-family: var(--ick-font-mono);\n letter-spacing: -0.01em;\n color: var(--ick-ink-glass);\n background: var(--ick-glass-fill);\n box-shadow: var(--ick-shadow-glass);\n backdrop-filter: blur(var(--ick-glass-blur));\n -webkit-backdrop-filter: blur(var(--ick-glass-blur));\n /* Named, not `all`, and this is the button the composer is made of — mic,\n plus, send — plus the X on an attachment. Pressed constantly.\n\n `all` animates whatever happens to change, and what changes here includes\n `backdrop-filter`: a blur re-composited every frame for 400ms, which is\n the expensive one in Safari and buys nothing, because the blur is the\n same before and after. Colour and shadow are the state; the lift is the\n movement, and it is quicker, because a press is not a journey. */\n transition:\n background var(--ick-duration-base) var(--ick-ease),\n color var(--ick-duration-base) var(--ick-ease),\n box-shadow var(--ick-duration-base) var(--ick-ease),\n transform var(--ick-duration-fast) var(--ick-ease);\n}\n\n.glass::before {\n content: \"\";\n position: absolute;\n inset: 0;\n border-radius: inherit;\n padding: var(--ick-glass-border-width);\n background: var(--ick-glass-edge);\n /* The only literal colours in this file, and they are not colours: a mask\n needs an opaque stencil, and any opaque value does. */\n mask:\n linear-gradient(#fff 0 0) content-box,\n linear-gradient(#fff 0 0);\n -webkit-mask:\n linear-gradient(#fff 0 0) content-box,\n linear-gradient(#fff 0 0);\n mask-composite: exclude;\n -webkit-mask-composite: xor;\n pointer-events: none;\n transition: background var(--ick-duration-slow) var(--ick-ease);\n}\n\n.glass::after {\n content: \"\";\n position: absolute;\n top: 0;\n left: 15%;\n right: 15%;\n height: var(--ick-glass-border-width);\n background: var(--ick-glass-highlight);\n border-radius: var(--ick-radius-pill);\n pointer-events: none;\n opacity: 0.85;\n transition: opacity var(--ick-duration-slow) var(--ick-ease);\n}\n\n.glass:hover:not(:disabled) {\n background: var(--ick-glass-fill-hover);\n color: var(--ick-ink-glass-strong);\n box-shadow: var(--ick-shadow-glass-hover);\n}\n\n/* The lift only where there is something to hover with.\n \n A touchscreen fires a hover on tap and leaves it there until the next tap\n somewhere else, so ungated this left the composer's buttons standing a\n pixel high for as long as you did not touch anything — a state the design\n has no name for. The colour and shadow above stay: those read as \"this is\n the one under your finger\" and are right either way. */\n@media (hover: hover) and (pointer: fine) {\n .glass:hover:not(:disabled) {\n transform: translateY(-1px);\n }\n}\n\n.glass:hover:not(:disabled)::after {\n opacity: 0.6;\n}\n\n.glass:active:not(:disabled) {\n background: var(--ick-glass-fill-press);\n box-shadow: var(--ick-shadow-glass-press);\n transform: translateY(1px) scale(0.99);\n transition-duration: 80ms;\n}\n\n.glass:active:not(:disabled)::before {\n background: var(--ick-glass-edge-press);\n}\n\n.glass:active:not(:disabled)::after {\n opacity: 0.3;\n}\n\n.glass:disabled {\n box-shadow: var(--ick-shadow-glass-disabled);\n}\n\n.glass:disabled::after {\n opacity: 0.15;\n}\n\n/* ── Disabled ───────────────────────────────────────────────────────────── */\n\n/* No `pointer-events: none` here, though the glass button used to have it.\n `disabled` already refuses activation; switching off hit-testing on top of\n that only removes the hover feedback and any tooltip explaining why the\n button cannot be used. */\n.btn:disabled {\n cursor: not-allowed;\n opacity: 0.45;\n}\n\n/* ── Loading ────────────────────────────────────────────────────────────────\n Hidden with opacity, not visibility. `visibility: hidden` takes the label\n out of the accessibility tree, and the spinner is aria-hidden, so the button\n announced as one with no name at all. */\n\n.loading .label,\n.loading .icon {\n opacity: 0;\n}\n\n.spinner {\n position: absolute;\n z-index: var(--ick-z-base);\n width: 1.25em;\n height: 1.25em;\n border: 2px solid var(--ick-border);\n border-top-color: var(--ick-ink-soft);\n border-radius: 50%;\n animation: spin 0.7s linear infinite;\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n/* ── Reduced motion ─────────────────────────────────────────────────────────\n The colour, shadow and outline fades stay — nothing about them moves, and\n dropping them would take the interactive states with them. Only transform\n goes, so the press snaps rather than travelling. The spinner keeps turning:\n it is the only sign that anything is happening. */\n\n@media (prefers-reduced-motion: reduce) {\n .btn,\n .glass {\n transition-property: box-shadow, background, outline-color, color;\n }\n}\n","\"use client\";\n\nimport { forwardRef, type ButtonHTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./Button.module.css\";\n\nexport type ButtonVariant = \"primary\" | \"secondary\" | \"outline\" | \"ghost\" | \"glass\";\n\n/**\n * Heights: xs 24, s 28, m 32, l 40, xl 48.\n *\n * One scale, from two that disagreed — the round icon button was 28 with no\n * say in the matter, and the glass pill offered 32/40/48 under the names\n * s/m/l. Both sets of values survive; only the glass names moved up one step,\n * which is what the deprecated `GlassButton` wrapper exists to absorb.\n */\nexport type ButtonSize = \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\";\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n /** Leading icon. On its own it makes a square button; give it an aria-label. */\n icon?: ReactNode;\n /** Trailing icon. */\n iconRight?: ReactNode;\n /** Replaces the content with a spinner and refuses interaction. */\n loading?: boolean;\n children?: ReactNode;\n}\n\nconst variantClass: Record<ButtonVariant, string> = {\n primary: styles.primary,\n secondary: styles.secondary,\n outline: styles.outline,\n ghost: styles.ghost,\n glass: styles.glass,\n};\n\nconst sizeClass: Record<ButtonSize, string> = {\n xs: styles.xs,\n s: styles.s,\n m: styles.m,\n l: styles.l,\n xl: styles.xl,\n};\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n {\n variant = \"primary\",\n size = \"s\",\n icon,\n iconRight,\n loading = false,\n children,\n className,\n disabled,\n ...rest\n },\n ref\n) {\n const classes = [\n styles.btn,\n variantClass[variant],\n sizeClass[size],\n children ? styles.hasText : \"\",\n loading ? styles.loading : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <button\n ref={ref}\n className={classes}\n disabled={disabled || loading}\n /* Stated either way rather than only when true: \"not busy\" is a fact\n worth having, and it is the contract the glass button already had. */\n aria-busy={loading}\n {...rest}\n >\n {icon && <span className={styles.icon}>{icon}</span>}\n {children && <span className={styles.label}>{children}</span>}\n {iconRight && <span className={styles.icon}>{iconRight}</span>}\n {loading && <span className={styles.spinner} aria-hidden=\"true\" />}\n </button>\n );\n});\n","/* ─────────────────────────────────────────────\n Attachments\n\n What is going along with the message. One row, drawn the same in the\n composer and under the message once it has been sent — the only difference\n is whether it carries a remove button, which is the difference between a\n control and a record.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.list {\n box-sizing: border-box;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--ick-space-3);\n width: 100%;\n margin: 0;\n padding: 0;\n list-style: none;\n font-family: var(--ick-font-sans);\n}\n\n/* A named file: a glyph, its name, its size. An inset panel, the same thing an\n option row is inside a question card. */\n.item {\n position: relative;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--ick-space-3);\n max-width: var(--ick-attachment-max);\n min-width: 0;\n padding: var(--ick-space-3) var(--ick-space-4);\n border-radius: var(--ick-attachment-radius);\n background: var(--ick-attachment-fill);\n}\n\n/* A picture is its own panel: it fills the square and needs no padding around\n itself to say what it is. */\n.item[data-picture] {\n width: var(--ick-attachment-size);\n height: var(--ick-attachment-size);\n padding: 0;\n overflow: hidden;\n /* Against a picture that happens to end in the surface's own colour, and\n lifted a little off the section it sits in. */\n outline: 1px solid var(--ick-border-bright);\n outline-offset: -1px;\n box-shadow: 0 2px 3px -2px rgb(var(--ick-ink-rgb) / 0.2);\n}\n\n.picture {\n display: block;\n width: 100%;\n height: 100%;\n background-position: 50%;\n background-size: cover;\n transition: filter var(--ick-duration-fast) ease;\n}\n\n.glyph {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n color: var(--ick-ink-faint);\n}\n\n.body {\n box-sizing: border-box;\n display: flex;\n align-items: baseline;\n gap: var(--ick-space-3);\n min-width: 0;\n}\n\n/* One line, ellipsised. A filename that wraps is a paragraph. */\n.name {\n font-size: var(--ick-text-sm);\n color: var(--ick-ink);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.size {\n flex-shrink: 0;\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n color: var(--ick-ink-faint);\n}\n\n/* ── Removing one ───────────────────────────────────────────────────────── */\n\n/* On the picture, over a scrim, because there is nowhere beside it to put a\n button without making the square bigger. Beside a named file, where there\n is. */\n.remove {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.item[data-picture] .remove {\n position: absolute;\n inset: 0;\n background: var(--ick-scrim);\n opacity: 0;\n transition: opacity var(--ick-duration-fast) ease;\n}\n\n.item[data-picture]:hover .remove,\n.item[data-picture]:focus-within .remove {\n opacity: 1;\n}\n\n.item[data-picture]:hover .picture {\n filter: blur(2px);\n}\n\n/* Reaching for the button must not be a race against the reveal. */\n@media (prefers-reduced-motion: reduce) {\n .item[data-picture] .remove,\n .picture {\n transition: none;\n }\n}\n\n/* ── Where there is no hover ─────────────────────────────────────────────\n A control that only appears on hover does not exist on a phone.\n\n The wash above is a good answer for a pointer: the button has the whole\n square to be pressed in, and the square is only obscured while somebody is\n pointing at it. Neither half survives the move. There is no pointing, so\n the wash would have to be permanent — a scrim over every picture for as\n long as it is attached, which is the picture you attached, hidden. And a\n full-square target that is always armed is a picture you cannot touch\n without deleting it.\n\n So on touch it stops being a wash and becomes a button, in the corner,\n always there. Nothing is covered but the corner it sits in, and the rest of\n the square goes back to being a picture. `hover: none` rather than\n `pointer: coarse`: the reason is the missing hover, not the finger — and\n the two come apart on a touchscreen laptop, where hovering still works and\n this rule should not apply. */\n@media (hover: none) {\n .item[data-picture] .remove {\n inset: var(--ick-attachment-remove-inset) var(--ick-attachment-remove-inset) auto auto;\n background: none;\n opacity: 1;\n }\n\n /* The picture is the thing you attached. Leave it alone. */\n .item[data-picture]:hover .picture {\n filter: none;\n }\n}\n","\"use client\";\n\nimport { X } from \"lucide-react\";\nimport { FileText, Film, Music, Image as ImageIcon, File } from \"lucide-react\";\nimport type { HTMLAttributes } from \"react\";\nimport { Button } from \"../Button/Button\";\nimport styles from \"./Attachments.module.css\";\n\n/** Something sent along with a message. */\nexport interface Attachment {\n id: string;\n /** The filename, shown when there is no picture to show instead. */\n name: string;\n /**\n * What to draw for it. An object URL, a data URL, or a remote one.\n *\n * Only used when `type` is an image. Anything else gets its name and a\n * glyph, because a thumbnail of a PDF at 64px is a grey rectangle.\n */\n url?: string;\n /** The MIME type. Decides between a picture and a name. */\n type?: string;\n /** In bytes. Shown beside the name. */\n size?: number;\n}\n\ntype Labels = { remove: string };\nconst LABELS: Labels = { remove: \"Remove\" };\n\nexport interface AttachmentsProps extends Omit<HTMLAttributes<HTMLUListElement>, \"onRemove\"> {\n attachments: Attachment[];\n /**\n * Leave it out and they are a record rather than a control — which is what\n * they are once the message has been sent.\n */\n onRemove?: (id: string) => void;\n labels?: Partial<Labels>;\n}\n\n/**\n * `1.2 MB`, and nothing for a size nobody supplied.\n *\n * Powers of two, because that is what a file browser says and this number is\n * only ever compared to one of those.\n */\nexport function formatSize(bytes?: number): string {\n if (bytes === undefined || !Number.isFinite(bytes) || bytes < 0) return \"\";\n if (bytes < 1024) return `${bytes} B`;\n const units = [\"KB\", \"MB\", \"GB\"];\n let value = bytes / 1024;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;\n}\n\nconst isImage = (a: Attachment) => Boolean(a.url) && (a.type ?? \"\").startsWith(\"image/\");\n\n/**\n * A URL that survives being written into `url(\"…\")`.\n *\n * Unquoted, a bracket inside the URL closes the `url()` early — the rest is\n * garbage, the browser throws the whole declaration away, and the element\n * gets `background-image: none`. No error anywhere; the picture is simply not\n * there. Which is what the stories showed for as long as they have existed: a\n * row of blank squares under the heading \"pictures — they show themselves\",\n * because an SVG data URL says `fill=\"url(#g)\"` and those brackets are not\n * touched by `encodeURIComponent`.\n *\n * So it is quoted, and the two characters that could close the quote are\n * escaped. Everything else is a URL's business.\n */\nconst cssUrl = (url: string) => url.replace(/[\"\\\\]/g, encodeURIComponent);\n\n/** A picture of the kind of thing it is, for the ones with no picture. */\nfunction Glyph({ type }: { type?: string }) {\n const kind = (type ?? \"\").split(\"/\")[0];\n const props = { size: 16, \"aria-hidden\": true } as const;\n if (kind === \"image\") return <ImageIcon {...props} />;\n if (kind === \"video\") return <Film {...props} />;\n if (kind === \"audio\") return <Music {...props} />;\n if (type === \"application/pdf\" || kind === \"text\") return <FileText {...props} />;\n return <File {...props} />;\n}\n\n/**\n * What is going along with the message.\n *\n * An image shows itself; everything else shows its name, because a thumbnail\n * of a PDF at this size is a grey rectangle with a corner turned down and\n * tells you less than the filename does.\n *\n * The same row draws them in the composer and under the message once it has\n * been sent — with `onRemove` in the first case and without in the second,\n * which is the whole difference between a control and a record.\n */\nexport function Attachments({\n attachments,\n onRemove,\n labels,\n className,\n ...rest\n}: AttachmentsProps) {\n const label = { ...LABELS, ...labels };\n if (attachments.length === 0) return null;\n\n return (\n <ul className={[styles.list, className ?? \"\"].filter(Boolean).join(\" \")} {...rest}>\n {attachments.map((file) => {\n const size = formatSize(file.size);\n return (\n <li\n key={file.id}\n className={styles.item}\n data-picture={isImage(file) || undefined}\n >\n {isImage(file) ? (\n /* A background rather than an `<img>`: it has to fill a square\n crop, and the name is already on the element for anybody who\n is not looking at it. */\n <span\n className={styles.picture}\n style={{ backgroundImage: `url(\"${cssUrl(file.url!)}\")` }}\n role=\"img\"\n aria-label={file.name}\n />\n ) : (\n <>\n <span className={styles.glyph}>\n <Glyph type={file.type} />\n </span>\n <span className={styles.body}>\n <span className={styles.name}>{file.name}</span>\n {size && <span className={styles.size}>{size}</span>}\n </span>\n </>\n )}\n\n {onRemove && (\n <span className={styles.remove}>\n <Button\n variant=\"glass\"\n size=\"s\"\n onClick={() => onRemove(file.id)}\n /* In the composer this sits on the highlighter's surface,\n where a pointerdown starts drawing a marker. */\n onPointerDown={(event) => event.stopPropagation()}\n aria-label={`${label.remove} ${file.name}`}\n icon={<X size={14} aria-hidden />}\n />\n </span>\n )}\n </li>\n );\n })}\n </ul>\n );\n}\n","\"use client\";\n\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { Check, CornerDownLeft, Mic, Square } from \"lucide-react\";\n\n/**\n * Glyph slot for the send/stop/check action. Static Lucide icons swap\n * with a vertical slide + fade — outgoing slides up and out, incoming\n * slides up from below. Single slot, single transition.\n */\n\nexport interface MorphGlyphProps {\n mode: \"send\" | \"stop\" | \"check\" | \"mic\";\n /** Icon colour. Inherits from whatever contains it unless set. */\n color?: string;\n /** Slide duration in seconds. */\n duration?: number;\n /** Icon pixel size. */\n size?: number;\n}\n\nconst SLIDE_DISTANCE = 10;\n\nexport function MorphGlyph({\n mode,\n color = \"currentColor\",\n duration = 0.22,\n size = 14,\n}: MorphGlyphProps) {\n return (\n <span\n style={{\n position: \"relative\",\n display: \"inline-flex\",\n width: size,\n height: size,\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n aria-hidden\n >\n <AnimatePresence initial={false} mode=\"popLayout\">\n <motion.span\n key={mode}\n initial={{ y: SLIDE_DISTANCE, opacity: 0 }}\n animate={{ y: 0, opacity: 1 }}\n exit={{ y: -SLIDE_DISTANCE, opacity: 0 }}\n transition={{ duration, ease: [0.32, 0.72, 0, 1] }}\n style={{\n position: \"absolute\",\n inset: 0,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n {mode === \"send\" ? (\n <CornerDownLeft\n size={size}\n color={color}\n strokeWidth={1.8}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n ) : mode === \"mic\" ? (\n <Mic\n size={size}\n color={color}\n strokeWidth={1.8}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n ) : mode === \"check\" ? (\n <Check\n size={size}\n color={color}\n strokeWidth={2.2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n ) : (\n <Square\n size={size}\n color={color}\n fill={color}\n strokeWidth={1.8}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n )}\n </motion.span>\n </AnimatePresence>\n </span>\n );\n}\n","/* ─────────────────────────────────────────────\n ChatInput — single morphing element\n States: idle | typing | responding | resting\n ───────────────────────────────────────────── */\n\n.root {\n /* The component tier: named here, derived from the semantic layer, and\n overridable by a host that wants a taller or rounder input without\n touching anything else. `--slot` went with this rewrite — declared, never\n read, by either this file or any other. */\n --ick-input-h: 36px;\n --ick-input-radius: var(--ick-radius-xl);\n --ick-input-fill: rgb(var(--ick-ink-rgb) / 0.03);\n --ick-input-fill-hover: rgb(var(--ick-ink-rgb) / 0.05);\n --ick-input-text: var(--ick-ink-soft);\n\n display: inline-flex;\n align-items: center;\n gap: var(--ick-space-2);\n font-family: var(--ick-font-sans);\n font-size: var(--ick-composer-size);\n letter-spacing: 0.03em;\n /* Fixed width when typing */\n width: 320px;\n}\n\n.root.isGlassRoot {\n width: auto;\n max-width: 320px;\n}\n\n/* ── Surface — the morphing pill ── */\n.surface {\n position: relative;\n display: flex;\n align-items: center;\n flex: 1;\n min-height: var(--ick-input-h);\n min-width: 0;\n padding: var(--ick-space-4);\n border-radius: var(--ick-input-radius);\n background: var(--ick-input-fill);\n color: var(--ick-input-text);\n cursor: text;\n transition:\n background var(--ick-duration-slow) var(--ick-ease),\n box-shadow var(--ick-duration-slow) var(--ick-ease),\n color var(--ick-duration-slow) var(--ick-ease);\n /* keep the morph performant */\n will-change: transform, background, box-shadow, padding;\n}\n\n/* Idle/typing hover — reads as clickable but stays subtle. */\n.surface:not(.glass):hover {\n --ick-input-fill: var(--ick-input-fill-hover);\n}\n\n.surface:not(.glass):focus-within {\n --ick-input-fill: var(--ick-input-fill-hover);\n --ick-input-text: var(--ick-ink);\n}\n\n/* ── Glass surface variant (responding / resting bubble) ──\n Mirrors GlassButton.module.css concave-glass shadow stack so\n the morph is purely a CSS state change on the SAME element. */\n.surface.glass {\n --ick-input-fill: var(--ick-glass-fill);\n --ick-input-text: var(--ick-ink-glass);\n cursor: default;\n backdrop-filter: blur(var(--ick-glass-blur));\n -webkit-backdrop-filter: blur(var(--ick-glass-blur));\n box-shadow: var(--ick-shadow-glass);\n}\n\n.surface.glass::before {\n content: \"\";\n position: absolute;\n inset: 0;\n border-radius: inherit;\n padding: var(--ick-glass-border-width);\n background: var(--ick-glass-edge);\n /* Stencils, not colours: a mask needs an opaque value and any one does. */\n mask:\n linear-gradient(#fff 0 0) content-box,\n linear-gradient(#fff 0 0);\n -webkit-mask:\n linear-gradient(#fff 0 0) content-box,\n linear-gradient(#fff 0 0);\n mask-composite: exclude;\n -webkit-mask-composite: xor;\n pointer-events: none;\n}\n\n.surface.glass::after {\n content: \"\";\n position: absolute;\n top: 0;\n left: 15%;\n right: 15%;\n height: var(--ick-glass-border-width);\n background: var(--ick-glass-highlight);\n border-radius: var(--ick-radius-pill);\n pointer-events: none;\n opacity: 0.85;\n transition: opacity var(--ick-duration-slow) var(--ick-ease);\n}\n\n/* ── Hover ──\n The bubble is made of the same glass as the buttons, so it wears the same\n stack. It used to be a copy of it, pasted, and the two drifted. */\n.surface.glass.hovered {\n --ick-input-fill: var(--ick-glass-fill-hover);\n color: var(--ick-ink);\n box-shadow: var(--ick-shadow-glass-hover);\n}\n\n.surface.glass.hovered::after {\n opacity: 0.6;\n}\n\n/* ── Impact pulse ──\n Fires once, the moment the action button is absorbed. Not a brighter hover:\n it is about the event, not the pointer. */\n.surface.glass.pulsed {\n box-shadow: var(--ick-shadow-glass-pulse);\n transition:\n background var(--ick-duration-slow) var(--ick-ease),\n box-shadow 0.18s var(--ick-ease-overshoot-strong),\n color var(--ick-duration-slow) var(--ick-ease);\n}\n\n.surface.glass.pulsed::after {\n opacity: 1;\n}\n\n\n/* ── Editor clip — non-scrolling wrapper that holds the fade mask ── */\n.editorClip {\n flex: 1;\n min-width: 0;\n}\n\n.editorClip[data-fade=\"top\"] {\n mask-image: linear-gradient(to bottom, transparent 0, black 48px 100%);\n -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 48px 100%);\n}\n\n.editorClip[data-fade=\"bottom\"] {\n mask-image: linear-gradient(to top, transparent 0, black 48px 100%);\n -webkit-mask-image: linear-gradient(to top, transparent 0, black 48px 100%);\n}\n\n.editorClip[data-fade=\"both\"] {\n mask-image: linear-gradient(to bottom, transparent 0, black 48px calc(100% - 48px), transparent 100%);\n -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 48px calc(100% - 48px), transparent 100%);\n}\n\n/* ── Editor wrapper — scroll container ── */\n.editorWrap {\n overflow-x: hidden;\n overflow-y: scroll;\n max-height: 240px;\n scrollbar-width: none;\n}\n\n.editorWrap::-webkit-scrollbar {\n display: none;\n}\n\n/* In glass (bubble) mode: disable scroll, clip text at max-height */\n.editorWrapGlass {\n overflow: hidden;\n}\n\n/* ── Editor area (contenteditable div) ── */\n.editor {\n /* This kit has no global `box-sizing` reset — a reset inside a CSS module\n leaks into the host page — so every padded box states its own. This one\n did not: `width: 100%` plus 8px of padding either side made its border box\n 16px wider than the wrapper that clips it, and the right edge of the text\n was cut off. Found by the visual QA pass, which reported it as\n \"runs 16px past the right of a box that clips\" on 25 stories. */\n box-sizing: border-box;\n width: 100%;\n outline: none;\n font: inherit;\n letter-spacing: inherit;\n color: inherit;\n line-height: 1.45;\n padding: var(--ick-space-2) var(--ick-space-4);\n caret-color: var(--ick-ink);\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.editor:empty::before {\n content: attr(data-placeholder);\n color: var(--ick-ink-faint);\n pointer-events: none;\n}\n\n\n/* In glass mode the text is inked, not muted, and the caret is hidden */\n.surface.glass .editor {\n color: var(--ick-ink);\n caret-color: transparent;\n}\n\n/* ─────────────────────────────────────────────\n Bubble wrapper + hover-revealed actions row\n ───────────────────────────────────────────── */\n\n/* The hover wrapper holds the bubble + an absolutely positioned actions row\n so that revealing the actions does NOT push the bubble upward. */\n.wrap {\n /* Where the words inside the pill start, so anything drawn under the pill\n can line up with them rather than with its edge — this kit's one alignment\n rule. Read from the surface's own padding. */\n --ick-input-text-inset: var(--ick-space-4);\n\n position: relative;\n display: inline-flex;\n justify-content: flex-end;\n /* So a second item can drop below the pill instead of standing beside it.\n The only one there has ever been is the microphone's refusal note, which\n landed in a column to the right of the composer the first time it was\n photographed — the row was the whole layout and nothing had needed a\n second line before. The two overlays here are absolutely positioned and\n are not items at all, so this changes nothing that already existed. */\n flex-wrap: wrap;\n}\n\n.wrap::after {\n content: '';\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n height: 16px;\n}\n\n.buttonGroup {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.actionsRow {\n position: absolute;\n top: calc(100% + 8px);\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 4px;\n padding: 0 8px;\n}\n\n/* ─────────────────────────────────────────────\n Add cards overlay\n ───────────────────────────────────────────── */\n\n.addBackdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: var(--ick-z-raised);\n cursor: default;\n}\n\n/* ─────────────────────────────────────────────\n Attached Image Preview\n ───────────────────────────────────────────── */\n\n/* Holds an attached image above the input. Opaque against the page, and\n transparent once the bubble it sits in is made of glass. */\n/* A section at the top of the composer, above the line you type on. Its own\n surface, because what is going with the message is a different thing from\n the message — and this is how it has looked since it was one image. */\n.attachedTray {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n min-height: 80px;\n padding: var(--ick-space-4);\n margin-bottom: var(--ick-space-4);\n border-radius: var(--ick-radius-lg);\n background-color: var(--ick-surface);\n}\n\n/* Sent: the bubble is glass and the section stops being a panel inside it. */\n.attachedTray[data-glass] {\n background-color: transparent;\n}\n\n.addOverlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: var(--ick-z-sticky);\n}\n\n.addCardsContainer {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n/* The overlay's own close button: a filled circle rather than the naked ghost,\n so it reads against the fanned cards behind it. */\n.closeAdd {\n flex-shrink: 0;\n width: 28px;\n background-color: var(--ick-surface-active);\n color: var(--ick-ink);\n}\n\n.addCardFan {\n all: unset;\n box-sizing: border-box;\n position: absolute;\n width: 160px;\n height: 42px;\n border-radius: var(--ick-radius-xl);\n background-color: var(--ick-surface);\n box-shadow: var(--ick-shadow-float);\n display: flex;\n align-items: center;\n padding: 0 var(--ick-space-6);\n gap: 10px;\n font-family: var(--ick-font-sans);\n font-size: var(--ick-text-md);\n font-weight: var(--ick-weight-medium);\n color: var(--ick-ink);\n cursor: pointer;\n pointer-events: auto;\n transition: background-color 150ms ease;\n}\n\n/* ── Dictation ──────────────────────────────────────────────────────────────\n The microphone sits in the send glyph's slot, so it needs nothing of its\n own except the ring that shows the level of what it is hearing. */\n\n/* Driven by `useVoiceInput`, which writes `--ick-voice-level` here once a\n frame and never through React. A number this component re-rendered on would\n be sixty renders a second for a decoration — the same arithmetic that took\n the highlighter's hover out of React after it cost 28 mutations a crossing.\n\n `scale` and `opacity` only: both are composited, so the ring costs no\n layout and no paint however loudly somebody talks. */\n.voiceMeter {\n --ick-voice-level: 0;\n\n position: absolute;\n inset: 0;\n border-radius: inherit;\n pointer-events: none;\n background: var(--ick-voice-meter);\n transform: scale(calc(1 + var(--ick-voice-level) * var(--ick-voice-meter-spread)));\n opacity: 0;\n transition: opacity var(--ick-duration-fast) var(--ick-ease);\n}\n\n.voiceMeter[data-live] {\n opacity: calc(0.35 + var(--ick-voice-level) * 0.65);\n}\n\n/* Under reduced motion the level is never written, so the ring would sit at\n its resting scale and read as nothing at all. A steady halo instead: the\n state is still visible, it just does not move. */\n@media (prefers-reduced-motion: reduce) {\n .voiceMeter[data-live] {\n transform: scale(calc(1 + var(--ick-voice-meter-spread) * 0.5));\n opacity: 0.6;\n }\n}\n\n/* Lined up with the words in the composer rather than with its edge — the\n kit's one alignment rule, stated the same way everywhere. */\n.voiceNote {\n /* Its own line, the full width of the composer. */\n flex: 0 0 100%;\n /* `margin-right: auto` because the wrap justifies to the end: without it a\n capped width is pushed to the right edge and the note reads as belonging\n to nothing. */\n margin: var(--ick-space-2) auto 0 0;\n padding-inline: var(--ick-input-text-inset);\n max-width: 46ch;\n font-size: var(--ick-text-sm);\n line-height: 1.5;\n color: var(--ick-ink-soft);\n}\n\n.voiceNote[data-tone=\"denied\"] {\n color: var(--ick-danger);\n}\n\n/* The microphone's slot is a Motion child now, like the plus and the send\n glyph, so its size and entrance live in `ChatInput.tsx` beside theirs. What\n is left here is the ring that shows the level of what it is hearing. */\n\n/* Lined up with the words in the composer rather than with its edge — the\n kit's one alignment rule, stated the same way everywhere. */\n.voiceNote {\n /* Its own line, the full width of the composer. */\n flex: 0 0 100%;\n /* `margin-right: auto` because the wrap justifies to the end: without it a\n capped width is pushed to the right edge and the note reads as belonging\n to nothing. */\n margin: var(--ick-space-2) auto 0 0;\n padding-inline: var(--ick-input-text-inset);\n max-width: 46ch;\n font-size: var(--ick-text-sm);\n line-height: 1.5;\n color: var(--ick-ink-soft);\n}\n\n.voiceNote[data-tone=\"denied\"] {\n color: var(--ick-danger);\n}\n\n","\"use client\";\n\nimport React from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { Copy, Pencil } from \"lucide-react\";\nimport { Button } from \"../Button/Button\";\nimport styles from \"./ChatInput.module.css\";\nimport type { InlineAnimConfig } from \"./ChatInput\";\n\nexport interface HoverActionsRowProps {\n showActions: boolean;\n showReadMore: boolean;\n isExpanded: boolean;\n setIsExpanded: React.Dispatch<React.SetStateAction<boolean>>;\n onCopy?: (value: string) => void;\n onEdit?: (value: string) => void;\n value: string;\n ac: InlineAnimConfig | undefined;\n}\n\nexport function HoverActionsRow({\n showActions,\n showReadMore,\n isExpanded,\n setIsExpanded,\n onCopy,\n onEdit,\n value,\n ac,\n}: HoverActionsRowProps) {\n return (\n <AnimatePresence initial={false}>\n {showActions && (\n <motion.div\n key=\"actions\"\n className={styles.actionsRow}\n variants={{\n hidden: { scale: 0.85, opacity: 0 },\n visible: { scale: 1, opacity: 1, transition: { stiffness: 460, damping: 38, staggerChildren: ac?.actions?.staggerDelay ?? 0.07 } },\n exit: { scale: 0.85, opacity: 0, transition: { duration: 0.12 } },\n }}\n initial=\"hidden\"\n animate=\"visible\"\n exit=\"exit\"\n >\n {showReadMore && (\n <motion.div\n key=\"read-more\"\n style={{ marginRight: \"auto\" }}\n variants={{\n hidden: { opacity: 0, scale: 0.8 },\n visible: { opacity: 1, scale: 1, transition: { type: \"spring\", stiffness: ac?.actions?.stiffness ?? 400, damping: ac?.actions?.damping ?? 22 } },\n exit: {},\n }}\n >\n <Button\n variant=\"ghost\"\n icon={isExpanded ? \"Read less\" : \"Read more\"}\n onClick={(e) => {\n e.stopPropagation();\n setIsExpanded((v) => !v);\n }}\n aria-label={isExpanded ? \"Read less\" : \"Read more\"}\n style={{ width: \"auto\", padding: \"4px 10px\", fontSize: 10, letterSpacing: \"0.03em\" }}\n />\n </motion.div>\n )}\n {([\n { icon: <Copy size={14} aria-hidden />, onClick: () => onCopy?.(value), label: \"Copy\" },\n { icon: <Pencil size={14} aria-hidden />, onClick: () => onEdit?.(value), label: \"Edit\" },\n ] as const).map(({ icon, onClick, label }) => (\n <motion.div\n key={label}\n variants={{\n hidden: { opacity: 0, scale: 0.8 },\n visible: { opacity: 1, scale: 1, transition: { type: \"spring\", stiffness: ac?.actions?.stiffness ?? 400, damping: ac?.actions?.damping ?? 22 } },\n exit: {},\n }}\n >\n <Button variant=\"ghost\" icon={icon} onClick={onClick} aria-label={label} title={label} />\n </motion.div>\n ))}\n </motion.div>\n )}\n </AnimatePresence>\n );\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { Paperclip, Palette, Link2, X } from \"lucide-react\";\nimport { Button } from \"../Button/Button\";\nimport styles from \"./ChatInput.module.css\";\nimport type { InlineAnimConfig } from \"./ChatInput\";\n\nconst ADD_CARDS = [\n { Icon: Paperclip, label: \"Add\" },\n { Icon: Palette, label: \"Design\" },\n { Icon: Link2, label: \"Connectors\" },\n] as const;\n\nexport interface AddCardsOverlayProps {\n isAddOpen: boolean;\n setIsAddOpen: React.Dispatch<React.SetStateAction<boolean>>;\n onAdd?: (label?: string) => void;\n showInlineGlyph: boolean;\n showButtons: boolean;\n ac: InlineAnimConfig | undefined;\n}\n\nexport function AddCardsOverlay({\n isAddOpen,\n setIsAddOpen,\n onAdd,\n showInlineGlyph,\n showButtons,\n ac,\n}: AddCardsOverlayProps) {\n return (\n <AnimatePresence>\n {isAddOpen && (\n <>\n <motion.div\n key=\"backdrop\"\n className={styles.addBackdrop}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n onClick={(e) => {\n e.stopPropagation();\n setIsAddOpen(false);\n }}\n />\n <motion.div\n key=\"add-overlay\"\n className={styles.addOverlay}\n >\n <div className={styles.addCardsContainer}>\n {ADD_CARDS.map(({ Icon, label }, i) => {\n const sd = ac?.addCards?.staggerDelay ?? 0.04;\n const enterDelay = i * sd;\n const exitDelay = (ADD_CARDS.length - 1 - i) * sd;\n const angles = [\n ac?.addCards?.angle1 ?? -26,\n ac?.addCards?.angle2 ?? -2,\n ac?.addCards?.angle3 ?? 22,\n ];\n const hoverPull = ac?.addCards?.hoverPull ?? 8;\n\n return (\n <motion.button\n key={label}\n className={styles.addCardFan}\n style={{\n right: showInlineGlyph && showButtons ? 36 : 0,\n bottom: 1,\n transformOrigin: \"calc(100% - 22px) 50%\",\n zIndex: 3 - i,\n }}\n initial={{ opacity: 0, scale: 0.95, rotate: 0, width: 160 }}\n animate={{\n opacity: 1, scale: 1, rotate: angles[i], width: 160,\n transition: {\n type: \"spring\", stiffness: ac?.addCards?.stiffness ?? 350, damping: ac?.addCards?.damping ?? 25, delay: enterDelay,\n opacity: { duration: 0.1, delay: enterDelay },\n },\n }}\n whileHover={{\n width: 160 + hoverPull,\n transition: { type: \"spring\", stiffness: 400, damping: 25 },\n }}\n exit={{\n opacity: 0, scale: 0.5, rotate: 0, width: 160,\n transition: { duration: 0.15, delay: exitDelay, ease: \"easeIn\" },\n }}\n onClick={(e) => {\n e.stopPropagation();\n setIsAddOpen(false);\n onAdd?.(label);\n }}\n aria-label={label}\n >\n <Icon size={16} aria-hidden />\n <span>{label}</span>\n </motion.button>\n );\n })}\n </div>\n\n <motion.div\n key=\"x-btn-wrap\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={{ duration: 0.12, ease: \"easeOut\" }}\n style={{\n position: \"absolute\",\n right: showInlineGlyph && showButtons ? 44 : 8,\n bottom: 8,\n width: 28,\n height: 28,\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n pointerEvents: \"auto\",\n zIndex: 10,\n }}\n >\n <Button\n variant=\"ghost\"\n icon={<X size={14} aria-hidden />}\n onClick={(e) => {\n e.stopPropagation();\n setIsAddOpen(false);\n }}\n aria-label=\"Close\"\n title=\"Close\"\n className={styles.closeAdd}\n />\n </motion.div>\n </motion.div>\n </>\n )}\n </AnimatePresence>\n );\n}\n","/**\n * A shared live region, for telling a screen reader what just happened.\n *\n * Three things about live regions decide the shape of this file:\n *\n * 1. The region has to be in the document *before* the text goes into it.\n * Adding a region and its content in the same commit is, to most screen\n * readers, an ordinary DOM insertion — nothing is spoken. So the node is\n * created first and written to on a later tick.\n * 2. Writing the same string twice is not a change, and is not announced.\n * Clearing first makes a repeated answer speak again.\n * 3. One region per politeness, shared by every component on the page. Several\n * regions competing is how announcements get dropped or interleaved.\n *\n * This is deliberately not a React component: it must not be something a\n * consumer can forget to render.\n */\n\nexport type Politeness = \"polite\" | \"assertive\";\n\n/** Long enough for the region to be registered before it is written to. */\nconst WRITE_DELAY = 100;\n\nconst regions = new Map<Politeness, HTMLElement>();\nconst pending = new Map<Politeness, ReturnType<typeof setTimeout>>();\n\nconst visuallyHidden = {\n position: \"absolute\",\n width: \"1px\",\n height: \"1px\",\n margin: \"-1px\",\n padding: \"0\",\n border: \"0\",\n overflow: \"hidden\",\n clip: \"rect(0 0 0 0)\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n} as const;\n\nfunction getRegion(politeness: Politeness): HTMLElement | null {\n // Server render, or a test environment without a DOM.\n if (typeof document === \"undefined\") return null;\n\n const existing = regions.get(politeness);\n if (existing?.isConnected) return existing;\n\n const node = document.createElement(\"div\");\n node.setAttribute(\"role\", politeness === \"assertive\" ? \"alert\" : \"status\");\n node.setAttribute(\"aria-live\", politeness);\n // Answers are read as a whole. Without this a screen reader may read only\n // the part that changed, which for a replaced answer is meaningless.\n node.setAttribute(\"aria-atomic\", \"true\");\n node.dataset.inlineChatKit = \"live-region\";\n Object.assign(node.style, visuallyHidden);\n document.body.appendChild(node);\n regions.set(politeness, node);\n return node;\n}\n\n/**\n * Speak a message. Empty strings are ignored — clearing is not an announcement.\n */\nexport function announce(message: string, politeness: Politeness = \"polite\"): void {\n // Checked before the region is created, so nothing to say leaves no trace.\n if (!message) return;\n const node = getRegion(politeness);\n if (!node) return;\n\n const queued = pending.get(politeness);\n if (queued) clearTimeout(queued);\n\n node.textContent = \"\";\n pending.set(\n politeness,\n setTimeout(() => {\n pending.delete(politeness);\n node.textContent = message;\n }, WRITE_DELAY)\n );\n}\n\n/**\n * Drop the regions and anything queued. For tests, so one case cannot hear\n * what a previous one said.\n */\nexport function resetAnnouncer(): void {\n pending.forEach((timer) => clearTimeout(timer));\n pending.clear();\n regions.forEach((node) => node.remove());\n regions.clear();\n}\n","/**\n * Whether this reader has asked their system to reduce motion.\n *\n * Motion's own `useReducedMotion` covers rendering. This exists for the places\n * that are not rendering — the reveal in `useChatTurns` decides inside a\n * callback, where a hook's value would have to be captured in a closure and\n * kept fresh in a ref for no gain.\n *\n * The query is matched once and kept. `matchMedia` is not free, and the\n * decision is read on every answer.\n */\nconst QUERY = \"(prefers-reduced-motion: reduce)\";\n\nlet query: MediaQueryList | null | undefined;\n\nexport function prefersReducedMotion(): boolean {\n if (query === undefined) {\n // Server render, or a test environment without matchMedia. Assume motion\n // is fine: the client will correct it on the first real render.\n query = typeof window !== \"undefined\" && window.matchMedia ? window.matchMedia(QUERY) : null;\n }\n return query?.matches ?? false;\n}\n\n/** For tests, which change what `matchMedia` returns between cases. */\nexport function resetReducedMotionCache(): void {\n query = undefined;\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState, useSyncExternalStore, type RefObject } from \"react\";\nimport { announce } from \"../announce/announce\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\n\n/**\n * The microphone, and everything around turning it into text except the part\n * that is a service.\n *\n * The division is the same one the artifact pane settled: the kit owns what is\n * the same in every product and the host owns what differs. Permission,\n * recording, the level of the incoming signal and the states in between are\n * identical wherever this ships — sixty lines every host would otherwise write\n * again, slightly differently. Turning audio into words is a choice between\n * Whisper, Deepgram, a local model and somebody's own endpoint, and a kit that\n * chose one for its consumers would be wrong for most of them.\n *\n * So this records and hands over a `Blob`. What comes back is text.\n *\n * **What that costs, stated rather than discovered:** the browser's own\n * `SpeechRecognition` cannot be reached through this, because it insists on\n * holding the microphone itself and will not take a recording. It is the only\n * free transcriber there is, and this shuts the door on it. The door stays\n * shut on purpose: a kit that let each host drive the microphone would ship\n * four chats that behave differently and call it flexibility.\n */\n\nexport type VoiceState =\n /** No `getUserMedia` — an insecure context, or a browser without it. */\n | \"unsupported\"\n | \"idle\"\n /** The browser's permission prompt is up. It owns that dialog, not us. */\n | \"requesting\"\n | \"listening\"\n /** Recording is over and the host is working on it. */\n | \"transcribing\"\n /** Refused. Not an error: the browser will not ask again on its own. */\n | \"denied\"\n | \"failed\";\n\nexport interface TranscribeContext {\n /** Aborts when the reader cancels, or when the component goes away. */\n signal: AbortSignal;\n /** What the recorder actually produced, which is not the same everywhere. */\n mimeType: string;\n}\n\n/**\n * Audio in, words out.\n *\n * Return a string, a promise of one, or an async iterable of **deltas** — the\n * same three shapes `onSend` takes, and deltas accumulate the same way, so a\n * long dictation can appear as it is recognised rather than all at the end.\n */\nexport type TranscribeHandler = (\n audio: Blob,\n context: TranscribeContext\n) => string | Promise<string> | AsyncIterable<string>;\n\nexport interface UseVoiceInputOptions {\n onTranscribe?: TranscribeHandler;\n /**\n * Called with the transcript so far, every time it grows. Cumulative rather\n * than per-delta: the caller is putting this inside a string it already\n * holds, and giving it the whole run means it does not have to remember\n * where the last one ended.\n */\n onTranscript?: (text: string) => void;\n /** Called once when a recording has been fully transcribed. */\n onDone?: (text: string) => void;\n /**\n * The element the level is written onto, as `--ick-voice-level`, from 0 to 1.\n *\n * A number this component re-rendered on would be sixty renders a second for\n * a decoration. This is the same decision the highlighter's hover made after\n * it cost 28 DOM mutations per crossing: the value the browser needs goes\n * straight to the browser.\n */\n meterRef?: RefObject<HTMLElement | null>;\n}\n\nexport interface VoiceInput {\n state: VoiceState;\n /** Whether a microphone can be asked for at all. */\n supported: boolean;\n /** Why it failed, when it did. Safe to show. */\n error: string | null;\n start: () => void;\n stop: () => void;\n toggle: () => void;\n /** Forget a refusal, so the button can be offered again. */\n reset: () => void;\n}\n\n/** Level meter smoothing. Raw RMS jitters enough to read as noise. */\nconst SMOOTHING = 0.6;\n/** Quietest signal that still moves the meter, so a silent room reads as still. */\nconst FLOOR = 0.02;\n\n/** Support cannot change while the page is open, so there is nothing to watch. */\nconst subscribeNever = () => () => {};\n\nconst canRecord = () =>\n typeof window !== \"undefined\" &&\n typeof navigator !== \"undefined\" &&\n !!navigator.mediaDevices?.getUserMedia &&\n typeof window.MediaRecorder !== \"undefined\";\n\n/**\n * The first container the recorder will admit to supporting.\n *\n * Left to itself Safari produces mp4 and Chrome webm, and a host posting the\n * blob somewhere has to be told which arrived — that is what `mimeType` in the\n * context is for.\n */\nconst pickMimeType = (): string | undefined => {\n const wanted = [\"audio/webm;codecs=opus\", \"audio/webm\", \"audio/mp4\", \"audio/ogg;codecs=opus\"];\n const supported = window.MediaRecorder?.isTypeSupported;\n if (!supported) return undefined;\n return wanted.find((type) => window.MediaRecorder.isTypeSupported(type));\n};\n\nexport function useVoiceInput({\n onTranscribe,\n onTranscript,\n onDone,\n meterRef,\n}: UseVoiceInputOptions): VoiceInput {\n /**\n * Whether a microphone can be asked for, answered once and never again.\n *\n * `useSyncExternalStore` rather than an effect that corrects itself: a\n * server render has no `navigator`, so it must say no, and the client says\n * whatever is true — which is exactly the split this hook is for, and it\n * costs no second render. Written first as `useState(\"unsupported\")` plus an\n * effect, which lint stopped on the right grounds: setting state inside an\n * effect body is a cascading render, and here it was one on every mount.\n */\n const supported = useSyncExternalStore(subscribeNever, canRecord, () => false);\n const [phase, setPhase] = useState<Exclude<VoiceState, \"unsupported\">>(\"idle\");\n const [error, setError] = useState<string | null>(null);\n // Support is not a phase the machine moves through; it decides whether the\n // machine is on the page at all.\n const state: VoiceState = supported ? phase : \"unsupported\";\n\n const recorderRef = useRef<MediaRecorder | null>(null);\n const streamRef = useRef<MediaStream | null>(null);\n const audioRef = useRef<AudioContext | null>(null);\n const frameRef = useRef<number | null>(null);\n const chunksRef = useRef<BlobPart[]>([]);\n const abortRef = useRef<AbortController | null>(null);\n /** Set while stopping, so the recorder's own stop event knows why. */\n const cancelledRef = useRef(false);\n\n // Callbacks are read through a ref so that starting the microphone does not\n // depend on the identity of a function the host recreates every render.\n const handlers = useRef({ onTranscribe, onTranscript, onDone });\n useEffect(() => {\n handlers.current = { onTranscribe, onTranscript, onDone };\n });\n\n /** Everything that holds hardware or a frame loop, released in one place. */\n const teardown = useCallback(() => {\n if (frameRef.current !== null) {\n cancelAnimationFrame(frameRef.current);\n frameRef.current = null;\n }\n streamRef.current?.getTracks().forEach((track) => track.stop());\n streamRef.current = null;\n // Suspended rather than closed on older Safari, which throws on a second\n // close. Either way the graph stops running.\n void audioRef.current?.close().catch(() => {});\n audioRef.current = null;\n recorderRef.current = null;\n meterRef?.current?.style.setProperty(\"--ick-voice-level\", \"0\");\n }, [meterRef]);\n\n useEffect(\n () => () => {\n cancelledRef.current = true;\n abortRef.current?.abort();\n teardown();\n },\n [teardown]\n );\n\n /**\n * Writes the incoming level onto the meter element, once per frame.\n *\n * Skipped entirely under reduced motion. The meter is the only thing moving,\n * and a reader who asked for stillness gets a state that says \"listening\"\n * rather than one that pulses at them.\n */\n const runMeter = useCallback(\n (analyser: AnalyserNode) => {\n if (prefersReducedMotion()) return;\n const buffer = new Uint8Array(analyser.fftSize);\n let smoothed = 0;\n const tick = () => {\n analyser.getByteTimeDomainData(buffer);\n let sum = 0;\n for (const sample of buffer) {\n const centred = (sample - 128) / 128;\n sum += centred * centred;\n }\n const rms = Math.sqrt(sum / buffer.length);\n const level = rms < FLOOR ? 0 : Math.min(1, rms * 3);\n smoothed = smoothed * SMOOTHING + level * (1 - SMOOTHING);\n meterRef?.current?.style.setProperty(\"--ick-voice-level\", smoothed.toFixed(3));\n frameRef.current = requestAnimationFrame(tick);\n };\n frameRef.current = requestAnimationFrame(tick);\n },\n [meterRef]\n );\n\n /** Drain whatever the host returned, in any of its three shapes. */\n const consume = useCallback(async (result: ReturnType<TranscribeHandler>) => {\n if (typeof result === \"string\") return result;\n if (result && typeof (result as AsyncIterable<string>)[Symbol.asyncIterator] === \"function\") {\n let text = \"\";\n for await (const delta of result as AsyncIterable<string>) {\n text += delta;\n handlers.current.onTranscript?.(text);\n }\n return text;\n }\n return await (result as Promise<string>);\n }, []);\n\n const transcribe = useCallback(\n async (audio: Blob, mimeType: string) => {\n const handler = handlers.current.onTranscribe;\n if (!handler) {\n setPhase(\"idle\");\n return;\n }\n const controller = new AbortController();\n abortRef.current = controller;\n setPhase(\"transcribing\");\n announce(\"Transcribing.\");\n try {\n const text = await consume(handler(audio, { signal: controller.signal, mimeType }));\n if (controller.signal.aborted) return;\n // A recording with nothing in it is not a failure. Saying so out loud\n // is the difference between \"it is broken\" and \"say something\".\n if (!text) {\n announce(\"Nothing was heard.\");\n setPhase(\"idle\");\n return;\n }\n handlers.current.onTranscript?.(text);\n handlers.current.onDone?.(text);\n announce(\"Transcript added.\");\n setPhase(\"idle\");\n } catch (cause) {\n if (controller.signal.aborted) return;\n setError(cause instanceof Error ? cause.message : \"The transcript could not be made.\");\n setPhase(\"failed\");\n announce(\"The transcript could not be made.\", \"assertive\");\n } finally {\n abortRef.current = null;\n }\n },\n [consume]\n );\n\n const start = useCallback(async () => {\n // Support is not a phase any more — it is read straight from the platform\n // and gates whether this is reachable at all. Nothing to set here.\n if (!canRecord()) return;\n setError(null);\n cancelledRef.current = false;\n setPhase(\"requesting\");\n let stream: MediaStream;\n try {\n stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch (cause) {\n const name = (cause as DOMException)?.name;\n // A refusal is a durable state, not a passing error: the browser will\n // not raise its prompt again, so telling the reader to try again would\n // be sending them to a button that can no longer work.\n if (name === \"NotAllowedError\" || name === \"SecurityError\") {\n setPhase(\"denied\");\n announce(\"Microphone access was refused.\", \"assertive\");\n } else {\n setError(name === \"NotFoundError\" ? \"No microphone was found.\" : \"The microphone could not be opened.\");\n setPhase(\"failed\");\n announce(\"The microphone could not be opened.\", \"assertive\");\n }\n return;\n }\n\n streamRef.current = stream;\n chunksRef.current = [];\n\n try {\n const Ctor = window.AudioContext ?? (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;\n const context = new Ctor();\n audioRef.current = context;\n const analyser = context.createAnalyser();\n analyser.fftSize = 512;\n context.createMediaStreamSource(stream).connect(analyser);\n runMeter(analyser);\n } catch {\n // No meter is a lesser thing than no recording. Carry on without it.\n }\n\n const mimeType = pickMimeType();\n const recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);\n recorderRef.current = recorder;\n recorder.ondataavailable = (event) => {\n if (event.data.size > 0) chunksRef.current.push(event.data);\n };\n recorder.onstop = () => {\n const type = recorder.mimeType || mimeType || \"audio/webm\";\n const audio = new Blob(chunksRef.current, { type });\n chunksRef.current = [];\n teardown();\n if (cancelledRef.current || audio.size === 0) {\n setPhase(\"idle\");\n return;\n }\n void transcribe(audio, type);\n };\n recorder.start();\n setPhase(\"listening\");\n announce(\"Listening.\");\n }, [runMeter, teardown, transcribe]);\n\n const stop = useCallback(() => {\n const recorder = recorderRef.current;\n if (recorder && recorder.state !== \"inactive\") {\n announce(\"Stopped listening.\");\n recorder.stop();\n return;\n }\n // Nothing recording, so this is a cancel of whatever came after it.\n abortRef.current?.abort();\n abortRef.current = null;\n teardown();\n setPhase(\"idle\");\n }, [teardown]);\n\n const toggle = useCallback(() => {\n if (state === \"listening\" || state === \"requesting\" || state === \"transcribing\") stop();\n else if (state === \"idle\" || state === \"failed\") void start();\n }, [state, start, stop]);\n\n const reset = useCallback(() => {\n setError(null);\n if (canRecord()) setPhase(\"idle\");\n }, []);\n\n return {\n state,\n supported,\n error,\n start: () => void start(),\n stop,\n toggle,\n reset,\n };\n}\n","\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useId,\n useImperativeHandle,\n useRef,\n useState,\n forwardRef,\n} from \"react\";\n\nfunction placeCursorAtEnd(el: HTMLElement) {\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(el);\n range.collapse(false);\n sel?.removeAllRanges();\n sel?.addRange(range);\n}\n\n/**\n * How many characters sit before the caret, or the whole length when the caret\n * is somewhere else on the page.\n *\n * Dictation goes in where the cursor was, which is the difference between\n * finishing a half-typed sentence out loud and losing it. Measured by the\n * length of a range rather than by a node offset: the editor holds one text\n * node most of the time and several after a paste, and only the first of those\n * makes an offset mean anything.\n */\nfunction caretOffset(el: HTMLElement): number {\n const sel = window.getSelection();\n const text = el.textContent ?? \"\";\n if (!sel || sel.rangeCount === 0) return text.length;\n const range = sel.getRangeAt(0);\n if (!el.contains(range.endContainer)) return text.length;\n const upTo = range.cloneRange();\n upTo.selectNodeContents(el);\n upTo.setEnd(range.endContainer, range.endOffset);\n return upTo.toString().length;\n}\n\n/** Put the caret `offset` characters into `el`, clamped to what is there. */\nfunction placeCursorAt(el: HTMLElement, offset: number) {\n const node = el.firstChild;\n if (!node || node.nodeType !== Node.TEXT_NODE) {\n placeCursorAtEnd(el);\n return;\n }\n const range = document.createRange();\n const sel = window.getSelection();\n range.setStart(node, Math.max(0, Math.min(offset, node.textContent?.length ?? 0)));\n range.collapse(true);\n sel?.removeAllRanges();\n sel?.addRange(range);\n}\nimport { AnimatePresence, LayoutGroup, MotionConfig, animate, motion, useAnimationControls, type Transition } from \"motion/react\";\nimport { Plus, X } from \"lucide-react\";\nimport { Attachments, type Attachment } from \"../Attachments/Attachments\";\nimport { Button } from \"../Button/Button\";\nimport { MorphGlyph } from \"./MorphGlyph\";\nimport { HoverActionsRow } from \"./HoverActionsRow\";\nimport { AddCardsOverlay } from \"./AddCardsOverlay\";\nimport { useVoiceInput, type TranscribeHandler } from \"../voice/useVoiceInput\";\nimport styles from \"./ChatInput.module.css\";\n\n\nexport type ChatInputState = \"idle\" | \"typing\" | \"responding\" | \"resting\";\n\nexport interface InlineAnimConfig {\n bubble: { stiffness: number; damping: number; mass: number };\n button: { stiffness: number; damping: number; mass: number; staggerEnter: number; staggerExit: number };\n addButton: { duration: number; visualDuration: number; bounce: number };\n enterButton: { duration: number; visualDuration: number; bounce: number };\n ripple: { scaleX: number; duration: number; pulseDuration: number };\n wrap: {\n nearThreshold: number;\n exitThreshold: number;\n preExpandHeight: number;\n slideInDelay: number;\n /**\n * How long the controls take to leave when the composer grows into a\n * second line, in seconds.\n *\n * It was a literal `0.15` in three places, which is why the exit could not\n * be tuned while everything around it could: the dial reached the springs\n * that bring the row back and nothing that takes it away.\n */\n exitDuration: number;\n };\n actions: { staggerDelay: number; duration: number; stiffness: number; damping: number };\n addCards: { staggerDelay: number; stiffness: number; damping: number; inputScale: number; inputBlur: number; angle1: number; angle2: number; angle3: number; hoverPull: number };\n}\n\nexport const defaultInlineAnimConfig: InlineAnimConfig = {\n bubble: { stiffness: 600, damping: 22, mass: 0.3 },\n button: { stiffness: 500, damping: 50, mass: 0.2, staggerEnter: 0.06, staggerExit: 0.06 },\n addButton: { duration: 0.2, visualDuration: 0.2, bounce: 0.2 },\n enterButton: { duration: 0.2, visualDuration: 0.2, bounce: 0.2 },\n ripple: { scaleX: 1.000, duration: 0.19, pulseDuration: 140 },\n wrap: { nearThreshold: 0.92, exitThreshold: 0.75, preExpandHeight: 16, slideInDelay: 120, exitDuration: 0.15 },\n actions: { staggerDelay: 0.07, duration: 0.12, stiffness: 400, damping: 22 },\n addCards: { staggerDelay: 0.06, stiffness: 800, damping: 41, inputScale: 0.95, inputBlur: 2, angle1: 0, angle2: -25, angle3: -50, hoverPull: 12 },\n};\n\nexport interface ChatInputHandle {\n focus: (options?: FocusOptions) => void;\n setValue: (value: string) => void;\n getValue: () => string;\n}\n\nexport interface ChatInputProps {\n state: ChatInputState;\n value: string;\n onChange: (value: string) => void;\n /**\n * What was typed, and what is going with it.\n *\n * The second argument is the whole reason attachments exist: before it, one\n * could be picked and shown in the composer and then quietly dropped on\n * send, which is worse than not offering it.\n */\n onSubmit: (value: string, attachments: Attachment[]) => void;\n onStop?: () => void;\n onAdd?: () => void;\n /**\n * What is attached, controlled. Leave it out and the composer keeps its own\n * — the same rule `useDisclosure` follows.\n */\n attachments?: Attachment[];\n /**\n * Files somebody picked. Given, this is called instead of the composer\n * attaching them itself — for a host that wants to upload first and attach\n * the URL it gets back.\n */\n onAttach?: (files: File[]) => void;\n onRemoveAttachment?: (id: string) => void;\n /** Passed to the file picker. Defaults to images. */\n accept?: string;\n /** More than one at a time. Off by default. */\n multiple?: boolean;\n /**\n * Audio in, words out. Given, the composer offers a microphone where the\n * send glyph would be while there is nothing to send; left out, there is no\n * microphone at all — the same rule `onRemove` follows on `<Attachments>`.\n *\n * The kit records and hands over a `Blob`; what it does with the audio is\n * the half a host owns. See `useVoiceInput`.\n */\n onTranscribe?: TranscribeHandler;\n onCopy?: (value: string) => void;\n onEdit?: (value: string) => void;\n onCancelEdit?: () => void;\n isEditing?: boolean;\n placeholder?: string;\n animationConfig?: InlineAnimConfig;\n style?: React.CSSProperties;\n}\n\n/** Bubble spring used when no animationConfig override is supplied. */\nconst defaultBubbleSpring: Transition = {\n type: \"spring\",\n stiffness: 600,\n damping: 21.5,\n mass: 0.2,\n};\n\n/**\n * Insert text at the caret of a contenteditable.\n *\n * `execCommand` is deprecated but remains the only way to insert into a\n * contenteditable while keeping the browser's native undo stack, so it stays\n * as the preferred path. It is absent in some environments and can refuse the\n * command, hence the range-based fallback — which the browser does not follow\n * with an `input` event, so the caller is told to sync state itself.\n *\n * Returns true when the browser handled it and an input event is coming.\n */\nconst insertTextAtCaret = (text: string): boolean => {\n if (typeof document.execCommand === \"function\") {\n try {\n if (document.execCommand(\"insertText\", false, text)) return true;\n } catch {\n // Fall through to the manual path.\n }\n }\n\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return false;\n\n const range = selection.getRangeAt(0);\n range.deleteContents();\n const node = document.createTextNode(text);\n range.insertNode(node);\n range.setStartAfter(node);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n return false;\n};\n\nexport const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(\n function ChatInput(\n {\n state,\n value,\n onChange,\n onSubmit,\n onStop,\n onAdd,\n attachments,\n onAttach,\n onRemoveAttachment,\n accept = \"image/*\",\n multiple = false,\n onTranscribe,\n onCopy,\n onEdit,\n onCancelEdit,\n isEditing = false,\n /* A default that can ship. It was \"Placeholder text...\", a stand-in\n nobody replaced, and a default is what every consumer who has not\n thought about it yet gets in production — which is most of them on\n their first afternoon. Found by writing the getting-started page and\n looking at what it drew. */\n placeholder = \"Ask anything…\",\n animationConfig,\n style,\n },\n ref\n ) {\n const instanceId = useId();\n const editorRef = useRef<HTMLDivElement>(null);\n const editorWrapRef = useRef<HTMLDivElement>(null);\n const editorClipRef = useRef<HTMLDivElement>(null);\n const textScrollHeightRef = useRef(0);\n const internalChangeRef = useRef(false);\n const measureSpanRef = useRef<HTMLSpanElement>(null);\n const [hovered, setHovered] = useState(false);\n const [pulsing, setPulsing] = useState(false);\n const [expandedMode, setExpandedMode] = useState(false);\n const [showButtons, setShowButtons] = useState(true);\n const [isExpanded, setIsExpanded] = useState(false);\n const [isOverflowing, setIsOverflowing] = useState(false);\n const [isAddOpen, setIsAddOpen] = useState(false);\n const singleLineHeight = useRef(0);\n const expandedModeRef = useRef(false);\n const compactAvailWidthRef = useRef(0);\n const pendingExpansion = useRef(false);\n const buttonsTimerRef = useRef<number | null>(null);\n\n // The timer below outlives the component otherwise. In a browser React\n // shrugs at a setState on an unmounted component; in a test environment\n // that has already been torn down it throws, which made the suite fail at\n // random depending on how long the last render happened to take.\n useEffect(\n () => () => {\n if (buttonsTimerRef.current) clearTimeout(buttonsTimerRef.current);\n },\n []\n );\n const surfaceControls = useAnimationControls();\n const prevState = useRef(state);\n const animCfgRef = useRef(animationConfig);\n animCfgRef.current = animationConfig;\n\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [own, setOwn] = useState<Attachment[]>([]);\n const attached = attachments ?? own;\n /* Read through a ref by the keydown handler, which is memoised on things\n that do not change per keystroke. Putting the list in its deps would\n rebuild the handler on every attach for no gain. */\n const attachedRef = useRef(attached);\n attachedRef.current = attached;\n\n /* Every object URL this composer made, so it can hand them back.\n `URL.createObjectURL` pins the file in memory until it is revoked, and\n nothing was revoking: attaching and removing an image ten times leaked\n ten of them, and so did navigating away with one attached. */\n const madeUrls = useRef<string[]>([]);\n useEffect(\n () => () => {\n madeUrls.current.forEach((url) => URL.revokeObjectURL(url));\n madeUrls.current = [];\n },\n []\n );\n\n const forget = useCallback((url?: string) => {\n if (!url) return;\n const at = madeUrls.current.indexOf(url);\n // Only ours. A URL the host passed in is the host's to revoke.\n if (at === -1) return;\n URL.revokeObjectURL(url);\n madeUrls.current.splice(at, 1);\n }, []);\n\n const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(e.target.files ?? []);\n // Cleared, so picking the same file twice in a row still fires.\n e.target.value = \"\";\n if (files.length === 0) return;\n\n if (onAttach) {\n onAttach(files);\n return;\n }\n\n const picked = files.map((file) => {\n const url = URL.createObjectURL(file);\n madeUrls.current.push(url);\n return {\n id: `${file.name}-${file.size}-${file.lastModified}`,\n name: file.name,\n type: file.type,\n size: file.size,\n url,\n };\n });\n setOwn((current) => {\n if (multiple) return [...current, ...picked];\n // Replacing drops the old one, so its URL goes with it. Picking six\n // images in a row leaked five otherwise.\n const kept = picked.slice(-1);\n current.forEach((a) => forget(a.url));\n picked.slice(0, -1).forEach((a) => forget(a.url));\n return kept;\n });\n };\n\n const removeAttachment = useCallback(\n (id: string) => {\n if (onRemoveAttachment) {\n onRemoveAttachment(id);\n return;\n }\n setOwn((current) => {\n forget(current.find((a) => a.id === id)?.url);\n return current.filter((a) => a.id !== id);\n });\n },\n [onRemoveAttachment, forget]\n );\n\n useImperativeHandle(ref, () => ({\n focus: (options) => editorRef.current?.focus(options),\n setValue: (v) => {\n const el = editorRef.current;\n if (el) { el.textContent = v; placeCursorAtEnd(el); }\n onChange(v);\n },\n getValue: () => value,\n }), [onChange, value]);\n\n /**\n * Dictation.\n *\n * The microphone takes the send glyph's slot, and it can, because that slot\n * is empty exactly when it is wanted: `showSend` is content-gated, so an\n * empty composer draws nothing there. One control that means \"say it\" until\n * there is something to send and \"send it\" afterwards, rather than a third\n * button pushed into a pill that has room for two.\n */\n const meterRef = useRef<HTMLSpanElement>(null);\n /** Where the transcript goes, fixed when recording starts. */\n const dictation = useRef<{ base: string; at: number } | null>(null);\n /**\n * Where the caret last was inside the editor, or null if it has never been\n * there.\n *\n * Read when recording starts rather than measured then, because by then it\n * is gone: pressing the microphone moves focus to a button, and a\n * contenteditable's selection does not survive that. Reading it in the\n * click handler put every transcript at the end of the field — which is\n * the behaviour \"insert at the caret\" exists to avoid, and it was a test\n * that noticed rather than a screenshot.\n */\n const lastCaret = useRef<number | null>(null);\n\n useEffect(() => {\n if (!onTranscribe) return;\n const el = editorRef.current;\n if (!el) return;\n const remember = () => {\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0) return;\n if (!el.contains(sel.getRangeAt(0).endContainer)) return;\n lastCaret.current = caretOffset(el);\n };\n // On the document, because that is where the event is: a selection\n // inside a contenteditable does not raise one on the element.\n document.addEventListener(\"selectionchange\", remember);\n return () => document.removeEventListener(\"selectionchange\", remember);\n }, [onTranscribe]);\n\n const putTranscript = useCallback(\n (text: string) => {\n const where = dictation.current;\n const el = editorRef.current;\n if (!where || !el) return;\n // The whole run every time rather than the last delta, so a partial\n // transcript that grows replaces itself instead of stacking up.\n const next = where.base.slice(0, where.at) + text + where.base.slice(where.at);\n el.textContent = next;\n placeCursorAt(el, where.at + text.length);\n onChange(next);\n },\n [onChange]\n );\n\n const voice = useVoiceInput({\n onTranscribe,\n onTranscript: putTranscript,\n onDone: () => {\n dictation.current = null;\n editorRef.current?.focus();\n },\n meterRef,\n });\n\n const isGlass = state === \"responding\" || state === \"resting\";\n const isReadOnly = isGlass;\n const hasContent = value.trim().length > 0 || attached.length > 0;\n const isInputting = state === \"typing\" || state === \"idle\";\n const showSend = isInputting && hasContent;\n const showStop = state === \"responding\";\n const isRestingHovered = state === \"resting\" && hovered;\n const showActions = isRestingHovered;\n const showReadMore = state === \"resting\" && isOverflowing;\n const voiceBusy = voice.state === \"listening\" || voice.state === \"requesting\" || voice.state === \"transcribing\";\n /**\n * The microphone stands beside the plus, not in the send glyph's slot.\n *\n * The slot was the first idea and it was wrong for a reason a test found\n * rather than a screenshot: that slot is empty exactly while the composer\n * is, so the microphone would disappear the moment anything was in the\n * field — including the moment the first dictated word landed. Dictating\n * into a half-typed sentence is the case that made \"insert at the caret\"\n * worth building, and it needs a control that is still there once there is\n * something to insert into.\n *\n * Beside the plus it also reads better: both are ways of putting something\n * into the message that is not typing.\n *\n * A refusal keeps it on screen, disabled. The browser will not raise its\n * prompt a second time, so a control that vanished at that moment would\n * leave nothing to explain why — and no way back once the reader has\n * changed it in site settings.\n */\n const showMic = !!onTranscribe && voice.supported && !isEditing;\n const showInlineGlyph = showSend || showStop;\n\n const ac = animationConfig;\n const bubbleSpring: Transition = ac\n ? { type: \"spring\", stiffness: ac.bubble.stiffness, damping: ac.bubble.damping, mass: ac.bubble.mass }\n : defaultBubbleSpring;\n\n const updateFade = useCallback(() => {\n const wrap = editorWrapRef.current;\n const clip = editorClipRef.current;\n if (!wrap || !clip) return;\n const hasOverflow = wrap.scrollHeight > wrap.clientHeight;\n if (!hasOverflow) { clip.removeAttribute(\"data-fade\"); return; }\n const atTop = wrap.scrollTop <= 1;\n const atBottom = wrap.scrollTop + wrap.clientHeight >= wrap.scrollHeight - 1;\n if (!atTop && !atBottom) clip.dataset.fade = \"both\";\n else if (!atTop) clip.dataset.fade = \"top\";\n else if (!atBottom) clip.dataset.fade = \"bottom\";\n else clip.removeAttribute(\"data-fade\");\n }, []);\n\n useEffect(() => {\n const wrap = editorWrapRef.current;\n if (!wrap) return;\n wrap.addEventListener(\"scroll\", updateFade, { passive: true });\n return () => wrap.removeEventListener(\"scroll\", updateFade);\n }, [updateFade]);\n\n useEffect(() => {\n const id = requestAnimationFrame(() => {\n const wrap = editorWrapRef.current;\n if (wrap && !isReadOnly) {\n wrap.scrollTop = wrap.scrollHeight;\n }\n updateFade();\n });\n return () => cancelAnimationFrame(id);\n }, [value, isReadOnly, updateFade]);\n\n /**\n * The composer takes focus when it becomes one, and **does not scroll**.\n *\n * `focus()` scrolls the element into view. That is usually a kindness and\n * here it is a fault: when an answer settles, the next turn is appended\n * and this runs on a composer that has just mounted at the bottom of the\n * conversation — so the browser scrolled to it instantly, and the message\n * that had been sent leapt off the top of the view. Measured on the demo\n * at 1100×700: `scrollTop` 0 → 175 in a single frame at the moment the\n * turn count went from one to two.\n *\n * It took three traces to find, because every instrument pointed at the\n * scroll logic — which was innocent. `Conversation` made exactly one\n * correction in the whole session, and it was a smooth one arriving after\n * the jump had already happened.\n *\n * Where the view goes belongs to `Conversation`, which knows about\n * anchors, tails and whether a reader has scrolled away. Focus is about\n * the keyboard.\n */\n useEffect(() => {\n if (state === \"idle\" || state === \"typing\") {\n editorRef.current?.focus({ preventScroll: true });\n }\n }, [state]);\n\n useEffect(() => {\n const el = editorRef.current;\n if (el) singleLineHeight.current = el.scrollHeight || Math.round(12 * 1.45);\n }, []);\n\n useEffect(() => {\n const el = editorRef.current;\n const span = measureSpanRef.current;\n if (!el || !singleLineHeight.current) return;\n\n requestAnimationFrame(() => {\n const textW = span ? span.offsetWidth : 0;\n const contentW = el.clientWidth - 16;\n const wrap = editorWrapRef.current;\n const isMultiline = (wrap?.scrollHeight ?? 0) > singleLineHeight.current + 2;\n\n const ac = animCfgRef.current;\n if (!expandedModeRef.current) {\n compactAvailWidthRef.current = contentW;\n if (isMultiline || textW >= compactAvailWidthRef.current * (ac?.wrap?.nearThreshold ?? 0.92)) {\n expandedModeRef.current = true;\n pendingExpansion.current = true;\n setShowButtons(false);\n }\n } else {\n const wrappedNow = isMultiline || textW >= contentW - 2;\n if (!wrappedNow && textW < compactAvailWidthRef.current * (ac?.wrap?.exitThreshold ?? 0.75)) {\n expandedModeRef.current = false;\n pendingExpansion.current = false;\n setExpandedMode(false);\n setShowButtons(true);\n if (buttonsTimerRef.current) {\n clearTimeout(buttonsTimerRef.current);\n buttonsTimerRef.current = null;\n }\n }\n }\n });\n }, [value]);\n\n useEffect(() => {\n if (!isGlass) return;\n const wrap = editorWrapRef.current;\n if (!wrap || wrap.scrollTop === 0) return;\n const from = wrap.scrollTop;\n animate(from, 0, {\n type: \"spring\",\n stiffness: 280,\n damping: 28,\n mass: 0.6,\n onUpdate: (v) => { wrap.scrollTop = v; },\n });\n }, [isGlass]);\n\n useEffect(() => {\n if (!isGlass) {\n setIsExpanded(false);\n setIsOverflowing(false);\n } else {\n setIsAddOpen(false);\n }\n }, [isGlass]);\n\n useEffect(() => {\n if (!isGlass) return;\n const clip = editorClipRef.current;\n if (!clip) return;\n if (isOverflowing && !isExpanded) {\n clip.dataset.fade = \"bottom\";\n } else {\n clip.removeAttribute(\"data-fade\");\n }\n }, [isGlass, isOverflowing, isExpanded]);\n\n useEffect(() => {\n const prev = prevState.current;\n prevState.current = state;\n if (prev !== \"responding\" || state !== \"resting\") return;\n\n requestAnimationFrame(() => {\n const wrap = editorWrapRef.current;\n if (wrap) {\n textScrollHeightRef.current = wrap.scrollHeight;\n setIsOverflowing(wrap.scrollHeight > 241);\n updateFade();\n }\n });\n\n setPulsing(true);\n const ac = animCfgRef.current;\n surfaceControls.start({\n scaleX: [1, ac?.ripple?.scaleX ?? 1.018, 1],\n transition: {\n duration: ac?.ripple?.duration ?? 0.38,\n times: [0, 0.4, 1],\n ease: [0.25, 1, 0.5, 1],\n },\n });\n const id = window.setTimeout(() => setPulsing(false), ac?.ripple?.pulseDuration ?? 260);\n return () => clearTimeout(id);\n }, [state, surfaceControls, updateFade]);\n\n const handleInput = useCallback(() => {\n const el = editorRef.current;\n if (!el) return;\n internalChangeRef.current = true;\n onChange(el.textContent ?? \"\");\n }, [onChange]);\n\n const handlePaste = useCallback(\n (e: React.ClipboardEvent<HTMLDivElement>) => {\n e.preventDefault();\n const text = e.clipboardData.getData(\"text/plain\");\n if (!insertTextAtCaret(text)) handleInput();\n },\n [handleInput]\n );\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n if (!e.shiftKey && (value.trim().length > 0 || attachedRef.current.length > 0)) {\n onSubmit(value, attachedRef.current);\n } else if (e.shiftKey && !insertTextAtCaret(\"\\n\")) handleInput();\n }\n },\n [value, onSubmit, handleInput]\n );\n\n useEffect(() => {\n const el = editorRef.current;\n if (!el) return;\n if (internalChangeRef.current) { internalChangeRef.current = false; return; }\n if (el.textContent !== value) {\n el.textContent = value;\n if (document.activeElement === el) placeCursorAtEnd(el);\n }\n }, [value]);\n\n return (\n // `reducedMotion=\"user\"` is set here rather than left to the host app,\n // because a host that never sets it leaves every reader with the full\n // morph. Motion snaps transforms and layout to their final values and\n // keeps opacity and colour, which is the right split: the input still\n // reads as changing state, it just stops travelling to get there.\n <MotionConfig reducedMotion=\"user\">\n <LayoutGroup id={instanceId}>\n <div\n className={styles.wrap}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n style={style}\n >\n <motion.div\n className={`${styles.root} ${isGlass ? styles.isGlassRoot : \"\"}`}\n layout\n animate={{\n filter: isAddOpen ? `blur(${ac?.addCards?.inputBlur ?? 2}px)` : \"blur(0px)\",\n }}\n style={{\n pointerEvents: isAddOpen ? \"none\" : \"auto\",\n width: style?.width === \"100%\" ? \"100%\" : undefined,\n maxWidth: style?.maxWidth,\n }}\n transition={{\n ...bubbleSpring,\n filter: { type: \"tween\", duration: 0.2, ease: \"easeOut\" },\n }}\n >\n <motion.div\n className={`${styles.surface} ${isGlass ? styles.glass : \"\"} ${isRestingHovered ? styles.hovered : \"\"} ${pulsing ? styles.pulsed : \"\"}`}\n style={{\n alignItems: \"stretch\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n }}\n transition={bubbleSpring}\n animate={surfaceControls}\n onClick={() => editorRef.current?.focus()}\n >\n <input\n type=\"file\"\n ref={fileInputRef}\n accept={accept}\n multiple={multiple}\n style={{ display: \"none\" }}\n onChange={handleFileChange}\n />\n\n <AnimatePresence>\n {attached.length > 0 && (\n <motion.div\n initial={{ opacity: 0, height: 0 }}\n animate={{ opacity: 1, height: \"auto\" }}\n exit={{ opacity: 0, height: 0 }}\n transition={{ type: \"spring\", stiffness: 300, damping: 28 }}\n style={{ overflow: \"hidden\", width: \"100%\", flexShrink: 0 }}\n >\n <div className={styles.attachedTray} data-glass={isGlass || undefined}>\n {/* Read-only while the bubble is glass: the message has\n been sent, so these are a record of what went with\n it rather than something still being assembled. */}\n <Attachments\n attachments={attached}\n onRemove={isGlass ? undefined : removeAttachment}\n />\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n\n\n\n <div style={{ display: \"flex\", flexDirection: \"row\", alignItems: \"center\", flexWrap: expandedMode ? \"wrap\" : \"nowrap\", justifyContent: expandedMode ? \"flex-end\" : \"flex-start\", width: \"100%\", minWidth: 0, flex: 1 }}>\n <span\n ref={measureSpanRef}\n aria-hidden=\"true\"\n style={{\n position: \"absolute\",\n visibility: \"hidden\",\n pointerEvents: \"none\",\n whiteSpace: \"nowrap\",\n font: \"inherit\",\n letterSpacing: \"inherit\",\n }}\n >\n {value}\n </span>\n\n <div\n ref={editorClipRef}\n className={styles.editorClip}\n style={{ flexBasis: expandedMode ? \"100%\" : undefined }}\n >\n <motion.div\n ref={editorWrapRef}\n className={`${styles.editorWrap} ${isGlass ? styles.editorWrapGlass : \"\"}`}\n layout=\"position\"\n animate={isGlass && isOverflowing\n ? { maxHeight: isExpanded ? textScrollHeightRef.current : 240 }\n : undefined\n }\n // The text has to travel on the same spring as the box around\n // it. Given its own — this used to be a slower, heavier one —\n // it lags the morph and visibly slides outside the bubble\n // before catching up, for about half a second after the\n // bubble itself has settled. Height is a separate concern and\n // keeps its own easing.\n transition={{\n ...bubbleSpring,\n maxHeight: { type: \"spring\", stiffness: 300, damping: 35, mass: 0.8 },\n }}\n >\n <div\n ref={editorRef}\n className={styles.editor}\n contentEditable={isReadOnly ? \"false\" : \"plaintext-only\"}\n suppressContentEditableWarning\n onInput={handleInput}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n spellCheck={false}\n role=\"textbox\"\n aria-multiline=\"true\"\n aria-label={placeholder}\n data-placeholder={isReadOnly ? \"\" : placeholder}\n />\n </motion.div>\n </div>\n\n <div className={styles.buttonGroup}>\n {/* A keyed child of the row's own presence, like the other two.\n\n It was outside Motion for a while, because inside this\n presence its props appeared to stop reaching the DOM. That\n was measured in jsdom, where Motion's animations never\n complete — an exit that never finishes leaves\n `AnimatePresence` rendering its stored children forever.\n In a real browser the exits do finish, and the props do\n land; verified before moving it back.\n\n Outside, it could not leave: the other two unmount for the\n wrap, so the layout cannot drag them anywhere, while this\n one stayed mounted and got carried into the second line\n with the text. */}\n {!isGlass && showButtons && showMic && (\n <motion.div\n key=\"mic\"\n initial={{ opacity: 0, scale: 0, width: 0, height: 0, marginLeft: 0 }}\n animate={{ opacity: 1, scale: 1, width: 28, height: 28, marginLeft: 2 }}\n exit={{\n opacity: 0, scale: 0, width: 0, height: 0, marginLeft: 0,\n transition: pendingExpansion.current\n ? { type: \"tween\", duration: ac?.wrap?.exitDuration ?? 0.15, ease: \"easeOut\", delay: (ac?.button?.staggerExit ?? 0.055) * 2 }\n : undefined\n }}\n transition={{\n type: \"spring\",\n visualDuration: ac?.addButton?.visualDuration ?? 0.4,\n bounce: ac?.addButton?.bounce ?? 0.5,\n /* The row assembles right to left — the send glyph has\n no delay and the plus waits one stagger — so the\n microphone, leftmost, waits two. */\n delay: (ac?.button?.staggerEnter ?? 0.055) * 2,\n opacity: { type: \"tween\", duration: 0.15 }\n }}\n style={{ display: \"flex\", justifyContent: \"center\", alignItems: \"center\", flexShrink: 0, position: \"relative\" }}\n >\n <Button\n variant=\"ghost\"\n data-voice={voice.state}\n icon={\n <>\n {/* The level of the incoming signal. `useVoiceInput`\n writes `--ick-voice-level` here once a frame and\n nothing reads it but CSS — a number this\n component re-rendered on would be sixty renders a\n second for a decoration. */}\n <span\n ref={meterRef}\n className={styles.voiceMeter}\n data-live={voice.state === \"listening\" || undefined}\n aria-hidden\n />\n {/* A square means \"press to stop what is running\".\n Nothing is running while the browser's prompt is\n up, so `requesting` keeps the microphone and says\n it is busy instead — a stop button over a\n permission dialog claims a recording that has not\n started. */}\n <MorphGlyph\n mode={voice.state === \"listening\" || voice.state === \"transcribing\" ? \"stop\" : \"mic\"}\n size={14}\n />\n </>\n }\n onClick={(e) => {\n e.stopPropagation();\n if (voice.state === \"denied\") return;\n if (!voiceBusy) {\n // Fixed here rather than read when the transcript\n // arrives: by then focus has been on a button for\n // however long somebody spoke, and the caret is\n // wherever that left it.\n // Never seen a caret in the editor means nobody\n // has been typing in it, and the end is where a\n // first sentence belongs.\n const at = Math.min(lastCaret.current ?? value.length, value.length);\n dictation.current = { base: value, at };\n }\n voice.toggle();\n }}\n aria-busy={voice.state === \"requesting\" || voice.state === \"transcribing\"}\n aria-pressed={voice.state === \"listening\"}\n disabled={voice.state === \"denied\"}\n aria-label={\n voice.state === \"listening\"\n ? \"Stop listening\"\n : voice.state === \"requesting\"\n ? \"Waiting for microphone access\"\n : voice.state === \"transcribing\"\n ? \"Cancel transcription\"\n : voice.state === \"denied\"\n ? \"Microphone blocked\"\n : \"Dictate a message\"\n }\n style={{ width: \"100%\", height: \"100%\", position: \"relative\" }}\n />\n </motion.div>\n )}\n <AnimatePresence\n initial={false}\n onExitComplete={() => {\n if (!pendingExpansion.current) return;\n pendingExpansion.current = false;\n setExpandedMode(true);\n if (buttonsTimerRef.current) clearTimeout(buttonsTimerRef.current);\n buttonsTimerRef.current = window.setTimeout(() => {\n setShowButtons(true);\n buttonsTimerRef.current = null;\n }, animCfgRef.current?.wrap?.slideInDelay ?? 120);\n }}\n >\n {!isGlass && showButtons && (\n <motion.div\n key=\"lead\"\n initial={{ opacity: 0, scale: 0, width: 0, height: 0 }}\n animate={{ opacity: 1, scale: 1, width: 28, height: 28 }}\n exit={{\n opacity: 0, scale: 0, width: 0, height: 0,\n transition: pendingExpansion.current\n ? { type: \"tween\", duration: ac?.wrap?.exitDuration ?? 0.15, ease: \"easeOut\", delay: ac?.button?.staggerExit ?? 0.055 }\n : undefined\n }}\n transition={{\n type: \"spring\",\n visualDuration: ac?.addButton?.visualDuration ?? 0.4,\n bounce: ac?.addButton?.bounce ?? 0.5,\n delay: ac?.button?.staggerEnter ?? 0.055,\n opacity: { type: \"tween\", duration: 0.15 }\n }}\n style={{ display: \"flex\", justifyContent: \"center\", alignItems: \"center\", flexShrink: 0, position: \"relative\" }}\n >\n <AnimatePresence mode=\"popLayout\" initial={false}>\n {isEditing ? (\n <motion.div\n key=\"cancel-btn\"\n initial={{ opacity: 0, scale: 0.5, rotate: -90 }}\n animate={{ opacity: 1, scale: 1, rotate: 0 }}\n exit={{ opacity: 0, scale: 0.5, rotate: -90 }}\n transition={{ duration: 0.14, ease: \"easeOut\" }}\n style={{ position: \"absolute\", inset: 0 }}\n >\n <Button\n variant=\"ghost\"\n icon={<X size={14} aria-hidden />}\n onClick={(e) => {\n e.stopPropagation();\n onCancelEdit?.();\n }}\n aria-label=\"Cancel edit\"\n title=\"Cancel edit\"\n style={{ width: \"100%\", height: \"100%\" }}\n />\n </motion.div>\n ) : !isAddOpen ? (\n <motion.div\n key=\"plus-btn\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={{ duration: 0.12, ease: \"easeOut\" }}\n style={{ position: \"absolute\", inset: 0 }}\n >\n <Button\n variant=\"ghost\"\n icon={<Plus size={14} aria-hidden />}\n onClick={(e) => {\n e.stopPropagation();\n setIsAddOpen(true);\n }}\n aria-label=\"Add\"\n title=\"Add\"\n style={{ width: \"100%\", height: \"100%\" }}\n />\n </motion.div>\n ) : null}\n </AnimatePresence>\n </motion.div>\n )}\n {showInlineGlyph && showButtons && (\n <motion.div\n key=\"inline-action\"\n initial={{ opacity: 0, scale: 0, width: 0, height: 0, marginLeft: 0 }}\n animate={{ opacity: 1, scale: 1, width: 28, height: 28, marginLeft: 8 }}\n exit={{\n opacity: 0,\n scale: 0,\n width: 0,\n height: 0,\n marginLeft: 0,\n transition: pendingExpansion.current\n ? { type: \"tween\", duration: ac?.wrap?.exitDuration ?? 0.15, ease: \"easeOut\" }\n : {\n type: \"spring\",\n visualDuration: ac?.enterButton?.visualDuration ?? 0.18,\n bounce: ac?.enterButton?.bounce ?? 0.3,\n opacity: { type: \"tween\", duration: 0.15 },\n height: bubbleSpring,\n width: bubbleSpring,\n marginLeft: bubbleSpring\n }\n }}\n transition={{\n type: \"spring\",\n visualDuration: ac?.enterButton?.visualDuration ?? 0.4,\n bounce: ac?.enterButton?.bounce ?? 0.5,\n opacity: { type: \"tween\", duration: 0.15 }\n }}\n style={{ display: \"flex\", justifyContent: \"center\", alignItems: \"center\", flexShrink: 0, overflow: \"visible\", transformOrigin: \"right\" }}\n >\n <Button\n variant=\"primary\"\n icon={<MorphGlyph mode={showStop ? \"stop\" : isEditing ? \"check\" : \"send\"} />}\n onClick={(e) => {\n e.stopPropagation();\n if (showStop) onStop?.();\n else onSubmit(value, attached);\n }}\n aria-label={showStop ? \"Stop response\" : isEditing ? \"Save edits\" : \"Send message\"}\n style={{ flexShrink: 0, width: 28 }}\n />\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n </div>\n </motion.div>\n\n </motion.div>\n\n <AddCardsOverlay\n isAddOpen={isAddOpen}\n setIsAddOpen={setIsAddOpen}\n onAdd={(label) => {\n if (!label || label === \"Add\") {\n fileInputRef.current?.click();\n }\n onAdd?.();\n }}\n showInlineGlyph={showInlineGlyph}\n showButtons={showButtons}\n ac={ac}\n />\n\n <HoverActionsRow\n showActions={showActions}\n showReadMore={showReadMore}\n isExpanded={isExpanded}\n setIsExpanded={setIsExpanded}\n onCopy={onCopy}\n onEdit={onEdit}\n value={value}\n ac={ac}\n />\n\n {/* A refusal is the one voice state that has to say something in\n words. Every other one is legible from the button: a microphone\n means \"not yet\", a square means \"recording, press to stop\". A\n blocked microphone looks exactly like an idle one, and the\n browser has already stopped offering its prompt, so without this\n line the reader presses a dead control and learns nothing. */}\n {showMic && (voice.state === \"denied\" || voice.state === \"failed\") && (\n <p className={styles.voiceNote} data-tone={voice.state}>\n {voice.state === \"denied\"\n ? \"Microphone access is blocked. Allow it for this site in your browser settings, then reload.\"\n : (voice.error ?? \"The microphone could not be opened.\")}\n </p>\n )}\n </div>\n </LayoutGroup>\n </MotionConfig>\n );\n }\n);\n","import type { ReasoningState } from \"../Reasoning/Reasoning\";\nimport type { Thought } from \"../ChainOfThought/ChainOfThought\";\nimport type { Source } from \"../Sources/Sources\";\nimport type { Task } from \"../TaskList/TaskList\";\nimport type { ToolState } from \"../Tool/Tool\";\nimport type { Decision } from \"../Approval/Approval\";\nimport type { Answer, Question } from \"../QuestionCard/types\";\nimport type { SystemTone } from \"../SystemMessage/SystemMessage\";\nimport type { ArtifactKind, ArtifactState } from \"../Artifact/ArtifactCard\";\n\n/**\n * The parts of a turn that are not the answer's prose.\n *\n * An answer used to be one string, and everything the agent tier draws had\n * nowhere to live: a tool call, a plan and a block of reasoning all flattened\n * into the same text or did not arrive at all. These are what a turn carries\n * alongside `ai`.\n *\n * Every part has an `id` and parts are **merged by it**, which is what makes\n * streaming one of them bearable: send `{ kind: \"reasoning\", id: \"r1\", text }`\n * as it grows, then `{ kind: \"reasoning\", id: \"r1\", state: \"done\" }` when it\n * stops, and the text is still there. Only the fields you send change.\n */\nexport type TurnPart =\n | {\n kind: \"reasoning\";\n id: string;\n text?: string;\n state?: ReasoningState;\n /** In ms. Left out, the block times itself. */\n duration?: number;\n }\n | {\n kind: \"tool\";\n id: string;\n name: string;\n state?: ToolState;\n summary?: string;\n input?: unknown;\n output?: unknown;\n error?: string;\n duration?: number;\n }\n | {\n kind: \"tasks\";\n id: string;\n title?: string;\n tasks: Task[];\n collapsible?: boolean;\n }\n | {\n /**\n * Reasoning that has structure — steps that follow from one another,\n * rather than one block of prose. `reasoning` is the block; this is the\n * chain. Sending both for the same stretch of thinking says it twice.\n */\n kind: \"chain\";\n id: string;\n steps: Thought[];\n state?: \"thinking\" | \"done\";\n /** In ms. Left out, the block times itself. */\n duration?: number;\n }\n | {\n /**\n * What the answer stands on.\n *\n * Also what an inline `[^1]` in the prose resolves against: the citation\n * takes the source at that position, so the list is ordered by the order\n * the answer cites them, not by rank. Send it **before** the prose that\n * cites it and the markers come up already knowing what they point at.\n */\n kind: \"sources\";\n id: string;\n sources: Source[];\n title?: string;\n collapsible?: boolean;\n }\n | {\n /**\n * Something the agent wants to do, and has not done yet.\n *\n * Data rather than JSX, like every other part: a part is what a stream\n * can send, and a stream cannot send a component. The tool it names is\n * drawn for it, unrun.\n */\n kind: \"approval\";\n id: string;\n title: string;\n description?: string;\n tool?: { name: string; input?: unknown };\n /** `null` or absent while it is still being asked. */\n decision?: Decision | null;\n }\n | {\n kind: \"question\";\n id: string;\n /** What this step is about, at the top of the group. */\n title?: string;\n questions: Question[];\n /** Owned by the host: the row reports an answer, it does not keep one. */\n answers?: Record<string, Answer | undefined>;\n activeIndex?: number | null;\n collapsible?: boolean;\n readOnly?: boolean;\n }\n | {\n /**\n * Something that happened to the conversation rather than in it — the\n * window filling, the model changing partway through, a connection\n * going. A part rather than a component the host drops between rows,\n * because those are the two ways anything reaches a conversation here\n * and only one of them can be sent down a stream.\n */\n kind: \"notice\";\n id: string;\n text: string;\n tone?: SystemTone;\n }\n | {\n /**\n * Something the answer produced that is bigger than the answer — a plan,\n * a document, a file. The card in the transcript is a window onto it;\n * pressing that card is what opens the pane.\n *\n * `content` is absent while it is being written, which is the usual way\n * one arrives: the title comes first and the card shimmers it.\n */\n kind: \"artifact\";\n id: string;\n title: string;\n /** What it is, in a word or two — \"8 weeks\", \"Python\". */\n meta?: string;\n /** How the preview is drawn. Defaults to code. */\n preview?: ArtifactKind;\n lang?: string;\n content?: string;\n state?: ArtifactState;\n };\n\n/**\n * An update to a part, which is a part with everything optional but the two\n * fields that say which one it is.\n *\n * This is what a stream and a host actually send. Once a tool call is on\n * screen, saying it finished should be `{ kind: \"tool\", id, state: \"done\" }`\n * and nothing else — repeating the name to satisfy a type is how a field that\n * was not meant to change gets overwritten with whatever was easiest to type.\n */\nexport type TurnPartUpdate = {\n [K in TurnPart[\"kind\"]]: { kind: K; id: string } & Partial<\n Omit<Extract<TurnPart, { kind: K }>, \"kind\" | \"id\">\n >;\n}[TurnPart[\"kind\"]];\n\n/**\n * Fold one update into a turn's list, by id.\n *\n * A shallow merge, deliberately: the fields left out keep the values they had.\n * Sending a state change should not wipe the text that arrived before it.\n */\nexport function mergeParts(parts: TurnPart[], incoming: TurnPartUpdate): TurnPart[] {\n const at = parts.findIndex((part) => part.id === incoming.id);\n if (at === -1) return [...parts, incoming as TurnPart];\n\n const next = parts.slice();\n next[at] = { ...next[at], ...incoming } as TurnPart;\n return next;\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { Attachment } from \"../Attachments/Attachments\";\nimport type { ChatInputState } from \"../ChatInput/ChatInput\";\nimport { announce } from \"../announce/announce\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport { mergeParts, type TurnPart, type TurnPartUpdate } from \"../turnParts/turnParts\";\n\n/** One answer this turn has had. */\nexport interface TurnVersion {\n id: string;\n ai: string;\n parts: TurnPart[];\n}\n\nexport interface ChatTurn {\n id: string;\n /** What the person asked. */\n user: string;\n /** What went with it. */\n attachments?: Attachment[];\n /**\n * Every answer this turn has had, oldest first.\n *\n * Regenerating used to overwrite the answer, which threw away the one being\n * compared against — the reason anybody presses regenerate is to see whether\n * a second attempt is better, and there is no \"better\" once the first is\n * gone. Each attempt is kept and `versionIndex` says which is on screen.\n *\n * Empty until the first answer starts, and absent on a turn a host built by\n * hand — the same tolerance `parts` has.\n */\n versions?: TurnVersion[];\n /** Which of `versions` is in `ai` and `parts`. */\n versionIndex?: number;\n /** What has arrived of the answer so far. */\n ai: string;\n /**\n * Everything the answer is made of that is not its prose — reasoning, tool\n * calls, a plan, a question being asked. Merged by id as they arrive. See\n * `TurnPart`.\n */\n parts: TurnPart[];\n state: ChatInputState;\n}\n\nexport interface SendContext {\n /** Aborted when the reader presses stop, or the component unmounts. */\n signal: AbortSignal;\n turnId: string;\n /**\n * What was sent along with the message. Empty when nothing was.\n *\n * On the context rather than as a second argument: the signature already has\n * a place for \"everything about this send that is not the message\", and a\n * handler that does not care never has to mention it.\n */\n attachments: Attachment[];\n}\n\n/**\n * Produce the reply.\n *\n * Return a string for a complete answer, or an async iterable to stream one.\n * Whatever an API hands back — an SSE reader, an SDK's stream, a plain fetch —\n * fits one of those two shapes.\n *\n * A streamed item is either a **delta of the answer's prose** (a string, which\n * is appended) or a **`TurnPart`** (which is merged into the turn by its id).\n * That is what carries a model's thinking, its tool calls and its plan through\n * to the components that draw them; a stream of strings alone has nowhere to\n * put any of it.\n */\nexport type SendHandler = (\n message: string,\n context: SendContext\n) => AsyncIterable<string | TurnPartUpdate> | Promise<string> | string;\n\n/**\n * What a screen reader is told, and in which language.\n *\n * An answer that appears silently is an answer a blind reader never learns\n * about, so this is on by default. It is spoken once, when the answer settles\n * — never per character. A live region updated on every frame makes a screen\n * reader restart the whole answer on every frame, which is worse than silence.\n */\nexport interface ChatAnnouncements {\n /** Spoken when a request starts. `null` for silence. */\n responding?: string | null;\n /** Spoken when the answer settles. Return `null` for silence. */\n answer?: (text: string) => string | null;\n}\n\nexport interface UseChatTurnsOptions {\n onSend: SendHandler;\n /**\n * Reveal rate for non-streaming replies, in characters per second. Streamed\n * replies are paced by whatever produced them and ignore this.\n */\n revealSpeed?: number;\n /** Pause after a full stop, in ms. Gives read-aloud rhythm to the reveal. */\n sentencePause?: number;\n /** Override the spoken strings, or pass `false` to say nothing at all. */\n announcements?: ChatAnnouncements | false;\n}\n\nexport interface UseChatTurnsResult {\n turns: ChatTurn[];\n /** Report the person's editing of a turn's input. */\n setDraft: (id: string, value: string) => void;\n submit: (id: string, value?: string, attachments?: Attachment[]) => void;\n /**\n * Show another of a turn's answers. Out-of-range is ignored rather than\n * clamped: a caller asking for version 7 of a turn that has three has a bug,\n * and quietly showing them version 3 hides it.\n */\n showVersion: (id: string, index: number) => void;\n /** Abort the answer in flight and settle the turn where it stands. */\n stop: () => void;\n beginEdit: (id: string) => void;\n cancelEdit: (id: string) => void;\n isStreaming: boolean;\n /**\n * Merge a part into a turn from outside the stream.\n *\n * The stream is not the only thing that changes a part: a question the\n * assistant asked is answered by the person reading it, and that answer has\n * to land somewhere. Same merge-by-id as a streamed part.\n */\n updatePart: (turnId: string, part: TurnPartUpdate) => void;\n}\n\nconst DEFAULT_REVEAL_SPEED = 260;\nconst DEFAULT_SENTENCE_PAUSE = 220;\nconst DEFAULT_RESPONDING = \"Generating response\";\n\nconst newId = () =>\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : Math.random().toString(36).slice(2, 11);\n\nconst emptyTurn = (): ChatTurn => ({\n id: newId(),\n user: \"\",\n ai: \"\",\n parts: [],\n versions: [],\n versionIndex: 0,\n state: \"idle\",\n});\n\n/**\n * File the answer on screen under the version it belongs to.\n *\n * `ai` and `parts` are what is rendered; `versions[versionIndex]` is the same\n * answer kept so an earlier one can come back. Two writers for one fact is how\n * they drift, so this is the only place that copies between them — and both\n * writers, the batched stream flush and `patchTurn`, go through it.\n *\n * Returns the turn unchanged when there is nothing filed yet or nothing has\n * moved, so it never makes a new object for no reason.\n */\nconst fileAnswer = (turn: ChatTurn): ChatTurn => {\n const versions = turn.versions;\n if (!versions?.length) return turn;\n\n const at = turn.versionIndex ?? 0;\n const filed = versions[at];\n if (!filed || (filed.ai === turn.ai && filed.parts === turn.parts)) return turn;\n\n return {\n ...turn,\n versions: versions.map((version, i) =>\n i === at ? { ...version, ai: turn.ai, parts: turn.parts } : version\n ),\n };\n};\n\nconst isAsyncIterable = (value: unknown): value is AsyncIterable<string | TurnPartUpdate> =>\n typeof value === \"object\" && value !== null && Symbol.asyncIterator in value;\n\n/**\n * Timing for a typewriter reveal, precomputed once per answer.\n *\n * Index i holds the moment character i should appear. Deriving visible length\n * from elapsed time rather than stepping a timer per character means the\n * reveal runs at the same speed on any display and survives a dropped frame\n * without falling behind.\n */\nconst revealSchedule = (text: string, speed: number, pause: number): number[] => {\n const perChar = 1000 / speed;\n const times = new Array<number>(text.length);\n let t = 0;\n for (let i = 0; i < text.length; i++) {\n t += perChar;\n const c = text[i];\n if (c === \".\" || c === \"!\" || c === \"?\") t += pause;\n else if (c === \",\" || c === \";\" || c === \":\") t += pause * 0.35;\n times[i] = t;\n }\n return times;\n};\n\n/**\n * Owns the turn list, the request in flight, and the reveal.\n *\n * The reason this is a hook in the package rather than an example in the\n * README: writing it correctly means never updating state faster than the\n * display can show it, and leaving finished turns referentially untouched so\n * they can bail out of rendering. Both are easy to get wrong, and getting them\n * wrong is invisible until a conversation grows long.\n */\nexport function useChatTurns({\n onSend,\n revealSpeed = DEFAULT_REVEAL_SPEED,\n sentencePause = DEFAULT_SENTENCE_PAUSE,\n announcements,\n}: UseChatTurnsOptions): UseChatTurnsResult {\n const [turns, setTurns] = useState<ChatTurn[]>(() => [emptyTurn()]);\n const [isStreaming, setIsStreaming] = useState(false);\n\n const abortRef = useRef<AbortController | null>(null);\n const rafRef = useRef<number | null>(null);\n // Text that has arrived but not yet been shown. Flushed once per frame.\n const pendingRef = useRef<Map<string, string>>(new Map());\n /* Parts wait for the same animation frame the text does. A stream that sends\n a part per token would otherwise re-render the row per token, which is the\n cost the frame batching exists to avoid. A queue rather than a map: two\n updates to one part in a single frame both have to be folded, in order. */\n const pendingPartsRef = useRef<Map<string, TurnPartUpdate[]>>(new Map());\n const editingRef = useRef<{ id: string; user: string } | null>(null);\n const onSendRef = useRef(onSend);\n // Consumers write this as an object literal, so it is a new object on every\n // render. Held in a ref rather than a dependency, or every callback below\n // would be rebuilt each render and the memoised rows would stop skipping.\n const announcementsRef = useRef(announcements);\n // A live mirror of `turns`. Reading state inside a setState updater and\n // acting on it there makes the updater impure: React is free to call it\n // twice — StrictMode does, in development — and any work scheduled from\n // inside lands a render late. Both matter here. The morph out of the input\n // is driven by `state`, so a render's delay shows up as the text lagging\n // behind the transition.\n const turnsRef = useRef(turns);\n\n useEffect(() => {\n onSendRef.current = onSend;\n turnsRef.current = turns;\n announcementsRef.current = announcements;\n });\n\n /**\n * Rewrite exactly one turn. Every other turn keeps its object identity, so a\n * memoised row sees the same props and skips rendering entirely — which is\n * what keeps the cost of streaming flat as the conversation grows.\n */\n /**\n * The one writer, which is what keeps `ai`/`parts` and `versions` from\n * disagreeing.\n *\n * `ai` and `parts` are the answer on screen; `versions[versionIndex]` is the\n * same answer in the archive. Every patch that touches one writes through to\n * the other — except a patch that moves `versionIndex`, which is a *switch*\n * and carries both halves itself. Two writers for one fact is how they drift.\n */\n const patchTurn = useCallback((id: string, patch: Partial<ChatTurn>) => {\n setTurns((current) => {\n let changed = false;\n const next = current.map((turn) => {\n if (turn.id !== id) return turn;\n changed = true;\n const merged = { ...turn, ...patch };\n /* A patch that moves `versionIndex` or replaces `versions` is a\n *switch* and carries both halves itself; anything else that touches\n the answer gets filed. */\n const isSwitch = \"versionIndex\" in patch || \"versions\" in patch;\n return isSwitch ? merged : fileAnswer(merged);\n });\n if (!changed) return current;\n turnsRef.current = next;\n return next;\n });\n }, []);\n\n const flush = useCallback(() => {\n rafRef.current = null;\n const pending = pendingRef.current;\n const pendingParts = pendingPartsRef.current;\n if (pending.size === 0 && pendingParts.size === 0) return;\n\n const entries = [...pending];\n const partEntries = [...pendingParts];\n pending.clear();\n pendingParts.clear();\n\n setTurns((current) => {\n const next = current.map((turn) => {\n const text = entries.find(([id]) => id === turn.id)?.[1];\n const incoming = partEntries.find(([id]) => id === turn.id)?.[1];\n if (text === undefined && !incoming) return turn;\n\n return fileAnswer({\n ...turn,\n ...(text === undefined ? null : { ai: text }),\n ...(incoming ? { parts: incoming.reduce(mergeParts, turn.parts) } : null),\n });\n });\n turnsRef.current = next;\n return next;\n });\n }, []);\n\n const schedule = useCallback(() => {\n if (rafRef.current !== null) return;\n rafRef.current = requestAnimationFrame(flush);\n }, [flush]);\n\n const publish = useCallback(\n (id: string, text: string) => {\n pendingRef.current.set(id, text);\n schedule();\n },\n [schedule]\n );\n\n const publishPart = useCallback(\n (id: string, part: TurnPartUpdate) => {\n const queue = pendingPartsRef.current.get(id);\n if (queue) queue.push(part);\n else pendingPartsRef.current.set(id, [part]);\n schedule();\n },\n [schedule]\n );\n\n const updatePart = useCallback(\n (turnId: string, part: TurnPartUpdate) => {\n publishPart(turnId, part);\n },\n [publishPart]\n );\n\n useEffect(\n () => () => {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);\n abortRef.current?.abort();\n },\n []\n );\n\n const settle = useCallback(\n (id: string, wasEdit: boolean) => {\n setIsStreaming(false);\n abortRef.current = null;\n patchTurn(id, { state: \"resting\" });\n // An edited turn already has an input beneath it; a fresh answer needs one.\n if (!wasEdit) {\n setTurns((current) => {\n if (current.some((t) => t.state === \"idle\" && t.ai === \"\" && t.user === \"\")) {\n return current;\n }\n const next = [...current, emptyTurn()];\n turnsRef.current = next;\n return next;\n });\n }\n },\n [patchTurn]\n );\n\n const reveal = useCallback(\n (id: string, text: string, signal: AbortSignal) =>\n new Promise<void>((resolve) => {\n // A typewriter is an animation, and this reader has asked for fewer of\n // them. The answer arrives whole.\n if (prefersReducedMotion()) {\n publish(id, text);\n resolve();\n return;\n }\n\n const times = revealSchedule(text, revealSpeed, sentencePause);\n const start = performance.now();\n let shown = 0;\n\n const step = () => {\n if (signal.aborted) {\n // Stopping shows everything received rather than truncating it —\n // the answer arrived, only the theatre was cut short.\n publish(id, text);\n resolve();\n return;\n }\n const elapsed = performance.now() - start;\n while (shown < text.length && times[shown] <= elapsed) shown++;\n publish(id, text.slice(0, shown));\n if (shown >= text.length) {\n resolve();\n return;\n }\n requestAnimationFrame(step);\n };\n\n requestAnimationFrame(step);\n }),\n [publish, revealSpeed, sentencePause]\n );\n\n /** Route one of the two spoken moments through the live region. */\n const speak = useCallback((moment: \"responding\" | \"answer\", text = \"\") => {\n const config = announcementsRef.current;\n if (config === false) return;\n\n if (moment === \"responding\") {\n // `in` rather than `??`, so an explicit `null` can mean silence.\n const message = config && \"responding\" in config ? config.responding : DEFAULT_RESPONDING;\n if (message) announce(message);\n return;\n }\n\n const message = config?.answer ? config.answer(text) : text;\n if (message) announce(message);\n }, []);\n\n const run = useCallback(\n async (id: string, message: string, wasEdit: boolean, attachments: Attachment[] = []) => {\n const controller = new AbortController();\n abortRef.current = controller;\n setIsStreaming(true);\n /* A new version rather than an overwrite. Parts go with it: the tool\n calls that produced the old answer are not evidence for the new one,\n and they stay filed with the answer they belong to. */\n const before = turnsRef.current.find((t) => t.id === id);\n const versions = [\n ...(before?.versions ?? []),\n { id: `${id}-v${(before?.versions?.length ?? 0) + 1}`, ai: \"\", parts: [] },\n ];\n patchTurn(id, {\n user: message,\n state: \"responding\",\n ai: \"\",\n parts: [],\n versions,\n versionIndex: versions.length - 1,\n });\n speak(\"responding\");\n\n // Held out here so the announcement can read it in `finally`, whether\n // the answer completed, was stopped, or threw partway through.\n let answer = \"\";\n try {\n const result = onSendRef.current(message, {\n signal: controller.signal,\n turnId: id,\n attachments,\n });\n\n if (isAsyncIterable(result)) {\n for await (const chunk of result) {\n if (controller.signal.aborted) break;\n if (typeof chunk === \"string\") {\n answer += chunk;\n publish(id, answer);\n } else {\n publishPart(id, chunk);\n }\n }\n } else {\n answer = (await result) ?? \"\";\n await reveal(id, answer, controller.signal);\n }\n } catch (error) {\n if (!controller.signal.aborted) throw error;\n } finally {\n flush();\n settle(id, wasEdit);\n speak(\"answer\", answer);\n }\n },\n [flush, patchTurn, publish, publishPart, reveal, settle, speak]\n );\n\n const setDraft = useCallback(\n (id: string, value: string) => {\n patchTurn(id, { user: value, state: value.length > 0 ? \"typing\" : \"idle\" });\n },\n [patchTurn]\n );\n\n const submit = useCallback(\n (id: string, value?: string, attachments?: Attachment[]) => {\n const turn = turnsRef.current.find((t) => t.id === id);\n if (!turn) return;\n const message = (value ?? turn.user).trim();\n const going = attachments ?? turn.attachments ?? [];\n /* A picture on its own is a message. Blocking on empty text would offer\n an attach button and then refuse to send what it attached. */\n if (!message && going.length === 0) return;\n if (going.length > 0 || turn.attachments) patchTurn(id, { attachments: going });\n\n // Re-submitting a turn that already has an answer regenerates it in\n // place; it must not spawn a second input below.\n const wasEdit = turn.ai.length > 0;\n editingRef.current = null;\n // Synchronous, so the turn enters `responding` in the same update as the\n // keystroke that sent it and the morph starts with the text already set.\n void run(id, message, wasEdit, going);\n },\n [run, patchTurn]\n );\n\n const showVersion = useCallback(\n (id: string, index: number) => {\n const turn = turnsRef.current.find((t) => t.id === id);\n const version = turn?.versions?.[index];\n if (!version) return;\n /* Carries both halves, which is what tells `patchTurn` this is a switch\n and not an edit of the answer on screen. */\n patchTurn(id, { versionIndex: index, ai: version.ai, parts: version.parts });\n },\n [patchTurn]\n );\n\n const stop = useCallback(() => {\n abortRef.current?.abort();\n }, []);\n\n const beginEdit = useCallback(\n (id: string) => {\n const turn = turnsRef.current.find((t) => t.id === id);\n if (turn) editingRef.current = { id, user: turn.user };\n patchTurn(id, { state: \"typing\" });\n },\n [patchTurn]\n );\n\n const cancelEdit = useCallback(\n (id: string) => {\n const snapshot = editingRef.current;\n editingRef.current = null;\n patchTurn(id, {\n state: \"resting\",\n ...(snapshot && snapshot.id === id ? { user: snapshot.user } : {}),\n });\n },\n [patchTurn]\n );\n\n return {\n turns,\n setDraft,\n submit,\n showVersion,\n stop,\n beginEdit,\n cancelEdit,\n isStreaming,\n updatePart,\n };\n}\n","/* ─────────────────────────────────────────────\n ChatHeader\n\n Two regions and a rule about which one owns what. `start` holds identity —\n back, avatar, title, subtitle — and is the part allowed to shrink, because\n a title can always be cut short. `end` holds actions and never shrinks: an\n action that is half visible is worse than one folded into the menu.\n\n No literal colours. Everything is a token, and the dark theme lives with\n the tokens rather than in a block down here.\n ───────────────────────────────────────────── */\n\n.header {\n box-sizing: border-box;\n position: relative;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--ick-header-gap);\n width: 100%;\n padding-inline: var(--ick-header-pad);\n font-family: var(--ick-font-sans);\n color: var(--ick-ink);\n transition:\n background var(--ick-duration-base) ease,\n box-shadow var(--ick-duration-base) ease,\n border-color var(--ick-duration-base) ease,\n backdrop-filter var(--ick-duration-base) ease;\n}\n\n/* ── Sizes ──────────────────────────────────────────────────────────────── */\n\n.s { min-height: var(--ick-header-height-s); }\n.m { min-height: var(--ick-header-height-m); }\n.l { min-height: var(--ick-header-height-l); }\n\n.s .title { font-size: var(--ick-text-md); }\n.m .title { font-size: var(--ick-text-lg); }\n.l .title { font-size: 1rem; }\n\n/* ── Materials ──────────────────────────────────────────────────────────── */\n\n.plain {\n background: transparent;\n border-bottom: 1px solid transparent;\n}\n\n/* Opaque, which is the whole difference between this and `glass`: a panel\n with a line under it rather than a frosted pane. It was transparent, and\n sticky content scrolled straight through the title — found the moment the\n story was made to actually scroll rather than posing. */\n.bordered {\n background: var(--ick-surface);\n border-bottom: 1px solid var(--ick-header-border);\n}\n\n.glass {\n background: var(--ick-header-bg);\n backdrop-filter: blur(var(--ick-header-blur));\n -webkit-backdrop-filter: blur(var(--ick-header-blur));\n border-bottom: 1px solid var(--ick-header-border);\n}\n\n/* Nothing until the conversation has moved under it. A border drawn over\n untouched content is a line for its own sake; a border that appears at the\n moment content slides beneath says what it is for. */\n.plain[data-scrolled] {\n background: var(--ick-header-bg);\n backdrop-filter: blur(var(--ick-header-blur));\n -webkit-backdrop-filter: blur(var(--ick-header-blur));\n border-bottom-color: var(--ick-header-border);\n}\n\n.bordered[data-scrolled],\n.glass[data-scrolled] {\n box-shadow: var(--ick-shadow-float);\n}\n\n.sticky {\n position: sticky;\n top: 0;\n z-index: var(--ick-z-sticky);\n}\n\n/* ── Regions ────────────────────────────────────────────────────────────── */\n\n.start {\n display: flex;\n align-items: center;\n gap: var(--ick-header-gap);\n /* Without this a flex item refuses to go below its content's width, and the\n ellipsis never appears however narrow the header gets. */\n min-width: 0;\n}\n\n.end {\n display: flex;\n align-items: center;\n /* Its own token rather than the header's, because the two sides are not the\n same problem: the left is a logo beside a title, the right is a row of\n square controls that read as one lump when they are 4px apart. */\n gap: var(--ick-header-action-gap);\n flex-shrink: 0;\n}\n\n.avatar {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.titles {\n display: flex;\n flex-direction: column;\n justify-content: center;\n min-width: 0;\n}\n\n.title {\n display: flex;\n align-items: center;\n gap: var(--ick-space-3);\n margin: 0;\n font-weight: var(--ick-weight-medium);\n font-size: var(--ick-text-lg);\n line-height: 1.3;\n color: var(--ick-header-title);\n}\n\n.subtitle {\n font-size: var(--ick-text-sm);\n line-height: 1.4;\n color: var(--ick-header-subtitle);\n}\n\n.truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n /* `display: flex` on the title beats `text-overflow`, which only applies to\n a block container. The title keeps its dot beside it by making the text\n itself the thing that overflows. */\n display: block;\n}\n\n.title.truncate {\n display: block;\n}\n\n/* Centred: the native arrangement. The title leaves the flow so the two side\n regions can be different widths without pushing it off centre. */\n.center .titles {\n position: absolute;\n left: 50%;\n transform: translateX(-50%);\n max-width: 50%;\n text-align: center;\n align-items: center;\n}\n\n.center .title {\n justify-content: center;\n}\n\n/* ── Links styled as buttons ────────────────────────────────────────────────\n Back and any action carrying an `href`. An anchor rather than a button\n because it navigates, and the browser's own affordances — middle click,\n copy link, open in a tab — only exist on an anchor. It borrows the\n `secondary` button's material so the row reads as one set of controls. */\n\n.back {\n box-sizing: border-box;\n composes: target from \"../styles/touch.module.css\";\n display: inline-flex;\n align-items: center;\n justify-content: center;\n position: relative;\n flex-shrink: 0;\n height: 32px;\n min-width: 32px;\n border-radius: var(--ick-radius-pill);\n color: var(--ick-ink);\n text-decoration: none;\n outline: 0.5px solid transparent;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n background var(--ick-duration-fast) ease,\n box-shadow var(--ick-duration-fast) ease,\n outline-color var(--ick-duration-fast) ease,\n transform 120ms var(--ick-ease);\n}\n\n.s .back { height: 28px; min-width: 28px; }\n.l .back { height: 40px; min-width: 40px; }\n\n.back:hover {\n background: var(--ick-glass-gradient);\n box-shadow: var(--ick-shadow-2);\n outline-color: var(--ick-border-bright);\n}\n\n.back:active {\n background: var(--ick-glass-gradient);\n box-shadow: var(--ick-shadow-inset);\n outline-color: var(--ick-border-bright);\n transform: scale(0.94);\n transition-duration: var(--ick-duration-press);\n}\n\n.back:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: var(--ick-space-1);\n}\n\n/* ── Badge ──────────────────────────────────────────────────────────────────\n Decoration. The number it shows is in the button's accessible name, so a\n reader who never sees this still hears the count. */\n\n.actionWrap {\n position: relative;\n display: inline-flex;\n}\n\n.badge {\n position: absolute;\n top: -2px;\n right: -4px;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 15px;\n height: 15px;\n padding: 0 var(--ick-space-2);\n box-sizing: border-box;\n border-radius: var(--ick-radius-pill);\n background: var(--ick-header-badge-bg);\n color: var(--ick-header-badge-ink);\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n font-weight: var(--ick-weight-semibold);\n line-height: 1;\n pointer-events: none;\n}\n\n/* ── Overflow menu ──────────────────────────────────────────────────────── */\n\n.overflow {\n position: relative;\n display: inline-flex;\n}\n\n.menu {\n position: absolute;\n top: calc(100% + var(--ick-space-3));\n right: 0;\n z-index: var(--ick-z-menu);\n display: flex;\n flex-direction: column;\n /* Wide enough that an ordinary label — \"Switch to the dark theme\" — sits on\n one line, capped so an unusually long one wraps instead of running off\n the edge of a phone. */\n min-width: 210px;\n max-width: 280px;\n padding: var(--ick-space-2);\n border-radius: var(--ick-radius-lg);\n background: var(--ick-surface-raised);\n border: 1px solid var(--ick-border);\n box-shadow: var(--ick-shadow-float);\n}\n\n.menuItem {\n all: unset;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--ick-space-4);\n padding: var(--ick-space-3) var(--ick-space-4);\n border-radius: var(--ick-radius-md);\n font-family: var(--ick-font-sans);\n font-size: var(--ick-text-md);\n color: var(--ick-ink);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n}\n\n.menuItem:hover:not(:disabled) {\n background: var(--ick-surface-active);\n}\n\n.menuItem:active:not(:disabled) {\n background: var(--ick-surface-press);\n}\n\n/* The ring is worn by the item an arrow key would move from, and the menu\n says when that is the case — see `byKeyboard` in `ChatHeader.tsx`.\n\n Not `:focus-visible`, which is what this used and which the engines do not\n agree on. A menu focuses an item as it opens; Chromium reads that as\n programmatic and shows nothing, WebKit shows a ring. On a phone that meant\n every menu opened with a thumb came up with its first item ringed, looking\n chosen. Measured in both engines rather than argued about. */\n.menu[data-keyboard] .menuItem:focus {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n.menuItem:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n\n.menuItem[aria-checked=\"true\"] {\n background: var(--ick-surface-active);\n}\n\n.menuIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--ick-ink-soft);\n}\n\n.menuCount {\n margin-left: auto;\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n color: var(--ick-ink-faint);\n}\n\n/* ── Reduced motion ─────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .back {\n transition-property: background, box-shadow, outline-color;\n }\n}\n","\"use client\";\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { ArrowLeft, MoreHorizontal } from \"lucide-react\";\nimport { Button, type ButtonSize } from \"../Button/Button\";\nimport styles from \"./ChatHeader.module.css\";\n\nexport type ChatHeaderVariant = \"plain\" | \"glass\" | \"bordered\";\nexport type ChatHeaderSize = \"s\" | \"m\" | \"l\";\n\n/**\n * One action in the end region.\n *\n * Described rather than handed over as a node, and the reason is not tidiness:\n * a header that is given arbitrary children cannot fold them into an overflow\n * menu when the space runs out, because it has no idea what any of them are —\n * no label to list, no icon to draw. Everything `collapseActionsAt` does\n * depends on this shape.\n */\nexport interface ChatHeaderAction {\n /** Stable across renders. Also the React key and the menu item's id. */\n id: string;\n /**\n * The accessible name, and the text used in the overflow menu.\n *\n * Not optional. An icon button has no name of its own, and a header full of\n * unlabelled glyphs is a header a screen reader reads as \"button, button,\n * button\".\n */\n label: string;\n icon: ReactNode;\n onClick?: () => void;\n /** Renders an anchor rather than a button. Navigation is not a click. */\n href?: string;\n /**\n * A number worth showing on the glyph — saved bookmarks, unread replies.\n * It is folded into the accessible name too: the badge is decorative, and a\n * reader who cannot see it would otherwise lose the count entirely.\n */\n count?: number;\n /** A toggle rather than a command. Renders `aria-pressed`. */\n active?: boolean;\n disabled?: boolean;\n /** Stays visible when the rest collapse. For the one action that matters. */\n pinned?: boolean;\n}\n\nexport interface ChatHeaderProps\n extends Omit<HTMLAttributes<HTMLElement>, \"title\" | \"children\"> {\n /** What the conversation is about, so the reader can see where they are. */\n title?: ReactNode;\n /** The second line: the model, a count, a state. */\n subtitle?: ReactNode;\n /** Logo or agent avatar, drawn before the title. */\n avatar?: ReactNode;\n /**\n * How the title is exposed to assistive technology. A heading is what makes\n * the conversation reachable by heading navigation; the level belongs to the\n * host's document, not to us, so it is a prop. `false` renders plain text.\n */\n headingLevel?: 1 | 2 | 3 | 4 | 5 | 6 | false;\n /** Long titles get an ellipsis rather than a second line. */\n truncate?: boolean;\n\n /** Renders a back button. */\n onBack?: () => void;\n /** Renders a back link instead — navigation belongs in an anchor. */\n backHref?: string;\n backLabel?: string;\n\n /** The managed actions. These are what collapse. */\n actions?: ChatHeaderAction[];\n overflowLabel?: string;\n\n variant?: ChatHeaderVariant;\n size?: ChatHeaderSize;\n /** `center` is the native/mobile arrangement: title centred, actions apart. */\n align?: \"start\" | \"center\";\n sticky?: boolean;\n /** Grow a border and a backdrop once the conversation has scrolled under it. */\n elevateOnScroll?: boolean;\n /**\n * Width, in pixels, below which unpinned actions fold into an overflow menu.\n * `false` never folds. Measured on the header itself, not the viewport, so\n * a header in a narrow panel collapses on its own terms.\n */\n collapseActionsAt?: number | false;\n /**\n * Render as `<header>`, which is a `banner` landmark at the top level of a\n * page. Set `false` for a header inside a panel or a card, where claiming\n * the page's banner would be a lie.\n */\n landmark?: boolean;\n\n /**\n * Anything the kit should not manage — a segmented control, a model picker.\n * Sits in the end region before the actions, and never collapses: the kit\n * cannot summarise what it cannot read.\n */\n children?: ReactNode;\n}\n\nconst BUTTON_SIZE: Record<ChatHeaderSize, ButtonSize> = { s: \"s\", m: \"m\", l: \"l\" };\n/**\n * How big an icon in this header should be, per header size.\n *\n * Exported because the actions are the caller's: the header draws its own back\n * button and overflow menu from this, and everything beside them is a\n * `ReactNode` somebody else made at whatever size they guessed. A segmented\n * toggle in the demo came out at 15 against everything else's 16 — a pixel,\n * and visible, because the row is four icons in a line and one of them was\n * smaller.\n *\n * `<Highlighter size={headerIconSize.m} />` rather than a number, and the row\n * stays even when the header's size changes.\n */\nexport const headerIconSize: Record<ChatHeaderSize, number> = { s: 14, m: 16, l: 18 };\n\nconst ICON_SIZE = headerIconSize;\n\nconst sizeClass: Record<ChatHeaderSize, string> = {\n s: styles.s,\n m: styles.m,\n l: styles.l,\n};\n\nconst variantClass: Record<ChatHeaderVariant, string> = {\n plain: styles.plain,\n glass: styles.glass,\n bordered: styles.bordered,\n};\n\n/** The name a reader hears, which has to carry what the badge shows. */\nconst actionName = (action: ChatHeaderAction): string =>\n action.count === undefined ? action.label : `${action.label}, ${action.count}`;\n\n/**\n * The nearest ancestor that actually scrolls, or the window.\n *\n * `elevateOnScroll` cannot just listen to the window: a chat is very often a\n * panel with its own overflow, and in that case the window never scrolls at\n * all — the header would stay flat forever.\n */\nconst scrollParent = (node: HTMLElement | null): HTMLElement | Window => {\n let current = node?.parentElement ?? null;\n while (current) {\n const { overflowY } = getComputedStyle(current);\n if (/(auto|scroll|overlay)/.test(overflowY) && current.scrollHeight > current.clientHeight) {\n return current;\n }\n current = current.parentElement;\n }\n return window;\n};\n\nexport const ChatHeader = forwardRef<HTMLElement, ChatHeaderProps>(function ChatHeader(\n {\n title,\n subtitle,\n avatar,\n headingLevel = 2,\n truncate = true,\n onBack,\n backHref,\n backLabel = \"Back\",\n actions = [],\n overflowLabel = \"More actions\",\n variant = \"plain\",\n size = \"m\",\n align = \"start\",\n sticky = false,\n elevateOnScroll = sticky,\n collapseActionsAt = 520,\n landmark = true,\n children,\n className,\n ...rest\n },\n ref\n) {\n const rootRef = useRef<HTMLElement | null>(null);\n const [collapsed, setCollapsed] = useState(false);\n const [scrolled, setScrolled] = useState(false);\n\n const setRefs = useCallback(\n (node: HTMLElement | null) => {\n rootRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n },\n [ref]\n );\n\n /* ── Collapsing ──────────────────────────────────────────────────────────\n Measured on the element, not the viewport. A width of zero means nobody\n has laid it out yet — jsdom, a display:none ancestor — and guessing\n \"collapsed\" from an unmeasurable element would hide the actions in every\n test and every hidden tab. */\n useEffect(() => {\n const node = rootRef.current;\n if (!node || collapseActionsAt === false || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(([entry]) => {\n const width = entry.contentRect.width;\n setCollapsed(width > 0 && width < collapseActionsAt);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, [collapseActionsAt]);\n\n /* ── Elevation ───────────────────────────────────────────────────────── */\n useEffect(() => {\n if (!elevateOnScroll) return;\n const target = scrollParent(rootRef.current);\n const read = () => {\n // Identity, not `instanceof Window` and not a property check. The first\n // is a realm test — already false under jsdom, and false in a browser\n // the moment the node lives in an iframe. The second is worse than it\n // looks: `\"scrollTop\" in window` is true under jsdom even though the\n // value is undefined. This is the one thing that cannot be wrong, since\n // `scrollParent` returned this very object a moment ago.\n const top = target === window ? window.scrollY : (target as HTMLElement).scrollTop;\n setScrolled(top > 0);\n };\n read();\n target.addEventListener(\"scroll\", read, { passive: true });\n return () => target.removeEventListener(\"scroll\", read);\n }, [elevateOnScroll]);\n\n /* Both flags are read through their prop rather than cleared when it turns\n off. An effect that resets state on the way out leaves a render where the\n old value is still on screen, and needs the state to be written twice for\n what is really one derived fact. */\n const isCollapsed = collapseActionsAt !== false && collapsed;\n const visible = isCollapsed ? actions.filter((a) => a.pinned) : actions;\n const folded = isCollapsed ? actions.filter((a) => !a.pinned) : [];\n\n const Heading = (headingLevel ? (`h${headingLevel}` as const) : \"span\") as \"span\";\n\n const Root = (landmark ? \"header\" : \"div\") as \"header\";\n\n return (\n <Root\n ref={setRefs}\n className={[\n styles.header,\n sizeClass[size],\n variantClass[variant],\n align === \"center\" ? styles.center : \"\",\n sticky ? styles.sticky : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n data-scrolled={(elevateOnScroll && scrolled) || undefined}\n {...rest}\n >\n <div className={styles.start}>\n {backHref ? (\n <a href={backHref} className={styles.back} aria-label={backLabel}>\n <ArrowLeft size={ICON_SIZE[size]} aria-hidden />\n </a>\n ) : onBack ? (\n <Button\n variant=\"secondary\"\n size={BUTTON_SIZE[size]}\n icon={<ArrowLeft size={ICON_SIZE[size]} aria-hidden />}\n aria-label={backLabel}\n onClick={onBack}\n />\n ) : null}\n\n {avatar && <span className={styles.avatar}>{avatar}</span>}\n\n {(title || subtitle) && (\n <div className={styles.titles}>\n {title && (\n <Heading\n className={[styles.title, truncate ? styles.truncate : \"\"].filter(Boolean).join(\" \")}\n title={typeof title === \"string\" && truncate ? title : undefined}\n >\n {title}\n </Heading>\n )}\n {subtitle && (\n <span\n className={[styles.subtitle, truncate ? styles.truncate : \"\"]\n .filter(Boolean)\n .join(\" \")}\n >\n {subtitle}\n </span>\n )}\n </div>\n )}\n </div>\n\n <div className={styles.end}>\n {children}\n {visible.map((action) => (\n <HeaderAction key={action.id} action={action} size={size} />\n ))}\n {folded.length > 0 && (\n <OverflowMenu actions={folded} size={size} label={overflowLabel} />\n )}\n </div>\n </Root>\n );\n});\n\n/** One managed action: a button, or an anchor when it navigates. */\nfunction HeaderAction({ action, size }: { action: ChatHeaderAction; size: ChatHeaderSize }) {\n const name = actionName(action);\n const badge =\n action.count !== undefined && action.count > 0 ? (\n <span className={styles.badge} aria-hidden=\"true\">\n {action.count > 99 ? \"99+\" : action.count}\n </span>\n ) : null;\n\n if (action.href) {\n return (\n <a href={action.href} className={styles.back} aria-label={name} title={action.label}>\n {action.icon}\n {badge}\n </a>\n );\n }\n\n return (\n <span className={styles.actionWrap}>\n <Button\n variant=\"secondary\"\n size={BUTTON_SIZE[size]}\n icon={action.icon}\n aria-label={name}\n title={action.label}\n aria-pressed={action.active}\n disabled={action.disabled}\n onClick={action.onClick}\n />\n {badge}\n </span>\n );\n}\n\n/**\n * The folded actions.\n *\n * A real menu, not a list of buttons in a box: one tab stop, arrows to move,\n * Escape to leave, and focus handed back to the trigger on the way out. The\n * pattern is the same one the highlighter's menu uses.\n */\nfunction OverflowMenu({\n actions,\n size,\n label,\n}: {\n actions: ChatHeaderAction[];\n size: ChatHeaderSize;\n label: string;\n}) {\n const [open, setOpen] = useState(false);\n const [index, setIndex] = useState(0);\n /**\n * Whether the reader is working this menu by keyboard, which decides whether\n * the focused item wears a ring.\n *\n * `:focus-visible` was supposed to decide it and cannot, because the two\n * engines disagree about what it means. A menu moves focus to an item the\n * moment it opens — that is the pattern, and it is what lets an arrow key\n * take over. Chromium sees programmatic focus after a tap and correctly\n * shows no ring; WebKit shows one. Measured in both: on a tap Chromium says\n * `:focus-visible` does not match and Safari says it does, so on a phone the\n * first item of every menu opened with a thumb came up ringed as though it\n * had been chosen.\n *\n * So the question is answered here rather than guessed at by the engine. It\n * is also the more honest question: what the ring is for is *\"an arrow key\n * will move this\"*, and only this component knows whether an arrow key is\n * in play.\n */\n const [byKeyboard, setByKeyboard] = useState(false);\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n const menuRef = useRef<HTMLDivElement | null>(null);\n const menuId = useId();\n\n const close = useCallback(\n (returnFocus: boolean) => {\n setOpen(false);\n if (returnFocus) triggerRef.current?.focus();\n },\n []\n );\n\n /* Clamped here rather than corrected by an effect. A menu left open while\n the list shrinks under it would otherwise point past the end for one\n render, and that render is the one that moves focus. */\n const active = Math.min(index, Math.max(actions.length - 1, 0));\n\n // Focus lands on the item the roving index names, once the menu exists.\n useEffect(() => {\n if (!open) return;\n const items = menuRef.current?.querySelectorAll<HTMLElement>(\n '[role=\"menuitem\"], [role=\"menuitemcheckbox\"]'\n );\n items?.[active]?.focus();\n }, [open, active]);\n\n const onKeyDown = (event: React.KeyboardEvent) => {\n if (event.key === \"Escape\") {\n event.stopPropagation();\n close(true);\n return;\n }\n /* Any of the keys below means the reader has taken the keyboard, whatever\n opened the menu — so the ring appears from here on even if a thumb\n opened it. */\n if ([\"ArrowDown\", \"ArrowUp\", \"Home\", \"End\"].includes(event.key)) setByKeyboard(true);\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n setIndex((i) => (i + 1) % actions.length);\n return;\n }\n if (event.key === \"ArrowUp\") {\n event.preventDefault();\n setIndex((i) => (i - 1 + actions.length) % actions.length);\n return;\n }\n if (event.key === \"Home\") {\n event.preventDefault();\n setIndex(0);\n return;\n }\n if (event.key === \"End\") {\n event.preventDefault();\n setIndex(actions.length - 1);\n }\n };\n\n return (\n <span className={styles.overflow}>\n <Button\n ref={triggerRef}\n variant=\"secondary\"\n size={BUTTON_SIZE[size]}\n icon={<MoreHorizontal size={ICON_SIZE[size]} aria-hidden />}\n aria-label={label}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n aria-controls={open ? menuId : undefined}\n onClick={(event) => {\n /* `detail` is the click count, and a click synthesised from Enter or\n Space reports 0. Which makes it the one reliable way to tell how a\n button was pressed from inside its own handler — a keydown\n listener would have to duplicate what the browser already does\n with activation behaviour. */\n setByKeyboard(event.detail === 0);\n setIndex(0);\n setOpen((v) => !v);\n }}\n />\n {open && (\n <div\n ref={menuRef}\n id={menuId}\n role=\"menu\"\n aria-label={label}\n className={styles.menu}\n data-keyboard={byKeyboard || undefined}\n onKeyDown={onKeyDown}\n onBlur={(event) => {\n // Tab away and the menu goes with you, rather than being left\n // open behind the focus ring.\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {\n close(false);\n }\n }}\n >\n {actions.map((action, i) => (\n <button\n key={action.id}\n type=\"button\"\n /* A toggle inside a menu is a `menuitemcheckbox`. `menuitem`\n does not take a pressed or checked state, so saying it that\n way is an ARIA error, not a shortcut. */\n role={action.active === undefined ? \"menuitem\" : \"menuitemcheckbox\"}\n aria-checked={action.active}\n tabIndex={active === i ? 0 : -1}\n className={styles.menuItem}\n disabled={action.disabled}\n onClick={() => {\n action.onClick?.();\n close(true);\n }}\n >\n <span className={styles.menuIcon}>{action.icon}</span>\n <span>{action.label}</span>\n {action.count !== undefined && action.count > 0 && (\n <span className={styles.menuCount}>{action.count}</span>\n )}\n </button>\n ))}\n </div>\n )}\n </span>\n );\n}\n","/* ─────────────────────────────────────────────\n AnswerActions\n ───────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--ick-space-1);\n /* Pulled left by the button's own padding, so the first glyph lines up with\n the text above it rather than sitting a few pixels in. */\n margin-left: calc(var(--ick-space-2) * -1);\n}\n\n/* Four 28px buttons two pixels apart: on a pointer that is a tidy cluster; on\n a finger it is four 44px hit areas overlapping by fourteen, and a press\n between two of them goes to whichever was painted last. The row spreads only\n where that is true. */\n@media (pointer: coarse) {\n .row {\n gap: var(--ick-touch-gap);\n }\n}\n\n/* Invisible, not absent. It keeps its space so nothing shifts when it appears,\n and it still hit-tests at zero opacity — which is what makes hovering it\n work at all. `:focus-within` is what saves anyone arriving by keyboard from\n tabbing into something they cannot see. */\n.reveal {\n opacity: 0;\n transition: opacity var(--ick-duration-fast) ease;\n}\n\n.reveal:hover,\n.reveal:focus-within {\n opacity: 1;\n}\n\n/* The verdict that was given stays lit. `aria-pressed` is already the state;\n this is the same fact, drawn. */\n.row :global(button[aria-pressed=\"true\"]) {\n color: var(--ick-ink);\n background: var(--ick-surface-active);\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { Check, Copy, RotateCcw, ThumbsDown, ThumbsUp } from \"lucide-react\";\nimport { Button } from \"../Button/Button\";\nimport { announce } from \"../announce/announce\";\nimport styles from \"./AnswerActions.module.css\";\n\nexport type Verdict = \"up\" | \"down\";\n\nexport interface AnswerActionsProps\n // Ours hands over the answer; the DOM's hands over a ClipboardEvent.\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onCopy\"> {\n /** The answer these act on. Copy takes it; the callbacks receive it. */\n text: string;\n /** Defaults to the clipboard. */\n onCopy?: (text: string) => void;\n /** Omit and no regenerate button is drawn. */\n onRegenerate?: () => void;\n /**\n * Omit and no thumbs are drawn. Called with `null` when a verdict is taken\n * back — pressing the one already given is how somebody undoes it.\n */\n onFeedback?: (verdict: Verdict | null) => void;\n /** Controlled, for a host that stores the verdict. */\n feedback?: Verdict | null;\n /** Anything else, after the built-in controls. */\n children?: ReactNode;\n /**\n * Invisible until hovered or focused. It still occupies its space and still\n * hit-tests, so nothing shifts and nothing is unreachable — `:focus-within`\n * brings it back for anyone arriving by keyboard.\n */\n reveal?: boolean;\n /** While an answer is being regenerated. */\n busy?: boolean;\n labels?: Partial<Record<\"copy\" | \"copied\" | \"regenerate\" | \"up\" | \"down\", string>>;\n}\n\nconst DEFAULT_LABELS = {\n copy: \"Copy answer\",\n copied: \"Copied\",\n regenerate: \"Regenerate\",\n up: \"Good answer\",\n down: \"Bad answer\",\n} as const;\n\nconst COPIED_FOR = 1600;\n\n/**\n * What you can do to an answer once it has arrived.\n *\n * The input has had a hover row since the beginning; the answer had nothing,\n * which is backwards — the answer is the part worth keeping.\n */\nexport function AnswerActions({\n text,\n onCopy,\n onRegenerate,\n onFeedback,\n feedback = null,\n children,\n reveal = false,\n busy = false,\n labels,\n className,\n ...rest\n}: AnswerActionsProps) {\n const [copied, setCopied] = useState(false);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const label = { ...DEFAULT_LABELS, ...labels };\n\n useEffect(() => () => { if (timer.current) clearTimeout(timer.current); }, []);\n\n const copy = useCallback(() => {\n if (onCopy) onCopy(text);\n else void navigator.clipboard?.writeText(text);\n\n setCopied(true);\n // The tick is a picture. Without this a reader is told nothing happened,\n // which is indistinguishable from the copy having failed.\n announce(label.copied);\n if (timer.current) clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopied(false), COPIED_FOR);\n }, [label.copied, onCopy, text]);\n\n /** Pressing the verdict already given takes it back. */\n const vote = (verdict: Verdict) => onFeedback?.(feedback === verdict ? null : verdict);\n\n return (\n <div\n className={[styles.row, reveal ? styles.reveal : \"\", className ?? \"\"].filter(Boolean).join(\" \")}\n {...rest}\n >\n <Button\n variant=\"ghost\"\n icon={copied ? <Check size={14} aria-hidden /> : <Copy size={14} aria-hidden />}\n onClick={copy}\n aria-label={copied ? label.copied : label.copy}\n title={copied ? label.copied : label.copy}\n />\n\n {onRegenerate && (\n <Button\n variant=\"ghost\"\n icon={<RotateCcw size={14} aria-hidden />}\n onClick={onRegenerate}\n disabled={busy}\n loading={busy}\n aria-label={label.regenerate}\n title={label.regenerate}\n />\n )}\n\n {onFeedback && (\n <>\n <Button\n variant=\"ghost\"\n icon={<ThumbsUp size={14} aria-hidden />}\n onClick={() => vote(\"up\")}\n aria-pressed={feedback === \"up\"}\n aria-label={label.up}\n title={label.up}\n />\n <Button\n variant=\"ghost\"\n icon={<ThumbsDown size={14} aria-hidden />}\n onClick={() => vote(\"down\")}\n aria-pressed={feedback === \"down\"}\n aria-label={label.down}\n title={label.down}\n />\n </>\n )}\n\n {children}\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n Branch\n\n Which answer you are looking at, and how to reach the others. It sits in the\n row of answer actions and is built like them — a quiet control that only\n asks for attention when the pointer is on it.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.branch {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n gap: var(--ick-space-1);\n font-family: var(--ick-font-sans);\n}\n\n.step {\n all: unset;\n composes: target from \"../styles/touch.module.css\";\n /* A positioned box for it to centre on. */\n position: relative;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--ick-branch-size);\n height: var(--ick-branch-size);\n border-radius: var(--ick-radius-sm);\n color: var(--ick-branch-ink);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n background var(--ick-duration-fast) ease,\n color var(--ick-duration-fast) ease;\n}\n\n.step:hover:not(:disabled) {\n background: var(--ick-surface-hover);\n color: var(--ick-ink);\n}\n\n.step:active:not(:disabled) {\n background: var(--ick-surface-active);\n}\n\n.step:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n/* At an end. Still drawn, so the control keeps its shape and the count does\n not slide sideways as you walk to the last one. */\n.step:disabled {\n cursor: default;\n color: var(--ick-branch-ink-off);\n}\n\n.count {\n display: inline-flex;\n align-items: center;\n gap: 0.15em;\n /* Tabular, so the count does not jog as the numbers change width. */\n font-family: var(--ick-font-mono);\n font-variant-numeric: tabular-nums;\n font-size: var(--ick-text-xs);\n color: var(--ick-branch-ink);\n user-select: none;\n}\n\n.of {\n color: var(--ick-branch-ink-off);\n}\n","\"use client\";\n\nimport type { HTMLAttributes } from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport styles from \"./Branch.module.css\";\n\ntype Labels = {\n previous: string;\n next: string;\n /** `{index}` and `{total}` are filled in. */\n position: string;\n};\n\nconst LABELS: Labels = {\n previous: \"Previous answer\",\n next: \"Next answer\",\n position: \"Answer {index} of {total}\",\n};\n\nexport interface BranchProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** How many answers this turn has had. */\n total: number;\n /** Which one is on screen, from zero. */\n index: number;\n onSelect: (index: number) => void;\n labels?: Partial<Labels>;\n}\n\n/**\n * Which answer you are looking at, and how to reach the others.\n *\n * Regenerating used to overwrite: the answer you were comparing against was\n * gone the moment the second one started. But the reason anybody presses\n * regenerate is to find out whether a second attempt is better, and there is\n * no better once the first has been thrown away.\n *\n * So it draws nothing at all for a turn with one answer. A control that says\n * \"1 of 1\" is a control offering to take you nowhere.\n */\nexport function Branch({ total, index, onSelect, labels, className, ...rest }: BranchProps) {\n const label = { ...LABELS, ...labels };\n if (total < 2) return null;\n\n const at = Math.min(Math.max(index, 0), total - 1);\n const position = label.position\n .replace(\"{index}\", String(at + 1))\n .replace(\"{total}\", String(total));\n\n return (\n <div\n className={[styles.branch, className ?? \"\"].filter(Boolean).join(\" \")}\n /* A group rather than a toolbar: two buttons and the count between them\n are one thing, and the count is the group's name. */\n role=\"group\"\n aria-label={position}\n {...rest}\n >\n <button\n type=\"button\"\n className={styles.step}\n onClick={() => onSelect(at - 1)}\n /* Inside an answer this sits on the highlighter's surface, where a\n pointerdown starts drawing a marker. The click is for the button. */\n onPointerDown={(event) => event.stopPropagation()}\n disabled={at === 0}\n aria-label={label.previous}\n >\n <ChevronLeft size={14} aria-hidden />\n </button>\n\n {/* Not `aria-live`: moving between answers already replaces the answer\n itself, which is what a reader wants read out — not the counter. */}\n <span className={styles.count}>\n {at + 1}\n <span className={styles.of}>/</span>\n {total}\n </span>\n\n <button\n type=\"button\"\n className={styles.step}\n onClick={() => onSelect(at + 1)}\n onPointerDown={(event) => event.stopPropagation()}\n disabled={at === total - 1}\n aria-label={label.next}\n >\n <ChevronRight size={14} aria-hidden />\n </button>\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n Loader\n ───────────────────────────────────────────── */\n\n.loader {\n display: inline-flex;\n align-items: center;\n gap: var(--ick-space-2);\n}\n\n/* ── Dots ───────────────────────────────────────────────────────────────── */\n\n.dot {\n width: var(--ick-loader-dot);\n height: var(--ick-loader-dot);\n border-radius: var(--ick-radius-pill);\n background: var(--ick-ink-faint);\n animation: blink 1.2s var(--ick-ease) infinite;\n}\n\n.dot:nth-child(2) { animation-delay: 0.15s; }\n.dot:nth-child(3) { animation-delay: 0.3s; }\n\n/* `.srOnly` is the first child when there is a label, which would push the\n delays onto the wrong dots. Keyed off the dots themselves instead. */\n.dot + .dot { animation-delay: 0.15s; }\n.dot + .dot + .dot { animation-delay: 0.3s; }\n\n@keyframes blink {\n 0%, 100% { opacity: 0.25; }\n 50% { opacity: 1; }\n}\n\n/* ── Shimmer ────────────────────────────────────────────────────────────────\n A gradient the width of the text, slid across it and clipped to the glyphs.\n It reads as \"these words are provisional\" without anything moving. */\n\n.text {\n font-size: var(--ick-text-md);\n color: var(--ick-ink-faint);\n background: linear-gradient(\n 90deg,\n var(--ick-ink-faint) 0%,\n var(--ick-ink-faint) 40%,\n var(--ick-ink) 50%,\n var(--ick-ink-faint) 60%,\n var(--ick-ink-faint) 100%\n );\n background-size: 200% 100%;\n -webkit-background-clip: text;\n background-clip: text;\n -webkit-text-fill-color: transparent;\n animation: sweep 1.8s linear infinite;\n}\n\n/* See the note in `Reasoning.module.css`: the same numbers, and the same fix.\n The bright point used to spend two thirds of every cycle off the words. */\n@keyframes sweep {\n from { background-position: 110% 0; }\n to { background-position: -10% 0; }\n}\n\n/* ── Reduced motion ─────────────────────────────────────────────────────────\n Both stop, and both stay legible standing still. A loader that vanishes\n when the animation does has been carrying its meaning in the movement. */\n\n@media (prefers-reduced-motion: reduce) {\n .dot {\n animation: none;\n opacity: 0.6;\n }\n\n .text {\n animation: none;\n background: none;\n -webkit-text-fill-color: currentColor;\n color: var(--ick-ink-faint);\n }\n}\n\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n","\"use client\";\n\nimport type { HTMLAttributes } from \"react\";\nimport styles from \"./Loader.module.css\";\n\nexport type LoaderVariant = \"dots\" | \"shimmer\";\n\nexport interface LoaderProps extends HTMLAttributes<HTMLDivElement> {\n variant?: LoaderVariant;\n /** The words the shimmer runs through. Ignored by `dots`. */\n children?: React.ReactNode;\n /**\n * What a screen reader is told, if anything.\n *\n * `null` by default, and that is deliberate: `useChatTurns` already\n * announces that a response is coming, and a second live region saying the\n * same thing means hearing it twice. Pass a string only when nothing else\n * is speaking for you.\n */\n label?: string | null;\n}\n\n/**\n * The gap between sending and the first word arriving.\n *\n * `dots` for that gap, when there is nothing to show yet. `shimmer` for words\n * that are standing in for something — a status line that has not settled.\n */\nexport function Loader({\n variant = \"dots\",\n children,\n label = null,\n className,\n ...rest\n}: LoaderProps) {\n const classes = [styles.loader, styles[variant], className ?? \"\"].filter(Boolean).join(\" \");\n\n return (\n <div\n className={classes}\n // Decorative unless it is given something to say. See `label`.\n role={label ? \"status\" : undefined}\n aria-hidden={label ? undefined : true}\n {...rest}\n >\n {label && <span className={styles.srOnly}>{label}</span>}\n {variant === \"dots\" ? (\n <>\n <span className={styles.dot} />\n <span className={styles.dot} />\n <span className={styles.dot} />\n </>\n ) : (\n <span className={styles.text}>{children}</span>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback, useState } from \"react\";\n\nexport interface DisclosureOptions {\n /** Controlled. Leave it out and the row keeps its own. */\n open?: boolean;\n /** Where it starts, when the row's own preference is not what you want. */\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /**\n * What the row would do if nobody had an opinion.\n *\n * Read every render, so it follows the state: a tool call that fails later\n * opens itself, a reasoning block folds away when the answer starts.\n */\n preferOpen: boolean;\n}\n\n/**\n * Open or shut, decided by three sources in order of who gets the last word:\n * the host if it is controlling the row, then whoever clicked it, then the\n * row's own preference.\n *\n * That order is the whole point, and it is why this is not an effect. An\n * effect forcing the row open on a state change would also reopen one that\n * somebody had deliberately shut — showing a reader again what they have\n * already dismissed is not help. Derived, a reader's decision simply outlives\n * every state change after it, and a row nobody has touched still follows\n * along.\n */\nexport function useDisclosure({ open, defaultOpen, onOpenChange, preferOpen }: DisclosureOptions) {\n const [toggled, setToggled] = useState<boolean | null>(null);\n const isOpen = open ?? toggled ?? defaultOpen ?? preferOpen;\n\n const toggle = useCallback(() => {\n const next = !isOpen;\n // Controlled: report, and wait to be told. Setting our own would make the\n // row move before the host agreed to it.\n if (open === undefined) setToggled(next);\n onOpenChange?.(next);\n }, [isOpen, onOpenChange, open]);\n\n return { isOpen, toggle };\n}\n","/* ─────────────────────────────────────────────\n DisclosureHeader\n\n The row you click to open something, written once. Five components had one\n — `Tool`, `Reasoning`, `ChainOfThought`, `TaskList`, `Sources` — and the CSS\n was written out five times. Two pairs of them were byte-for-byte identical.\n\n What is **not** unified is the shape, because the difference is real. A\n `band` is a full-width row: it has a right edge, so meta and the chevron are\n pushed to it. An `inline` header is a label that hugs its own words — it\n sits in the flow of an answer as an aside, and pushing its chevron to a\n right edge 500px away would leave it floating alone in white space. One\n attribute, two layouts, and everything else shared.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.header {\n all: unset;\n composes: target from \"../styles/touch.module.css\";\n /* A positioned box for it to centre on. */\n position: relative;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--ick-space-4);\n /* The box is the container's content column widened by its own padding on\n both sides, so the label lands exactly on the left edge and the chevron\n on the right edge, with only the hover wash reaching past them. It used\n to be `width: 100%` with a pull-back on the left alone, which left the\n chevron six pixels shy of an edge every other row in the kit sits on.\n\n `--ick-disclosure-inset` then moves both ends inward together — for a\n section title standing over cards, whose words start a card's padding in\n from the cards' edges. The widening is the same either way: the inset\n eats into the content column, not into the box. */\n width: calc(100% + var(--ick-space-3) * 2);\n margin-inline: calc(var(--ick-space-3) * -1);\n padding: var(--ick-space-2) calc(var(--ick-space-3) + var(--ick-disclosure-inset));\n /* Stated rather than inherited. The row sits inside the prose, so without\n this it takes the prose's size — which on a phone is 16px, and the icons\n beside the label are then sized against a number the row does not use. */\n font-size: var(--ick-disclosure-size);\n border-radius: var(--ick-radius-sm);\n font-family: var(--ick-font-sans);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition: background var(--ick-duration-fast) ease;\n}\n\n/* A label, not a band: it takes the width of its own words. */\n.header[data-fit=\"inline\"] {\n display: inline-flex;\n gap: var(--ick-space-3);\n width: auto;\n max-width: 100%;\n /* It hugs its words, so there is no right edge to reach — and a negative\n margin there would pull whatever follows in over it. */\n margin-right: 0;\n}\n\n\n.header:hover {\n background: var(--ick-disclosure-hover);\n}\n\n/* The response when there is no wash to give one — and an improvement where\n there is, since the chevron is the part that says what the row does. */\n.header:hover .chevron {\n color: var(--ick-ink);\n}\n\n.header:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n/* Nothing to fold: a heading, not a control. */\n.static,\n.static:hover {\n cursor: default;\n background: none;\n}\n\n.static:hover .chevron,\n.header:disabled:hover .chevron {\n color: var(--ick-disclosure-meta);\n}\n\n/* Nothing to open yet — no input, no output, nothing to say. It is still a row\n worth reading, so it stays put and stops being a control. */\n.header:disabled {\n cursor: default;\n}\n\n.header:disabled:hover {\n background: none;\n}\n\n/* Icons from the text they sit beside. `auto` on a pointer is the SVG's own\n width and height attributes, so this changes nothing there. */\n.header svg {\n width: var(--ick-icon);\n height: var(--ick-icon);\n}\n\n.glyph {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n color: var(--ick-disclosure-glyph);\n}\n\n\n/* Set from tokens rather than overridden by a class the caller passes in:\n two `.label`s from two stylesheets have the same specificity, so which one\n wins is import order — which is not a thing to build on. A component that\n means something different by its label repoints the token from its own\n root, the way `Tool` already repoints the code block's fill. */\n.label {\n font-family: var(--ick-disclosure-font);\n font-size: var(--ick-disclosure-size);\n font-weight: var(--ick-disclosure-weight);\n color: var(--ick-disclosure-label);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* A gradient the width of the word, slid across it and clipped to the glyphs —\n the kit's way of saying \"provisional\". Its own element rather than a\n `<Loader variant=\"shimmer\">`: the loader is decorative and marks itself\n `aria-hidden`, and this word is the button's name. Hiding it would leave a\n control with nothing to call it.\n\n No counter either. A number ticking up while somebody waits is a stopwatch\n pointed at them. */\n.label[data-pending] {\n background: linear-gradient(\n 90deg,\n var(--ick-disclosure-label) 0%,\n var(--ick-disclosure-label) 40%,\n var(--ick-ink) 50%,\n var(--ick-disclosure-label) 60%,\n var(--ick-disclosure-label) 100%\n );\n background-size: 200% 100%;\n -webkit-background-clip: text;\n background-clip: text;\n -webkit-text-fill-color: transparent;\n animation: sweep 1.8s linear infinite;\n}\n\n/* The geometry, because the numbers are not obvious and the first version got\n them wrong.\n\n With `background-size: 200%` the image is twice the element, so the bright\n stop at 50% of the image sits one element-width in from its left edge. At\n `background-position: 100%` that lands on the element's left edge and at\n `0%` on its right — so the highlight crosses the whole word and nothing\n else. The overshoot either side keeps it from arriving and leaving on the\n letters themselves.\n\n It was 250% swept from 150% to -50%, which sends the bright point from one\n width left of the word to two widths right of it: on the text for a third of\n the cycle and off it for the rest. Measured over one period, frame by frame\n — two of every three frames were a static grey word, which is the opposite\n of what a shimmer is for. */\n@keyframes sweep {\n from { background-position: 110% 0; }\n to { background-position: -10% 0; }\n}\n\n/* Standing still it is still the word \"Thinking\", which was always the part\n carrying the meaning. */\n@media (prefers-reduced-motion: reduce) {\n .label[data-pending] {\n animation: none;\n background: none;\n -webkit-text-fill-color: currentColor;\n }\n}\n\n/* Hard against the right end of a band, whatever else the row carries. In an\n inline header there is no right end to be against, so it simply follows. */\n.meta {\n flex-shrink: 0;\n margin-left: auto;\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n color: var(--ick-disclosure-meta);\n}\n\n.chevron {\n flex-shrink: 0;\n margin-left: auto;\n color: var(--ick-disclosure-meta);\n transition: transform var(--ick-duration-fast) var(--ick-ease);\n}\n\n/* Something ahead of it already did the pushing; a second `auto` would split\n the free space between them instead of closing the pair up. */\n.meta ~ .chevron {\n margin-left: 0;\n}\n\n.header[data-fit=\"inline\"] .meta,\n.header[data-fit=\"inline\"] .chevron {\n margin-left: 0;\n}\n\n.header[aria-expanded=\"true\"] .chevron {\n transform: rotate(180deg);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .chevron {\n transition: none;\n }\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { ChevronDown } from \"lucide-react\";\nimport styles from \"./DisclosureHeader.module.css\";\n\nexport interface DisclosureHeaderProps {\n /**\n * `band` is a full-width row — it has a right edge, so the meta and the\n * chevron are pushed to it. `inline` is a label that hugs its own words,\n * for a header that sits in the flow of an answer as an aside.\n *\n * The two are not a style choice. An inline header's chevron pushed to a\n * right edge 500px away floats alone in white space with nothing beside it.\n */\n fit?: \"band\" | \"inline\";\n open: boolean;\n /** Leave it out and this is a heading rather than a control — no chevron,\n no `aria-expanded`, nothing to press. */\n onToggle?: () => void;\n /** The id of the box it opens. Always present, so this always points at\n something even while the box is empty. */\n controls: string;\n /** A control with nothing to open yet. Still a row worth reading. */\n disabled?: boolean;\n glyph?: ReactNode;\n /** The control's name. */\n label: ReactNode;\n /** Shimmers the label: the kit's way of saying \"still happening\". */\n pending?: boolean;\n /** A duration, a count — pushed to the right end of a band. */\n meta?: ReactNode;\n /** Anything between the label and the meta. */\n children?: ReactNode;\n className?: string;\n}\n\nconst join = (...names: (string | undefined | false)[]) => names.filter(Boolean).join(\" \");\n\n/**\n * The row you click to open something.\n *\n * Five components had one of these and the markup was written out five times,\n * with two pairs byte-for-byte identical and the others differing in ways\n * nobody had decided on. The shimmer under the label was in two of them, with\n * the same twenty lines of gradient in each.\n *\n * What it does not do is force one shape on all five — see `fit`.\n */\nexport function DisclosureHeader({\n fit = \"band\",\n open,\n onToggle,\n controls,\n disabled,\n glyph,\n label,\n pending,\n meta,\n children,\n className,\n}: DisclosureHeaderProps) {\n const inside = (\n <>\n {glyph && <span className={styles.glyph}>{glyph}</span>}\n <span className={styles.label} data-pending={pending || undefined}>\n {label}\n </span>\n {children}\n {meta ? <span className={styles.meta}>{meta}</span> : null}\n {onToggle && <ChevronDown className={styles.chevron} size={14} aria-hidden />}\n </>\n );\n\n const shared = {\n className: join(styles.header, className),\n \"data-fit\": fit,\n };\n\n if (!onToggle) {\n return (\n <div {...shared} className={join(shared.className, styles.static)}>\n {inside}\n </div>\n );\n }\n\n return (\n <button\n type=\"button\"\n {...shared}\n onClick={onToggle}\n /* Inside an answer this sits on the highlighter's surface, where a\n pointerdown starts drawing a marker. The click is for the row. */\n onPointerDown={(event) => event.stopPropagation()}\n aria-expanded={open}\n aria-controls={controls}\n disabled={disabled}\n >\n {inside}\n </button>\n );\n}\n","/* ─────────────────────────────────────────────\n DisclosureBody\n ───────────────────────────────────────────── */\n\n/* Always present, so the header's `aria-controls` always points at something.\n Empty and flat when the row is shut. */\n.outer {\n box-sizing: border-box;\n width: 100%;\n}\n\n.wrap {\n box-sizing: border-box;\n width: 100%;\n overflow: hidden;\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport styles from \"./DisclosureBody.module.css\";\n\nexport interface DisclosureBodyProps {\n /** What the header's `aria-controls` points at. */\n id: string;\n open: boolean;\n /** On the animated box, so a caller can pad or lay out what is revealed. */\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * What a `DisclosureHeader` opens, and how it opens.\n *\n * The outer box is **always rendered**, empty and flat when shut, so the\n * header's `aria-controls` always points at something — a control that names\n * an id nothing has is a control that says nothing.\n *\n * The reveal was ten lines of identical `motion` props in five components. The\n * height animation is what makes growing read as a reveal rather than as\n * content arriving before its container has caught up, and `overflow: hidden`\n * on the moving box is what keeps it from spilling on the way.\n */\nexport function DisclosureBody({ id, open, className, children }: DisclosureBodyProps) {\n const still = prefersReducedMotion();\n\n return (\n <div id={id} className={styles.outer}>\n <AnimatePresence initial={false}>\n {open && children ? (\n <motion.div\n key=\"body\"\n className={styles.wrap}\n initial={still ? { opacity: 0 } : { height: 0, opacity: 0 }}\n animate={still ? { opacity: 1 } : { height: \"auto\", opacity: 1 }}\n exit={still ? { opacity: 0 } : { height: 0, opacity: 0 }}\n transition={{ duration: still ? 0.12 : 0.22, ease: [0.32, 0.72, 0, 1] }}\n >\n <div className={className}>{children}</div>\n </motion.div>\n ) : null}\n </AnimatePresence>\n </div>\n );\n}\n","/**\n * How long something took, in words somebody reads rather than in the number\n * the clock handed over.\n *\n * `840ms` under a second, `1.2s` over it, whole seconds past ten. Nobody reads\n * `1173ms`, and the tenth stops mattering once there are two digits in front\n * of it.\n */\nexport function formatDuration(ms: number): string {\n if (!Number.isFinite(ms) || ms < 0) return \"\";\n if (ms < 1000) return `${Math.round(ms)}ms`;\n const seconds = ms / 1000;\n return `${seconds < 10 ? seconds.toFixed(1) : Math.round(seconds)}s`;\n}\n","/* ─────────────────────────────────────────────\n Reasoning\n\n Prose, not a panel — which is the difference between this and `Tool`. A tool\n call is a record of something that ran and it gets a box; thinking is the\n model talking to itself on the way to the answer, and boxing it gives it a\n weight it has not earned. A rule down the side is enough to say \"this is an\n aside\", and it stays out of the way of the answer underneath.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.reasoning {\n box-sizing: border-box;\n width: 100%;\n /* It carries no surface of its own — no ground, no card — so it sits in the\n answer's flow, and everything in that flow starts on the answer's column.\n See `--ick-answer-column`. */\n padding-left: var(--ick-answer-column);\n font-family: var(--ick-font-sans);\n}\n\n.bodyInner {\n box-sizing: border-box;\n width: 100%;\n margin-top: var(--ick-space-3);\n /* The rule is the whole container. An aside, marked as one. */\n padding-left: var(--ick-reasoning-indent);\n border-left: 1px solid var(--ick-reasoning-rule);\n font-size: var(--ick-reasoning-size);\n line-height: var(--ick-reasoning-leading);\n color: var(--ick-reasoning-ink);\n /* Thinking arrives with its own line breaks and they carry the shape of it. */\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n/* Paragraphs a host passes in, rather than a string. */\n.bodyInner > :first-child {\n margin-top: 0;\n}\n\n.bodyInner > :last-child {\n margin-bottom: 0;\n}\n","\"use client\";\n\nimport { useId, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { Brain } from \"lucide-react\";\nimport { useDisclosure } from \"../disclosure/useDisclosure\";\nimport { DisclosureHeader } from \"../disclosure/DisclosureHeader\";\nimport { DisclosureBody } from \"../disclosure/DisclosureBody\";\nimport { formatDuration } from \"../duration/formatDuration\";\nimport styles from \"./Reasoning.module.css\";\n\n/** Still working it out, or finished. */\nexport type ReasoningState = \"thinking\" | \"done\";\n\ntype Labels = Record<\"thinking\" | \"thought\" | \"thoughtFor\", string>;\n\nconst LABELS: Labels = {\n thinking: \"Thinking\",\n thought: \"Thought\",\n /** Followed by the duration: \"Thought for 12s\". */\n thoughtFor: \"Thought for\",\n};\n\nexport interface ReasoningProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** The thinking itself. Prose, not structure — see `Tool` for that. */\n children: ReactNode;\n state?: ReasoningState;\n /**\n * How long it took, in ms.\n *\n * Left out, the block times itself: it starts a clock when it begins\n * thinking and reads it when it stops. Pass one when you already know —\n * replaying a transcript, where the thinking did not happen just now.\n */\n duration?: number;\n /** Controlled. Leave it out and the block looks after itself. */\n open?: boolean;\n /** Where it starts, overruling the block's own preference. */\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n labels?: Partial<Labels>;\n}\n\n/**\n * What the model worked through before it answered.\n *\n * **Open while it thinks, folded away once the answer starts.** That is the\n * one detail every kit that ships this has converged on, and it is right:\n * thinking is worth watching while it is happening and worth almost nothing\n * afterwards — but it has to stay reachable, because the times it matters are\n * exactly the times the answer looks wrong.\n *\n * Folding is the block's *preference*, not something done to the reader. Open\n * it and it stays open, however many times the state changes underneath.\n */\nexport function Reasoning({\n children,\n state = \"done\",\n duration,\n open,\n defaultOpen,\n onOpenChange,\n labels,\n className,\n ...rest\n}: ReasoningProps) {\n const label = { ...LABELS, ...labels };\n const bodyId = useId();\n const thinking = state === \"thinking\";\n\n const { isOpen, toggle } = useDisclosure({\n open,\n defaultOpen,\n onOpenChange,\n preferOpen: thinking,\n });\n\n /* Timed here rather than asked of the host, which would only be reading the\n same clock. Adjusted during render on a change of state rather than in an\n effect: an effect would mean a second pass every time the thinking stops,\n and React documents this as the way to derive state from a prop that\n changed. `was` is what makes it run once per change rather than once per\n render. */\n const [was, setWas] = useState(thinking);\n const [clock, setClock] = useState<{ from: number | null; took: number | null }>(() => ({\n from: thinking ? Date.now() : null,\n took: null,\n }));\n\n if (was !== thinking) {\n setWas(thinking);\n setClock((c) =>\n thinking\n ? { from: Date.now(), took: null }\n : { from: null, took: c.from === null ? null : Date.now() - c.from }\n );\n }\n\n const took = duration ?? clock.took;\n const time = thinking || took === null ? \"\" : formatDuration(took);\n\n return (\n <div\n className={[styles.reasoning, className ?? \"\"].filter(Boolean).join(\" \")}\n data-state={state}\n {...rest}\n >\n {/* An aside in the flow of an answer, so the header takes the width of\n its own words rather than a band's. See `DisclosureHeader`'s `fit`. */}\n <DisclosureHeader\n fit=\"inline\"\n open={isOpen}\n onToggle={toggle}\n controls={bodyId}\n glyph={<Brain size={14} aria-hidden />}\n label={thinking ? label.thinking : time ? `${label.thoughtFor} ${time}` : label.thought}\n pending={thinking}\n />\n\n <DisclosureBody id={bodyId} open={isOpen} className={styles.bodyInner}>\n {children}\n </DisclosureBody>\n </div>\n );\n}\n","/**\n * Syntax highlighting, loaded only once something needs it.\n *\n * `lowlight` and eleven grammars are **22.4 kB gzipped** — more than a third\n * of this package, for a thing most conversations never show. Imported at the\n * top of the module they were in everybody's bundle whether or not an answer\n * ever contained a fence.\n *\n * So the grammars sit behind a dynamic `import()` and the module itself keeps\n * only the names. A block renders its code unhighlighted on the first paint\n * and colours in when the chunk lands; every block after that is highlighted\n * from the first paint, because the loaded highlighter is kept.\n *\n * What is *not* deferred is knowing whether a language can be highlighted at\n * all — `canHighlight` answers off the list below, so a caller can decide what\n * to draw without pulling 22 kB to find out.\n */\n\n/**\n * Eleven languages, chosen and measured rather than taken wholesale.\n *\n * `lowlight/common` is 37 languages and 51.6 kB gzipped — nearly the size of\n * everything else in this package put together, for grammars a chat will\n * almost never show. These cost a quarter of that and cover what actually\n * turns up in an answer. A language outside the list renders unhighlighted\n * rather than throwing; nobody loses their code over a missing grammar.\n *\n * Written out rather than read off the registry, because reading the registry\n * means loading it. A test holds this list against what `load()` registers.\n */\nconst LANGUAGES = [\n \"bash\",\n \"css\",\n \"diff\",\n \"javascript\",\n \"json\",\n \"markdown\",\n \"python\",\n \"sql\",\n \"typescript\",\n \"xml\",\n \"yaml\",\n] as const;\n\n/** What the fences people actually write map to. */\nconst ALIASES: Record<string, string> = {\n js: \"javascript\",\n jsx: \"javascript\",\n mjs: \"javascript\",\n cjs: \"javascript\",\n ts: \"typescript\",\n tsx: \"typescript\",\n html: \"xml\",\n svg: \"xml\",\n vue: \"xml\",\n sh: \"bash\",\n shell: \"bash\",\n zsh: \"bash\",\n console: \"bash\",\n py: \"python\",\n md: \"markdown\",\n postgres: \"sql\",\n psql: \"sql\",\n patch: \"diff\",\n yml: \"yaml\",\n yamllint: \"yaml\",\n};\n\nconst KNOWN = new Set<string>(LANGUAGES);\n\n/** A run of code with one highlight class, or none. */\nexport interface CodeToken {\n value: string;\n /** The `hljs-` class, without the prefix. `null` for plain text. */\n kind: string | null;\n}\n\n/** The whole block, one run, no colour. What a block shows before the chunk\n arrives and what it keeps for a language nothing here can read. */\nexport const plain = (code: string): CodeToken[] => [{ value: code, kind: null }];\n\nconst resolve = (lang?: string): string | null => {\n if (!lang) return null;\n const name = ALIASES[lang.toLowerCase()] ?? lang.toLowerCase();\n return KNOWN.has(name) ? name : null;\n};\n\n/** Whether the fence's language is one we can actually colour. */\nexport const canHighlight = (lang?: string): boolean => resolve(lang) !== null;\n\nexport type Highlighter = (code: string, lang?: string) => CodeToken[];\n\n/**\n * Flatten highlight.js's tree into a list of runs.\n *\n * The tree nests — a string inside a template inside a function — and only the\n * innermost class is worth drawing, so the walk carries the nearest one down\n * and emits a flat list. Flat is also what renders without a recursive\n * component, and a code block is the one place in an answer where the DOM is\n * worth keeping shallow: a long file is thousands of nodes either way.\n */\ntype Node = {\n type: string;\n value?: string;\n children?: Node[];\n properties?: { className?: string[] };\n};\n\nfunction flatten(tree: Node): CodeToken[] {\n const tokens: CodeToken[] = [];\n const push = (value: string, kind: string | null) => {\n if (!value) return;\n const last = tokens[tokens.length - 1];\n // Runs of the same kind are joined, which is most of them.\n if (last && last.kind === kind) last.value += value;\n else tokens.push({ value, kind });\n };\n\n const walk = (nodes: Node[], inherited: string | null) => {\n for (const node of nodes) {\n if (node.type === \"text\") {\n push(node.value ?? \"\", inherited);\n continue;\n }\n const own = node.properties?.className?.find((c) => c.startsWith(\"hljs-\"));\n walk(node.children ?? [], own ? own.slice(5) : inherited);\n }\n };\n\n walk(tree.children ?? [], null);\n return tokens;\n}\n\n/** Set once the chunk has landed, so every block after the first is coloured\n on its first paint rather than flashing plain and correcting itself. */\nlet ready: Highlighter | null = null;\nlet loading: Promise<Highlighter> | null = null;\n\n/** The highlighter, if some block has already paid for it. */\nexport const loaded = (): Highlighter | null => ready;\n\n/**\n * Fetch the grammars, once.\n *\n * Concurrent callers share the one promise: three code blocks in an answer\n * arrive together, and three requests for the same chunk is two too many.\n */\nexport function loadHighlighter(): Promise<Highlighter> {\n if (ready) return Promise.resolve(ready);\n if (loading) return loading;\n\n loading = (async () => {\n /* One module, not thirteen `import()`s: `lowlight`'s own entry re-exports\n `all` and `common` beside `createLowlight`, so importing the package\n dynamically pulls 190 grammars that nothing can shake back out. The\n static imports live in `./grammars`, which is the chunk. */\n const { createHighlighter } = await import(\"./grammars\");\n const lowlight = createHighlighter();\n\n ready = (code, lang) => {\n const name = resolve(lang);\n if (!name) return plain(code);\n try {\n const tokens = flatten(lowlight.highlight(name, code) as unknown as Node);\n return tokens.length > 0 ? tokens : plain(code);\n } catch {\n // A grammar can throw on input it cannot make sense of. Unhighlighted\n // code is a small loss; a chat that crashes on one answer is not.\n return plain(code);\n }\n };\n return ready;\n })();\n\n return loading;\n}\n","/* ─────────────────────────────────────────────\n CodeBlock\n\n Restrained on purpose. This kit is ink, paper and one acid yellow, and a\n twelve-colour syntax theme dropped into it reads as somebody else's\n component. So the scheme is ink at four weights: comments recede, keywords\n carry weight, strings and numbers sit between.\n\n It is all tokens, though, and that is the point — six values turn it into\n whatever palette a brand already owns.\n ───────────────────────────────────────────── */\n\n.block {\n position: relative;\n /* A token rather than the scale directly: a block nested inside another box\n has to take a smaller corner than one standing on its own, or the two\n curves sit at different insets with the same radius — which is the thing\n that reads as not fitting. `Tool` repoints this, the way it already\n repoints the fill. */\n border-radius: var(--ick-code-radius);\n background: var(--ick-md-code-fill);\n overflow: hidden;\n font-family: var(--ick-font-mono);\n}\n\n/* ── The bar ────────────────────────────────────────────────────────────── */\n\n.bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--ick-space-4);\n padding: var(--ick-space-3) var(--ick-space-3) 0 var(--ick-code-pad);\n /* A label over code, not a header with a rule under it. See the note in\n `Tool.module.css`: this kit separates by surface and gap. */\n min-height: 26px;\n box-sizing: border-box;\n}\n\n.lang {\n font-size: var(--ick-text-xs);\n letter-spacing: 0.04em;\n text-transform: lowercase;\n color: var(--ick-ink-faint);\n}\n\n.copy {\n all: unset;\n composes: target from \"../styles/touch.module.css\";\n /* A positioned box for it to centre on. */\n position: relative;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n gap: var(--ick-space-2);\n padding: 0 var(--ick-space-3);\n height: 22px;\n border-radius: var(--ick-radius-sm);\n cursor: pointer;\n flex-shrink: 0;\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n color: var(--ick-ink-faint);\n -webkit-tap-highlight-color: transparent;\n transition:\n background var(--ick-duration-fast) ease,\n color var(--ick-duration-fast) ease;\n}\n\n.copy:hover {\n background: var(--ick-surface-hover);\n color: var(--ick-ink);\n}\n\n.copy:active {\n background: var(--ick-surface-press);\n}\n\n.copy:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n/* The word is what makes the glyph mean something the first time. It goes\n when there is no room, and the button keeps its `aria-label` either way. */\n@media (max-width: 420px) {\n .copyLabel {\n display: none;\n }\n}\n\n/* ── The code ───────────────────────────────────────────────────────────── */\n\n.pre {\n margin: 0;\n padding: var(--ick-code-pad);\n overflow-x: auto;\n /* A long line scrolls inside the block rather than widening the answer. */\n white-space: pre;\n font-size: var(--ick-code-size);\n line-height: var(--ick-code-leading);\n color: var(--ick-ink);\n tab-size: 2;\n\n /* Stated here rather than inherited from `.block`.\n\n An inherited value loses to any direct declaration, and `pre` and `code`\n are two of the elements a host app or docs tool will style without\n thinking about it — Storybook puts a Fira Code stack on both. Declared on\n the parent alone, the kit's mono font would simply not arrive. Verified\n the other way round too: with `pre { font-family: … }` on the page, the\n block keeps its own.\n\n Ligatures off because `--ick-font-mono` is a token, and a brand is free\n to set Fira Code or JetBrains Mono, both of which draw `--` as one long\n dash and `!==` as a single glyph. What gets copied is unaffected, which\n makes it worse rather than better: the block would show one thing and\n hand over another. Code is the one place a character has to be the\n character. (Geist Mono, the default, has no such ligatures — this is for\n whoever changes it.) */\n font-family: var(--ick-font-mono);\n font-variant-ligatures: none;\n font-feature-settings: \"liga\" 0, \"calt\" 0;\n}\n\n.pre code {\n /* Same again: `code` is styled by hosts as reliably as `pre` is. */\n font-family: var(--ick-font-mono);\n font-variant-ligatures: none;\n font-feature-settings: \"liga\" 0, \"calt\" 0;\n font-size: inherit;\n background: none;\n padding: 0;\n}\n\n/* ── The scheme ─────────────────────────────────────────────────────────────\n `data-hl` carries highlight.js's class without its prefix. Grouped by what\n a reader is actually looking for, not by what the grammar calls it. */\n\n[data-hl=\"comment\"],\n[data-hl=\"quote\"] {\n color: var(--ick-code-comment);\n font-style: italic;\n}\n\n[data-hl=\"keyword\"],\n[data-hl=\"built_in\"],\n[data-hl=\"literal\"],\n[data-hl=\"type\"],\n[data-hl=\"meta\"],\n[data-hl=\"selector-tag\"] {\n color: var(--ick-code-keyword);\n font-weight: var(--ick-weight-medium);\n}\n\n[data-hl=\"string\"],\n[data-hl=\"regexp\"],\n[data-hl=\"symbol\"],\n[data-hl=\"char\"] {\n color: var(--ick-code-string);\n}\n\n[data-hl=\"number\"],\n[data-hl=\"bullet\"] {\n color: var(--ick-code-number);\n}\n\n[data-hl=\"title\"],\n[data-hl=\"name\"],\n[data-hl=\"section\"],\n[data-hl=\"function\"],\n[data-hl=\"class\"] {\n color: var(--ick-code-name);\n}\n\n[data-hl=\"attr\"],\n[data-hl=\"attribute\"],\n[data-hl=\"property\"],\n[data-hl=\"variable\"],\n[data-hl=\"params\"],\n[data-hl=\"tag\"] {\n color: var(--ick-code-attr);\n}\n\n/* A diff is the one place a whole line means something. Bands rather than\n text colour, so the `+` and `-` still read as the characters they are. */\n[data-hl=\"addition\"] {\n display: inline-block;\n width: 100%;\n background: var(--ick-code-addition);\n}\n\n[data-hl=\"deletion\"] {\n display: inline-block;\n width: 100%;\n background: var(--ick-code-deletion);\n color: var(--ick-ink-soft);\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState, type HTMLAttributes } from \"react\";\nimport { Check, Copy } from \"lucide-react\";\nimport { announce } from \"../announce/announce\";\nimport {\n canHighlight,\n loadHighlighter,\n loaded,\n plain,\n type Highlighter,\n} from \"./highlight\";\nimport styles from \"./CodeBlock.module.css\";\n\nexport interface CodeBlockProps\n // `onCopy` is a DOM clipboard handler as well as a prop here, and the two\n // signatures do not agree. Ours wins: a copy button's callback should hand\n // over the code, not a ClipboardEvent nobody asked for.\n extends Omit<HTMLAttributes<HTMLDivElement>, \"children\" | \"onCopy\"> {\n code: string;\n /** The fence's language. Unknown ones render unhighlighted. */\n lang?: string;\n /** Shown top-left. Defaults to the language; pass `false` for no bar at all. */\n label?: string | false;\n /** Set false for a block nobody is meant to take away. */\n copyable?: boolean;\n /** Defaults to the clipboard. */\n onCopy?: (code: string) => void;\n /** How long the button stays confirmed, in ms. */\n copiedFor?: number;\n}\n\nconst COPIED_FOR = 1600;\n\n/**\n * A fenced block: language, a copy button, and code that scrolls sideways\n * rather than widening the answer.\n *\n * Deliberately not markable. The rest of an answer is split into word tokens\n * so a marker can be drawn over it; preformatted text split on whitespace\n * stops being preformatted, so this renders whole and the highlighter skips\n * it. Copy is what people want from code anyway.\n */\nexport function CodeBlock({\n code,\n lang,\n label,\n copyable = true,\n onCopy,\n copiedFor = COPIED_FOR,\n className,\n ...rest\n}: CodeBlockProps) {\n const [copied, setCopied] = useState(false);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n /* The grammars are a dynamic import — 22.4 kB that most conversations never\n need — so the first block on a page paints plain and colours in when the\n chunk lands. `loaded()` is what stops that from being a flash on every\n block after it: once something has paid, the highlighter is there on the\n first render and the initial state is already right. */\n const [highlighter, setHighlighter] = useState<Highlighter | null>(loaded);\n\n useEffect(() => {\n if (highlighter || !canHighlight(lang)) return;\n let alive = true;\n void loadHighlighter().then((h) => {\n // The block can be gone by then — in a chat they unmount all the time.\n if (alive) setHighlighter(() => h);\n });\n return () => {\n alive = false;\n };\n }, [highlighter, lang]);\n\n const tokens = useMemo(\n () => (highlighter ? highlighter(code, lang) : plain(code)),\n [highlighter, code, lang]\n );\n\n // A block that unmounts while confirmed would otherwise set state on a gone\n // component, and in a chat they unmount all the time.\n useEffect(() => () => { if (timer.current) clearTimeout(timer.current); }, []);\n\n const copy = useCallback(() => {\n if (onCopy) onCopy(code);\n else void navigator.clipboard?.writeText(code);\n\n setCopied(true);\n // The tick is a picture. Without this a screen reader is told nothing\n // happened at all, which is the same as the copy having failed.\n announce(\"Copied to clipboard\");\n if (timer.current) clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopied(false), copiedFor);\n }, [code, copiedFor, onCopy]);\n\n const caption = label === false ? null : (label ?? lang ?? null);\n const showBar = caption !== null || copyable;\n\n return (\n <div className={[styles.block, className ?? \"\"].filter(Boolean).join(\" \")} {...rest}>\n {showBar && (\n <div className={styles.bar}>\n <span className={styles.lang}>{caption}</span>\n {copyable && (\n <button\n type=\"button\"\n className={styles.copy}\n onClick={copy}\n /* Inside an answer this sits on the highlighter's surface, where\n a pointerdown starts drawing a marker. The click is for the\n button, not the paragraph under it. */\n onPointerDown={(event) => event.stopPropagation()}\n aria-label={copied ? \"Copied\" : \"Copy code\"}\n >\n {copied ? <Check size={13} aria-hidden /> : <Copy size={13} aria-hidden />}\n <span className={styles.copyLabel}>{copied ? \"Copied\" : \"Copy\"}</span>\n </button>\n )}\n </div>\n )}\n\n <pre className={styles.pre}>\n <code>\n {tokens.map((token, i) =>\n token.kind ? (\n <span key={i} data-hl={token.kind}>\n {token.value}\n </span>\n ) : (\n token.value\n )\n )}\n </code>\n </pre>\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n StateGlyph\n\n Shared by `Tool` and `TaskList`, which is the point: it lived in one of them\n first, and a second copy is how two drawings of the same thing start to\n disagree. The shimmer in `Loader` and `Reasoning` had already done exactly\n that once.\n ───────────────────────────────────────────── */\n\n.glyph {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--ick-state-size);\n height: var(--ick-state-size);\n color: var(--ick-state-pending);\n}\n\n.glyph[data-state=\"running\"] {\n color: var(--ick-state-running);\n}\n\n.glyph[data-state=\"done\"] {\n color: var(--ick-state-done);\n}\n\n.glyph[data-state=\"error\"] {\n color: var(--ick-state-error);\n}\n\n/* Queued: an outline, because nothing has happened to fill it in yet. */\n.queued {\n width: 9px;\n height: 9px;\n border: 1.5px solid currentColor;\n border-radius: var(--ick-radius-pill);\n}\n\n/* Running: a ring with one quarter in ink, turning. */\n.spinner {\n width: 12px;\n height: 12px;\n border: 1.5px solid var(--ick-state-track);\n border-top-color: currentColor;\n border-radius: var(--ick-radius-pill);\n animation: spin 0.7s linear infinite;\n}\n\n@keyframes spin {\n to { transform: rotate(360deg); }\n}\n\n/* Standing still it is still a ring that is not a tick and not a triangle,\n which is the whole job. A spinner that vanishes with its animation was\n carrying its meaning in the movement. */\n@media (prefers-reduced-motion: reduce) {\n .spinner {\n animation: none;\n }\n}\n","\"use client\";\n\nimport { Check, TriangleAlert } from \"lucide-react\";\nimport styles from \"./StateGlyph.module.css\";\n\n/**\n * Queued, working, finished, failed.\n *\n * One vocabulary across the kit rather than one per component — a tool call\n * and a task in a list are in the same four states, and calling the third one\n * `active` in one place and `running` in another buys nothing and costs a\n * mental translation every time somebody reads both.\n */\nexport type WorkState = \"pending\" | \"running\" | \"done\" | \"error\";\n\n/**\n * The 16px square that says which of the four states something is in.\n *\n * Four shapes, not four colours. Colour is the second thing it says, and a\n * status carried only by colour is a status half the people reading it do not\n * have — so a queued ring, a turning ring, a tick and a triangle are different\n * before anything is tinted.\n *\n * Not exported from the package. Two components draw it and neither wants a\n * consumer's version of it; if that changes it is one line in `index.ts`.\n */\nexport function StateGlyph({ state }: { state: WorkState }) {\n return (\n <span className={styles.glyph} data-state={state} aria-hidden>\n {state === \"running\" ? (\n <span className={styles.spinner} />\n ) : state === \"done\" ? (\n <Check size={13} />\n ) : state === \"error\" ? (\n <TriangleAlert size={13} />\n ) : (\n <span className={styles.queued} />\n )}\n </span>\n );\n}\n","/* ─────────────────────────────────────────────\n Tool\n\n One tool call, in the same three surfaces a question is: a **ground**, a\n **card** on it, and what the card holds **inset** into it. It used to be one\n box, and the box was the grey one — the same three stacked backwards, which\n beside a question card read as a different system.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n/* The ground. What the card stands on, and what the header stands on above\n it — the same two the question group has, in the same order. */\n.tool {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-5);\n width: 100%;\n padding: var(--ick-tool-ground-pad);\n border-radius: var(--ick-tool-ground-radius);\n background: var(--ick-tool-ground);\n font-family: var(--ick-font-sans);\n\n /* An identifier, not prose — so the header's label is set as one. These live\n on the ground rather than on the card now, because that is where the\n header is. */\n --ick-disclosure-font: var(--ick-font-mono);\n --ick-disclosure-size: var(--ick-text-sm);\n --ick-disclosure-label: var(--ick-tool-name);\n --ick-disclosure-meta: var(--ick-tool-meta);\n /* And the header's own content sits on the column the card's content sits\n on: the glyph where a panel's label starts, the chevron where they end.\n Measured against the card's edges instead, the chevron sat hard in the\n corner with nothing under it agreeing. */\n --ick-disclosure-inset: var(--ick-tool-pad);\n /* And no wash, for the reason a question group has none: the hover is a\n rounded box the width of the header, and on a ground there is nothing\n under it for that shape to agree with. The chevron lighting up is the\n response. */\n --ick-disclosure-hover: transparent;\n}\n\n.card {\n box-sizing: border-box;\n width: 100%;\n border-radius: var(--ick-tool-radius);\n background: var(--ick-tool-surface);\n box-shadow: var(--ick-tool-shadow);\n /* Clips the body to the box while it is opening, so growing reads as a\n reveal rather than as content arriving before its container has. */\n overflow: hidden;\n}\n\n/* ── What the card says when it is shut ─────────────────────────────────── */\n\n/* One row, on the card's own column — a folded question group's summary row in\n everything but its contents. It stays when the call opens, which is where\n this parts company with the question group: nothing swaps, so nothing can be\n drawn over anything.\n\n The line box takes the badge's height for the same reason every label beside\n a badge does, and it is what makes this row 40 tall — a folded question's\n height, so a stack of the two lines up. */\n.overview {\n box-sizing: border-box;\n width: 100%;\n margin: 0;\n padding: var(--ick-space-4) var(--ick-tool-pad);\n font-size: var(--ick-text-sm);\n line-height: var(--ick-badge-size);\n color: var(--ick-tool-meta);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.bodyInner {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-5);\n width: 100%;\n padding: 0 var(--ick-tool-inner-gap) var(--ick-tool-inner-gap);\n /* The overview above is the separation, so the body starts right under it. */\n /* No rule under the header.\n\n The question card separates things by **surface and gap** — a row is an\n inset panel with space around it, and nothing is underlined. This drew a\n line instead, and with two sections open that made three stacked rules in\n a component the size of a paragraph. The panels already sit on the tool's\n ground with space between them; the line was saying again what the surface\n had already said. */\n margin-top: var(--ick-space-2);\n /* Repointed rather than restyled: the code block reads its own tokens, so\n this moves its surface and its corner without knowing anything about how a\n code block is built. */\n --ick-md-code-fill: var(--ick-tool-code-fill);\n --ick-code-radius: var(--ick-tool-inner-radius);\n /* 8, so a panel's label sits at 8 + 8 = 16 from the card's edge — the column\n the header's glyph is in. At 12 it landed at 20, which is the kind of\n four-pixel near-miss that reads as wrong without being nameable. */\n --ick-code-pad: var(--ick-space-4);\n}\n\n/* An inset panel, exactly like the code block beside it.\n\n It was a bare stack: no surface, no padding of its own, so its label and its\n text sat flush at the body's 8px while a fenced value's label and code sat\n at 12px inside the block's own panel. Two sections of one tool call at two\n left edges — which is what \"the padding ran away at the bottom\" is, when the\n bottom section happens to be the plain-text one.\n\n The numbers are `CodeBlock`'s, deliberately: its bar puts a label 12px in\n and its `pre` puts the code 12px in, so matching that is what makes the two\n line up rather than nearly line up. */\n.section {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-3);\n width: 100%;\n min-width: 0;\n padding: var(--ick-space-3) var(--ick-code-pad) var(--ick-code-pad);\n border-radius: var(--ick-tool-inner-radius);\n background: var(--ick-tool-code-fill);\n}\n\n.sectionLabel {\n font-size: var(--ick-text-xs);\n letter-spacing: 0.04em;\n text-transform: lowercase;\n color: var(--ick-tool-meta);\n}\n\n.text {\n margin: 0;\n font-size: var(--ick-text-md);\n line-height: 1.55;\n color: var(--ick-ink);\n /* What came back keeps its own line breaks; it is a value, not a paragraph\n somebody wrote. */\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n/* ── When it failed ─────────────────────────────────────────────────────── */\n\n/* The same panel, tinted. It used to be a box *inside* the section's box —\n two panels for one thing, and the inner one at a different inset again. */\n.section[data-tone=\"error\"] {\n background: var(--ick-tool-error-surface);\n color: var(--ick-danger);\n}\n\n.section[data-tone=\"error\"] .text,\n.section[data-tone=\"error\"] .sectionLabel {\n color: inherit;\n}\n\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n","\"use client\";\n\nimport { isValidElement, useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { CodeBlock } from \"../CodeBlock/CodeBlock\";\nimport { StateGlyph, type WorkState } from \"../stateGlyph/StateGlyph\";\nimport { useDisclosure } from \"../disclosure/useDisclosure\";\nimport { DisclosureHeader } from \"../disclosure/DisclosureHeader\";\nimport { DisclosureBody } from \"../disclosure/DisclosureBody\";\nimport { formatDuration } from \"../duration/formatDuration\";\nimport { Loader } from \"../Loader/Loader\";\nimport styles from \"./Tool.module.css\";\n\n/** Queued, working, finished, or failed — the kit's four, shared with `TaskList`. */\nexport type ToolState = WorkState;\n\ntype Labels = Record<\"input\" | \"output\" | \"error\" | ToolState, string>;\n\nexport interface ToolProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\" | \"onToggle\"> {\n /** What was called. Set in mono: it is an identifier, not prose. */\n name: string;\n state?: ToolState;\n /** A sentence for what it did — \"Searched the web\", \"Read 3 files\". */\n summary?: ReactNode;\n /**\n * What it was called with.\n *\n * A string is shown as text and an object as JSON. Anything you want drawn\n * some other way, pass as an element and it is rendered untouched.\n */\n input?: unknown;\n /** What came back. Read the same way as `input`. */\n output?: unknown;\n /** What went wrong. Drawn instead of the output, and opens the row. */\n error?: ReactNode;\n /** How long it took, in ms. Shown once it has finished. */\n duration?: number;\n /** Open, controlled. Leave out and the row keeps its own. */\n open?: boolean;\n /** Where it starts. Defaults to open only if it failed. */\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n labels?: Partial<Labels>;\n}\n\nconst LABELS: Labels = {\n input: \"Input\",\n output: \"Output\",\n error: \"Error\",\n pending: \"Queued\",\n running: \"Running\",\n done: \"Done\",\n};\n\n/**\n * A value, and how it wants to be drawn.\n *\n * A string is what came back rather than a program, so it is text. An object\n * is JSON, which is what it already was on the wire. An element is somebody\n * having decided for themselves, so it is left alone.\n */\ntype Shown =\n | { kind: \"node\"; node: ReactNode }\n | { kind: \"text\"; text: string }\n | { kind: \"json\"; code: string }\n | null;\n\nfunction show(value: unknown): Shown {\n if (value === null || value === undefined) return null;\n if (isValidElement(value)) return { kind: \"node\", node: value };\n if (typeof value === \"string\") return value.trim() ? { kind: \"text\", text: value } : null;\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return { kind: \"text\", text: String(value) };\n }\n try {\n const code = JSON.stringify(value, null, 2);\n return code ? { kind: \"json\", code } : null;\n } catch {\n // Circular, or a BigInt. A row that throws while rendering a tool call is\n // worse than one that prints something unhelpful.\n return { kind: \"text\", text: String(value) };\n }\n}\n\nfunction Section({\n label,\n value,\n tone,\n}: {\n label: string;\n value: Shown;\n /** `error` puts the section on its own ground. */\n tone?: \"error\";\n}) {\n if (!value) return null;\n\n /* A fenced value carries its heading in its own bar. Stacking a title above\n a bar that held nothing but a copy button was two rows doing one row's\n work, and the empty strip between them was the first thing you saw. */\n if (value.kind === \"json\") {\n return <CodeBlock code={value.code} lang=\"json\" label={label} />;\n }\n\n const body =\n value.kind === \"text\" ? <p className={styles.text}>{value.text}</p> : value.node;\n\n return (\n <div className={styles.section} data-tone={tone}>\n <span className={styles.sectionLabel}>{label}</span>\n {body}\n </div>\n );\n}\n\n/**\n * One tool call: what was run, what with, what came back.\n *\n * Collapsed, because most of the time nobody cares — and open when it failed,\n * because an error nobody can see has not been reported. The glyph says which\n * state it is in and the row says so in words too; a status carried only by\n * colour is a status half the people reading it do not have.\n */\nexport function Tool({\n name,\n state = \"done\",\n summary,\n input,\n output,\n error,\n duration,\n open,\n defaultOpen,\n onOpenChange,\n labels,\n className,\n ...rest\n}: ToolProps) {\n const label = { ...LABELS, ...labels };\n const bodyId = useId();\n\n const failed = state === \"error\";\n\n /* Open when it failed, because an error nobody can see has not been\n reported — but only as the row's own preference, which anybody reading it\n can overrule. See `useDisclosure`. */\n const { isOpen, toggle } = useDisclosure({\n open,\n defaultOpen,\n onOpenChange,\n preferOpen: failed,\n });\n const shownInput = show(input);\n const shownOutput = failed ? null : show(output);\n const shownError = failed ? show(error) : null;\n const hasBody = Boolean(shownInput || shownOutput || shownError || state === \"running\");\n\n const time = state === \"done\" || failed ? formatDuration(duration ?? NaN) : \"\";\n\n return (\n <div\n className={[styles.tool, className ?? \"\"].filter(Boolean).join(\" \")}\n data-state={state}\n {...rest}\n >\n {/* The name of the call, on the ground, above the card — which is where\n a question group puts its section title, and for the same reason. A\n header that is the card's top edge has to move when the card grows;\n one on the ground does not, so opening the call is the body changing\n under a row that stays put.\n\n It also settles the alignment: on the ground the header takes\n `--ick-disclosure-inset`, so the glyph starts on the column the card's\n panels start on and the chevron ends where they end. Filled, it was\n measured against the *card's* edges, which is what put the chevron\n hard into the corner. */}\n <DisclosureHeader\n open={isOpen}\n onToggle={toggle}\n controls={bodyId}\n disabled={!hasBody}\n glyph={<StateGlyph state={state} />}\n label={name}\n meta={time || undefined}\n >\n {/* The glyph is a picture. This is the same thing in words, for\n anybody the picture is not reaching. */}\n <span className={styles.srOnly}>{label[state]}</span>\n </DisclosureHeader>\n\n {/* The card, and the overview stays in it whether or not the call is\n open — which is where this deliberately parts company with the\n question group. There the two bodies swap, and because both are\n anchored to the same top edge they want the same band and cannot be\n crossfaded without drawing one over the other. Here nothing swaps:\n the summary is the card's first row in both states and the detail\n opens underneath it. Same shape, one fewer thing to go wrong. */}\n <div className={styles.card}>\n <p className={styles.overview}>{summary ?? label[state]}</p>\n\n <DisclosureBody\n id={bodyId}\n open={isOpen && hasBody}\n className={styles.bodyInner}\n >\n <Section label={label.input} value={shownInput} />\n {state === \"running\" && !shownOutput && (\n <div className={styles.section}>\n <span className={styles.sectionLabel}>{label.output}</span>\n <Loader />\n </div>\n )}\n <Section label={label.output} value={shownOutput} />\n <Section label={label.error} value={shownError} tone=\"error\" />\n </DisclosureBody>\n </div>\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n Approval\n\n The one component here whose whole job is to slow somebody down for a\n moment, so it is the one thing in an answer that is allowed to look like a\n box that stops you. Everything else in the agent tier is quiet on purpose.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n/* The ground — the same one a question group stands on. An approval is a\n question: it asks, it blocks, it has an answer. See `--ick-approval-surface`\n for why it stopped being tinted. */\n.approval {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-5);\n width: 100%;\n padding: var(--ick-approval-pad);\n border-radius: var(--ick-approval-radius);\n background: var(--ick-approval-surface);\n font-family: var(--ick-font-sans);\n\n /* The subject brings the *contents* of the card; the approval draws the card\n itself. So a tool call standing in one brings no surfaces of its own — not\n its ground, not its card, not its shadow.\n\n Two arrangements were tried before this. The approval's card wrapping all\n three (title, subject, buttons) made an approval holding a tool call two\n papers for one thing. Letting the tool bring its own card then left its\n header stranded on the ground beside the title — two headers over one\n card, and the second of them a stray row.\n\n One card, and everything in it belongs to the subject. The title above and\n the buttons below are on the ground, because they are the asking and the\n answering rather than part of what is being approved. */\n --ick-tool-ground: transparent;\n /* Vertical only. Sideways it has to be nothing, or the tool's header is\n pushed off the column the title and the buttons stand on; downwards and\n upwards it is what keeps the header off the card's top edge — which is\n what zeroing all four sides did, and it showed. The bottom is the tool\n body's own, so this side gives nothing back. */\n --ick-tool-ground-pad: var(--ick-nest-pad) 0 0;\n --ick-tool-surface: transparent;\n --ick-tool-shadow: none;\n --ick-tool-radius: 0;\n /* And a subject that is not a tool call still lands on the column: a bare\n code block's own padding is 12, which would sit four pixels inside it. */\n --ick-code-pad: var(--ick-approval-column);\n}\n\n/* Nothing here changes when it settles. It used to shed its tint and pick up a\n border, because the tint was the thing saying \"unanswered\" and it had to\n stop saying it. The buttons say that now, and they leave on their own — the\n way a question card keeps its paper and its shadow when it folds into an\n answered row. */\n\n\n/* ── What is being asked, on the ground above the card ──────────────────── */\n\n.head {\n box-sizing: border-box;\n width: 100%;\n min-width: 0;\n /* On the card's column, so the shield starts where the subject's own content\n starts. It used to be 8 plus the card's 8, which was the same arithmetic\n from inside the card; from out here there is no card padding to add to, so\n the column is stated. */\n padding: var(--ick-space-2) 0 0 var(--ick-approval-column);\n}\n\n\n\n.title {\n margin: 0;\n font-size: var(--ick-approval-size);\n font-weight: var(--ick-weight-medium);\n color: var(--ick-ink);\n}\n\n.description {\n margin: var(--ick-space-1) 0 0;\n font-size: var(--ick-text-sm);\n line-height: 1.5;\n color: var(--ick-ink-soft);\n}\n\n/* The card, and the only thing on paper: what is actually being decided about.\n\n No padding of its own, deliberately. Whatever is inside brings it — a tool\n call's rows are padded to the column already, a code block's panel likewise\n — and adding a gap here would push all of it eight pixels off the line the\n title and the buttons stand on. */\n.subject {\n box-sizing: border-box;\n width: 100%;\n min-width: 0;\n border-radius: var(--ick-nest-card);\n background: var(--ick-card);\n box-shadow: var(--ick-shadow-float);\n /* Clips whatever is inside to the corner, since nothing in there knows it is\n against the card's edge. */\n overflow: hidden;\n}\n\n/* ── The decision ───────────────────────────────────────────────────────── */\n\n.actions {\n box-sizing: border-box;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--ick-space-3);\n width: 100%;\n /* The footer, on the ground under the card. Deny sits on the column the\n shield above it sits on; the two that say yes end where the card's content\n ends, which is the same column mirrored. */\n padding: var(--ick-space-2) var(--ick-approval-column) 0;\n}\n\n/* Alone at the left end, with the two that say yes pushed to the right. A\n destructive choice sitting flush against an affirmative one is a mis-click\n waiting to happen, and the gap is what stops it.\n\n Neutral at rest and only red under the pointer: a permanently red button is\n the first thing the eye lands on, and the destructive choice is not the one\n to draw somebody towards. */\n.deny {\n margin-right: auto;\n /* Pulled back by its own padding, so the *word* lands on the column rather\n than the invisible box around it. A ghost button is text — what you see is\n \"Deny\", and it sat 16px inside the line the shield above it is on. The two\n that say yes are the other way round: they are filled, so the box is the\n visible thing and its edge is what belongs on the column.\n\n `size=\"m\"`, hence `--ick-space-6`. Sharing a token with `Button` would\n couple this to a size it does not choose. */\n margin-left: calc(var(--ick-space-6) * -1);\n}\n\n.deny:hover:not(:disabled),\n.deny:focus-visible {\n color: var(--ick-danger);\n}\n\n.settled {\n display: flex;\n padding: var(--ick-space-2) var(--ick-approval-column) 0;\n align-items: center;\n gap: var(--ick-space-3);\n margin: 0;\n font-size: var(--ick-text-sm);\n color: var(--ick-ink-soft);\n}\n\n.settled[data-decision=\"denied\"] {\n color: var(--ick-danger);\n}\n","\"use client\";\n\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { Check, X } from \"lucide-react\";\nimport { Button } from \"../Button/Button\";\nimport styles from \"./Approval.module.css\";\n\n/** Once, every time from now on, or not at all. */\nexport type Decision = \"once\" | \"always\" | \"denied\";\n\ntype Labels = Record<\"once\" | \"always\" | \"deny\", string> &\n Record<\"allowedOnce\" | \"allowedAlways\" | \"wasDenied\" | \"pending\", string>;\n\nconst LABELS: Labels = {\n once: \"Allow once\",\n always: \"Always allow\",\n deny: \"Deny\",\n allowedOnce: \"Allowed once\",\n allowedAlways: \"Allowed from now on\",\n wasDenied: \"Denied\",\n pending: \"Waiting for you\",\n};\n\nexport interface ApprovalProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n /** What is being asked. One line: \"Run a command in your shell\". */\n title: ReactNode;\n /** Why it is being asked, or what it will touch. */\n description?: ReactNode;\n /**\n * The thing itself — usually a `<Tool>` or a `<CodeBlock>`.\n *\n * Somebody deciding needs to see what they are deciding about, and a name is\n * not that. An approval with nothing under it is asking for a signature on a\n * blank page.\n */\n children?: ReactNode;\n /** `null` while it is still being asked. */\n decision?: Decision | null;\n onDecide?: (decision: Decision) => void;\n /** Nothing can be decided from here — a record of a decision already made. */\n readOnly?: boolean;\n labels?: Partial<Labels>;\n}\n\nconst SETTLED: Record<Decision, keyof Labels> = {\n once: \"allowedOnce\",\n always: \"allowedAlways\",\n denied: \"wasDenied\",\n};\n\n/**\n * \"It wants to do this. Is that all right?\"\n *\n * The one pattern from a coding agent that generalises to any agent that acts,\n * and the only component in this kit whose whole job is to slow somebody down\n * for a moment.\n *\n * Three decisions rather than two, because \"yes\" and \"yes forever\" are not the\n * same answer and a UI offering one button for both collects the wrong one.\n * **Allow once is the primary**: the narrow permission is the one that should\n * be easiest to give, and the standing one should cost a moment's thought.\n *\n * Decided, it stops being a set of buttons and becomes a record of what was\n * decided. Leaving live controls under a decision already made invites a\n * second, contradictory one.\n */\nexport function Approval({\n title,\n description,\n children,\n decision = null,\n onDecide,\n readOnly = false,\n labels,\n className,\n ...rest\n}: ApprovalProps) {\n const label = { ...LABELS, ...labels };\n const titleId = useId();\n const settled = decision !== null;\n\n return (\n <section\n className={[styles.approval, className ?? \"\"].filter(Boolean).join(\" \")}\n data-decision={decision ?? undefined}\n aria-labelledby={titleId}\n {...rest}\n >\n {/* Ground, then what is being asked over the card, then the answer under\n it — a question group's three, in a question group's order.\n\n The title and the buttons are *on the ground*, not on the card. They\n are not part of the thing being approved; they are the asking and the\n answering, and the card between them is the subject. It used to wrap\n all three, which meant an approval holding a tool call was a card\n inside a card — two papers, two shadows, one thing. */}\n {/* No shield. It said \"this is about permission\" in a picture, next to a\n title that says it in words, above a card that shows the thing and\n three buttons that are visibly a decision. Every other glyph in the\n kit carries *state* — queued, running, failed, allowed, denied — and\n pairs with a word for anybody the picture is not reaching. This one\n carried a category nothing else could be mistaken for.\n\n It was also drawn at 24 against everything else's 14, because the\n badge box it was meant to sit *in* was applied to the icon itself. */}\n <div className={styles.head}>\n <p className={styles.title} id={titleId}>\n {title}\n </p>\n {description && <p className={styles.description}>{description}</p>}\n </div>\n\n {/* What is actually being asked for, and the only thing on paper. */}\n {children && <div className={styles.subject}>{children}</div>}\n\n {settled ? (\n /* A record, not a control. Live buttons under a decision already made\n invite a second one that contradicts the first. */\n <p className={styles.settled} data-decision={decision}>\n {decision === \"denied\" ? (\n <X size={13} aria-hidden />\n ) : (\n <Check size={13} aria-hidden />\n )}\n {label[SETTLED[decision]]}\n </p>\n ) : (\n /* Three weights for three answers, in the order they should be reached\n for: filled for the narrow yes, outlined for the standing one, flat\n for no. Two outlined buttons beside each other said the last two were\n equals, which they are not.\n\n Deny stays first in the DOM, so a keyboard lands on the safe answer\n without tabbing past two that say yes. */\n !readOnly && (\n <div className={styles.actions}>\n <Button\n variant=\"ghost\"\n size=\"m\"\n className={styles.deny}\n onClick={() => onDecide?.(\"denied\")}\n >\n {label.deny}\n </Button>\n <Button variant=\"outline\" size=\"m\" onClick={() => onDecide?.(\"always\")}>\n {label.always}\n </Button>\n <Button variant=\"primary\" size=\"m\" onClick={() => onDecide?.(\"once\")}>\n {label.once}\n </Button>\n </div>\n )\n )}\n </section>\n );\n}\n","/* ─────────────────────────────────────────────\n TaskList\n\n A list, not a box. `Tool` is a record of one thing that ran and gets a\n surface; this is a sequence, and the sequence is what it is saying — so the\n rows read down the page with nothing drawn around them.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.tasks {\n box-sizing: border-box;\n width: 100%;\n /* It carries no surface of its own — no ground, no card — so it sits in the\n answer's flow, and everything in that flow starts on the answer's column.\n See `--ick-answer-column`. */\n padding-left: var(--ick-answer-column);\n font-family: var(--ick-font-sans);\n\n /* A title, not a label: this heads a plan rather than describing an aside.\n See the same note in `Sources`. */\n --ick-disclosure-label: var(--ick-ink);\n --ick-disclosure-weight: var(--ick-weight-medium);\n}\n\n.list {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-task-gap);\n width: 100%;\n margin: var(--ick-space-3) 0 0;\n padding: 0;\n list-style: none;\n}\n\n.item {\n box-sizing: border-box;\n display: flex;\n align-items: flex-start;\n gap: var(--ick-space-4);\n width: 100%;\n}\n\n.body {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-1);\n /* The glyph is 16 and the text is smaller, so the first line is nudged onto\n the glyph's centre rather than its top. */\n padding-top: 1px;\n}\n\n.label {\n font-size: var(--ick-task-size);\n line-height: 1.45;\n color: var(--ick-task-ink);\n}\n\n/* Finished work recedes. It is still there to read, and it is no longer what\n anybody is looking for. */\n.item[data-state=\"done\"] .label {\n color: var(--ick-task-ink-done);\n}\n\n.item[data-state=\"pending\"] .label {\n color: var(--ick-task-ink-pending);\n}\n\n/* The one being worked on is the answer to \"where is it up to\". */\n.item[data-state=\"running\"] .label {\n color: var(--ick-ink);\n font-weight: var(--ick-weight-medium);\n}\n\n.detail {\n font-size: var(--ick-text-sm);\n line-height: 1.5;\n color: var(--ick-ink-faint);\n}\n\n.item[data-state=\"error\"] .detail {\n color: var(--ick-danger);\n}\n\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n","\"use client\";\n\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { StateGlyph, type WorkState } from \"../stateGlyph/StateGlyph\";\nimport { useDisclosure } from \"../disclosure/useDisclosure\";\nimport { DisclosureHeader } from \"../disclosure/DisclosureHeader\";\nimport { DisclosureBody } from \"../disclosure/DisclosureBody\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport styles from \"./TaskList.module.css\";\n\n/** The same four as a tool call. See `StateGlyph`. */\nexport type TaskState = WorkState;\n\nexport interface Task {\n id: string;\n label: ReactNode;\n state?: TaskState;\n /** A line under the label — what it found, or why it failed. */\n detail?: ReactNode;\n}\n\ntype Labels = Record<TaskState, string> & {\n /** `{done}` and `{total}` are filled in. */\n progress: string;\n};\n\nconst LABELS: Labels = {\n pending: \"Queued\",\n running: \"In progress\",\n done: \"Done\",\n error: \"Failed\",\n progress: \"{done} of {total}\",\n};\n\nexport interface TaskListProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n tasks: Task[];\n /** Names the list, and gives it a row to fold into. */\n title?: ReactNode;\n /**\n * Let the whole list fold away.\n *\n * It folds itself once every task is done — a plan is worth watching while\n * it runs and worth little afterwards — and anybody reading it can overrule\n * that, in either direction, for good.\n */\n collapsible?: boolean;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n labels?: Partial<Labels>;\n}\n\n/**\n * What the agent means to do, what it is doing, and what it has finished.\n *\n * The order never changes. A list that sorted itself as work progressed would\n * move the line somebody is reading out from under them, and the sequence is\n * half of what the list is saying — these steps, in this order. Only the\n * glyphs change.\n */\nexport function TaskList({\n tasks,\n title,\n collapsible = false,\n open,\n defaultOpen,\n onOpenChange,\n labels,\n className,\n ...rest\n}: TaskListProps) {\n const label = { ...LABELS, ...labels };\n const listId = useId();\n const still = prefersReducedMotion();\n\n const done = tasks.filter((t) => (t.state ?? \"pending\") === \"done\").length;\n const finished = tasks.length > 0 && done === tasks.length;\n\n const disclosure = useDisclosure({\n open,\n defaultOpen,\n onOpenChange,\n preferOpen: !finished,\n });\n // Without a row to fold into there is nothing to fold with.\n const isOpen = collapsible ? disclosure.isOpen : true;\n\n const progress = label.progress\n .replace(\"{done}\", String(done))\n .replace(\"{total}\", String(tasks.length));\n\n\n return (\n <section\n className={[styles.tasks, className ?? \"\"].filter(Boolean).join(\" \")}\n {...rest}\n >\n {title && (\n <DisclosureHeader\n open={isOpen}\n /* No handler when it cannot fold, which is what makes it a heading\n rather than a control that does nothing. */\n onToggle={collapsible ? disclosure.toggle : undefined}\n controls={listId}\n label={title}\n meta={progress}\n />\n )}\n\n <DisclosureBody id={listId} open={isOpen}>\n <ol className={styles.list}>\n <AnimatePresence initial={false}>\n {tasks.map((task) => {\n const state = task.state ?? \"pending\";\n return (\n <motion.li\n key={task.id}\n className={styles.item}\n data-state={state}\n /* The one being worked on, said in a way a screen\n reader can jump to. `step` rather than `true`: this\n is a sequence, and which step it is on is the\n question somebody is asking. */\n aria-current={state === \"running\" ? \"step\" : undefined}\n initial={still ? { opacity: 0 } : { opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={still ? { opacity: 0 } : { opacity: 0, y: -4 }}\n transition={{ duration: still ? 0.12 : 0.2 }}\n >\n <StateGlyph state={state} />\n <span className={styles.body}>\n <span className={styles.label}>{task.label}</span>\n {/* The glyph is a picture. This is the same thing in\n words, for anybody it is not reaching. */}\n <span className={styles.srOnly}>{label[state]}</span>\n {task.detail && <span className={styles.detail}>{task.detail}</span>}\n </span>\n </motion.li>\n );\n })}\n </AnimatePresence>\n </ol>\n </DisclosureBody>\n </section>\n );\n}\n","/* ─────────────────────────────────────────────\n ChainOfThought\n\n A derivation, and the line down the glyph column is the point: each step\n follows from the one above it. `TaskList` is a plan and draws no line\n between its items, because a plan's items do not follow from each other —\n they are a set, in an order somebody chose.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.chain {\n box-sizing: border-box;\n width: 100%;\n /* It carries no surface of its own — no ground, no card — so it sits in the\n answer's flow, and everything in that flow starts on the answer's column.\n See `--ick-answer-column`. */\n padding-left: var(--ick-answer-column);\n font-family: var(--ick-font-sans);\n}\n\n.list {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n width: 100%;\n margin: var(--ick-space-3) 0 0;\n padding: 0;\n list-style: none;\n}\n\n.step {\n box-sizing: border-box;\n display: flex;\n align-items: flex-start;\n gap: var(--ick-space-4);\n width: 100%;\n padding-bottom: var(--ick-chain-gap);\n}\n\n/* Nothing follows the last one, so nothing should suggest it does. */\n.step:last-child {\n padding-bottom: 0;\n}\n\n/* The glyph, and the line to the step under it. Drawn on the rail rather than\n between the rows, so it stretches with however tall a step's working is. */\n.rail {\n position: relative;\n flex-shrink: 0;\n /* The line runs from under this glyph to the next one, so the rail has to be\n as tall as the step is. Left to itself it is as tall as the glyph, and the\n line came out a nine-pixel stub — measured, after the first screenshot. */\n align-self: stretch;\n display: flex;\n flex-direction: column;\n align-items: center;\n width: var(--ick-state-size);\n /* One pixel down, so the glyph sits on the label's first line rather than\n above it — the text is smaller than the glyph's box. */\n padding-top: 1px;\n}\n\n.step:not(:last-child) .rail::after {\n content: \"\";\n position: absolute;\n top: calc(var(--ick-state-size) + var(--ick-space-2));\n bottom: calc(var(--ick-chain-gap) * -1);\n left: 50%;\n width: 1px;\n margin-left: -0.5px;\n background: var(--ick-chain-connector);\n}\n\n.body {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-2);\n padding-top: 1px;\n}\n\n.stepLabel {\n font-size: var(--ick-chain-size);\n line-height: 1.45;\n font-weight: var(--ick-weight-medium);\n color: var(--ick-chain-label);\n}\n\n/* Still to come, so it should not read as something that happened. */\n.step[data-state=\"pending\"] .stepLabel {\n font-weight: var(--ick-weight-regular);\n color: var(--ick-ink-faint);\n}\n\n/* The working behind the step. Prose, so it is set like the answer it belongs\n to and coloured like an aside. */\n.working {\n font-size: var(--ick-chain-size);\n line-height: var(--ick-chain-leading);\n color: var(--ick-chain-ink);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n.working > :first-child {\n margin-top: 0;\n}\n\n.working > :last-child {\n margin-bottom: 0;\n}\n","\"use client\";\n\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\n\nimport { StateGlyph, type WorkState } from \"../stateGlyph/StateGlyph\";\nimport { useDisclosure } from \"../disclosure/useDisclosure\";\nimport { DisclosureHeader } from \"../disclosure/DisclosureHeader\";\nimport { DisclosureBody } from \"../disclosure/DisclosureBody\";\nimport { formatDuration } from \"../duration/formatDuration\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport styles from \"./ChainOfThought.module.css\";\n\n/** One step of a derivation: what was worked out, and the working. */\nexport interface Thought {\n id: string;\n /** What this step established. One line. */\n label: ReactNode;\n /** The working behind it. Prose, or anything you want drawn — a `Tool`, say. */\n body?: ReactNode;\n /** Defaults to `done`: a step you can read is a step that happened. */\n state?: WorkState;\n}\n\ntype Labels = {\n /** Followed by the count: \"Thought through 4 steps\". */\n through: string;\n step: string;\n steps: string;\n thinking: string;\n};\n\nconst LABELS: Labels = {\n through: \"Thought through\",\n step: \"step\",\n steps: \"steps\",\n thinking: \"Thinking\",\n};\n\nexport interface ChainOfThoughtProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n steps: Thought[];\n /** The chain as a whole. `thinking` holds it open and narrates the last step. */\n state?: \"thinking\" | \"done\";\n /** How long the whole chain took, in ms. */\n duration?: number;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n labels?: Partial<Labels>;\n}\n\n/**\n * How the answer was arrived at, step by step.\n *\n * The third thing in this kit that draws a sequence, and the line between them\n * is worth stating because it is the only reason there are three:\n *\n * - **`Reasoning`** is prose. The model talking to itself, unstructured.\n * - **`TaskList`** is a plan. Known up front, forward-looking, fixed order,\n * items changing state as work happens.\n * - **`ChainOfThought`** is a derivation. Backward-looking, and it *grows* —\n * each step follows from the one above it, which is what the line down the\n * side is drawing. A step carries its own working, not a footnote.\n *\n * Open while it thinks and folded away once the answer starts, on the same\n * terms as `Reasoning`: the chain's preference, overruled for good by anybody\n * who touches it.\n */\nexport function ChainOfThought({\n steps,\n state = \"done\",\n duration,\n open,\n defaultOpen,\n onOpenChange,\n labels,\n className,\n ...rest\n}: ChainOfThoughtProps) {\n const label = { ...LABELS, ...labels };\n const listId = useId();\n const still = prefersReducedMotion();\n const thinking = state === \"thinking\";\n\n const { isOpen, toggle } = useDisclosure({\n open,\n defaultOpen,\n onOpenChange,\n preferOpen: thinking,\n });\n\n /* While it is running the header narrates the step it is on — which is the\n one question somebody watching is asking, and the reason to look at a\n folded chain at all. */\n const running = thinking\n ? steps.find((s) => (s.state ?? \"done\") === \"running\") ?? steps[steps.length - 1]\n : undefined;\n\n const time = thinking ? \"\" : formatDuration(duration ?? NaN);\n const counted = `${label.through} ${steps.length} ${steps.length === 1 ? label.step : label.steps}`;\n\n return (\n <section\n className={[styles.chain, className ?? \"\"].filter(Boolean).join(\" \")}\n data-state={state}\n {...rest}\n >\n {/* An aside in the flow of an answer, like `Reasoning`'s — it takes the\n width of its own words rather than a band's. */}\n <DisclosureHeader\n fit=\"inline\"\n open={isOpen}\n onToggle={toggle}\n controls={listId}\n glyph={<StateGlyph state={thinking ? \"running\" : \"done\"} />}\n /* While it runs the header narrates the step it is on, which is the\n one question somebody watching a folded chain is asking. */\n label={thinking ? (running?.label ?? label.thinking) : counted}\n pending={thinking}\n meta={time || undefined}\n />\n\n <DisclosureBody id={listId} open={isOpen && steps.length > 0}>\n <ol className={styles.list}>\n <AnimatePresence initial={false}>\n {steps.map((step) => {\n const stepState = step.state ?? \"done\";\n return (\n <motion.li\n key={step.id}\n className={styles.step}\n data-state={stepState}\n aria-current={stepState === \"running\" ? \"step\" : undefined}\n initial={still ? { opacity: 0 } : { opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={still ? { opacity: 0 } : { opacity: 0, y: -4 }}\n transition={{ duration: still ? 0.12 : 0.2 }}\n >\n <span className={styles.rail} aria-hidden>\n <StateGlyph state={stepState} />\n </span>\n <span className={styles.body}>\n <span className={styles.stepLabel}>{step.label}</span>\n {step.body && <span className={styles.working}>{step.body}</span>}\n </span>\n </motion.li>\n );\n })}\n </AnimatePresence>\n </ol>\n </DisclosureBody>\n </section>\n );\n}\n","/* ─────────────────────────────────────────────\n Sources\n\n What the answer was drawn from. Numbered to match the markers in the text,\n and wearing the same square, so the two read as one thing counted twice\n rather than as two numbering schemes that happen to agree.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.sources {\n box-sizing: border-box;\n width: 100%;\n /* It carries no surface of its own — no ground, no card — so it sits in the\n answer's flow, and everything in that flow starts on the answer's column.\n See `--ick-answer-column`. */\n padding-left: var(--ick-answer-column);\n font-family: var(--ick-font-sans);\n\n /* A title, not a label: this heads a list rather than describing an aside,\n so it is ink at medium against `Reasoning`'s soft regular. Repointed from\n here rather than overridden with a class passed into the header — two\n `.label` rules from two stylesheets have equal specificity, and which one\n wins is import order. */\n --ick-disclosure-label: var(--ick-ink);\n --ick-disclosure-weight: var(--ick-weight-medium);\n}\n\n.list {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-2);\n width: 100%;\n margin: var(--ick-space-3) 0 0;\n padding: 0;\n list-style: none;\n}\n\n.item {\n box-sizing: border-box;\n width: 100%;\n border-radius: var(--ick-radius-md);\n transition: background var(--ick-duration-fast) ease;\n}\n\n/* Arrived at from a marker in the text. Marked rather than scrolled to: the\n list is already under the answer, and moving the page under somebody who\n clicked a marker in a sentence loses them the sentence. */\n.item[data-active] {\n background: var(--ick-cite-active);\n}\n\n.entry {\n box-sizing: border-box;\n display: flex;\n align-items: flex-start;\n gap: var(--ick-space-4);\n width: 100%;\n padding: var(--ick-space-3) var(--ick-space-4);\n border-radius: var(--ick-radius-md);\n color: inherit;\n text-decoration: none;\n}\n\na.entry {\n cursor: pointer;\n}\n\na.entry:hover {\n background: var(--ick-surface-hover);\n}\n\na.entry:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n.number {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: var(--ick-cite-size-marker);\n height: var(--ick-cite-size-marker);\n padding: 0 var(--ick-space-1);\n border-radius: var(--ick-radius-sm);\n background: var(--ick-cite-fill);\n color: var(--ick-cite-ink);\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n line-height: 1;\n}\n\n.number[data-active] {\n background: var(--ick-marker);\n color: var(--ick-marker-ink);\n}\n\n.body {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-1);\n}\n\n.name {\n font-size: var(--ick-cite-size);\n line-height: 1.45;\n color: var(--ick-ink);\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.origin {\n font-size: var(--ick-text-sm);\n color: var(--ick-ink-faint);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* The passage in the source that carries the claim. Quoted, so it is visibly\n somebody else's words and not the answer's. */\n.quote {\n margin-top: var(--ick-space-1);\n padding-left: var(--ick-space-4);\n border-left: 1px solid var(--ick-border);\n font-size: var(--ick-text-sm);\n line-height: 1.5;\n color: var(--ick-ink-soft);\n}\n","\"use client\";\n\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { useDisclosure } from \"../disclosure/useDisclosure\";\nimport { DisclosureHeader } from \"../disclosure/DisclosureHeader\";\nimport { DisclosureBody } from \"../disclosure/DisclosureBody\";\nimport styles from \"./Sources.module.css\";\n\n/** Where a claim came from. */\nexport interface Source {\n id: string;\n title: string;\n /** Opened when the entry is a link. Without one it is a row, not a link. */\n url?: string;\n /** A domain, a filename, a page — whatever names where this is. */\n origin?: string;\n /** The passage in the source that carries the claim. */\n quote?: string;\n}\n\ntype Labels = { title: string; one: string; many: string };\n\nconst LABELS: Labels = { title: \"Sources\", one: \"source\", many: \"sources\" };\n\nexport interface SourcesProps extends Omit<HTMLAttributes<HTMLElement>, \"title\" | \"onSelect\"> {\n sources: Source[];\n /** Heads the list. Defaults to `labels.title`. */\n title?: ReactNode;\n /** Offer to fold the list to its own row. */\n collapsible?: boolean;\n /**\n * The one just arrived at, from a citation in the text.\n *\n * Marked rather than scrolled to: the list is directly under the answer, and\n * moving the page under somebody who clicked a marker in a sentence they\n * were reading loses them the sentence.\n */\n activeId?: string | null;\n onSelect?: (source: Source, index: number) => void;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n labels?: Partial<Labels>;\n}\n\n/**\n * What the answer was drawn from, numbered to match the markers in it.\n *\n * Open by default. Sources are the difference between an answer somebody can\n * check and one they have to trust, and folding that away by default says the\n * opposite of what a citation is for.\n */\nexport function Sources({\n sources,\n title,\n collapsible = false,\n activeId = null,\n onSelect,\n open,\n defaultOpen,\n onOpenChange,\n labels,\n className,\n ...rest\n}: SourcesProps) {\n const label = { ...LABELS, ...labels };\n const listId = useId();\n\n const disclosure = useDisclosure({ open, defaultOpen, onOpenChange, preferOpen: true });\n const isOpen = collapsible ? disclosure.isOpen : true;\n\n const count = `${sources.length} ${sources.length === 1 ? label.one : label.many}`;\n\n return (\n <section className={[styles.sources, className ?? \"\"].filter(Boolean).join(\" \")} {...rest}>\n <DisclosureHeader\n open={isOpen}\n /* No handler when it cannot fold, which is what makes it a heading\n rather than a control that does nothing. */\n onToggle={collapsible ? disclosure.toggle : undefined}\n controls={listId}\n label={title ?? label.title}\n meta={count}\n />\n\n <DisclosureBody id={listId} open={isOpen && sources.length > 0}>\n <ol className={styles.list}>\n {sources.map((source, i) => {\n const number = i + 1;\n const active = source.id === activeId;\n const body = (\n <>\n {/* The same square the marker in the text wears, so the\n two read as one thing counted twice. */}\n <span className={styles.number} data-active={active || undefined}>\n {number}\n </span>\n <span className={styles.body}>\n <span className={styles.name}>{source.title}</span>\n {source.origin && <span className={styles.origin}>{source.origin}</span>}\n {source.quote && <span className={styles.quote}>{source.quote}</span>}\n </span>\n </>\n );\n\n return (\n <li\n key={source.id}\n className={styles.item}\n data-active={active || undefined}\n id={`${listId}-${source.id}`}\n >\n {source.url ? (\n <a\n className={styles.entry}\n href={source.url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n onClick={() => onSelect?.(source, number)}\n onPointerDown={(event) => event.stopPropagation()}\n >\n {body}\n </a>\n ) : (\n <div className={styles.entry}>{body}</div>\n )}\n </li>\n );\n })}\n </ol>\n </DisclosureBody>\n </section>\n );\n}\n",".chip {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n gap: var(--ick-space-2);\n height: var(--ick-chip-height);\n padding: 0 var(--ick-space-4);\n border-radius: var(--ick-chip-radius);\n background: var(--ick-chip-fill);\n color: var(--ick-ink);\n font-family: var(--ick-font-sans);\n font-size: var(--ick-text-sm);\n line-height: 1;\n white-space: nowrap;\n max-width: var(--ick-chip-max);\n overflow: hidden;\n text-overflow: ellipsis;\n}\n","\"use client\";\n\nimport { motion } from \"motion/react\";\nimport type { ReactNode } from \"react\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport styles from \"./Chip.module.css\";\n\nexport interface ChipProps {\n children: ReactNode;\n className?: string;\n}\n\n/** A small, self-contained value — an answer given, a count, a tag. */\nexport function Chip({ children, className }: ChipProps) {\n const still = prefersReducedMotion();\n return (\n <motion.span\n className={[styles.chip, className ?? \"\"].filter(Boolean).join(\" \")}\n initial={still ? { opacity: 0 } : { opacity: 0, scale: 0.85 }}\n animate={still ? { opacity: 1 } : { opacity: 1, scale: 1 }}\n transition={still ? { duration: 0.15 } : { type: \"spring\", stiffness: 500, damping: 30 }}\n >\n {children}\n </motion.span>\n );\n}\n","/* ─────────────────────────────────────────────\n QuestionCard\n\n One question in three states, and the card morphs between them. Everything\n here is a token — the original was built on another product's palette, and\n the point of bringing it across was that it stops being that product's.\n ───────────────────────────────────────────── */\n\n/* `box-sizing` is stated on every box here that has padding and a width.\n This package has no global reset — a reset in a CSS module leaks into the\n host's page — so each rule has to say it. Left off `.active`, its 8px of\n padding was added *outside* a 100% width and the card clipped 16px of every\n row and the Next button with it. */\n\n.item {\n box-sizing: border-box;\n position: relative;\n width: 100%;\n border-radius: var(--ick-question-radius-card);\n /* Never shrink. The list around it is a flex column, and without this every\n card is squashed the moment one of them is tall. */\n flex-shrink: 0;\n /* Clips the counter-scaled content to the morphing box, so growing reads as\n a reveal rather than as content spilling out of a box that has not caught\n up with it. */\n overflow: hidden;\n}\n\n.item[data-card] {\n background: var(--ick-question-card);\n box-shadow: var(--ick-shadow-float);\n}\n\n/* ── Upcoming ───────────────────────────────────────────────────────────── */\n\n.upcoming {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--ick-space-4);\n /* The card's own column on both sides, because this row *is* the card rather\n than a row inside one: there is no inner padding to make up the difference,\n so the badge takes it here. It was 8 on the right against 16 on the left —\n the pencil sat half a column nearer its edge than the badge sat to its\n own, which is the sort of four-pixel thing that reads as wrong long before\n anybody can say why. */\n padding: var(--ick-space-3) var(--ick-question-pad);\n width: 100%;\n white-space: nowrap;\n overflow: hidden;\n}\n\n\n/* The badge's own line box, which is the rule `.title` already states: a badge\n is 24 tall with `line-height: 1`, and text beside it inherits whatever line\n height the type gives it. Two line boxes of different heights, both centred,\n land half a pixel apart — measured, the label sat at 86.5 against the badge's\n 87. Same box, same centre, and nothing to re-guess when the type changes. */\n.upcomingLabel {\n font-size: var(--ick-text-md);\n line-height: var(--ick-badge-size);\n color: var(--ick-ink-soft);\n}\n\n/* ── Collapsed ──────────────────────────────────────────────────────────── */\n\n.collapsed {\n all: unset;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: space-between;\n flex-wrap: wrap;\n row-gap: var(--ick-space-4);\n gap: var(--ick-space-4);\n width: 100%;\n padding: var(--ick-space-4) var(--ick-question-pad);\n font-family: var(--ick-font-sans);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition: background var(--ick-duration-fast) ease;\n}\n\n.collapsed:hover {\n background: var(--ick-surface-hover);\n}\n\n.collapsed:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n.static {\n cursor: default;\n}\n\n.collapsedLabel {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--ick-space-4);\n min-width: 0;\n}\n\n/* The badge's line box. See the note over `.upcomingLabel`. */\n.collapsedTitle {\n font-size: var(--ick-text-md);\n line-height: var(--ick-badge-size);\n color: var(--ick-ink);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.collapsedAnswer {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--ick-space-2);\n flex: 1;\n min-width: 0;\n}\n\n.pencil {\n flex-shrink: 0;\n margin-left: var(--ick-space-2);\n color: var(--ick-ink-faint);\n}\n\n.collapsed:hover .pencil {\n color: var(--ick-ink);\n}\n\n/* ── Active ─────────────────────────────────────────────────────────────── */\n\n.active {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-5);\n width: 100%;\n padding: var(--ick-space-4);\n}\n\n.header {\n box-sizing: border-box;\n display: flex;\n align-items: flex-start;\n gap: var(--ick-space-4);\n /* 8 here plus the card's own 8 puts this badge on the same vertical line\n as the badges in the rows below it. See the note over `.upcoming`. */\n padding: var(--ick-space-2) 0 0 var(--ick-space-4);\n}\n\n.headerText {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n}\n\n/* The badge is 24 tall and this line was 16, both top-aligned — which puts the\n number four pixels below the middle of the question it belongs to. Measured,\n because at four pixels it reads as wrong long before anybody can say why.\n\n The line box takes the badge's height rather than the badge taking a margin:\n a margin is a guess that has to be re-guessed whenever the type changes, and\n this way the two boxes are the same box. It is also what `.fieldLabel` has\n always done, which is why field rows were the only ones already right.\n\n On the first line, deliberately. A question long enough to wrap keeps its\n badge on the line it starts on, which is where a number belongs. */\n.title {\n margin: 0;\n font-size: var(--ick-text-md);\n font-weight: var(--ick-weight-medium);\n line-height: var(--ick-badge-size);\n color: var(--ick-ink);\n}\n\n.subtitle {\n margin: var(--ick-space-1) 0 0;\n font-size: var(--ick-text-sm);\n line-height: 1.45;\n color: var(--ick-ink-soft);\n}\n\n.rows {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-3);\n width: 100%;\n}\n\n.footer {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding-top: var(--ick-space-2);\n}\n\n/* ── The badge ──────────────────────────────────────────────────────────── */\n\n.badge {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--ick-badge-size);\n height: var(--ick-badge-size);\n border-radius: var(--ick-question-radius-badge);\n background: var(--ick-badge-fill);\n color: var(--ick-ink);\n font-family: var(--ick-font-sans);\n font-size: var(--ick-text-sm);\n line-height: 1;\n transition:\n background var(--ick-duration-fast) ease,\n color var(--ick-duration-fast) ease;\n}\n\n.badge[data-on-card] {\n background: var(--ick-badge-fill-on-card);\n}\n\n/* The signature colour, doing the one job an accent is for: saying which one. */\n.badge[data-selected] {\n background: var(--ick-marker);\n color: var(--ick-marker-ink);\n}\n\n/* Except on a row already wearing it, where the accent at full strength is a\n shade off the tint under it — two yellows that close are not a badge on a\n row, they are one yellow with a rounded hole in it. Paper was the first\n answer and it was no better: pale on pale. This is the marker taken down\n towards ink, so the badge is still the accent and still separates. */\n.option[data-selected] .badge[data-selected] {\n background: var(--ick-marker-deep);\n color: var(--ick-marker-deep-ink);\n}\n\n/* ── Option and field rows ──────────────────────────────────────────────── */\n\n.option {\n all: unset;\n box-sizing: border-box;\n display: flex;\n align-items: flex-start;\n gap: var(--ick-space-4);\n width: 100%;\n padding: var(--ick-space-4);\n border-radius: var(--ick-question-radius-row);\n background: var(--ick-question-row);\n font-family: var(--ick-font-sans);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition: background var(--ick-duration-fast) ease;\n}\n\n.option:hover {\n background: var(--ick-question-row-hover);\n}\n\n.option[data-selected],\n.option[data-selected]:hover {\n background: var(--ick-question-row-selected);\n}\n\n/* `:focus-within` only on the row that has something inside it to focus.\n Unqualified it caught the option button as well, and a button *is* focused\n by a mouse click — so picking one drew a focus ring around it, which is the\n exact thing `:focus-visible` exists to avoid. The \"something else\" row is a\n label around an input: the label never takes focus itself, so it has no\n `:focus-visible` of its own to use. */\n.option:focus-visible,\n.option[data-other]:focus-within {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n.option[data-other] {\n cursor: text;\n}\n\n/* No padding: the 2px here was nudging the title towards the badge's middle\n and landing two pixels short. The line box does it exactly now. */\n.optionBody {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n/* Same again, two pixels' worth. See the note over `.title`. */\n.optionTitle {\n font-size: var(--ick-text-md);\n font-weight: var(--ick-weight-medium);\n line-height: var(--ick-badge-size);\n color: var(--ick-ink);\n}\n\n.optionDescription {\n margin-top: var(--ick-space-1);\n font-size: var(--ick-text-sm);\n line-height: 1.45;\n color: var(--ick-ink-soft);\n}\n\n.otherInput {\n all: unset;\n width: 100%;\n font-family: var(--ick-font-sans);\n /* Not `--ick-text-md`: this is text somebody types into, and on a touch\n device that is a different size. See `--ick-field-size`. */\n font-size: var(--ick-field-size);\n color: var(--ick-ink);\n caret-color: var(--ick-ink);\n}\n\n.otherInput::placeholder {\n color: var(--ick-ink-faint);\n}\n\n.field {\n box-sizing: border-box;\n display: flex;\n align-items: flex-start;\n gap: var(--ick-space-4);\n width: 100%;\n padding: var(--ick-space-4);\n border-radius: var(--ick-question-radius-row);\n background: var(--ick-question-row);\n cursor: text;\n transition: box-shadow var(--ick-duration-fast) ease;\n}\n\n/* The row lights up rather than the input, because the row is what somebody\n is looking at — the field inside it has no border of its own to notice.\n\n An inset shadow rather than a border: a border is part of the box, and a\n 1px one on the field rows and none on the option rows put the two kinds of\n badge a pixel apart. This paints the same edge and takes up no room. */\n.field[data-focused] {\n box-shadow: inset 0 0 0 1px var(--ick-question-row-focus);\n}\n\n.fieldBody {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.fieldLabel {\n display: flex;\n align-items: center;\n height: var(--ick-badge-size);\n font-size: var(--ick-text-sm);\n font-weight: var(--ick-weight-medium);\n color: var(--ick-ink);\n}\n\n.fieldInput {\n all: unset;\n width: 100%;\n font-family: var(--ick-font-sans);\n /* Not `--ick-text-md`: this is text somebody types into, and on a touch\n device that is a different size. See `--ick-field-size`. */\n font-size: var(--ick-field-size);\n color: var(--ick-ink);\n caret-color: var(--ick-ink);\n}\n\n.fieldInput::placeholder {\n color: var(--ick-ink-faint);\n}\n\n/* The card box, for a shell standing on its own rather than inside a\n QuestionCard — which brings its own, and would otherwise nest two. */\n.shellCard {\n border-radius: var(--ick-question-radius-card);\n background: var(--ick-question-card);\n box-shadow: var(--ick-shadow-float);\n}\n","\"use client\";\n\nimport {\n forwardRef,\n useId,\n useState,\n type ComponentPropsWithoutRef,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { motion } from \"motion/react\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport styles from \"./QuestionCard.module.css\";\n\n/* ─────────────────────────────────────────────\n The parts a QuestionCard is built from.\n\n Exported, so a fourth kind of question is a composition rather than a fork.\n The kit ships three shapes; a product that needs a fifth should not have to\n reimplement the badge, the row, the focus behaviour and the tokens to get\n there.\n\n Being public API is what decides how they look from outside: named for what\n they are rather than for where they sit (`QuestionOptionRow`, not\n `OptionCard`), each taking a `className` and the DOM props of the element it\n ends in, and each forwarding its ref to the thing you would actually want\n one for — the input, in the two rows that have one.\n ───────────────────────────────────────────── */\n\n/* ── The badge ──────────────────────────────────────────────────────────── */\n\nexport interface QuestionBadgeProps extends Omit<HTMLAttributes<HTMLSpanElement>, \"children\"> {\n /** The number or the letter. */\n children: ReactNode;\n /** Wearing the accent, because this is the one being answered or chosen. */\n selected?: boolean;\n /** Sitting on a raised card rather than on the group's own surface. */\n onCard?: boolean;\n}\n\n/** The 24px square carrying a number or a letter. */\nexport const QuestionBadge = forwardRef<HTMLSpanElement, QuestionBadgeProps>(\n function QuestionBadge({ children, selected, onCard, className, ...rest }, ref) {\n return (\n <span\n ref={ref}\n className={[styles.badge, className ?? \"\"].filter(Boolean).join(\" \")}\n /* Hidden from assistive technology, because the letter is a visual\n index and not information — it exists so somebody can say \"the third\n one\". Left in the tree it joins the accessible name of whatever\n contains it, and a field called \"a Their name\" is worse than one\n called \"Their name\".\n\n Said before the spread, so somebody using the badge for something\n that *is* information can pass `aria-hidden={false}`. */\n aria-hidden=\"true\"\n data-selected={selected || undefined}\n data-on-card={(!selected && onCard) || undefined}\n {...rest}\n >\n {children}\n </span>\n );\n }\n);\n\n/* ── The option row ─────────────────────────────────────────────────────── */\n\n/* Motion declares its own `onAnimationStart` and drag handlers with different\n shapes to the DOM's, so the DOM's are dropped rather than fought with. */\ntype OptionBase = Omit<\n ComponentPropsWithoutRef<\"button\">,\n \"title\" | \"onAnimationStart\" | \"onAnimationEnd\" | \"onDrag\" | \"onDragStart\" | \"onDragEnd\"\n>;\n\nexport interface QuestionOptionRowProps extends OptionBase {\n /** The a/b/c badge. Left out, the row starts at its title. */\n letter?: ReactNode;\n /** Wins the name from the button's own `title` attribute, which is a tooltip. */\n title: ReactNode;\n description?: ReactNode;\n selected?: boolean;\n}\n\n/** One option, in a single- or multi-select question. */\nexport const QuestionOptionRow = forwardRef<HTMLButtonElement, QuestionOptionRowProps>(\n function QuestionOptionRow(\n { letter, title, description, selected = false, className, ...rest },\n ref\n ) {\n const still = prefersReducedMotion();\n return (\n <motion.button\n ref={ref}\n type=\"button\"\n className={[styles.option, className ?? \"\"].filter(Boolean).join(\" \")}\n data-selected={selected || undefined}\n /* A toggle, said plainly. Not a radio: a single-select here commits and\n moves on, so it behaves like a button that picks rather than like one\n of a set you arrow between. */\n aria-pressed={selected}\n whileTap={still ? undefined : { scale: 0.99 }}\n {...rest}\n >\n {letter !== undefined && <QuestionBadge selected={selected}>{letter}</QuestionBadge>}\n <span className={styles.optionBody}>\n <span className={styles.optionTitle}>{title}</span>\n {description && <span className={styles.optionDescription}>{description}</span>}\n </span>\n </motion.button>\n );\n }\n);\n\n/* ── The field row ──────────────────────────────────────────────────────── */\n\n/** `className` styles the row; everything else lands on the input. */\ntype FieldBase = Omit<ComponentPropsWithoutRef<\"input\">, \"value\" | \"onChange\" | \"className\">;\n\nexport interface QuestionFieldRowProps extends FieldBase {\n /** The a/b/c badge. Left out, the row starts at its label. */\n letter?: ReactNode;\n label: ReactNode;\n value: string;\n /** The value, not the event — the row exists to be typed into. */\n onChange: (value: string) => void;\n /** Enter, which in a question nearly always means \"the next one\", not \"send\". */\n onEnter?: () => void;\n /** On the row. Every other prop goes to the input. */\n className?: string;\n}\n\n/** A free-text row: letter, label, and the field itself. */\nexport const QuestionFieldRow = forwardRef<HTMLInputElement, QuestionFieldRowProps>(\n function QuestionFieldRow(\n { letter, label, value, onChange, onEnter, className, id, onFocus, onBlur, onKeyDown, ...rest },\n ref\n ) {\n const [focused, setFocused] = useState(false);\n /* React's own, rather than one built out of the letter and the label. Two\n cards on a page can hold the same label, and two elements sharing an id\n means the label focuses the wrong input. */\n const auto = useId();\n const inputId = id ?? auto;\n\n return (\n /* A label, so the whole row focuses the input — which is what the\n original achieved with a click handler on a div, and this does without\n asking a pointer to be involved. */\n <label\n className={[styles.field, className ?? \"\"].filter(Boolean).join(\" \")}\n data-focused={focused || undefined}\n htmlFor={inputId}\n >\n {letter !== undefined && <QuestionBadge selected={focused}>{letter}</QuestionBadge>}\n <span className={styles.fieldBody}>\n <span className={styles.fieldLabel}>{label}</span>\n <input\n id={inputId}\n ref={ref}\n type=\"text\"\n className={styles.fieldInput}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n onFocus={(event) => {\n setFocused(true);\n onFocus?.(event);\n }}\n onBlur={(event) => {\n setFocused(false);\n onBlur?.(event);\n }}\n onKeyDown={(event) => {\n /* Theirs first: a handler that calls preventDefault is saying it\n has dealt with this key, and Enter is the one somebody is most\n likely to want for themselves. */\n onKeyDown?.(event);\n if (event.key === \"Enter\" && !event.defaultPrevented) {\n event.preventDefault();\n onEnter?.();\n }\n }}\n {...rest}\n />\n </span>\n </label>\n );\n }\n);\n\n/* ── The \"something else\" row ───────────────────────────────────────────── */\n\ntype OtherBase = Omit<\n ComponentPropsWithoutRef<\"input\">,\n \"value\" | \"onChange\" | \"className\" | \"placeholder\"\n>;\n\nexport interface QuestionOtherRowProps extends OtherBase {\n /** The a/b/c badge. Left out, the row starts at the field. */\n letter?: ReactNode;\n value: string;\n /** Also the accessible name: the row has no visible label of its own. */\n placeholder: string;\n onChange: (value: string) => void;\n onEnter?: () => void;\n /** On the row. Every other prop goes to the input. */\n className?: string;\n}\n\n/**\n * The \"something else\" row: reads as one more option, but it is a text field.\n *\n * A label rather than a button, and that is not a detail — an input inside a\n * button is not reliably focusable, and the whole row exists to be typed into.\n */\nexport const QuestionOtherRow = forwardRef<HTMLInputElement, QuestionOtherRowProps>(\n function QuestionOtherRow(\n { letter, value, placeholder, onChange, onEnter, className, onKeyDown, ...rest },\n ref\n ) {\n const filled = value.trim().length > 0;\n return (\n <label\n className={[styles.option, className ?? \"\"].filter(Boolean).join(\" \")}\n data-selected={filled || undefined}\n data-other=\"\"\n >\n {letter !== undefined && <QuestionBadge selected={filled}>{letter}</QuestionBadge>}\n <span className={styles.optionBody}>\n <input\n ref={ref}\n type=\"text\"\n className={styles.otherInput}\n value={value}\n placeholder={placeholder}\n aria-label={placeholder}\n onChange={(event) => onChange(event.target.value)}\n onKeyDown={(event) => {\n onKeyDown?.(event);\n if (event.key === \"Enter\" && !event.defaultPrevented) {\n event.preventDefault();\n onEnter?.();\n }\n }}\n {...rest}\n />\n </span>\n </label>\n );\n }\n);\n\n/* ── The shell they sit in ──────────────────────────────────────────────── */\n\nexport interface QuestionShellProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** The badge over the question. Left out, the header starts at the title. */\n number?: ReactNode;\n title: ReactNode;\n subtitle?: ReactNode;\n /** The rows this question is made of. */\n children: ReactNode;\n /** Right-aligned under the rows — usually the button that commits. */\n footer?: ReactNode;\n /**\n * Paint the card box: background, radius, shadow.\n *\n * On by default, because a shell used on its own is the reason this is\n * exported. `QuestionCard` turns it off — it brings its own box, which is\n * the one that morphs between the three states.\n */\n card?: boolean;\n}\n\n/**\n * A question laid out: header, rows, footer.\n *\n * The scaffolding, so composing a fourth kind of question is a matter of\n * choosing the rows rather than rebuilding the box, the spacing and the type\n * around them out of numbers that were tokens ten minutes ago.\n */\nexport const QuestionShell = forwardRef<HTMLDivElement, QuestionShellProps>(\n function QuestionShell(\n { number, title, subtitle, children, footer, card = true, className, ...rest },\n ref\n ) {\n return (\n <div\n ref={ref}\n className={[styles.active, card ? styles.shellCard : \"\", className ?? \"\"]\n .filter(Boolean)\n .join(\" \")}\n {...rest}\n >\n <div className={styles.header}>\n {number !== undefined && <QuestionBadge selected>{number}</QuestionBadge>}\n <div className={styles.headerText}>\n <p className={styles.title}>{title}</p>\n {subtitle && <p className={styles.subtitle}>{subtitle}</p>}\n </div>\n </div>\n <div className={styles.rows}>{children}</div>\n {footer && <div className={styles.footer}>{footer}</div>}\n </div>\n );\n }\n);\n","\"use client\";\n\nimport { useLayoutEffect, useState, type RefObject } from \"react\";\n\n/**\n * A box's corner in pixels, so Motion can keep it round while it scales it.\n *\n * A `layout` animation does not resize a box — it **scales** one, and a browser\n * scaling a box scales the corner with it. Measured on the question group\n * opening a card: `scaleY` runs from 0.932 to 1 while `border-radius` stays a\n * flat 40px, which paints a 40 × 37 ellipse and eases back to a circle. That is\n * the stretching and squashing everyone can see and nobody can name.\n *\n * Motion has a corrector for exactly this — it rewrites the radius as\n * `x% y%` against the projected box every frame — but it only runs on values\n * **Motion is managing**. A radius that lives in a CSS class is invisible to\n * it, so the whole morph goes uncorrected.\n *\n * So the number is read off the element and handed back through `style`, where\n * Motion can see it. Read rather than hard-coded, because the corner is a\n * token and a host may retune it; `var(--…)` is no use here, since the\n * corrector parses pixels and returns anything else untouched.\n *\n * `undefined` until the element exists, which is one render with the class's\n * own corner and no animation running yet — nothing to see.\n */\nexport function useCorrectedRadius(ref: RefObject<HTMLElement | null>): number | undefined {\n const [radius, setRadius] = useState<number | undefined>(undefined);\n\n useLayoutEffect(() => {\n const element = ref.current;\n if (!element) return;\n /* Before paint, so the first frame is already the corrected value rather\n than the class's and then a swap. */\n const px = Number.parseFloat(getComputedStyle(element).borderTopLeftRadius);\n setRadius((current) => (Number.isFinite(px) && px !== current ? px : current));\n }, [ref]);\n\n return radius;\n}\n","\"use client\";\n\nimport { useEffect, useMemo, useRef, useState, type ReactNode, type RefObject } from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { Pencil } from \"lucide-react\";\nimport { Button } from \"../Button/Button\";\nimport { Chip } from \"../Chip/Chip\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport {\n QuestionBadge,\n QuestionFieldRow,\n QuestionOptionRow,\n QuestionOtherRow,\n QuestionShell,\n} from \"./parts\";\nimport type { Answer, Question, QuestionState } from \"./types\";\nimport { useCorrectedRadius } from \"../radiusCorrection/useCorrectedRadius\";\nimport styles from \"./QuestionCard.module.css\";\n\nexport interface QuestionCardProps {\n question: Question;\n /** Shown in the badge. 1-based. */\n number: number;\n state: QuestionState;\n answer?: Answer;\n onCommit?: (answer: Answer) => void;\n /** Called when somebody asks to change an answer already given. */\n onEdit?: () => void;\n /** Answers can be read but not changed. */\n readOnly?: boolean;\n labels?: Partial<Record<\"next\" | \"none\" | \"edit\", string>>;\n}\n\ntype Labels = Record<\"next\" | \"none\" | \"edit\", string>;\n\nconst DEFAULT_LABELS: Labels = { next: \"Next\", none: \"None of these\", edit: \"Edit answer\" };\n\nconst SPRING = { type: \"spring\", stiffness: 260, damping: 30 } as const;\n\n/** a, b, c … — the option's letter. */\nconst letterFor = (i: number) => String.fromCharCode(97 + i);\n\n/** How long a single-select stays visibly picked before it moves on. */\nconst PICK_SETTLE = 300;\n/** Long enough for the card to have finished growing before focus lands. */\nconst FOCUS_AFTER = 380;\n\n/**\n * What an answer looks like once folded into a row.\n *\n * Two at most, then a count. Three chips of unpredictable width in a row that\n * also holds a title is a row that wraps, and a summary that wraps is not a\n * summary.\n */\nexport function answerChips(question: Question, answer?: Answer): string[] {\n if (!answer) return [];\n\n if (question.type === \"inputs\" && \"values\" in answer) {\n const values = question.fields.map((f) => answer.values?.[f.id]).filter(Boolean) as string[];\n return values.length <= 2 ? values : [...values.slice(0, 2), `+${values.length - 2}`];\n }\n\n if (question.type === \"single\" && \"optionId\" in answer) {\n const option = question.options.find((o) => o.id === answer.optionId);\n return option ? [option.title] : [];\n }\n\n if (question.type === \"multi\" && \"optionIds\" in answer) {\n const picked = question.options.filter((o) => answer.optionIds?.includes(o.id));\n const other = answer.other?.trim();\n // Several short ones read better joined than stacked.\n if (!other && picked.length > 1 && picked.every((o) => o.short)) {\n return [picked.map((o) => o.short).join(\", \")];\n }\n const labels = [...picked.map((o) => o.title), ...(other ? [other] : [])];\n return labels.length <= 2 ? labels : [...labels.slice(0, 2), `+${labels.length - 2}`];\n }\n\n return [];\n}\n\n/** The body of a question being answered. */\nfunction ActiveBody({\n question,\n number,\n answer,\n onCommit,\n labels,\n}: {\n question: Question;\n number: number;\n answer?: Answer;\n onCommit?: (answer: Answer) => void;\n labels: Labels;\n}) {\n const [values, setValues] = useState<Record<string, string>>(() =>\n answer && \"values\" in answer ? { ...answer.values } : {}\n );\n const [ids, setIds] = useState<string[]>(() =>\n answer && \"optionIds\" in answer ? [...(answer.optionIds ?? [])] : []\n );\n const [other, setOther] = useState(() => (answer && \"optionIds\" in answer ? answer.other ?? \"\" : \"\"));\n const [picked, setPicked] = useState<string | null>(\n answer && \"optionId\" in answer ? answer.optionId : null\n );\n\n const firstField = useRef<HTMLInputElement | null>(null);\n /* Built once per question rather than filled in during the map. Writing to a\n ref while rendering is the kind of thing that works until React renders\n twice, and it renders twice in development on purpose. */\n const fields = useMemo<RefObject<HTMLInputElement | null>[]>(\n () => (question.type === \"inputs\" ? question.fields.map(() => ({ current: null })) : []),\n [question]\n );\n\n useEffect(() => {\n if (question.type !== \"inputs\") return;\n // After the card has finished growing, or focus lands mid-animation and\n // the browser scrolls to where the field was rather than where it is.\n const t = setTimeout(\n () => firstField.current?.focus({ preventScroll: true }),\n prefersReducedMotion() ? 0 : FOCUS_AFTER\n );\n return () => clearTimeout(t);\n }, [question.type]);\n\n const complete = useMemo(\n () =>\n question.type !== \"inputs\" ||\n question.fields.every((f) => f.optional || (values[f.id] ?? \"\").trim()),\n [question, values]\n );\n\n /* The same in all three branches. An object rather than a local component —\n a component declared inside a render is a new type every render, and the\n rows would remount on every keystroke, taking the focus with them. The\n card around this one is the one that morphs, so this shell draws no box. */\n const shell = {\n number,\n title: question.title,\n subtitle: question.subtitle,\n card: false,\n } as const;\n\n if (question.type === \"inputs\") {\n const commit = () => complete && onCommit?.({ values });\n return (\n <QuestionShell\n {...shell}\n footer={\n <Button variant=\"secondary\" size=\"m\" disabled={!complete} onClick={commit}>\n {labels.next}\n </Button>\n }\n >\n {question.fields.map((field, i) => (\n <QuestionFieldRow\n key={field.id}\n letter={letterFor(i)}\n label={field.label}\n placeholder={field.placeholder}\n value={values[field.id] ?? \"\"}\n onChange={(v) => setValues((all) => ({ ...all, [field.id]: v }))}\n ref={i === 0 ? firstField : fields[i]}\n /* Enter moves on rather than submitting the lot: the next field\n is nearly always what somebody means by it. */\n onEnter={() => {\n const next = i === 0 ? fields[1]?.current : fields[i + 1]?.current;\n if (next) next.focus();\n else commit();\n }}\n />\n ))}\n </QuestionShell>\n );\n }\n\n if (question.type === \"single\") {\n return (\n <QuestionShell {...shell}>\n {question.options.map((option, i) => (\n <QuestionOptionRow\n key={option.id}\n letter={letterFor(i)}\n title={option.title}\n description={option.description}\n selected={picked === option.id}\n /* Picked, then a beat, then on. Committing instantly means the\n card is gone before anyone sees which one they chose. */\n onClick={() => {\n setPicked(option.id);\n setTimeout(() => onCommit?.({ optionId: option.id }), PICK_SETTLE);\n }}\n />\n ))}\n </QuestionShell>\n );\n }\n\n const trimmed = other.trim();\n const empty = ids.length === 0 && !trimmed;\n const commitMulti = () => {\n if (!question.allowEmpty && empty) return;\n onCommit?.({ optionIds: ids, ...(trimmed ? { other: trimmed } : {}) });\n };\n\n return (\n <QuestionShell\n {...shell}\n footer={\n <Button\n variant=\"secondary\"\n size=\"m\"\n disabled={!question.allowEmpty && empty}\n onClick={commitMulti}\n >\n {question.allowEmpty && empty ? labels.none : labels.next}\n </Button>\n }\n >\n {question.options.map((option, i) => (\n <QuestionOptionRow\n key={option.id}\n letter={letterFor(i)}\n title={option.title}\n description={option.description}\n selected={ids.includes(option.id)}\n onClick={() =>\n setIds((all) =>\n all.includes(option.id) ? all.filter((x) => x !== option.id) : [...all, option.id]\n )\n }\n />\n ))}\n {question.allowOther && (\n <QuestionOtherRow\n letter={letterFor(question.options.length)}\n value={other}\n placeholder={question.otherPlaceholder ?? \"Something else\"}\n onChange={setOther}\n onEnter={commitMulti}\n />\n )}\n </QuestionShell>\n );\n}\n\n/**\n * One question, in whichever of its three states it is in.\n *\n * The card morphs between them rather than swapping: the box animates with\n * FLIP, and the content inside counter-scales so text keeps its real size\n * instead of stretching on the way. `overflow: hidden` is what makes growing\n * read as a reveal rather than as content spilling out of a box that has not\n * caught up.\n */\nexport function QuestionCard({\n question,\n number,\n state,\n answer,\n onCommit,\n onEdit,\n readOnly = false,\n labels,\n}: QuestionCardProps) {\n const label = { ...DEFAULT_LABELS, ...labels };\n const isCard = state !== \"upcoming\";\n const still = prefersReducedMotion();\n const cardRef = useRef<HTMLDivElement>(null);\n const cardRadius = useCorrectedRadius(cardRef);\n\n let body: ReactNode;\n if (state === \"active\") {\n body = (\n <ActiveBody\n question={question}\n number={number}\n answer={answer}\n onCommit={onCommit}\n labels={label}\n />\n );\n } else if (state === \"collapsed\") {\n const chips = answerChips(question, answer);\n const summary = (\n <>\n <span className={styles.collapsedLabel}>\n <QuestionBadge onCard>{number}</QuestionBadge>\n <span className={styles.collapsedTitle}>{question.shortTitle}</span>\n </span>\n <span className={styles.collapsedAnswer}>\n {chips.map((chip, i) => (\n <Chip key={`${chip}-${i}`}>{chip}</Chip>\n ))}\n {!readOnly && <Pencil className={styles.pencil} size={14} aria-hidden />}\n </span>\n </>\n );\n\n /* One control for the whole row rather than a click handler on a div with\n a button inside it. The original could be clicked but not tabbed to, and\n a nested button inside a clickable row is a second tab stop for the same\n action. Read-only, it is not a control at all. */\n body = readOnly ? (\n <div className={`${styles.collapsed} ${styles.static}`}>{summary}</div>\n ) : (\n <button\n type=\"button\"\n className={styles.collapsed}\n onClick={onEdit}\n aria-label={`${label.edit}: ${question.shortTitle}`}\n >\n {summary}\n </button>\n );\n } else {\n body = (\n <div className={styles.upcoming}>\n {/* The number, not a mark meaning \"one of these\" — the row is question\n three whether or not it has been reached yet, and saying so is what\n makes a list of them read as a list. White, because an upcoming\n card has no card under it: the badge is what stands off the\n group's own surface. */}\n <QuestionBadge>{number}</QuestionBadge>\n <span className={styles.upcomingLabel}>{question.shortTitle}</span>\n </div>\n );\n }\n\n return (\n <motion.div\n ref={cardRef}\n layout={!still}\n data-active={state === \"active\" || undefined}\n className={styles.item}\n data-card={isCard || undefined}\n transition={{ layout: SPRING }}\n /* Handed to Motion so it can keep the corner round while it scales the\n box. This one morphs between a tall card and a 40px row, so it is the\n one that scales furthest. See `useCorrectedRadius`. */\n style={{ borderRadius: cardRadius }}\n >\n {/* The wrapper resizes with FLIP, which scales everything inside it.\n `layout=\"position\"` makes Motion counter-scale the content, so text\n and controls keep their real size instead of stretching. `popLayout`\n takes the outgoing state out of flow so it is not squashed on its\n way out. */}\n <AnimatePresence mode=\"popLayout\" initial={false}>\n <motion.div\n key={state}\n layout={still ? false : \"position\"}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1, transition: { duration: 0.2, delay: still ? 0 : 0.12 } }}\n exit={{ opacity: 0, transition: { duration: 0.12 } }}\n >\n {body}\n </motion.div>\n </AnimatePresence>\n </motion.div>\n );\n}\n","/* ─────────────────────────────────────────────\n QuestionGroup\n ───────────────────────────────────────────── */\n\n.group {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-5);\n\n /* A section's name, not an aside's label. See `Sources` for the same note. */\n --ick-disclosure-label: var(--ick-ink);\n --ick-disclosure-weight: var(--ick-weight-medium);\n /* And a name stands over the words it names, not over the cards' edges: the\n title starts on the column the badges and the numbers start on, and the\n chevron ends on the column the rows' pencils end on. */\n --ick-disclosure-inset: var(--ick-question-pad);\n /* No wash. It is a rounded box the width of the header, and there is no box\n under it for that shape to agree with — it read as a stray highlight. */\n --ick-disclosure-hover: transparent;\n width: 100%;\n padding: var(--ick-space-6);\n border-radius: var(--ick-question-radius-group);\n background: var(--ick-question-surface);\n font-family: var(--ick-font-sans);\n /* The ground is the edge of this thing, so nothing paints past it.\n `AnimatePresence mode=\"popLayout\"` takes the leaving body out of flow so\n the arriving one can have its place immediately — which means that while\n the ground shrinks, the old list stays pinned where it was and hangs out\n of the bottom, three rows of it, over whatever is underneath. Card shadows\n still have room: the ground's own 16 of padding is more than the float\n shadow reaches. */\n overflow: hidden;\n}\n\n/* What the header opens. One slot: the questions, or the one row they fold\n into. The header above it does not move either way, which is what makes the\n swap read as content changing rather than a control changing places. */\n.body {\n box-sizing: border-box;\n position: relative;\n width: 100%;\n}\n\n/* The negative margin and matching padding keep the cards' shadows out of the\n clip that the layout animation puts on this box. */\n.list {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-5);\n margin: 0 calc(var(--ick-space-4) * -1);\n padding: 0 var(--ick-space-4);\n}\n\n/* One card's slot in the list, and the thing the row variants are applied to.\n It exists because a card arriving has to be a Motion child of the list for\n the stagger to reach it, and `QuestionCard` is not one.\n\n `flex-shrink: 0` for the reason `.item` carries it: this is a flex column,\n and without it every card is squashed the moment one of them is tall. */\n.row {\n box-sizing: border-box;\n flex-shrink: 0;\n width: 100%;\n}\n\n/* Motion animates a row's `y` as an independent transform, and an independent\n transform does not promote the element on its own — sampled through a whole\n fold, every row read `will-change: auto`. Motion's own guidance is to name\n the properties being animated and then take the hint away again, because a\n permanent one is a permanent layer.\n\n Hence the attribute rather than a rule on `.row`: it goes on when the fold\n starts and comes off when the bodies have settled, so the layers exist for\n the three hundred milliseconds that need them. */\n.group[data-moving] .row,\n.group[data-moving] .summary {\n will-change: transform, opacity;\n}\n\n/* ── Folded ─────────────────────────────────────────────────────────────── */\n\n/* Folded: what the step covered, on one card. Not a control any more — the\n header above it is. */\n.summary {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--ick-space-4);\n width: 100%;\n /* On the card's column, like every other row that is a card: the count rides\n where a badge rides, and the line ends where a pencil ends. */\n padding: var(--ick-space-4) var(--ick-question-pad);\n border-radius: var(--ick-question-radius-card);\n background: var(--ick-question-card);\n box-shadow: var(--ick-shadow-float);\n font-family: var(--ick-font-sans);\n}\n\n/* A badge, not a chip. It stands in the badge column on a card, so it is the\n same box the numbers stand in one state over: same height, same corner, same\n fill on a card, same type. Only the width differs, because this one holds\n words rather than a numeral.\n\n It had been 2px of padding, a pill corner and the smallest type in the kit,\n which made the single badge on the folded row about half the size of every\n badge on the open one — and the two states are a fold apart. */\n.count {\n box-sizing: border-box;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n height: var(--ick-badge-size);\n padding: 0 var(--ick-space-4);\n border-radius: var(--ick-question-radius-badge);\n background: var(--ick-badge-fill-on-card);\n font-size: var(--ick-text-sm);\n line-height: 1;\n color: var(--ick-ink);\n}\n\n/* One line, ellipsised. A summary that wraps is not a summary. */\n.summaryList {\n flex: 1;\n min-width: 0;\n font-size: var(--ick-text-sm);\n /* The count beside it is a badge, so this takes the badge's line box — the\n same rule the card's own rows follow. Measured with a `Range` over the\n text itself, it sat at 86.5 against the count's 87: two line boxes of\n different heights, both centred, land half a pixel apart. */\n line-height: var(--ick-badge-size);\n color: var(--ick-ink-soft);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n","\"use client\";\n\nimport { useId, useRef, useState, type ReactNode } from \"react\";\nimport { AnimatePresence, LayoutGroup, motion } from \"motion/react\";\nimport { QuestionCard } from \"../QuestionCard/QuestionCard\";\nimport { DisclosureHeader } from \"../disclosure/DisclosureHeader\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport type { Answer, Question } from \"../QuestionCard/types\";\nimport { useCorrectedRadius } from \"../radiusCorrection/useCorrectedRadius\";\nimport styles from \"./QuestionGroup.module.css\";\n\n/** Below this, folding saves less room than the summary row costs. */\nexport const FOLDABLE_FROM = 3;\n\n/**\n * What the fold is made of, in numbers.\n *\n * Springs for anything that moves, tweens for opacity alone.\n *\n * That split is not a preference. A spring is a description of *travel* —\n * where a thing is going and how it arrives — and opacity has nowhere to\n * travel: it is bounded at 0 and 1, so a spring with any bounce in it\n * overshoots into a clamp and spends the overshoot sitting still. Position and\n * size have no such ceiling, which is exactly why they are worth springing.\n *\n * `visualDuration` rather than stiffness and damping, for the same reason\n * throughout: the two of them describe one spring between them without either\n * answering \"how long is this\", which is the only question anybody tuning it\n * is asking. Motion solves the spring for the duration you name.\n */\nexport interface FoldMotion {\n /** How long the ground looks like it takes to resize, in seconds. */\n visualDuration: number;\n /** The ground's overshoot, 0–1. */\n bounce: number;\n /** How long a row takes to arrive, in seconds. */\n rowDuration: number;\n /** A row's overshoot, 0–1. Enough to read as arriving, not as bouncing. */\n rowBounce: number;\n /** How far above its place a row starts, in pixels. */\n rowOffset: number;\n /** Between one row and the next, in seconds. */\n stagger: number;\n /** A row's fade, in seconds. Tween: see the note above. */\n fadeIn: number;\n /** And the fade of one leaving. */\n fadeOut: number;\n /**\n * How long the arriving body waits, in seconds.\n *\n * Not a stylistic pause — it is what stops the two bodies from being in the\n * same pixels at once. They are anchored to the same top edge, so the folded\n * row and the first card always want that same band; crossfading them put\n * two different sentences on top of each other at half opacity, which is\n * mush rather than motion. At `fadeOut` the one is gone before the other\n * starts, and the box growing underneath is what carries the eye across.\n */\n fadeInDelay: number;\n}\n\nexport const defaultFoldMotion: FoldMotion = {\n visualDuration: 0.34,\n bounce: 0.1,\n rowDuration: 0.3,\n rowBounce: 0.12,\n rowOffset: -10,\n stagger: 0.045,\n fadeIn: 0.16,\n fadeOut: 0.08,\n fadeInDelay: 0.08,\n};\n\nexport interface QuestionGroupProps {\n /** Distinguishes one group's layout animations from another's. */\n id: string;\n /**\n * What this step is about, at the top of the group.\n *\n * Also the fold control when there is one — which is the point of putting it\n * there. A control at the top does not move when the thing under it opens,\n * so folding stops being two shapes swapping places and becomes a body\n * changing under a header that stays put.\n *\n * Left out on a foldable group, the count stands in, so the control still\n * has a name.\n */\n title?: ReactNode;\n questions: Question[];\n answers: Record<string, Answer | undefined>;\n /** Index of the question being answered, or `null` when none is. */\n activeIndex?: number | null;\n /** Offer to fold the whole group into one row. */\n collapsible?: boolean;\n onCommit?: (questionId: string, answer: Answer) => void;\n onEdit?: (index: number) => void;\n readOnly?: boolean;\n labels?: Partial<Record<\"answers\", string>>;\n /** Overrides for the fold's timing. See `defaultFoldMotion`. */\n foldMotion?: Partial<FoldMotion>;\n className?: string;\n}\n\n/* `hide` went with the pill that used to say it. The header carries the\n section's name in both states now, so there is nothing left to word. */\nconst DEFAULT_LABELS = { answers: \"answers\" };\n\n/**\n * One step of a questionnaire: the surface holding its questions.\n *\n * Once the conversation has moved past it, it folds down to a single row.\n * Not a peek at the list — a peek costs more height than the answers it shows,\n * and a conversation with four half-open steps above it is unreadable.\n */\nexport function QuestionGroup({\n id,\n title,\n questions,\n answers,\n activeIndex = null,\n collapsible = false,\n onCommit,\n onEdit,\n readOnly = false,\n labels,\n foldMotion,\n className,\n}: QuestionGroupProps) {\n const [expanded, setExpanded] = useState(false);\n /* True for the length of one fold, and off again after. See the note over\n `[data-moving]` in the stylesheet. */\n const [moving, setMoving] = useState(false);\n const bodyId = useId();\n const groundRef = useRef<HTMLDivElement>(null);\n const groundRadius = useCorrectedRadius(groundRef);\n const label = { ...DEFAULT_LABELS, ...labels };\n const still = prefersReducedMotion();\n const folded = collapsible && !expanded;\n\n const summary = questions.map((q) => q.shortTitle).join(\" · \");\n\n const beat = { ...defaultFoldMotion, ...foldMotion };\n\n /* The box's own resize. Handed to every element that carries `layout` here,\n so the ground and the things inside it move on one clock — a header\n counter-scaling on a different spring to the box scaling it is a header\n that visibly lags. */\n const resize = still\n ? { duration: 0 }\n : { type: \"spring\" as const, visualDuration: beat.visualDuration, bounce: beat.bounce };\n\n /* What a body does, one row at a time.\n \n This was a crossfade between two blocks: the summary dissolving into the\n list and back. A dissolve is what you reach for when two things are\n unrelated, and these are not — the row and the stack are the same answers\n in two states. It read as the box moving while the content sat there\n bleeding through itself.\n \n Rows arrive instead, each one a little above its place and settling into\n it, one after the next, and leave the same way. The ground follows them\n rather than the other way round. */\n const bodyVariants = {\n /* Both directions stagger the same way round. The conventional mirror —\n last in, first out — is right when a thing is being *dismissed*, because\n it unwinds the way it was built. This is not a dismissal: it is one body\n being replaced by another on the same edge, and the two are the same\n answers. Read forwards both ways, leaving and arriving are one gesture\n passing through the row rather than two gestures meeting in the middle. */\n hidden: { transition: { staggerChildren: still ? 0 : beat.stagger } },\n shown: { transition: { staggerChildren: still ? 0 : beat.stagger } },\n };\n\n /* One spring, both directions. A row leaving used to be a plain tween while a\n row arriving was sprung, which made collapsing a different gesture from\n expanding rather than the same one run backwards. */\n const travel = (fade: number, delay = 0) =>\n still\n ? { duration: 0 }\n : {\n type: \"spring\" as const,\n visualDuration: beat.rowDuration,\n bounce: beat.rowBounce,\n delay,\n /* Opacity is bounded, so it gets the tween. See `FoldMotion`. */\n opacity: { duration: fade, delay },\n };\n\n const rowMotion = {\n hidden: { opacity: 0, y: still ? 0 : beat.rowOffset, transition: travel(beat.fadeOut) },\n shown: {\n opacity: 1,\n y: 0,\n transition: travel(beat.fadeIn, still ? 0 : beat.fadeInDelay),\n },\n };\n\n const bodyMotion = {\n variants: bodyVariants,\n initial: \"hidden\" as const,\n animate: \"shown\" as const,\n exit: \"hidden\" as const,\n /* The arriving body finishing is when the fold is over. Both bodies carry\n this, and the leaving one finishes first — clearing on that took the\n layers away at 165ms with the rows still travelling until 300. */\n onAnimationComplete: (definition: unknown) => {\n if (definition === \"shown\") setMoving(false);\n },\n };\n\n return (\n <LayoutGroup id={id}>\n <motion.div\n ref={groundRef}\n layout={!still}\n transition={resize}\n className={[styles.group, className ?? \"\"].filter(Boolean).join(\" \")}\n data-moving={moving || undefined}\n /* Handed to Motion so it can keep the corner round while it scales the\n box. See `useCorrectedRadius`. */\n style={{ borderRadius: groundRadius }}\n >\n {/* The section's name, at the top, and the fold control when there is\n one. At the top because a control that does not move is the whole\n trick: folding stops being two shapes swapping places and becomes a\n body changing under a header that stays put.\n \n Without a title a foldable group falls back to the count, so the\n control still has a name; without either there is no header at all\n and the group is the list, as it was. */}\n {(title || collapsible) && (\n /* `layout=\"position\"` and not decoration.\n\n The ground animates its height the only way Motion animates a\n size: it sets the new one in the DOM and scales the box back. So\n every child that is not itself a layout child rides that scale,\n and this one is a line of type. Measured over one open: the header\n was squashed to 11.67px of its 23 in a single frame and stretched\n back over the next 450ms, the title with it — which is exactly\n what \"it flickers and jumps around\" looks like when you slow it\n down. `position` is the right half of it: the header does not\n move, so all it needs is the scale taken back off. */\n <motion.div layout={still ? false : \"position\"} transition={resize}>\n <DisclosureHeader\n open={!folded}\n onToggle={\n collapsible\n ? () => {\n setMoving(true);\n setExpanded((open) => !open);\n }\n : undefined\n }\n controls={bodyId}\n label={title ?? `${questions.length} ${label.answers}`}\n />\n </motion.div>\n )}\n\n <motion.div\n id={bodyId}\n layout={still ? false : \"position\"}\n transition={resize}\n className={styles.body}\n >\n <AnimatePresence initial={false} mode=\"popLayout\">\n {folded ? (\n /* No `layout` on either body, deliberately. `popLayout` takes\n the leaving one out of flow, and `layout` then animates it\n toward wherever it resolves to once absolute — measured, the\n summary left its own row and travelled 67px down the moment it\n popped, so it faded out somewhere it had never been. Scale\n correction is what these needed and they inherit it from the\n body above them.\n\n Each body is a list of rows even when it holds one, so that\n both sides of the fold arrive by the same rule. */\n <motion.div key=\"summary\" {...bodyMotion}>\n <motion.div variants={rowMotion} className={styles.summary}>\n <span className={styles.count}>\n {questions.length} {label.answers}\n </span>\n <span className={styles.summaryList}>{summary}</span>\n </motion.div>\n </motion.div>\n ) : (\n <motion.div key=\"list\" className={styles.list} {...bodyMotion}>\n {questions.map((question, i) => (\n <motion.div key={question.id} variants={rowMotion} className={styles.row}>\n <QuestionCard\n question={question}\n number={i + 1}\n state={\n i === activeIndex\n ? \"active\"\n : answers[question.id]\n ? \"collapsed\"\n : \"upcoming\"\n }\n answer={answers[question.id]}\n readOnly={readOnly}\n onCommit={(answer) => onCommit?.(question.id, answer)}\n onEdit={() => onEdit?.(i)}\n />\n </motion.div>\n ))}\n </motion.div>\n )}\n </AnimatePresence>\n </motion.div>\n </motion.div>\n </LayoutGroup>\n );\n}\n","/* ─────────────────────────────────────────────\n SystemMessage\n\n A recessed row in the transcript: the surface talking, rather than either of\n the two people in the conversation. It takes the **ground** for that reason\n — a ground is what things sit on, and a note about the conversation is not a\n thing in it.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.message {\n box-sizing: border-box;\n width: 100%;\n /* The column every card's *content* starts on — which from out here is the\n ground's own padding plus that column, because this row stands where a\n ground stands rather than where a card does. Measured with only the column\n it read 32 against a tool call's 48: a note lined up with the cards' edges\n instead of with the words in them, which is the one alignment rule this\n kit has. */\n padding: var(--ick-space-4)\n calc(var(--ick-nest-ground-pad) + var(--ick-nest-column));\n border-radius: var(--ick-system-radius);\n background: var(--ick-system-surface);\n font-family: var(--ick-font-sans);\n font-size: var(--ick-text-sm);\n /* Prose spacing, not the badge's line box.\n \n The badge's box is the rule for a label standing *beside* a badge, so that\n the two centre on one line. Nothing stands beside this. Taking it anyway\n bought a one-line note the height of a folded row and cost every note that\n wraps — 24 over 13px type is 1.85, which on three lines reads as a poster\n rather than a sentence. A note is prose and wraps like prose; the eight\n above and below is what keeps a one-liner close to a folded row. */\n line-height: 1.5;\n color: var(--ick-system-ink);\n}\n\n/* Something went wrong. The tint is the whole signal, and the sentence beside\n it is what anybody can act on — see the note in the component about why\n there is no icon. */\n.message[data-tone=\"danger\"] {\n background: var(--ick-system-surface-danger);\n color: var(--ick-system-ink-danger);\n}\n","\"use client\";\n\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./SystemMessage.module.css\";\n\n/**\n * Two, not three.\n *\n * `Context` settled this already: an amber in the middle makes somebody learn\n * a scale to read a state they can already read in words. Something either\n * went wrong or it did not.\n */\nexport type SystemTone = \"notice\" | \"danger\";\n\nexport interface SystemMessageProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n children: ReactNode;\n tone?: SystemTone;\n}\n\n/**\n * The conversation itself saying something — not the reader, not the agent.\n *\n * \"The oldest messages are dropping out of the window.\" \"The model changed\n * partway through this answer.\" \"You went offline.\" Things that happened *to*\n * the conversation, which until now had nowhere to be said: `Context` warns\n * that the window is nearly full and then nothing speaks when it fills.\n *\n * **No glyph**, deliberately, and for the reason the approval's shield came\n * off an hour before this was written. Every picture in this kit carries a\n * state the words beside it also carry — queued, running, failed, allowed — so\n * that a reader the picture does not reach loses nothing. An icon here would\n * carry \"something is being announced\", next to a sentence announcing it. The\n * tint says which of the two tones it is and the sentence says the rest, which\n * is the only part anybody can act on.\n *\n * **No dismiss, and no action.** It is a line of the transcript rather than a\n * toast: dismissing one would be editing what happened. A host that needs a\n * button under it composes one, which costs them a line and costs this\n * component a whole API.\n *\n * **No live region either.** The kit has one, shared and written to on a later\n * tick — see `announce`. A second one says everything twice, which is the\n * fault that region exists to have fixed.\n */\nexport function SystemMessage({\n children,\n tone = \"notice\",\n className,\n ...rest\n}: SystemMessageProps) {\n return (\n <div\n className={[styles.message, className ?? \"\"].filter(Boolean).join(\" \")}\n data-tone={tone}\n {...rest}\n >\n {children}\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n ArtifactCard\n\n The kit's usual three: a ground, a card on it, and what the card holds inset\n into that. The ground is not decoration — it is what puts the card's words on\n the answer's column, which every other box in a turn reaches the same way.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n\n/* The ground, which is what puts the card's words on the answer's column —\n see the note in the component. */\n.artifact {\n box-sizing: border-box;\n width: 100%;\n padding: var(--ick-artifact-ground-pad);\n border-radius: var(--ick-artifact-ground-radius);\n background: var(--ick-artifact-ground);\n font-family: var(--ick-font-sans);\n}\n\n/* The whole card is the control, because the whole card is the affordance: a\n window onto something with a way in, rather than a panel with a button\n somewhere on it. */\n.card {\n all: unset;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-4);\n width: 100%;\n padding: var(--ick-nest-pad);\n border-radius: var(--ick-artifact-radius);\n background: var(--ick-artifact-surface);\n box-shadow: var(--ick-shadow-float);\n /* Clips the preview at the cut, which is what makes it a preview. */\n overflow: hidden;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n transform var(--ick-duration-fast) var(--ick-ease),\n box-shadow var(--ick-duration-fast) var(--ick-ease);\n}\n\n/* ── Pointed at, and pressed ────────────────────────────────────────────────\n\n The card moves; its surface does not.\n\n It used to take a grey fill on hover, and the fill was the problem. The card\n sits on a ground that is itself ink at 5%, and the hover fill was ink at\n about 4% of the card — so pointing at it slid the card *towards* the colour\n of the thing it is meant to be lifted off. The nearer it got to being\n pressed, the more it looked merged. A card is a physical metaphor and the\n physical answer is the right one: nearer the reader on hover, down under the\n finger on press, and the surface left alone.\n\n Two pixels, not one. One is the right amount of travel for a button in a\n row; a card the width of the answer needs the shadow to carry it, and the\n shadow only reads if the gap under the card actually opens. */\n.artifact:not([data-open]) .card:hover {\n box-shadow: var(--ick-shadow-float-lift);\n}\n\n/* The travel, only where there is something to hover with. A tap on a phone\n fires a hover that stays, so ungated the card sat two pixels up until you\n touched something else. The shadow stays on touch: it says which card you\n are on without claiming the card moved. */\n@media (hover: hover) and (pointer: fine) {\n .artifact:not([data-open]) .card:hover {\n transform: translateY(-2px);\n }\n}\n\n/* Pressed, including the open one — pressing that closes the pane, so it has\n to answer. Faster than the lift, because a press is not a journey.\n\n Written out twice, and it has to be. A press only ever happens under a\n pointer that is already hovering, so this rule is in a fight with the one\n above it and has to win: `.card:active` alone loses on specificity, four\n to two, and the card stayed lifted under the finger. It measured `-2` where\n it should have measured `+1` — caught by `npm run showcase:artifact`, not by\n looking, because a press that does nothing looks exactly like a press\n arriving a frame late. */\n.artifact:not([data-open]) .card:active,\n.artifact[data-open] .card:active {\n transform: translateY(1px) scale(0.997);\n box-shadow: var(--ick-shadow-float-press);\n transition-duration: var(--ick-duration-press);\n}\n\n.card:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: -2px;\n}\n\n/* Which one the pane is showing — and it is drawn with nothing.\n\n It had a two-pixel ink ring. Beside a whole pane holding the same document,\n a ring on the card was a second answer to a question already answered, and\n it read as a selected list row rather than as an open one. What says it now\n is the absence above: every other card lifts under the pointer, this one\n stays put, because there is nowhere for it to go. The fact itself is not\n left to the eye — the button carries `aria-expanded`, which is the reading\n the ring was never giving anybody. */\n\n/* The reader has asked for less movement, so the card stops moving. The\n shadow still deepens: it is a change of state, not a journey. */\n@media (prefers-reduced-motion: reduce) {\n .card,\n .artifact:not([data-open]) .card:active,\n .artifact[data-open] .card:active,\n .artifact:not([data-open]) .card:hover {\n transform: none;\n }\n}\n\n.head {\n box-sizing: border-box;\n display: flex;\n align-items: baseline;\n gap: var(--ick-space-4);\n width: 100%;\n min-width: 0;\n /* The column, reached from inside the card the way every row here reaches\n it: the card's own gap plus a row's. */\n padding: var(--ick-space-2) var(--ick-nest-pad) 0;\n}\n\n.title {\n font-size: var(--ick-text-md);\n font-weight: var(--ick-weight-medium);\n color: var(--ick-ink);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Still being written. The same shimmer a disclosure header's pending label\n uses, and for the same reason: the word is the name of the thing, so it\n cannot be hidden or replaced by a spinner. */\n.title[data-writing] {\n background: linear-gradient(\n 90deg,\n var(--ick-ink) 0%,\n var(--ick-ink) 40%,\n var(--ick-ink-faint) 50%,\n var(--ick-ink) 60%,\n var(--ick-ink) 100%\n );\n background-size: 200% 100%;\n -webkit-background-clip: text;\n background-clip: text;\n -webkit-text-fill-color: transparent;\n animation: artifact-sweep 1.8s linear infinite;\n}\n\n@keyframes artifact-sweep {\n from { background-position: 110% 0; }\n to { background-position: -10% 0; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .title[data-writing] {\n animation: none;\n background: none;\n -webkit-text-fill-color: currentColor;\n }\n}\n\n.meta {\n flex-shrink: 0;\n margin-left: auto;\n font-size: var(--ick-text-sm);\n color: var(--ick-ink-faint);\n}\n\n/* ── The window ─────────────────────────────────────────────────────────── */\n\n.preview {\n position: relative;\n box-sizing: border-box;\n width: 100%;\n min-width: 0;\n /* Repointed rather than restyled, the way a tool call moves the code block's\n surface: the preview is a row inset into this card. */\n --ick-md-code-fill: var(--ick-artifact-preview-fill);\n --ick-code-radius: var(--ick-nest-row);\n --ick-code-pad: var(--ick-nest-pad);\n}\n\n/* The cut. A hard edge says the file ends there; a fade says it goes on, which\n is the one thing the card has to get across. */\n.preview[data-more]::after {\n content: \"\";\n position: absolute;\n inset-inline: 0;\n bottom: 0;\n height: var(--ick-artifact-fade);\n border-end-start-radius: var(--ick-nest-row);\n border-end-end-radius: var(--ick-nest-row);\n background: linear-gradient(to bottom, transparent, var(--ick-artifact-preview-fill));\n pointer-events: none;\n}\n\n.text {\n box-sizing: border-box;\n margin: 0;\n padding: var(--ick-nest-pad) var(--ick-code-pad);\n border-radius: var(--ick-nest-row);\n background: var(--ick-artifact-preview-fill);\n font-size: var(--ick-text-sm);\n line-height: 1.5;\n color: var(--ick-ink-soft);\n /* What was written keeps its own line breaks; it is a document, not a\n paragraph somebody typed into a box. */\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n/* Nothing to show yet, which is not the same as nothing to say: the title is\n already there and shimmering. */\n.waiting {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n padding: var(--ick-nest-pad) var(--ick-code-pad);\n border-radius: var(--ick-nest-row);\n background: var(--ick-artifact-preview-fill);\n}\n","\"use client\";\n\nimport { type HTMLAttributes, type ReactNode } from \"react\";\nimport { CodeBlock } from \"../CodeBlock/CodeBlock\";\nimport { Loader } from \"../Loader/Loader\";\nimport styles from \"./ArtifactCard.module.css\";\n\n/**\n * Code, or everything else.\n *\n * Two, because they are drawn differently and nothing else is: code goes\n * through the highlighter, prose keeps its line breaks and does not. An\n * `image` kind can be added the day something needs one — it is a third\n * preview, not a third idea.\n */\nexport type ArtifactKind = \"code\" | \"text\";\n\n/** Being written, or written. A file does not fail the way a call does. */\nexport type ArtifactState = \"writing\" | \"done\";\n\nexport interface ArtifactCardProps\n extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"title\" | \"content\" | \"onSelect\"\n > {\n /** What the pane opens. The card and the pane share nothing else. */\n id: string;\n title: ReactNode;\n /** What it is, in a word or two — \"Training plan\", \"12 lines of Python\". */\n meta?: ReactNode;\n kind?: ArtifactKind;\n /** The fence's language, for `kind=\"code\"`. */\n lang?: string;\n /** What was written. Absent while it is still being written. */\n content?: string;\n state?: ArtifactState;\n /**\n * How much of it the card shows before the cut.\n *\n * The whole point of an artifact is that it is bigger than the answer, so\n * the card is a window rather than the thing. Poured out in full it is not\n * an artifact, it is a long message.\n */\n lines?: number;\n /** Open it. Without one the card is a record rather than a control. */\n onOpen?: (id: string) => void;\n /**\n * True while its pane is the one on screen.\n *\n * Nothing is drawn for it. The pane standing beside the conversation is\n * already the answer, and a ring on the card was a second one — so what\n * marks it is that it no longer lifts under the pointer, and\n * `aria-expanded`, which says it outright to anybody not using a pointer.\n */\n open?: boolean;\n}\n\nconst LINES = 8;\n\n/** The first `lines` of it, and whether there was more. */\nfunction clip(content: string, lines: number) {\n const all = content.split(\"\\n\");\n return { shown: all.slice(0, lines).join(\"\\n\"), more: all.length > lines };\n}\n\n/**\n * What the answer produced, in the transcript.\n *\n * A window onto it and a way in — not the thing itself. Press it and the pane\n * beside the conversation shows the whole of it; the card stays where it is,\n * because it is the line of the transcript that says the artifact was made.\n */\nexport function ArtifactCard({\n id,\n title,\n meta,\n kind = \"code\",\n lang,\n content,\n state = \"done\",\n lines = LINES,\n onOpen,\n open,\n className,\n ...rest\n}: ArtifactCardProps) {\n const writing = state === \"writing\";\n const clipped = content ? clip(content, lines) : null;\n\n const body = (\n <div className={styles.preview} data-more={clipped?.more || undefined} aria-hidden>\n {clipped === null ? (\n <div className={styles.waiting}>\n <Loader />\n </div>\n ) : kind === \"code\" ? (\n <CodeBlock code={clipped.shown} lang={lang} label={false} copyable={false} />\n ) : (\n <p className={styles.text}>{clipped.shown}</p>\n )}\n </div>\n );\n\n /* A ground under the card after all, and the reason is the answer's column\n rather than this component.\n\n It was taken away when the prose still began at the turn's own edge: a\n ground under a single card was 32px of indent that put the card's words on\n a different line from the answer's. Now everything in an answer begins on\n one column and the boxes bleed wider than it — so the ground is what puts\n this card's words *on* that column, the same way a tool call's does. See\n `--ick-answer-column`. */\n const inside = (\n <>\n <Head title={title} meta={meta} writing={writing} />\n {body}\n </>\n );\n\n const shared = { className: styles.card };\n\n return (\n <div\n className={[styles.artifact, className ?? \"\"].filter(Boolean).join(\" \")}\n data-state={state}\n data-open={open || undefined}\n {...(rest as HTMLAttributes<HTMLDivElement>)}\n >\n {onOpen ? (\n <button\n type=\"button\"\n {...shared}\n /* The state the ring used to draw, said properly. Omitted rather\n than `false` when the host is not tracking which is open: a\n button that claims to expand nothing is worse than a plain one. */\n aria-expanded={open}\n onClick={() => onOpen(id)}\n >\n {inside}\n </button>\n ) : (\n <div {...shared}>{inside}</div>\n )}\n </div>\n );\n}\n\nfunction Head({\n title,\n meta,\n writing,\n}: {\n title: ReactNode;\n meta?: ReactNode;\n writing: boolean;\n}) {\n return (\n <span className={styles.head}>\n <span className={styles.title} data-writing={writing || undefined}>\n {title}\n </span>\n {meta && <span className={styles.meta}>{meta}</span>}\n </span>\n );\n}\n","/**\n * Markdown, parsed here rather than by `unified` + `remark`.\n *\n * That pipeline was 31.6 KB gzip — a third of the whole package — and the\n * slowest thing in it: 0.9ms per thousand characters, 8.9ms at ten thousand,\n * which is half a frame spent re-parsing an answer that grew by one word.\n *\n * The kit uses a thin slice of it. `parseMarkdown` walks the tree straight\n * into a flat token list and a handful of elements, and it touches nine block\n * types and nine inline ones. This produces exactly those, in the same shape,\n * so nothing downstream changed.\n *\n * **It is not a CommonMark implementation and does not try to be.** It handles\n * what a model actually writes. Where it diverges from `remark` the difference\n * is caught rather than assumed: `remark` stays a dev dependency and a test\n * parses a corpus through both and compares the finished documents.\n */\n\nexport type Phrasing =\n | { type: \"text\"; value: string }\n | { type: \"strong\"; children: Phrasing[] }\n | { type: \"emphasis\"; children: Phrasing[] }\n | { type: \"delete\"; children: Phrasing[] }\n | { type: \"inlineCode\"; value: string }\n | { type: \"link\"; url: string; title?: string; children: Phrasing[] }\n | { type: \"image\"; url: string; alt: string }\n /**\n * `[^1]` — a citation marker, pointing at the nth source of the turn.\n *\n * The kit's one extension to the grammar, and the only place this parser\n * deliberately reads something `remark` does not. GFM spells footnotes the\n * same way but needs a `[^1]: …` definition somewhere in the document to\n * make one; a model streaming an answer emits the marker and sends the\n * sources beside the text, never below it. Without this an `InlineCitation`\n * can only be written by hand in JSX, which a stream cannot do — so the\n * component existed and nothing in a real conversation could reach it. */\n | { type: \"citation\"; index: number }\n | { type: \"break\" }\n /** Recognised so it can be dropped, which is what the renderer does with it. */\n | { type: \"html\"; value: string };\n\nexport interface ListItem {\n type: \"listItem\";\n children: Block[];\n}\n\nexport interface TableCell {\n type: \"tableCell\";\n children: Phrasing[];\n}\n\nexport interface TableRow {\n type: \"tableRow\";\n children: TableCell[];\n}\n\nexport type Block =\n | { type: \"paragraph\"; children: Phrasing[] }\n | { type: \"heading\"; depth: number; children: Phrasing[] }\n | { type: \"list\"; ordered: boolean; start?: number; children: ListItem[] }\n | { type: \"blockquote\"; children: Block[] }\n | { type: \"code\"; lang?: string; value: string }\n | { type: \"thematicBreak\" }\n | { type: \"table\"; children: TableRow[] }\n | { type: \"html\"; value: string };\n\n/* ─────────────────────────────────────────────\n Inline\n ───────────────────────────────────────────── */\n\n/** `\\*` and friends: the next character is itself, not syntax. */\nconst ESCAPABLE = \"\\\\`*_{}[]()#+-.!>~|\";\n\nconst isSpace = (c: string | undefined) => c === undefined || /\\s/.test(c);\n\n/**\n * A run of `*` or `_` that can open or close emphasis.\n *\n * The full CommonMark rule is about flanking runs and unicode punctuation.\n * This is the part of it that matters for prose: a marker opens if what\n * follows is not a space and closes if what precedes is not one, and `_`\n * additionally has to sit at a word boundary — otherwise `snake_case_name`\n * comes back italic, which is the one failure anybody would actually hit.\n */\nfunction canOpen(src: string, at: number, run: number, char: string): boolean {\n const after = src[at + run];\n if (isSpace(after)) return false;\n if (char === \"_\" && /[\\w]/.test(src[at - 1] ?? \"\")) return false;\n return true;\n}\n\nfunction canClose(src: string, at: number, run: number, char: string): boolean {\n const before = src[at - 1];\n if (isSpace(before)) return false;\n if (char === \"_\" && /[\\w]/.test(src[at + run] ?? \"\")) return false;\n return true;\n}\n\n/** How many of `char` start at `at`. */\nfunction runLength(src: string, at: number, char: string): number {\n let n = 0;\n while (src[at + n] === char) n++;\n return n;\n}\n\n/** The index of the `]` matching the `[` at `open`, or -1. */\nfunction closingBracket(src: string, open: number): number {\n let depth = 0;\n for (let i = open; i < src.length; i++) {\n const c = src[i];\n if (c === \"\\\\\") {\n i++;\n continue;\n }\n if (c === \"`\") {\n // A bracket inside a code span is not a bracket.\n const run = runLength(src, i, \"`\");\n const end = src.indexOf(\"`\".repeat(run), i + run);\n i = end === -1 ? src.length : end + run - 1;\n continue;\n }\n if (c === \"[\") depth++;\n else if (c === \"]\") {\n depth--;\n if (depth === 0) return i;\n }\n }\n return -1;\n}\n\n/**\n * `(url \"title\")` after a link's `]`.\n *\n * Returns where it ends, so the caller knows how much to consume. Angle\n * brackets around the url are stripped, which is how a url with a space in it\n * is written.\n */\nfunction destination(src: string, open: number): { url: string; title?: string; end: number } | null {\n if (src[open] !== \"(\") return null;\n let depth = 0;\n let close = -1;\n for (let i = open; i < src.length; i++) {\n if (src[i] === \"\\\\\") {\n i++;\n continue;\n }\n if (src[i] === \"(\") depth++;\n else if (src[i] === \")\") {\n depth--;\n if (depth === 0) {\n close = i;\n break;\n }\n }\n }\n if (close === -1) return null;\n\n const inner = src.slice(open + 1, close).trim();\n const quoted = inner.match(/^(.*?)\\s+(?:\"([^\"]*)\"|'([^']*)'|\\(([^)]*)\\))$/s);\n const rawUrl = (quoted ? quoted[1] : inner).trim();\n const title = quoted ? (quoted[2] ?? quoted[3] ?? quoted[4]) : undefined;\n const url = rawUrl.startsWith(\"<\") && rawUrl.endsWith(\">\") ? rawUrl.slice(1, -1) : rawUrl;\n return { url, ...(title ? { title } : {}), end: close + 1 };\n}\n\n/** Anything shaped like a tag, so it can be recognised and thrown away. */\nconst HTML_TAG = /^<\\/?[A-Za-z][A-Za-z0-9-]*(\\s[^<>]*?)?\\/?>|^<!--[\\s\\S]*?-->/;\n\nexport function parseInline(src: string): Phrasing[] {\n const out: Phrasing[] = [];\n let text = \"\";\n\n const flush = () => {\n if (text) out.push({ type: \"text\", value: text });\n text = \"\";\n };\n\n let i = 0;\n while (i < src.length) {\n const c = src[i];\n\n // A backslash before punctuation, or before a newline: an escape or a break.\n if (c === \"\\\\\") {\n const next = src[i + 1];\n if (next === \"\\n\") {\n flush();\n out.push({ type: \"break\" });\n i += 2;\n continue;\n }\n if (next && ESCAPABLE.includes(next)) {\n text += next;\n i += 2;\n continue;\n }\n text += c;\n i++;\n continue;\n }\n\n // Two spaces at the end of a line is a hard break.\n if (c === \" \" && src[i + 1] === \" \" && /^ *\\n/.test(src.slice(i))) {\n const nl = src.indexOf(\"\\n\", i);\n flush();\n out.push({ type: \"break\" });\n i = nl + 1;\n continue;\n }\n\n if (c === \"`\") {\n const run = runLength(src, i, \"`\");\n const close = src.indexOf(\"`\".repeat(run), i + run);\n // An unmatched run is literal backticks, not the start of anything.\n if (close !== -1 && !src.slice(i + run, close).startsWith(\"`\")) {\n flush();\n let value = src.slice(i + run, close);\n // One space either side is the fence, not the content — it is how you\n // write a span that itself starts with a backtick.\n if (value.length > 2 && value.startsWith(\" \") && value.endsWith(\" \") && value.trim()) {\n value = value.slice(1, -1);\n }\n out.push({ type: \"inlineCode\", value: value.replace(/\\n/g, \" \") });\n i = close + run;\n continue;\n }\n }\n\n if (c === \"!\" && src[i + 1] === \"[\") {\n const close = closingBracket(src, i + 1);\n const dest = close === -1 ? null : destination(src, close + 1);\n if (dest) {\n flush();\n out.push({ type: \"image\", url: dest.url, alt: src.slice(i + 2, close) });\n i = dest.end;\n continue;\n }\n }\n\n /* Before the link branch, which would otherwise take `[^1]` as a label\n and then fail to find a `(url)` after it — leaving the marker as text. */\n if (c === \"[\" && src[i + 1] === \"^\") {\n const close = src.indexOf(\"]\", i + 2);\n const digits = close === -1 ? null : src.slice(i + 2, close);\n if (digits && /^\\d{1,3}$/.test(digits)) {\n flush();\n out.push({ type: \"citation\", index: Number(digits) });\n i = close + 1;\n continue;\n }\n }\n\n if (c === \"[\") {\n const close = closingBracket(src, i);\n const dest = close === -1 ? null : destination(src, close + 1);\n if (dest) {\n flush();\n out.push({\n type: \"link\",\n url: dest.url,\n ...(dest.title ? { title: dest.title } : {}),\n children: parseInline(src.slice(i + 1, close)),\n });\n i = dest.end;\n continue;\n }\n }\n\n if (c === \"~\" && src[i + 1] === \"~\") {\n const close = src.indexOf(\"~~\", i + 2);\n if (close !== -1 && close > i + 2) {\n flush();\n out.push({ type: \"delete\", children: parseInline(src.slice(i + 2, close)) });\n i = close + 2;\n continue;\n }\n }\n\n if (c === \"*\" || c === \"_\") {\n const run = Math.min(runLength(src, i, c), 2);\n if (canOpen(src, i, run, c)) {\n // The first run of the same length that can close it.\n let j = i + run;\n let close = -1;\n while (j < src.length) {\n if (src[j] === \"\\\\\") {\n j += 2;\n continue;\n }\n if (src[j] === c) {\n const there = runLength(src, j, c);\n if (there >= run && canClose(src, j, run, c)) {\n close = j;\n break;\n }\n j += there;\n continue;\n }\n j++;\n }\n if (close !== -1 && close > i + run) {\n flush();\n const children = parseInline(src.slice(i + run, close));\n out.push(run === 2 ? { type: \"strong\", children } : { type: \"emphasis\", children });\n i = close + run;\n continue;\n }\n }\n }\n\n if (c === \"<\") {\n const tag = src.slice(i).match(HTML_TAG);\n if (tag) {\n flush();\n out.push({ type: \"html\", value: tag[0] });\n i += tag[0].length;\n continue;\n }\n }\n\n text += c;\n i++;\n }\n\n flush();\n return out;\n}\n\n/* ─────────────────────────────────────────────\n Blocks\n ───────────────────────────────────────────── */\n\nconst FENCE = /^ {0,3}(`{3,}|~{3,})\\s*([^`\\s]*)[^`]*$/;\nconst HEADING = /^ {0,3}(#{1,6})(?:\\s+(.*?))?\\s*#*\\s*$/;\nconst RULE = /^ {0,3}(?:(?:\\*\\s*){3,}|(?:-\\s*){3,}|(?:_\\s*){3,})$/;\nconst QUOTE = /^ {0,3}>\\s?/;\nconst BULLET = /^(\\s*)([-*+])(\\s+)(.*)$/;\nconst ORDERED = /^(\\s*)(\\d{1,9})([.)])(\\s+)(.*)$/;\nconst DELIMITER = /^ {0,3}\\|?\\s*:?-{1,}:?\\s*(\\|\\s*:?-{1,}:?\\s*)*\\|?\\s*$/;\nconst DELIMITER_CELL = /^:?-+:?$/;\nconst BLANK = /^\\s*$/;\n\n/** `| a | b |` → `[\"a\", \"b\"]`, respecting `\\|`. */\nfunction cells(line: string): string[] {\n const trimmed = line.trim().replace(/^\\|/, \"\").replace(/\\|$/, \"\");\n const out: string[] = [];\n let cell = \"\";\n for (let i = 0; i < trimmed.length; i++) {\n if (trimmed[i] === \"\\\\\" && trimmed[i + 1] === \"|\") {\n cell += \"|\";\n i++;\n continue;\n }\n if (trimmed[i] === \"|\") {\n out.push(cell.trim());\n cell = \"\";\n continue;\n }\n cell += trimmed[i];\n }\n out.push(cell.trim());\n return out;\n}\n\nconst isBlockStart = (line: string) =>\n FENCE.test(line) ||\n HEADING.test(line) ||\n RULE.test(line) ||\n QUOTE.test(line) ||\n BULLET.test(line) ||\n ORDERED.test(line);\n\nexport function parseBlocks(src: string): Block[] {\n const lines = src.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n const out: Block[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n if (BLANK.test(line)) {\n i++;\n continue;\n }\n\n // ── Fenced code ────────────────────────────────────────────────────────\n const fence = line.match(FENCE);\n if (fence) {\n const marker = fence[1][0];\n const width = fence[1].length;\n const body: string[] = [];\n i++;\n while (i < lines.length) {\n const closing = lines[i].match(new RegExp(`^ {0,3}${marker === \"`\" ? \"`\" : \"~\"}{${width},}\\\\s*$`));\n if (closing) {\n i++;\n break;\n }\n body.push(lines[i]);\n i++;\n }\n out.push({ type: \"code\", value: body.join(\"\\n\"), ...(fence[2] ? { lang: fence[2] } : {}) });\n continue;\n }\n\n // ── Heading ────────────────────────────────────────────────────────────\n const heading = line.match(HEADING);\n if (heading) {\n out.push({\n type: \"heading\",\n depth: heading[1].length,\n children: parseInline(heading[2] ?? \"\"),\n });\n i++;\n continue;\n }\n\n // ── Thematic break ─────────────────────────────────────────────────────\n if (RULE.test(line)) {\n out.push({ type: \"thematicBreak\" });\n i++;\n continue;\n }\n\n // ── Blockquote ─────────────────────────────────────────────────────────\n if (QUOTE.test(line)) {\n const quoted: string[] = [];\n while (i < lines.length && !BLANK.test(lines[i])) {\n // Lazy continuation: a line inside a quote need not repeat the marker.\n quoted.push(lines[i].replace(QUOTE, \"\"));\n i++;\n }\n out.push({ type: \"blockquote\", children: parseBlocks(quoted.join(\"\\n\")) });\n continue;\n }\n\n // ── Table ──────────────────────────────────────────────────────────────\n /* A table needs a delimiter row with **as many cells as the header**, which\n is the rule that keeps a half-typed one from becoming a table a row\n early. Streaming parses every prefix of an answer, so `| --- ` arrives\n on its own for a frame or two and it is still a paragraph. */\n const delimiter =\n line.includes(\"|\") && i + 1 < lines.length && DELIMITER.test(lines[i + 1])\n ? cells(lines[i + 1])\n : null;\n if (delimiter && delimiter.length === cells(line).length && delimiter.every((c) => DELIMITER_CELL.test(c))) {\n const header = cells(line);\n const rows: TableRow[] = [\n { type: \"tableRow\", children: header.map((c) => ({ type: \"tableCell\" as const, children: parseInline(c) })) },\n ];\n i += 2;\n while (i < lines.length && !BLANK.test(lines[i]) && lines[i].includes(\"|\")) {\n const row = cells(lines[i]);\n rows.push({\n type: \"tableRow\",\n children: header.map((_, at) => ({\n type: \"tableCell\" as const,\n children: parseInline(row[at] ?? \"\"),\n })),\n });\n i++;\n }\n out.push({ type: \"table\", children: rows });\n continue;\n }\n\n // ── List ───────────────────────────────────────────────────────────────\n const bullet = line.match(BULLET);\n const ordered = line.match(ORDERED);\n if (bullet || ordered) {\n const isOrdered = Boolean(ordered);\n const start = ordered ? Number(ordered[2]) : undefined;\n const items: ListItem[] = [];\n\n while (i < lines.length) {\n const b = lines[i].match(BULLET);\n const o = lines[i].match(ORDERED);\n const m = isOrdered ? o : b;\n if (!m || Boolean(o) !== isOrdered) break;\n\n const indent = (isOrdered ? m[1].length + m[2].length + 1 + m[4].length : m[1].length + 1 + m[3].length);\n const body = [isOrdered ? m[5] : m[4]];\n i++;\n\n // Everything indented under the marker, and lazy lines that continue\n // the item's paragraph.\n while (i < lines.length) {\n const next = lines[i];\n if (BLANK.test(next)) {\n const after = lines[i + 1];\n if (after !== undefined && new RegExp(`^ {${indent},}\\\\S`).test(after)) {\n body.push(\"\");\n i++;\n continue;\n }\n break;\n }\n if (new RegExp(`^ {${indent},}`).test(next)) {\n body.push(next.slice(indent));\n i++;\n continue;\n }\n if (BULLET.test(next) || ORDERED.test(next) || isBlockStart(next)) break;\n body.push(next.trim());\n i++;\n }\n\n items.push({ type: \"listItem\", children: parseBlocks(body.join(\"\\n\")) });\n }\n\n out.push({\n type: \"list\",\n ordered: isOrdered,\n ...(isOrdered && start !== undefined ? { start } : {}),\n children: items,\n });\n continue;\n }\n\n // ── Paragraph ──────────────────────────────────────────────────────────\n const para: string[] = [];\n while (i < lines.length && !BLANK.test(lines[i])) {\n if (para.length && isBlockStart(lines[i])) break;\n if (\n para.length &&\n lines[i].includes(\"|\") &&\n i + 1 < lines.length &&\n DELIMITER.test(lines[i + 1])\n ) {\n break;\n }\n para.push(lines[i]);\n i++;\n }\n /* A paragraph's own indentation is not content: the leading whitespace of\n every line goes, and so does the trailing whitespace of the last one.\n Interior trailing spaces are left alone — two of them before a newline\n is a hard break, which is the one place whitespace means something. */\n const text = para.map((l) => l.replace(/^[ \\t]+/, \"\")).join(\"\\n\").replace(/\\s+$/, \"\");\n out.push({ type: \"paragraph\", children: parseInline(text) });\n }\n\n return out;\n}\n","import { parseBlocks, type Block, type Phrasing } from \"./parse\";\n\n/**\n * Markdown, in the one shape the highlighter can work with.\n *\n * The highlighter's whole model is a **flat array of word and space tokens**,\n * addressed by index. Hit-testing reads `data-index` off whatever is under the\n * pointer; the keyboard cursor walks the indices; the text of a highlight is\n * rebuilt by joining a run of them. Markdown is a tree, and the obvious move —\n * render markdown and highlight the result — breaks every one of those.\n *\n * So the tree and the indices are separated. Parsing produces both:\n *\n * - `tokens`, still flat, still in reading order, exactly as before\n * - `nodes`, a small tree that says where each token sits\n *\n * Nothing downstream changes. A stroke across `**bold**` is a run of indices\n * like any other, even though the words live in different elements.\n */\nexport type MdNode =\n /** One entry of `tokens`, by index. */\n | { type: \"token\"; index: number }\n | { type: \"el\"; tag: string; props?: Record<string, string>; children: MdNode[] }\n /** A fenced block. Preformatted, so it is not tokenised — see below. */\n | { type: \"code\"; lang?: string; value: string };\n\nexport interface MarkdownDoc {\n /** Flat, in reading order. The bridge to every existing mechanism. */\n tokens: string[];\n nodes: MdNode[];\n}\n\n/**\n * Separates two blocks in `tokens` without appearing in `nodes`.\n *\n * An index does not have to be rendered. Nothing requires every token to exist\n * in the DOM — the cursor only walks words, and hit-testing only finds what is\n * drawn. What this buys is a sane rebuild: without it, the last word of one\n * paragraph and the first of the next would be joined into one word.\n */\nconst BLOCK_BREAK = \"\\n\\n\";\n\nconst HEADINGS = [\"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\"] as const;\n\nclass Builder {\n tokens: string[] = [];\n\n /** Split a run of text into words and spaces, and record where they went. */\n text(value: string): MdNode[] {\n const out: MdNode[] = [];\n for (const piece of value.split(/(\\s+)/)) {\n if (piece === \"\") continue;\n out.push({ type: \"token\", index: this.tokens.length });\n this.tokens.push(piece);\n }\n return out;\n }\n\n /** A token nobody draws. Keeps the rebuilt text readable across blocks. */\n break(): void {\n this.tokens.push(BLOCK_BREAK);\n }\n}\n\nconst el = (tag: string, children: MdNode[], props?: Record<string, string>): MdNode => ({\n type: \"el\",\n tag,\n children,\n ...(props ? { props } : {}),\n});\n\n/**\n * Map siblings, dropping a separator between each pair.\n *\n * Every place two pieces of text sit side by side with no whitespace of their\n * own needs this — list items, table cells, table rows. Without it the last\n * word of one and the first of the next are rebuilt as a single word, which is\n * how \"one\" and \"two\" first came back as \"onetwo\".\n */\nfunction siblings<T>(items: T[], b: Builder, each: (item: T) => MdNode): MdNode[] {\n return items.map((item, i) => {\n if (i > 0) b.break();\n return each(item);\n });\n}\n\nfunction inline(nodes: Phrasing[], b: Builder): MdNode[] {\n const out: MdNode[] = [];\n for (const node of nodes) {\n switch (node.type) {\n case \"text\":\n out.push(...b.text(node.value));\n break;\n case \"strong\":\n out.push(el(\"strong\", inline(node.children, b)));\n break;\n case \"emphasis\":\n out.push(el(\"em\", inline(node.children, b)));\n break;\n case \"delete\":\n out.push(el(\"del\", inline(node.children, b)));\n break;\n case \"inlineCode\":\n // Tokenised, unlike a fenced block: this is a run of words inside a\n // sentence, and a stroke has to be able to cross it.\n out.push(el(\"code\", b.text(node.value)));\n break;\n case \"link\":\n out.push(\n el(\"a\", inline(node.children, b), {\n href: node.url,\n // Model output is untrusted text. A link in it goes to somebody\n // else's page, so it opens away from the conversation and cannot\n // reach back through `window.opener`.\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n ...(node.title ? { title: node.title } : {}),\n })\n );\n break;\n case \"image\":\n out.push(el(\"img\", [], { src: node.url, alt: node.alt ?? \"\" }));\n break;\n case \"citation\":\n /* Untokenised, like a fenced block and for the same reason: a marker\n is not a word of the answer. Tokenising it would put `[1]` inside a\n highlight and inside the text a thread quotes back, which is not\n what anybody drew a line under. */\n out.push(el(\"cite\", [], { index: String(node.index) }));\n break;\n case \"break\":\n out.push(el(\"br\", []));\n break;\n // `html` is deliberately dropped. Raw HTML from a model is arbitrary\n // markup in the host's page; there is no version of rendering it that is\n // worth the surface it opens.\n default:\n if (\"children\" in node && Array.isArray(node.children)) {\n out.push(...inline(node.children as Phrasing[], b));\n }\n }\n }\n return out;\n}\n\nfunction block(nodes: Block[], b: Builder): MdNode[] {\n const out: MdNode[] = [];\n nodes.forEach((node, i) => {\n if (i > 0) b.break();\n switch (node.type) {\n case \"paragraph\":\n out.push(el(\"p\", inline(node.children, b)));\n break;\n case \"heading\":\n out.push(el(HEADINGS[node.depth - 1] ?? \"h6\", inline(node.children, b)));\n break;\n case \"list\":\n out.push(\n el(\n node.ordered ? \"ol\" : \"ul\",\n siblings(node.children, b, (item) => el(\"li\", block(item.children, b))),\n node.ordered && node.start != null && node.start !== 1\n ? { start: String(node.start) }\n : undefined\n )\n );\n break;\n case \"blockquote\":\n out.push(el(\"blockquote\", block(node.children, b)));\n break;\n case \"code\":\n out.push({ type: \"code\", value: node.value, ...(node.lang ? { lang: node.lang } : {}) });\n break;\n case \"thematicBreak\":\n out.push(el(\"hr\", []));\n break;\n case \"table\": {\n const [head, ...body] = node.children;\n const cells = (row: typeof head, tag: \"th\" | \"td\") =>\n siblings(row.children, b, (cell) => el(tag, inline(cell.children, b)));\n // Built head-first so the indices run in reading order; `siblings`\n // only separates within a run, so the seam between the two needs one\n // of its own.\n const thead = head ? el(\"thead\", [el(\"tr\", cells(head, \"th\"))]) : null;\n if (thead && body.length) b.break();\n const tbody = body.length\n ? el(\"tbody\", siblings(body, b, (row) => el(\"tr\", cells(row, \"td\"))))\n : null;\n out.push(el(\"table\", [thead, tbody].filter((n): n is MdNode => n !== null)));\n break;\n }\n default:\n if (\"children\" in node && Array.isArray(node.children)) {\n out.push(...inline(node.children as Phrasing[], b));\n }\n }\n });\n return out;\n}\n\n/**\n * Parse once per distinct string.\n *\n * Called from a `useMemo` keyed on the text, which during streaming still\n * means once per frame — the text is different every frame. That is measured\n * rather than assumed; see the note in `TextHighlighter`.\n */\nexport function parseMarkdown(text: string): MarkdownDoc {\n const b = new Builder();\n const nodes = block(parseBlocks(text), b);\n return { tokens: b.tokens, nodes };\n}\n","/* ─────────────────────────────────────────────\n InlineCitation\n\n The kit already had a way of saying \"this run of words is picked out\" — the\n marker somebody draws over an answer to ask about it. A citation is the same\n statement made by the answer rather than by the reader, so it is drawn the\n same way rather than in a second visual language nobody has learned.\n ───────────────────────────────────────────── */\n\n.citation {\n /* Inline, so it flows with the sentence it belongs to and wraps with it. */\n display: inline;\n}\n\n/* The passage the citation speaks for. */\n.marked .passage {\n background: var(--ick-cite-passage);\n /* Painted per line rather than as one box, so a passage that wraps is marked\n on every line it covers instead of as a rectangle behind the paragraph. */\n box-decoration-break: clone;\n -webkit-box-decoration-break: clone;\n padding: 0.05em 0.1em;\n border-radius: var(--ick-radius-xs);\n}\n\n/* Sized to the number rather than to a square.\n\n It was `min-width: 16px` with 2px of padding — the square the list uses. In\n a list that is right: the numbers sit in a column and have to line up. In a\n sentence it is not. A single digit is about five pixels wide, so five and a\n half pixels of empty badge sat on either side of it, and whatever followed —\n a comma, most often — came after that gap and read as detached from the\n number it belongs to.\n\n Padding in `em` so it tracks the number rather than a fixed square, and no\n minimum: what a badge needs is a fill and a corner, not a width. */\n.marker {\n all: unset;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: var(--ick-cite-size-marker);\n padding: 0 0.3em;\n margin-left: 0.25em;\n /* Lifted, the way a footnote mark is, without the size loss of a real\n superscript — the number still has to be readable and clickable. */\n vertical-align: 0.25em;\n border-radius: var(--ick-radius-sm);\n background: var(--ick-cite-fill);\n color: var(--ick-cite-ink);\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n line-height: 1;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n background var(--ick-duration-fast) ease,\n color var(--ick-duration-fast) ease;\n}\n\n.marker:hover {\n background: var(--ick-marker);\n color: var(--ick-marker-ink);\n}\n\n.marker:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: 1px;\n}\n\n/* Nowhere to go, so it is not a control and should not offer to be one. */\n.marker[data-static] {\n cursor: default;\n}\n\n.marker[data-static]:hover {\n background: var(--ick-cite-fill);\n color: var(--ick-cite-ink);\n}\n","\"use client\";\n\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport type { Source } from \"../Sources/Sources\";\nimport styles from \"./InlineCitation.module.css\";\n\nexport interface InlineCitationProps\n extends Omit<HTMLAttributes<HTMLElement>, \"children\" | \"onSelect\"> {\n /** 1-based, and it has to match the entry's place in `<Sources>`. */\n index: number;\n /** Named in the marker's label, so the marker says more than a number. */\n source?: Source;\n /**\n * The passage this citation is for.\n *\n * Given one, the passage is **marked** — the same yellow the highlighter\n * draws, because it is the same statement: this run of words is spoken for.\n * Left out, the marker stands on its own after whatever precedes it.\n */\n children?: ReactNode;\n /** Where the reader is taken. Without one the marker is not a control. */\n onSelect?: (index: number, source?: Source) => void;\n labels?: Partial<{ cite: string }>;\n}\n\n/**\n * The numbered marker in the text, and the passage it speaks for.\n *\n * The kit already had a way of saying \"this run of words is picked out\" — the\n * marker somebody draws over an answer to ask about it. A citation is that\n * same statement made by the answer rather than by the reader, so it is drawn\n * the same way rather than in a second visual language nobody has learned.\n */\nexport function InlineCitation({\n index,\n source,\n children,\n onSelect,\n labels,\n className,\n ...rest\n}: InlineCitationProps) {\n const cite = labels?.cite ?? \"Source\";\n const name = source ? `${cite} ${index}: ${source.title}` : `${cite} ${index}`;\n\n const marker = onSelect ? (\n <button\n type=\"button\"\n className={styles.marker}\n onClick={() => onSelect(index, source)}\n /* Inside an answer this sits on the highlighter's surface, where a\n pointerdown starts drawing a marker. The click is for the citation. */\n onPointerDown={(event) => event.stopPropagation()}\n aria-label={name}\n >\n {index}\n </button>\n ) : (\n <span className={styles.marker} data-static=\"\" aria-label={name} role=\"note\">\n {index}\n </span>\n );\n\n if (!children) {\n return (\n <span className={[styles.citation, className ?? \"\"].filter(Boolean).join(\" \")} {...rest}>\n {marker}\n </span>\n );\n }\n\n return (\n <span\n className={[styles.citation, styles.marked, className ?? \"\"].filter(Boolean).join(\" \")}\n {...rest}\n >\n <span className={styles.passage}>{children}</span>\n {marker}\n </span>\n );\n}\n","/* ─────────────────────────────────────────────\n Marker hover, driven by CSS\n\n Hover used to live in React state. Every crossing of the marker's stroke\n re-rendered the whole highlighter — every word span, every path string —\n and started springs that wrote stroke-width, opacity and filter on each\n frame. Tracing a paragraph crosses that boundary hundreds of times, which\n is why input-to-paint stretched past 100ms while the frame rate itself\n looked fine.\n\n The browser already knows what the pointer is over. Nothing here needs\n React to find out.\n ───────────────────────────────────────────── */\n\n.marker {\n --marker-stroke: 20px;\n\n /* Set here rather than as an SVG `stroke` attribute: an attribute cannot\n read a custom property, and the marker colour is the signature of the\n whole feature — it belongs in the token layer, not in a TSX constant. */\n stroke: var(--ick-marker-stroke);\n fill: none;\n stroke-linejoin: round;\n stroke-linecap: butt;\n stroke-width: var(--marker-stroke);\n opacity: 1;\n transition:\n stroke-width var(--ick-duration-base) var(--ick-ease),\n opacity var(--ick-duration-base) var(--ick-ease);\n}\n\n.marker:hover {\n --marker-stroke: 24px;\n opacity: 0.9;\n}\n\n/* Pressed and menu-open are click-driven, so they stay in React — clicks are\n rare and a re-render there costs nothing. They win over hover. */\n.marker[data-pressed=\"true\"],\n.marker[data-pressed=\"true\"]:hover {\n --marker-stroke: 16px;\n opacity: 1;\n}\n\n.marker[data-active=\"true\"],\n.marker[data-active=\"true\"]:hover {\n --marker-stroke: 28px;\n opacity: 1;\n filter: drop-shadow(0 6px 16px var(--ick-marker-glow));\n}\n\n/* Every other marker recedes while one has its menu open. */\n.marker[data-dimmed=\"true\"],\n.marker[data-dimmed=\"true\"]:hover {\n opacity: var(--ick-recede);\n}\n\n/* ── Shimmer ──\n Mounted once per marker rather than added and removed on every crossing.\n Paused costs nothing, so it simply runs while hovered. `pathLength=\"1\"`\n normalises the geometry so the dash values work for any path. */\n\n.shimmer {\n fill: none;\n stroke: var(--ick-marker-sheen);\n stroke-width: 24px;\n stroke-linejoin: round;\n stroke-linecap: butt;\n mix-blend-mode: overlay;\n pointer-events: none;\n\n stroke-dasharray: 1;\n stroke-dashoffset: 1;\n opacity: 0;\n\n animation: shimmer-sweep 2s ease-out infinite;\n animation-play-state: paused;\n}\n\n.marker:hover + .shimmer,\n.marker[data-active=\"true\"] + .shimmer {\n animation-play-state: running;\n}\n\n.marker[data-active=\"true\"] + .shimmer {\n stroke-width: 28px;\n}\n\n@keyframes shimmer-sweep {\n 0% {\n stroke-dashoffset: 1;\n opacity: 0.8;\n }\n 80% {\n stroke-dashoffset: 0;\n opacity: 0;\n }\n 100% {\n stroke-dashoffset: 0;\n opacity: 0;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .marker {\n transition: none;\n }\n .shimmer {\n animation: none;\n }\n}\n\n/* ─────────────────────────────────────────────\n Tokens\n\n The text is split per word because the marker needs to know which words a\n stroke crossed, and because the focus effect blurs everything except them —\n something no range-level API can express. `::highlight()` styles colour and\n shadow but not `filter` or `opacity`, so per-word elements are the only way\n to get this look.\n\n What was wrong was not the splitting. It was giving each of the ~120 words\n its own animation engine: opening one menu wrote 350 inline styles, one word\n at a time, for identical values. Two attributes and a stylesheet do the same\n job, and the browser composites it.\n ───────────────────────────────────────────── */\n\n.tokens {\n --token-ease: var(--ick-ease);\n --token-ease-press: var(--ick-ease-overshoot);\n\n position: relative;\n display: block;\n z-index: var(--ick-z-base);\n}\n\n/* ── Markdown ───────────────────────────────────────────────────────────────\n An answer is markdown now, so the tree under `.tokens` has real structure in\n it. Every rule here is scoped to `.tokens` and reads a token, so a host that\n already styles `p` or `ul` does not fight with this and a brand can move all\n of it.\n\n The outer margins are stripped rather than reduced. A block's margin at the\n very top or bottom of an answer pushes the answer away from the bubble it\n belongs to, and margin collapse makes the amount unpredictable. */\n\n.tokens > :first-child { margin-top: 0; }\n.tokens > :last-child { margin-bottom: 0; }\n\n.tokens p,\n.tokens ul,\n.tokens ol,\n.tokens blockquote,\n.tokens pre,\n.tokens table {\n margin: 0 0 var(--ick-md-gap);\n}\n\n.tokens h1,\n.tokens h2,\n.tokens h3,\n.tokens h4,\n.tokens h5,\n.tokens h6 {\n margin: var(--ick-md-heading-space) 0 var(--ick-md-gap);\n font-weight: var(--ick-weight-semibold);\n line-height: 1.3;\n}\n\n/* A heading in a chat answer is a label on a section, not a page title. The\n scale is compressed hard for that reason: h1 at browser default would be\n twice the size of the answer around it. */\n.tokens h1 { font-size: 1.25em; }\n.tokens h2 { font-size: 1.15em; }\n.tokens h3 { font-size: 1.05em; }\n.tokens h4,\n.tokens h5,\n.tokens h6 { font-size: 1em; }\n\n.tokens ul,\n.tokens ol {\n padding-left: var(--ick-md-indent);\n}\n\n.tokens li {\n margin-bottom: var(--ick-md-item-gap);\n}\n\n.tokens li:last-child {\n margin-bottom: 0;\n}\n\n/* Nested lists close up: the outer item's own gap already separates them. */\n.tokens li > ul,\n.tokens li > ol {\n margin-top: var(--ick-md-item-gap);\n margin-bottom: 0;\n}\n\n.tokens blockquote {\n padding-left: var(--ick-md-quote-indent);\n border-left: 2px solid var(--ick-md-rule);\n color: var(--ick-ink-soft);\n}\n\n.tokens code {\n font-family: var(--ick-font-mono);\n font-size: 0.92em;\n background: var(--ick-md-code-fill);\n border-radius: var(--ick-radius-xs);\n /* Vertical padding only in the box, so an inline code run does not push its\n own line taller than its neighbours. */\n padding: 0.1em var(--ick-space-2);\n}\n\n.tokens pre {\n overflow-x: auto;\n padding: var(--ick-space-5);\n border-radius: var(--ick-radius-md);\n background: var(--ick-md-code-fill);\n}\n\n/* The block already has the fill; the run inside it should not have a second. */\n.tokens pre code {\n display: block;\n padding: 0;\n background: none;\n font-size: 0.92em;\n line-height: 1.5;\n white-space: pre;\n}\n\n.tokens a {\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 0.2em;\n text-decoration-color: var(--ick-md-rule);\n transition: text-decoration-color var(--ick-duration-fast) ease;\n}\n\n.tokens a:hover {\n text-decoration-color: currentColor;\n}\n\n.tokens hr {\n margin: var(--ick-md-gap) 0;\n border: 0;\n border-top: 1px solid var(--ick-md-rule);\n}\n\n.tokens img {\n max-width: 100%;\n height: auto;\n border-radius: var(--ick-radius-md);\n}\n\n/* A wide table scrolls inside itself rather than widening the answer. */\n.tokens table {\n display: block;\n overflow-x: auto;\n border-collapse: collapse;\n font-size: 0.95em;\n}\n\n.tokens th,\n.tokens td {\n padding: var(--ick-space-3) var(--ick-space-5);\n border: 1px solid var(--ick-md-rule);\n text-align: left;\n vertical-align: top;\n}\n\n.tokens th {\n font-weight: var(--ick-weight-semibold);\n}\n\n.token {\n /* `inline-block` so the press transform applies — transforms do nothing to a\n non-replaced inline box. Filters work either way. */\n display: inline-block;\n white-space: pre-wrap;\n /* No `color` here. A token sets no colour of its own at rest — it inherits\n the page's — so transitioning the property meant transitioning whatever\n the host changed it to. On a theme swap the ink inverts, and 320ms of\n travel between the two inks goes straight through the background it is\n sitting on: every word of every answer disappeared for nine frames.\n Measured at 11.96% ink coverage before the swap, 3.56% one frame after,\n back at 13.98% ten frames later. The bullets stayed, because `::marker`\n takes no transition from here, which is what gave it away. */\n transition:\n filter 320ms var(--token-ease),\n opacity 320ms var(--token-ease),\n text-shadow 320ms var(--token-ease),\n transform 240ms var(--token-ease-press);\n}\n\n/* Colour is animated only where a token actually sets one — under an open\n menu — so the property cannot be dragged around by a change to what the\n token inherits. Scoping it here rather than on `.token` is the whole fix\n for the theme swap: the rule does not match at rest, so a swap repaints in\n one frame.\n\n Colouring a marked word at rest was tried and reverted. A stroke is drawn\n by hand and does not stop at word boundaries — it entered \"Higgs\" three\n letters in — so a token painted marker-ink is legible on the part the\n stroke covers and invisible on the part it misses. Ink cannot follow a\n stroke; only the stroke can. */\n.tokens[data-focus=\"true\"] .token {\n transition:\n filter 320ms var(--token-ease),\n opacity 320ms var(--token-ease),\n color 320ms var(--token-ease),\n text-shadow 320ms var(--token-ease),\n transform 240ms var(--token-ease-press);\n}\n\n/* The spaces between words stay inline.\n As `inline-block` each one is an atomic box, and an atomic box cannot hang\n at the end of a line the way a space does — so a line breaking *before* a\n space put that space at the start of the next line and pushed the text one\n character to the right. Only the words need to be transformable. */\n.token[data-space] {\n display: inline;\n}\n\n/* One attribute on the container recedes the whole paragraph. */\n.tokens[data-focus=\"true\"] .token {\n filter: blur(3px);\n opacity: var(--ick-recede);\n}\n\n/* …except the words the open menu belongs to, which come forward. */\n.tokens[data-focus=\"true\"] .token[data-active=\"true\"] {\n filter: blur(0);\n opacity: 1;\n color: var(--ick-marker-active-ink);\n text-shadow: 0 1px 2px rgb(var(--ick-glass-rim-rgb) / 0.4);\n}\n\n.token[data-pressed=\"true\"] {\n transform: scale(0.96);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .token {\n transition: none;\n }\n}\n\n/* ─────────────────────────────────────────────\n The word cursor\n\n The paragraph is focusable, so it needs a focus ring of its own — and the\n cursor and its selection need to be visible to the sighted keyboard user\n driving them, not only to the screen reader.\n ───────────────────────────────────────────── */\n\n/* Layout that used to be an inline style block. The only part that still\n changes per render is whether a menu is open, which is an attribute. */\n.surface {\n position: relative;\n display: block; /* wraps the text tightly */\n /* Suppressed only while a marker is being drawn. It used to be off\n permanently in marker mode, which meant the answer could not be selected\n at all — not to highlight it, not even to copy it. */\n user-select: text;\n -webkit-user-select: text;\n /* `pan-y`, not `none`. `none` meant a finger anywhere on an answer could\n not scroll the page at all — the whole conversation became a dead zone on\n a phone. Marker strokes are horizontal, scrolling is vertical, so the two\n gestures separate cleanly: a sideways drag draws, a downward drag\n scrolls, and the browser cancels the stroke it took over. */\n touch-action: pan-y;\n z-index: var(--ick-z-base);\n}\n\n.surface[data-drawing] {\n user-select: none;\n -webkit-user-select: none;\n}\n\n/* Raised over its neighbours while its menu is open, so the menu is not\n clipped by the next answer. */\n.surface[data-menu] {\n z-index: var(--ick-z-sticky);\n}\n\n/* Widens the pointer's catch area without moving anything.\n\n Up and down only. It used to bleed 20px on all four sides, and the two\n sideways ones were the only part that could hurt: an answer that fills its\n column — which on a phone is the whole screen — put this 20px past the right\n edge and gave the **host's page** a horizontal scrollbar. A component that\n can make a page scroll sideways is a defect wherever it is embedded, not\n only on a phone.\n\n Nothing is lost. The slop that matters is vertical: lines are ~24px apart\n and a stroke is aimed between them, while horizontally the text already runs\n to the surface's own edge, so a drag starting further out starts in the\n margin and snaps to the first word regardless. */\n.hitbox {\n position: absolute;\n inset-block: -20px;\n inset-inline: 0;\n z-index: 0;\n}\n\n.canvas {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n mix-blend-mode: var(--ick-marker-blend);\n z-index: var(--ick-z-raised);\n overflow: visible; /* lets a stroke run slightly outside the box */\n}\n\n.menu {\n position: absolute;\n transform: translate(-50%, calc(-100% - 16px));\n z-index: var(--ick-z-menu);\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: var(--ick-space-4);\n pointer-events: auto;\n}\n\n.surface:focus {\n outline: none; /* replaced by the focus-visible ring below */\n}\n\n.surface:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: 6px;\n border-radius: 6px;\n}\n\n/* Deliberately not the marker yellow. This is a pending selection, not a\n highlight — they should not look like the same thing. */\n.token[data-kbd-selected] {\n background: var(--ick-marker-tint);\n border-radius: 2px;\n}\n\n.token[data-kbd-cursor] {\n outline: 2px solid rgb(var(--ick-ink-rgb) / 0.55);\n outline-offset: 1px;\n border-radius: 2px;\n}\n\n/* Read on focus through aria-describedby, shown to nobody. */\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n overflow: hidden;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n white-space: nowrap;\n}\n","\"use client\";\n\nimport React, { useState, useRef, useEffect, useMemo, useCallback, useId, createElement, type ReactNode } from \"react\";\nimport { motion, AnimatePresence, MotionConfig } from \"motion/react\";\nimport { Button } from \"../Button/Button\";\nimport { CodeBlock } from \"../CodeBlock/CodeBlock\";\nimport { parseMarkdown, type MdNode } from \"../markdown/parseMarkdown\";\nimport { InlineCitation } from \"../InlineCitation/InlineCitation\";\nimport type { Source } from \"../Sources/Sources\";\nimport styles from \"./TextHighlighter.module.css\";\nimport { MessageCircle, Trash2 } from \"lucide-react\";\n\nconst SKEW_ANGLE = -20;\nconst TAN_ANGLE = Math.tan((SKEW_ANGLE * Math.PI) / 180);\n\ninterface PathData {\n id: string;\n points: { x: number; y: number }[];\n highlightedIndices: Set<number>;\n}\n\ninterface SelectionHighlight {\n id: string;\n rects: { x: number; y: number; w: number; h: number }[]; // container-relative\n text: string;\n highlightedIndices: Set<number>;\n}\n\nexport interface TextHighlighterProps {\n text: string;\n selectionMode?: \"marker\" | \"precise\";\n onHighlightComplete?: (highlightedText: string) => void;\n onReplyInThread?: (text: string, rect: DOMRect) => void;\n /**\n * What `[^1]` in the text points at, in the order the answer cites them.\n *\n * Leave it out and a marker still draws — as a number with nothing behind\n * it, which is what it is until the list arrives.\n */\n sources?: Source[];\n onSelectSource?: (index: number, source?: Source) => void;\n}\n\n/* Pure geometry helpers — hoisted so memoised values can depend on them. */\n\nconst makePathString = (points: { x: number; y: number }[]) => {\n if (points.length === 0) return \"\";\n const start = points[0];\n let d = `M ${start.x} ${start.y}`;\n for (let i = 1; i < points.length; i++) {\n d += ` L ${points[i].x} ${points[i].y}`;\n }\n return d;\n};\n// Menu position: horizontal center + top edge of the highlight (in container coords).\n// Points store skew-corrected x, so reapply the skew (visualX = x + TAN_ANGLE * y) to get the rendered x.\nconst getMenuPosition = (points: { x: number; y: number }[]) => {\n let minX = Infinity, maxX = -Infinity, minY = Infinity;\n points.forEach((p) => {\n const visualX = p.x + TAN_ANGLE * p.y;\n if (visualX < minX) minX = visualX;\n if (visualX > maxX) maxX = visualX;\n if (p.y < minY) minY = p.y;\n });\n return { x: (minX + maxX) / 2, y: minY };\n};\n// Same idea for precise-mode rect highlights (container-relative rects).\nconst getRectsMenuPosition = (rects: { x: number; y: number; w: number; h: number }[]) => {\n let minX = Infinity, maxX = -Infinity, minY = Infinity;\n rects.forEach((r) => {\n if (r.x < minX) minX = r.x;\n if (r.x + r.w > maxX) maxX = r.x + r.w;\n if (r.y < minY) minY = r.y;\n });\n return { x: (minX + maxX) / 2, y: minY };\n};\n/**\n * The words a set of token indices covers, gaps included.\n *\n * A fast drag skips tokens — the pointer lands on \"the\" and \"matter\" without\n * ever being over the space between them — so the run is filled in from the\n * first index to the last rather than joining only what was touched.\n */\nconst textFromIndices = (indices: Set<number>, tokens: string[]) => {\n if (indices.size === 0) return \"\";\n const sorted = [...indices].sort((a, b) => a - b);\n let out = \"\";\n for (let i = sorted[0]; i <= sorted[sorted.length - 1]; i++) out += tokens[i];\n return out.trim();\n};\n\n/** Long passages make unusable labels; a screen reader reads every character. */\nconst shorten = (text: string, max = 60) =>\n text.length > max ? `${text.slice(0, max).trimEnd()}…` : text;\n\nconst getSelectionPathString = (rects: { x: number; y: number; w: number; h: number }[]) => {\n let d = \"\";\n rects.forEach(r => {\n const centerY = r.y + r.h / 2;\n const startX = r.x - centerY * TAN_ANGLE;\n const endX = (r.x + r.w) - centerY * TAN_ANGLE;\n d += `M ${startX} ${centerY} L ${endX} ${centerY} `;\n });\n return d.trim();\n};\n\nexport function TextHighlighter({\n text,\n selectionMode = \"marker\",\n onHighlightComplete,\n onReplyInThread,\n sources,\n onSelectSource,\n}: TextHighlighterProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [paths, setPaths] = useState<PathData[]>([]);\n const [selections, setSelections] = useState<SelectionHighlight[]>([]);\n const [currentPath, setCurrentPath] = useState<PathData | null>(null);\n const [isDrawing, setIsDrawing] = useState(false);\n // `viaKeyboard` decides whether focus moves into the menu when it opens.\n // Pulling focus out from under someone who just drew with a mouse would be\n // an interruption; for someone who arrived by keyboard it is the only way in.\n const [menuAnchor, setMenuAnchor] = useState<{\n x: number | string;\n y: number;\n pathId: string;\n kind: \"path\" | \"selection\";\n viaKeyboard?: boolean;\n } | null>(null);\n /** Which action the menu's roving tabindex is currently on. */\n const [menuIndex, setMenuIndex] = useState(0);\n const menuRef = useRef<HTMLDivElement>(null);\n /** Where focus goes when a keyboard-opened menu is dismissed. */\n const returnFocusRef = useRef<HTMLElement | null>(null);\n const [pressedPathId, setPressedPathId] = useState<string | null>(null);\n // Which highlight the keyboard is currently on. Drives the same emphasis the\n // open menu does, so tabbing through them is visible on the page and not\n // only to a screen reader.\n const [focusedMarkerId, setFocusedMarkerId] = useState<string | null>(null);\n\n type MenuAnchor = NonNullable<typeof menuAnchor>;\n\n /** Open the menu, remembering how it was opened and what focus came from. */\n const openMenu = useCallback((anchor: MenuAnchor) => {\n if (anchor.viaKeyboard) {\n returnFocusRef.current = document.activeElement as HTMLElement | null;\n }\n setMenuIndex(0);\n setMenuAnchor(anchor);\n }, []);\n\n /**\n * Close the menu, and decide what focus does next.\n *\n * \"restore\" hands it back to whatever opened the menu — a highlight's button,\n * or the paragraph. \"container\" is for when that thing is about to stop\n * existing, as when the highlight itself is deleted. \"none\" is for the\n * pointer, where moving focus at all would be an interruption.\n */\n const dismissMenu = useCallback((focus: \"restore\" | \"container\" | \"none\" = \"none\") => {\n const target = returnFocusRef.current;\n returnFocusRef.current = null;\n setMenuAnchor(null);\n if (focus === \"none\") return;\n if (focus === \"restore\" && target?.isConnected) target.focus();\n else containerRef.current?.focus();\n }, []);\n\n useEffect(() => {\n const handleGlobalPointerDown = (e: PointerEvent) => {\n if (menuAnchor && containerRef.current && !containerRef.current.contains(e.target as Node)) {\n dismissMenu();\n }\n };\n window.addEventListener(\"pointerdown\", handleGlobalPointerDown);\n return () => window.removeEventListener(\"pointerdown\", handleGlobalPointerDown);\n }, [menuAnchor, dismissMenu]);\n\n /**\n * Markdown, parsed into a tree of elements over a flat array of tokens.\n *\n * The flat array is what everything else here depends on — hit-testing reads\n * an index off the span under the pointer, the keyboard cursor walks the\n * indices, a highlight's text is a run of them joined back together. Keeping\n * it flat is what lets the words live inside `<strong>` and `<li>` without a\n * single one of those mechanisms knowing.\n *\n * Keyed on the text, which during streaming is a new string every frame. See\n * the render below for what that costs.\n */\n const doc = useMemo(() => parseMarkdown(text), [text]);\n const tokens = doc.tokens;\n\n\n\n\n // Live values for the stable document listener (avoids re-registering on every\n // render). Written after commit — the listener only reads them from event\n // handlers, which run well after paint.\n const selectionModeRef = useRef(selectionMode);\n const selectionsRef = useRef(selections);\n const onHighlightCompleteRef = useRef(onHighlightComplete);\n const menuAnchorRef = useRef(menuAnchor);\n\n useEffect(() => {\n selectionModeRef.current = selectionMode;\n selectionsRef.current = selections;\n onHighlightCompleteRef.current = onHighlightComplete;\n menuAnchorRef.current = menuAnchor;\n });\n\n\n // Rebuilding these path strings on every hover was a large part of the cost.\n const allMarkers = useMemo(\n () => [\n ...paths.map((p) => ({\n id: p.id,\n d: makePathString(p.points),\n kind: \"path\" as const,\n item: p,\n text: textFromIndices(p.highlightedIndices, tokens),\n })),\n ...selections.map((sel) => ({\n id: sel.id,\n d: getSelectionPathString(sel.rects),\n kind: \"selection\" as const,\n item: sel,\n text: sel.text,\n })),\n ],\n [paths, selections, tokens]\n );\n\n type Marker = (typeof allMarkers)[number];\n\n /** Where the menu sits for a marker, in container coordinates. */\n const openMenuFor = (marker: Marker, viaKeyboard = false) => {\n const pos =\n marker.kind === \"path\"\n ? getMenuPosition((marker.item as PathData).points)\n : getRectsMenuPosition((marker.item as SelectionHighlight).rects);\n openMenu({ x: pos.x, y: pos.y, pathId: marker.id, kind: marker.kind, viaKeyboard });\n };\n\n /**\n * Turn a DOM Range into a committed highlight.\n *\n * Three routes arrive at a range — a drag in precise mode, a native\n * selection finished with the keyboard, and the word cursor below. They\n * differ only in how the range is arrived at, so everything after it is\n * shared.\n */\n const commitRange = useCallback((range: Range, rawText: string, viaKeyboard = false) => {\n const container = containerRef.current;\n const text = rawText.trim();\n if (!container || !text) return;\n\n const highlightedIndices = new Set<number>();\n container.querySelectorAll(\"span[data-index]\").forEach((span) => {\n if (range.intersectsNode(span)) {\n highlightedIndices.add(parseInt(span.getAttribute(\"data-index\")!, 10));\n }\n });\n\n // Reported before the geometry, and regardless of it. Somebody highlighted\n // this text; whether a marker can be drawn over it is a separate question,\n // and one the host app has no stake in.\n onHighlightCompleteRef.current?.(text);\n\n const cRect = container.getBoundingClientRect();\n const rawRects = Array.from(range.getClientRects())\n .map((r) => ({ x: r.left - cRect.left, y: r.top - cRect.top, w: r.width, h: r.height }))\n .filter((r) => r.w > 0 && r.h > 0);\n if (rawRects.length === 0) return;\n\n // The browser returns a separate rect for each text node. Merge them by\n // line, so the marker is one continuous stroke per line rather than one\n // stroke per word.\n const lines: { [y: string]: { x: number; y: number; w: number; h: number }[] } = {};\n rawRects.forEach((r) => {\n // Group rects that share roughly the same Y coordinate (within 5px)\n const lineY = Object.keys(lines).find((yStr) => Math.abs(parseFloat(yStr) - r.y) < 5);\n if (lineY) lines[lineY].push(r);\n else lines[r.y.toString()] = [r];\n });\n\n const rects = Object.values(lines).map((lineRects) => {\n const minX = Math.min(...lineRects.map((r) => r.x));\n const maxX = Math.max(...lineRects.map((r) => r.x + r.w));\n const minY = Math.min(...lineRects.map((r) => r.y));\n const maxH = Math.max(...lineRects.map((r) => r.h));\n return { x: minX, y: minY, w: maxX - minX, h: maxH };\n });\n\n const id = Date.now().toString();\n setSelections((prev) => [...prev, { id, rects, text, highlightedIndices }]);\n const pos = getRectsMenuPosition(rects);\n openMenu({ x: pos.x, y: pos.y, pathId: id, kind: \"selection\", viaKeyboard });\n }, [openMenu]);\n\n /**\n * A selection the reader made themselves.\n *\n * The pointer half only applies in precise mode, where native selection owns\n * the pointer. The keyboard half applies in both modes: dragging a marker\n * has no keyboard equivalent, so refusing keyboard selection in marker mode\n * would leave that mode with no way in at all.\n */\n useEffect(() => {\n const capture = (fromKeyboard: boolean) => {\n if (menuAnchorRef.current) return;\n const container = containerRef.current;\n if (!container) return;\n if (!fromKeyboard && selectionModeRef.current !== \"precise\") return;\n\n const sel = window.getSelection();\n if (!sel || sel.isCollapsed || sel.rangeCount === 0 || !sel.toString().trim()) return;\n\n const range = sel.getRangeAt(0);\n if (!container.contains(range.commonAncestorContainer)) return; // not this paragraph\n\n commitRange(range, sel.toString(), fromKeyboard);\n // Clear the native selection on the next frame so it doesn't linger as blue.\n requestAnimationFrame(() => window.getSelection()?.removeAllRanges());\n };\n\n const onMouseUp = () => capture(false);\n const onKeyUp = (e: KeyboardEvent) => {\n // Only keys that can have changed a selection. Anything else would\n // re-commit the same range on every keystroke.\n const selectAll = (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === \"a\";\n if (!e.shiftKey && !selectAll) return;\n capture(true);\n };\n\n document.addEventListener(\"mouseup\", onMouseUp);\n document.addEventListener(\"keyup\", onKeyUp);\n return () => {\n document.removeEventListener(\"mouseup\", onMouseUp);\n document.removeEventListener(\"keyup\", onKeyUp);\n };\n }, [commitRange]);\n\n // Close any open menu when switching selection mode.\n const [lastSelectionMode, setLastSelectionMode] = useState(selectionMode);\n if (lastSelectionMode !== selectionMode) {\n setLastSelectionMode(selectionMode);\n setMenuAnchor(null);\n }\n\n /**\n * The word cursor — how this works without a mouse.\n *\n * Dragging a marker has no keyboard equivalent, and native selection is not\n * a way in either: browsers give no caret to non-editable text unless caret\n * browsing is switched on, which almost nobody has. So the paragraph owns a\n * cursor of its own, moving over the word tokens that are already there for\n * hit-testing.\n *\n * Left and right move it, shift extends, Enter commits the same kind of\n * highlight a drag would have made. Up and down are deliberately left alone:\n * moving by line would need to know where the lines break, and guessing that\n * would be worse than letting the page scroll.\n */\n const wordIndices = useMemo(\n () => tokens.map((token, i) => (token.trim() ? i : -1)).filter((i) => i >= 0),\n [tokens]\n );\n const [caret, setCaret] = useState<number | null>(null);\n const [anchorIndex, setAnchorIndex] = useState<number | null>(null);\n const hintId = useId();\n\n const cursorRange = useMemo(() => {\n if (caret === null || anchorIndex === null) return null;\n return { from: Math.min(anchorIndex, caret), to: Math.max(anchorIndex, caret) };\n }, [caret, anchorIndex]);\n\n const clearCursor = () => {\n setCaret(null);\n setAnchorIndex(null);\n };\n\n const commitCursor = () => {\n const container = containerRef.current;\n if (!container || !cursorRange) return;\n const first = container.querySelector(`span[data-index=\"${cursorRange.from}\"]`);\n const last = container.querySelector(`span[data-index=\"${cursorRange.to}\"]`);\n if (!first || !last) return;\n\n const range = document.createRange();\n range.setStartBefore(first);\n range.setEndAfter(last);\n\n let text = \"\";\n for (let i = cursorRange.from; i <= cursorRange.to; i++) text += tokens[i];\n commitRange(range, text, true);\n clearCursor();\n };\n\n const menuItems = () => [\n ...(menuRef.current?.querySelectorAll<HTMLElement>('[role=\"menuitem\"]') ?? []),\n ];\n\n /**\n * Focus follows the roving tabindex — but only once focus is already in the\n * menu, or the menu was opened by someone who has no other way in. A menu\n * that grabs focus from a reader who opened it with a mouse is a menu that\n * interrupts.\n */\n useEffect(() => {\n if (!menuAnchor) return;\n const items = menuItems();\n if (items.length === 0) return;\n const inside = menuRef.current?.contains(document.activeElement);\n if (menuAnchor.viaKeyboard || inside) items[Math.min(menuIndex, items.length - 1)]?.focus();\n }, [menuAnchor, menuIndex]);\n\n const handleMenuKeyDown = (e: React.KeyboardEvent) => {\n const count = menuItems().length;\n if (count === 0) return;\n\n const step = (delta: number) => {\n e.preventDefault();\n // Stopped here so the paragraph's own arrow keys do not also move the\n // word cursor behind the open menu.\n e.stopPropagation();\n setMenuIndex((i) => (i + delta + count) % count);\n };\n\n switch (e.key) {\n case \"ArrowRight\":\n case \"ArrowDown\":\n return step(1);\n case \"ArrowLeft\":\n case \"ArrowUp\":\n return step(-1);\n case \"Home\":\n e.preventDefault();\n return setMenuIndex(0);\n case \"End\":\n e.preventDefault();\n return setMenuIndex(count - 1);\n default:\n return;\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n // Something inside has focus — a highlight's button, or a menu action.\n // Only Escape is still ours; the rest belongs to whatever is focused.\n if (e.target !== e.currentTarget && e.key !== \"Escape\") return;\n if (wordIndices.length === 0) return;\n\n const move = (direction: 1 | -1, extend: boolean) => {\n e.preventDefault();\n const at = caret === null ? -1 : wordIndices.indexOf(caret);\n const next =\n at === -1\n ? wordIndices[direction === 1 ? 0 : wordIndices.length - 1]\n : wordIndices[Math.min(wordIndices.length - 1, Math.max(0, at + direction))];\n setCaret(next);\n // Without shift the selection collapses onto the cursor, which is what\n // makes an unextended move feel like moving rather than selecting.\n if (!extend || anchorIndex === null) setAnchorIndex(next);\n };\n\n const jump = (to: number) => {\n e.preventDefault();\n setCaret(to);\n if (!e.shiftKey || anchorIndex === null) setAnchorIndex(to);\n };\n\n switch (e.key) {\n case \"ArrowRight\":\n return move(1, e.shiftKey);\n case \"ArrowLeft\":\n return move(-1, e.shiftKey);\n case \"Home\":\n return jump(wordIndices[0]);\n case \"End\":\n return jump(wordIndices[wordIndices.length - 1]);\n case \"Enter\":\n case \" \":\n if (!cursorRange) return;\n e.preventDefault();\n return commitCursor();\n case \"Escape\":\n if (!menuAnchor && caret === null) return; // let the host app have it\n e.preventDefault();\n dismissMenu(\"restore\");\n return clearCursor();\n default:\n return;\n }\n };\n\n const checkHighlight = (clientX: number, clientY: number, indices: Set<number>) => {\n const el = document.elementFromPoint(clientX, clientY);\n if (el && el.hasAttribute(\"data-index\")) {\n const idx = parseInt(el.getAttribute(\"data-index\")!, 10);\n indices.add(idx);\n }\n };\n\n const handlePointerDown = (e: React.PointerEvent) => {\n if (menuAnchor) {\n dismissMenu();\n return;\n }\n\n if (selectionMode === \"precise\") return; // native selection handles this mode\n // Only left click / main touch\n if (e.button !== 0 && e.pointerType === \"mouse\") return;\n\n // Text is selectable now, so the drag would otherwise start a native\n // selection underneath the marker. Preventing the default here suppresses\n // the compatibility mouse events that would begin one.\n e.preventDefault();\n\n setIsDrawing(true);\n const target = e.currentTarget as HTMLDivElement;\n target.setPointerCapture(e.pointerId);\n\n const rect = target.getBoundingClientRect();\n const mouseX = e.clientX - rect.left;\n const mouseY = e.clientY - rect.top;\n\n const correctedX = mouseX - mouseY * TAN_ANGLE;\n const newPath: PathData = {\n id: Date.now().toString(),\n points: [{ x: correctedX, y: mouseY }],\n highlightedIndices: new Set<number>(),\n };\n setCurrentPath(newPath);\n dismissMenu(); // Hide menu when starting a new highlight\n checkHighlight(e.clientX, e.clientY, newPath.highlightedIndices);\n };\n\n const handlePointerMove = (e: React.PointerEvent) => {\n if (selectionMode === \"precise\") return;\n if (!currentPath) return;\n\n const rect = containerRef.current!.getBoundingClientRect();\n const mouseX = e.clientX - rect.left;\n const mouseY = e.clientY - rect.top;\n const correctedX = mouseX - mouseY * TAN_ANGLE;\n\n setCurrentPath((prev) => {\n if (!prev) return prev;\n const newIndices = new Set(prev.highlightedIndices);\n checkHighlight(e.clientX, e.clientY, newIndices);\n return {\n ...prev,\n points: [...prev.points, { x: correctedX, y: mouseY }],\n highlightedIndices: newIndices,\n };\n });\n };\n\n const handlePointerUp = (e: React.PointerEvent) => {\n if (selectionMode === \"precise\") return;\n setIsDrawing(false);\n if (!currentPath) return;\n const target = e.currentTarget as HTMLDivElement;\n target.releasePointerCapture(e.pointerId);\n\n setPaths((prev) => [...prev, currentPath]);\n \n if (currentPath.highlightedIndices.size > 0) {\n const pos = getMenuPosition(currentPath.points);\n openMenu({ x: pos.x, y: pos.y, pathId: currentPath.id, kind: \"path\" });\n }\n\n if (onHighlightComplete && currentPath.highlightedIndices.size > 0) {\n onHighlightComplete(textFromIndices(currentPath.highlightedIndices, tokens));\n }\n \n setCurrentPath(null);\n };\n\n /**\n * The stroke was interrupted rather than finished — almost always because\n * the browser decided the gesture was a scroll and took the pointer away.\n *\n * Discarded, not committed. Treating a cancellation like a release meant\n * that on a phone, every scroll that happened to begin on an answer left a\n * highlight behind on whatever word the finger landed on.\n */\n const handlePointerCancel = (e: React.PointerEvent) => {\n if (selectionMode === \"precise\") return;\n setIsDrawing(false);\n if (!currentPath) return;\n (e.currentTarget as HTMLDivElement).releasePointerCapture(e.pointerId);\n setCurrentPath(null);\n };\n\n const getHighlightedText = (pathId: string) => {\n const pathData = paths.find((p) => p.id === pathId);\n return pathData ? textFromIndices(pathData.highlightedIndices, tokens) : \"\";\n };\n\n const removeHighlight = (id: string) => {\n const viaKeyboard = menuAnchor?.viaKeyboard;\n if (menuAnchor?.kind === \"selection\") {\n setSelections(prev => prev.filter(s => s.id !== id));\n } else {\n setPaths(prev => prev.filter(p => p.id !== id));\n }\n // The control focus would return to is the one being deleted, so focus\n // goes back to the paragraph instead of nowhere.\n dismissMenu(viaKeyboard ? \"container\" : \"none\");\n };\n\n const replyInThread = () => {\n // Focus goes back to the highlight *before* the host app opens its thread,\n // so whatever the thread captures as its return target is the highlight\n // and not a menu item that is about to stop existing.\n const viaKeyboard = menuAnchor?.viaKeyboard;\n if (menuAnchor && onReplyInThread) {\n if (menuAnchor.kind === \"selection\") {\n const sel = selections.find(s => s.id === menuAnchor.pathId);\n const container = containerRef.current;\n if (sel && container) {\n // Build a viewport-space union rect from the container-relative rects.\n const cRect = container.getBoundingClientRect();\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n sel.rects.forEach(r => {\n minX = Math.min(minX, r.x); minY = Math.min(minY, r.y);\n maxX = Math.max(maxX, r.x + r.w); maxY = Math.max(maxY, r.y + r.h);\n });\n const rect = new DOMRect(cRect.left + minX, cRect.top + minY, maxX - minX, maxY - minY);\n onReplyInThread(sel.text, rect);\n }\n } else {\n const text = getHighlightedText(menuAnchor.pathId);\n const el = document.getElementById(`highlight-path-${menuAnchor.pathId}`);\n if (el) {\n onReplyInThread(text, el.getBoundingClientRect());\n }\n }\n }\n dismissMenu(viaKeyboard ? \"restore\" : \"none\");\n };\n\n /**\n * One token: a word or the whitespace beside it, addressed by index.\n *\n * Unchanged from before markdown, and that is the point — the attributes\n * here are the entire contract with hit-testing, the keyboard cursor and the\n * dimming. Where the span sits in the tree is not part of it.\n */\n const renderToken = (index: number): ReactNode => {\n const token = tokens[index];\n\n // Both freeform (path) and precise (selection) highlights dim the surrounding tokens.\n let isPartOfActiveHighlight = false;\n if (menuAnchor) {\n const active =\n menuAnchor.kind === \"path\"\n ? paths.find((p) => p.id === menuAnchor.pathId)\n : selections.find((sel) => sel.id === menuAnchor.pathId);\n isPartOfActiveHighlight = active?.highlightedIndices?.has(index) ?? false;\n }\n\n let isPartOfPressed = false;\n if (pressedPathId) {\n const pressed =\n paths.find((p) => p.id === pressedPathId) ??\n selections.find((sel) => sel.id === pressedPathId);\n isPartOfPressed = pressed?.highlightedIndices?.has(index) ?? false;\n }\n\n return (\n <span\n key={index}\n data-index={index}\n className={styles.token}\n data-space={token.trim() ? undefined : true}\n data-active={isPartOfActiveHighlight || undefined}\n data-pressed={isPartOfPressed || undefined}\n data-kbd-cursor={caret === index || undefined}\n data-kbd-selected={\n (cursorRange && index >= cursorRange.from && index <= cursorRange.to) || undefined\n }\n >\n {token}\n </span>\n );\n };\n\n /** Walk the parsed tree, dropping a token span at every text position. */\n const renderNodes = (nodes: MdNode[]): ReactNode =>\n nodes.map((node, i) => {\n if (node.type === \"token\") return renderToken(node.index);\n if (node.type === \"code\") {\n // Not tokenised, so not markable. A fenced block is preformatted —\n // splitting it on whitespace would destroy the one thing it is for.\n // What it gets instead is a copy button, which is what people want\n // from code anyway.\n return <CodeBlock key={`c${i}`} code={node.value} lang={node.lang} />;\n }\n /* `[^1]` in the answer, resolved against the turn's sources. The number\n is what the model wrote and the list is 1-based, which is why this\n subtracts one and does not renumber: a marker pointing at a source\n that has not arrived yet still draws, and starts working the moment\n the list does. */\n if (node.type === \"el\" && node.tag === \"cite\") {\n const at = Number(node.props?.index ?? 0);\n return (\n <InlineCitation\n key={`q${i}`}\n index={at}\n source={sources?.[at - 1]}\n onSelect={onSelectSource ? (n, source) => onSelectSource(n, source) : undefined}\n />\n );\n }\n return createElement(\n node.tag,\n { key: `e${i}`, ...node.props },\n // Void elements take no children at all, not even an empty array.\n node.children.length > 0 ? renderNodes(node.children) : undefined\n );\n });\n\n return (\n // See the note in ChatInput: the kit sets this itself rather than relying\n // on the host app to.\n <MotionConfig reducedMotion=\"user\">\n <motion.div\n ref={containerRef}\n data-cursor={selectionMode === \"precise\" ? \"text\" : \"marker\"}\n data-cursor-active={isDrawing ? \"true\" : \"false\"}\n animate={{\n scale: 1,\n y: 0,\n }}\n transition={{ type: \"spring\", stiffness: 400, damping: 30 }}\n data-drawing={isDrawing || undefined}\n data-menu={!!menuAnchor || undefined}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerCancel}\n className={styles.surface}\n tabIndex={0}\n onKeyDown={handleKeyDown}\n onBlur={clearCursor}\n aria-describedby={hintId}\n >\n {/* Referenced by aria-describedby, so it is read on focus and nowhere\n else — a directly referenced node is used even when hidden. Without\n this, the keys exist but nothing tells anyone they do. */}\n <span id={hintId} className={styles.srOnly} aria-hidden=\"true\">\n Left and right arrow keys move by word. Hold shift to select. Enter\n highlights the selection, Escape clears it.\n </span>\n\n <div className={styles.hitbox} />\n\n {/* Underlying text.\n\n A `div`, not a `span`: markdown brings paragraphs, lists and headings,\n and a span may not contain them. The class already made it a block. */}\n <div className={styles.tokens} data-focus={!!menuAnchor || undefined}>\n {renderNodes(doc.nodes)}\n </div>\n\n {/* SVG Canvas overlay */}\n <svg className={styles.canvas}>\n <g transform={`skewX(${SKEW_ANGLE})`}>\n {allMarkers.map((marker) => (\n <React.Fragment key={marker.id}>\n <path\n id={`highlight-${marker.kind}-${marker.id}`}\n className={styles.marker}\n d={marker.d}\n data-cursor=\"pointer\"\n data-pressed={pressedPathId === marker.id || undefined}\n data-active={\n menuAnchor?.pathId === marker.id || focusedMarkerId === marker.id || undefined\n }\n data-dimmed={(menuAnchor && menuAnchor.pathId !== marker.id) || undefined}\n style={{ pointerEvents: isDrawing ? \"none\" : \"stroke\" }}\n onPointerDown={(e) => {\n if (isDrawing) return;\n e.stopPropagation();\n setPressedPathId(marker.id);\n }}\n onPointerUp={(e) => {\n if (pressedPathId === marker.id) {\n e.stopPropagation();\n openMenuFor(marker);\n setPressedPathId(null);\n }\n }}\n onPointerLeave={() => setPressedPathId(null)}\n onPointerCancel={() => setPressedPathId(null)}\n />\n {/* Sibling of the marker so CSS can drive it off :hover. Mounted\n once and paused, rather than added and removed per crossing. */}\n <path\n className={styles.shimmer}\n d={marker.d}\n pathLength={1}\n data-active={\n menuAnchor?.pathId === marker.id || focusedMarkerId === marker.id || undefined\n }\n />\n </React.Fragment>\n ))}\n {currentPath && (\n <path className={styles.marker} d={makePathString(currentPath.points)} />\n )}\n </g>\n </svg>\n\n {/* Every committed highlight, as something the keyboard can reach.\n Real buttons rather than focusable SVG paths — focus on SVG elements\n behaves differently in every browser and is not a fight worth having.\n They are invisible, but focusing one lights up the marker it belongs\n to, so tabbing through them is visible on the page and not only to a\n screen reader. */}\n {allMarkers.length > 0 && (\n <div\n role=\"group\"\n aria-label={`${allMarkers.length} highlight${allMarkers.length === 1 ? \"\" : \"s\"}`}\n >\n {allMarkers.map((marker) => (\n <button\n key={marker.id}\n type=\"button\"\n className={styles.srOnly}\n onPointerDown={(e) => e.stopPropagation()} // must not start a new drawing\n onFocus={() => setFocusedMarkerId(marker.id)}\n onBlur={() => setFocusedMarkerId(null)}\n onClick={(e) => {\n e.stopPropagation();\n // These are invisible, so they can only have been activated\n // from the keyboard.\n openMenuFor(marker, true);\n }}\n >\n {`Highlight: ${shorten(marker.text)}`}\n </button>\n ))}\n </div>\n )}\n\n {/* Floating Action Menu */}\n <AnimatePresence>\n {menuAnchor && (\n <motion.div\n ref={menuRef}\n role=\"menu\"\n aria-label=\"Highlight actions\"\n onKeyDown={handleMenuKeyDown}\n onBlur={(e) => {\n // Tab out and the menu goes with you. Leaving one open behind\n // the focus ring is how a menu becomes a thing to get lost in.\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) dismissMenu();\n }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1, transition: { staggerChildren: 0.05 } }}\n exit={{ opacity: 0, transition: { staggerChildren: 0.05, staggerDirection: -1 } }}\n className={styles.menu}\n // Where the menu sits is the one thing that cannot be a class.\n style={{ left: menuAnchor.x, top: menuAnchor.y }}\n onPointerDown={(e) => e.stopPropagation()} // Sprečava da klik na dugme započne novo crtananje\n >\n <motion.div\n initial={{ opacity: 0, y: 10, scale: 0.9 }}\n animate={{ opacity: 1, y: 0, scale: 1, transition: { type: \"spring\", stiffness: 400, damping: 25 } }}\n exit={{ opacity: 0, scale: 0.9, transition: { duration: 0.1 } }}\n >\n <Button\n variant=\"primary\"\n role=\"menuitem\"\n tabIndex={menuIndex === 0 ? 0 : -1}\n icon={<MessageCircle size={14} />}\n onClick={(e) => {\n e.stopPropagation();\n replyInThread();\n }}\n title=\"Reply in thread\"\n >\n Reply in thread\n </Button>\n </motion.div>\n\n <motion.div\n initial={{ opacity: 0, y: 10, scale: 0.9 }}\n animate={{ opacity: 1, y: 0, scale: 1, transition: { type: \"spring\", stiffness: 400, damping: 25 } }}\n exit={{ opacity: 0, scale: 0.9, transition: { duration: 0.1 } }}\n >\n <Button\n variant=\"primary\"\n role=\"menuitem\"\n tabIndex={menuIndex === 1 ? 0 : -1}\n icon={<Trash2 size={14} />}\n onClick={(e) => {\n e.stopPropagation();\n removeHighlight(menuAnchor.pathId);\n }}\n title=\"Remove highlight\"\n aria-label=\"Remove highlight\"\n />\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>\n </motion.div>\n </MotionConfig>\n );\n}\n","/* ─────────────────────────────────────────────\n ChatTurnRow\n\n The question sits right, the answer sits left and full width. That asymmetry\n is the only layout opinion in here; everything else is measurement, and\n every measurement is a token.\n ───────────────────────────────────────────── */\n\n.turn {\n display: flex;\n flex-direction: column;\n gap: var(--ick-turn-gap);\n /* Room above a turn scrolled to by id, so it does not land flush against\n whatever is fixed at the top of the page. */\n scroll-margin-top: var(--ick-space-6);\n}\n\n.question {\n display: flex;\n justify-content: flex-end;\n}\n\n/* The opening composer, which is not a message yet. See `questionAlign`. */\n.question[data-align=\"stretch\"] {\n justify-content: stretch;\n}\n\n/* Everything the answer is made of: the reasoning, the tool calls, the plan,\n the question, and the prose. Closer together than the row's own gap, which\n separates a whole turn from the next one. */\n.body {\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-7);\n}\n\n/* Nothing arrived yet, so it should take up no room and add no gap. */\n.body:empty {\n display: none;\n}\n\n.answer {\n margin: 0;\n /* On the answer's column, at both ends. Everything that draws in a turn\n starts here; only the boxes bleed wider. See `--ick-answer-column`.\n\n The right margin was missing, and on a wide screen nothing showed it: the\n measure below runs out first, so the line ends where the measure says and\n the column never comes into it. On a phone the measure is wider than the\n screen, so the prose ran to the container's edge — measured at 390px, the\n text ended at 374 while the card's own words ended at 342, and the answer\n looked ragged against the card above it. Mirroring the column puts it back\n on 342, which is the same 48 in from the other side. */\n margin-left: var(--ick-answer-column);\n margin-right: var(--ick-answer-column);\n font-family: var(--ick-font-sans);\n font-size: var(--ick-answer-size);\n line-height: var(--ick-answer-leading);\n letter-spacing: var(--ick-answer-tracking);\n color: var(--ick-ink);\n /* Measured in characters, not pixels: the comfortable line length is a\n count of letters, so it should follow the font rather than fight it. */\n max-width: var(--ick-answer-measure);\n}\n\n/* Close under the last line, and clear of the next turn. */\n/* The row under an answer: which version you are on, then what you can do\n with it. One row, because they are the same kind of thing — quiet controls\n that belong to the answer above them. */\n.actions {\n display: flex;\n align-items: center;\n gap: var(--ick-space-4);\n /* No column of its own: this row is *inside* `.answer`, so it is already on\n one. Adding the margin here as well put it at 64 — measured, which is the\n only way to notice an indent applied twice. */\n margin-top: var(--ick-space-4);\n}\n\n/* Dragging across the answer with the OS selection still reads as marking it,\n so it is marked in the marker's colour. */\n.answer ::selection {\n background: var(--ick-marker);\n color: var(--ick-marker-ink);\n}\n\n.answer ::-moz-selection {\n background: var(--ick-marker);\n color: var(--ick-marker-ink);\n}\n","\"use client\";\n\nimport { memo, type Ref } from \"react\";\nimport { motion } from \"motion/react\";\nimport { AnswerActions, type Verdict } from \"../AnswerActions/AnswerActions\";\nimport { Branch } from \"../Branch/Branch\";\nimport { ChatInput, type ChatInputHandle, type InlineAnimConfig } from \"../ChatInput/ChatInput\";\nimport type { Attachment } from \"../Attachments/Attachments\";\nimport type { TranscribeHandler } from \"../voice/useVoiceInput\";\nimport { Loader } from \"../Loader/Loader\";\nimport { Reasoning } from \"../Reasoning/Reasoning\";\nimport { Tool } from \"../Tool/Tool\";\nimport { Approval, type Decision } from \"../Approval/Approval\";\nimport { TaskList } from \"../TaskList/TaskList\";\nimport { ChainOfThought } from \"../ChainOfThought/ChainOfThought\";\nimport { Sources } from \"../Sources/Sources\";\nimport { QuestionGroup, type FoldMotion } from \"../QuestionGroup/QuestionGroup\";\nimport { SystemMessage } from \"../SystemMessage/SystemMessage\";\nimport { ArtifactCard } from \"../Artifact/ArtifactCard\";\nimport type { Answer } from \"../QuestionCard/types\";\nimport { TextHighlighter } from \"../TextHighlighter/TextHighlighter\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport type { ChatTurn } from \"../useChatTurns/useChatTurns\";\nimport styles from \"./ChatTurnRow.module.css\";\n\nexport interface ChatTurnRowProps {\n turn: ChatTurn;\n /** This row owns the live composer: the one the reader types the next question into. */\n isActiveInput?: boolean;\n inputRef?: Ref<ChatInputHandle> | null;\n placeholder?: string;\n animationConfig?: InlineAnimConfig;\n /** Overrides for a question group's fold timing. See `defaultFoldMotion`. */\n foldMotion?: Partial<FoldMotion>;\n /**\n * Which artifact the pane is showing, so the card that opened it can say so.\n * Held by `useArtifacts`, above both of them.\n */\n openArtifactId?: string | null;\n onOpenArtifact?: (turnId: string, artifactId: string) => void;\n /** Stagger for the entrance, in seconds. */\n entranceDelay?: number;\n /** Passed through to the highlighter over the answer. */\n selectionMode?: \"marker\" | \"precise\";\n\n /**\n * Passed through to the composer. Given, this row's live input offers a\n * microphone; left out, it does not. See `ChatInput`.\n */\n onTranscribe?: TranscribeHandler;\n\n /**\n * Where the composer sits in the row.\n *\n * `end` by default, because it is about to become the reader's own bubble\n * and those sit right. `stretch` fills the row instead, which is what an\n * *opening* composer wants: on an empty conversation it is not a message\n * yet, it is the thing under the openers — and a pill floating at the right\n * edge of a centred block reads as unrelated to the block.\n */\n questionAlign?: \"end\" | \"stretch\";\n\n /**\n * Every callback takes the turn's id rather than being closed over per row.\n *\n * Not a style choice. An inline arrow is a new function on every render, and\n * a new function prop is what makes `memo` give up — see the note on the\n * component below. Taking the id lets a consumer hoist these once.\n */\n onDraft?: (id: string, value: string) => void;\n onSubmit?: (id: string, value: string, attachments: Attachment[]) => void;\n onStop?: () => void;\n onEdit?: (id: string) => void;\n onCancelEdit?: (id: string) => void;\n /** Defaults to writing to the clipboard. */\n onCopy?: (value: string) => void;\n onHighlight?: (turnId: string, text: string) => void;\n onReplyInThread?: (text: string, rect: DOMRect) => void;\n\n /**\n * The row of actions under a settled answer.\n *\n * Copy is always offered once there is something to copy. Regenerate and the\n * thumbs are drawn only when there is somewhere for them to report to, so a\n * host that has no use for them is not showing a button that does nothing.\n *\n * They appear when the answer *settles*, not while it arrives: offering to\n * copy a half-written answer, or to rate one, is offering the wrong thing.\n */\n onRegenerate?: (id: string) => void;\n /** Show another of this turn's answers. Without it the control is inert, so\n it is not drawn. */\n onShowVersion?: (id: string, index: number) => void;\n onFeedback?: (id: string, verdict: Verdict | null) => void;\n feedback?: Verdict | null;\n /** Leave the row out entirely. */\n answerActions?: boolean;\n\n /**\n * A question the assistant asked, answered.\n *\n * The row does not keep the answer — `turn.parts` is the host's, and this is\n * how it hears that one of them changed. `useChatTurns` gives you\n * `updatePart` to write it back.\n */\n onAnswerQuestion?: (turnId: string, partId: string, questionId: string, answer: Answer) => void;\n /** Somebody asked to change an answer already given. */\n onEditQuestion?: (turnId: string, partId: string, index: number) => void;\n\n /**\n * Something the agent asked to do, decided.\n *\n * Same shape as the question callbacks and for the same reason: the row does\n * not keep the decision, it reports it. Write it back with `updatePart`.\n */\n onDecideApproval?: (turnId: string, partId: string, decision: Decision) => void;\n\n className?: string;\n}\n\nconst copyToClipboard = (value: string) => {\n void navigator.clipboard?.writeText(value);\n};\n\n/**\n * One turn: the question as a composer that has become a bubble, and the\n * answer beneath it.\n *\n * Not `Message`, because it is not one. The user half is a live input that\n * morphs into its own bubble rather than a rendered record of what was typed\n * — which is the whole idea, and the reason the row is a turn.\n *\n * **Memoised, and the memo is load-bearing.** `useChatTurns` already leaves\n * untouched turns referentially identical when it rewrites one of them, but\n * that only pays off if the rows can act on it. Left inline in a `.map()`,\n * every row re-renders on every flush anyway, because the parent re-rendered.\n * Measured before this existed: streaming a second answer produced 366 DOM\n * mutations inside the first, already-finished turn.\n *\n * The two halves have to be in place together. Stable objects give React the\n * grounds to skip; `memo` is what makes it skip.\n */\nexport const ChatTurnRow = memo(function ChatTurnRow({\n turn,\n isActiveInput = false,\n inputRef = null,\n placeholder,\n animationConfig,\n foldMotion,\n openArtifactId = null,\n onOpenArtifact,\n entranceDelay = 0,\n selectionMode = \"marker\",\n questionAlign = \"end\",\n onDraft,\n onSubmit,\n onStop,\n onEdit,\n onCancelEdit,\n onCopy = copyToClipboard,\n onHighlight,\n onReplyInThread,\n onRegenerate,\n onShowVersion,\n onFeedback,\n feedback = null,\n answerActions = true,\n onAnswerQuestion,\n onEditQuestion,\n onDecideApproval,\n className,\n onTranscribe,\n}: ChatTurnRowProps) {\n // A row arriving is travel, and this reader has asked for less of it. The\n // fade stays: without it a turn would appear with no transition at all,\n // which reads as a glitch rather than as calm.\n const still = prefersReducedMotion();\n const offset = still ? 0 : -16;\n // A turn a host built by hand may not have any.\n const parts = turn.parts ?? [];\n const cited = parts.find((part) => part.kind === \"sources\")?.sources;\n\n return (\n <motion.article\n id={`turn-${turn.id}`}\n className={[styles.turn, className ?? \"\"].filter(Boolean).join(\" \")}\n /* Which row is the live composer is something only this component knows,\n and a hashed CSS-module class is not something a host can target. So\n it is stated as an attribute.\n\n The one that needs it: a page with a gradient fading the conversation\n off the bottom edge has no way to exempt the box you type in, and a\n washed-out composer reads as one you are not allowed to use. */\n data-active-input={isActiveInput || undefined}\n aria-busy={turn.state === \"responding\" || undefined}\n initial={{ opacity: 0, y: offset }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: offset }}\n transition={{ duration: still ? 0.15 : 0.4, ease: [0.22, 1, 0.36, 1], delay: entranceDelay }}\n >\n <div className={styles.question} data-align={questionAlign}>\n <ChatInput\n ref={inputRef}\n /* A flex child sizes to its content, and the composer has a width of\n its own — so stretching the row is not enough on its own. */\n style={questionAlign === \"stretch\" ? { width: \"100%\" } : undefined}\n state={turn.state}\n value={turn.user}\n onChange={(v) => onDraft?.(turn.id, v)}\n onSubmit={(v, files) => onSubmit?.(turn.id, v, files)}\n /* The bubble *is* the message, so what went with it is shown by the\n same composer that held it — read-only once it is sent, because a\n sent message is a record. */\n attachments={turn.attachments}\n onStop={onStop}\n onTranscribe={onTranscribe}\n onCopy={onCopy}\n onEdit={() => onEdit?.(turn.id)}\n onCancelEdit={() => onCancelEdit?.(turn.id)}\n // An edit, rather than a first draft: there is already an answer\n // under this input, so the buttons say save and cancel.\n isEditing={turn.ai.length > 0 && turn.state === \"typing\"}\n animationConfig={animationConfig}\n placeholder={placeholder}\n />\n </div>\n\n {/* One block, so the row's generous gap sits between the question and\n everything the answer is made of — and not between a tool call and\n the sentence it produced, which belong together. */}\n <div className={styles.body}>\n {parts.map((part) => {\n switch (part.kind) {\n case \"reasoning\":\n return (\n <Reasoning key={part.id} state={part.state} duration={part.duration}>\n {part.text ?? \"\"}\n </Reasoning>\n );\n case \"tool\":\n return (\n <Tool\n key={part.id}\n name={part.name ?? \"\"}\n state={part.state}\n summary={part.summary}\n input={part.input}\n output={part.output}\n error={part.error}\n duration={part.duration}\n />\n );\n case \"tasks\":\n return (\n <TaskList\n key={part.id}\n title={part.title}\n tasks={part.tasks ?? []}\n collapsible={part.collapsible}\n />\n );\n case \"chain\":\n return (\n <ChainOfThought\n key={part.id}\n steps={part.steps ?? []}\n state={part.state}\n duration={part.duration}\n />\n );\n case \"sources\":\n return (\n <Sources\n key={part.id}\n sources={part.sources ?? []}\n title={part.title}\n collapsible={part.collapsible}\n />\n );\n case \"approval\":\n return (\n <Approval\n key={part.id}\n title={part.title}\n description={part.description}\n decision={part.decision ?? null}\n onDecide={(decision) => onDecideApproval?.(turn.id, part.id, decision)}\n >\n {part.tool && (\n <Tool\n name={part.tool.name}\n state=\"pending\"\n input={part.tool.input}\n defaultOpen\n />\n )}\n </Approval>\n );\n case \"artifact\":\n return (\n <ArtifactCard\n key={part.id}\n id={part.id}\n title={part.title}\n meta={part.meta}\n kind={part.preview}\n lang={part.lang}\n content={part.content}\n state={part.state}\n open={openArtifactId === part.id}\n onOpen={\n onOpenArtifact ? (id) => onOpenArtifact(turn.id, id) : undefined\n }\n />\n );\n case \"notice\":\n return (\n <SystemMessage key={part.id} tone={part.tone}>\n {part.text}\n </SystemMessage>\n );\n case \"question\":\n return (\n <QuestionGroup\n key={part.id}\n id={part.id}\n title={part.title}\n questions={part.questions ?? []}\n answers={part.answers ?? {}}\n activeIndex={part.activeIndex}\n collapsible={part.collapsible}\n readOnly={part.readOnly}\n foldMotion={foldMotion}\n onCommit={(questionId, answer) =>\n onAnswerQuestion?.(turn.id, part.id, questionId, answer)\n }\n onEdit={(index) => onEditQuestion?.(turn.id, part.id, index)}\n />\n );\n }\n })}\n\n {/* Sent, and nothing back yet. Without this the turn is a question with\n a blank space under it, which reads as nothing having happened.\n Silent on purpose: `useChatTurns` has already announced that a\n response is coming, and a second live region says it twice. */}\n {turn.state === \"responding\" && !turn.ai && parts.length === 0 && (\n <div className={styles.answer}>\n <Loader />\n </div>\n )}\n\n {turn.ai && (\n <div className={styles.answer}>\n <TextHighlighter\n text={turn.ai}\n selectionMode={selectionMode}\n /* What `[^1]` in the answer points at. The first sources part in\n the turn, because an answer stands on one list — a second would\n make the numbering ambiguous the moment both are non-empty. */\n sources={cited}\n onHighlightComplete={(text) => onHighlight?.(turn.id, text)}\n onReplyInThread={onReplyInThread}\n />\n\n {answerActions && turn.state === \"resting\" && (\n <div className={styles.actions}>\n {/* Draws nothing until there are two, so a turn answered once\n looks exactly as it did before there were versions at all. */}\n <Branch\n total={turn.versions?.length ?? 0}\n index={turn.versionIndex ?? 0}\n onSelect={(index) => onShowVersion?.(turn.id, index)}\n />\n <AnswerActions\n text={turn.ai}\n onCopy={onCopy}\n onRegenerate={onRegenerate ? () => onRegenerate(turn.id) : undefined}\n onFeedback={onFeedback ? (verdict) => onFeedback(turn.id, verdict) : undefined}\n feedback={feedback}\n />\n </div>\n )}\n </div>\n )}\n </div>\n </motion.article>\n );\n});\n","/* ─────────────────────────────────────────────\n Context\n\n A gauge, not a feature. It sits in a header or over a composer and it should\n be possible to ignore it until it matters — which is the whole design: quiet\n until the window is nearly full, and then saying what happens next rather\n than only that a number is high.\n ───────────────────────────────────────────── */\n\n.context {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n gap: var(--ick-space-3);\n font-family: var(--ick-font-sans);\n color: var(--ick-context-ink);\n}\n\n.context[data-warn] {\n color: var(--ick-context-ink-warn);\n}\n\n.ring {\n flex-shrink: 0;\n width: var(--ick-context-size);\n height: var(--ick-context-size);\n}\n\n.track {\n fill: none;\n stroke: var(--ick-context-track);\n stroke-width: 2;\n}\n\n.fill {\n fill: none;\n stroke: currentColor;\n stroke-width: 2;\n /* Rounded, so a nearly-empty window is still a mark rather than a speck. */\n stroke-linecap: round;\n transition: stroke-dashoffset var(--ick-duration-slow) var(--ick-ease);\n}\n\n/* The number climbing is the information; the sweep animating to it is not. */\n@media (prefers-reduced-motion: reduce) {\n .fill {\n transition: none;\n }\n}\n\n.label {\n font-family: var(--ick-font-mono);\n font-size: var(--ick-text-xs);\n line-height: 1;\n white-space: nowrap;\n}\n","\"use client\";\n\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Context.module.css\";\n\ntype Labels = {\n name: string;\n /** Between the two numbers: \"128k of 1M\". */\n of: string;\n tokens: string;\n /** Said once it is past `warnAt`, and it should say what happens next. */\n nearlyFull: string;\n};\n\nconst LABELS: Labels = {\n name: \"Context used\",\n of: \"of\",\n tokens: \"tokens\",\n nearlyFull: \"Nearly full — the oldest messages will start dropping out\",\n};\n\nexport interface ContextProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n used: number;\n total: number;\n /** Where it stops being quiet. A fraction, default `0.8`. */\n warnAt?: number;\n /**\n * Drawn beside the ring. The percentage by default; `false` for the ring on\n * its own, which is what a header usually wants.\n */\n label?: ReactNode | false;\n labels?: Partial<Labels>;\n}\n\n/** `128000` → `128k`, `1000000` → `1M`. Nobody reads the zeroes. */\nexport function formatCount(n: number): string {\n if (!Number.isFinite(n) || n < 0) return \"\";\n if (n >= 1_000_000) {\n const m = n / 1_000_000;\n return `${m < 10 ? Number(m.toFixed(1)) : Math.round(m)}M`;\n }\n if (n >= 1000) {\n const k = n / 1000;\n return `${k < 10 ? Number(k.toFixed(1)) : Math.round(k)}k`;\n }\n return String(Math.round(n));\n}\n\nconst RADIUS = 6;\nconst CIRCUMFERENCE = 2 * Math.PI * RADIUS;\n\n/**\n * How full the context window is.\n *\n * Small on purpose. It is a gauge, not a feature, and it earns its place for\n * one reason: it is the only honest way to explain why a long conversation\n * starts forgetting. Without it the forgetting looks like the model being\n * stupid rather than the window being full.\n *\n * Which is why the warning says what happens next rather than only that a\n * number is high. \"82%\" tells somebody nothing they can act on.\n */\nexport function Context({\n used,\n total,\n warnAt = 0.8,\n label,\n labels,\n className,\n ...rest\n}: ContextProps) {\n const label_ = { ...LABELS, ...labels };\n\n /* A total of zero is a window that has not been reported yet, not one that\n is full. Clamped, because a host summing its own tokens will overshoot\n before it notices. */\n const fraction = total > 0 ? Math.min(1, Math.max(0, used / total)) : 0;\n const percent = Math.round(fraction * 100);\n const warn = fraction >= warnAt;\n\n const shown = label === false ? null : (label ?? `${percent}%`);\n const spoken = `${label_.name}: ${percent}%, ${formatCount(used)} ${label_.of} ${formatCount(\n total\n )} ${label_.tokens}${warn ? `. ${label_.nearlyFull}` : \"\"}`;\n\n return (\n <div\n className={[styles.context, className ?? \"\"].filter(Boolean).join(\" \")}\n data-warn={warn || undefined}\n /* A gauge within a known range, which is what a meter is for. The\n numbers are on it rather than only in the label, so assistive\n technology can read the value without parsing a sentence. */\n role=\"meter\"\n aria-valuenow={percent}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={spoken}\n // The same sentence for a pointer, since the ring alone says a fraction\n // and not what to do about it.\n title={spoken}\n {...rest}\n >\n <svg className={styles.ring} viewBox=\"0 0 16 16\" aria-hidden focusable=\"false\">\n <circle className={styles.track} cx=\"8\" cy=\"8\" r={RADIUS} />\n <circle\n className={styles.fill}\n cx=\"8\"\n cy=\"8\"\n r={RADIUS}\n strokeDasharray={CIRCUMFERENCE}\n strokeDashoffset={CIRCUMFERENCE * (1 - fraction)}\n /* From the top rather than from three o'clock, because a gauge that\n starts anywhere else reads as decoration. */\n transform=\"rotate(-90 8 8)\"\n />\n </svg>\n {shown !== null && <span className={styles.label}>{shown}</span>}\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n ArtifactPane\n\n A card, at the size of a column. It takes the card surface rather than the\n ground for the reason a tool call's card does: this is the thing itself, and\n the conversation beside it is what it stands on.\n\n It sets no width and no position — `ChatLayout` owns those, because where\n the pane goes is a fact about the layout and not about the pane. What it\n does own is the geometry inside it: a head that stays and a body that\n scrolls.\n\n `box-sizing` is stated on every box here that has padding and a width. The\n package has no global reset — one written inside a CSS module leaks into the\n host's page — so each rule has to say it.\n ───────────────────────────────────────────── */\n\n.pane {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n border-radius: var(--ick-artifact-pane-radius);\n background: var(--ick-artifact-pane-surface);\n box-shadow: var(--ick-shadow-float);\n font-family: var(--ick-font-sans);\n overflow: hidden;\n}\n\n/* The sheet's handle. Drawn, not pressable — what it says is \"this edge\n moves\", and the thing that moves is the whole sheet. */\n.grabber {\n display: block;\n flex-shrink: 0;\n width: var(--ick-sheet-grabber-width);\n height: var(--ick-sheet-grabber-height);\n margin: var(--ick-space-3) auto 0;\n border-radius: var(--ick-radius-pill);\n background: var(--ick-sheet-grabber);\n}\n\n/* Stays while the body moves. A title that scrolls away takes with it the only\n thing saying what is being read. */\n.head {\n box-sizing: border-box;\n display: flex;\n align-items: flex-start;\n gap: var(--ick-space-4);\n flex-shrink: 0;\n padding: var(--ick-artifact-pane-pad) var(--ick-artifact-pane-pad) var(--ick-space-5);\n}\n\n.headText {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n}\n\n.title {\n margin: 0;\n font-size: var(--ick-text-lg);\n font-weight: var(--ick-weight-medium);\n color: var(--ick-ink);\n}\n\n/* Focused on open, so a keyboard reader is put down on what the pane is rather\n than left behind in the conversation — and given no ring for it, because\n nobody pressed anything. */\n.title:focus {\n outline: none;\n}\n\n.meta {\n margin: var(--ick-space-1) 0 0;\n font-size: var(--ick-text-sm);\n color: var(--ick-ink-soft);\n}\n\n.body {\n box-sizing: border-box;\n flex: 1;\n min-height: 0;\n padding: 0 var(--ick-artifact-pane-pad) var(--ick-artifact-pane-pad);\n overflow-y: auto;\n overscroll-behavior: contain;\n color: var(--ick-ink);\n /* The one thing that decides whether a web sheet feels right.\n\n The sheet is dragged with a finger and the body is scrolled with the same\n finger, so something has to say which. `pan-y` here gives the vertical\n gesture to the scroller whenever it starts inside the body — so reading\n never drags the sheet — while the header, the grabber and the margins\n around them have no scroller under them and drag it. Two surfaces, one\n finger, and no arbitration code at all.\n\n `overscroll-behavior: contain` above is the other half: at the top of the\n body a further pull does not become the page's. */\n touch-action: pan-y;\n}\n\n/* ── Before there is anything to read ───────────────────────────────────── */\n\n.skeleton {\n display: flex;\n flex-direction: column;\n gap: var(--ick-space-5);\n padding-top: var(--ick-space-2);\n}\n\n.line {\n height: var(--ick-space-5);\n border-radius: var(--ick-radius-sm);\n background: var(--ick-artifact-skeleton);\n animation: artifact-pulse 1.6s ease-in-out infinite;\n}\n\n.line:nth-child(2n) { animation-delay: 0.12s; }\n.line:nth-child(3n) { animation-delay: 0.24s; }\n\n@keyframes artifact-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.45; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .line { animation: none; }\n}\n","\"use client\";\n\nimport { useEffect, useId, useRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport { Maximize2, Minimize2, X } from \"lucide-react\";\nimport { Button } from \"../Button/Button\";\nimport styles from \"./ArtifactPane.module.css\";\n\nexport interface ArtifactPaneProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n title: ReactNode;\n /** What it is, under the title. */\n meta?: ReactNode;\n /**\n * What the host renders. Absent, the pane shows its own skeleton — an\n * artifact usually arrives before it is finished, and a pane that opens\n * empty reads as broken rather than as early.\n */\n children?: ReactNode;\n onClose?: () => void;\n /**\n * The one prop that changes behaviour, and it is not about position.\n *\n * Below the layout's breakpoint the pane covers the conversation instead of\n * standing beside it, and covering changes what it *is*: focus has to be\n * held inside it and Escape has to close it, because there is nothing usable\n * behind it. Beside the conversation both would be wrong — trapping focus\n * would lock a reader out of the chat they are still reading.\n *\n * `<ChatLayout>` sets it from its own width. Set it yourself only if you are\n * placing the pane without one.\n */\n modal?: boolean;\n /**\n * Widened. The pane draws the control; `ChatLayout` owns the width, because\n * how much room the pane takes is a fact about the layout.\n */\n expanded?: boolean;\n onToggleExpanded?: () => void;\n closeLabel?: string;\n expandLabel?: string;\n collapseLabel?: string;\n}\n\n/**\n * What the answer produced, in full, beside the conversation.\n *\n * The kit decides that it is on the right and that the conversation makes room\n * for it — this is a pattern rather than a slot, and a chat where the pane\n * arrives somewhere different each time is a chat nobody learns. What the kit\n * does **not** decide is the content: a plan, a document, a table, a diagram.\n * That is the host's, and it is why this takes children.\n *\n * The part worth having in a library is not the box. It is what happens on\n * open: focus has to move here or a keyboard reader is left behind in the\n * conversation, and it must **not** be trapped unless the pane is covering\n * that conversation. Every host gets one of those two wrong.\n */\nexport function ArtifactPane({\n title,\n meta,\n children,\n onClose,\n modal = false,\n expanded = false,\n onToggleExpanded,\n closeLabel = \"Close\",\n expandLabel = \"Widen\",\n collapseLabel = \"Narrow\",\n className,\n ...rest\n}: ArtifactPaneProps) {\n const titleId = useId();\n const pane = useRef<HTMLDivElement>(null);\n\n /* Focus moves to the pane's heading, not into it: a heading is where a\n reader wants to be put down, and focusing the first control would skip\n what the thing is. `tabIndex={-1}` makes it a target without making it a\n stop. */\n const heading = useRef<HTMLHeadingElement>(null);\n useEffect(() => {\n heading.current?.focus({ preventScroll: true });\n }, []);\n\n /* Escape closes it only when it is covering the conversation. Beside it,\n Escape belongs to whatever the reader is actually in. */\n useEffect(() => {\n if (!modal || !onClose) return;\n const onKey = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n event.stopPropagation();\n onClose();\n }\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [modal, onClose]);\n\n /* And focus stays inside it, for the same reason and only then. */\n useEffect(() => {\n if (!modal) return;\n const onFocus = (event: FocusEvent) => {\n const root = pane.current;\n if (root && event.target instanceof Node && !root.contains(event.target)) {\n heading.current?.focus({ preventScroll: true });\n }\n };\n document.addEventListener(\"focusin\", onFocus);\n return () => document.removeEventListener(\"focusin\", onFocus);\n }, [modal]);\n\n return (\n /* A `div` with the role written out, not an `<aside>`.\n \n Beside the conversation this is a complementary region and `<aside>`\n would have said so for free — but the same element has to become a\n dialog when it covers the conversation, and `role=\"dialog\"` is not\n allowed on an `<aside>`. One element that is both means neither can be\n implied. Caught by the axe pass rather than by reading, which is what\n that pass is for. */\n <div\n ref={pane}\n className={[styles.pane, className ?? \"\"].filter(Boolean).join(\" \")}\n aria-labelledby={titleId}\n aria-modal={modal || undefined}\n role={modal ? \"dialog\" : \"complementary\"}\n {...rest}\n >\n {/* A `div`, not a `<header>`. Inside a landmark a `<header>` *is* a\n banner landmark, and a banner has to be top-level — nesting one in\n the pane's own region is a second landmark claiming to be the page's.\n It only became a problem when the pane stopped being an `<aside>`,\n which would have scoped it; the axe pass caught the consequence a\n minute after it caught the cause. */}\n {/* The strip that says \"pull me\", and only where pulling does something.\n Not a control: pressing it does nothing, so it is `aria-hidden` and\n the close button beside it is the reachable way out. */}\n {modal && <span className={styles.grabber} aria-hidden />}\n <div className={styles.head}>\n <div className={styles.headText}>\n <h2 className={styles.title} id={titleId} ref={heading} tabIndex={-1}>\n {title}\n </h2>\n {meta && <p className={styles.meta}>{meta}</p>}\n </div>\n {/* Widening is meaningless while it is covering the conversation:\n there is nothing left to take. */}\n {onToggleExpanded && !modal && (\n <Button\n variant=\"ghost\"\n size=\"s\"\n icon={\n expanded ? <Minimize2 size={15} aria-hidden /> : <Maximize2 size={15} aria-hidden />\n }\n aria-label={expanded ? collapseLabel : expandLabel}\n aria-pressed={expanded}\n onClick={onToggleExpanded}\n />\n )}\n {onClose && (\n <Button\n variant=\"ghost\"\n size=\"s\"\n icon={<X size={15} aria-hidden />}\n aria-label={closeLabel}\n onClick={onClose}\n />\n )}\n </div>\n\n <div className={styles.body}>\n {children ?? <Skeleton />}\n </div>\n </div>\n );\n}\n\n/**\n * What a pane looks like before its content exists.\n *\n * Not a spinner. An artifact is written a line at a time and the shape of what\n * is coming is already known — a title, some paragraphs — so the skeleton says\n * that, and the content replaces it in place rather than after a blank.\n */\nfunction Skeleton() {\n return (\n <div className={styles.skeleton} aria-hidden>\n {[68, 100, 92, 100, 46, 100, 84].map((width, i) => (\n <span key={i} className={styles.line} style={{ width: `${width}%` }} />\n ))}\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n ChatLayout\n\n The conversation, and the pane beside it. The kit decides that the pane is\n on the right and that the conversation makes room — a chat where the pane\n arrives somewhere different each time is a chat nobody learns.\n\n A **container** query rather than a media query, deliberately: the pane\n should answer to the width it actually has, not to the width of the window.\n A kit embedded in a 480px column inside a wide page would otherwise put a\n 420px pane beside a 60px conversation, which is the fault a media query\n cannot see.\n\n ── The shape of the opening ────────────────────────────────────────────────\n\n Two boxes, and which one is which is the whole trick.\n\n `.slot` is the room the pane takes. Its width is the only thing that\n animates, and animating it is what moves the conversation — a transform\n cannot push a sibling, so the property has to be the one the layout is made\n of. `.card` is what the host rendered, and it is held at a width the\n animation never touches, so the document inside it is laid out once instead\n of re-wrapping on every frame of its own entrance.\n\n The slot clips. So as it widens, it uncovers a card that is already the\n right size and already in its final place: the pane appears rather than\n inflating. All the spacing lives on the card for the same reason — padding\n on the slot would be a floor under a width that has to reach zero.\n ───────────────────────────────────────────── */\n\n.layout {\n container-type: inline-size;\n box-sizing: border-box;\n position: relative;\n display: flex;\n min-height: 0;\n flex: 1;\n}\n\n/* The conversation. `min-width: 0` because a flex child's default is its\n content, and a long unbroken line in an answer would push the pane off the\n end rather than scroll inside itself.\n\n A column, so a `Conversation` inside it fills the height. Worth knowing if\n you are wrapping an existing centred page in this: a child with\n `margin: 0 auto` is a flex item that has opted out of stretching, so it will\n be sized by its content rather than by its `max-width` until it is given a\n `width` to cap. */\n.chat {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n min-width: 0;\n min-height: 0;\n flex: 1;\n}\n\n/* The room. Nothing is drawn here — no padding, no background, no gap — so\n that its width can be animated to and from zero and mean it. */\n.slot {\n box-sizing: border-box;\n display: flex;\n /* Glued right, so the slot uncovers the card from its left edge inwards:\n the pane comes in from the side it lives on. */\n justify-content: flex-end;\n flex-shrink: 0;\n min-height: 0;\n overflow: hidden;\n}\n\n/* What the host rendered, at a width the animation never touches.\n\n The margins are the layout's spacing, moved here off the slot: the gap from\n the conversation on the left, the card's clearance from the window on the\n other three sides. Which means the slot's natural width — what it animates\n *to* — is already the whole of the room the pane needs. */\n.card {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n flex: 0 0 auto;\n min-height: 0;\n width: var(--ick-artifact-pane-width);\n margin: var(--ick-artifact-pane-inset) var(--ick-artifact-pane-inset)\n var(--ick-artifact-pane-inset) var(--ick-artifact-pane-gap);\n /* Widening is a resize, not an entrance: the document *should* re-wrap, so\n this one animates the card itself and the slot's `auto` follows it. */\n transition: width var(--ick-duration-slow) var(--ick-ease);\n}\n\n.card[data-expanded] {\n width: var(--ick-artifact-pane-width-wide);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .card {\n transition: none;\n }\n}\n\n/* Narrow: the pane covers the conversation instead of standing beside it.\n Which is not a different component — it is the same pane, told that it is\n now modal, so it holds focus and answers Escape. See `ArtifactPane`.\n\n `left: auto` matters. The slot is still the thing whose width animates, and\n a box pinned on both sides would ignore that width; pinned on the right\n only, it grows leftwards out of the edge it belongs to — the same entrance\n as the wide case, at the size of the whole layout. */\n@container (max-width: 760px) {\n /* Pinned to the bottom and short of the top, which is the whole difference\n between a sheet and a cover: a strip of the conversation stays visible, so\n you can still see what you asked while reading what came back. */\n .slot {\n position: absolute;\n inset-inline: 0;\n top: auto;\n bottom: 0;\n height: var(--ick-sheet-height);\n z-index: 1;\n /* The card is dragged past this edge on the way out. */\n overflow: visible;\n }\n\n .card,\n .card[data-expanded] {\n /* Nothing to expand into: a sheet is already as wide as the screen. */\n width: 100%;\n height: 100%;\n margin: 0;\n /* Dragged, so it must not be waiting for the browser to decide whether\n this gesture was a scroll. */\n touch-action: none;\n }\n\n /* Corners at the top only. The bottom runs off the screen, and rounding it\n shows the page through two notches against the bezel. */\n .card > * {\n border-start-start-radius: var(--ick-sheet-radius);\n border-start-end-radius: var(--ick-sheet-radius);\n border-end-start-radius: 0;\n border-end-end-radius: 0;\n }\n}\n\n/* What the conversation goes under while a sheet is over it. Its own element\n rather than a shadow on the sheet, because it has to fade on its own clock:\n the sheet slides and the scrim dims, and a reader reads the second one\n first. */\n.scrim {\n position: absolute;\n inset: 0;\n z-index: 1;\n background: var(--ick-sheet-scrim);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n}\n","\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { AnimatePresence, motion, useReducedMotion, type Variants } from \"motion/react\";\nimport styles from \"./ChatLayout.module.css\";\n\n/** Below this the pane covers the conversation. Kept with the stylesheet. */\nconst NARROW = 760;\n\nexport interface PaneState {\n /** The pane is covering the conversation rather than standing beside it. */\n narrow: boolean;\n /** It has been widened. Meaningless while `narrow`, which is already full. */\n expanded: boolean;\n toggleExpanded: () => void;\n}\n\nexport interface ChatLayoutProps extends HTMLAttributes<HTMLDivElement> {\n /** The conversation, and whatever else belongs above and below it. */\n children: ReactNode;\n /**\n * The pane, when one is open.\n *\n * Given `narrow`, so it can hold focus and answer Escape once it is covering\n * the conversation rather than standing beside it — the one thing about a\n * pane that is not a matter of taste. And given `expanded` with the control\n * for it, because the *width* belongs to the layout while the button that\n * changes it belongs in the pane's own header, where somebody can find it.\n */\n pane?: (state: PaneState) => ReactNode;\n /**\n * Close it — dragged down, or the conversation behind it pressed.\n *\n * Only used on a phone, where the pane is a sheet. Both of those are ways\n * out that belong to the *layout* rather than to the pane: the pane owns its\n * close button, the layout owns the gesture and the ground around it. Leave\n * it out and the sheet has neither, which is a sheet a thumb cannot dismiss.\n */\n onDismiss?: () => void;\n}\n\n/**\n * How the pane arrives, in two parts that are deliberately not the same.\n *\n * The room opens flat and the pane arrives with a little life in it. That\n * split is the whole design: `width` is what the conversation is laid out\n * against, so any overshoot there re-wraps every line of the answer twice on\n * the way past — a bounce nobody asked for, paid for in text. Transform and\n * opacity cost the layout nothing, so that is where the spring goes.\n *\n * Leaving is quicker than arriving, and in the other order: the pane goes\n * first and the room closes behind it. A thing that leaves as slowly as it\n * came reads as reluctant.\n */\nconst room: Variants = {\n closed: {\n width: 0,\n transition: { type: \"spring\", visualDuration: 0.26, bounce: 0, delay: 0.05 },\n },\n open: {\n width: \"auto\",\n transition: { type: \"spring\", visualDuration: 0.42, bounce: 0 },\n },\n};\n\nconst arriving: Variants = {\n /* Held inside the slot's right margin, so what slides is the pane and not\n the clip: it comes from the edge it lives on. */\n closed: { opacity: 0, x: 24, transition: { duration: 0.16, ease: \"easeIn\" } },\n open: {\n opacity: 1,\n x: 0,\n transition: { type: \"spring\", visualDuration: 0.36, bounce: 0.2, delay: 0.06 },\n },\n};\n\n/**\n * And on a phone, where the pane is a sheet rather than a column beside the\n * conversation.\n *\n * Nothing about the wide entrance survives the change. There, the room opening\n * *is* the animation, because the conversation has to make way; here the\n * conversation stays where it is and the sheet comes up over it, so what moves\n * is a transform and the layout never changes. That also makes it cheap: the\n * wide case re-wraps a column on every frame, this one does not touch layout\n * at all.\n *\n * A little bounce, which the width animation could not have. Nothing is laid\n * out against `y`, so an overshoot costs nothing and is what tells a thumb the\n * thing it flicked has weight.\n */\nconst sheet: Variants = {\n closed: {\n y: \"100%\",\n transition: { type: \"spring\", visualDuration: 0.28, bounce: 0 },\n },\n open: {\n y: 0,\n transition: { type: \"spring\", visualDuration: 0.42, bounce: 0.18 },\n },\n};\n\n/** The wash over the conversation, on its own clock. */\nconst scrim: Variants = {\n closed: { opacity: 0, transition: { duration: 0.18 } },\n open: { opacity: 1, transition: { duration: 0.26 } },\n};\n\n/** The same choreography with the time taken out. */\nconst AT_ONCE = { transition: { duration: 0 } };\nconst still = (variants: Variants): Variants => ({\n closed: { ...(variants.closed as object), ...AT_ONCE },\n open: { ...(variants.open as object), ...AT_ONCE },\n});\n\n/**\n * Where the pane goes, decided once.\n *\n * The kit takes this decision rather than handing over a slot. A preview pane\n * is one of the few patterns every AI chat now has, and the value of a pattern\n * is that it is the same every time: ask for a plan, get a card, press it, the\n * plan opens on the right. A kit that let each host place it would be shipping\n * four chats that behave differently and calling it flexibility.\n *\n * What is left open is the part that actually differs — what is *in* the pane.\n * See `ArtifactPane`.\n */\nexport function ChatLayout({\n children,\n pane,\n onDismiss,\n className,\n ...rest\n}: ChatLayoutProps) {\n const root = useRef<HTMLDivElement>(null);\n const [narrow, setNarrow] = useState(false);\n const [expanded, setExpanded] = useState(false);\n const toggleExpanded = useCallback(() => setExpanded((wide) => !wide), []);\n\n /* Read here rather than through `<MotionConfig reducedMotion=\"user\">`, which\n is what the rest of the kit uses and would not be enough on its own: it\n drops transforms and layout animations, and the slot's `width` is neither.\n A pane that still took four hundred milliseconds to unfold would be the\n one animation on the page ignoring the request. */\n const reduce = useReducedMotion();\n const [slot, card, wash] = useMemo<[Variants, Variants | undefined, Variants]>(() => {\n const outer = narrow ? sheet : room;\n /* Inside a sheet the card has no entrance of its own — the sheet coming up\n *is* the arrival, and a second fade within it reads as two things\n happening for one gesture.\n \n `undefined`, not a stilled copy of the wide one. A stilled variant still\n *runs*, at zero duration: on the way out the document blinked away\n instantly and an empty white sheet slid down after it. Which looks\n exactly like the content being deleted rather than put away. */\n const inner = narrow ? undefined : arriving;\n return reduce\n ? [still(outer), inner && still(inner), still(scrim)]\n : [outer, inner, scrim];\n }, [reduce, narrow]);\n\n /* Measured off this element rather than the window, to agree with the\n container query in the stylesheet. A kit embedded in a narrow column\n inside a wide page is the case a media query gets wrong. */\n useEffect(() => {\n const element = root.current;\n if (!element || typeof ResizeObserver === \"undefined\") return;\n const watch = new ResizeObserver(([entry]) => {\n setNarrow(entry.contentRect.width <= NARROW);\n });\n watch.observe(element);\n return () => watch.disconnect();\n }, []);\n\n const shown = pane?.({ narrow, expanded, toggleExpanded });\n\n return (\n <div\n ref={root}\n className={[styles.layout, className ?? \"\"].filter(Boolean).join(\" \")}\n data-pane={shown ? \"\" : undefined}\n {...rest}\n >\n <div className={styles.chat}>{children}</div>\n {/* `initial={false}`: a layout that mounts with a pane already open did\n not just open one. Anything opened afterwards animates. */}\n <AnimatePresence initial={false}>\n {shown && narrow && onDismiss && (\n /* A wash, not a control.\n \n It was a `<button aria-label=\"Close\">`, which put a second thing\n called Close on the page — the pane's own X is the first — and a\n tab stop in front of a sheet that already traps focus. Pressing\n the ground is a convenience for a thumb; the reachable ways out\n are the button and Escape, and both are in the pane. So this is\n hidden from the tree and does nothing a keyboard can reach. */\n <motion.div\n key=\"scrim\"\n className={styles.scrim}\n variants={wash}\n initial=\"closed\"\n animate=\"open\"\n exit=\"closed\"\n aria-hidden\n onClick={onDismiss}\n />\n )}\n {shown && (\n <motion.div\n /* Keyed on the shape, so crossing the breakpoint builds the right\n one rather than leaving an inline `width` from the other mode on\n a box that is now positioned by its edges. */\n key={narrow ? \"sheet\" : \"beside\"}\n className={styles.slot}\n variants={slot}\n initial=\"closed\"\n animate=\"open\"\n exit=\"closed\"\n >\n {/* Variants rather than props on each element: named states are\n what Motion propagates down a tree, so the pane leaving is one\n decision here instead of two that have to agree. */}\n <motion.div\n className={styles.card}\n data-expanded={(expanded && !narrow) || undefined}\n variants={card}\n /* ── Dragged down to dismiss, and only downwards ──────────────\n `dragConstraints` with `top: 0` and no elastic upwards: a\n sheet that can be pulled *above* its own top edge is a sheet\n with a gap under it, and there is nothing up there to show.\n\n `dragPropagation` off so the gesture does not also become a\n scroll of the conversation underneath.\n\n What decides is distance **or** speed. Distance alone loses a\n quick flick, which is how a thumb actually dismisses things;\n speed alone dismisses a slow deliberate nudge that was meant\n to peek. */\n {...(narrow && onDismiss\n ? {\n drag: \"y\" as const,\n dragConstraints: { top: 0, bottom: 0 },\n dragElastic: { top: 0, bottom: 0.6 },\n dragMomentum: false,\n onDragEnd: (\n _e: unknown,\n info: { offset: { y: number }; velocity: { y: number } }\n ) => {\n if (info.offset.y > 120 || info.velocity.y > 600) onDismiss();\n },\n }\n : {})}\n >\n {shown}\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback, useState } from \"react\";\n\nexport interface Artifacts {\n /** The one on screen, or `null`. */\n openId: string | null;\n open: (id: string) => void;\n close: () => void;\n /** Press the card that is already open and it closes, which is what a reader expects. */\n toggle: (id: string) => void;\n}\n\n/**\n * Which artifact is open.\n *\n * A hook rather than state inside the pane, for the reason `useChatTurns` is a\n * hook: the card in the transcript and the pane beside it are in different\n * parts of the tree and both need the answer. A pane that owned it could not\n * tell a card it was the open one, and a card that owned it could not be told\n * the pane had closed.\n *\n * One at a time. A stack of open artifacts is a tab bar, and a tab bar is a\n * product's decision about how many things somebody is holding at once.\n */\nexport function useArtifacts(initial: string | null = null): Artifacts {\n const [openId, setOpenId] = useState<string | null>(initial);\n\n const open = useCallback((id: string) => setOpenId(id), []);\n const close = useCallback(() => setOpenId(null), []);\n const toggle = useCallback((id: string) => setOpenId((at) => (at === id ? null : id)), []);\n\n return { openId, open, close, toggle };\n}\n","/* ─────────────────────────────────────────────\n EmptyState\n ───────────────────────────────────────────── */\n\n.empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--ick-space-5);\n padding: var(--ick-empty-pad) var(--ick-space-6);\n text-align: center;\n font-family: var(--ick-font-sans);\n}\n\n.icon {\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--ick-ink-faint);\n}\n\n.title {\n font-size: var(--ick-text-lg);\n font-weight: var(--ick-weight-medium);\n color: var(--ick-ink);\n}\n\n.description {\n margin: 0;\n max-width: 42ch;\n font-size: var(--ick-text-md);\n line-height: 1.6;\n color: var(--ick-ink-soft);\n}\n\n/* Wrapping rather than scrolling: an opener that runs off the edge is one\n nobody reads, and there are never enough of them to need a rail. */\n.suggestions {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n gap: var(--ick-space-3);\n margin-top: var(--ick-space-2);\n}\n","\"use client\";\n\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { Button } from \"../Button/Button\";\nimport styles from \"./EmptyState.module.css\";\n\nexport interface EmptyStateProps\n // `title` is a DOM attribute as well as a prop here, and the DOM's is a\n // plain string. Ours wins: a heading can be a node.\n extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n icon?: ReactNode;\n title?: ReactNode;\n description?: ReactNode;\n /**\n * Openers. A blank input asks somebody to think of something; these are for\n * the moment before they have.\n */\n suggestions?: string[];\n /** Given the suggestion's text. Without it, none are drawn. */\n onSuggestion?: (text: string) => void;\n suggestionsLabel?: string;\n children?: ReactNode;\n}\n\n/**\n * What is on screen before anybody has asked anything.\n *\n * Not a landmark and not a heading by default — this sits inside a\n * conversation the host already owns, and claiming a level in their document\n * is not ours to do. `title` renders as text; wrap it yourself if it should be\n * a heading.\n */\nexport function EmptyState({\n icon,\n title,\n description,\n suggestions,\n onSuggestion,\n suggestionsLabel = \"Suggestions\",\n children,\n className,\n ...rest\n}: EmptyStateProps) {\n const chips = onSuggestion ? (suggestions ?? []) : [];\n\n return (\n <div className={[styles.empty, className ?? \"\"].filter(Boolean).join(\" \")} {...rest}>\n {icon && <div className={styles.icon}>{icon}</div>}\n {title && <div className={styles.title}>{title}</div>}\n {description && <p className={styles.description}>{description}</p>}\n\n {chips.length > 0 && (\n <div className={styles.suggestions} role=\"group\" aria-label={suggestionsLabel}>\n {chips.map((text) => (\n /* `primary`, not `secondary`. The secondary button is transparent\n until it is hovered, which for an opener means it reads as a\n line of bold text rather than as something to press — and an\n opener nobody recognises as pressable is one nobody presses. */\n <Button key={text} variant=\"primary\" size=\"m\" onClick={() => onSuggestion?.(text)}>\n {text}\n </Button>\n ))}\n </div>\n )}\n\n {children}\n </div>\n );\n}\n","/* ─────────────────────────────────────────────\n Conversation\n\n Three layers: a positioning root, the thing that actually scrolls, and a\n content wrapper whose height is what \"the end\" is measured against. The\n wrapper is not decorative — scrollHeight includes the container's own\n padding, and a chat with a screen-height pad below it would be scrolled to\n a blank space rather than to the answer.\n ───────────────────────────────────────────── */\n\n.root {\n position: relative;\n display: flex;\n flex-direction: column;\n min-height: 0;\n flex: 1;\n}\n\n.viewport {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n overscroll-behavior: contain;\n padding: var(--ick-conversation-pad-block) var(--ick-conversation-pad-inline);\n box-sizing: border-box;\n}\n\n.content {\n display: flex;\n flex-direction: column;\n gap: var(--ick-conversation-gap);\n /* Room under the last turn, so a turn brought to the anchor can actually\n reach the top of the view.\n\n **On the wrapper, never on the scroller.** A scroll container is a flex\n item told to be the height of its parent, and `box-sizing: border-box`\n will not let any box be shorter than its own padding — so a screen-height\n `padding-bottom` on the viewport makes the viewport taller than the page\n that holds it. The demo had exactly that: a 680px page with a 773px\n scroller inside it, the extra 93px clipped away by the page, and the\n follow logic reading `clientHeight` 773 and believing a line drawn at 700\n was on screen. It was not; it was under the clip. Answers stopped\n scrolling the moment they reached the real bottom edge.\n\n Here it costs nothing: the wrapper is content, so it lengthens the scroll\n without touching the size of the thing doing the scrolling. */\n padding-bottom: var(--ick-conversation-tail);\n}\n\n/* ── The way back ───────────────────────────────────────────────────────── */\n\n.jump {\n all: unset;\n font-size: var(--ick-text-md);\n box-sizing: border-box;\n position: absolute;\n left: 50%;\n bottom: var(--ick-space-6);\n z-index: var(--ick-z-sticky);\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border-radius: var(--ick-radius-pill);\n cursor: pointer;\n color: var(--ick-ink);\n background: var(--ick-surface-raised);\n box-shadow: var(--ick-shadow-float);\n outline: 0.5px solid var(--ick-border);\n -webkit-tap-highlight-color: transparent;\n\n /* Not `display: none`: the button has to be able to fade rather than blink,\n and a display change cannot be transitioned. `visibility` is what takes\n it out of hit-testing on the way out. */\n opacity: 0;\n visibility: hidden;\n transform: translateX(-50%) translateY(6px) scale(0.9);\n transition:\n opacity var(--ick-duration-fast) ease,\n transform var(--ick-duration-fast) var(--ick-ease),\n visibility 0s linear var(--ick-duration-fast);\n}\n\n/* Sized from the text scale rather than the 15px its call site asks for, so\n it grows with everything else on a touch device. Inert on a pointer. */\n.jump svg {\n width: var(--ick-icon);\n height: var(--ick-icon);\n}\n\n.jump[data-shown] {\n opacity: 1;\n visibility: visible;\n transform: translateX(-50%) translateY(0) scale(1);\n transition-delay: 0s;\n}\n\n.jump:hover {\n background: var(--ick-surface-raised);\n box-shadow: var(--ick-shadow-2);\n}\n\n.jump[data-shown]:active {\n transform: translateX(-50%) translateY(0) scale(0.92);\n transition-duration: var(--ick-duration-press);\n}\n\n.jump:focus-visible {\n outline: 2px solid var(--ick-focus-ring);\n outline-offset: var(--ick-space-1);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .jump {\n transition-property: opacity, visibility;\n transform: translateX(-50%);\n }\n\n .jump[data-shown]:active,\n .jump[data-shown] {\n transform: translateX(-50%);\n }\n}\n","\"use client\";\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { ArrowDown } from \"lucide-react\";\nimport { prefersReducedMotion } from \"../reducedMotion/reducedMotion\";\nimport styles from \"./Conversation.module.css\";\n\nexport interface ConversationProps extends HTMLAttributes<HTMLDivElement> {\n children?: ReactNode;\n /**\n * How close to the end still counts as following, in pixels. Below this the\n * view keeps up on its own; above it, the reader is reading and is left\n * alone.\n */\n threshold?: number;\n /** The button offering a way back. `false` for none. */\n scrollButton?: boolean;\n scrollButtonLabel?: string;\n /**\n * The id of an element to hold at the top of the view — a turn, usually.\n *\n * Without it the view follows the end of the content, which is what a chat\n * that stacks downwards wants. With it, the named element is brought to the\n * top and **held** there while the answer grows underneath, so a reader sees\n * their question and its answer and nothing else. Change the id and the view\n * moves to the new one.\n *\n * This needs room to scroll into: an element cannot be brought to the top of\n * a container that ends just below it. That is what a large bottom padding\n * on the viewport is for.\n */\n anchorId?: string;\n /**\n * How far below the top edge the anchor sits, in pixels. A fixed header over\n * the conversation is the usual reason — without it the turn is scrolled\n * neatly underneath and out of sight.\n */\n anchorOffset?: number;\n /**\n * How much room is left under the last turn when the view is at the end, in\n * pixels.\n *\n * Zero puts the last line flush with the bottom edge, which is right for a\n * transcript and wrong for this kit: the last turn *is* the composer — the\n * input is the message — so flush means the thing you type into is jammed\n * against the edge of the screen the moment an answer finishes. On a phone\n * that is also where the browser's own chrome lives.\n */\n endOffset?: number;\n /**\n * The empty room under the last turn, which is what lets a turn be brought\n * to the top at all.\n *\n * An element cannot be scrolled to the top of a container that ends just\n * below it — so `anchorId` only works if there is somewhere to scroll into.\n * That room used to be a guess written in the host's stylesheet, `99vh`,\n * and a guess is wrong in both directions at once: too much of it and the\n * reader can scroll a whole screen into nothing (measured: 697px of blank\n * in a 680px view, with the last turn above the top edge), too little and\n * the newest message cannot reach the top.\n *\n * `\"auto\"` measures it instead:\n *\n * screen - anchorOffset - (the anchored turn's top to the end) - padding\n *\n * and the second term is the whole point. Measuring the **last turn's own\n * height** instead — which this did first — leaves everything between the\n * anchor and the end unaccounted for: the last turn is the composer, 44px\n * of it, so an answer that already filled the screen still got 536px of\n * room under it to scroll into. What the room is for is lifting the\n * anchored turn to the top, and what stands between that turn and the\n * bottom edge is the whole stack under it.\n *\n * Sized this way, the end of the scroll lands exactly where the anchored\n * turn sits at the anchor. So the view comes to rest **at** the end of the\n * scroll and there is nowhere further to go — the room is spent holding the\n * turn up, not left over underneath. It shrinks as the answer grows, which\n * is also why an answer does not have to be chased: the content grows and\n * the room shrinks by the same pixel, so the scroll height does not move\n * and neither does the turn at the top.\n *\n * Never below `endOffset`, so a turn taller than the screen still rests\n * with the composer clear of the bottom edge instead of jammed against it.\n *\n * Zero unless this conversation anchors, because the room exists for\n * anchoring. A number takes it over, in pixels; so does the\n * `--ick-conversation-tail` token, which this writes.\n */\n tail?: \"auto\" | number;\n /** Switch the whole thing off and it is a plain scroll container. */\n follow?: boolean;\n /**\n * For the element that actually scrolls, which is not the one `className`\n * lands on.\n *\n * There are three: a root that positions the button, the viewport that\n * scrolls, and a wrapper whose height is what \"the end\" is measured from.\n * `className` goes to the root, because that is the box a consumer lays out.\n * Padding has to go here instead — on the scroller — or it is not padding\n * inside the scroll at all.\n */\n viewportClassName?: string;\n}\n\nconst THRESHOLD = 64;\n\n/**\n * How long a deliberate move is given before the view goes back to tracking.\n *\n * A ceiling, not a duration — the browser owns how long its own smooth scroll\n * takes. Without one, a travel that never quite arrives (because the answer\n * kept growing and moved the target out from under it) would leave the view\n * refusing to track for the rest of the session.\n */\nconst TRAVEL = 500;\n\n/**\n * How close to the end counts as having **arrived** there.\n *\n * Not the same number as `threshold`, and the difference is the whole of a\n * bug. `threshold` is how far you have to go before the view accepts that you\n * have left — generous on purpose, so a small nudge does not abandon the\n * answer you are reading. Arriving is the opposite question, and answering it\n * with the same generosity means the view re-acquires you while you are still\n * on your way out.\n *\n * Measured on a conversation at rest, which since the room under the last turn\n * became measured is exactly the end of the scroll — so every upward scroll\n * now starts inside that band:\n *\n * wheeled up 20px → wanted 134, ended at 154 dragged back\n * wheeled up 40px → wanted 114, ended at 154 dragged back\n * wheeled up 63px → wanted 91, ended at 154 dragged back\n * wheeled up 80px → wanted 74, ended at 74 free\n *\n * The first 64 pixels of reading back through a conversation were spent\n * fighting, and then it let go all at once — which is what \"it jumps at a\n * certain point\" is. Four pixels is a rounding allowance, not an opinion.\n */\nconst ARRIVED = 4;\n\n/**\n * How far down the scroll an element's top sits, in layout coordinates.\n *\n * `offsetTop` is measured from the nearest **positioned** ancestor, which here\n * is the root — the viewport itself is not positioned, so every turn and the\n * wrapper around them all report against the same origin. Adding the\n * wrapper's own `offsetTop` to a turn's, as this used to, counts the\n * viewport's padding twice: measured in the demo, a conversation ending at\n * 729 was read as ending at 829, and the composer came to rest 100px higher\n * than the `endOffset` it was given.\n *\n * `offsetTop` rather than a rect on purpose. Turns animate in, and a rect\n * reads the transform mid-flight; this is layout, which does not move.\n */\nconst flowTop = (el: HTMLElement, view: HTMLElement): number => el.offsetTop - view.offsetTop;\n\n/** The turn the room is measured against, if it is still on the page. */\nconst held0 = (view: HTMLElement, id: string | null): HTMLElement | null =>\n id ? view.querySelector<HTMLElement>(`[id=\"${CSS.escape(id)}\"]`) : null;\n\n/** Written only when it changes, so an answer does not touch the DOM per frame. */\nconst write = (inner: HTMLElement, applied: { current: number }, room: number): void => {\n if (room === applied.current) return;\n applied.current = room;\n inner.style.setProperty(\"--ick-conversation-tail\", `${Math.round(room)}px`);\n};\n\n/**\n * The scroll container: it keeps up with an answer as it arrives, and stops\n * the instant the reader scrolls away.\n *\n * **It follows the end of the content, not the bottom of the container**, and\n * those are only the same thing when nothing is padded below. This kit's demo\n * carries a screen-height bottom padding so a turn can be pulled to the top,\n * and scrolling to the true bottom there would push the answer off the screen\n * to sit in front of a blank space. Measuring the content instead makes one\n * behaviour correct for both.\n *\n * The other half is not fighting the reader. A naive version listens to the\n * scroll event, cannot tell its own scrolling from theirs, and either drags\n * them back down while they are reading or lets go entirely. This reads intent\n * from the input — a wheel, a drag, a page key — and uses the scroll event\n * only to measure where things ended up.\n */\nexport const Conversation = forwardRef<HTMLDivElement, ConversationProps>(function Conversation(\n {\n children,\n anchorId,\n anchorOffset = 0,\n endOffset = 0,\n tail = \"auto\",\n threshold = THRESHOLD,\n scrollButton = true,\n scrollButtonLabel = \"Jump to the latest\",\n follow = true,\n className,\n viewportClassName,\n onScroll,\n ...rest\n },\n ref\n) {\n const viewport = useRef<HTMLDivElement | null>(null);\n /* The forwarded ref is the *viewport*, not the root. Anyone reaching for a\n ref here wants to scroll something, and the root does not scroll. */\n useImperativeHandle(ref, () => viewport.current as HTMLDivElement, []);\n const content = useRef<HTMLDivElement | null>(null);\n const [following, setFollowing] = useState(true);\n /**\n * The tail this component last wrote, so it can tell it apart from content\n * and so an unchanged measurement does not touch the DOM on every frame of\n * an answer. `-1` because no measurement can produce it: a real one of zero\n * still has to be written down the first time.\n */\n const applied = useRef(-1);\n /**\n * The turn the room is measured against: the last one anchored, held on to\n * after the host lets go.\n *\n * A boolean would not do, and neither would reading `anchorId` live. The\n * demo drops the anchor when an answer settles — holding it for ever is\n * what pinned the composer below the fold — so a measurement that needed a\n * live anchor would lose its subject at the end of every answer and the\n * room would collapse under the reader. Which turn was last brought to the\n * top does not stop being true in between messages.\n */\n const anchors = useRef<string | null>(null);\n /**\n * Whether the room was actually measured against a turn that is on the page.\n *\n * Not the same question as whether this conversation anchors. A host can\n * name an anchor that is not there — a turn removed, an id that never\n * matched — and then there was nothing to size the room against and the end\n * of the scroll means nothing in particular. The end of the *content* is\n * the honest answer in that case.\n */\n const sized = useRef(false);\n\n /**\n * Where the last pixel of the last *turn* sits flush with the bottom edge.\n *\n * The last child rather than the wrapper, because the wrapper carries the\n * tail — the empty room under the conversation that lets a turn reach the\n * top. Measuring the wrapper would count that room as content and park the\n * reader a screen below the thing they are reading.\n */\n const endOfContent = useCallback(() => {\n const view = viewport.current;\n const inner = content.current;\n if (!view || !inner) return 0;\n\n /* When the kit sized the room, the end of the scroll is where the room\n was **aimed** — the anchored turn at the top — so that is where coming\n to rest means something.\n \n Measuring the end of the content instead only agrees with it while the\n answer is longer than the screen. Below that the room is bigger than\n the floor, the end of the scroll moves down with it, and resting at the\n end of the *content* leaves the difference unspent: measured in a\n 1400px window, 585px and 551px of it after the second and third\n answers, with the turn that had just been sent left in the middle of\n the view instead of at the top. The first fix for this was checked\n only against answers longer than the screen, where the two numbers are\n the same and nothing shows. */\n if (tail === \"auto\" && sized.current) {\n return Math.max(0, view.scrollHeight - view.clientHeight);\n }\n\n const last = inner.lastElementChild as HTMLElement | null;\n const bottom = last\n ? flowTop(last, view) + last.offsetHeight\n : /* No element to measure — a consumer whose children are bare text.\n The wrapper stands in, less the tail, which is padding this\n component put there itself and is emphatically not content. */\n flowTop(inner, view) + inner.offsetHeight - applied.current;\n return Math.max(0, bottom + endOffset - view.clientHeight);\n }, [endOffset, tail]);\n\n /**\n * The tail, measured rather than guessed. See the `tail` prop.\n *\n * Written as the token the stylesheet already reads, so there is one\n * mechanism rather than two, and written **on the wrapper** — a scroll\n * container cannot be shorter than its own padding, which is how a 99vh pad\n * once made a 773px scroller inside a 680px page.\n */\n const fitTail = useCallback(() => {\n const view = viewport.current;\n const inner = content.current;\n if (!view || !inner) return;\n if (tail !== \"auto\") return write(inner, applied, Math.max(0, tail));\n\n /* The conversation's own bottom padding is room under the last turn too,\n and it is already inside `scrollHeight`. Counting it twice puts the end\n of the scroll below the anchor by exactly that much, which is somewhere\n to scroll and nothing to see there. */\n const pad = parseFloat(getComputedStyle(view).paddingBottom) || 0;\n /* Never less than the end gap, so a turn taller than the screen still\n comes to rest with the composer clear of the bottom edge rather than\n jammed against it. */\n const floor = Math.max(0, endOffset - pad);\n\n const last = inner.lastElementChild as HTMLElement | null;\n const held = held0(view, anchors.current);\n sized.current = !!last && !!held;\n if (!sized.current || !last || !held) return write(inner, applied, floor);\n\n /* **From the anchored turn to the end of the conversation** — not the last\n turn's own height, which is what this measured first and is wrong by\n everything in between. The last turn is the composer, 44px of it, so an\n answer that already filled the screen still got a screen of room under\n it: 536px of nothing to scroll into after every answer.\n \n What the room is for is lifting the anchored turn to the top, and what\n stands between it and the bottom edge is the whole stack under it. So\n that is what comes off. */\n const stack = flowTop(last, view) + last.offsetHeight - flowTop(held, view);\n const needed = Math.max(floor, view.clientHeight - anchorOffset - stack - pad);\n\n /* ── While a turn is anchored, the room only grows ────────────────────\n The room's contract is *at least this much* — enough for the anchored\n turn to reach the anchor — and a floor does not drop while the thing\n standing on it is still standing.\n\n Letting it shrink on every frame made it a feedback loop. The room is\n part of `scrollHeight`, `target` clamps to `scrollHeight`, so the room\n moved the view, the view moved the measurements, and the measurements\n moved the room. Sized to be *exactly* enough, the clamp sat on a knife\n edge and a fraction of a pixel decided which side. Measured while a\n second answer was written: the message sat at y=100 and flipped to 101\n and back thirty-one times, with the room going 418, 379, 374, 372, 362,\n 360, 351 underneath it.\n\n Monotonic while held, and recomputed exactly the moment the anchor is\n let go — so a long answer carries some spare room under it until it\n settles, and nothing carries it afterwards. Growing is still allowed,\n which is what keeps this self-correcting: a first measurement taken\n before the layout is final is raised by the next one rather than\n standing for the whole answer. */\n write(inner, applied, anchorId ? Math.max(needed, applied.current) : needed);\n }, [tail, anchorOffset, endOffset, anchorId]);\n\n /**\n * Where the view wants to be: the anchor's top — unless holding it there\n * would push the anchored turn's own bottom off the screen.\n *\n * The second half is what this was missing. An anchor pins the turn you sent\n * to the top and holds it there while the answer is written underneath,\n * which is right and is the whole point of anchoring. It stops being right\n * the moment the turn is longer than the screen: the anchor does not move,\n * so the target does not move, and every line after the first screenful is\n * written below the fold with nothing to bring it back.\n *\n * So the anchor holds the top only while its own end is still in view, and\n * gives way to that end when it is not. The handover lands exactly where it\n * should, because the two are the same number at the moment the turn grows\n * to the height of the viewport.\n *\n * Note what this is **not**: the end of the *conversation*. Taking that\n * would follow whatever came after the anchored turn, and anchoring an older\n * turn — a jump to a message further up — would snap straight back to the\n * bottom. Only the anchored turn's own bottom is allowed to overrule its\n * top.\n */\n const target = useCallback(() => {\n const view = viewport.current;\n if (!view) return 0;\n\n if (anchorId) {\n const el = view.querySelector<HTMLElement>(`[id=\"${CSS.escape(anchorId)}\"]`);\n if (el) {\n const max = Math.max(0, view.scrollHeight - view.clientHeight);\n const from = flowTop(el, view);\n const top = from - anchorOffset;\n // Where the anchored turn's last pixel sits flush with the bottom.\n const end = from + el.offsetHeight - view.clientHeight;\n return Math.max(0, Math.min(Math.max(top, end), max));\n }\n }\n return endOfContent();\n }, [anchorId, anchorOffset, endOfContent]);\n\n /**\n * True while the way-back button's own scroll is still travelling.\n *\n * Without it that scroll never happened. `jump` asks for `behavior: smooth`\n * and then sets `following`, which re-runs the effect below, whose first act\n * is `view.scrollTop = target()` — an assignment, which cancels a smooth\n * scroll on the spot. Measured: the view went from 0 to 1088 inside 80ms,\n * so the button teleported instead of travelling and a reader lost their\n * place with nothing to follow.\n *\n * A ref rather than state: nothing renders differently for it, and a render\n * in the middle of a scroll is the last thing this wants.\n */\n const travelling = useRef(0);\n /** The last anchor this has already moved to, so it only moves once. */\n const moved = useRef<string | null>(null);\n\n const jump = useCallback(\n (smooth: boolean) => {\n const view = viewport.current;\n if (!view) return;\n const gentle = smooth && !prefersReducedMotion();\n travelling.current = gentle ? performance.now() + TRAVEL : 0;\n view.scrollTo({ top: target(), behavior: gentle ? \"smooth\" : \"auto\" });\n setFollowing(true);\n },\n [target]\n );\n\n /* ── The room to move into ─────────────────────────────────────────────\n Declared before the effect that keeps up, and that ordering is the point:\n observers fire in registration order, so the tail is the right size\n before anything reads where the end of the scroll now is. */\n useLayoutEffect(() => {\n /* Latched rather than read live. The demo lets go of the anchor once an\n answer settles — holding it for ever is what pinned the composer below\n the fold — so a tail that existed only while `anchorId` was set would\n collapse at the end of every answer and take the view with it. What is\n true is that this conversation *anchors*, and that does not stop being\n true between messages. */\n if (anchorId) anchors.current = anchorId;\n const view = viewport.current;\n const inner = content.current;\n fitTail();\n if (!view || !inner || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(fitTail);\n // The wrapper for the last turn's height, the viewport for the screen's.\n observer.observe(inner);\n observer.observe(view);\n return () => observer.disconnect();\n }, [anchorId, fitTail]);\n\n /* ── Keeping up ────────────────────────────────────────────────────────\n Layout effect, and the reason matters: run after paint and the answer is\n drawn one frame lower before the scroll catches up, which reads as a\n shudder on every frame of every answer. */\n useLayoutEffect(() => {\n /* An anchor is an instruction, not a preference: the host has said which\n turn to hold, so it is honoured whether or not the reader was following\n the end. Gating it on `following` meant that pressing anything — which\n now releases the follow, see below — also stopped the next message from\n being taken to the top. */\n if (!follow || (!following && !anchorId)) return;\n const view = viewport.current;\n const inner = content.current;\n if (!view || !inner || typeof ResizeObserver === \"undefined\") return;\n\n const keepUp = () => {\n const want = target();\n /* Let the way-back button's scroll finish rather than snapping past it —\n and notice here when it has arrived, rather than waiting for a scroll\n event. jsdom fires none, so a flag cleared only by that event stays\n set for ever and the view stops keeping up with the answer entirely.\n A test said so. */\n if (travelling.current) {\n if (performance.now() < travelling.current && Math.abs(view.scrollTop - want) > 1) return;\n travelling.current = 0;\n }\n\n /* ── Growth while nothing is being written ────────────────────────\n Tracking an answer has to be instant: easing the arrival of text\n means the line being read slides for a third of a second. But not\n everything that grows the conversation is an answer arriving.\n\n When one finishes, the next turn — the empty composer — is appended,\n and the content grows by its height in a single frame. Measured on\n the demo at 1100×700, at the moment the turn count went from one to\n two: `scrollTop` 0 → 182 in one frame and the message that had been\n sent leapt from 100 to −82, off the top of the view. Nothing was\n being read at that instant; the answer had just ended.\n\n The component can tell the two apart without being told, because the\n host lets go of the anchor when an answer settles. An anchor held\n means something is still being written, and growth is tracked.\n\n **`anchors.current` and not just `!anchorId`.** A conversation that\n never anchors — a plain transcript following its own end — has no\n anchor to hold at any moment, and there growth really is the answer\n arriving and must stay instant. Two tests said so, immediately. What\n this is about is an anchoring conversation between turns. */\n const gap = want - view.scrollTop;\n if (anchors.current && !anchorId && Math.abs(gap) > threshold && !prefersReducedMotion()) {\n travelling.current = performance.now() + TRAVEL;\n view.scrollTo({ top: want, behavior: \"smooth\" });\n return;\n }\n\n view.scrollTop = want;\n };\n\n /* ── Arriving at a new turn ──────────────────────────────────────────\n A sent message travels to the top rather than appearing there.\n\n Sending one adds a whole turn at once, so the content grows by its\n height in a single frame and the scroll used to be assigned the new\n position in that same frame. Measured across two answers at 1120×680:\n three moves over 24px in the whole session and every one of them a\n single frame — 154px, 416px, 172px — while the reasoning folding away\n at the end was gradual and cost nothing. The sharpness was never the\n settle; it was the send.\n\n So the one move that is a **decision** gets to be a journey, and the\n thousand that are tracking stay instant. Only when it is far enough to\n be worth watching — a short hop reads better snapped than eased. */\n const fresh = anchorId !== null && anchorId !== undefined && anchorId !== moved.current;\n if (anchorId) moved.current = anchorId;\n if (fresh && !prefersReducedMotion() && Math.abs(view.scrollTop - target()) > threshold) {\n travelling.current = performance.now() + TRAVEL;\n view.scrollTo({ top: target(), behavior: \"smooth\" });\n } else {\n keepUp();\n }\n\n const observer = new ResizeObserver(keepUp);\n observer.observe(inner);\n return () => observer.disconnect();\n /* `anchorId` is in here on purpose: a new turn means the view moves to it,\n and it moves whether or not the reader had scrolled away from the last\n one. Sending a message is asking to be taken to it. */\n }, [follow, following, target, anchorId, threshold]);\n\n /* ── Letting go ────────────────────────────────────────────────────────\n Intent, read from the input rather than inferred from the scroll event.\n A wheel or a drag upwards means the reader wants to be somewhere else,\n and that is true before the scroll has even happened. */\n useEffect(() => {\n if (!follow) return;\n const view = viewport.current;\n if (!view) return;\n\n const away = () => {\n // A reader who touches the view during the journey has taken it over.\n travelling.current = 0;\n if (Math.abs(view.scrollTop - target()) > threshold) setFollowing(false);\n };\n\n const onWheel = (event: WheelEvent) => {\n // Upwards means \"not here\" only when the place to be is above. With an\n // anchor held at the top, scrolling up is often how a reader returns to\n // it, so the distance decides rather than the direction.\n if (anchorId) away();\n else if (event.deltaY < 0) setFollowing(false);\n };\n const onKey = (event: KeyboardEvent) => {\n if ([\"ArrowUp\", \"PageUp\", \"Home\", \"ArrowDown\", \"PageDown\", \"End\"].includes(event.key)) {\n // Same reasoning: with an anchor, any key that moves the view is only\n // \"away\" if it lands somewhere else.\n if (anchorId) requestAnimationFrame(away);\n else if ([\"ArrowUp\", \"PageUp\", \"Home\"].includes(event.key)) setFollowing(false);\n }\n };\n\n /**\n * And a press inside the conversation, which is the one this was missing.\n *\n * Everything that folds open — a tool call, the reasoning, the sources —\n * grows the content, and growth is what makes the view keep up. So opening\n * one while sitting at the end pinned the *end* and dragged the thing you\n * had just opened up and off the top: measured, `scrollTop` 29 to 131 and\n * the header from 160px down the view to 58, which reads as the panel\n * opening upwards. With a long panel the header leaves the screen\n * entirely, and the control to shut it again with it.\n *\n * A press on **something that folds** is intent in exactly the way a wheel\n * is — you reached for this, here — so it lets go of the end and the\n * browser's own scroll anchoring keeps what you pressed where it was.\n *\n * Only those. The first cut released on any press at all, and that is too\n * much: clicking into the composer released it too, so when the answer\n * settled there was nothing left to bring the view back — and settling is\n * also when the reasoning block folds itself away, so the content shrank\n * by its height and everything above dropped into view. It read as the\n * conversation jumping to show an older message. A control that folds is\n * the one press that changes the layout under the reader, and it is the\n * only one that has to be answered this way.\n */\n const pressed = (event: PointerEvent) => {\n const on = event.target;\n if (on instanceof Element && on.closest(\"[aria-expanded]\")) setFollowing(false);\n };\n\n view.addEventListener(\"wheel\", onWheel, { passive: true });\n view.addEventListener(\"touchmove\", away, { passive: true });\n view.addEventListener(\"keydown\", onKey);\n view.addEventListener(\"pointerdown\", pressed, { passive: true });\n return () => {\n view.removeEventListener(\"wheel\", onWheel);\n view.removeEventListener(\"touchmove\", away);\n view.removeEventListener(\"keydown\", onKey);\n view.removeEventListener(\"pointerdown\", pressed);\n };\n }, [follow, threshold, target, anchorId]);\n\n /**\n * Back at the end by any route — dragging the bar, momentum, the button.\n *\n * **Arrived, not nearly.** This is the one place the component is allowed to\n * infer intent from the scroll event rather than from the input, and it has\n * to be strict about it: a reader on their way *up* passes through every\n * distance between here and there, so a generous band catches them mid-\n * gesture and hands them back to the bottom. See `ARRIVED`.\n */\n const handleScroll = useCallback(\n (event: React.UIEvent<HTMLDivElement>) => {\n onScroll?.(event);\n if (!follow) return;\n const view = viewport.current;\n if (!view) return;\n if (Math.abs(view.scrollTop - target()) <= ARRIVED) {\n // Arrived. Following resumes and the effect may set the scroll again.\n travelling.current = 0;\n setFollowing(true);\n }\n },\n [follow, onScroll, target]\n );\n\n const detached = follow && !following;\n\n return (\n <div className={[styles.root, className ?? \"\"].filter(Boolean).join(\" \")}>\n <div\n ref={viewport}\n className={[styles.viewport, viewportClassName ?? \"\"].filter(Boolean).join(\" \")}\n onScroll={handleScroll}\n {...rest}\n >\n <div ref={content} className={styles.content}>\n {children}\n </div>\n </div>\n\n {scrollButton && (\n <button\n type=\"button\"\n className={styles.jump}\n data-shown={detached || undefined}\n /* Out of the tab order while it is not offering anything, rather\n than merely invisible: a hidden control that still takes focus is\n a dead stop in the middle of a page. */\n tabIndex={detached ? 0 : -1}\n aria-hidden={detached ? undefined : true}\n onClick={() => jump(true)}\n aria-label={scrollButtonLabel}\n >\n <ArrowDown size={15} aria-hidden />\n </button>\n )}\n </div>\n );\n});\n","/**\n * Where the reply panel goes, as arithmetic.\n *\n * A function rather than ten lines inside the render, and the reason is that\n * it could not otherwise be tested. The panel's geometry reaches the DOM\n * through Motion's `animate` prop, and Motion animations never complete under\n * jsdom — so a test that reads `panel.style.left` reads the `initial` value\n * for ever, which is the anchor. The first guard written for this passed\n * against the broken version because of it.\n */\n\nexport interface Anchor {\n left: number;\n top: number;\n width: number;\n}\n\nexport interface Placement {\n x: number;\n y: number;\n width: number;\n /**\n * The room it has between where it starts and the bottom of the screen.\n *\n * The panel hangs off a phrase and grows downwards as the thread fills, and\n * nothing used to stop it growing past the bottom edge. Measured on a 390px\n * phone with a passage 556px down: after one reply the panel ended at 920\n * in an 844px screen, and every message after that pushed more of it\n * further out of reach. This is what it may use, so the thread scrolls\n * inside it instead of running off the end of the world.\n */\n maxHeight: number;\n}\n\n/** Its clearance from every edge of the screen. */\nconst PADDING = 24;\n/** Narrower than this and a thread is a column of two-word lines. */\nconst IDEAL = 480;\n/** Room either side of the passage it hangs off, when that is the wider one. */\nconst AROUND = 80;\n/**\n * The least it is worth opening at: a heading, a line of the passage, and\n * somewhere to type.\n *\n * A phrase near the bottom of the screen would otherwise be answered with a\n * panel twenty pixels tall. Below this the panel stops following the phrase\n * down and sits where it can still be used — which is the one case where\n * being beside the passage has to give way to being usable at all.\n */\nconst LEAST = 260;\n\n/**\n * **Fit first, then place.** That order is the whole of it.\n *\n * The version before this widened to 480 whatever the screen was, then ran two\n * clamps in a row: push the left edge in to the padding, then push the right\n * edge in to `screenWidth - padding`. For a box that does not fit, the second\n * always undoes the first — on a 390px phone it finished at `390 - 24 - 480`,\n * so the panel sat at **x = −114** with its heading off the side of the world.\n * Clamping a box into a space smaller than itself has no answer; the box has\n * to be made smaller first.\n */\nexport function placePanel(anchor: Anchor, screenWidth: number, screenHeight = Infinity): Placement {\n const room = Math.max(screenWidth - PADDING * 2, 0);\n const width = Math.min(Math.max(anchor.width + AROUND, IDEAL), room);\n\n // Centred on the passage, then pulled inside whichever edge it crossed.\n const centred = anchor.left + anchor.width / 2 - width / 2;\n const x = Math.min(Math.max(centred, PADDING), screenWidth - PADDING - width);\n\n /* Above the passage, but never off the top — a phrase marked in an answer's\n first line would otherwise open its thread where nobody can read it — and\n never so far down that there is no room left underneath. */\n const lowest = Math.max(PADDING, screenHeight - PADDING - LEAST);\n const y = Math.min(\n Math.max(anchor.top - PADDING, Math.min(PADDING, screenHeight)),\n Number.isFinite(lowest) ? lowest : Infinity\n );\n\n /* And what is left between there and the bottom edge. Finite screens only:\n `screenHeight` defaults to `Infinity` for callers that do not care, and a\n panel told it may be infinitely tall is a panel with no constraint, which\n is what it had before. */\n const maxHeight = Number.isFinite(screenHeight)\n ? Math.max(LEAST, screenHeight - y - PADDING)\n : Infinity;\n\n return { x, y, width, maxHeight };\n}\n","/* ─────────────────────────────────────────────\n ReplyThreadPopup\n\n This component had no stylesheet at all — seventeen inline `style={{}}`\n blocks, which meant nothing about it could be themed and nothing about it\n respected the tokens the rest of the kit reads.\n\n What stays inline is only what changes per render: the panel's animated\n geometry, which Motion owns.\n ───────────────────────────────────────────── */\n\n.backdrop {\n position: fixed;\n inset: 0;\n z-index: var(--ick-z-modal);\n display: block;\n pointer-events: auto;\n}\n\n.panel {\n position: absolute;\n box-sizing: border-box;\n /* A column, so the thread inside can be told to take what is left rather\n than a number. The panel's own ceiling comes from `placePanel`, which\n knows how much room there is between where it opens and the bottom of\n the screen — see `maxHeight` there. */\n display: flex;\n padding: var(--ick-space-2);\n overflow: hidden;\n transform-origin: center center;\n z-index: var(--ick-z-raised);\n /* Set here rather than animated. Motion was fading the background and the\n shadow in alongside the panel's own opacity, which is the same picture\n with two more properties to keep in step — and two more colours that\n could not come from a token. */\n background-color: var(--ick-surface-raised);\n box-shadow: var(--ick-shadow-modal);\n}\n\n/* ── On a phone ──────────────────────────────────────────────────────────\n The thread stops hanging off the phrase and comes up from the bottom.\n\n Hanging off a phrase works where there is somewhere to hang: beside the\n passage, above it, with room underneath. A phone has none of that. Measured\n on a 390×844 screen with a passage 556px down, the panel ended at 920 after\n a single reply — 76px past the bottom edge, and every message after it\n pushed more out of reach. Capping the height stops it running off the\n screen and turns it into a letterbox instead: 289px of thread, in the lower\n third, under a phrase you can no longer see.\n\n So it becomes what the artifact pane already becomes here, with the same\n tokens and the same gesture. Geometry is stated here rather than animated,\n because Motion writes `left`/`top`/`width` inline and no rule in this file\n could outrank that — see the note on the variants. */\n.panel[data-sheet] {\n position: fixed;\n inset: auto 0 0 0;\n width: auto;\n /* As tall as the thread, and no taller than the artifact sheet would be.\n \n Fixed at the sheet height it left most of a screen of white under the\n input on a thread with one reply in it — which is what a thread usually\n has, since the whole idea is one question about one passage. A sheet that\n is mostly empty reads as something that failed to load. It grows from the\n bottom edge as the thread fills, which is the direction a sheet grows. */\n height: auto;\n max-height: var(--ick-sheet-height);\n /* Top corners only. The bottom two are off the screen, and rounding them\n puts a sliver of the page under a sheet that is supposed to be resting on\n the edge. */\n border-radius: var(--ick-sheet-radius) var(--ick-sheet-radius) 0 0;\n /* The drag is the sheet's; the thread inside keeps its own scrolling. */\n touch-action: none;\n}\n\n.panel[data-sheet] .feed {\n /* No ceiling of its own here: the sheet's is the one that counts, and two\n would leave the shorter one deciding for reasons nobody can see. */\n max-height: none;\n height: auto;\n overscroll-behavior: contain;\n touch-action: pan-y;\n}\n\n/* Says it can be pulled, and gives a thumb somewhere to pull that is not a\n control. Same shape as the artifact sheet's. */\n.grabber {\n display: block;\n flex-shrink: 0;\n width: var(--ick-sheet-grabber-width);\n height: var(--ick-sheet-grabber-height);\n margin: 0 auto var(--ick-space-2);\n border-radius: var(--ick-radius-pill);\n background: var(--ick-sheet-grabber);\n}\n\n/* The panel is `replyTargetWidth` wide with 4px of padding, so the column is\n simply the space left over. It used to be told `replyTargetWidth - 8` from\n JavaScript. */\n.column {\n width: 100%;\n display: flex;\n flex-direction: column;\n /* `min-height: 0` on every link of the chain, or a flex item refuses to be\n shorter than its content and the panel grows past its own ceiling\n regardless. */\n min-height: 0;\n flex: 1;\n}\n\n/* ── Header ─────────────────────────────────────────────────────────────── */\n\n.header {\n display: flex;\n align-self: stretch;\n align-items: center;\n justify-content: center;\n gap: var(--ick-space-2);\n border-radius: var(--ick-radius-lg);\n padding: 6px var(--ick-space-6) var(--ick-space-5);\n}\n\n.headerLabel {\n display: flex;\n flex: 1;\n align-items: center;\n gap: var(--ick-space-2);\n color: var(--ick-ink-soft);\n}\n\n.headerIcon {\n width: 16px;\n height: auto;\n overflow: visible;\n flex-shrink: 0;\n /* Inherits, so the icon and its label cannot drift apart. */\n fill: currentColor;\n}\n\n.headerText {\n margin: 0;\n font-family: var(--ick-font-sans);\n font-size: var(--ick-text-xs);\n font-weight: var(--ick-weight-medium);\n letter-spacing: 0.01em;\n line-height: 14px;\n}\n\n.headerActions {\n display: flex;\n align-items: center;\n gap: var(--ick-space-4);\n}\n\n/* ── Feed ───────────────────────────────────────────────────────────────── */\n\n.feedClip {\n position: relative;\n border-radius: var(--ick-radius-xl);\n overflow: hidden;\n min-height: 0;\n flex: 1;\n}\n\n/* The fades that say there is more above or below. */\n.fade {\n position: absolute;\n left: 0;\n right: 0;\n height: 48px;\n pointer-events: none;\n z-index: var(--ick-z-sticky);\n opacity: 0;\n transition: opacity var(--ick-duration-base) ease;\n}\n\n.fadeTop {\n top: 0;\n background: linear-gradient(to bottom, var(--ick-surface) 0%, transparent 100%);\n}\n\n.fadeBottom {\n bottom: 0;\n background: linear-gradient(to top, var(--ick-surface) 0%, transparent 100%);\n}\n\n.fade[data-visible] {\n opacity: 1;\n}\n\n.feed {\n background-color: var(--ick-surface);\n border-radius: var(--ick-radius-xl);\n /* Two ceilings, and they mean different things. This one is taste — a\n thread taller than this is a conversation and belongs in the main feed.\n The other is the screen, and it is the panel's. */\n max-height: 640px;\n height: 100%;\n box-sizing: border-box;\n overflow-y: auto;\n scrollbar-width: none;\n}\n\n/* This used to be a `<style>` element rendered inside the component, carrying\n `div::-webkit-scrollbar { display: none }` — an unscoped rule that hid the\n scrollbar on every div in the host application for as long as the thread\n was open. */\n.feed::-webkit-scrollbar {\n display: none;\n}\n\n.feedInner {\n display: flex;\n flex-direction: column;\n align-items: start;\n padding: var(--ick-space-6);\n}\n\n/* ── Text ───────────────────────────────────────────────────────────────── */\n\n.quote,\n.answer {\n display: inline-block;\n color: var(--ick-ink);\n font-family: var(--ick-font-sans);\n font-size: var(--ick-text-md);\n letter-spacing: 0.01em;\n line-height: 150%;\n}\n\n/* ── Turns ──────────────────────────────────────────────────────────────── */\n\n.turn {\n display: flex;\n flex-direction: column;\n width: 100%;\n gap: var(--ick-space-8);\n margin-top: var(--ick-space-8);\n}\n\n/* A settled turn sits to the right, as a bubble; one still being written\n stretches, because it is an input. */\n.turnRow {\n display: flex;\n align-self: flex-end;\n justify-content: flex-end;\n max-width: 100%;\n width: auto;\n}\n\n.turnRow[data-editing] {\n align-self: stretch;\n justify-content: stretch;\n width: 100%;\n}\n\n/* The input has a fixed width of its own, so stretching the row is not enough\n — the child has to be told. Reached as a direct child rather than by class,\n since its class name is hashed and belongs to another module. */\n.turnRow[data-editing] > * {\n width: 100%;\n max-width: 100%;\n}\n\n/* Screen readers only: names the dialog together with the quoted passage. */\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n","\"use client\";\n\nimport { useState, useRef, useEffect, useId } from \"react\";\nimport { motion, MotionConfig } from \"motion/react\";\nimport { X, Save } from \"lucide-react\";\nimport { ChatInput, defaultInlineAnimConfig, type ChatInputHandle } from \"../ChatInput/ChatInput\";\nimport { useChatTurns, type ChatTurn, type SendContext } from \"../useChatTurns/useChatTurns\";\n\n/** @deprecated Use `ChatTurn`. Kept so existing imports keep resolving. */\nexport type Turn = ChatTurn;\nimport { Button } from '../Button/Button';\nimport { placePanel } from \"./placePanel\";\nimport styles from \"./ReplyThreadPopup.module.css\";\n\nexport interface ReplyThreadPopupProps {\n activeReply: { text: string; rect: DOMRect };\n onClose: () => void;\n onSave?: () => void;\n /**\n * Produce the reply to a message sent inside the thread. Receives the\n * message and the passage the thread hangs off. Required — the popup has no\n * answers of its own to fall back on.\n */\n onSendMessage: (\n message: string,\n quotedText: string,\n context: SendContext\n ) => AsyncIterable<string> | Promise<string> | string;\n}\n\n/**\n * Below this the thread stops hanging off the phrase and comes up from the\n * bottom instead. Kept with the stylesheet, and with `ChatLayout`'s own\n * breakpoint — two sheets in one kit that disagree about what a phone is\n * would be worse than either choice.\n */\nconst NARROW = 760;\n\n/** Coming up from the bottom, and going back down. */\nconst sheet = {\n closed: { y: \"100%\", opacity: 1, transition: { type: \"spring\" as const, visualDuration: 0.28, bounce: 0 } },\n open: { y: 0, opacity: 1, transition: { type: \"spring\" as const, visualDuration: 0.42, bounce: 0.18 } },\n};\n\n/** Everything inside the panel that can take focus, in tab order. */\nconst FOCUSABLE =\n 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [contenteditable]:not([contenteditable=\"false\"]), [tabindex]:not([tabindex=\"-1\"])';\n\nexport function ReplyThreadPopup({ activeReply, onClose, onSave, onSendMessage }: ReplyThreadPopupProps) {\n const [threadFade, setThreadFade] = useState<\"none\" | \"top\" | \"bottom\" | \"both\">(\"none\");\n const threadActiveInputRef = useRef<ChatInputHandle>(null);\n const threadFeedRef = useRef<HTMLDivElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n /**\n * Where focus came from. Read during the first render rather than in an\n * effect: a child's effects run before its parent's, and the thread's input\n * focuses itself in one of them — so by the time an effect here could look,\n * the answer would already be \"the input\", and closing would restore focus\n * to an element that no longer exists.\n */\n const [returnFocusTo] = useState<HTMLElement | null>(() =>\n typeof document === \"undefined\" ? null : (document.activeElement as HTMLElement | null)\n );\n const labelId = useId();\n const quoteId = useId();\n const animConfig = defaultInlineAnimConfig;\n\n /**\n * Whether this is a phone, watched rather than read once.\n *\n * `matchMedia` on the window rather than a container query like\n * `ChatLayout` uses, and the difference is real: that pane lives inside the\n * conversation's own column and has to answer to it, while this is fixed\n * over the whole screen. What matters here is the screen.\n */\n /* Guarded on the **result**, not just on the function. `?.()` says the\n method may be missing; it says nothing about what comes back, and a test\n environment that stubs `matchMedia` with something returning nothing\n brought the whole dialog down on `undefined.matches`. An axe test found\n it, which is the sort of thing the accessibility pass is quietly good\n for. */\n const [narrow, setNarrow] = useState(() =>\n typeof window === \"undefined\"\n ? false\n : (window.matchMedia?.(`(max-width: ${NARROW}px)`)?.matches ?? false)\n );\n useEffect(() => {\n const query = window.matchMedia?.(`(max-width: ${NARROW}px)`);\n if (!query?.addEventListener) return;\n const answer = () => setNarrow(query.matches);\n answer();\n query.addEventListener(\"change\", answer);\n return () => query.removeEventListener(\"change\", answer);\n }, []);\n\n // Turn state, streaming and the reveal are the same problem here as in the\n // main feed, so they come from the same place rather than being written\n // twice. The quoted passage is closed over and handed to the host app.\n const quoted = activeReply.text;\n const {\n turns: threadTurns,\n setDraft,\n submit,\n stop,\n beginEdit,\n } = useChatTurns({\n onSend: (message, context) => onSendMessage(message, quoted, context),\n });\n\n /**\n * A dialog owns focus while it is open, and gives it back when it closes.\n *\n * Without the first half, someone who opened this from the keyboard is left\n * behind on a page they can no longer see. Without the second, closing it\n * drops them at the top of the document with no idea where they were.\n */\n useEffect(() => {\n // The input, not the panel: writing a reply is the only reason to be here.\n if (threadActiveInputRef.current) threadActiveInputRef.current.focus();\n else panelRef.current?.focus();\n\n return () => {\n if (returnFocusTo?.isConnected) returnFocusTo.focus();\n };\n }, [returnFocusTo]);\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Escape\") {\n // Something inside may have wanted it first — a highlight menu open on\n // one of the thread's own answers, for instance. Those mark the event as\n // handled; this only acts on what is left.\n if (e.defaultPrevented) return;\n e.preventDefault();\n onClose();\n return;\n }\n\n if (e.key !== \"Tab\") return;\n const items = [...(panelRef.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? [])];\n if (items.length === 0) return;\n\n const first = items[0];\n const last = items[items.length - 1];\n const active = document.activeElement;\n\n // Wrap at both ends, so tab cannot walk out of the dialog onto a page the\n // reader can no longer see.\n if (e.shiftKey && active === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && active === last) {\n e.preventDefault();\n first.focus();\n }\n };\n\n const updateThreadFade = () => {\n const wrap = threadFeedRef.current;\n if (!wrap) return;\n \n /* Whether there is anything out of sight, which is the only question the\n fades answer.\n \n This used to also require `clientHeight >= 630` — the thread's old\n fixed ceiling, written again as a number. The panel takes the room the\n screen leaves it now, so on a phone it is rarely 630 tall and the fades\n would have stopped appearing exactly where they are most needed: the\n short panel is the one whose content does not fit. Overflow is the\n question; height was a proxy for it. */\n const hasOverflow = wrap.scrollHeight - wrap.clientHeight > 2;\n \n if (!hasOverflow) { \n setThreadFade(\"none\"); \n return; \n }\n \n const atTop = wrap.scrollTop <= 2;\n const atBottom = wrap.scrollTop + wrap.clientHeight >= wrap.scrollHeight - 2;\n if (!atTop && !atBottom) setThreadFade(\"both\");\n else if (!atTop) setThreadFade(\"top\");\n else if (!atBottom) setThreadFade(\"bottom\");\n else setThreadFade(\"none\");\n };\n\n useEffect(() => {\n const wrap = threadFeedRef.current;\n if (!wrap) return;\n \n const handleScroll = () => updateThreadFade();\n wrap.addEventListener(\"scroll\", handleScroll, { passive: true });\n \n const resizeObserver = new ResizeObserver(() => {\n updateThreadFade();\n });\n \n resizeObserver.observe(wrap);\n if (wrap.firstElementChild) {\n resizeObserver.observe(wrap.firstElementChild);\n }\n \n updateThreadFade(); \n return () => {\n wrap.removeEventListener(\"scroll\", handleScroll);\n resizeObserver.disconnect();\n };\n }, [threadTurns]);\n\n // Keep the newest turn in view as the answer grows.\n useEffect(() => {\n const feed = threadFeedRef.current;\n if (!feed) return;\n feed.scrollTo({ top: feed.scrollHeight, behavior: \"smooth\" });\n }, [threadTurns]);\n\n /* Fit first, then place — see `placePanel`, which is a function precisely\n so the arithmetic can be tested without a browser. */\n const { x: replyTargetX, y: replyTargetY, width: replyTargetWidth, maxHeight } =\n typeof window === \"undefined\"\n ? { x: 0, y: 0, width: 480, maxHeight: Infinity }\n : placePanel(activeReply.rect, window.innerWidth, window.innerHeight);\n\n\n return (\n // See the note in ChatInput: the kit sets this itself rather than relying\n // on the host app to.\n <MotionConfig reducedMotion=\"user\">\n <div\n className={styles.backdrop}\n onClick={onClose}\n onKeyDown={handleKeyDown}\n >\n <motion.div\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n // Named by both: the phrase gives it meaning, the passage says which\n // thread. `aria-modal` is the whole claim to modality — marking the\n // rest of the page inert would mean reaching outside this component,\n // into a document it does not own.\n aria-labelledby={`${labelId} ${quoteId}`}\n tabIndex={-1}\n onClick={(e) => e.stopPropagation()}\n /* Two shapes, and only one of them is placed.\n \n Beside the phrase, the panel travels from the passage's own box to\n where it is going, so it reads as the passage opening. On a phone\n there is nowhere for it to go — a panel hanging off a phrase near\n the bottom of the screen is a panel mostly below it, and capping\n its height only turns that into a letterbox. So it comes up from\n the bottom instead, and its geometry belongs to the stylesheet:\n Motion writes `left`/`top`/`width` as inline styles, which no media\n query can override, so the sheet's variants must not mention them\n at all. */\n data-sheet={narrow || undefined}\n initial={narrow ? \"closed\" : {\n left: activeReply.rect.left, \n top: activeReply.rect.top, \n width: activeReply.rect.width, \n borderRadius: 12,\n opacity: 0\n }}\n animate={narrow ? \"open\" : { \n left: replyTargetX, \n top: replyTargetY, \n width: replyTargetWidth, \n borderRadius: 28,\n opacity: 1,\n }}\n exit={narrow ? \"closed\" : { \n opacity: 0,\n scale: 0.95,\n filter: \"blur(4px)\"\n }}\n variants={narrow ? sheet : undefined}\n transition={narrow ? undefined : { type: \"spring\", bounce: 0, duration: 0.4 }}\n {...(narrow\n ? {\n /* Dragged down to put away, like the artifact sheet. Distance\n **or** speed: distance alone loses a quick flick, speed alone\n dismisses a slow nudge that was meant to peek. */\n drag: \"y\" as const,\n dragConstraints: { top: 0, bottom: 0 },\n dragElastic: { top: 0, bottom: 0.6 },\n dragMomentum: false,\n onDragEnd: (\n _e: unknown,\n info: { offset: { y: number }; velocity: { y: number } }\n ) => {\n if (info.offset.y > 120 || info.velocity.y > 600) onClose();\n },\n }\n : {})}\n /* A ceiling rather than an animated property: how much room there is\n between the panel and the bottom of the screen is a fact about the\n screen, not a thing to travel to. Motion owns everything in\n `animate`; this sits beside it. */\n style={!narrow && Number.isFinite(maxHeight) ? { maxHeight } : undefined}\n className={styles.panel}\n >\n <div className={styles.column}>\n {/* Says the sheet can be pulled, and gives a thumb somewhere to pull\n it that is not a control. Drawn only where it means something. */}\n {narrow && <div className={styles.grabber} aria-hidden />}\n <div className={styles.header}>\n <div className={styles.headerLabel}>\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\" width=\"100\" height=\"100\" className={styles.headerIcon} aria-hidden>\n <path d=\"m85.1 21c1.5-2.2 4.6-6.7 5.8-11 0.7-2.5 0.3-4.7-1.6-6.1-1.6-1.4-4.3-1.7-7.3-0.4-3.2 1.2-7.3 4.6-12 9.1l-0.2 0.2c-0.9-0.3-1.9-0.6-2.9-0.7-7.9-1.2-14.7 0-20 3.6-6.8 4.6-10.8 12.3-9.5 22.6 0.1 1.4 1.2 2.4 2.7 2.3 1.4 0 2.6-1.4 2.4-2.8-1.1-6.8 1-12.9 6.5-16.9 3.9-2.9 9.1-4.8 16.5-3.9l-35.4 39.5c0 0.3 4.1 2.8 4.2 2.8 1-0.9 4-4.4 5.2-5.7l27.2-30.6c-0.4 3.3 0.6 5.8 5.6 5.1-8.4 9.4-25.2 25.2-33.5 33.7 0.5 0.5 4 2 4.8 2.3 7.8-7.2 25.4-24 34.8-34.5l3.5-4.6c4.1 3.7 11.7 11.9 11.7 24.7 0 9.6-6 20-20 22.7-8.2 1.2-17.3 0.1-26.4-3.5-8.5-3.1-15.7-7.8-22.3-13.1-7-6-13.3-13.5-14.9-21.8-0.9-4.9 0.2-9.7 4.1-12.9 2.8-2.4 6.5-4.2 12-4.3 3.8-0.1 7.3 0.4 11.9 2.1 1.2 0.6 2.8 0.4 3.4-0.8 0.8-1.1 0.2-3.2-1.1-3.6-5.2-1.9-9-2.9-14-2.9-5.7 0.1-10.7 1.5-14.9 4.9-4.7 3.8-8.5 10.3-5.8 20 2.4 9.2 9.5 17.3 19.5 25.4l-20.8 27.8c-1.4 1.7-2.3 2.8-2.3 3.9-0.2 1.5 0.6 2.7 1.6 3.3 1.3 0.8 3.4 0.6 4.7-0.3 3.4-2.2 15.8-13.1 29.7-26.7 6.5 3 16.5 7 27.9 7.8 5.6 0.2 10.7-0.2 15.4-1.9 10.8-3.5 17.2-13 17.1-25.8 0-11.9-6.7-22.3-13.3-29zm-73.3 65.7 17.5-21.9 4.1 2.5c-5.8 5.2-13.6 12.4-21.6 19.4zm64.9-63.3c-1.7 0.7-4.7 2-6.6 1.9-0.7-0.4 1.5-6.5 1.7-6.5 2.1 0.9 4.7 2 6.1 3.1l-1.2 1.5zm4.2-5.5c-1.6-1.1-3.7-2.3-5.5-3.2 2.1-1.9 4.7-4 6.3-5.1 1.4-0.9 3.2-0.5 3 1.4-0.3 2-3 5.8-3.8 6.9z\" />\n </svg>\n <p className={styles.headerText}>Replying in a thread</p>\n </div>\n <div className={styles.headerActions}>\n <Button\n variant=\"primary\"\n icon={<Save size={16} />}\n onClick={onSave}\n >\n Save\n </Button>\n <Button\n variant=\"secondary\"\n icon={<X size={16} />}\n onClick={onClose}\n aria-label=\"Close thread\"\n />\n </div>\n </div>\n\n <div className={styles.feedClip}>\n <div\n className={`${styles.fade} ${styles.fadeTop}`}\n data-visible={threadFade === \"top\" || threadFade === \"both\" || undefined}\n />\n <div\n className={`${styles.fade} ${styles.fadeBottom}`}\n data-visible={threadFade === \"bottom\" || threadFade === \"both\" || undefined}\n />\n <div ref={threadFeedRef} className={styles.feed}>\n <div className={styles.feedInner}>\n <span id={labelId} className={styles.srOnly}>\n Thread on\n </span>\n <div id={quoteId} className={styles.quote}>\n {activeReply.text}\n </div>\n \n {threadTurns.map((turn, i) => {\n const isActiveInput =\n i === threadTurns.length - 1 &&\n (turn.state === \"idle\" || turn.state === \"typing\");\n \n const isInputMode = turn.state === \"idle\" || turn.state === \"typing\";\n \n return (\n <motion.div\n key={turn.id}\n id={`thread-turn-${turn.id}`}\n aria-busy={turn.state === \"responding\" || undefined}\n initial={{ opacity: 0, y: 8, filter: \"blur(4px)\" }}\n animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n transition={{ type: \"spring\", stiffness: 260, damping: 30, mass: 0.8 }}\n className={styles.turn}\n >\n <div className={styles.turnRow} data-editing={isInputMode || undefined}>\n <ChatInput\n ref={isActiveInput ? threadActiveInputRef : null}\n state={turn.state}\n value={turn.user}\n onChange={(v) => setDraft(turn.id, v)}\n onSubmit={(v) => submit(turn.id, v)}\n onStop={stop}\n onCopy={() => navigator.clipboard.writeText(turn.ai)}\n onEdit={() => beginEdit(turn.id)}\n animationConfig={animConfig}\n placeholder=\"Ask me about this text...\"\n />\n </div>\n \n {turn.ai && (\n <div className={styles.answer}>{turn.ai}</div>\n )}\n </motion.div>\n );\n })}\n </div>\n </div>\n </div>\n </div>\n </motion.div>\n </div>\n </MotionConfig>\n );\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { motion, AnimatePresence, useMotionValue } from \"motion/react\";\n\nexport function CustomCursor() {\n const x = useMotionValue(-100);\n const y = useMotionValue(-100);\n const [variant, setVariant] = useState<\"default\" | \"marker\" | \"text\">(\"default\");\n const [isActive, setIsActive] = useState(false);\n const [isVisible, setIsVisible] = useState(false);\n\n useEffect(() => {\n const handleMove = (e: PointerEvent) => {\n if (!isVisible) setIsVisible(true);\n x.set(e.clientX);\n y.set(e.clientY);\n \n const target = e.target as Element | null;\n if (!target) return;\n \n const cursorEl = target.closest('[data-cursor]');\n const activeEl = target.closest('[data-cursor-active=\"true\"]');\n const controlEl = target.closest('a, button, [role=\"button\"], input, select, textarea, [tabindex]');\n\n if (activeEl || e.buttons > 0) {\n setIsActive(true);\n } else {\n setIsActive(false);\n }\n\n // Whichever of the two is nearer to the pointer wins. A button sitting\n // inside a marker surface should still show the arrow — but a marker\n // surface that is itself focusable is not a button, and `[tabindex]`\n // alone cannot tell the two apart.\n const controlWins =\n !!controlEl && (!cursorEl || (controlEl !== cursorEl && cursorEl.contains(controlEl)));\n\n if (controlWins || !cursorEl) {\n setVariant(\"default\");\n } else {\n const customVariant = cursorEl.getAttribute(\"data-cursor\");\n setVariant(customVariant === \"marker\" ? \"marker\" : customVariant === \"text\" ? \"text\" : \"default\");\n }\n };\n \n const handleDown = () => setIsActive(true);\n const handleUp = () => setIsActive(false);\n const handleLeave = () => setIsVisible(false);\n const handleEnter = () => setIsVisible(true);\n\n window.addEventListener(\"pointermove\", handleMove);\n window.addEventListener(\"pointerdown\", handleDown);\n window.addEventListener(\"pointerup\", handleUp);\n document.addEventListener(\"mouseleave\", handleLeave);\n document.addEventListener(\"mouseenter\", handleEnter);\n \n return () => {\n window.removeEventListener(\"pointermove\", handleMove);\n window.removeEventListener(\"pointerdown\", handleDown);\n window.removeEventListener(\"pointerup\", handleUp);\n document.removeEventListener(\"mouseleave\", handleLeave);\n document.removeEventListener(\"mouseenter\", handleEnter);\n };\n }, [isVisible, x, y]);\n\n if (!isVisible) return null;\n\n return (\n <motion.div\n style={{\n position: \"fixed\",\n top: 0,\n left: 0,\n pointerEvents: \"none\",\n zIndex: \"var(--ick-z-cursor)\",\n x,\n y,\n }}\n >\n <motion.div\n animate={{\n scale: isActive ? 0.8 : 1,\n x: variant === \"marker\" ? -2 : variant === \"text\" ? -7 : 0,\n y: variant === \"marker\" ? -12 : variant === \"text\" ? -10 : 0,\n }}\n transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n >\n <AnimatePresence mode=\"popLayout\">\n {variant === \"default\" && (\n <motion.div\n key=\"default\"\n initial={{ scale: 0, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n exit={{ scale: 0, opacity: 0 }}\n transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n >\n <svg width=\"19\" height=\"19\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\" style={{ filter: \"drop-shadow(0 1px 2px rgb(var(--ick-glass-shade-rgb) / 0.15))\" }}>\n <path d=\"M2.691 3.125C2.665 3.065 2.658 2.997 2.67 2.932C2.682 2.867 2.714 2.808 2.761 2.761C2.808 2.714 2.867 2.682 2.932 2.67C2.997 2.658 3.065 2.665 3.125 2.691L13.792 7.025C13.857 7.051 13.912 7.097 13.949 7.157C13.986 7.216 14.003 7.286 13.999 7.355C13.994 7.425 13.968 7.492 13.923 7.546C13.878 7.6 13.818 7.638 13.75 7.656L9.667 8.709C9.437 8.769 9.226 8.889 9.057 9.057C8.889 9.225 8.768 9.435 8.709 9.666L7.656 13.75C7.638 13.818 7.6 13.878 7.546 13.923C7.492 13.968 7.425 13.994 7.355 13.999C7.286 14.003 7.216 13.986 7.157 13.949C7.097 13.912 7.051 13.857 7.025 13.792L2.691 3.125Z\" fill=\"var(--ick-cursor-fill)\" stroke=\"var(--ick-cursor-ink)\" strokeWidth=\"1.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </motion.div>\n )}\n {variant === \"text\" && (\n <motion.div\n key=\"text\"\n initial={{ scale: 0.5, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n exit={{ scale: 0.5, opacity: 0 }}\n transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n >\n <svg width=\"14\" height=\"20\" viewBox=\"0 0 14 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n {/* white halo for visibility on any background */}\n <path d=\"M7 3 V17 M4 3 H10 M4 17 H10\" stroke=\"var(--ick-cursor-fill)\" strokeWidth=\"3.4\" strokeLinecap=\"round\" />\n <path d=\"M7 3 V17 M4 3 H10 M4 17 H10\" stroke=\"var(--ick-cursor-ink)\" strokeWidth=\"1.4\" strokeLinecap=\"round\" />\n </svg>\n </motion.div>\n )}\n {variant === \"marker\" && (\n <motion.div\n key=\"marker\"\n initial={{ scale: 0.5, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n exit={{ scale: 0.5, opacity: 0 }}\n transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n style={{\n filter: isActive \n ? \"drop-shadow(0px 1px 0px rgb(var(--ick-ink-rgb) / 0.2))\" \n : \"drop-shadow(0px 3px 0px rgb(var(--ick-ink-rgb) / 0.2))\"\n }}\n >\n <svg width=\"27\" height=\"16\" viewBox=\"0 0 34 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M9.383 7.767L25.309 1.111C27.347 0.259 29.69 1.22 30.542 3.259L32.085 6.949C32.937 8.987 31.975 11.33 29.937 12.182L14.011 18.839L5.353 17.813L3.251 18.692L1.228 15.821L4.03 14.65L9.383 7.767Z\" fill=\"var(--ick-cursor-fill)\" />\n <path d=\"M4.03 14.65L9.383 7.767L25.309 1.111C27.347 0.259 29.69 1.22 30.542 3.259L32.085 6.949C32.937 8.987 31.975 11.33 29.937 12.182L14.011 18.839L5.353 17.813M9.383 7.767L14.011 18.839M4.03 14.65L1.228 15.821L3.251 18.692L5.353 17.813M4.03 14.65L5.353 17.813\" stroke=\"var(--ick-cursor-ink)\" strokeWidth=\"1.6\" />\n </svg>\n </motion.div>\n )}\n </AnimatePresence>\n </motion.div>\n </motion.div>\n );\n}\n","\"use client\";\n\nimport { forwardRef, type ButtonHTMLAttributes, type ReactNode } from \"react\";\nimport { Button } from \"../Button/Button\";\n\nexport type GlassButtonSize = \"s\" | \"m\" | \"l\";\n\nexport interface GlassButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n children: ReactNode;\n /** Show loading spinner and disable interaction */\n loading?: boolean;\n /** Leading icon element */\n iconLeft?: ReactNode;\n /** Trailing icon element */\n iconRight?: ReactNode;\n /** Button size: s=32px, m=40px, l=48px */\n size?: GlassButtonSize;\n}\n\n/**\n * The old size names, one step below the merged scale.\n *\n * `GlassButton size=\"s\"` was 32px. On the single scale 32px is `m`, because\n * `s` is the 28px the round icon button has always been. Translating here\n * keeps every existing call site rendering exactly what it rendered before.\n */\nconst SIZE: Record<GlassButtonSize, \"m\" | \"l\" | \"xl\"> = { s: \"m\", m: \"l\", l: \"xl\" };\n\n/**\n * @deprecated Use `<Button variant=\"glass\">`. Kept so existing installs keep\n * working; it is a thin wrapper, not a second component.\n */\nconst GlassButton = forwardRef<HTMLButtonElement, GlassButtonProps>(function GlassButton(\n { size = \"m\", iconLeft, ...rest },\n ref\n) {\n return <Button ref={ref} variant=\"glass\" size={SIZE[size]} icon={iconLeft} {...rest} />;\n});\n\nexport default GlassButton;\n"],"mappings":"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"}