{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "note",
  "title": "Note",
  "description": "Marginalia: a dotted term, and a leader line that draws out to its note; or a gloss set over the word; an abbreviation that opens into its words; or a word struck through and rewritten.",
  "registryDependencies": [
    "https://thedirectors.agency/ui/r/0nlytype.json"
  ],
  "files": [
    {
      "path": "registry/0nlytype/ui/note.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype NoteProps = Omit<React.ComponentProps<\"button\">, \"children\"> & {\n  /** The word or phrase being annotated. For expand, the abbreviation itself, as a string. */\n  children: React.ReactNode\n  /** The note. Phrasing content only; a sentence or two. For ossia, a few words; for expand, the words the abbreviation stands for, as a string. */\n  note: React.ReactNode\n  /**\n   * leader: a leader line draws out to the note. ossia: the note is always there,\n   * set small in the line space above the term. expand: the abbreviation's letters\n   * move apart and the words grow between them. revise: pressed, the term is struck\n   * through and the note, its replacement, is written in after it.\n   */\n  variant?: \"leader\" | \"ossia\" | \"expand\" | \"revise\"\n}\n\ntype Part = { text: string; keep: boolean }\n\n/**\n * Splits `full` into the letters `term` keeps and the rest, which grows in between.\n * A kept letter is the start of a word or a capital inside one (HyperText); a word\n * that keeps none (\"and\") grows whole. Null if the term's letters aren't found in order.\n */\nfunction initials(term: string, full: string): Part[][] | null {\n  const letters = [...term].filter((c) => /[\\p{L}\\p{N}]/u.test(c))\n  let k = 0\n  const words = full.trim().split(/\\s+/).map((word) => {\n    const parts: Part[] = []\n    ;[...word].forEach((c, i) => {\n      const keep = k < letters.length && c.toLowerCase() === letters[k].toLowerCase() && (i === 0 || c !== c.toLowerCase())\n      const text = keep ? letters[k++] : c\n      const last = parts[parts.length - 1]\n      if (last && last.keep === keep && !keep) last.text += text\n      else parts.push({ text, keep })\n    })\n    return parts\n  })\n  return k === letters.length && letters.length > 0 ? words : null\n}\n\n/**\n * Marginalia: a dotted term. Point at it or focus it and a leader line draws\n * out to the note. Leaving plays it backwards; Escape puts it away.\n */\nfunction Note({ children, note, variant = \"leader\", className, onKeyDown, onClick, ...props }: NoteProps) {\n  const id = React.useId()\n  const [open, setOpen] = React.useState(false)\n\n  if (variant === \"ossia\") {\n    return (\n      <span data-slot=\"note\" data-variant=\"ossia\" className={cn(\"ot-note\", className)} {...(props as React.ComponentProps<\"span\">)}>\n        {children}\n        <span data-slot=\"note-text\" className=\"ot-note-text\">\n          <span className=\"ot-sr\"> (</span>\n          {note}\n          <span className=\"ot-sr\">)</span>\n        </span>\n      </span>\n    )\n  }\n\n  if (variant === \"revise\") {\n    // The editor's correction: the pen strikes the term, and the word that replaces it is written in after it.\n    // A span, not a button, so a struck phrase can break across lines as the sentence does.\n    const toggle = () => setOpen((o) => !o)\n    return (\n      <span\n        role=\"button\"\n        tabIndex={0}\n        aria-expanded={open}\n        data-slot=\"note\"\n        data-variant=\"revise\"\n        className={cn(\"ot-note\", className)}\n        {...(props as React.ComponentProps<\"span\">)}\n        onClick={(e) => {\n          onClick?.(e as unknown as React.MouseEvent<HTMLButtonElement>)\n          toggle()\n        }}\n        onKeyDown={(e) => {\n          onKeyDown?.(e as unknown as React.KeyboardEvent<HTMLButtonElement>)\n          if (e.key === \"Enter\" || e.key === \" \") {\n            e.preventDefault()\n            toggle()\n          }\n          if (e.key === \"Escape\") setOpen(false)\n        }}\n      >\n        <span className=\"ot-sr\">{open ? <><del>{children}</del> <ins>{note}</ins></> : children}</span>\n        <span className=\"ot-note-was\" aria-hidden=\"true\">{children}</span>\n        {/* A real space, so that where the correction wraps to the next line the space falls away at the break. */}\n        {open || String((props as Record<string, unknown>)[\"data-force\"] ?? \"\").includes(\"open\") ? \" \" : null}\n        <span className=\"ot-note-now\" aria-hidden=\"true\">{note}</span>\n      </span>\n    )\n  }\n\n  const words = variant === \"expand\" && typeof children === \"string\" && typeof note === \"string\" ? initials(children, note) : null\n  if (words) {\n    // A span, not a button: a button can't break across lines, and the words it grows into must.\n    const toggle = () => setOpen((o) => !o)\n    return (\n      <span\n        role=\"button\"\n        tabIndex={0}\n        dir=\"auto\"\n        aria-expanded={open}\n        aria-describedby={id}\n        data-slot=\"note\"\n        data-variant=\"expand\"\n        className={cn(\"ot-note\", className)}\n        {...(props as React.ComponentProps<\"span\">)}\n        onClick={(e) => {\n          onClick?.(e as unknown as React.MouseEvent<HTMLButtonElement>)\n          toggle()\n        }}\n        onKeyDown={(e) => {\n          onKeyDown?.(e as unknown as React.KeyboardEvent<HTMLButtonElement>)\n          if (e.key === \"Enter\" || e.key === \" \") {\n            e.preventDefault()\n            toggle()\n          }\n          if (e.key === \"Escape\") setOpen(false)\n        }}\n      >\n        <span className=\"ot-sr\">{children}</span>\n        <span aria-hidden=\"true\">\n          {words.map((parts, w) => (\n            <React.Fragment key={w}>\n              {w > 0 ? <span className=\"ot-note-grow\">{\" \"}</span> : null}\n              <span className=\"ot-note-word\">\n                {parts.map((p, i) => (p.keep ? <span key={i} className=\"ot-note-keep\">{p.text}</span> : <span key={i} className=\"ot-note-grow\">{p.text}</span>))}\n              </span>\n            </React.Fragment>\n          ))}\n        </span>\n        <span id={id} hidden>{note}</span>\n      </span>\n    )\n  }\n\n  return (\n    <button\n      type=\"button\"\n      data-slot=\"note\"\n      aria-describedby={id}\n      className={cn(\"ot-note\", className)}\n      onClick={onClick}\n      onKeyDown={(e) => {\n        onKeyDown?.(e)\n        if (e.key === \"Escape\") e.currentTarget.blur()\n      }}\n      {...props}\n    >\n      {children}\n      <span data-slot=\"note-text\" id={id} aria-hidden=\"true\" className=\"ot-note-text\">\n        {note}\n      </span>\n    </button>\n  )\n}\n\nexport { Note, type NoteProps }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/0nlytype/styles/note.css",
      "content": "@layer components {\n  /* ── ot-note ── marginalia: a leader line draws out to the note */\n  .ot-note:not([data-variant]) {\n    position: relative; display: inline; appearance: none; border: 0; background: none; margin: 0; padding: 0;\n    font: inherit; color: inherit; cursor: help;\n    text-decoration-line: underline; text-decoration-style: dotted; text-decoration-thickness: var(--ot-hairline);\n    text-decoration-color: var(--ot-pencil); text-underline-offset: 0.24em;\n  }\n  .ot-note:not([data-variant]) {\n    padding-inline: 0.12em; margin-inline: -0.12em; box-decoration-break: clone; -webkit-box-decoration-break: clone;\n    background: linear-gradient(var(--ot-mark), var(--ot-mark)) no-repeat 100% 0 / 0% 100%;\n    transition: background-size var(--ot-moderato) var(--ot-exhale) 180ms, background-position 0s, color 0s linear calc(180ms + var(--ot-allegro));\n  }\n  /* In: mark, anchor, line, note. Out: the same, backwards. */\n  .ot-note:not([data-variant])::before {\n    content: \"\"; position: absolute; inset-inline-start: calc(0.6em - 2px); inset-block-start: calc(100% + 0.15em - 2px); z-index: 1;\n    inline-size: 5px; block-size: 5px; border-radius: 50%; background: var(--ot-ink);\n    scale: 0; transition: scale var(--ot-moderato) var(--ot-spiccato) 140ms;\n  }\n  .ot-note:not([data-variant]):is(:hover, :focus-visible, [data-force~=\"hover\"]) { background-position: 0 0; background-size: 100% 100%; color: var(--ot-on-mark); text-decoration-color: transparent; transition-delay: 0s; }\n  .ot-note:not([data-variant]):is(:hover, :focus-visible, [data-force~=\"hover\"])::before { scale: 1; transition-delay: 60ms; }\n  .ot-note:not([data-variant])::after {\n    content: \"\"; position: absolute; inset-inline-start: 0.6em; inset-block-start: calc(100% + 0.15em);\n    inline-size: var(--ot-hairline); block-size: 2em; background: var(--ot-ink);\n    scale: 1 0; transform-origin: top; transition: scale var(--ot-moderato) var(--ot-exhale) 60ms;\n  }\n  .ot-note:not([data-variant]) > .ot-note-text {\n    position: absolute; z-index: 5; inset-inline-start: 0; inset-block-start: calc(100% + 2.1em);\n    inline-size: max-content; max-inline-size: min(28ch, calc(100vw - 2.5rem)); padding: 0.55em 0.95em 0.6em; border-radius: 1em;\n    background: var(--ot-ink); color: var(--ot-paper);\n    font-family: var(--ot-voice); font-style: normal; font-weight: 450; font-size: var(--ot-pp); line-height: var(--ot-pp-lh); letter-spacing: var(--ot-pp-tr);\n    text-align: start; white-space: normal; text-wrap: balance;\n    clip-path: circle(0 at 0.6em 0); pointer-events: none;\n    /* Hidden, the note leaves layout: one near the page's edge must not make the page scroll sideways. */\n    display: none;\n    transition: clip-path var(--ot-moderato) var(--ot-breath), display var(--ot-moderato) allow-discrete;\n  }\n  .ot-note:not([data-variant]):is(:hover, :focus-visible, [data-force~=\"hover\"])::after { scale: 1 1; transition-delay: 120ms; }\n  .ot-note:not([data-variant]):is(:hover, :focus-visible, [data-force~=\"hover\"]) > .ot-note-text {\n    display: block; clip-path: circle(150% at 0.6em 0); pointer-events: auto;\n    transition: clip-path var(--ot-andante) var(--ot-exhale) 300ms, display 0s allow-discrete;\n    @starting-style { clip-path: circle(0 at 0.6em 0); }\n  }\n\n  /* ossia: the note is always there, small, in the line space above the term, as a score prints an ossia bar over the staff.\n     The term's line opens to make room; the note starts where the term starts, as an ossia starts with its bar, and may run on past its end. Pointing inks it. */\n  .ot-note[data-variant=\"ossia\"] { position: relative; color: var(--ot-ink); display: inline-block; padding-block-start: calc(var(--ot-pp) * 1.25); }\n  .ot-note[data-variant=\"ossia\"] > .ot-note-text {\n    position: absolute; inset-block-start: 0; inset-inline-start: 0; white-space: nowrap;\n    font-family: var(--ot-voice); font-style: normal; font-weight: 450; font-size: var(--ot-pp); line-height: 1; letter-spacing: var(--ot-pp-tr);\n    color: var(--ot-pencil); transition: color var(--ot-allegro);\n  }\n  .ot-note[data-variant=\"ossia\"]:is(:hover, [data-force~=\"hover\"]) > .ot-note-text { color: var(--ot-ink); }\n\n  /* expand: an abbreviation that opens into its words. Pressed, the letters move apart and each word grows out of its initial;\n     a word with none (\"and\") grows whole. The initials stay in ink, the rest arrives in graphite. */\n  .ot-note[data-variant=\"expand\"] {\n    cursor: pointer; interpolate-size: allow-keywords; color: var(--ot-ink);\n    text-decoration-line: underline; text-decoration-style: dotted; text-decoration-thickness: var(--ot-hairline);\n    text-decoration-color: var(--ot-pencil); text-underline-offset: 0.24em; transition: text-decoration-color var(--ot-allegro);\n  }\n  .ot-note[data-variant=\"expand\"]:is(:hover, [data-force~=\"hover\"]) { text-decoration-color: var(--ot-ink); }\n  .ot-note[data-variant=\"expand\"]:is([aria-expanded=\"true\"], [data-force~=\"open\"]) { text-decoration-color: transparent; }\n  .ot-note-word { white-space: nowrap; }\n  .ot-note-grow {\n    display: inline-block; inline-size: 0; overflow-x: clip; vertical-align: top; white-space: pre; color: var(--ot-graphite); opacity: 0;\n    transition: inline-size var(--ot-moderato) var(--ot-exhale), opacity var(--ot-moderato) var(--ot-exhale);\n  }\n  .ot-note[data-variant=\"expand\"]:is([aria-expanded=\"true\"], [data-force~=\"open\"]) .ot-note-grow { inline-size: auto; opacity: 1; }\n\n  /* revise: the editor's correction, after the Basquiat sheet's struck headline and the Weingart letter's typed page. At rest\n     the term is dotted like the others; pointing sketches the strike in pencil, as the checkbox does. Pressed, the pen\n     strikes it in ink, running past the words as a hand does, the struck words step back to pencil, and the replacement\n     is written in after them in the italic, its box opening from the start as a pen moves. Pressed again, both lift. */\n  .ot-note[data-variant=\"revise\"] { cursor: pointer; interpolate-size: allow-keywords; color: var(--ot-ink); }\n  .ot-note-was {\n    --y: 60%;\n    padding-inline: 0.1em; margin-inline: -0.1em; /* the pen runs a little past the words, short of the word before */\n    text-decoration-line: underline; text-decoration-style: dotted; text-decoration-thickness: var(--ot-hairline);\n    text-decoration-color: var(--ot-pencil); text-underline-offset: 0.24em;\n    background:\n      linear-gradient(var(--ot-ink), var(--ot-ink)) no-repeat 100% var(--y) / 0% var(--ot-stroke),\n      linear-gradient(var(--ot-rule-strong), var(--ot-rule-strong)) no-repeat 100% var(--y) / 0% var(--ot-hairline);\n    -webkit-box-decoration-break: clone; box-decoration-break: clone;\n    transition: background-size var(--ot-moderato) var(--ot-breath), background-position 0s, color var(--ot-moderato) var(--ot-breath), text-decoration-color var(--ot-allegro);\n  }\n  .ot-note[data-variant=\"revise\"]:is(:hover, [data-force~=\"hover\"]):not([aria-expanded=\"true\"], [data-force~=\"open\"]) > .ot-note-was { text-decoration-color: var(--ot-ink); background-position: 100% var(--y), 0 var(--y); background-size: 0% var(--ot-stroke), 100% var(--ot-hairline); }\n  .ot-note[data-variant=\"revise\"]:is([aria-expanded=\"true\"], [data-force~=\"open\"]) > .ot-note-was { color: var(--ot-pencil); text-decoration-color: transparent; background-position: 0 var(--y), 100% var(--y); background-size: 100% var(--ot-stroke), 0% var(--ot-hairline); }\n  :is([dir=\"rtl\"], [dir=\"rtl\"] *).ot-note[data-variant=\"revise\"] > .ot-note-was { background-position: 0 var(--y), 0 var(--y); }\n  :is([dir=\"rtl\"], [dir=\"rtl\"] *).ot-note[data-variant=\"revise\"]:is(:hover, [data-force~=\"hover\"]):not([aria-expanded=\"true\"], [data-force~=\"open\"]) > .ot-note-was { background-position: 0 var(--y), 100% var(--y); }\n  :is([dir=\"rtl\"], [dir=\"rtl\"] *).ot-note[data-variant=\"revise\"]:is([aria-expanded=\"true\"], [data-force~=\"open\"]) > .ot-note-was { background-position: 100% var(--y), 0 var(--y); }\n  .ot-note-now {\n    display: inline-block; inline-size: 0; overflow-x: clip; overflow-clip-margin: 0.2em; white-space: pre; opacity: 0;\n    font-family: var(--ot-expression); font-style: italic; font-size: calc(var(--ot-expression-scale) * 1em); line-height: 1;\n    transition: inline-size var(--ot-moderato) var(--ot-exhale), opacity var(--ot-allegro);\n  }\n  /* The strike draws first; the word is written in once the pen has crossed. */\n  .ot-note[data-variant=\"revise\"]:is([aria-expanded=\"true\"], [data-force~=\"open\"]) > .ot-note-now { inline-size: auto; opacity: 1; transition-delay: var(--ot-allegro); }\n  @media (forced-colors: active) {\n    /* Forced colours drop the gradients that draw the strike; the plain line-through says it instead. */\n    .ot-note[data-variant=\"revise\"]:is([aria-expanded=\"true\"], [data-force~=\"open\"]) > .ot-note-was { text-decoration: line-through CanvasText; }\n  }\n}\n",
      "type": "registry:file",
      "target": "styles/0nlytype/note.css"
    }
  ],
  "css": {
    "@import \"../styles/0nlytype/note.css\"": {}
  },
  "meta": {
    "movement": "VIII",
    "underneath": "native",
    "contract": "ot-note"
  },
  "type": "registry:ui"
}
