{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "switch",
  "title": "Switch",
  "description": "A sentence whose last word you can change. The state rolls between on and off in italic, and the full stop answers: a dot when on, a ring when off.",
  "registryDependencies": [
    "https://thedirectors.agency/ui/r/0nlytype.json"
  ],
  "files": [
    {
      "path": "registry/0nlytype/ui/switch.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype SwitchProps = Omit<React.ComponentProps<\"input\">, \"type\" | \"role\" | \"children\" | \"onChange\"> & {\n  /** The sentence, up to its last word: \"Email me when someone replies:\". */\n  children: React.ReactNode\n  onCheckedChange?: (checked: boolean) => void\n  /**\n   * sentence: the last word rolls between on and off, and the full stop is a dot or a ring.\n   * either: both words stand, \"on / off\", and the one that doesn't apply is struck out, as a printed form asks.\n   * question: off, the sentence asks, ending in a question mark; on, the hook lifts away and leaves its dot, a full stop.\n   * For question, pass the sentence without its last mark.\n   */\n  variant?: \"sentence\" | \"either\" | \"question\"\n  /** The word for on, in italic. */\n  on?: string\n  /** The word for off. */\n  off?: string\n  /** Classes for the label, which is the root. className goes to the input. */\n  labelClassName?: string\n  /** Pins a state for documentation (\"hover\", \"focus\"); set on the root. */\n  \"data-force\"?: string\n}\n\n/**\n * A sentence whose last word you can change. The state word rolls between on and\n * off in italic, and the full stop answers: a dot when on, a ring when off.\n * A native checkbox with the switch role, so Space toggles it.\n */\nfunction Switch({ children, variant = \"sentence\", onCheckedChange, on = \"on\", off = \"off\", className, labelClassName, \"data-force\": force, ...props }: SwitchProps) {\n  return (\n    <label data-slot=\"switch\" data-variant={variant} data-force={force} className={cn(\"ot-switch\", labelClassName)}>\n      <input type=\"checkbox\" role=\"switch\" className={className} onChange={(e) => onCheckedChange?.(e.target.checked)} {...props} />\n      {variant === \"question\" ? (\n        <>\n          {children}\n          <span className=\"ot-switch-ask\" aria-hidden=\"true\">\n            <span>.</span>\n            <span>?</span>\n          </span>\n        </>\n      ) : variant === \"either\" ? (\n        <>\n          {children}{\" \"}\n          <span className=\"ot-switch-either\" aria-hidden=\"true\">\n            <span>{on}</span>\n            <span className=\"ot-switch-or\">/</span>\n            <span>{off}</span>\n          </span>\n        </>\n      ) : (\n        <>\n          {children}{\" \"}\n          <span className=\"ot-switch-state\" aria-hidden=\"true\">\n            <span>{on}</span>\n            <span>{off}</span>\n          </span>\n          <span className=\"ot-switch-stop\" aria-hidden=\"true\" />\n        </>\n      )}\n    </label>\n  )\n}\n\nexport { Switch, type SwitchProps }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/0nlytype/styles/switch.css",
      "content": "@layer components {\n  /* ── ot-switch ── a sentence whose last word you can change */\n  .ot-switch { position: relative; display: inline-block; cursor: pointer; color: var(--ot-graphite); -webkit-tap-highlight-color: transparent; }\n  .ot-switch > input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; pointer-events: none; }\n  .ot-switch-state {\n    display: inline-grid; vertical-align: baseline; overflow: clip;\n    padding: 0.12em 0.14em 0.24em; margin-block: -0.12em -0.24em; margin-inline: -0.08em -0.14em;\n    font-family: var(--ot-expression); font-style: italic; font-size: calc(var(--ot-expression-scale) * 1em); line-height: 1;\n    color: var(--ot-ink);\n    background: linear-gradient(var(--ot-rule-strong), var(--ot-rule-strong)) no-repeat 0.14em calc(100% - 0.1em) / calc(100% - 0.28em) var(--ot-hairline);\n    transition: color var(--ot-moderato) var(--ot-breath);\n  }\n  .ot-switch-state > span { grid-area: 1 / 1; transition: translate var(--ot-moderato) var(--ot-breath), opacity var(--ot-moderato) var(--ot-breath); }\n  .ot-switch > input:not(:checked) ~ .ot-switch-state { color: var(--ot-pencil); }\n  .ot-switch > input:not(:checked) ~ .ot-switch-state > :first-child { translate: 0 -110%; opacity: 0; }\n  .ot-switch > input:checked ~ .ot-switch-state > :last-child { translate: 0 110%; opacity: 0; }\n  .ot-switch:is(:hover, [data-force~=\"hover\"]) > .ot-switch-state { background-image: linear-gradient(var(--ot-ink), var(--ot-ink)); }\n  .ot-switch > input:focus-visible ~ :is(.ot-switch-state, .ot-switch-either),\n  .ot-switch[data-force~=\"focus\"] > :is(.ot-switch-state, .ot-switch-either) { outline: var(--ot-stroke) solid var(--ot-accent); outline-offset: 0; }\n  .ot-switch[data-force~=\"focus\"] { outline: 0; }\n  .ot-switch:has(input:disabled) { opacity: 0.55; cursor: not-allowed; }\n  /* The sentence's full stop: a dot when on, a ring when off. */\n  .ot-switch-stop {\n    display: inline-block; width: max(0.22em, 5px); height: max(0.22em, 5px); margin-inline-start: 0.24em; border-radius: 50%;\n    box-shadow: inset 0 0 0 0.5em currentColor;\n    transition: box-shadow var(--ot-andante) var(--ot-exhale);\n  }\n  .ot-switch > input:not(:checked) ~ .ot-switch-stop { box-shadow: inset 0 0 0 var(--ot-hairline) currentColor; }\n\n  /* ── either ── \"delete as appropriate\": a printed form offers both words and you strike out the one that doesn't\n     apply. Both stand in the italic; the one that holds is in ink, the other pencil, struck. Pointing pencils a hairline\n     through the one that holds, since that is the one you'd strike; switching lifts the old strike off to the right\n     and draws the new one from the left, as the checkbox's pen does. */\n  .ot-switch-either { font-family: var(--ot-expression); font-style: italic; font-size: calc(var(--ot-expression-scale) * 1em); line-height: 1; white-space: nowrap; }\n  .ot-switch-either > :not(.ot-switch-or) {\n    --y: 58%;\n    padding-inline: 0.06em; margin-inline: -0.06em; color: var(--ot-ink);\n    background:\n      linear-gradient(var(--ot-graphite), var(--ot-graphite)) 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    transition: background-size var(--ot-moderato) var(--ot-breath), background-position 0s, color var(--ot-moderato) var(--ot-breath);\n  }\n  .ot-switch-or { margin-inline: 0.22em; font-family: var(--ot-voice); font-style: normal; font-size: calc(1em / var(--ot-expression-scale)); color: var(--ot-pencil); }\n  .ot-switch > input:checked ~ .ot-switch-either > :last-child,\n  .ot-switch > input:not(:checked) ~ .ot-switch-either > :first-child { color: var(--ot-pencil); background-position: 0 var(--y), 100% var(--y); background-size: 100% var(--ot-stroke), 0% var(--ot-hairline); }\n  .ot-switch:is(:hover, [data-force~=\"hover\"]):not(:has(input:disabled)) > input:checked ~ .ot-switch-either > :first-child,\n  .ot-switch:is(:hover, [data-force~=\"hover\"]):not(:has(input:disabled)) > input:not(:checked) ~ .ot-switch-either > :last-child { background-position: 100% var(--y), 0 var(--y); background-size: 0% var(--ot-stroke), 100% var(--ot-hairline); }\n  .ot-switch:is([dir=\"rtl\"], [dir=\"rtl\"] *) .ot-switch-either > :not(.ot-switch-or) { background-position: 0 var(--y), 0 var(--y); }\n  .ot-switch:is([dir=\"rtl\"], [dir=\"rtl\"] *) > input:checked ~ .ot-switch-either > :last-child,\n  .ot-switch:is([dir=\"rtl\"], [dir=\"rtl\"] *) > input:not(:checked) ~ .ot-switch-either > :first-child { background-position: 100% var(--y), 0 var(--y); }\n  .ot-switch:is([dir=\"rtl\"], [dir=\"rtl\"] *):is(:hover, [data-force~=\"hover\"]):not(:has(input:disabled)) > input:checked ~ .ot-switch-either > :first-child,\n  .ot-switch:is([dir=\"rtl\"], [dir=\"rtl\"] *):is(:hover, [data-force~=\"hover\"]):not(:has(input:disabled)) > input:not(:checked) ~ .ot-switch-either > :last-child { background-position: 0 var(--y), 100% var(--y); }\n\n  /* ── question ── after \"It has to be design.\": \"the concept of passion?\" is answered by \"the voice of reason.\"\n     Off, the sentence asks, in graphite, ending on an italic question mark in pencil. On, the hook is wiped away from\n     the top down to its dot, which stays as the full stop, and the sentence inks: it has been answered. Switching off,\n     the hook grows back up out of the dot. The full stop sits at the start of the question mark's cell, so the line never moves. */\n  .ot-switch[data-variant=\"question\"] { transition: color var(--ot-moderato) var(--ot-breath); }\n  .ot-switch[data-variant=\"question\"]:has(input:checked) { color: var(--ot-ink); }\n  /* An inline span, not a box, so the mark can never wrap away from the last word; the full stop is laid over its start. */\n  .ot-switch-ask { position: relative; margin-inline-start: 0.02em; font-family: var(--ot-expression); font-style: italic; font-size: calc(var(--ot-expression-scale) * 1em); line-height: 0; color: var(--ot-pencil); } /* 0: the larger italic never opens the line */\n  .ot-switch-ask > :first-child { position: absolute; inset-inline-start: 0; top: 0; line-height: normal; opacity: 0; color: var(--ot-ink); transition: opacity var(--ot-allegro); }\n  .ot-switch-ask > :last-child { clip-path: inset(-0.2em -0.3em); transition: clip-path var(--ot-moderato) var(--ot-breath), color var(--ot-allegro); }\n  .ot-switch:is(:hover, [data-force~=\"hover\"]):not(:has(input:disabled)) .ot-switch-ask > :last-child { color: var(--ot-ink); }\n  .ot-switch > input:checked ~ .ot-switch-ask > :first-child { opacity: 1; transition-delay: calc(var(--ot-moderato) * 0.55); }\n  .ot-switch > input:checked ~ .ot-switch-ask > :last-child { clip-path: inset(calc(100% + 0.2em) -0.3em -0.2em); }\n  .ot-switch[data-variant=\"question\"]:has(input:focus-visible),\n  .ot-switch[data-variant=\"question\"][data-force~=\"focus\"] { outline: var(--ot-stroke) solid var(--ot-accent); outline-offset: 4px; }\n\n  /* Forced colours drop box-shadow, which draws the full stop (keep it, in the forced text colour), and background\n     images, which draw either's strike (restate it as a line-through). */\n  @media (forced-colors: active) {\n    .ot-switch-stop { forced-color-adjust: none; }\n    .ot-switch > input:checked ~ .ot-switch-either > :last-child,\n    .ot-switch > input:not(:checked) ~ .ot-switch-either > :first-child { text-decoration: line-through; }\n  }\n}\n",
      "type": "registry:file",
      "target": "styles/0nlytype/switch.css"
    }
  ],
  "css": {
    "@import \"../styles/0nlytype/switch.css\"": {}
  },
  "meta": {
    "movement": "VI",
    "underneath": "native",
    "contract": "ot-switch"
  },
  "type": "registry:ui"
}
