{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "badge",
  "title": "Badge",
  "description": "The one rounded shape: a hairline pill for a tag or a status; or tags written as one list in the italic; or a seal with its legend set round a ring.",
  "registryDependencies": [
    "https://thedirectors.agency/ui/r/0nlytype.json"
  ],
  "files": [
    {
      "path": "registry/0nlytype/ui/badge.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype BadgeProps = Omit<React.ComponentProps<\"span\">, \"onClick\"> & {\n  /**\n   * default, ink and accent are pills. series drops the pill: sibling tags are\n   * written as one list (\"Identity, Web and Motion\"), in the italic, and the\n   * commas and the \"and\" re-set as tags come and go. seal sets `legend` round\n   * a ring, like a coin, with the children in the middle.\n   */\n  variant?: \"default\" | \"ink\" | \"accent\" | \"series\" | \"seal\"\n  /**\n   * Makes the tag removable: the whole pill becomes a button with a cross, and\n   * pointing at it strikes the word. It closes up, then this is called.\n   * Focus moves to the neighbouring tag. Not for seal.\n   */\n  onRemove?: () => void\n  /** seal: the words set round the rim, about 12 to 24 characters. */\n  legend?: string\n}\n\n/** The one rounded shape: a hairline pill. */\nfunction Badge({ className, variant = \"default\", onRemove, legend, children, ...props }: BadgeProps) {\n  const id = React.useId()\n  const dataVariant = variant === \"default\" ? undefined : variant\n\n  if (variant === \"seal\") {\n    const name = props[\"aria-label\"] ?? [typeof children === \"string\" ? children : \"\", legend].filter(Boolean).join(\", \")\n    return (\n      <span data-slot=\"badge\" data-variant=\"seal\" role=\"img\" className={cn(\"ot-tag\", className)} {...props} aria-label={name}>\n        <svg viewBox=\"0 0 90 90\" aria-hidden=\"true\">\n          <circle className=\"ot-tag-rim\" cx=\"45\" cy=\"45\" r=\"44.5\" />\n          <circle className=\"ot-tag-rim\" cx=\"45\" cy=\"45\" r=\"28.5\" />\n          {/* From the foot, round by the left: the legend reads clockwise over the top, as on a coin. */}\n          <path id={id} d=\"M45 77a32 32 0 1 1 0-64a32 32 0 1 1 0 64\" fill=\"none\" />\n          <text className=\"ot-tag-legend\">\n            <textPath href={`#${id}`} textLength={2 * Math.PI * 32} lengthAdjust=\"spacing\">{`${legend ?? \"\"} · `}</textPath>\n          </text>\n        </svg>\n        <span className=\"ot-tag-face\" aria-hidden=\"true\">{children}</span>\n      </span>\n    )\n  }\n\n  if (!onRemove) {\n    return (\n      <span data-slot=\"badge\" data-variant={dataVariant} className={cn(\"ot-tag\", className)} {...props}>\n        {children}\n      </span>\n    )\n  }\n\n  const { \"aria-label\": label, ...rest } = props\n  const remove = (e: React.MouseEvent<HTMLButtonElement>) => {\n    const tag = e.currentTarget\n    if (tag.hasAttribute(\"data-leaving\")) return\n    const next = tag.nextElementSibling ?? tag.previousElementSibling\n    const done = () => {\n      if (next instanceof HTMLButtonElement) next.focus()\n      onRemove()\n    }\n    if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return done()\n    // Struck through, the tag closes up and its neighbours slide into the space.\n    tag.setAttribute(\"data-leaving\", \"\")\n    tag.style.overflow = \"clip\"\n    const gap = parseFloat(getComputedStyle(tag.parentElement ?? tag).columnGap) || 0\n    tag\n      .animate(\n        [\n          { width: `${tag.offsetWidth}px`, opacity: 1 },\n          { width: \"0px\", paddingInline: \"0px\", marginInlineEnd: `${-gap}px`, opacity: 0 },\n        ],\n        { duration: 480, easing: \"cubic-bezier(.65,0,.35,1)\", fill: \"forwards\" },\n      )\n      .finished.then(done)\n  }\n\n  return (\n    <button\n      type=\"button\"\n      data-slot=\"badge\"\n      data-variant={dataVariant}\n      data-removable=\"\"\n      aria-label={label ?? (typeof children === \"string\" ? `Remove ${children}` : \"Remove\")}\n      className={cn(\"ot-tag\", className)}\n      onClick={remove}\n      {...(rest as React.ComponentProps<\"button\">)}\n    >\n      <span>{children}</span>\n    </button>\n  )\n}\n\nexport { Badge, type BadgeProps }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/0nlytype/styles/badge.css",
      "content": "@layer components {\n  /* ── ot-tag ── the one rounded shape. The word is set as Weingart set his callouts: small, a little heavy and narrow, so it holds as type. */\n  .ot-tag {\n    display: inline-flex; align-items: baseline; gap: 0.45em;\n    margin: 0; border: 0; padding: 0.22em 0.8em 0.26em; border-radius: 999px;\n    background: none; box-shadow: inset 0 0 0 var(--ot-hairline) var(--ot-rule-strong);\n    font-family: var(--ot-voice); font-size: var(--ot-pp); line-height: 1.3; font-weight: 500; font-stretch: 92%; letter-spacing: 0.01em;\n    color: var(--ot-ink); white-space: nowrap;\n  }\n  .ot-tag[data-variant=\"ink\"] { background: var(--ot-ink); color: var(--ot-paper); box-shadow: none; font-weight: 650; font-stretch: 88%; }\n  .ot-tag[data-variant=\"accent\"] { color: var(--ot-accent); box-shadow: inset 0 0 0 var(--ot-hairline) currentColor; }\n  button.ot-tag { cursor: pointer; transition: box-shadow var(--ot-allegro), color var(--ot-allegro); }\n  button.ot-tag[data-removable]::after { content: \"×\" / \"\"; opacity: 0.5; transition: opacity var(--ot-allegro); }\n  button.ot-tag:is(:hover, [data-force~=\"hover\"]) { color: var(--ot-ink); box-shadow: inset 0 0 0 var(--ot-hairline) var(--ot-ink); }\n  button.ot-tag:is(:hover, [data-force~=\"hover\"])::after { opacity: 1; }\n  button.ot-tag[data-removable] > span {\n    background: linear-gradient(currentColor, currentColor) no-repeat 100% 58% / 0% var(--ot-hairline);\n    transition: background-size var(--ot-moderato) var(--ot-breath), background-position 0s;\n  }\n  button.ot-tag[data-removable]:is(:hover, :focus-visible, [data-force~=\"hover\"]) > span { background-position: 0 58%; background-size: 100% var(--ot-hairline); }\n\n  /* series: no pill. Sibling tags are one written list, yours in the italic; ours are the commas and the \"and\", which re-set\n     themselves as a tag leaves (a leaving tag no longer counts). --and localises the conjunction. */\n  .ot-tag[data-variant=\"series\"] {\n    --and: \"and\";\n    display: inline-block; padding: 0; border-radius: 0; box-shadow: none; background: none; color: var(--ot-ink);\n    font-family: var(--ot-expression); font-style: italic; font-weight: 400; font-stretch: 100%; letter-spacing: 0;\n    font-size: calc(var(--ot-expression-scale) * 1em); line-height: inherit; vertical-align: baseline;\n  }\n  button.ot-tag[data-variant=\"series\"], button.ot-tag[data-variant=\"series\"]:is(:hover, [data-force~=\"hover\"]) { box-shadow: none; color: var(--ot-ink); }\n  button.ot-tag[data-variant=\"series\"] > span { background-position: 100% 62%; }\n  button.ot-tag[data-variant=\"series\"]:is(:hover, :focus-visible, [data-force~=\"hover\"]) > span { background-position: 0 62%; }\n  button.ot-tag[data-variant=\"series\"]::after { content: none; }\n  :is(span, button).ot-tag[data-variant=\"series\"]::after {\n    font-family: var(--ot-voice); font-style: normal; font-size: calc(1em / var(--ot-expression-scale)); color: var(--ot-graphite); opacity: 1;\n  }\n  .ot-tag[data-variant=\"series\"]:not(:nth-last-child(1 of .ot-tag[data-variant=\"series\"]:not([data-leaving]))) { margin-inline-end: 0.28em; }\n  .ot-tag[data-variant=\"series\"]:nth-last-child(n + 3 of .ot-tag[data-variant=\"series\"]:not([data-leaving]))::after { content: \",\"; }\n  .ot-tag[data-variant=\"series\"]:nth-last-child(2 of .ot-tag[data-variant=\"series\"]:not([data-leaving]))::after { content: \"\\a0\" var(--and); }\n\n  /* seal: a coin. The legend set round the rim between two hairline rings, the face in the middle; pointing turns the legend a little. */\n  .ot-tag[data-variant=\"seal\"] {\n    position: relative; display: inline-grid; place-items: center; inline-size: var(--ot-space-8); block-size: var(--ot-space-8); padding: 0;\n    box-shadow: none; color: var(--ot-ink); vertical-align: middle;\n  }\n  .ot-tag[data-variant=\"seal\"] > svg { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; overflow: visible; }\n  .ot-tag-rim { fill: none; stroke: var(--ot-rule-strong); stroke-width: var(--ot-hairline); }\n  .ot-tag-legend {\n    fill: currentColor; font-family: var(--ot-voice); font-size: var(--ot-pp); font-weight: 550; font-stretch: 100%; text-transform: uppercase;\n    transform-origin: 45px 45px; transition: rotate var(--ot-andante) var(--ot-breath);\n  }\n  .ot-tag[data-variant=\"seal\"]:is(:hover, [data-force~=\"hover\"]) .ot-tag-legend { rotate: 30deg; }\n  .ot-tag-face { font-size: var(--ot-mf); line-height: 1; letter-spacing: var(--ot-mf-tr); font-weight: 250; font-stretch: 100%; font-variant-numeric: lining-nums; }\n  @media (forced-colors: active) {\n    .ot-tag:not([data-variant=\"series\"], [data-variant=\"seal\"]) { outline: var(--ot-hairline) solid CanvasText; outline-offset: calc(-1 * var(--ot-hairline)); }\n    button.ot-tag[data-removable]:is(:hover, :focus-visible, [data-force~=\"hover\"]) > span { text-decoration: line-through; }\n  }\n  @media (forced-colors: active) { .ot-tag-rim { stroke: CanvasText; } .ot-tag-legend { fill: CanvasText; } }\n}\n",
      "type": "registry:file",
      "target": "styles/0nlytype/badge.css"
    }
  ],
  "css": {
    "@import \"../styles/0nlytype/badge.css\"": {}
  },
  "meta": {
    "movement": "VIII",
    "underneath": "native",
    "contract": "ot-tag"
  },
  "type": "registry:ui"
}
