{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "button",
  "title": "Button",
  "description": "One family of three (a statement in an ink block, a bracket that steps apart, a quiet line that redraws), four heroes for the big call to action (overture, crescendo, stave and ink), and two measures: space and repeat. Nothing moves its neighbours.",
  "dependencies": [
    "@radix-ui/react-slot"
  ],
  "registryDependencies": [
    "https://thedirectors.agency/ui/r/0nlytype.json",
    "https://thedirectors.agency/ui/r/spinner.json"
  ],
  "files": [
    {
      "path": "registry/0nlytype/ui/button.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Spinner } from \"@/components/ui/spinner\"\n\ntype ButtonProps = React.ComponentProps<\"button\"> & {\n  /**\n   * The base set, one family: statement (reversed type in an ink block, one per view),\n   * bracket (a secondary action held in ( )) and quiet (only a line, and the line redraws).\n   * The hero set, for the big call to action: overture (the word swells and ends in a full stop),\n   * crescendo (a hairpin under the word opens as it swells), stave (the word set across five lines) and\n   * ink (an outlined block that fills from the side you came in).\n   * The measures: space (the words spread across the line; pointing gathers them and the silence\n   * they leave becomes an arrow) and repeat (the word between repeat signs, for doing it again).\n   */\n  variant?: \"statement\" | \"bracket\" | \"quiet\" | \"overture\" | \"crescendo\" | \"stave\" | \"ink\" | \"space\" | \"repeat\"\n  size?: \"m\" | \"l\"\n  /** Render the child element (a link, say) with the button's look. */\n  asChild?: boolean\n  /**\n   * The button says what it's doing. A string replaces the label while busy\n   * (\"Save\" becomes \"Saving\"); true keeps the label. Either way the periods breathe.\n   */\n  busy?: boolean | string\n}\n\n/** The words as plain text, or null when the label holds elements. */\nfunction textOf(node: React.ReactNode) {\n  let text = \"\"\n  let plain = true\n  React.Children.forEach(node, (c) => {\n    if (typeof c === \"string\" || typeof c === \"number\") text += c\n    else if (c != null && typeof c !== \"boolean\") plain = false\n  })\n  return plain ? text : null\n}\n\n/**\n * Space's label: the words as flex items with a real space between each, so the name reads as\n * one phrase, and a lead before them that grows into an arrow as the spaces close.\n */\nfunction Spread({ text }: { text: string }) {\n  const words = text.trim().split(/\\s+/)\n  return (\n    <span className=\"ot-btn-label\">\n      <span className=\"ot-btn-lead\" aria-hidden=\"true\" />\n      {words.map((w, i) => (\n        <React.Fragment key={i}>\n          {i > 0 ? <span className=\"ot-btn-gap\"> </span> : null}\n          <span>{w}</span>\n        </React.Fragment>\n      ))}\n    </span>\n  )\n}\n\n/**\n * The label sits in a grid cell with an invisible copy of itself set at its widest\n * (the hover weight and width). The button is always as wide as its widest state,\n * so nothing beside it moves when the letters swell.\n */\nfunction Label({ children }: { children?: React.ReactNode }) {\n  return (\n    <span className=\"ot-btn-label\" data-text={textOf(children) ?? undefined}>\n      <span>{children}</span>\n    </span>\n  )\n}\n\nfunction Button({ className, variant = \"bracket\", size = \"m\", asChild = false, busy = false, children, onPointerEnter, onPointerLeave, ...props }: ButtonProps) {\n  const Comp = asChild ? Slot : \"button\"\n  // The ink fills from the side you came in and leaves toward the side you go.\n  const edge = (e: React.PointerEvent<HTMLElement>) => {\n    const r = e.currentTarget.getBoundingClientRect()\n    e.currentTarget.dataset.edge = e.clientX < r.left + r.width / 2 ? \"left\" : \"right\"\n  }\n  const child = asChild && React.isValidElement<{ children?: React.ReactNode }>(children) ? children : null\n  const label = (words: React.ReactNode) => {\n    const text = variant === \"space\" ? textOf(words) : null\n    return text ? <Spread text={text} /> : <Label>{words}</Label>\n  }\n  const content =\n    busy && !asChild ? (\n      <>\n        {label(typeof busy === \"string\" ? busy : children)}\n        <Spinner />\n      </>\n    ) : (\n      label(child ? child.props.children : children)\n    )\n  return (\n    <Comp\n      data-slot=\"button\"\n      data-variant={variant}\n      data-size={size === \"m\" ? undefined : size}\n      aria-busy={busy ? true : undefined}\n      className={cn(\"ot-btn\", className)}\n      {...(asChild ? {} : { type: \"button\" as const })}\n      {...(variant === \"ink\"\n        ? {\n            onPointerEnter: (e: React.PointerEvent<HTMLElement>) => (edge(e), (onPointerEnter as React.PointerEventHandler<HTMLElement> | undefined)?.(e)),\n            onPointerLeave: (e: React.PointerEvent<HTMLElement>) => (edge(e), (onPointerLeave as React.PointerEventHandler<HTMLElement> | undefined)?.(e)),\n          }\n        : { onPointerEnter, onPointerLeave })}\n      {...props}\n    >\n      {child ? React.cloneElement(child, undefined, content) : content}\n    </Comp>\n  )\n}\n\nexport { Button, type ButtonProps }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/0nlytype/styles/button.css",
      "content": "@layer components {\n  /* ── ot-btn ── one family (statement, bracket, quiet), four heroes (overture, crescendo, stave, ink) and two measures (space, repeat).\n     Every variant shares one box: the same line-height and vertical padding, so they stand on one\n     baseline at one height. Nothing ever changes the box: what swells or draws does so inside it. */\n  .ot-btn {\n    --py: 0.7em; --dir: 1;\n    --w: 500; --w-on: 500; --s: 100%; --s-on: 100%; /* the label's weight and width at rest and pointed at */\n    appearance: none; border: 0; background: none; margin: 0; padding: var(--py) 0.25em;\n    position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 0.4em;\n    font-family: var(--ot-voice); font-size: var(--ot-p); font-weight: 500; line-height: 1.2; letter-spacing: 0.005em; white-space: nowrap;\n    color: var(--ot-ink); text-decoration: none; cursor: pointer;\n    -webkit-tap-highlight-color: transparent;\n  }\n  .ot-btn:is([dir=\"rtl\"], [dir=\"rtl\"] *) { --dir: -1; }\n  .ot-btn[data-size=\"l\"] { font-size: var(--ot-mp); }\n  .ot-btn:is(:disabled, [aria-disabled=\"true\"]) { color: var(--ot-pencil); cursor: not-allowed; }\n  .ot-btn[aria-busy=\"true\"] { cursor: progress; }\n\n  /* The label reserves the width of its widest state: an invisible copy at the hover weight shares its cell. */\n  .ot-btn-label { display: inline-grid; justify-items: var(--align, center); }\n  .ot-btn-label > span, .ot-btn-label::after { grid-area: 1 / 1; }\n  .ot-btn-label > span { position: relative; font-weight: var(--w); font-stretch: var(--s); transition: font-weight var(--ot-moderato) var(--ot-exhale), font-stretch var(--ot-moderato) var(--ot-exhale); }\n  .ot-btn-label::after { content: attr(data-text) / \"\"; visibility: hidden; height: 0; font-weight: var(--w-on); font-stretch: var(--s-on); }\n  .ot-btn:is(:hover, [data-force~=\"hover\"]):not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"]) > .ot-btn-label > span { font-weight: var(--w-on); font-stretch: var(--s-on); }\n\n  /* ── the family ── */\n  .ot-btn[data-variant=\"statement\"] {\n    --s-on: 114%; --ot-corner-inset: -9px;\n    background: var(--ot-ink); color: var(--ot-paper); padding-inline: 1.25em;\n    transition: background-color var(--ot-allegro), color var(--ot-allegro), translate var(--ot-allegro) var(--ot-exhale);\n  }\n  .ot-btn[data-variant=\"statement\"]::after { opacity: 0; scale: 1.14 1.3; transition: opacity var(--ot-allegro), scale var(--ot-andante) var(--ot-spiccato); }\n  .ot-btn[data-variant=\"statement\"]:is(:hover, [data-force~=\"hover\"]):not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"])::after { opacity: 1; scale: 1; }\n  .ot-btn[data-variant=\"statement\"]:active:not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"]) { translate: 0 1px; }\n  .ot-btn[data-variant=\"statement\"]:active:not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"])::after { scale: 0.97 0.84; transition-duration: var(--ot-allegro); }\n  .ot-btn[data-variant=\"statement\"]:is(:disabled, [aria-disabled=\"true\"]) { background: var(--ot-rule); color: var(--ot-pencil); }\n\n  /* The parentheses are drawn outside the flow, so the label alone sets the baseline and the box. */\n  .ot-btn[data-variant=\"bracket\"] { padding-inline: 1.2em; }\n  .ot-btn[data-variant=\"bracket\"]::before,\n  .ot-btn[data-variant=\"bracket\"]::after {\n    position: absolute; inset-block: 0; display: grid; align-items: center;\n    font-weight: 200; font-size: 1.2em; line-height: 1;\n    transition: translate var(--ot-andante) var(--ot-spiccato);\n  }\n  .ot-btn[data-variant=\"bracket\"]::before { content: \"(\" / \"\"; inset-inline-start: 0.1em; }\n  .ot-btn[data-variant=\"bracket\"]::after { content: \")\" / \"\"; inset-inline-end: 0.1em; }\n  .ot-btn[data-variant=\"bracket\"]:is(:hover, [data-force~=\"hover\"]):not(:disabled, [aria-disabled=\"true\"])::before { translate: calc(-0.3em * var(--dir)) 0; }\n  .ot-btn[data-variant=\"bracket\"]:is(:hover, [data-force~=\"hover\"]):not(:disabled, [aria-disabled=\"true\"])::after { translate: calc(0.3em * var(--dir)) 0; }\n  .ot-btn[data-variant=\"bracket\"]:active:not(:disabled, [aria-disabled=\"true\"])::before { translate: calc(0.12em * var(--dir)) 0; transition-duration: var(--ot-allegro); }\n  .ot-btn[data-variant=\"bracket\"]:active:not(:disabled, [aria-disabled=\"true\"])::after { translate: calc(-0.12em * var(--dir)) 0; transition-duration: var(--ot-allegro); }\n\n  /* The line is two strokes, so every change is a transition and can turn back from wherever it is: the hairline\n     (::before) leaves to the inline end, then the stroke (::after) draws from the inline start. Leaving rewinds them:\n     the stroke goes back to the start and the hairline returns behind it. */\n  .ot-btn[data-variant=\"quiet\"] {\n    /* The line stands just under the words, inside the shared box: half the box, half the 1.2 line, then 0.14em. Measured\n       from the middle, it stays under the words when the box is taller (a key cap beside the label, a 44px touch target). */\n    --at: calc(50% + 0.74em); padding-inline: 0.1em;\n    transition: translate var(--ot-allegro) var(--ot-exhale);\n  }\n  .ot-btn[data-variant=\"quiet\"]::before, .ot-btn[data-variant=\"quiet\"]::after {\n    content: \"\"; position: absolute; inset-inline: 0; top: var(--at); pointer-events: none; background: currentColor;\n  }\n  .ot-btn[data-variant=\"quiet\"]::before {\n    height: var(--ot-hairline); transform-origin: calc(50% + 50% * var(--dir)) 0;\n    transition: scale var(--ot-moderato) var(--ot-breath);\n  }\n  .ot-btn[data-variant=\"quiet\"]::after {\n    height: var(--ot-stroke); scale: 0 1; transform-origin: calc(50% - 50% * var(--dir)) 0;\n    transition: scale var(--ot-allegro) var(--ot-breath);\n  }\n  .ot-btn[data-variant=\"quiet\"]:is(:hover, [data-force~=\"hover\"]):not(:disabled, [aria-disabled=\"true\"])::before { scale: 0 1; }\n  .ot-btn[data-variant=\"quiet\"]:is(:hover, [data-force~=\"hover\"]):not(:disabled, [aria-disabled=\"true\"])::after {\n    scale: 1 1; transition-duration: var(--ot-moderato); transition-delay: var(--ot-moderato);\n  }\n  .ot-btn[data-variant=\"quiet\"]:is(:active, [data-force~=\"press\"]):not(:disabled, [aria-disabled=\"true\"]) { translate: 0 1px; }\n\n  /* ── the heroes ── each its own personality, all type-led; each keeps the box it rests in */\n  .ot-btn:is([data-variant=\"crescendo\"], [data-variant=\"stave\"], [data-variant=\"ink\"]) { font-size: var(--ot-mp); }\n  .ot-btn:is([data-variant=\"crescendo\"], [data-variant=\"stave\"], [data-variant=\"ink\"])[data-size=\"l\"] { font-size: var(--ot-mf); }\n\n  /* overture: light, and swelling to heavy as you come to it; a full stop lands after the word. */\n  .ot-btn[data-variant=\"overture\"] {\n    --w: 200; --w-on: 800; --align: start; --py: 0.1em;\n    align-items: baseline; justify-content: flex-start; gap: 0.06em; padding-inline: 0; font-size: var(--ot-mf); letter-spacing: -0.02em; line-height: 1.1;\n  }\n  .ot-btn[data-variant=\"overture\"][data-size=\"l\"] { font-size: var(--ot-f); }\n  .ot-btn[data-variant=\"overture\"]::after {\n    content: \"\"; flex: none; inline-size: 0.16em; block-size: 0.16em; border-radius: 50%; background: var(--ot-accent);\n    opacity: 0; scale: 0.4; transition: opacity var(--ot-allegro), scale var(--ot-moderato) var(--ot-spiccato);\n  }\n  .ot-btn[data-variant=\"overture\"]:is(:hover, :focus-visible, [data-force~=\"hover\"]):not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"])::after { opacity: 1; scale: 1; }\n  .ot-btn[data-variant=\"overture\"]:is(:hover, :focus-visible, [data-force~=\"hover\"]):not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"]) > .ot-btn-label > span { font-weight: var(--w-on); }\n  .ot-btn[data-variant=\"overture\"]:active:not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"]) > .ot-btn-label > span { font-weight: 560; transition-duration: var(--ot-allegro); }\n  .ot-btn[data-variant=\"overture\"]:active:not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"])::after { scale: 0.7; transition-duration: var(--ot-allegro); }\n\n  /* crescendo: the hairpin that says \"grow louder\", two hairlines opening from a point. At rest it is barely open, a pencil sign\n     under the word (closed flat, it read as an empty field's line). Pointing opens them across the word and inks them, and the word swells a weight; pressing opens them wide in the accent.\n     The hairpin is drawn by an image mask, so its lines stay one hairline thick however far it opens. */\n  .ot-btn[data-variant=\"crescendo\"] {\n    --w: 450; --w-on: 600; --open: 0.16em; --line: var(--ot-pencil);\n    --py: 0.6em; padding: 0.6em 0.2em 1.15em;\n  }\n  .ot-btn[data-variant=\"crescendo\"]::before {\n    content: \"\"; position: absolute; inset-inline: 0.2em; top: calc(100% - 0.62em); block-size: var(--open); translate: 0 -50%; scale: var(--dir) 1;\n    background: var(--line);\n    -webkit-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath d='M100 0.5 L0 5 L100 9.5' fill='none' stroke='%23000' stroke-width='1' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E\") 0 0 / 100% 100% no-repeat;\n    mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath d='M100 0.5 L0 5 L100 9.5' fill='none' stroke='%23000' stroke-width='1' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E\") 0 0 / 100% 100% no-repeat;\n    transition: block-size var(--ot-andante) var(--ot-exhale), background-color var(--ot-moderato) var(--ot-breath);\n  }\n  .ot-btn[data-variant=\"crescendo\"]:is(:hover, :focus-visible, [data-force~=\"hover\"]):not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"]) { --open: 0.42em; --line: var(--ot-ink); }\n  .ot-btn[data-variant=\"crescendo\"]:active:not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"]) { --open: 0.62em; --line: var(--ot-accent); }\n  .ot-btn[data-variant=\"crescendo\"]:active:not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"])::before { transition-duration: var(--ot-allegro); }\n  .ot-btn[data-variant=\"crescendo\"]:is(:disabled, [aria-disabled=\"true\"]) { --line: var(--ot-rule-strong); }\n\n  /* stave: the word in the expression italic, between two staves of five hairlines that never cross it.\n     Pointing runs each stave out from the word to the button's edge. */\n  .ot-btn[data-variant=\"stave\"] {\n    --w: 400; --w-on: 400; --run: 2.6em; --reach: 0.42;\n    padding-inline: var(--run); font-family: var(--ot-expression); font-style: italic; letter-spacing: 0;\n    font-size: calc(var(--ot-mp) * var(--ot-expression-scale));\n  }\n  .ot-btn[data-variant=\"stave\"][data-size=\"l\"] { font-size: calc(var(--ot-mf) * var(--ot-expression-scale)); }\n  .ot-btn[data-variant=\"stave\"]::before, .ot-btn[data-variant=\"stave\"]::after {\n    content: \"\"; position: absolute; inset-block-start: 50%; inline-size: calc(var(--run) - 0.5em); block-size: calc(0.68em + var(--ot-hairline)); translate: 0 -50%;\n    background: repeating-linear-gradient(to bottom, var(--ot-rule-strong) 0 var(--ot-hairline), transparent var(--ot-hairline) 0.17em);\n    scale: var(--reach) 1; transition: scale var(--ot-andante) var(--ot-exhale);\n  }\n  .ot-btn[data-variant=\"stave\"]::before { inset-inline-start: 0; transform-origin: 100% 50%; }\n  .ot-btn[data-variant=\"stave\"]::after { inset-inline-end: 0; transform-origin: 0 50%; }\n  .ot-btn[data-variant=\"stave\"]:is([dir=\"rtl\"], [dir=\"rtl\"] *)::before { transform-origin: 0 50%; }\n  .ot-btn[data-variant=\"stave\"]:is([dir=\"rtl\"], [dir=\"rtl\"] *)::after { transform-origin: 100% 50%; }\n  .ot-btn[data-variant=\"stave\"]:is(:hover, :focus-visible, [data-force~=\"hover\"]):not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"]) { --reach: 1; }\n  .ot-btn[data-variant=\"stave\"]:active:not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"]) { --reach: 0.7; }\n\n  /* ink: an outlined block; pointing fills it with ink from the side you came in, and the words reverse out. */\n  .ot-btn[data-variant=\"ink\"] {\n    --py: 0.85em; --side: 0; isolation: isolate; padding-inline: 1.6em; letter-spacing: 0.01em;\n    box-shadow: inset 0 0 0 var(--ot-hairline) var(--ot-ink);\n    transition: color var(--ot-allegro), box-shadow var(--ot-allegro);\n  }\n  .ot-btn[data-variant=\"ink\"]:is([dir=\"rtl\"], [dir=\"rtl\"] *) { --side: 100%; }\n  .ot-btn[data-variant=\"ink\"][data-edge=\"left\"] { --side: 0; }\n  .ot-btn[data-variant=\"ink\"][data-edge=\"right\"] { --side: 100%; }\n  .ot-btn[data-variant=\"ink\"]::before {\n    content: \"\"; position: absolute; inset: 0; z-index: -1; background: var(--ot-ink);\n    scale: 0 1; transform-origin: var(--side) 50%; transition: scale var(--ot-moderato) var(--ot-exhale);\n  }\n  .ot-btn[data-variant=\"ink\"]:is(:hover, :focus-visible, [data-force~=\"hover\"]):not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"]) { color: var(--ot-paper); }\n  .ot-btn[data-variant=\"ink\"]:is(:hover, :focus-visible, [data-force~=\"hover\"]):not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"])::before { scale: 1 1; }\n  .ot-btn[data-variant=\"ink\"]:active:not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"]) { color: var(--ot-paper); }\n  .ot-btn[data-variant=\"ink\"]:active:not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"])::before { scale: 1 0.92; transition-duration: var(--ot-allegro); }\n  .ot-btn[data-variant=\"ink\"]:is(:disabled, [aria-disabled=\"true\"]) { box-shadow: inset 0 0 0 var(--ot-hairline) var(--ot-rule-strong); }\n\n  /* ── the measures ── */\n  /* space: after \"Watch this space.\" The words spread across the whole line, as the Renaissance poster spreads its top row.\n     Pointing closes the spaces, the phrase gathers at the line's end, and the silence it leaves is drawn as the hairline arrow\n     that leads to it. The spaces are real, so the name reads as one phrase. */\n  .ot-btn[data-variant=\"space\"] {\n    --gap: 1; --lead: 0; --reach: 0; --nudge: 0em;\n    display: flex; inline-size: 100%; padding-inline: 0; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;\n  }\n  .ot-btn[data-variant=\"space\"][data-size=\"l\"] { font-size: var(--ot-mp); letter-spacing: 0.06em; }\n  .ot-btn[data-variant=\"space\"] > .ot-btn-label { display: flex; align-items: baseline; flex: 1; min-inline-size: 0; }\n  .ot-btn-gap { flex: var(--gap) 0 auto; white-space: pre; transition: flex-grow var(--ot-andante) var(--ot-exhale); }\n  .ot-btn-lead {\n    position: relative; flex: var(--lead) 0 0; align-self: stretch; margin-inline-end: calc(var(--reach) * 0.8em);\n    opacity: var(--reach); scale: var(--dir) 1;\n    transition: flex-grow var(--ot-andante) var(--ot-exhale), margin var(--ot-andante) var(--ot-exhale), opacity var(--ot-moderato) var(--ot-breath);\n  }\n  .ot-btn-lead::before, .ot-btn-lead::after { content: \"\"; position: absolute; top: 52%; }\n  .ot-btn-lead::before { inset-inline: 0 0.05em; border-block-start: var(--ot-hairline) solid currentColor; }\n  .ot-btn-lead::after {\n    right: 0.03em; inline-size: 0.3em; block-size: 0.3em; margin-block-start: calc(var(--ot-hairline) / 2);\n    border: solid currentColor; border-width: var(--ot-hairline) var(--ot-hairline) 0 0;\n    translate: var(--nudge) -50%; rotate: 45deg; transition: translate var(--ot-allegro) var(--ot-spiccato);\n  }\n  .ot-btn[data-variant=\"space\"]:is(:hover, :focus-visible, [data-force~=\"hover\"]):not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"]) { --gap: 0; --lead: 1; --reach: 1; }\n  .ot-btn[data-variant=\"space\"]:active:not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"]) { --nudge: 0.18em; }\n\n  /* repeat: the word between repeat signs, a thick and a thin bar at each end. At rest only the closing sign has its dots, which in\n     a score already means \"go back to the beginning\"; pointing inks the bars and sets the opening dots, so the passage is framed to go round. */\n  .ot-btn[data-variant=\"repeat\"] { --line: var(--ot-pencil); --dots: var(--ot-graphite); --open: 0; padding-inline: 1.6em; }\n  .ot-btn[data-variant=\"repeat\"]::before, .ot-btn[data-variant=\"repeat\"]::after, .ot-btn[data-variant=\"repeat\"] > .ot-btn-label::before { content: \"\"; position: absolute; pointer-events: none; }\n  .ot-btn[data-variant=\"repeat\"]::before {\n    inset-block: calc(var(--py) - 0.12em); inset-inline: 0.2em; color: var(--line);\n    background:\n      linear-gradient(currentColor, currentColor) 0 0 / 0.15em 100% no-repeat,\n      linear-gradient(currentColor, currentColor) 0.32em 0 / var(--ot-hairline) 100% no-repeat,\n      linear-gradient(currentColor, currentColor) calc(100% - 0.32em) 0 / var(--ot-hairline) 100% no-repeat,\n      linear-gradient(currentColor, currentColor) 100% 0 / 0.15em 100% no-repeat;\n    transition: color var(--ot-allegro) var(--ot-breath);\n  }\n  .ot-btn[data-variant=\"repeat\"]::after, .ot-btn[data-variant=\"repeat\"] > .ot-btn-label::before {\n    top: 50%; inline-size: 0.17em; block-size: 0.6em; translate: 0 -50%; color: var(--dots);\n    background:\n      radial-gradient(circle closest-side, currentColor 88%, transparent) 50% 0 / 0.17em 0.17em no-repeat,\n      radial-gradient(circle closest-side, currentColor 88%, transparent) 50% 100% / 0.17em 0.17em no-repeat;\n    transition: color var(--ot-allegro) var(--ot-breath), scale var(--ot-moderato) var(--ot-spiccato);\n  }\n  .ot-btn[data-variant=\"repeat\"]::after { inset-inline-end: 0.82em; }\n  .ot-btn[data-variant=\"repeat\"] > .ot-btn-label::before { inset-inline-start: 0.82em; scale: var(--open); }\n  .ot-btn[data-variant=\"repeat\"]:is(:hover, :focus-visible, [data-force~=\"hover\"]):not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"]) { --line: var(--ot-ink); --dots: var(--ot-ink); --open: 1; }\n  .ot-btn[data-variant=\"repeat\"]:active:not(:disabled, [aria-disabled=\"true\"], [aria-busy=\"true\"]) { --open: 0.75; }\n  .ot-btn[data-variant=\"repeat\"][aria-busy=\"true\"]::after { scale: 0; } /* busy, the breathing periods stand where the repeat's dots were */\n  .ot-btn[data-variant=\"repeat\"]:is(:disabled, [aria-disabled=\"true\"]) { --line: var(--ot-rule-strong); --dots: var(--ot-rule-strong); }\n\n  /* On a phone the large heroes step down one size, so the widest still fits. */\n  @media (max-width: 40rem) {\n    .ot-btn:is([data-variant=\"crescendo\"], [data-variant=\"ink\"])[data-size=\"l\"] { font-size: var(--ot-mp); }\n    .ot-btn[data-variant=\"stave\"][data-size=\"l\"] { font-size: calc(var(--ot-mp) * var(--ot-expression-scale)); }\n    .ot-btn[data-variant=\"overture\"][data-size=\"l\"] { font-size: var(--ot-mf); }\n  }\n\n  @media (forced-colors: active) {\n    .ot-btn:is([data-variant=\"statement\"], [data-variant=\"ink\"]) { border: 1px solid ButtonText; }\n    .ot-btn[data-variant=\"ink\"]::before, .ot-btn[data-variant=\"overture\"]::after { forced-color-adjust: none; background: Highlight; }\n    .ot-btn[data-variant=\"crescendo\"]::before { forced-color-adjust: none; background: ButtonText; }\n    .ot-btn[data-variant=\"repeat\"]::before, .ot-btn[data-variant=\"repeat\"]::after, .ot-btn[data-variant=\"repeat\"] > .ot-btn-label::before { forced-color-adjust: none; color: ButtonText; }\n    .ot-btn[data-variant=\"ink\"]:is(:hover, :focus-visible, [data-force~=\"hover\"]) { color: HighlightText; }\n    .ot-btn[data-variant=\"quiet\"]::before, .ot-btn[data-variant=\"quiet\"]::after { forced-color-adjust: none; background: ButtonText; }\n  }\n}\n",
      "type": "registry:file",
      "target": "styles/0nlytype/button.css"
    }
  ],
  "css": {
    "@import \"../styles/0nlytype/button.css\"": {}
  },
  "meta": {
    "movement": "VI",
    "underneath": "native",
    "contract": "ot-btn"
  },
  "type": "registry:ui"
}
