{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "source",
  "title": "Source",
  "description": "Code, set as type. Weight is the only highlighting, what someone wrote is italic, and a bracket joins the lines like a system in a score.",
  "dependencies": [
    "sugar-high"
  ],
  "registryDependencies": [
    "https://thedirectors.agency/ui/r/0nlytype.json",
    "https://thedirectors.agency/ui/r/button.json"
  ],
  "files": [
    {
      "path": "registry/0nlytype/ui/source.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { highlight } from \"sugar-high\"\n\nimport { cn } from \"@/lib/utils\"\nimport { roll } from \"@/lib/0nlytype/roll\"\nimport { Button } from \"@/components/ui/button\"\n\ntype SourceProps = Omit<React.ComponentProps<\"figure\">, \"children\"> & {\n  code: string\n  /** The file's name. Wide, it stands in the margin the way a score names its instrument. */\n  title?: React.ReactNode\n  /** Hide the Copy action. */\n  noCopy?: boolean\n  /**\n   * system (the default): a bracket joins every line. gloss: whole-line comments leave the code for the\n   * outer margin, beside the line they explain. passage: the bracket gathers to the lines in `passage`,\n   * which stay in ink while the rest recede.\n   */\n  variant?: \"system\" | \"gloss\" | \"passage\"\n  /** passage: the first and last line to mark, counted from 1. */\n  passage?: [number, number]\n  /** Turn long lines over, hanging (the default). false keeps each line whole and the lines scroll sideways under the numbers. */\n  wrap?: boolean\n  /** The language, named in the margin under the file's name, the way a score marks its key. */\n  language?: string\n}\n\nconst escape = (s: string) => s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\")\n\n/**\n * Indentation you can see: a proportional face makes two spaces nearly nothing, so each line's\n * leading spaces become --i (a tab counts two) and the line is indented by that many half-ems.\n * Turnovers hang beneath it, like verse.\n */\nfunction indent(html: string, code: string, variant: SourceProps[\"variant\"], passage?: [number, number]) {\n  const src = code.split(\"\\n\")\n  const lead = src.map((l) => l.match(/^[ \\t]*/)![0])\n  const lines = html.split(\"\\n\").map((line, n) => {\n    const space = lead[n] ?? \"\"\n    let drop = space.length\n    const depth = space.length + (space.match(/\\t/g)?.length ?? 0)\n    // gloss: a line that is only a comment becomes a note; its slashes are dropped where it stands in the margin.\n    const note = variant === \"gloss\" ? src[n]?.match(/^\\s*\\/\\/\\s?(.*)$/) : null\n    if (note)\n      return `<span class=\"sh__line\" data-gloss><span class=\"sh__token--comment\"><span class=\"ot-source-slashes\">// </span>${escape(note[1])}</span></span>`\n    let out = \"\"\n    let i = 0\n    while (i < line.length && drop > 0) {\n      if (line[i] === \"<\") {\n        const end = line.indexOf(\">\", i) + 1\n        out += line.slice(i, end)\n        i = end\n      } else if (line[i] === \" \" || line[i] === \"\\t\") {\n        drop--\n        i++\n      } else break\n    }\n    const marked = variant === \"passage\" && passage && n + 1 >= passage[0] && n + 1 <= passage[1]\n    const attrs = marked ? `data-passage style=\"--i:${depth};--k:${n + 1 - passage[0]}\"` : `style=\"--i:${depth}\"`\n    return (out + line.slice(i)).replace(/^<span class=\"sh__line\">/, `<span class=\"sh__line\" ${attrs}>`)\n  })\n  // Consecutive notes travel together, so a note of two lines stays one block beside its line.\n  let joined = \"\"\n  lines.forEach((l, n) => {\n    const gloss = l.includes(\"data-gloss\")\n    const was = n > 0 && lines[n - 1].includes(\"data-gloss\")\n    if (gloss && !was) joined += `<span class=\"ot-source-gloss\">`\n    if (!gloss && was) joined += `</span>`\n    joined += l\n  })\n  if (lines.at(-1)?.includes(\"data-gloss\")) joined += `</span>`\n  return joined // the lines are blocks: a newline between them would open a blank line\n}\n\n/**\n * Code, set as type. Weight is the only highlighting: the language's words are ink at 500, signs recede\n * to pencil, and what someone wrote (strings, JSX text, comments) is the expression italic.\n * A bracket joins the lines like a system in a score; copying runs the accent down it once.\n */\nfunction Source({ code, title, noCopy = false, variant = \"system\", passage, wrap = true, language, className, ...props }: SourceProps) {\n  const text = code.replace(/\\n$/, \"\")\n  const [from, to] = passage ?? [0, 0]\n  const html = React.useMemo(() => indent(highlight(text), text, variant, [from, to]), [text, variant, from, to])\n  const [copied, setCopied] = React.useState(0)\n  const pre = React.useRef<HTMLPreElement>(null)\n\n  // gloss: two notes on neighbouring lines would sit on each other, so a note that would overlap the one\n  // above steps down just clear of it, the way a book's sidenotes queue.\n  React.useLayoutEffect(() => {\n    const el = pre.current\n    if (variant !== \"gloss\" || !el) return\n    const notes = [...el.querySelectorAll<HTMLElement>(\".ot-source-gloss\")]\n    const lay = () => {\n      notes.forEach((n) => (n.style.translate = \"\"))\n      el.style.removeProperty(\"--ot-source-tail\")\n      if (getComputedStyle(notes[0] ?? el).position !== \"absolute\") return\n      let floor = -Infinity\n      for (const n of notes) {\n        const r = n.getBoundingClientRect()\n        const push = Math.max(0, floor - r.top)\n        if (push) n.style.translate = `0 ${push}px`\n        floor = r.bottom + push + 4\n      }\n      const over = floor - el.getBoundingClientRect().bottom\n      if (over > 0) el.style.setProperty(\"--ot-source-tail\", `${over}px`)\n    }\n    lay()\n    const ro = new ResizeObserver(lay)\n    ro.observe(el)\n    return () => ro.disconnect()\n  }, [variant, html, copied])\n\n  return (\n    <figure data-slot=\"source\" data-variant={variant} data-wrap={wrap ? undefined : \"off\"} data-copied={copied || undefined} className={cn(\"ot-source\", className)} {...props}>\n      <div className=\"ot-source-frame\">\n        {title || language || !noCopy ? (\n          <figcaption data-slot=\"source-meta\" className=\"ot-source-meta\">\n            {title ? <span className=\"ot-source-name\">{typeof title === \"string\" ? named(title) : title}</span> : null}\n            {language ? <span className=\"ot-source-lang\">{language}</span> : null}\n            {noCopy ? null : <CopyButton text={code} onCopied={() => setCopied((c) => c + 1)} />}\n          </figcaption>\n        ) : null}\n        {/* Code reads left to right on any page. Keyed by the copy count, so each copy replays the run down the bracket. */}\n        {/* Unwrapped, the lines scroll inside <code> while the numbers and the bracket stay on the <pre>; the scroller takes focus, so the arrows scroll it. */}\n        <pre ref={pre} key={copied} className=\"ot-source-code\" dir=\"ltr\" tabIndex={wrap ? 0 : undefined} data-lines={text.split(\"\\n\").length}>\n          <code tabIndex={wrap ? undefined : 0} dangerouslySetInnerHTML={{ __html: html }} />\n        </pre>\n      </div>\n    </figure>\n  )\n}\n\n/** A file name with its extension set back in pencil; a long path may break after a slash. */\nfunction named(title: string) {\n  const dot = title.lastIndexOf(\".\")\n  const cut = dot > 0 && dot > title.lastIndexOf(\"/\") ? dot : title.length\n  return (\n    <>\n      {title\n        .slice(0, cut)\n        .split(/(?<=\\/)/)\n        .map((part, i) => (\n          <React.Fragment key={i}>\n            {i ? <wbr /> : null}\n            {part}\n          </React.Fragment>\n        ))}\n      {cut < title.length ? <span className=\"ot-source-ext\">{title.slice(cut)}</span> : null}\n    </>\n  )\n}\n\ntype CopyButtonProps = React.ComponentProps<typeof Button> & {\n  text: string\n  /** Called once the text is on the clipboard. */\n  onCopied?: () => void\n}\n\n/** Copy keeps its name through the flow: Copy, then Copied. */\nfunction CopyButton({ text, onCopied, variant = \"quiet\", className, ...props }: CopyButtonProps) {\n  const label = React.useRef<HTMLSpanElement>(null)\n  const [copied, setCopied] = React.useState(false)\n  const timer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n\n  React.useEffect(() => () => clearTimeout(timer.current), [])\n\n  const show = (next: boolean) => {\n    if (label.current) roll(label.current, () => setCopied(next), \"0.5em\", next ? 1 : -1)\n    else setCopied(next)\n  }\n\n  return (\n    <Button\n      variant={variant}\n      data-slot=\"source-copy\"\n      className={cn(\"ot-source-copy\", className)}\n      onClick={async () => {\n        try {\n          await navigator.clipboard.writeText(text)\n        } catch {\n          return // the browser refused; the label stays \"Copy\", which is true\n        }\n        onCopied?.()\n        show(true)\n        clearTimeout(timer.current)\n        timer.current = setTimeout(() => show(false), 1800)\n      }}\n      {...props}\n    >\n      <span className=\"ot-source-copy-word\">\n        <span ref={label} aria-live=\"polite\">\n          {copied ? \"Copied\" : \"Copy\"}\n        </span>\n        {/* The room for the longer word, so Copied never moves what stands beside the button. */}\n        <span aria-hidden=\"true\">Copied</span>\n      </span>\n    </Button>\n  )\n}\n\nexport { Source, CopyButton, type SourceProps, type CopyButtonProps }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/0nlytype/styles/source.css",
      "content": "@layer components {\n  /* ── ot-source ── code, set as type. Weight is the only highlighting: the language's words\n     are ink at 500, signs recede to pencil, and what someone wrote (strings, JSX text, comments)\n     is the expression italic. A bracket joins the lines like a system in a score, the line\n     numbers stand before it like bar numbers, and wide, the file's name stands in the margin\n     the way a score names its instrument. Copying runs the accent down the bracket once. */\n  .ot-source { --ot-source-gutter: 2.75rem; --ot-source-lh: 1.625rem; container-type: inline-size; margin: 0; min-width: 0; }\n  .ot-source-frame { display: grid; grid-template-columns: minmax(0, 1fr); row-gap: var(--ot-space-3); }\n\n  .ot-source-meta {\n    display: flex; flex-wrap: wrap; align-items: baseline; column-gap: var(--ot-space-4); row-gap: var(--ot-space-1);\n    padding-inline-start: var(--ot-source-gutter);\n    font-size: var(--ot-pp); line-height: var(--ot-pp-lh); letter-spacing: var(--ot-pp-tr); color: var(--ot-graphite);\n  }\n  .ot-source-name { min-width: 0; overflow-wrap: anywhere; }\n  .ot-source-ext { color: var(--ot-pencil); }\n  /* The language, marked like a score's key: small capitals in the pencil, under the name. */\n  .ot-source-lang { font-variant-caps: all-small-caps; letter-spacing: 0.08em; color: var(--ot-pencil); }\n  .ot-source-copy { margin-inline-start: auto; }\n  .ot-source-meta > .ot-source-copy { font-size: inherit; }\n  .ot-source-copy-word { display: inline-grid; justify-items: end; }\n  .ot-source-copy-word > * { grid-area: 1 / 1; }\n  .ot-source-copy-word > [aria-hidden] { visibility: hidden; }\n\n  /* Wide enough for a margin: the name and Copy stand to the left of the lines. */\n  @container (min-width: 34rem) {\n    .ot-source-frame:has(> .ot-source-meta) { grid-template-columns: 8.5rem minmax(0, 1fr); column-gap: var(--ot-space-3); }\n    .ot-source-meta { flex-direction: column; align-items: flex-start; row-gap: var(--ot-space-2); padding-inline-start: 0; padding-block-start: 0.4em; }\n    .ot-source-copy { margin-inline-start: 0; }\n    .ot-source-copy-word { justify-items: start; }\n  }\n\n  .ot-source-code {\n    position: relative; margin: 0; padding-block: 0.25rem; padding-inline-start: var(--ot-source-gutter);\n    counter-reset: ot-line; white-space: pre-wrap; overflow-wrap: anywhere;\n    font-family: var(--ot-voice); font-stretch: 88%; font-size: 0.9375rem; line-height: var(--ot-source-lh); letter-spacing: 0.005em;\n    font-variant-numeric: tabular-nums lining-nums; color: var(--ot-graphite);\n    --sh-keyword: var(--ot-ink); --sh-entity: var(--ot-ink); --sh-class: var(--ot-ink);\n    --sh-identifier: var(--ot-graphite); --sh-property: var(--ot-graphite);\n    --sh-string: var(--ot-ink); --sh-jsxliterals: var(--ot-ink);\n    --sh-sign: var(--ot-pencil); --sh-comment: var(--ot-pencil); --sh-space: inherit;\n  }\n  .ot-source-code:focus-visible { outline-offset: 6px; }\n  .ot-source-code > code { display: block; font: inherit; }\n\n  /* Each line hangs: indented by its depth, its turnovers a step further in. */\n  .ot-source-code .sh__line {\n    position: relative; display: block; min-block-size: var(--ot-source-lh);\n    padding-inline-start: calc(var(--i, 0) * 0.5em + 1.5em); text-indent: -1.5em;\n  }\n  .ot-source-code .sh__line::before {\n    counter-increment: ot-line; content: counter(ot-line) / \"\";\n    position: absolute; inset-block-start: 0.1em; inset-inline-start: calc(-1 * var(--ot-source-gutter)); width: calc(var(--ot-source-gutter) - 1.35rem);\n    text-align: end; text-indent: 0; font-size: 0.75em; font-stretch: 100%; color: var(--ot-pencil); opacity: 0.6;\n    transition: opacity var(--ot-allegro), color var(--ot-allegro);\n  }\n  .ot-source-code .sh__line:hover::before { opacity: 1; color: var(--ot-ink); }\n  .ot-source-code[data-lines=\"1\"] .sh__line::before { content: none; }\n\n  .ot-source-code .sh__token--keyword { font-weight: 500; }\n  .ot-source-code :is(.sh__token--string, .sh__token--jsxliterals, .sh__token--comment) {\n    font-family: var(--ot-expression); font-style: italic; font-stretch: 100%; letter-spacing: 0;\n    font-size: calc(var(--ot-expression-scale) * 1em); line-height: 1;\n  }\n\n  /* The bracket: a hairline joining the lines, hooked at both ends. Copying runs the accent down it. */\n  .ot-source-code::before, .ot-source-code::after {\n    content: \"\"; position: absolute; inset-block: 0.55rem; inset-inline-start: calc(var(--ot-source-gutter) - 0.9rem); width: 0.3rem;\n    border: var(--ot-hairline) solid var(--ot-rule-strong); border-inline-end: 0; pointer-events: none;\n  }\n  .ot-source-code::after { border-color: var(--ot-accent); border-inline-start-width: var(--ot-stroke); opacity: 0; }\n  .ot-source[data-copied] .ot-source-code::after { animation: ot-source-run var(--ot-adagio) var(--ot-breath) both; }\n  @keyframes ot-source-run {\n    0%   { opacity: 1; clip-path: inset(0 0 100% 0); }\n    55%  { opacity: 1; clip-path: inset(0 0 0 0); }\n    100% { opacity: 0; clip-path: inset(0 0 0 0); }\n  }\n\n  /* gloss: the glossed manuscript, and Knuth's literate programming. A line that is only a comment is\n     what someone wrote about the code, so wide, it leaves the code for the outer margin and stands beside\n     the line it explains (an absolute block keeps its place in the flow, so it lands level with that line)\n     while the code closes up. Pointing at either inks the other. Narrow, the notes stay in the lines. */\n  .ot-source-gloss { display: block; }\n  .ot-source[data-variant=\"gloss\"] .ot-source-gloss > .sh__line { color: var(--ot-graphite); transition: color var(--ot-allegro); }\n  @container (min-width: 46rem) {\n    .ot-source[data-variant=\"gloss\"] .ot-source-code { padding-inline-end: 15.5rem; padding-block-end: calc(0.25rem + var(--ot-source-tail, 0px)); }\n    .ot-source-gloss {\n      position: absolute; inset-inline-end: 0; inline-size: 14rem; padding-block-start: 0.3rem;\n      font-size: var(--ot-pp); line-height: var(--ot-pp-lh); letter-spacing: 0;\n    }\n    .ot-source-gloss > .sh__line { min-block-size: 0; padding-inline-start: 0; text-indent: 0; }\n    .ot-source-gloss > .sh__line::before { visibility: hidden; } /* still counted, so the code keeps its true numbers */\n    .ot-source-gloss .sh__token--comment { line-height: inherit; }\n    .ot-source-slashes { display: none; }\n    .ot-source[data-variant=\"gloss\"] .ot-source-gloss:is(:hover, :has(+ .sh__line:hover)) > .sh__line { color: var(--ot-ink); }\n    .ot-source[data-variant=\"gloss\"] .ot-source-gloss:hover + .sh__line { --sh-identifier: var(--ot-ink); --sh-property: var(--ot-ink); --sh-sign: var(--ot-graphite); color: var(--ot-ink); }\n    .ot-source[data-variant=\"gloss\"] .ot-source-gloss:hover + .sh__line::before { opacity: 1; color: var(--ot-ink); }\n  }\n\n  /* passage: the reader's pencil line down the margin beside the lines that matter. The bracket that\n     joined the whole system gathers to the passage and inks; every other line recedes to pencil.\n     Copying runs the accent down the passage, line by line. */\n  .ot-source[data-variant=\"passage\"] .ot-source-code::before, .ot-source[data-variant=\"passage\"] .ot-source-code::after { content: none; }\n  .ot-source[data-variant=\"passage\"] .sh__line:not([data-passage]) {\n    color: var(--ot-pencil); --sh-keyword: var(--ot-pencil); --sh-entity: var(--ot-pencil); --sh-class: var(--ot-pencil);\n    --sh-identifier: var(--ot-pencil); --sh-property: var(--ot-pencil); --sh-string: var(--ot-pencil); --sh-jsxliterals: var(--ot-pencil);\n  }\n  .ot-source[data-variant=\"passage\"] .sh__line:not([data-passage]) .sh__token--keyword { font-weight: 400; }\n  .ot-source[data-variant=\"passage\"] .sh__line[data-passage] { color: var(--ot-ink); --sh-identifier: var(--ot-ink); --sh-property: var(--ot-ink); --sh-sign: var(--ot-graphite); }\n  .ot-source[data-variant=\"passage\"] .sh__line[data-passage]::before { opacity: 1; color: var(--ot-ink); }\n  .ot-source[data-variant=\"passage\"] .sh__line[data-passage]::after {\n    content: \"\"; position: absolute; inset-block: 0; inset-inline-start: -0.9rem; width: 0.3rem;\n    border-inline-start: var(--ot-stroke) solid var(--ot-ink); pointer-events: none;\n  }\n  .ot-source[data-variant=\"passage\"] .sh__line[data-passage]:not([data-passage] + [data-passage])::after { inset-block-start: 0.3rem; border-block-start: var(--ot-hairline) solid var(--ot-ink); }\n  .ot-source[data-variant=\"passage\"] .sh__line[data-passage]:not(:has(+ [data-passage]))::after { inset-block-end: 0.3rem; border-block-end: var(--ot-hairline) solid var(--ot-ink); }\n  .ot-source[data-variant=\"passage\"][data-copied] .sh__line[data-passage]::after { animation: ot-source-pass var(--ot-andante) var(--ot-breath) calc(var(--k, 0) * var(--ot-arpeggio)) both; }\n  @keyframes ot-source-pass { 0%, 40% { border-color: var(--ot-accent); } 100% { border-color: var(--ot-ink); } }\n\n  /* Unwrapped: each line is kept whole and runs on past the frame, cropped by its end as SPECTRA's rule crops its word;\n     the lines scroll sideways inside <code>. The lines stop being positioned, so the numbers (and the passage's line)\n     are placed from the <pre>, outside the scroller: they keep their place, at the height the line gives them. */\n  .ot-source[data-wrap=\"off\"] .ot-source-code { white-space: pre; overflow-wrap: normal; }\n  .ot-source[data-wrap=\"off\"] .ot-source-code > code {\n    overflow-x: auto; overscroll-behavior-x: contain; padding-block-end: var(--ot-space-2);\n    scrollbar-width: thin; scrollbar-color: var(--ot-rule-strong) transparent;\n  }\n  .ot-source[data-wrap=\"off\"] .ot-source-code > code:focus-visible { outline-offset: 6px; }\n  .ot-source[data-wrap=\"off\"] .ot-source-code .sh__line { position: static; inline-size: max-content; min-inline-size: 100%; }\n  .ot-source[data-wrap=\"off\"] .ot-source-code .sh__line::before { inset-block-start: auto; inset-inline-start: 0; margin-block-start: 0.1em; }\n  .ot-source[data-wrap=\"off\"][data-variant=\"passage\"] .sh__line[data-passage]::after { inset-block: auto; inset-inline-start: calc(var(--ot-source-gutter) - 0.9rem); block-size: var(--ot-source-lh); margin-block-start: 0; }\n  .ot-source[data-wrap=\"off\"][data-variant=\"passage\"] .sh__line[data-passage]:not([data-passage] + [data-passage])::after { inset-block-start: auto; margin-block-start: 0.3rem; block-size: calc(var(--ot-source-lh) - 0.3rem); }\n  .ot-source[data-wrap=\"off\"][data-variant=\"passage\"] .sh__line[data-passage]:not(:has(+ [data-passage]))::after { inset-block-end: auto; block-size: calc(var(--ot-source-lh) - 0.3rem); }\n  .ot-source[data-wrap=\"off\"][data-variant=\"passage\"] .sh__line[data-passage]:not([data-passage] + [data-passage]):not(:has(+ [data-passage]))::after { block-size: calc(var(--ot-source-lh) - 0.6rem); }\n  @media (forced-colors: active) {\n    .ot-source-code::before, .ot-source[data-variant=\"passage\"] .sh__line[data-passage]::after { border-color: CanvasText; }\n  }\n}\n",
      "type": "registry:file",
      "target": "styles/0nlytype/source.css"
    }
  ],
  "css": {
    "@import \"../styles/0nlytype/source.css\"": {}
  },
  "meta": {
    "movement": "X",
    "underneath": "hook",
    "contract": "ot-source"
  },
  "type": "registry:ui"
}
