export declare const codeTemplate = "\"use client\";\nimport { useState, useCallback, useMemo, useId } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport {\n interactiveStyles,\n resetButton,\n styledCode,\n} from \"cherry-styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { unified } from \"unified\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeHighlight from \"rehype-highlight\";\nimport rehypeStringify from \"rehype-stringify\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { thinScrollbar } from \"@/components/layout/SharedStyled\";\n\ninterface CodeProps extends Omit<\n React.HTMLAttributes,\n \"theme\"\n> {\n code: string;\n language?: string;\n title?: string;\n theme?: Theme;\n}\n\ninterface CodeTab {\n label: string;\n code: string;\n language?: string;\n}\n\ninterface CodeTabsProps extends Omit<\n React.HTMLAttributes,\n \"theme\"\n> {\n tabs: CodeTab[];\n theme?: Theme;\n}\n\nconst CodeWrapper = styled.span<{ theme: Theme }>`\n position: relative;\n z-index: 2;\n display: block;\n width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n background: ${({ theme }) => theme.colors.light};\n`;\n\n/* Code block uses a fixed GitHub-style palette for the syntax tokens in both\n modes so highlighting stays legible regardless of brand colors. The dark-mode\n surface, however, matches the left sidebar's translucent brand tint\n (color-mix of theme.colors.primaryLight over the black page) instead of\n GitHub's #0d1117, so the window sits on the same background as the nav. Dark\n variants live in :root[data-theme=\"dark\"] & blocks so the swap happens via the active\n class with no re-render. */\nconst TopBar = styled.div<{ theme: Theme }>`\n position: relative;\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 5%, transparent)`};\n border-top-left-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-top-right-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n height: 33px;\n width: 100%;\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 5px;\n padding: 0 10px;\n`;\n\nconst DotsContainer = styled.div`\n display: flex;\n gap: 5px;\n`;\n\nconst Dot = styled.span<{ theme: Theme }>`\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.1);\n\n :root[data-theme=\"dark\"] & {\n background: rgba(255, 255, 255, 0.1);\n }\n`;\n\n/* Icon-only copy button. interactiveStyles supplies the border highlight on\n hover plus the focus/active rings (no scale effect). Colors come from theme\n tokens (grayLight border, success/grayDark icon) that swap for dark mode via\n the theme prop, so no :root[data-theme=\"dark\"] & override is needed and the copied state\n reads consistently in both modes. */\nconst CopyButton = styled.button<{ theme: Theme; $copied: boolean }>`\n ${resetButton}\n ${interactiveStyles}\n background: ${({ theme }) => theme.colors.light};\n border-color: ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n padding: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-right: -6px;\n\n & svg.lucide {\n margin: 0;\n color: ${({ theme, $copied }) =>\n $copied ? theme.colors.success : theme.colors.grayDark};\n }\n`;\n\n/* Centered file name in the TopBar. Sits absolutely over the flex row so the\n dots and copy button keep their edge alignment. Monospace + a muted grayDark\n token that swaps for dark mode via the theme prop. pointer-events off so\n clicks fall through to nothing and never steal focus from the buttons. */\nconst TopBarTitle = styled.span<{ theme: Theme }>`\n position: absolute;\n left: 50%;\n transform: translateX(-50%);\n max-width: 60%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n pointer-events: none;\n font-family: ${({ theme }) => theme.fonts.mono};\n font-size: 12px;\n line-height: 2;\n color: ${({ theme }) => theme.colors.grayDark};\n`;\n\n/* Segmented control living in the TopBar for CodeTabs. Scrolls horizontally\n with the thin scrollbar when the labels overflow the 33px bar. */\nconst TabList = styled.div<{ theme: Theme }>`\n display: flex;\n align-items: center;\n gap: 2px;\n min-width: 0;\n overflow-x: auto;\n ${thinScrollbar};\n padding: 2px;\n margin-left: -8px;\n`;\n\nconst CodeTab = styled.button<{ theme: Theme; $active: boolean }>`\n ${resetButton}\n ${interactiveStyles}\n flex: 0 0 auto;\n font-family: ${({ theme }) => theme.fonts.mono};\n font-size: 12px;\n line-height: 1;\n padding: 5px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n cursor: pointer;\n white-space: nowrap;\n transition: all 0.3s ease;\n border: solid 1px\n ${({ theme, $active }) =>\n $active ? theme.colors.grayLight : \"transparent\"};\n background: ${({ theme, $active }) =>\n $active ? theme.colors.light : \"transparent\"};\n color: ${({ theme, $active }) =>\n $active ? theme.colors.dark : theme.colors.grayDark};\n box-shadow: 0 0 0 0px ${({ theme }) => theme.colors.primary};\n\n &:hover {\n color: ${({ theme }) => theme.colors.dark};\n background: ${({ theme, $active }) =>\n $active\n ? theme.colors.light\n : `color-mix(in srgb, ${theme.colors.dark} 4%, transparent)`};\n }\n\n &:focus {\n border-color: ${({ theme }) => theme.colors.primary};\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n\n &:active {\n box-shadow: 0 0 0 1px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\n/* GitHub Light syntax highlighting by default; GitHub Dark in :root[data-theme=\"dark\"].\n Browser resolves which rule wins based on the active class with no\n JS or React re-render involved. */\nconst lightSyntaxHighlight = css`\n & .hljs {\n color: #24292f;\n background: #ffffff;\n }\n & .hljs-doctag,\n & .hljs-keyword,\n & .hljs-meta .hljs-keyword,\n & .hljs-template-tag,\n & .hljs-template-variable,\n & .hljs-type,\n & .hljs-variable.language_ {\n color: #cf222e;\n }\n & .hljs-title,\n & .hljs-title.class_,\n & .hljs-title.class_.inherited__,\n & .hljs-title.function_ {\n color: #8250df;\n }\n & .hljs-attr,\n & .hljs-attribute,\n & .hljs-literal,\n & .hljs-meta,\n & .hljs-number,\n & .hljs-operator,\n & .hljs-selector-attr,\n & .hljs-selector-class,\n & .hljs-selector-id,\n & .hljs-variable {\n color: #0550ae;\n }\n & .hljs-meta .hljs-string,\n & .hljs-regexp,\n & .hljs-string {\n color: #0a3069;\n }\n & .hljs-built_in,\n & .hljs-symbol {\n color: #953800;\n }\n & .hljs-code,\n & .hljs-comment,\n & .hljs-formula {\n color: #6e7781;\n }\n & .hljs-name,\n & .hljs-quote,\n & .hljs-selector-pseudo,\n & .hljs-selector-tag {\n color: #116329;\n }\n & .hljs-subst {\n color: #24292f;\n }\n & .hljs-section {\n color: #0550ae;\n font-weight: 700;\n }\n & .hljs-bullet {\n color: #953800;\n }\n & .hljs-emphasis {\n color: #24292f;\n font-style: italic;\n }\n & .hljs-strong {\n color: #24292f;\n font-weight: 700;\n }\n`;\n\nconst darkSyntaxHighlight = css`\n & .hljs {\n color: #c9d1d9;\n background: transparent;\n }\n & .hljs-doctag,\n & .hljs-keyword,\n & .hljs-meta .hljs-keyword,\n & .hljs-template-tag,\n & .hljs-template-variable,\n & .hljs-type,\n & .hljs-variable.language_ {\n color: #ff7b72;\n }\n & .hljs-title,\n & .hljs-title.class_,\n & .hljs-title.class_.inherited__,\n & .hljs-title.function_ {\n color: #d2a8ff;\n }\n & .hljs-attr,\n & .hljs-attribute,\n & .hljs-literal,\n & .hljs-meta,\n & .hljs-number,\n & .hljs-operator,\n & .hljs-selector-attr,\n & .hljs-selector-class,\n & .hljs-selector-id,\n & .hljs-variable {\n color: #79c0ff;\n }\n & .hljs-meta .hljs-string,\n & .hljs-regexp,\n & .hljs-string {\n color: #a5d6ff;\n }\n & .hljs-built_in,\n & .hljs-symbol {\n color: #ffa657;\n }\n & .hljs-code,\n & .hljs-comment,\n & .hljs-formula {\n color: #8b949e;\n }\n & .hljs-name,\n & .hljs-quote,\n & .hljs-selector-pseudo,\n & .hljs-selector-tag {\n color: #7ee787;\n }\n & .hljs-subst {\n color: #c9d1d9;\n }\n & .hljs-section {\n color: #1f6feb;\n font-weight: 700;\n }\n & .hljs-bullet {\n color: #f2cc60;\n }\n & .hljs-emphasis {\n color: #c9d1d9;\n font-style: italic;\n }\n & .hljs-strong {\n color: #c9d1d9;\n font-weight: 700;\n }\n`;\n\nconst Body = styled.div<{ theme: Theme }>`\n background: #ffffff;\n border-bottom-left-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom-right-radius: ${({ theme }) => theme.spacing.radius.lg};\n color: #24292f;\n padding: 20px 0;\n font-family: ${({ theme }) => theme.fonts.mono};\n text-align: left;\n overflow-x: auto;\n overflow-y: auto;\n max-height: calc(100dvh - 400px);\n ${thinScrollbar};\n ${({ theme }) => styledCode(theme)};\n ${lightSyntaxHighlight};\n\n /* The 20px side gutter sits on the element instead of the Body so it\n scrolls with the content: the right gutter survives a scroll to the end of\n a long line, and diff rows can bleed back through it to the window edge. */\n & .hljs {\n display: block;\n width: 100%;\n min-width: min-content;\n padding: 0 20px;\n }\n\n /* Diff rows: highlight.js wraps each +/- line in its own span, so tinting it\n edge to edge reads like a GitHub diff. inline-table keeps the trailing\n newline as the line break instead of adding an empty row, and the negative\n margin pulls the row back through the gutter so the accent bar sits flush\n against the window edge. border-left + padding-left add up to the same\n 20px, keeping the code itself aligned with the untouched lines. The tints\n are the success/error tokens at low alpha, so a custom theme.json palette\n carries through and the syntax colors underneath stay legible. */\n & .hljs-addition,\n & .hljs-deletion {\n display: inline-table;\n width: calc(100% + 40px);\n margin: 0 -20px;\n padding: 0 20px 0 17px;\n border-left: solid 3px transparent;\n }\n\n & .hljs-addition {\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.success} 12%, transparent)`};\n border-left-color: ${({ theme }) => theme.colors.success};\n }\n\n & .hljs-deletion {\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.error} 12%, transparent)`};\n border-left-color: ${({ theme }) => theme.colors.error};\n }\n\n :root[data-theme=\"dark\"] & {\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 5%, transparent)`};\n color: #ffffff;\n ${darkSyntaxHighlight};\n\n /* The tints sit on a near-black surface in dark mode, so they need a few\n more points of alpha to read at the same strength as in light. */\n & .hljs-addition {\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.success} 16%, transparent)`};\n }\n\n & .hljs-deletion {\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.error} 16%, transparent)`};\n }\n }\n`;\n\nconst escapeHtml = (unsafe: string): string => {\n return unsafe\n .replace(/&/g, \"&\")\n .replace(//g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n};\n\nconst sanitizeLanguage = (lang: string): string =>\n lang.replace(/[^a-zA-Z0-9_-]/g, \"\");\n\nconst highlightCode = (code: string, language: string): string => {\n const escapedCode = escapeHtml(code);\n const safeLang = sanitizeLanguage(language);\n const result = unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeHighlight, {\n detect: true,\n ignoreMissing: true,\n })\n .use(rehypeStringify)\n .processSync(\n `
${escapedCode}
`,\n );\n\n return String(result);\n};\n\nfunction Code({\n code,\n language = \"javascript\",\n title,\n theme,\n className,\n}: CodeProps) {\n const [copied, setCopied] = useState(false);\n const highlightedCode = useMemo(\n () => highlightCode(code, language),\n [code, language],\n );\n\n const handleCopy = useCallback(async () => {\n try {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n console.error(\"Failed to copy code:\", err);\n }\n }, [code]);\n\n return (\n \n \n \n \n \n \n \n {title ? {title} : null}\n \n \n \n \n \n \n );\n}\n\n/* Multi-variant code block (npm / pnpm / yarn, etc). Tabs replace the macOS\n dots in the TopBar as a keyboard-accessible tablist; the copy button copies\n whichever tab is active and resets its copied state when the tab changes. */\nfunction CodeTabs({ tabs, theme, className }: CodeTabsProps) {\n const baseId = useId();\n const [active, setActive] = useState(0);\n const [copied, setCopied] = useState(false);\n\n const safeActive = active < tabs.length ? active : 0;\n const activeTab = tabs[safeActive];\n\n const highlightedCode = useMemo(\n () =>\n activeTab\n ? highlightCode(activeTab.code, activeTab.language ?? \"bash\")\n : \"\",\n [activeTab],\n );\n\n const handleSelect = useCallback((index: number) => {\n setActive(index);\n setCopied(false);\n }, []);\n\n const handleCopy = useCallback(async () => {\n if (!activeTab) return;\n try {\n await navigator.clipboard.writeText(activeTab.code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n console.error(\"Failed to copy code:\", err);\n }\n }, [activeTab]);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n let next = safeActive;\n if (event.key === \"ArrowRight\" || event.key === \"ArrowDown\") {\n next = (safeActive + 1) % tabs.length;\n } else if (event.key === \"ArrowLeft\" || event.key === \"ArrowUp\") {\n next = (safeActive - 1 + tabs.length) % tabs.length;\n } else if (event.key === \"Home\") {\n next = 0;\n } else if (event.key === \"End\") {\n next = tabs.length - 1;\n } else {\n return;\n }\n event.preventDefault();\n handleSelect(next);\n const target = document.getElementById(`${baseId}-tab-${next}`);\n if (target) target.focus();\n },\n [baseId, handleSelect, safeActive, tabs.length],\n );\n\n if (!tabs || tabs.length === 0) return null;\n\n return (\n \n \n \n {tabs.map((tab, index) => (\n handleSelect(index)}\n onKeyDown={handleKeyDown}\n $active={index === safeActive}\n theme={theme}\n >\n {tab.label}\n \n ))}\n \n \n \n \n \n \n \n );\n}\n\nexport { Code, CodeTabs };\n";