{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"comment-thread-clean","type":"registry:ui","title":"Comment Thread (Clean)","description":"One comment with replies – an assistant reports what it did, folds open details and answers @mentions","dependencies":[],"files":[{"path":"registry/comment-thread-clean/comment-thread-clean.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { useEffect, useId, useLayoutEffect, useRef, useState, type CSSProperties, type ReactNode, type RefObject } from \"react\";\n\n/*\n * Comment Thread (Clean) – one comment with its replies, including an assistant that reports what it did, in a single\n * self-contained file. Clean style: no tray – one white card with hairline shadows: the heading on top, the\n * conversation in the middle and the reply field lying right in the card as its last row.\n * Assistant replies can fold open to show the details; replying shows a shimmering thinking state\n * while an async answer comes back. The styles are injected below, so no extra files are needed.\n */\n\nexport type ThreadAuthor = { name: string; avatar?: ReactNode | string; assistant?: boolean };\n\nexport type ThreadMessage = {\n  id: string;\n  author: ThreadAuthor;\n  createdAt: Date | number;\n  text: string;\n  /** Assistant replies: a short status line that folds open to show these details */\n  status?: string;\n  details?: string[];\n};\n\nexport type CommentThreadProps = {\n  title?: string;\n  messages: ThreadMessage[];\n  currentUser: ThreadAuthor;\n  /** The assistant that answers – used for the thinking state */\n  assistant?: ThreadAuthor;\n  /**\n   * Called with every new reply. Return a message (or a promise of one) to let the assistant answer;\n   * return nothing to just post the reply.\n   */\n  onReply?: (text: string) => ThreadMessage | Promise<ThreadMessage | void> | void;\n  onClose?: () => void;\n  className?: string;\n};\n\nfunction ago(time: number, now: number) {\n  const s = Math.max(0, Math.round((now - time) / 1000));\n  if (s < 45) return \"just now\";\n  const m = Math.round(s / 60);\n  if (m < 60) return `${m}min ago`;\n  const h = Math.round(m / 60);\n  if (h < 24) return `${h}h ago`;\n  const d = Math.round(h / 24);\n  if (d < 30) return `${d}d ago`;\n  return d < 365 ? `${Math.round(d / 30)}mo ago` : `${Math.round(d / 365)}y ago`;\n}\n\n// 1284 → \"1,284\"\nconst count = new Intl.NumberFormat(\"en\");\n\n/** Two initials from whole letters – skips emoji and stray spaces, so \"🦊 Fox\" gives \"F\" */\nconst initials = (name: string) =>\n  name\n    .split(/\\s+/)\n    .map((w) => Array.from(w).find((c) => /[\\p{L}\\p{N}]/u.test(c)))\n    .filter(Boolean)\n    .slice(0, 2)\n    .join(\"\")\n    .toUpperCase();\n\nexport function CommentThread({\n  title = \"Comment\",\n  messages: initial,\n  currentUser,\n  assistant,\n  onReply,\n  onClose,\n  className,\n}: CommentThreadProps) {\n  const [messages, setMessages] = useState(initial);\n  // Assistant replies that are there from the start come in a beat after the comment above them\n  const [firstIds] = useState(() => new Set(initial.filter((m) => m.author.assistant).map((m) => m.id)));\n  const [open, setOpen] = useState<Set<string>>(new Set());\n  const [draft, setDraft] = useState(\"\");\n  const [thinking, setThinking] = useState(false);\n  const [now, setNow] = useState(() => Date.now());\n  const listRef = useRef<HTMLDivElement>(null);\n  const morphRef = useSmoothHeight(listRef);\n\n  useEffect(() => {\n    const t = window.setInterval(() => setNow(Date.now()), 30_000);\n    return () => window.clearInterval(t);\n  }, []);\n\n  // Follow the conversation as it grows\n  useEffect(() => {\n    listRef.current?.scrollTo({ top: listRef.current.scrollHeight, behavior: \"smooth\" });\n  }, [messages.length, thinking]);\n\n  const send = async () => {\n    const text = draft.trim();\n    if (!text || thinking) return;\n    setDraft(\"\");\n    setNow(Date.now());\n    lockHeight(true);\n    setMessages((m) => [...m, { id: `m-${Date.now()}`, author: currentUser, createdAt: Date.now(), text }]);\n    const answer = onReply?.(text);\n    if (!answer) return;\n    setThinking(true);\n    try {\n      const reply = await answer;\n      if (reply) setMessages((m) => [...m, { ...reply, createdAt: Date.now() }]);\n    } finally {\n      setThinking(false);\n      setNow(Date.now());\n    }\n  };\n\n  // New messages keep the card at its height and the conversation scrolls up; folding details lets it grow again\n  const lockHeight = (on: boolean) => {\n    const list = listRef.current;\n    if (list) list.style.maxHeight = on ? `${list.offsetHeight}px` : \"\";\n  };\n\n  const toggle = (id: string) => {\n    lockHeight(false);\n    setOpen((o) => {\n      const next = new Set(o);\n      if (next.has(id)) next.delete(id);\n      else next.add(id);\n      return next;\n    });\n  };\n\n  return (\n    <>\n      <style href=\"ui-comment-thread-clean\" precedence=\"default\">\n        {css}\n      </style>\n      <section className={[\"ui-ctc\", className].filter(Boolean).join(\" \")} aria-label={title}>\n        <header className=\"ui-ctc__head\">\n          <h3 className=\"ui-ctc__title\" title={title} dir=\"auto\">\n            {title}\n          </h3>\n          <span className=\"ui-ctc__count\">\n            {count.format(messages.length)} {messages.length === 1 ? \"message\" : \"messages\"}\n          </span>\n          {onClose && (\n            <button type=\"button\" className=\"ui-ctc__icon-btn\" aria-label=\"Close comment\" onClick={onClose}>\n              <Icon d=\"m4.5 4.5 7 7M11.5 4.5l-7 7\" />\n            </button>\n          )}\n        </header>\n\n        {/* Only the conversation glides to its new height – the reply row below always stays in view */}\n        <div className=\"ui-ctc__morph\" ref={morphRef}>\n          <div className=\"ui-ctc__scroll\" ref={listRef} aria-live=\"polite\">\n            {messages.map((m) => {\n              const isOpen = open.has(m.id);\n              const command = /^\\/edit\\b/i.test(m.text.trim());\n              return (\n                <article key={m.id} className=\"ui-ctc__msg\" data-delayed={firstIds.has(m.id) || undefined}>\n                  <div className=\"ui-ctc__meta\">\n                    <Avatar author={m.author} />\n                    <span className=\"ui-ctc__name\" title={m.author.name} dir=\"auto\">\n                    {m.author.name}\n                  </span>\n                    <time className=\"ui-ctc__time\" dateTime={new Date(m.createdAt).toISOString()}>\n                      {ago(+m.createdAt, now)}\n                    </time>\n                  </div>\n                  {m.text && (\n                    <p className=\"ui-ctc__text\" dir=\"auto\">\n                      {command ? (\n                        <>\n                          <span className=\"ui-ctc__command\">/edit</span> {m.text.trim().replace(/^\\/edit\\s*/i, \"\")}\n                        </>\n                      ) : (\n                        m.text\n                      )}\n                    </p>\n                  )}\n                  {m.status && (\n                    <div className=\"ui-ctc__status\">\n                      <button\n                        type=\"button\"\n                        className=\"ui-ctc__status-btn\"\n                        aria-expanded={isOpen}\n                        disabled={!m.details?.length}\n                        onClick={() => toggle(m.id)}\n                      >\n                        <svg viewBox=\"0 0 16 16\" className=\"ui-ctc__check\" aria-hidden=\"true\">\n                          <path d=\"M1.5 8a6.5 6.5 0 1 1 13 0 6.5 6.5 0 0 1-13 0Zm8.9-1.2a.5.5 0 0 0-.8-.6L7.44 9.23 6.35 8.15a.5.5 0 1 0-.7.7l1.5 1.5a.5.5 0 0 0 .76-.06l2.5-3.5Z\" />\n                        </svg>\n                        <span className=\"ui-ctc__status-text\" title={m.status} dir=\"auto\">\n                          {m.status}\n                        </span>\n                        {!!m.details?.length && (\n                          <span className=\"ui-ctc__chevron\">\n                            <Icon d=\"m5 6.5 3 3 3-3\" />\n                          </span>\n                        )}\n                      </button>\n                      {!!m.details?.length && (\n                        <div className=\"ui-ctc__details\" data-open={isOpen || undefined} inert={!isOpen}>\n                          <ul>\n                            {m.details.map((d, i) => (\n                              <li key={i} dir=\"auto\" style={{ \"--i\": i } as CSSProperties}>\n                                {d}\n                              </li>\n                            ))}\n                          </ul>\n                        </div>\n                      )}\n                    </div>\n                  )}\n                </article>\n              );\n            })}\n            {thinking && assistant && (\n              <article className=\"ui-ctc__msg\" aria-label={`${assistant.name} is thinking`}>\n                <div className=\"ui-ctc__meta\">\n                  <Avatar author={assistant} />\n                  <span className=\"ui-ctc__name\">{assistant.name}</span>\n                </div>\n                <p className=\"ui-ctc__text\">\n                  <span className=\"ui-ctc__thinking\" data-text=\"Thinking…\">\n                    Thinking…\n                  </span>\n                </p>\n              </article>\n            )}\n          </div>\n        </div>\n\n        {/* The reply row lies right in the card – no own fill, no line above */}\n        <form\n          className=\"ui-ctc__composer\"\n          onSubmit={(e) => {\n            e.preventDefault();\n            send();\n          }}\n        >\n          <input\n            className=\"ui-ctc__input\"\n            value={draft}\n            onChange={(e) => setDraft(e.target.value)}\n            placeholder={assistant ? `Reply, or ask @${assistant.name}…` : \"Add comment…\"}\n            aria-label=\"Reply\"\n          />\n          <button type=\"submit\" className=\"ui-ctc__send\" aria-label=\"Send reply\" disabled={!draft.trim() || thinking}>\n            <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n              <path d=\"M8 12.5v-9M4.25 7.25 8 3.5l3.75 3.75\" />\n            </svg>\n          </button>\n        </form>\n      </section>\n    </>\n  );\n}\n\nfunction Avatar({ author }: { author: ThreadAuthor }) {\n  // A picture that fails to load falls back to the initials\n  const [failed, setFailed] = useState(false);\n  if (author.avatar && typeof author.avatar !== \"string\") return <span className=\"ui-ctc__avatar\">{author.avatar}</span>;\n  if (typeof author.avatar === \"string\" && !failed) {\n    return <img className=\"ui-ctc__avatar\" src={author.avatar} alt=\"\" width={20} height={20} loading=\"lazy\" onError={() => setFailed(true)} />;\n  }\n  return (\n    <span className=\"ui-ctc__avatar ui-ctc__avatar--initials\" aria-hidden=\"true\">\n      {initials(author.name)}\n    </span>\n  );\n}\n\n/** A simple assistant mark: a full circle with a blue-to-violet gradient – pass your own logo as `avatar` instead */\nexport function AssistantMark() {\n  const id = useId();\n  return (\n    <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" aria-hidden=\"true\">\n      <defs>\n        <linearGradient id={`${id}-fill`} x1=\"3\" y1=\"3\" x2=\"21\" y2=\"21\" gradientUnits=\"userSpaceOnUse\">\n          <stop offset=\"0\" stopColor=\"#5b9dff\" />\n          <stop offset=\"0.5\" stopColor=\"#2f6bff\" />\n          <stop offset=\"1\" stopColor=\"#8b5cf6\" />\n        </linearGradient>\n      </defs>\n      <circle cx=\"12\" cy=\"12\" r=\"12\" fill={`url(#${id}-fill)`} />\n    </svg>\n  );\n}\n\n/** 16px icons with thin, round lines */\nconst Icon = ({ d }: { d: string }) => (\n  <svg viewBox=\"0 0 16 16\" className=\"ui-ctc__glyph\" aria-hidden=\"true\">\n    <path d={d} />\n  </svg>\n);\n\n/**\n * Lets a box glide to the height of its content instead of jumping: the outer box follows the inner one\n * with a height transition whenever the content grows or shrinks.\n */\nfunction useSmoothHeight(inner: RefObject<HTMLElement | null>) {\n  const outer = useRef<HTMLDivElement>(null);\n  useLayoutEffect(() => {\n    const o = outer.current;\n    const i = inner.current;\n    if (!o || !i) return;\n    const sync = () => {\n      o.style.height = `${i.offsetHeight}px`;\n    };\n    sync();\n    const ro = new ResizeObserver(sync);\n    ro.observe(i);\n    // Transitions switch on after the first measurement, so the first render doesn't animate\n    const frame = requestAnimationFrame(() => o.setAttribute(\"data-smooth\", \"\"));\n    return () => {\n      ro.disconnect();\n      cancelAnimationFrame(frame);\n    };\n  }, [inner]);\n  return outer;\n}\n\n/* ---------- Styles ---------- */\n\nconst css = /* css */ `\n/* Colors and motion as variables – override them on .ui-ctc */\n.ui-ctc {\n  --ui-ctc-card: var(--aui-surface, #ffffff);\n  --ui-ctc-strong: var(--aui-ink, #000000);\n  --ui-ctc-ink: var(--aui-ink, rgba(0, 0, 0, 0.88));\n  --ui-ctc-text: var(--aui-muted, rgba(0, 0, 0, 0.61));\n  --ui-ctc-muted: var(--aui-muted, rgba(0, 0, 0, 0.45));\n  --ui-ctc-faint: var(--aui-placeholder, rgba(0, 0, 0, 0.27));\n  --ui-ctc-icon: var(--aui-muted, rgba(0, 0, 0, 0.447));\n  --ui-ctc-line: var(--aui-border, rgba(0, 0, 0, 0.06));\n  --ui-ctc-tree: rgba(0, 0, 0, 0.1);\n  --ui-ctc-fill: rgba(0, 0, 0, 0.06);\n  --ui-ctc-wash: var(--aui-subtle, rgba(0, 0, 0, 0.03));\n  --ui-ctc-solid: var(--aui-primary, rgba(0, 0, 0, 0.88));\n  --ui-ctc-on-solid: var(--aui-on-primary, #fcfcfc);\n  --ui-ctc-accent: rgba(0, 96, 226, 0.765);\n  --ui-ctc-ok: var(--aui-success, #1e8a4f);\n  --ui-ctc-shine: rgba(255, 255, 255, 0.82);\n  --ui-ctc-avatar-ring: rgba(0, 0, 0, 0.08);\n  /* Strokes are shadows: a 0.5px hairline plus a soft lift */\n  --ui-ctc-shadow: var(--aui-shadow-card, 0 0 0 0.5px var(--aui-border, rgba(0, 0, 0, 0.06)), 0 1px 1px -1px rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.05));\n  --ui-ctc-focus: var(--aui-focus, rgba(0, 96, 226, 0.7));\n  --ui-ctc-ease: var(--aui-ease, cubic-bezier(0.22, 1, 0.36, 1));\n  box-sizing: border-box;\n  container-type: inline-size;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  max-width: 420px;\n  overflow: hidden;\n  border-radius: var(--aui-radius-lg, 10px);\n  background: var(--ui-ctc-card);\n  box-shadow: var(--ui-ctc-shadow);\n  font-family: var(--aui-font, \"Inter\", \"InterVariable\", ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif);\n  font-size: 13px;\n  line-height: 20px;\n  font-weight: 400;\n  color: var(--ui-ctc-ink);\n  -webkit-font-smoothing: antialiased;\n  -webkit-tap-highlight-color: transparent;\n}\n.ui-ctc *, .ui-ctc *::before, .ui-ctc *::after { box-sizing: border-box; }\n:where(.ui-ctc) button {\n  appearance: none;\n  margin: 0;\n  padding: 0;\n  border: 0;\n  background: none;\n  font: inherit;\n  color: inherit;\n  cursor: pointer;\n  -webkit-tap-highlight-color: transparent;\n}\n:where(.ui-ctc) svg { display: block; flex: none; }\n.ui-ctc button:focus-visible { outline: 2px solid var(--ui-ctc-focus); outline-offset: 2px; }\n.ui-ctc__glyph { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.1; stroke-linecap: round; stroke-linejoin: round; }\n\n/* ---------- Header ---------- */\n.ui-ctc__head {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  min-height: 48px;\n  padding: 10px 10px 10px 16px;\n  border-bottom: 0.5px solid var(--ui-ctc-line);\n}\n/* A long title takes two lines at most – the count and close stay in place */\n.ui-ctc__title { display: -webkit-box; min-width: 0; overflow: hidden; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow-wrap: anywhere; margin: 0; font-size: 13px; font-weight: 500; line-height: 20px; color: var(--ui-ctc-strong); }\n.ui-ctc__count { flex: none; margin-right: auto; white-space: nowrap; font-size: 12px; line-height: 16px; color: var(--ui-ctc-muted); font-variant-numeric: tabular-nums; }\n/* Close: a ghost icon button */\n.ui-ctc__icon-btn {\n  display: grid;\n  place-items: center;\n  flex: none;\n  width: 28px;\n  height: 28px;\n  border-radius: calc(var(--aui-radius-sm, 5px) + 1px);\n  color: var(--ui-ctc-icon);\n  transition: background-color 160ms ease, color 160ms ease;\n}\n.ui-ctc__icon-btn:hover { background: var(--ui-ctc-wash); color: var(--ui-ctc-ink); }\n\n/* ---------- The conversation ---------- */\n.ui-ctc__scroll {\n  display: grid;\n  gap: 16px;\n  align-content: start;\n  min-height: var(--ui-ctc-min-height, 0px);\n  max-height: var(--ui-ctc-max-height, 340px);\n  padding: 16px 16px 12px;\n  overflow-y: auto;\n  scrollbar-width: none;\n  /* Soft edges at the top and towards the reply row, so nothing ends in a hard cut */\n  -webkit-mask-image: linear-gradient(transparent, #000 12px, #000 calc(100% - 10px), transparent);\n  mask-image: linear-gradient(transparent, #000 12px, #000 calc(100% - 10px), transparent);\n}\n.ui-ctc__scroll::-webkit-scrollbar { display: none; }\n/* Each message builds up piece by piece: name row, text, status – the assistant's first reply a beat later */\n.ui-ctc__msg { --ui-ctc-d: 0ms; min-width: 0; }\n.ui-ctc__msg[data-delayed] { --ui-ctc-d: 700ms; }\n.ui-ctc__msg > * { animation: ui-ctc-in 420ms var(--ui-ctc-ease) both; animation-delay: var(--ui-ctc-d); }\n.ui-ctc__msg > :nth-child(2) { animation-delay: calc(var(--ui-ctc-d) + 80ms); }\n.ui-ctc__msg > :nth-child(3) { animation-delay: calc(var(--ui-ctc-d) + 160ms); }\n.ui-ctc__meta { display: flex; align-items: center; gap: 8px; min-width: 0; }\n.ui-ctc__avatar {\n  display: grid;\n  place-items: center;\n  flex: none;\n  width: 20px;\n  height: 20px;\n  border-radius: 50%;\n  object-fit: cover;\n  overflow: hidden;\n}\nimg.ui-ctc__avatar { box-shadow: 0 0 0 0.5px var(--ui-ctc-avatar-ring); }\n.ui-ctc__avatar svg { width: 100%; height: 100%; }\n.ui-ctc__avatar--initials { background: var(--ui-ctc-fill); font-size: 9px; font-weight: 500; line-height: 1; color: var(--ui-ctc-text); }\n.ui-ctc__name { min-width: 0; overflow: hidden; font-size: 13px; font-weight: 500; line-height: 20px; white-space: nowrap; text-overflow: ellipsis; color: var(--ui-ctc-strong); }\n.ui-ctc__time { flex: none; margin-left: auto; font-size: 12px; line-height: 16px; white-space: nowrap; color: var(--ui-ctc-faint); font-variant-numeric: tabular-nums; }\n/* Text and status line up with the name, not the avatar */\n.ui-ctc__text { margin: 4px 0 0 28px; font-size: 13px; line-height: 20px; overflow-wrap: anywhere; white-space: pre-line; text-align: start; color: var(--ui-ctc-ink); }\n/* \"/edit\": a small gray tag */\n.ui-ctc__command {\n  display: inline-block;\n  margin-right: 1px;\n  padding: 0 5px;\n  border-radius: var(--aui-radius-sm, 5px);\n  background: var(--aui-subtle, var(--ui-ctc-fill));\n  font-size: 12px;\n  font-weight: 500;\n  line-height: 18px;\n  color: var(--ui-ctc-text);\n}\n\n/* Status: a ghost row – a green check, the line, and a chevron to fold the details open */\n.ui-ctc__status { margin: 4px 0 0 22px; }\n.ui-ctc__status-btn {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  max-width: 100%;\n  height: 28px;\n  padding: 0 4px 0 6px;\n  border-radius: calc(var(--aui-radius-sm, 5px) + 1px);\n  font-size: 13px;\n  font-weight: 500;\n  line-height: 20px;\n  color: var(--ui-ctc-ink);\n  transition: background-color 160ms ease;\n}\n.ui-ctc__status-btn:hover:not(:disabled) { background: var(--ui-ctc-wash); }\n.ui-ctc__status-btn:disabled { cursor: default; }\n.ui-ctc__status-text { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }\n.ui-ctc__check { width: 16px; height: 16px; fill: var(--ui-ctc-ok); }\n.ui-ctc__chevron { display: grid; flex: none; color: var(--ui-ctc-icon); transition: transform 260ms var(--ui-ctc-ease), color 160ms ease; }\n.ui-ctc__status-btn:hover .ui-ctc__chevron { color: var(--ui-ctc-ink); }\n.ui-ctc__status-btn[aria-expanded=\"true\"] .ui-ctc__chevron { transform: rotate(180deg); }\n.ui-ctc__details {\n  display: grid;\n  grid-template-rows: 0fr;\n  transition: grid-template-rows 320ms var(--ui-ctc-ease);\n}\n.ui-ctc__details[data-open] { grid-template-rows: 1fr; }\n.ui-ctc__details ul {\n  min-height: 0;\n  overflow: hidden;\n  margin: 0;\n  padding: 0 0 0 28px;\n  list-style: none;\n}\n.ui-ctc__details li {\n  position: relative;\n  margin-top: 4px;\n  font-size: 13px;\n  line-height: 20px;\n  overflow-wrap: anywhere;\n  text-align: start;\n  color: var(--ui-ctc-text);\n  /* Closing: lines fade out quickly while the box folds */\n  opacity: 0;\n  transform: translateY(-4px);\n  transition: opacity 160ms ease, transform 220ms var(--ui-ctc-ease);\n}\n/* Opening: lines come in one after another */\n.ui-ctc__details[data-open] li {\n  opacity: 1;\n  transform: none;\n  transition-duration: 320ms, 420ms;\n  transition-delay: calc(60ms + var(--i, 0) * 60ms);\n}\n/* Thin tree lines: each line gets its own corner – down on the axis of the check icon, then a rounded turn right */\n.ui-ctc__details li:first-child { margin-top: 2px; }\n.ui-ctc__details li::before {\n  content: \"\";\n  position: absolute;\n  left: -14.5px;\n  top: -3px;\n  width: 9px;\n  height: 13.5px;\n  border-left: 1px solid var(--ui-ctc-tree);\n  border-bottom: 1px solid var(--ui-ctc-tree);\n  border-bottom-left-radius: 5px;\n}\n/* The first branch starts with a small gap under the check icon */\n.ui-ctc__details li:first-child::before { top: -6px; height: 16.5px; }\n\n/* \"Thinking…\": a lighter band sweeps across the gray text */\n.ui-ctc__thinking { position: relative; display: inline-block; color: var(--ui-ctc-muted); }\n.ui-ctc__thinking::after {\n  content: attr(data-text);\n  content: attr(data-text) / \"\";\n  position: absolute;\n  inset: 0;\n  white-space: nowrap;\n  color: transparent;\n  background: linear-gradient(90deg, transparent 38%, var(--ui-ctc-shine) 50%, transparent 62%) 100% 0 / 300% 100% no-repeat;\n  -webkit-background-clip: text;\n  background-clip: text;\n  animation: ui-ctc-sweep 1.4s linear infinite;\n  pointer-events: none;\n}\n\n/* ---------- Reply row: lies right in the card ---------- */\n.ui-ctc__composer {\n  flex: none;\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  margin: 0;\n  padding: 4px 10px 10px 16px;\n}\n.ui-ctc__input {\n  flex: 1;\n  min-width: 0;\n  height: 28px;\n  margin: 0;\n  padding: 0;\n  border: 0;\n  outline: none;\n  background: transparent;\n  font: inherit;\n  font-size: 13px;\n  line-height: 20px;\n  color: var(--ui-ctc-strong);\n  caret-color: var(--ui-ctc-accent);\n}\n.ui-ctc__input::placeholder { color: var(--ui-ctc-faint); opacity: 1; }\n/* Send: the dark chip – a quiet gray tile until there is something to send */\n.ui-ctc__send {\n  display: grid;\n  place-items: center;\n  flex: none;\n  width: 28px;\n  height: 28px;\n  border-radius: calc(var(--aui-radius-sm, 5px) + 1px);\n  background: var(--ui-ctc-solid);\n  color: var(--ui-ctc-on-solid);\n  transition: background-color 180ms ease, color 180ms ease, transform 160ms var(--ui-ctc-ease);\n}\n.ui-ctc__send:disabled { background: var(--ui-ctc-fill); color: var(--ui-ctc-faint); cursor: default; }\n.ui-ctc__send:not(:disabled):active { transform: scale(0.94); }\n.ui-ctc__send svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }\n\n@keyframes ui-ctc-in { from { opacity: 0; transform: translateY(4px); } }\n@keyframes ui-ctc-sweep { from { background-position: 100% 0; } to { background-position: 0% 0; } }\n\n/* Height changes glide instead of jumping */\n.ui-ctc__morph { flex: none; overflow: hidden; }\n.ui-ctc__morph[data-smooth] { transition: height 380ms var(--ui-ctc-ease); }\n\n/* Narrow containers: a little less padding */\n@container (max-width: 340px) {\n  .ui-ctc__head { padding-left: 12px; }\n  .ui-ctc__scroll { padding-left: 12px; padding-right: 12px; }\n  .ui-ctc__composer { padding-left: 12px; }\n}\n/* Touch screens: taller hit targets */\n@media (pointer: coarse) {\n  .ui-ctc__icon-btn, .ui-ctc__send { width: 34px; height: 34px; }\n  .ui-ctc__status-btn { height: 34px; }\n  .ui-ctc__input { height: 34px; }\n  .ui-ctc__details li:first-child::before { top: -9px; height: 19.5px; }\n}\n@media (prefers-reduced-motion: reduce) {\n  .ui-ctc__msg > *, .ui-ctc__thinking::after { animation: none; }\n  .ui-ctc__details, .ui-ctc__details li, .ui-ctc__chevron, .ui-ctc__morph[data-smooth] { transition: none; }\n}\n\n/* Text fields: no focus box while typing – also against global :focus-visible rules of the host page */\n.ui-ctc input:not([type=\"checkbox\"]):not([type=\"radio\"]):focus-visible,\n.ui-ctc textarea:focus-visible,\n.ui-ctc [contenteditable]:focus-visible { outline: none; }\n\n/* Dark mode: follows a .dark class or data-theme=\"dark\" on any ancestor, e.g. <html> */\n:where(.dark, [data-theme=\"dark\"]) .ui-ctc {\n  --ui-ctc-card: var(--aui-surface, #1c1c1e);\n  --ui-ctc-strong: var(--aui-ink, #ffffff);\n  --ui-ctc-ink: var(--aui-ink, rgba(255, 255, 255, 0.9));\n  --ui-ctc-text: var(--aui-muted, rgba(255, 255, 255, 0.62));\n  --ui-ctc-muted: var(--aui-muted, rgba(255, 255, 255, 0.46));\n  --ui-ctc-faint: var(--aui-placeholder, rgba(255, 255, 255, 0.3));\n  --ui-ctc-icon: var(--aui-muted, rgba(255, 255, 255, 0.5));\n  --ui-ctc-line: var(--aui-border, rgba(255, 255, 255, 0.08));\n  --ui-ctc-tree: rgba(255, 255, 255, 0.14);\n  --ui-ctc-fill: rgba(255, 255, 255, 0.08);\n  --ui-ctc-wash: var(--aui-subtle, rgba(255, 255, 255, 0.04));\n  --ui-ctc-solid: var(--aui-primary, rgba(255, 255, 255, 0.92));\n  --ui-ctc-on-solid: var(--aui-on-primary, #161618);\n  --ui-ctc-accent: var(--aui-blue, #4fb0fe);\n  --ui-ctc-ok: var(--aui-green, #5fc88a);\n  --ui-ctc-shine: #ffffff;\n  --ui-ctc-avatar-ring: rgba(255, 255, 255, 0.1);\n  --ui-ctc-shadow: var(--aui-shadow-card, 0 0 0 0.5px var(--aui-border, rgba(255, 255, 255, 0.1)), 0 1px 2px 0 rgba(0, 0, 0, 0.4));\n  --ui-ctc-focus: var(--aui-focus, rgba(77, 141, 255, 0.8));\n}\n`;\n"}]}