{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "skeleton",
  "title": "Skeleton",
  "description": "Where the words will be: their baselines, the guides a letterer rules for them, or their rhythm. Still until something is loading, and still when motion is off.",
  "registryDependencies": [
    "https://thedirectors.agency/ui/r/0nlytype.json"
  ],
  "files": [
    {
      "path": "registry/0nlytype/ui/skeleton.tsx",
      "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype SkeletonProps = React.ComponentProps<\"div\"> & {\n  /**\n   * baseline: the line each row of words will stand on. metrics: the guides a letterer rules before any letter, the\n   * baseline and a dotted x-height, measured from the real face. words: each line broken at word spaces, as a layout\n   * dummy greeks its text, so the skeleton has the rhythm of a sentence.\n   */\n  variant?: \"baseline\" | \"metrics\" | \"words\"\n}\n\n/**\n * Where the words will be. Still by default; it moves only while an ancestor has aria-busy=\"true\"\n * (and motion is welcome).\n */\nfunction Skeleton({ variant = \"baseline\", className, ...props }: SkeletonProps) {\n  return <div data-slot=\"skeleton\" data-variant={variant} aria-hidden=\"true\" className={cn(\"ot-skeleton\", className)} {...props} />\n}\n\ntype SkeletonLineProps = React.ComponentProps<\"i\"> & {\n  /** Any CSS width; the default is the full line. */\n  width?: string\n  /** Any CSS height; the default is one line of text. */\n  height?: string\n  /** Position in the stack, so the stroke reads down the lines in turn. */\n  index?: number\n}\n\nfunction SkeletonLine({ width, height, index = 0, className, style, ...props }: SkeletonLineProps) {\n  return (\n    <i\n      data-slot=\"skeleton-line\"\n      className={className}\n      style={{ \"--w\": width, \"--lh\": height, \"--i\": index, ...style } as React.CSSProperties}\n      {...props}\n    />\n  )\n}\n\n/** Where an avatar or a mark will be. */\nfunction SkeletonRing({ size, className, style, ...props }: React.ComponentProps<\"span\"> & { size?: string }) {\n  return (\n    <span\n      data-slot=\"skeleton-ring\"\n      className={cn(\"ot-skeleton-ring\", className)}\n      style={{ \"--s\": size, ...style } as React.CSSProperties}\n      {...props}\n    />\n  )\n}\n\nexport { Skeleton, SkeletonLine, SkeletonRing, type SkeletonProps, type SkeletonLineProps }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/0nlytype/styles/skeleton.css",
      "content": "@layer components {\n  /* ── ot-skeleton ── where the words will be: their baselines, their guides, or their rhythm */\n  .ot-skeleton { display: grid; }\n  .ot-skeleton > i { position: relative; display: block; width: var(--w, 100%); height: var(--lh, 1.7em); overflow: clip; background: linear-gradient(var(--ot-rule-strong), var(--ot-rule-strong)) no-repeat 0 100% / 100% var(--ot-hairline); }\n  .ot-skeleton > i::after { content: \"\"; position: absolute; inset: auto 0 0; height: var(--ot-stroke); background: linear-gradient(90deg, transparent, var(--ot-pencil) 50%, transparent) no-repeat -60% 0 / 30% 100%; }\n  /* While it loads, a pencil stroke reads along each line in turn. */\n  [aria-busy=\"true\"] .ot-skeleton > i::after { animation: ot-read 1600ms var(--ot-breath) infinite; animation-delay: calc(var(--i, 0) * 140ms); }\n  @keyframes ot-read { from { background-position: -60% 0; } to { background-position: 160% 0; } }\n\n  /* Metrics and words size each line as the type it stands for: a line's height sets its font size (a body line is\n     1.7em of leading), so a headline's guides and word spaces are a headline's. */\n  .ot-skeleton:is([data-variant=\"metrics\"], [data-variant=\"words\"]) > i { font-size: calc(var(--lh, 1.7em) / 1.7); height: 1.7em; }\n\n  /* Metrics. From Paul Rand's construction grid: before a letter is drawn, the letterer rules its guides. The baseline in\n     a hairline, and above it, one real x-height of the voice (1ex, so it changes with the pair), a dotted line. While it\n     loads, the dotted line is ruled out from the start of each line in turn and passes off the far end, then ruled again. */\n  @property --ot-skeleton-s { syntax: \"<percentage>\"; inherits: false; initial-value: 100%; }\n  @property --ot-skeleton-x { syntax: \"<percentage>\"; inherits: false; initial-value: 0%; }\n  .ot-skeleton[data-variant=\"metrics\"] > i {\n    --ot-skeleton-s: 100%; --ot-skeleton-x: 0%; --ot-skeleton-foot: 0.5em; /* the baseline's height in the line: half the leading plus the descender */\n    background:\n      linear-gradient(var(--ot-rule-strong), var(--ot-rule-strong)) 0 calc(100% - var(--ot-skeleton-foot)) / 100% var(--ot-hairline) no-repeat,\n      repeating-linear-gradient(to right, var(--ot-rule-strong) 0 1px, transparent 1px 4px) var(--ot-skeleton-x) calc(100% - var(--ot-skeleton-foot) - 1ex) / var(--ot-skeleton-s) var(--ot-hairline) no-repeat;\n  }\n  .ot-skeleton[data-variant=\"metrics\"] > i::after { content: none; }\n  .ot-skeleton[data-variant=\"metrics\"] > i:is([dir=\"rtl\"], [dir=\"rtl\"] *) { --ot-skeleton-x: 100%; }\n  [aria-busy=\"true\"] .ot-skeleton[data-variant=\"metrics\"] > i { animation: ot-skeleton-rule 2400ms var(--ot-breath) infinite both; animation-delay: calc(var(--i, 0) * 140ms); }\n  [aria-busy=\"true\"] .ot-skeleton[data-variant=\"metrics\"] > i:is([dir=\"rtl\"], [dir=\"rtl\"] *) { animation-direction: reverse; }\n  @keyframes ot-skeleton-rule { 0% { --ot-skeleton-s: 0%; --ot-skeleton-x: 0%; } 45% { --ot-skeleton-s: 100%; --ot-skeleton-x: 0%; } 70% { --ot-skeleton-s: 100%; --ot-skeleton-x: 100%; } 100% { --ot-skeleton-s: 0%; --ot-skeleton-x: 100%; } }\n\n  /* Words. A layout dummy greeks its text as lines broken at word spaces. Two runs of gaps, every 5em and every 7em,\n     intersect into words of one to five ems that only repeat after 35, and each line starts the pattern at another place,\n     so no two lines share a rhythm. The pencil stroke that reads along the baseline now reads word by word. */\n  .ot-skeleton[data-variant=\"words\"] > i {\n    mask:\n      linear-gradient(to right, #000 4.62em, transparent 0) calc(var(--i, 0) * -3em) 0 / 5em 100% repeat-x,\n      linear-gradient(to right, #000 6.62em, transparent 0) calc(var(--i, 0) * -1em) 0 / 7em 100% repeat-x;\n    mask-composite: intersect;\n  }\n  .ot-skeleton-ring { display: block; width: var(--s, 2.75rem); height: var(--s, 2.75rem); border-radius: 50%; box-shadow: inset 0 0 0 var(--ot-hairline) var(--ot-rule-strong); }\n  /* Forced colours drop the drawn lines unless the skeleton keeps its own, in the system's grey. */\n  @media (forced-colors: active) { .ot-skeleton, .ot-skeleton-ring { forced-color-adjust: none; --ot-rule-strong: GrayText; --ot-pencil: CanvasText; } }\n  @media (prefers-reduced-motion: reduce) { [aria-busy=\"true\"] .ot-skeleton[data-variant] > i, [aria-busy=\"true\"] .ot-skeleton > i::after { animation: none; } }\n}\n",
      "type": "registry:file",
      "target": "styles/0nlytype/skeleton.css"
    }
  ],
  "css": {
    "@import \"../styles/0nlytype/skeleton.css\"": {}
  },
  "meta": {
    "movement": "VII",
    "underneath": "native",
    "contract": "ot-skeleton"
  },
  "type": "registry:ui"
}
