{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "css-tab-shape",
  "type": "registry:lib",
  "title": "CSS Tab Shape",
  "description": "Create tab shapes using CSS mask and clip-path: shape() techniques",
  "dependencies": [],
  "files": [
    {
      "path": "registry/css-tab-shape/css-tab-shape.ts",
      "type": "registry:file",
      "target": "lib/vanilla-motion/css-tab-shape/css-tab-shape.ts",
      "content": "import { defaults, type CssTabShapeParams, type Technique } from \"./params\";\nimport { STYLE, STYLE_ID } from \"./styles\";\n\nexport type { CssTabShapeParams, Technique } from \"./params\";\nexport { defaults } from \"./params\";\n\n/** One tab. `background` is any CSS background (a gradient works on folder and clip-path). */\nexport type TabSpec = { label: string; background?: string; color?: string };\n\nexport type CssTabShapeOptions = Partial<CssTabShapeParams> & {\n  /** Tabs to show. Without them: Tab 1, Tab 2, Tab 3. */\n  tabs?: TabSpec[];\n};\n\n/** The public surface wrappers use (Toolcraft canvas, embed). */\nexport interface CssTabShapeHandle {\n  setParams(next: Partial<CssTabShapeParams>): void;\n  dispose(): void;\n}\n\nconst DEFAULT_TABS: TabSpec[] = [{ label: \"Tab 1\" }, { label: \"Tab 2\" }, { label: \"Tab 3\" }];\n\n/**\n * Embed markup: child elements with [data-tab] become tabs (their text is the label,\n * data-background and data-color are optional). Without any, the default tabs show.\n */\nexport function optionsFromElement(el: HTMLElement): CssTabShapeOptions {\n  const tabs = [...el.querySelectorAll<HTMLElement>(\"[data-tab]\")].map((t) => ({\n    label: t.textContent?.trim() ?? \"\",\n    background: t.dataset.background,\n    color: t.dataset.color,\n  }));\n  return tabs.length ? { tabs } : {};\n}\n\n/** Fill an element with the tabs. Used by the Toolcraft app, the embed page and the embed script. */\nexport function mount(container: HTMLElement, options: CssTabShapeOptions = {}): CssTabShapeHandle {\n  const fx = new CssTabShape(container, options);\n  return { setParams: (next) => fx.setParams(next), dispose: () => fx.dispose() };\n}\n\n/**\n * Tab shapes cut with CSS only (clip-path shape() or mask). The class builds the markup inside the\n * container and writes the params as CSS custom properties; there is no script at runtime.\n */\nexport class CssTabShape implements CssTabShapeHandle {\n  /** Same as meta.json \"version\"; bump both when the class or params change. */\n  static readonly version = \"1.0.0\";\n  private P: CssTabShapeParams;\n  private root = document.createElement(\"div\");\n  private previous: Node[];\n\n  constructor(private container: HTMLElement, options: CssTabShapeOptions = {}) {\n    const { tabs = DEFAULT_TABS, ...params } = options;\n    this.P = { ...defaults, ...params };\n    if (!document.getElementById(STYLE_ID)) {\n      const style = document.createElement(\"style\");\n      style.id = STYLE_ID;\n      style.textContent = STYLE;\n      document.head.appendChild(style);\n    }\n    this.previous = [...container.childNodes];\n    this.root.className = \"vmts\";\n    const row = document.createElement(\"div\");\n    row.className = \"vmts-tabs\";\n    for (const t of tabs) {\n      const tab = document.createElement(\"div\");\n      tab.className = \"vmts-tab\";\n      if (t.background) tab.style.setProperty(\"--tab-bg\", t.background);\n      if (t.color) tab.style.setProperty(\"--tab-color\", t.color);\n      const span = document.createElement(\"span\");\n      span.textContent = t.label;\n      tab.appendChild(span);\n      row.appendChild(tab);\n    }\n    this.root.appendChild(row);\n    this.apply();\n    container.replaceChildren(this.root);\n  }\n\n  setParams(next: Partial<CssTabShapeParams>): void {\n    this.P = { ...this.P, ...next };\n    this.apply();\n  }\n\n  private apply(): void {\n    const P = this.P;\n    const s = this.root.style;\n    this.root.dataset.technique = P.technique satisfies Technique;\n    if (P.hoverHighlight) this.root.dataset.hover = \"\";\n    else delete this.root.dataset.hover;\n    s.setProperty(\"--vmts-r\", `${P.radius}px`);\n    s.setProperty(\"--vmts-px\", `${P.paddingX}px`);\n    s.setProperty(\"--vmts-py\", `${P.paddingY}px`);\n    s.setProperty(\"--vmts-gap\", `${P.gap}px`);\n    s.setProperty(\"--vmts-overlap\", `${P.overlap}px`);\n    s.setProperty(\"--vmts-tab\", P.tabColor);\n    s.setProperty(\"--vmts-active\", P.activeColor);\n    s.setProperty(\"--vmts-text\", P.textColor);\n    s.setProperty(\"--vmts-track\", P.trackColor);\n    s.setProperty(\"--vmts-bg\", P.bgColor);\n  }\n\n  dispose(): void {\n    this.root.remove();\n    this.container.replaceChildren(...this.previous);\n  }\n}\n"
    },
    {
      "path": "registry/css-tab-shape/params.ts",
      "type": "registry:file",
      "target": "lib/vanilla-motion/css-tab-shape/params.ts",
      "content": "// The one definition of every CSS Tab Shape value. The class merges options over `defaults`,\n// css-tab-shape-controls.ts builds the Toolcraft panel from the same keys, and the embed reads\n// the same keys as kebab-case data attributes (data-technique, data-tab-color…).\n// Defaults are the tutorial's folder-shape tabs; the other techniques' own values live in demos.\n\n/** How the tab outline is cut: clip-path shape() with a folder slant, shape() with flared feet, or CSS mask. */\nexport type Technique = \"folder\" | \"clip-path\" | \"mask\";\n\nexport type CssTabShapeParams = {\n  technique: Technique;\n  // shape (px)\n  /** Corner radius; the folder slant scales with it. */\n  radius: number;\n  paddingX: number;\n  paddingY: number;\n  gap: number;\n  /** Tabs pull this far under their left neighbour (folder stacks). */\n  overlap: number;\n  // colour (#RRGGBB)\n  tabColor: string;\n  /** Fill of the first tab, and of the hovered one, when hoverHighlight is on. */\n  activeColor: string;\n  textColor: string;\n  /** Strip behind the tabs. */\n  trackColor: string;\n  /** Page colour around the strip. */\n  bgColor: string;\n  /** Tabs are transparent except the first one and the hovered one, which fill with activeColor. */\n  hoverHighlight: boolean;\n};\n\nexport const defaults: CssTabShapeParams = {\n  technique: \"folder\",\n  radius: 8, paddingX: 24, paddingY: 8, gap: 0, overlap: 0,\n  tabColor: \"#60A5FA\", activeColor: \"#67E8F9\", textColor: \"#18181B\", trackColor: \"#FFFFFF\", bgColor: \"#FFFFFF\",\n  hoverHighlight: false,\n};\n"
    },
    {
      "path": "registry/css-tab-shape/styles.ts",
      "type": "registry:file",
      "target": "lib/vanilla-motion/css-tab-shape/styles.ts",
      "content": "// The tab CSS as a string, injected once by the class (so the embed stays one JS file).\n// The three techniques are the tutorial's tab-folder-shape.css, tab-clip-path.css and tab-mask.css\n// with Tailwind's @apply written out and the hard-coded numbers tied to --vmts-r.\n\nexport const STYLE_ID = \"vmts-style\";\n\nexport const STYLE = `\n.vmts{box-sizing:border-box;display:grid;place-items:center;width:100%;min-height:100%;padding:24px;background:var(--vmts-bg);font:16px/1.5 system-ui,sans-serif}\n.vmts *{box-sizing:border-box}\n.vmts-tabs{display:flex;width:100%;max-width:720px;gap:var(--vmts-gap);background:var(--vmts-track)}\n.vmts-tab{--r:var(--vmts-r);--bg:var(--vmts-tab);flex:none;min-width:0;padding:var(--vmts-py) var(--vmts-px);text-align:center;color:var(--vmts-text);background:var(--bg);transition:background-color .3s ease,border-color .3s ease}\n.vmts-tab[style*=\"--tab-bg\"]{--bg:var(--tab-bg)}\n.vmts-tab[style*=\"--tab-color\"]{color:var(--tab-color)}\n.vmts-tab+.vmts-tab{margin-left:calc(-1 * var(--vmts-overlap))}\n.vmts[data-hover] .vmts-tab{--bg:transparent}\n.vmts[data-hover] .vmts-tabs:not(:hover) .vmts-tab:first-child,\n.vmts[data-hover] .vmts-tabs:hover .vmts-tab:hover{--bg:var(--vmts-active)}\n\n/* folder: clip-path shape(), left rounded corner, slanted right edge */\n.vmts[data-technique=folder] .vmts-tab{\n  padding-right:calc(var(--vmts-px) + 2 * var(--r));\n  clip-path:shape(\n    from 0 100%,\n    vline to var(--r),\n    arc by var(--r) calc(-1 * var(--r)) of var(--r) cw,\n    hline to calc(100% - 4 * var(--r)),\n    curve to calc(100% - 2.75 * var(--r)) calc(0.625 * var(--r)) with calc(100% - 3 * var(--r)) 0px,\n    line to calc(100% - var(--r)) calc(100% - 0.5 * var(--r)),\n    curve to 100% 100% with calc(100% - 0.75 * var(--r)) 100%,\n    line to 100% 100%,\n    close\n  );\n}\n\n/* clip-path: shape() with an inward arc at both feet, so the box model stays untouched */\n.vmts[data-technique=clip-path] .vmts-tab{\n  clip-path:shape(\n    from 0 100%,\n    arc by var(--r) calc(-1 * var(--r)) of var(--r),\n    vline to var(--r),\n    arc by var(--r) calc(-1 * var(--r)) of var(--r) cw,\n    hline to calc(100% - 2 * var(--r)),\n    arc by var(--r) var(--r) of var(--r) cw,\n    vline to calc(100% - var(--r)),\n    arc by var(--r) var(--r) of var(--r),\n    close\n  );\n}\n\n/* mask: a border the size of the radius, cut away with three mask layers */\n.vmts[data-technique=mask] .vmts-tab{\n  border:var(--r) solid var(--bg);\n  border-bottom:0;\n  border-radius:calc(var(--r) * 2) calc(var(--r) * 2) 0 0;\n  mask:linear-gradient(#000) padding-box,\n    radial-gradient(100% 100% at 0 0,#0000 98%,#000) 0 100% / var(--r) var(--r),\n    radial-gradient(100% 100% at 100% 0,#0000 98%,#000) 100% 100% / var(--r) var(--r);\n  mask-repeat:no-repeat;\n}\n`;\n"
    }
  ],
  "meta": {
    "version": "1.0.0",
    "entry": "css-tab-shape.ts",
    "docs": "https://vanilla-motion.com/css-tab-shape/skill.md"
  }
}
