{
  "schemaVersion": 1,
  "packageName": "ren10",
  "generator": "scripts/build-knowledge-graph.mjs",
  "outputs": {
    "json": "knowledge/ren10-graph.json",
    "sqlite": "knowledge/ren10-graph.sqlite"
  },
  "nodes": [
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-accordion",
          ".ren-accordion-bordered",
          ".ren-accordion-content",
          ".ren-accordion-flush",
          ".ren-accordion-item",
          ".ren-accordion-trigger"
        ],
        "tokens": [
          "--color-accent",
          "--color-border",
          "--color-fill",
          "--color-fill-active",
          "--color-text",
          "--color-text-muted",
          "--duration-enter",
          "--duration-exit",
          "--ease-enter",
          "--ease-exit",
          "--label-lg-size",
          "--label-lg-weight",
          "--radius-md",
          "--size-lg",
          "--space-2",
          "--space-3",
          "--space-4",
          "--transition-tactile"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-accordion Component Contract\n\nDisclosure group for vertically stacked expandable sections.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-accordion` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-accordion` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Accordion composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a vertically stacked group of disclosure sections sharing chrome.\"\n    - \"Exclusive (single open) behavior is required, optionally with collapsible=all-closed.\"\n    - \"Multiple sections may be open simultaneously (type=\\\"multiple\\\").\"\n    - \"You want native <details>/<summary> semantics with smooth height animation.\"\n    - \"Initial open items must be declarable via default-value=\\\"0,2\\\".\"\n  avoidWhen:\n    - \"Only one disclosure is needed — use ren-collapsible instead.\"\n    - \"Sections should not share chrome / divider lines — use isolated ren-collapsible items.\"\n    - \"The disclosure is a navigation menu — use ren-menu or ren-sidebar.\"\n    - \"Content swaps in place without expanding — use ren-tabs.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-accordion/ren-accordion.css\"\n  js:\n    - \"rends/components/composites/ren-accordion/ren-accordion.js\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"JS adds exclusive mode, default-value parsing, and the ren-accordion-change event; native <details> handles open/close without JS.\"\n\nrequiredMarkup:\n  - \"<ren-accordion> wraps real <details> elements; each <details> holds a <summary class=\\\"ren-accordion-trigger\\\"> and a <div class=\\\"ren-accordion-content\\\">.\"\n  - \"Do not replace <details>/<summary> with <button>+<div> — exclusive mode relies on native [name] grouping.\"\n  - \"Use type=\\\"single\\\" (default) or type=\\\"multiple\\\"; add collapsible only when single mode must allow all closed.\"\n  - \"Set default-value=\\\"0,1\\\" (comma-separated indices) to declare initial open items.\"\n  - \"Use .ren-accordion-bordered or .ren-accordion-flush on the host for variants; do not invent new variant classes.\"\n\nforbiddenPatterns:\n  - \"<div role=\\\"button\\\"> styled as a summary instead of a real <summary> / <button>.\"\n  - \"Manual height animation with hardcoded max-height: 500px in inline styles — rely on the component's ::details-content transition.\"\n  - \"Hardcoded chevron icons inside the summary; the ::after pseudo-element already renders one.\"\n  - \"Toggling open state via display: none on .ren-accordion-content instead of the <details>[open] attribute.\"\n  - \"Overriding focus ring with outline: none on .ren-accordion-trigger without restoring :focus-visible.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-collapse-bg, --ren-collapse-border, --ren-collapse-duration, --ren-collapse-easing, --ren-collapse-padding, --ren-collapse-radius, --ren-collapse-trigger-font, --ren-collapse-trigger-weight.\"\n    - \"Semantic tokens: --color-border, --color-text, --color-text-muted, --color-fill, --color-fill-active, --color-accent.\"\n    - \"Layout / motion tokens: --space-*, --size-*, --radius-*, --duration-enter, --ease-enter, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Raw transition durations; use --duration-enter / --ease-enter or --ren-collapse-duration / --ren-collapse-easing.\"\n\naccessibility:\n  required:\n    - \"Use real <details>/<summary> so the browser exposes the native disclosure pattern to AT.\"\n    - \"Keep the chevron decorative (::after pseudo); never put text inside it that an AT must announce.\"\n    - \"Touch target on .ren-accordion-trigger is min-height: var(--size-lg); do not shrink below 44px on touch surfaces.\"\n    - \"Visible :focus-visible outline (2px solid --color-accent) must be preserved on the summary.\"\n    - \"Disabled triggers must set aria-disabled=\\\"true\\\" and not toggle on activation.\"\n    - \"Animations respect prefers-reduced-motion (transitions are removed under reduce).\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-accordion/ren-accordion.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-accordion/ren-accordion.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-accordion type=\"single\"><details><summary class=\"ren-accordion-trigger\">Billing</summary><div class=\"ren-accordion-content\">Billing details</div></details></ren-accordion>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-accordion`\n- `.ren-accordion-bordered`\n- `.ren-accordion-content`\n- `.ren-accordion-flush`\n- `.ren-accordion-item`\n- `.ren-accordion-trigger`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[aria-expanded]`\n- `:active`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-collapse-bg`\n- `--ren-collapse-border`\n- `--ren-collapse-duration`\n- `--ren-collapse-easing`\n- `--ren-collapse-padding`\n- `--ren-collapse-radius`\n- `--ren-collapse-trigger-font`\n- `--ren-collapse-trigger-weight`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-accordion/ren-accordion.css`\n- `components/composites/ren-accordion/ren-accordion.js`\n- `docs/components/ren-accordion.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/**\n * RenDS — <ren-accordion> Component\n * ═══════════════════════════════════\n * CSS-first accordion using native <details><summary>\n *\n * Features:\n *   - Smooth height animation using ::details-content\n *   - Chevron rotation on open state\n *   - Multiple variants: default, bordered, flush\n *   - Reduced motion support\n *   - Dark mode support (via semantic tokens)\n *   - Fallback for browsers without ::details-content support\n */\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* ACCORDION CONTAINER */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion {\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  border-top: 1px solid var(--color-border);\n\n  /* Default variant: divider lines between items */\n  & > details {\n    border-bottom: 1px solid var(--color-border);\n\n    &:last-child {\n      border-bottom: none;\n    }\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* TRIGGER / SUMMARY ELEMENT */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion-trigger,\n.ren-accordion > details > summary {\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  justify-content: space-between;\n  padding: var(--space-3) var(--space-4);\n  cursor: pointer;\n  font-weight: var(--label-lg-weight);\n  font-size: var(--label-lg-size);\n  line-height: 1.4;\n  color: var(--color-text);\n  background-color: transparent;\n  border: none;\n  min-height: var(--size-lg);\n  width: 100%;\n  text-align: start;\n  user-select: none;\n  list-style: none;\n  transition: var(--transition-tactile);\n\n  /* Remove default browser marker */\n  &::marker,\n  &::-webkit-details-marker {\n    display: none;\n    content: '';\n  }\n\n  /* Chevron icon via ::after pseudo-element */\n  &::after {\n    content: '';\n    display: inline-block;\n    width: 20px;\n    height: 20px;\n    margin-inline-start: var(--space-3);\n    flex-shrink: 0;\n    background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"></polyline></svg>');\n    background-repeat: no-repeat;\n    background-position: center;\n    background-size: contain;\n    transition: transform var(--duration-enter) var(--ease-enter);\n      border: none;\n    transform: rotate(0deg);\n  }\n\n  /* Hover state */\n  &:hover {\n    background-color: var(--color-fill);\n  }\n\n  /* Active state */\n  &:active {\n    background-color: var(--color-fill-active);\n  }\n\n  /* Focus ring */\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: -2px;\n  }\n\n  /* Disabled state */\n  &:disabled,\n  &[aria-disabled=\"true\"] {\n    opacity: 0.5;\n    cursor: not-allowed;\n\n    &:hover {\n      background-color: transparent;\n    }\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* REMOVE NATIVE DISCLOSURE TRIANGLE (WebKit fallback) */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion > details > summary::-webkit-details-marker {\n  display: none;\n}\n\n.ren-accordion-trigger::-webkit-details-marker {\n  display: none;\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* OPEN STATE — ROTATE CHEVRON */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion > details[open] > summary::after {\n  transform: rotate(180deg);\n}\n\n/* Fallback for custom markup */\n.ren-accordion-trigger[aria-expanded=\"true\"]::after {\n  transform: rotate(180deg);\n}\n\n/* Animate the native details content box. The global reset enables\n   keyword-size interpolation, so block-size can transition to and from auto. */\n@supports selector(details::details-content) {\n  .ren-accordion > details::details-content {\n    block-size: 0;\n    overflow: clip;\n    opacity: 0;\n    transition:\n      block-size var(--duration-exit) var(--ease-exit),\n      opacity var(--duration-exit) var(--ease-exit),\n      content-visibility var(--duration-exit) var(--ease-exit);\n    transition-behavior: allow-discrete;\n  }\n\n  .ren-accordion > details[open]::details-content {\n    block-size: auto;\n    opacity: 1;\n    transition-duration: var(--duration-enter);\n    transition-timing-function: var(--ease-enter);\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* CONTENT ANIMATION — MODERN APPROACH */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion > details {\n  /* Modern: use ::details-content for height animation */\n  & > .ren-accordion-content {\n    padding: 0 var(--space-4) var(--space-4);\n    color: var(--color-text-muted);\n    line-height: 1.6;\n    overflow: hidden;\n\n    /* Browsers with ::details-content support */\n    @supports selector(::-webkit-details-content) {\n      height: 0;\n      overflow: hidden;\n      transition:\n        height var(--duration-enter) var(--ease-enter),\n        opacity var(--duration-enter) var(--ease-enter);\n    }\n\n    /* Also try Chrome/V8 syntax (may vary by engine) */\n    @supports selector(::details-content) {\n      height: 0;\n      overflow: hidden;\n      transition:\n        height var(--duration-enter) var(--ease-enter),\n        opacity var(--duration-enter) var(--ease-enter);\n    }\n  }\n\n  /* Fallback for browsers without ::details-content: use opacity + max-height */\n  & > .ren-accordion-content {\n    opacity: 0;\n    max-height: 0;\n    transition:\n      opacity var(--duration-enter) var(--ease-enter),\n      max-height var(--duration-enter) var(--ease-enter);\n  }\n\n  /* When details is open */\n  &[open] > .ren-accordion-content {\n    opacity: 1;\n    max-height: 1000px; /* Reasonable fallback limit */\n\n    /* Modern: animate to auto height with ::details-content */\n    @supports selector(::details-content) {\n      height: auto;\n    }\n  }\n\n  /* Entry animation starting point (for ::details-content) */\n  @starting-style {\n    &[open] > .ren-accordion-content {\n      opacity: 0;\n      max-height: 0;\n    }\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* CONTENT ELEMENT */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion-content {\n  padding: 0 var(--space-4) var(--space-4);\n  color: var(--color-text-muted);\n  line-height: 1.6;\n}\n\n/* Hide custom content via hidden attribute (for non-details markup) */\n.ren-accordion-content[hidden] {\n  display: none;\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* VARIANT: BORDERED */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion-bordered {\n  border-top: none;\n\n  & > details,\n  & .ren-accordion-item {\n    border: 1px solid var(--color-border);\n    border-radius: var(--radius-md);\n    margin-bottom: var(--space-2);\n    overflow: hidden;\n\n    &:last-child {\n      margin-bottom: 0;\n    }\n  }\n\n  & > details > summary,\n  & .ren-accordion-trigger {\n    padding: var(--space-4) var(--space-4);\n  }\n\n  & > details > .ren-accordion-content,\n  & .ren-accordion-content {\n    padding: 0 var(--space-4) var(--space-4);\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* VARIANT: FLUSH (no borders, minimal spacing) */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion-flush {\n  border-top: none;\n\n  & > details,\n  & .ren-accordion-item {\n    border-bottom: none;\n    padding: var(--space-2) 0;\n\n    &:last-child {\n      border-bottom: none;\n    }\n  }\n\n  & > details > summary,\n  & .ren-accordion-trigger {\n    padding: var(--space-3) 0;\n  }\n\n  & > details > .ren-accordion-content,\n  & .ren-accordion-content {\n    padding: 0 0 var(--space-3);\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* REDUCED MOTION */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-accordion-trigger::after,\n  .ren-accordion > details > summary::after {\n    transition: none;\n  }\n\n  .ren-accordion > details > .ren-accordion-content {\n    transition: none;\n  }\n\n  .ren-accordion > details::details-content {\n    transition: none;\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* ACCESSIBILITY SUPPORT */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n/* Ensure proper contrast for focus states */\n.ren-accordion > details > summary:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: -2px;\n}\n\n/* Dark mode: tokens will auto-adjust via CSS variables */\n@media (prefers-color-scheme: dark) {\n  /* Tokens like --color-text, --color-border, etc. should already be defined\n     in the design system's color token reset. No additional changes needed. */\n}\n\n\n/**\n * RenDS — <ren-accordion> (Minimal Enhancement)\n * ════════════════════════════════════════════════\n *\n * Native <details><summary> handles 90% of the work.\n * This JS only adds:\n *   - Exclusive mode (single): close others when one opens\n *     → Uses native <details name=\"...\"> when supported,\n *       with JS fallback for older browsers\n *   - default-value attribute: open items on init\n *   - ren-accordion-change event: custom event dispatch\n *\n * Attributes:\n *   type:          'single' (default) | 'multiple'\n *   collapsible:   boolean (single mode only) — allow all closed\n *   default-value: comma-separated indices to open on init\n *\n * Markup:\n *   <ren-accordion type=\"single\" default-value=\"0\">\n *     <details>\n *       <summary class=\"ren-accordion-trigger\">Item 1</summary>\n *       <div class=\"ren-accordion-content\">Content...</div>\n *     </details>\n *     <details>\n *       <summary class=\"ren-accordion-trigger\">Item 2</summary>\n *       <div class=\"ren-accordion-content\">Content...</div>\n *     </details>\n *   </ren-accordion>\n *\n * Events:\n *   ren-accordion-change: Fired when item open state changes\n *   - detail: { item, isOpen, index }\n */\n\n/**\n * Feature detect: does <details name=\"...\"> support exclusive mode natively?\n * When supported, setting the same `name` on multiple <details> makes them\n * behave like radio buttons — opening one closes the others automatically.\n */\nconst supportsDetailsName = (() => {\n  try {\n    const d1 = document.createElement('details');\n    const d2 = document.createElement('details');\n    d1.name = 'test';\n    d2.name = 'test';\n    d1.open = true;\n    d2.open = true;\n    const container = document.createElement('div');\n    container.append(d1, d2);\n    document.body.append(container);\n    // If native exclusive works, opening d2 should close d1\n    const works = !d1.open;\n    container.remove();\n    return works;\n  } catch {\n    return false;\n  }\n})();\n\nexport class RenAccordion extends HTMLElement {\n  static get observedAttributes() {\n    return ['type', 'collapsible', 'default-value'];\n  }\n\n  constructor() {\n    super();\n    /** @private */ this._useNativeExclusive = false;\n    /** @private */ this._groupName = '';\n    this._listenerController = null;\n    this._handleToggleBound = (event) => this._handleToggle(event);\n  }\n\n  connectedCallback() {\n    this._listenerController?.abort();\n    this._listenerController = new AbortController();\n    this.classList.add('ren-accordion');\n\n    // Determine mode (exclusive or multiple)\n    this._exclusive = this.getAttribute('type') !== 'multiple';\n    this._collapsible = this.hasAttribute('collapsible');\n\n    // Generate a unique group name for native <details name>\n    this._groupName = `ren-accordion-${this.id || Math.random().toString(36).slice(2, 8)}`;\n\n    // Use native exclusive mode when browser supports it\n    this._useNativeExclusive = supportsDetailsName && this._exclusive;\n    this._applyNativeExclusive();\n\n    // Listen for toggle events on all child details (event delegation)\n    this.addEventListener('toggle', this._handleToggleBound, {\n      capture: true,\n      signal: this._listenerController.signal,\n    });\n\n    // Apply default open items\n    const defaultValue = this.getAttribute('default-value');\n    if (defaultValue) {\n      this._setDefaultOpen(defaultValue);\n    }\n  }\n\n  disconnectedCallback() {\n    this._listenerController?.abort();\n    this._listenerController = null;\n  }\n\n  /**\n   * Apply or remove the native `name` attribute on child <details>.\n   * When supported, this lets the browser handle exclusive mode natively.\n   * @private\n   */\n  _applyNativeExclusive() {\n    this.items.forEach((detail) => {\n      // Closed accordion items must keep their summary visible. The global\n      // data-state helper hides closed surfaces unless they explicitly opt out.\n      detail.setAttribute('data-keep', '');\n\n      if (this._useNativeExclusive) {\n        detail.setAttribute('name', this._groupName);\n      } else {\n        // Remove name if switching to multiple mode or no native support\n        if (detail.getAttribute('name') === this._groupName) {\n          detail.removeAttribute('name');\n        }\n      }\n    });\n  }\n\n  /**\n   * Handle toggle event on child <details> elements\n   * @private\n   */\n  _handleToggle(event) {\n    const details = event.target;\n\n    // Only handle <details> that are direct children or nested in .ren-accordion-item\n    if (!this._isValidDetail(details)) {\n      return;\n    }\n\n    // Set data-state for CSS styling\n    details.setAttribute('data-state', details.open ? 'open' : 'closed');\n\n    // JS fallback for exclusive mode (only when native isn't handling it)\n    if (this._exclusive && !this._useNativeExclusive && details.open) {\n      this._closeOtherDetails(details);\n    }\n\n    // Sync data-state on siblings closed by native <details name>\n    if (this._useNativeExclusive && details.open) {\n      this.items.forEach((d) => {\n        if (d !== details) {\n          d.setAttribute('data-state', d.open ? 'open' : 'closed');\n        }\n      });\n    }\n\n    // Dispatch custom event\n    const index = this.items.indexOf(details);\n    this.dispatchEvent(\n      new CustomEvent('ren-accordion-change', {\n        detail: {\n          item: details,\n          isOpen: details.open,\n          index,\n        },\n        bubbles: true,\n      })\n    );\n  }\n\n  /**\n   * Check if a details element is a valid child\n   * @private\n   */\n  _isValidDetail(details) {\n    if (details.tagName !== 'DETAILS') {\n      return false;\n    }\n\n    // Direct child of accordion\n    if (details.parentElement === this) {\n      return true;\n    }\n\n    // Child of .ren-accordion-item within accordion\n    const parent = details.parentElement;\n    if (\n      parent &&\n      parent.classList &&\n      parent.classList.contains('ren-accordion-item') &&\n      parent.parentElement === this\n    ) {\n      return true;\n    }\n\n    return false;\n  }\n\n  /**\n   * Close all other details except the specified one\n   * @private\n   */\n  _closeOtherDetails(openDetails) {\n    this.items.forEach((detail) => {\n      if (detail !== openDetails && detail.open) {\n        detail.open = false;\n        detail.setAttribute('data-state', 'closed');\n      }\n    });\n  }\n\n  /**\n   * Set which items should be open by default\n   * @private\n   */\n  _setDefaultOpen(value) {\n    const indices = value\n      .split(',')\n      .map((s) => parseInt(s.trim(), 10))\n      .filter((i) => !isNaN(i));\n\n    this.items.forEach((detail, index) => {\n      detail.open = indices.includes(index);\n      detail.setAttribute('data-state', detail.open ? 'open' : 'closed');\n    });\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (!this.isConnected) return;\n\n    if (name === 'type') {\n      // Update exclusive mode\n      this._exclusive = newValue !== 'multiple';\n      this._useNativeExclusive = supportsDetailsName && this._exclusive;\n      this._applyNativeExclusive();\n    } else if (name === 'collapsible') {\n      // Collapsible flag updated\n      this._collapsible = this.hasAttribute('collapsible');\n    } else if (name === 'default-value') {\n      // Re-apply defaults\n      if (newValue) {\n        this._setDefaultOpen(newValue);\n      }\n    }\n  }\n\n  /* ═══════════════════════════════════════════ */\n  /* PUBLIC API */\n  /* ═══════════════════════════════════════════ */\n\n  /**\n   * Get all details elements\n   */\n  get items() {\n    const items = [];\n\n    // Direct <details> children\n    this.querySelectorAll(':scope > details').forEach((d) => items.push(d));\n\n    // <details> nested in .ren-accordion-item children\n    this.querySelectorAll(':scope > .ren-accordion-item > details').forEach(\n      (d) => items.push(d)\n    );\n\n    return items;\n  }\n\n  /**\n   * Open a specific item by index\n   */\n  openItem(index) {\n    const item = this.items[index];\n    if (!item) return;\n\n    // Exclusive mode: close others\n    if (this._exclusive) {\n      this._closeOtherDetails(item);\n    }\n\n    item.open = true;\n  }\n\n  /**\n   * Close a specific item by index\n   */\n  closeItem(index) {\n    const item = this.items[index];\n    if (!item) return;\n\n    // In exclusive mode without collapsible: prevent closing if only open item\n    if (\n      this._exclusive &&\n      !this._collapsible &&\n      this.items.filter((d) => d.open).length === 1 &&\n      item.open\n    ) {\n      return; // Keep at least one open\n    }\n\n    item.open = false;\n  }\n\n  /**\n   * Toggle a specific item by index\n   */\n  toggleItem(index) {\n    const item = this.items[index];\n    if (!item) return;\n\n    if (item.open) {\n      this.closeItem(index);\n    } else {\n      this.openItem(index);\n    }\n  }\n\n  /**\n   * Check if item is open\n   */\n  isOpen(index) {\n    return this.items[index]?.open ?? false;\n  }\n\n  /**\n   * Get all currently open indices\n   */\n  getOpenItems() {\n    return this.items\n      .map((item, index) => (item.open ? index : -1))\n      .filter((i) => i !== -1);\n  }\n}\n\n// Register the component\nif (!customElements.get('ren-accordion')) {\n  customElements.define('ren-accordion', RenAccordion);\n}\n",
      "path": "components/composites/ren-accordion",
      "id": "component:composite:ren-accordion",
      "type": "component",
      "name": "ren-accordion"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-alert-dialog",
          ".ren-alert-dialog-actions",
          ".ren-alert-dialog-description",
          ".ren-alert-dialog-icon",
          ".ren-alert-dialog-icon-danger",
          ".ren-alert-dialog-icon-warning",
          ".ren-alert-dialog-title"
        ],
        "tokens": [
          "--body-size",
          "--color-danger",
          "--color-danger-subtle",
          "--color-fill",
          "--color-overlay",
          "--color-surface",
          "--color-text",
          "--color-text-muted",
          "--color-warning",
          "--color-warning-subtle",
          "--duration-enter",
          "--ease-enter",
          "--icon-xl",
          "--leading-normal",
          "--radius-full",
          "--radius-xl",
          "--shadow-xl",
          "--space-2",
          "--space-3",
          "--space-5",
          "--space-6",
          "--text-lg",
          "--text-xl",
          "--title-sm-size",
          "--transition-overlay",
          "--weight-semibold"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-alert-dialog Component Contract\n\nNon-dismissible confirmation dialog for destructive or critical decisions.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-alert-dialog` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-alert-dialog` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Alert Dialog composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"A destructive or irreversible action (delete, sign-out, discard) requires explicit confirmation.\"\n    - \"Accidental dismissal (Escape, backdrop click) must NOT close the dialog.\"\n    - \"The decision is binary and centered around two actions (cancel + confirm).\"\n    - \"Content is short: an icon, a title, a one-paragraph description, and two buttons.\"\n    - \"You want the native <dialog> showModal() flow with returnValue for the outcome.\"\n  avoidWhen:\n    - \"The dialog hosts a form, multi-step flow, or scrollable content — use ren-dialog.\"\n    - \"The disclosure should dismiss on Escape / backdrop — use ren-dialog (no alert).\"\n    - \"The notification does not block the page — use ren-toast or ren-banner.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-alert-dialog/ren-alert-dialog.css\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"No colocated JS — call dialog.showModal() / dialog.close(returnValue) directly on the native <dialog>.\"\n    - \"The component uses the same --ren-dialog-* token namespace as ren-dialog, so token overrides cascade.\"\n\nrequiredMarkup:\n  - \"The root element is a real <dialog class=\\\"ren-alert-dialog\\\">; never substitute a <div role=\\\"alertdialog\\\">.\"\n  - \"Provide a .ren-alert-dialog-title (heading) and a .ren-alert-dialog-description so AT can announce the decision.\"\n  - \"Actions live inside .ren-alert-dialog-actions and use real <button> elements (.ren-btn-secondary + .ren-btn-danger or similar).\"\n  - \"Each button calls dialog.close('cancel' | 'confirm' | …) so the consumer can read returnValue on the close event.\"\n  - \"Use .ren-alert-dialog-icon-danger or .ren-alert-dialog-icon-warning on the icon slot for severity — do not invent new severity classes.\"\n\nforbiddenPatterns:\n  - \"<div class=\\\"ren-alert-dialog\\\"> rendered without the native <dialog> element.\"\n  - \"Calling dialog.show() instead of dialog.showModal() — alert behavior requires modality + ::backdrop.\"\n  - \"Adding Escape-to-close handlers; the alert variant intentionally blocks accidental dismissal.\"\n  - \"Hardcoded rgba backdrops (background: rgba(0,0,0,.5)) — the ::backdrop already reads --color-overlay.\"\n  - \"Replacing .ren-alert-dialog-actions with arbitrary flex containers that bypass the @container narrow-stack layout.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-dialog-backdrop, --ren-dialog-bg, --ren-dialog-border-color, --ren-dialog-duration, --ren-dialog-easing, --ren-dialog-gap, --ren-dialog-padding, --ren-dialog-radius, --ren-dialog-shadow, --ren-dialog-width.\"\n    - \"Semantic tokens: --color-surface, --color-overlay, --color-text, --color-text-muted, --color-danger, --color-danger-subtle, --color-warning, --color-warning-subtle, --color-fill.\"\n    - \"Layout / motion tokens: --space-*, --radius-*, --shadow-xl, --icon-xl, --duration-enter, --ease-enter, --transition-overlay.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Custom backdrop opacity values that bypass --color-overlay or --transition-overlay.\"\n\naccessibility:\n  required:\n    - \"Use a real <dialog> so the browser exposes role=\\\"dialog\\\" + aria-modal automatically; add aria-labelledby pointing to .ren-alert-dialog-title.\"\n    - \"Open with showModal(); the backdrop becomes inert so background content is unreachable to keyboard / AT.\"\n    - \"The destructive button must be visually distinct (.ren-btn-danger) AND announced by its text — never rely on color alone.\"\n    - \"Focus must move into the dialog on open and return to the trigger on close (native <dialog> behavior).\"\n    - \"Touch targets on action buttons stay ≥ 44px (default .ren-btn size).\"\n    - \"Reduced motion: transitions are disabled under prefers-reduced-motion (already handled by the component).\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-alert-dialog/ren-alert-dialog.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<dialog class=\"ren-alert-dialog\" aria-labelledby=\"delete-title\" aria-describedby=\"delete-description\" closedby=\"none\">\n  <h2 id=\"delete-title\" class=\"ren-alert-dialog-title\">Delete project?</h2>\n  <p id=\"delete-description\" class=\"ren-alert-dialog-description\">This cannot be undone.</p>\n  <form method=\"dialog\" class=\"ren-alert-dialog-actions\">\n    <button value=\"cancel\" class=\"ren-btn ren-btn-secondary\">Cancel</button>\n    <button value=\"delete\" class=\"ren-btn ren-btn-danger\">Delete</button>\n  </form>\n</dialog>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-alert-dialog`\n- `.ren-alert-dialog-actions`\n- `.ren-alert-dialog-description`\n- `.ren-alert-dialog-icon`\n- `.ren-alert-dialog-icon-danger`\n- `.ren-alert-dialog-icon-warning`\n- `.ren-alert-dialog-title`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-dialog-backdrop`\n- `--ren-dialog-bg`\n- `--ren-dialog-border-color`\n- `--ren-dialog-duration`\n- `--ren-dialog-easing`\n- `--ren-dialog-gap`\n- `--ren-dialog-padding`\n- `--ren-dialog-radius`\n- `--ren-dialog-shadow`\n- `--ren-dialog-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/composites/ren-alert-dialog/ren-alert-dialog.css`\n- `docs/components/ren-alert-dialog.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Alert Dialog Component\n   ============================================\n   Confirmation dialog that blocks interaction.\n   Extends native <dialog> like ren-dialog but:\n   - No dismiss on outside click (::backdrop blocks)\n   - No dismiss on Escape (optional)\n   - Requires explicit action (confirm/cancel)\n\n   Minimal JS: just showModal() and close().\n\n   Usage:\n     <dialog class=\"ren-alert-dialog\" id=\"confirm\">\n       <div class=\"ren-alert-dialog-icon\">⚠</div>\n       <h2 class=\"ren-alert-dialog-title\">Delete item?</h2>\n       <p class=\"ren-alert-dialog-description\">\n         This action cannot be undone.\n       </p>\n       <div class=\"ren-alert-dialog-actions\">\n         <button class=\"ren-btn-secondary\" onclick=\"this.closest('dialog').close('cancel')\">\n           Cancel\n         </button>\n         <button class=\"ren-btn-danger\" onclick=\"this.closest('dialog').close('confirm')\">\n           Delete\n         </button>\n       </div>\n     </dialog>\n\n   Open: document.getElementById('confirm').showModal()\n   Result: dialog.addEventListener('close', () => dialog.returnValue)\n   ============================================ */\n\n.ren-alert-dialog {\n  container-type: inline-size;\n  container-name: ren-alert-dialog;\n\n  /* Structure */\n  margin: auto;\n  padding: var(--space-6);\n  border: none;\n\n  /* Sizing */\n  max-width: 24rem;\n  width: 90vw;\n\n  /* Appearance */\n  background: var(--color-surface);\n  color: var(--color-text);\n  border-radius: var(--radius-xl);\n  box-shadow: var(--shadow-xl);\n  text-align: center;\n\n  /* Animation — semantic tokens keep this in sync with ren-dialog\n     and respect prefers-reduced-motion via tokens/semantic/motion.css. */\n  opacity: 1;\n  transform: scale(1);\n  transition:\n    opacity   var(--duration-enter) var(--ease-enter),\n    transform var(--duration-enter) var(--ease-enter),\n    overlay   var(--duration-enter) var(--ease-enter) allow-discrete,\n    display   var(--duration-enter) var(--ease-enter) allow-discrete;\n}\n\n/* Entry animation */\n@starting-style {\n  .ren-alert-dialog[open] {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n}\n\n/* Exit */\n.ren-alert-dialog:not([open]) {\n  opacity: 0;\n  transform: scale(0.95);\n}\n\n/* Backdrop — shared overlay transition keeps all scrims in sync. */\n.ren-alert-dialog::backdrop {\n  background-color: var(--color-overlay);\n  transition: var(--transition-overlay);\n}\n\n@starting-style {\n  .ren-alert-dialog[open]::backdrop {\n    opacity: 0;\n  }\n}\n\n/* ─── Icon (optional) ─── */\n.ren-alert-dialog-icon {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: var(--icon-xl);\n  height: var(--icon-xl);\n  margin: 0 auto var(--space-3);\n  font-size: var(--text-xl);\n  background-color: var(--color-fill);\n  border-radius: var(--radius-full);\n}\n\n/* Danger icon */\n.ren-alert-dialog-icon-danger {\n  background-color: var(--color-danger-subtle);\n  color: var(--color-danger);\n}\n\n/* Warning icon */\n.ren-alert-dialog-icon-warning {\n  background-color: var(--color-warning-subtle);\n  color: var(--color-warning);\n}\n\n/* ─── Title ─── */\n.ren-alert-dialog-title {\n  font-size: var(--title-sm-size, var(--text-lg));\n  font-weight: var(--weight-semibold);\n  color: var(--color-text);\n  margin-bottom: var(--space-2);\n}\n\n/* ─── Description ─── */\n.ren-alert-dialog-description {\n  font-size: var(--body-size);\n  color: var(--color-text-muted);\n  line-height: var(--leading-normal);\n  margin-bottom: var(--space-5);\n}\n\n/* ─── Actions ─── */\n.ren-alert-dialog-actions {\n  display: flex;\n  gap: var(--space-2);\n  justify-content: center;\n}\n\n/* Stack actions when dialog is narrow */\n@container ren-alert-dialog (max-width: 24rem) {\n  .ren-alert-dialog-actions {\n    flex-direction: column-reverse;\n  }\n\n  .ren-alert-dialog-actions > * {\n    width: 100%;\n  }\n}\n\n/* ─── Reduced motion ─── */\n@media (prefers-reduced-motion: reduce) {\n  .ren-alert-dialog {\n    transition: none;\n  }\n  .ren-alert-dialog::backdrop {\n    transition: none;\n  }\n}\n",
      "path": "components/composites/ren-alert-dialog",
      "id": "component:composite:ren-alert-dialog",
      "type": "component",
      "name": "ren-alert-dialog"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-calendar",
          ".ren-calendar-day",
          ".ren-calendar-day-in-range",
          ".ren-calendar-day-range-end",
          ".ren-calendar-day-range-start",
          ".ren-calendar-grid",
          ".ren-calendar-header",
          ".ren-calendar-lg",
          ".ren-calendar-nav",
          ".ren-calendar-next",
          ".ren-calendar-prev",
          ".ren-calendar-range",
          ".ren-calendar-sm",
          ".ren-calendar-title",
          ".ren-calendar-weekday",
          ".ren-calendar-weekdays"
        ],
        "tokens": [
          "--body-size",
          "--caption-size",
          "--color-accent",
          "--color-accent-subtle",
          "--color-border",
          "--color-fill",
          "--color-fill-active",
          "--color-on-accent",
          "--color-surface",
          "--color-surface-raised",
          "--color-text",
          "--color-text-faint",
          "--color-text-muted",
          "--duration-enter",
          "--ease-enter",
          "--label-size",
          "--label-weight",
          "--radius-lg",
          "--radius-md",
          "--ren-calendar-bg",
          "--ren-calendar-border",
          "--ren-calendar-day-size",
          "--ren-calendar-radius",
          "--ren-calendar-range-bg",
          "--ren-calendar-selected-bg",
          "--ren-calendar-selected-color",
          "--ren-calendar-today-bg",
          "--ren-calendar-width",
          "--shadow-md",
          "--size-md",
          "--space-0-5",
          "--space-1",
          "--space-2",
          "--space-3",
          "--space-4",
          "--space-6",
          "--stroke-1",
          "--touch-min",
          "--transition-tactile"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-calendar Component Contract\n\nCalendar grid and date selection composite.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-calendar` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-calendar` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Calendar composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The UI needs a full month grid for date browsing or selection.\"\n    - \"You need single, range, or multiple selection modes (mode=\\\"single|range|multiple\\\").\"\n    - \"Locale-aware month names and first-day-of-week must be supported (locale, first-day attrs).\"\n    - \"min / max bounds must constrain selectable dates.\"\n    - \"You need ARIA grid + roving tabindex keyboard navigation across days.\"\n  avoidWhen:\n    - \"You only need a date text input — use ren-date-picker (composite that may embed ren-calendar).\"\n    - \"You need a year/decade picker without a month grid — use a different control.\"\n    - \"The UI is a schedule / agenda view rather than date selection — use a calendar-view pattern.\"\n    - \"Only a static date display is needed — use ren-text with a formatted Date.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-calendar/ren-calendar.css\"\n  js:\n    - \"rends/components/composites/ren-calendar/ren-calendar.js\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"JS is required: <ren-calendar> renders the grid imperatively; there is no static markup fallback.\"\n    - \"Configure via attributes (value, mode, locale, first-day, min, max); avoid manual DOM mutations on .ren-calendar-grid.\"\n\nrequiredMarkup:\n  - \"Render <ren-calendar> as the host; the component populates its own .ren-calendar-header, .ren-calendar-weekdays, and .ren-calendar-grid.\"\n  - \"Each day cell is a real <button class=\\\"ren-calendar-day\\\"> with aria-selected and (when applicable) data-today / data-outside / [disabled].\"\n  - \"Navigation arrows use .ren-calendar-prev / .ren-calendar-next inside .ren-calendar-nav and rely on the component's chevron pseudo-elements.\"\n  - \"Use .ren-calendar-sm or .ren-calendar-lg on the host for size variants; do not invent custom size classes.\"\n  - \"For ranges, the component sets .ren-calendar-day-range-start, .ren-calendar-day-in-range, .ren-calendar-day-range-end — do not author these by hand.\"\n\nforbiddenPatterns:\n  - \"Replacing day <button>s with <div role=\\\"button\\\"> — breaks keyboard + screen reader expectations.\"\n  - \"Hardcoded today highlight (border: 2px solid #...) — rely on [data-today] and --color-accent.\"\n  - \"Toggling a day's selected state by adding a custom class instead of aria-selected=\\\"true\\\".\"\n  - \"Mounting the calendar inside a non-modal popover without focus management — wrap in ren-popover or ren-dialog if floating.\"\n  - \"Hardcoded weekday labels in the markup; the component renders them from locale.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-calendar-bg, --ren-calendar-border, --ren-calendar-day-size, --ren-calendar-radius, --ren-calendar-range-bg, --ren-calendar-selected-bg, --ren-calendar-selected-color, --ren-calendar-today-bg, --ren-calendar-width.\"\n    - \"Semantic tokens: --color-surface-raised, --color-border, --color-text, --color-text-muted, --color-text-faint, --color-fill, --color-fill-active, --color-accent, --color-accent-subtle, --color-on-accent.\"\n    - \"Layout / motion tokens: --space-*, --radius-*, --shadow-md, --touch-min, --duration-enter, --ease-enter, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Hardcoded day-cell sizes; tune --ren-calendar-day-size or pick the .ren-calendar-sm / .ren-calendar-lg variant.\"\n\naccessibility:\n  required:\n    - \"Day cells implement the ARIA grid pattern with a single roving tabindex; never expose more than one tab stop in the grid.\"\n    - \"Keyboard: Arrow keys move focus between days, PageUp/PageDown move months, Home/End jump within a week — preserve these in any customization.\"\n    - \"Selected days set aria-selected=\\\"true\\\"; do not communicate selection through color alone.\"\n    - \"Today indicator uses [data-today] AND an inset ring (box-shadow); the ring is required so color-blind users perceive today.\"\n    - \"Out-of-month days set [data-outside] and pointer-events: none; do not let them receive focus.\"\n    - \"Min/max bounds disable cells via the native [disabled] attribute so the browser blocks activation.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-calendar/ren-calendar.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-calendar/ren-calendar.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-calendar value=\"2026-07-10\" aria-label=\"Choose a date\"></ren-calendar>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-calendar`\n- `.ren-calendar-day`\n- `.ren-calendar-day-in-range`\n- `.ren-calendar-day-range-end`\n- `.ren-calendar-day-range-start`\n- `.ren-calendar-grid`\n- `.ren-calendar-header`\n- `.ren-calendar-lg`\n- `.ren-calendar-nav`\n- `.ren-calendar-next`\n- `.ren-calendar-prev`\n- `.ren-calendar-sm`\n- `.ren-calendar-title`\n- `.ren-calendar-weekday`\n- `.ren-calendar-weekdays`\n\n## States And Attributes\n\n- `[aria-selected]`\n- `[data-outside]`\n- `[data-today]`\n- `:active`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-calendar-bg`\n- `--ren-calendar-border`\n- `--ren-calendar-day-size`\n- `--ren-calendar-radius`\n- `--ren-calendar-range-bg`\n- `--ren-calendar-selected-bg`\n- `--ren-calendar-selected-color`\n- `--ren-calendar-today-bg`\n- `--ren-calendar-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-calendar/ren-calendar.css`\n- `components/composites/ren-calendar/ren-calendar.js`\n- `docs/components/ren-calendar.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ═══ REN CALENDAR WEB COMPONENT ═══\n   A fully accessible, keyboard-navigable calendar component\n   with support for single, range, and multiple date selection.\n   Uses ARIA grid pattern with roving tabindex for keyboard navigation.\n   ══════════════════════════════════════════════════════════════════ */\n\n/* ═══ BASE CONTAINER ═══ */\n.ren-calendar {\n  container-type: inline-size;\n  container-name: ren-calendar;\n  background: var(--color-surface-raised);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-md);\n  padding: var(--space-4);\n  font-family: inherit;\n}\n\n/* ═══ HEADER WITH NAVIGATION ═══ */\n.ren-calendar-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  margin-bottom: var(--space-4);\n  gap: var(--space-2);\n}\n\n/* ═══ MONTH/YEAR TITLE ═══ */\n.ren-calendar-title {\n  font-size: var(--body-size);\n  font-weight: var(--label-weight);\n  color: var(--color-text);\n  flex: 1;\n  text-align: center;\n  min-width: 150px;\n  white-space: nowrap;\n}\n\n/* ═══ NAVIGATION BUTTONS ═══ */\n.ren-calendar-nav {\n  display: flex;\n  gap: var(--space-1);\n}\n\n.ren-calendar-nav button {\n  width: var(--touch-min);\n  height: var(--touch-min);\n  min-width: var(--touch-min);\n  min-height: var(--touch-min);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  background: transparent;\n  border: none;\n  border-radius: var(--radius-md);\n  color: var(--color-text);\n  cursor: pointer;\n  transition: var(--transition-tactile);\n  position: relative;\n\n  &:hover {\n    background: var(--color-fill);\n  }\n\n  &:active {\n    background: var(--color-fill-active);\n  }\n\n  &:disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  /* ═══ CHEVRON ICONS ═══ */\n  &.ren-calendar-prev::before {\n    content: '‹';\n    font-size: 1.5em;\n    font-weight: bold;\n  }\n\n  &.ren-calendar-next::before {\n    content: '›';\n    font-size: 1.5em;\n    font-weight: bold;\n  }\n}\n\n/* ═══ WEEKDAY HEADERS ═══ */\n.ren-calendar-weekdays {\n  display: grid;\n  grid-template-columns: repeat(7, 1fr);\n  gap: var(--space-1);\n  margin-bottom: var(--space-2);\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  color: var(--color-text-muted);\n  text-align: center;\n}\n\n.ren-calendar-weekday {\n  padding: var(--space-2) var(--space-1);\n  text-transform: uppercase;\n  letter-spacing: 0.5px;\n}\n\n/* ═══ CALENDAR GRID ═══ */\n.ren-calendar-grid {\n  display: grid;\n  grid-template-columns: repeat(7, 1fr);\n  gap: var(--space-1);\n}\n\n/* ═══ INDIVIDUAL DAY BUTTON ═══ */\n.ren-calendar-day {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-width: var(--touch-min);\n  min-height: var(--touch-min);\n  width: 100%;\n  aspect-ratio: 1;\n  padding: var(--space-0-5);\n  background: transparent;\n  border: none;\n  border-radius: var(--radius-md);\n  color: var(--color-text);\n  font-size: var(--body-size);\n  cursor: pointer;\n  transition: var(--transition-tactile);\n  position: relative;\n\n  /* ═══ DEFAULT STATE ═══ */\n  &:not([disabled]):hover {\n    background: var(--color-fill);\n  }\n\n  &:not([disabled]):active {\n    background: var(--color-fill-active);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  /* ═══ SELECTED DATE ═══ */\n  &[aria-selected=\"true\"] {\n    background: var(--color-accent);\n    color: var(--color-on-accent);\n    font-weight: 600;\n  }\n\n  /* ═══ TODAY INDICATOR ═══ */\n  &[data-today] {\n    background: var(--color-accent-subtle);\n    color: var(--color-accent);\n    font-weight: 600;\n    box-shadow: inset 0 0 0 2px var(--color-accent);\n  }\n\n  &[data-today][aria-selected=\"true\"] {\n    background: var(--color-accent);\n    color: var(--color-on-accent);\n    box-shadow: none;\n  }\n\n  /* ═══ OUTSIDE MONTH DATES ═══ */\n  &[data-outside] {\n    color: var(--color-text-faint);\n    pointer-events: none;\n  }\n\n  /* ═══ DISABLED DATES ═══ */\n  &[disabled] {\n    color: var(--color-text-muted);\n    cursor: not-allowed;\n    opacity: 0.5;\n  }\n\n  /* ═══ RANGE START ═══ */\n  &.ren-calendar-day-range-start {\n    background: var(--color-accent);\n    color: var(--color-on-accent);\n    border-radius: var(--radius-md);\n\n    &::after {\n      content: '';\n      position: absolute;\n      right: -4px;\n      top: 0;\n      bottom: 0;\n      width: 50%;\n      background: var(--color-accent-subtle);\n      z-index: -1;\n    }\n  }\n\n  /* ═══ RANGE END ═══ */\n  &.ren-calendar-day-range-end {\n    background: var(--color-accent);\n    color: var(--color-on-accent);\n    border-radius: var(--radius-md);\n\n    &::before {\n      content: '';\n      position: absolute;\n      left: -4px;\n      top: 0;\n      bottom: 0;\n      width: 50%;\n      background: var(--color-accent-subtle);\n      z-index: -1;\n    }\n  }\n\n  /* ═══ IN RANGE ═══ */\n  &.ren-calendar-day-in-range {\n    background: var(--color-accent-subtle);\n    color: var(--color-text);\n    border-radius: 0;\n  }\n}\n\n/* ═══ ANIMATION FOR MONTH TRANSITIONS ═══ */\n@keyframes ren-calendar-fade-in {\n  from {\n    opacity: 0;\n    transform: translateY(-4px);\n  }\n\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n.ren-calendar-grid {\n  animation: ren-calendar-fade-in var(--duration-enter) var(--ease-enter);\n}\n\n/* ═══ REDUCED MOTION SUPPORT ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-calendar-grid {\n    animation: none;\n  }\n\n  .ren-calendar-nav button,\n  .ren-calendar-day {\n    transition: none;\n  }\n}\n\n/* ═══ SIZE VARIANT: SMALL ═══ */\n.ren-calendar-sm {\n  padding: var(--space-3);\n\n  .ren-calendar-header {\n    margin-bottom: var(--space-3);\n  }\n\n  .ren-calendar-title {\n    font-size: var(--label-size);\n  }\n\n  .ren-calendar-nav button {\n    width: 36px;\n    height: 36px;\n    min-width: 36px;\n    min-height: 36px;\n  }\n\n  .ren-calendar-weekday {\n    font-size: var(--caption-size);\n  }\n\n  .ren-calendar-day {\n    font-size: var(--label-size);\n    min-width: 36px;\n    min-height: 36px;\n  }\n}\n\n/* ═══ SIZE VARIANT: LARGE ═══ */\n.ren-calendar-lg {\n  padding: var(--space-6);\n\n  .ren-calendar-header {\n    margin-bottom: var(--space-6);\n    gap: var(--space-4);\n  }\n\n  .ren-calendar-title {\n    font-size: 1.25rem;\n  }\n\n  .ren-calendar-nav button {\n    width: 52px;\n    height: 52px;\n    min-width: 52px;\n    min-height: 52px;\n    font-size: 1.25rem;\n  }\n\n  .ren-calendar-weekdays {\n    margin-bottom: var(--space-4);\n  }\n\n  .ren-calendar-grid {\n    gap: var(--space-2);\n  }\n\n  .ren-calendar-day {\n    font-size: 1.125rem;\n    min-width: 52px;\n    min-height: 52px;\n  }\n}\n\n/* ═══ DARK MODE SUPPORT ═══ */\n@media (prefers-color-scheme: dark) {\n  .ren-calendar {\n    /* ═══ USES CSS VARIABLES - AUTO ADAPTS ═══ */\n  }\n}\n\n/* ═══ COMPACT LAYOUT (Container Query) ═══ */\n@container ren-calendar (max-width: 320px) {\n  .ren-calendar {\n    padding: var(--space-3);\n  }\n\n  .ren-calendar-grid {\n    gap: var(--space-0-5);\n  }\n\n  .ren-calendar-day {\n    min-width: 32px;\n    min-height: 32px;\n    font-size: var(--label-size);\n  }\n\n  .ren-calendar-nav button {\n    width: 40px;\n    height: 40px;\n    min-width: 40px;\n    min-height: 40px;\n  }\n}\n/* Appearance API bridge */\n.ren-calendar {\n  inline-size: var(--ren-calendar-width, 280px);\n  border-radius: var(--ren-calendar-radius, var(--radius-lg));\n  background: var(--ren-calendar-bg, var(--color-surface));\n  border-color: var(--ren-calendar-border, var(--color-border));\n}\n.ren-calendar [data-day], .ren-calendar-day { inline-size: var(--ren-calendar-day-size, var(--size-md)); block-size: var(--ren-calendar-day-size, var(--size-md)); }\n.ren-calendar [data-today] { background: var(--ren-calendar-today-bg, var(--color-accent-subtle)); }\n.ren-calendar [aria-selected=\"true\"], .ren-calendar [data-selected] { background: var(--ren-calendar-selected-bg, var(--color-accent)); color: var(--ren-calendar-selected-color, var(--color-on-accent)); }\n.ren-calendar [data-range], .ren-calendar-range { background: var(--ren-calendar-range-bg, var(--color-accent-subtle)); }\n\n@media (any-pointer: coarse), (pointer: coarse) {\n  .ren-calendar .ren-calendar-nav button,\n  .ren-calendar .ren-calendar-day {\n    inline-size: calc(var(--touch-min) + var(--stroke-1));\n    block-size: calc(var(--touch-min) + var(--stroke-1));\n    min-inline-size: calc(var(--touch-min) + var(--stroke-1));\n    min-block-size: calc(var(--touch-min) + var(--stroke-1));\n  }\n}\n\n\nimport { formatLocalDate, parseLocalDate } from '../../../utils/local-date.js';\n\n/* ═══ REN CALENDAR WEB COMPONENT ═══\n   A fully accessible, keyboard-navigable calendar for date selection.\n\n   Features:\n   - Multiple selection modes: single, range, multiple\n   - Full keyboard navigation with ARIA grid pattern\n   - Locale-aware date formatting\n   - Customizable min/max dates\n   - Month navigation with prev/next buttons\n   - Today indicator and outside-month date styling\n\n   Usage:\n   <ren-calendar value=\"2026-03-31\" locale=\"en-US\" mode=\"single\"></ren-calendar>\n   ══════════════════════════════════════════════════════════════════ */\n\nexport class RenCalendar extends HTMLElement {\n  constructor() {\n    super();\n\n    /* ═══ STATE ═══ */\n    this.currentDate = new Date();\n    this.selectedDate = null;\n    this.selectedDates = new Set();\n    this.rangeStart = null;\n    this.rangeEnd = null;\n    this.focusedDate = null;\n\n    /* ═══ CONFIGURATION ═══ */\n    this.mode = 'single'; // single, range, multiple\n    this.locale = 'en-US';\n    this.firstDay = 0; // 0 = Sunday, 1 = Monday\n    this.minDate = null;\n    this.maxDate = null;\n\n    /* ═══ BIND METHODS ═══ */\n    this.handleDayClick = this.handleDayClick.bind(this);\n    this.handleDayKeyDown = this.handleDayKeyDown.bind(this);\n    this.handlePrevMonth = this.handlePrevMonth.bind(this);\n    this.handleNextMonth = this.handleNextMonth.bind(this);\n  }\n\n  connectedCallback() {\n    /* ═══ READ ATTRIBUTES ═══ */\n    this.mode = this.getAttribute('mode') || 'single';\n    this.locale = this.getAttribute('locale') || 'en-US';\n    const firstDayAttr = this.getAttribute('first-day');\n    if (firstDayAttr !== null) this.firstDay = parseInt(firstDayAttr, 10);\n\n    /* ═══ PARSE DATE ATTRIBUTES ═══ */\n    const valueAttr = this.getAttribute('value');\n    if (valueAttr) {\n      const parsed = this.parseDate(valueAttr);\n      if (!isNaN(parsed)) {\n        this.selectedDate = parsed;\n        this.currentDate = new Date(parsed);\n      }\n    }\n\n    const minAttr = this.getAttribute('min');\n    if (minAttr) this.minDate = this.parseDate(minAttr);\n\n    const maxAttr = this.getAttribute('max');\n    if (maxAttr) this.maxDate = this.parseDate(maxAttr);\n\n    /* ═══ RENDER CALENDAR ═══ */\n    this.render();\n  }\n\n  /* ═══ RENDER CALENDAR ═══ */\n  render() {\n    this.innerHTML = '';\n    this.classList.add('ren-calendar');\n\n    /* ═══ BUILD HEADER ═══ */\n    const header = document.createElement('div');\n    header.className = 'ren-calendar-header';\n\n    const title = document.createElement('div');\n    title.className = 'ren-calendar-title';\n    title.textContent = this.formatMonthYear();\n    title.setAttribute('aria-live', 'polite');\n\n    const navDiv = document.createElement('div');\n    navDiv.className = 'ren-calendar-nav';\n\n    const prevBtn = document.createElement('button');\n    prevBtn.className = 'ren-calendar-prev';\n    prevBtn.setAttribute('aria-label', `Previous month (${this.formatPrevMonth()})`);\n    prevBtn.addEventListener('click', this.handlePrevMonth);\n\n    const nextBtn = document.createElement('button');\n    nextBtn.className = 'ren-calendar-next';\n    nextBtn.setAttribute('aria-label', `Next month (${this.formatNextMonth()})`);\n    nextBtn.addEventListener('click', this.handleNextMonth);\n\n    navDiv.appendChild(prevBtn);\n    navDiv.appendChild(nextBtn);\n\n    header.appendChild(title);\n    header.appendChild(navDiv);\n    this.appendChild(header);\n\n    /* ═══ BUILD WEEKDAY HEADERS ═══ */\n    const weekdaysDiv = document.createElement('div');\n    weekdaysDiv.className = 'ren-calendar-weekdays';\n\n    const weekdayNames = this.getWeekdayNames();\n    weekdayNames.forEach((name) => {\n      const dayLabel = document.createElement('div');\n      dayLabel.className = 'ren-calendar-weekday';\n      dayLabel.textContent = name;\n      dayLabel.setAttribute('aria-label', name);\n      weekdaysDiv.appendChild(dayLabel);\n    });\n\n    this.appendChild(weekdaysDiv);\n\n    /* ═══ BUILD CALENDAR GRID ═══ */\n    const grid = document.createElement('div');\n    grid.className = 'ren-calendar-grid';\n    grid.setAttribute('role', 'grid');\n\n    const dates = this.generateCalendarDates();\n    dates.forEach((date, index) => {\n      const dayBtn = document.createElement('button');\n      dayBtn.className = 'ren-calendar-day';\n      dayBtn.textContent = date.date.getDate();\n      dayBtn.setAttribute('type', 'button');\n      dayBtn.setAttribute('role', 'gridcell');\n      dayBtn.setAttribute('data-date', this.dateToString(date.date));\n\n      /* ═══ HANDLE OUTSIDE MONTH DATES ═══ */\n      if (date.outside) {\n        dayBtn.setAttribute('data-outside', '');\n        dayBtn.setAttribute('aria-disabled', 'true');\n        dayBtn.disabled = true;\n      }\n\n      /* ═══ CHECK IF DATE IS DISABLED ═══ */\n      if (this.isDateDisabled(date.date)) {\n        dayBtn.disabled = true;\n        dayBtn.setAttribute('aria-disabled', 'true');\n      }\n\n      /* ═══ CHECK IF TODAY ═══ */\n      if (this.isToday(date.date)) {\n        dayBtn.setAttribute('data-today', '');\n      }\n\n      /* ═══ CHECK IF SELECTED (SINGLE MODE) ═══ */\n      if (this.mode === 'single' && this.selectedDate && this.isSameDay(date.date, this.selectedDate)) {\n        dayBtn.setAttribute('aria-selected', 'true');\n        this.focusedDate = date.date;\n      }\n\n      /* ═══ CHECK IF SELECTED (MULTIPLE MODE) ═══ */\n      if (this.mode === 'multiple' && this.selectedDates.has(this.dateToString(date.date))) {\n        dayBtn.setAttribute('aria-selected', 'true');\n      }\n\n      /* ═══ CHECK RANGE SELECTION ═══ */\n      if (this.mode === 'range' && this.rangeStart && this.rangeEnd) {\n        const dateStr = this.dateToString(date.date);\n        const rangeStart = this.dateToString(this.rangeStart);\n        const rangeEnd = this.dateToString(this.rangeEnd);\n\n        if (dateStr === rangeStart) {\n          dayBtn.classList.add('ren-calendar-day-range-start');\n          dayBtn.setAttribute('aria-selected', 'true');\n        } else if (dateStr === rangeEnd) {\n          dayBtn.classList.add('ren-calendar-day-range-end');\n          dayBtn.setAttribute('aria-selected', 'true');\n        } else if (dateStr > rangeStart && dateStr < rangeEnd) {\n          dayBtn.classList.add('ren-calendar-day-in-range');\n        }\n      }\n\n      /* ═══ EVENT LISTENERS ═══ */\n      dayBtn.addEventListener('click', (e) => this.handleDayClick(e, date.date));\n      dayBtn.addEventListener('keydown', (e) => this.handleDayKeyDown(e, date.date, index, dates.length));\n\n      grid.appendChild(dayBtn);\n    });\n\n    this.appendChild(grid);\n\n    /* ═══ SET TABINDEX FOR FOCUS MANAGEMENT ═══ */\n    this.updateTabIndex();\n  }\n\n  /* ═══ UPDATE TABINDEX FOR ROVING TABINDEX PATTERN ═══ */\n  updateTabIndex() {\n    const buttons = Array.from(this.querySelectorAll('.ren-calendar-day'));\n    const enabledButtons = buttons.filter((btn) => !btn.disabled);\n    let target = null;\n\n    if (this.focusedDate) {\n      const focusedDateString = this.dateToString(this.focusedDate);\n      target = enabledButtons.find((btn) => btn.dataset.date === focusedDateString);\n    }\n\n    if (!target && this.selectedDate) {\n      const selectedDateString = this.dateToString(this.selectedDate);\n      target = enabledButtons.find((btn) => btn.dataset.date === selectedDateString);\n    }\n\n    if (!target) {\n      target = enabledButtons.find((btn) => btn.hasAttribute('data-today'));\n    }\n\n    target ??= enabledButtons[0] ?? null;\n\n    buttons.forEach((btn) => {\n      btn.setAttribute('tabindex', btn === target ? '0' : '-1');\n    });\n  }\n\n  /* ═══ GENERATE CALENDAR DATES ═══ */\n  generateCalendarDates() {\n    const year = this.currentDate.getFullYear();\n    const month = this.currentDate.getMonth();\n\n    /* ═══ FIRST DAY OF MONTH AND WEEKDAY ═══ */\n    const firstDay = new Date(year, month, 1);\n    const lastDay = new Date(year, month + 1, 0);\n    const daysInMonth = lastDay.getDate();\n\n    /* ═══ STARTING WEEKDAY ═══ */\n    let startingDayOfWeek = firstDay.getDay() - this.firstDay;\n    if (startingDayOfWeek < 0) startingDayOfWeek += 7;\n\n    const dates = [];\n\n    /* ═══ PREVIOUS MONTH DATES ═══ */\n    const prevMonthLastDay = new Date(year, month, 0).getDate();\n    for (let i = startingDayOfWeek - 1; i >= 0; i--) {\n      const prevDate = new Date(year, month - 1, prevMonthLastDay - i);\n      dates.push({ date: prevDate, outside: true });\n    }\n\n    /* ═══ CURRENT MONTH DATES ═══ */\n    for (let day = 1; day <= daysInMonth; day++) {\n      dates.push({ date: new Date(year, month, day), outside: false });\n    }\n\n    /* ═══ NEXT MONTH DATES ═══ */\n    const remainingCells = 42 - dates.length; // 6 rows × 7 cols\n    for (let day = 1; day <= remainingCells; day++) {\n      dates.push({ date: new Date(year, month + 1, day), outside: true });\n    }\n\n    return dates;\n  }\n\n  /* ═══ GET WEEKDAY NAMES ═══ */\n  getWeekdayNames() {\n    const formatter = new Intl.DateTimeFormat(this.locale, { weekday: 'short' });\n    const weekdays = [];\n\n    for (let i = 0; i < 7; i++) {\n      const dayIndex = (i + this.firstDay) % 7;\n      const date = new Date(2024, 0, dayIndex + 7); // Sunday is Jan 7, 2024\n      weekdays.push(formatter.format(date).toUpperCase().substring(0, 2));\n    }\n\n    return weekdays;\n  }\n\n  /* ═══ FORMAT MONTH AND YEAR ═══ */\n  formatMonthYear() {\n    const formatter = new Intl.DateTimeFormat(this.locale, { month: 'long', year: 'numeric' });\n    return formatter.format(this.currentDate);\n  }\n\n  /* ═══ FORMAT PREVIOUS MONTH ═══ */\n  formatPrevMonth() {\n    const prevMonth = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() - 1);\n    const formatter = new Intl.DateTimeFormat(this.locale, { month: 'long', year: 'numeric' });\n    return formatter.format(prevMonth);\n  }\n\n  /* ═══ FORMAT NEXT MONTH ═══ */\n  formatNextMonth() {\n    const nextMonth = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() + 1);\n    const formatter = new Intl.DateTimeFormat(this.locale, { month: 'long', year: 'numeric' });\n    return formatter.format(nextMonth);\n  }\n\n  /* ═══ HANDLE DAY CLICK ═══ */\n  handleDayClick(event, date) {\n    if (event.target.disabled || event.target.hasAttribute('data-outside')) {\n      return;\n    }\n\n    this.selectDate(date);\n  }\n\n  /* ═══ SELECT DATE ═══ */\n  selectDate(date) {\n    if (this.mode === 'single') {\n      this.selectedDate = date;\n      this.focusedDate = date;\n      this.dispatchDateSelectEvent();\n    } else if (this.mode === 'range') {\n      if (!this.rangeStart) {\n        this.rangeStart = date;\n      } else if (!this.rangeEnd) {\n        if (date < this.rangeStart) {\n          this.rangeEnd = this.rangeStart;\n          this.rangeStart = date;\n        } else {\n          this.rangeEnd = date;\n        }\n      } else {\n        this.rangeStart = date;\n        this.rangeEnd = null;\n      }\n      this.dispatchDateSelectEvent();\n    } else if (this.mode === 'multiple') {\n      const dateStr = this.dateToString(date);\n      if (this.selectedDates.has(dateStr)) {\n        this.selectedDates.delete(dateStr);\n      } else {\n        this.selectedDates.add(dateStr);\n      }\n      this.dispatchDateSelectEvent();\n    }\n\n    this.render();\n  }\n\n  /* ═══ HANDLE DAY KEYBOARD NAVIGATION ═══ */\n  handleDayKeyDown(event, date, index, totalDates) {\n    const key = event.key;\n\n    if (key === 'Enter' || key === ' ') {\n      event.preventDefault();\n      this.selectDate(date);\n      return;\n    }\n\n    if (key === 'ArrowUp') {\n      event.preventDefault();\n      const newIndex = Math.max(0, index - 7);\n      this.focusDateAtIndex(newIndex);\n      return;\n    }\n\n    if (key === 'ArrowDown') {\n      event.preventDefault();\n      const newIndex = Math.min(totalDates - 1, index + 7);\n      this.focusDateAtIndex(newIndex);\n      return;\n    }\n\n    if (key === 'ArrowLeft') {\n      event.preventDefault();\n      const newIndex = Math.max(0, index - 1);\n      this.focusDateAtIndex(newIndex);\n      return;\n    }\n\n    if (key === 'ArrowRight') {\n      event.preventDefault();\n      const newIndex = Math.min(totalDates - 1, index + 1);\n      this.focusDateAtIndex(newIndex);\n      return;\n    }\n\n    if (key === 'Home') {\n      event.preventDefault();\n      const weekStart = Math.floor(index / 7) * 7;\n      this.focusDateAtIndex(weekStart);\n      return;\n    }\n\n    if (key === 'End') {\n      event.preventDefault();\n      const weekEnd = Math.ceil((index + 1) / 7) * 7 - 1;\n      this.focusDateAtIndex(Math.min(totalDates - 1, weekEnd));\n      return;\n    }\n\n    if (key === 'PageUp') {\n      event.preventDefault();\n      this.handlePrevMonth();\n      return;\n    }\n\n    if (key === 'PageDown') {\n      event.preventDefault();\n      this.handleNextMonth();\n      return;\n    }\n  }\n\n  /* ═══ FOCUS DATE AT INDEX ═══ */\n  focusDateAtIndex(index) {\n    const buttons = this.querySelectorAll('.ren-calendar-day');\n    if (buttons[index] && !buttons[index].disabled) {\n      this.focusedDate = parseLocalDate(buttons[index].dataset.date);\n      this.updateTabIndex();\n      buttons[index].focus();\n    }\n  }\n\n  /* ═══ HANDLE PREVIOUS MONTH ═══ */\n  handlePrevMonth() {\n    this.currentDate = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() - 1);\n    this.render();\n  }\n\n  /* ═══ HANDLE NEXT MONTH ═══ */\n  handleNextMonth() {\n    this.currentDate = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() + 1);\n    this.render();\n  }\n\n  /* ═══ GO TO MONTH ═══ */\n  goToMonth(year, month) {\n    this.currentDate = new Date(year, month);\n    this.render();\n  }\n\n  /* ═══ SET RANGE ═══ */\n  setRange(start, end) {\n    if (this.mode !== 'range') {\n      console.warn('RenCalendar: setRange() only works in range mode');\n      return;\n    }\n\n    this.rangeStart = this.parseDate(start);\n    this.rangeEnd = this.parseDate(end);\n    this.render();\n  }\n\n  /* ═══ DISPATCH CUSTOM EVENT ═══ */\n  dispatchDateSelectEvent() {\n    let eventData = { date: this.selectedDate };\n\n    if (this.mode === 'range') {\n      eventData = { range: { start: this.rangeStart, end: this.rangeEnd } };\n    } else if (this.mode === 'multiple') {\n      eventData = { dates: Array.from(this.selectedDates).map((d) => parseLocalDate(d)) };\n    }\n\n    this.dispatchEvent(\n      new CustomEvent('ren-date-select', {\n        detail: eventData,\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ UTILITY METHODS ═══ */\n\n  /* ═══ CHECK IF DATE IS TODAY ═══ */\n  isToday(date) {\n    const today = new Date();\n    return this.isSameDay(date, today);\n  }\n\n  /* ═══ CHECK IF TWO DATES ARE THE SAME DAY ═══ */\n  isSameDay(date1, date2) {\n    return (\n      date1.getFullYear() === date2.getFullYear() &&\n      date1.getMonth() === date2.getMonth() &&\n      date1.getDate() === date2.getDate()\n    );\n  }\n\n  /* ═══ CHECK IF DATE IS DISABLED ═══ */\n  isDateDisabled(date) {\n    if (this.minDate && date < this.minDate) return true;\n    if (this.maxDate && date > this.maxDate) return true;\n    return false;\n  }\n\n  /* ═══ CONVERT DATE TO ISO STRING ═══ */\n  dateToString(date) {\n    return formatLocalDate(date);\n  }\n\n  parseDate(value) {\n    return parseLocalDate(value);\n  }\n\n  /* ═══ PUBLIC METHODS ═══ */\n\n  /* ═══ GET VALUE ═══ */\n  getValue() {\n    if (this.mode === 'single') {\n      return this.selectedDate ? this.dateToString(this.selectedDate) : null;\n    } else if (this.mode === 'range') {\n      return this.rangeStart && this.rangeEnd\n        ? { start: this.dateToString(this.rangeStart), end: this.dateToString(this.rangeEnd) }\n        : null;\n    } else if (this.mode === 'multiple') {\n      return Array.from(this.selectedDates);\n    }\n  }\n\n  /* ═══ SET VALUE ═══ */\n  setValue(value) {\n    if (this.mode === 'single' && typeof value === 'string') {\n    this.selectedDate = this.parseDate(value);\n      this.render();\n    } else if (this.mode === 'range' && value && typeof value === 'object') {\n      this.rangeStart = this.parseDate(value.start);\n      this.rangeEnd = this.parseDate(value.end);\n      this.render();\n    } else if (this.mode === 'multiple' && Array.isArray(value)) {\n      this.selectedDates = new Set(value);\n      this.render();\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-calendar')) {\n  customElements.define('ren-calendar', RenCalendar);\n}\n",
      "path": "components/composites/ren-calendar",
      "id": "component:composite:ren-calendar",
      "type": "component",
      "name": "ren-calendar"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-carousel",
          ".ren-carousel-2",
          ".ren-carousel-3",
          ".ren-carousel-4",
          ".ren-carousel-arrow",
          ".ren-carousel-counter",
          ".ren-carousel-counter-current",
          ".ren-carousel-dot",
          ".ren-carousel-dots",
          ".ren-carousel-fade",
          ".ren-carousel-full",
          ".ren-carousel-next",
          ".ren-carousel-no-arrows",
          ".ren-carousel-no-counter",
          ".ren-carousel-no-dots",
          ".ren-carousel-prev",
          ".ren-carousel-slide",
          ".ren-carousel-viewport"
        ],
        "tokens": [
          "--color-accent",
          "--color-border",
          "--color-fill",
          "--color-text",
          "--color-text-muted",
          "--duration-enter",
          "--duration-normal",
          "--ease-enter",
          "--ease-out",
          "--radius-full",
          "--radius-lg",
          "--ren-carousel-arrow-size",
          "--ren-carousel-dot-active",
          "--ren-carousel-dot-color",
          "--ren-carousel-dot-size",
          "--ren-carousel-duration",
          "--ren-carousel-easing",
          "--ren-carousel-gap",
          "--ren-carousel-radius",
          "--size-lg",
          "--space-1",
          "--space-2",
          "--space-3",
          "--space-4",
          "--touch-min",
          "--transition-tactile"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-carousel Component Contract\n\nSlide navigation composite for grouped media/content.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-carousel` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-carousel` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Carousel composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"Multiple peer items (images, cards, testimonials) need horizontal cycling with snap behavior.\"\n    - \"Different slides-per-view densities are needed (.ren-carousel-2 / -3 / -4 / -full).\"\n    - \"Optional autoplay with visible progress indicator is desired (data-autoplay + data-autoplay-duration).\"\n    - \"Touch swipe + keyboard arrow navigation are required.\"\n    - \"You want a fade transition variant via .ren-carousel-fade in addition to scroll-snap.\"\n  avoidWhen:\n    - \"Content is editorial reading order — use a vertical stack instead (carousels hide content).\"\n    - \"The interaction is a tab switch with persistent panels — use ren-tabs.\"\n    - \"Items are step-by-step in a process — use ren-stepper / ren-wizard.\"\n    - \"There is only one or two items — render them inline.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-carousel/ren-carousel.css\"\n  js:\n    - \"rends/components/composites/ren-carousel/ren-carousel.js\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"JS handles arrow generation, dot pagination, autoplay timer, and aria-current bookkeeping; CSS-only fallback offers scroll-snap + the native ::scroll-button() pseudos when supported.\"\n    - \"Add data-autoplay to enable autoplay; pair with data-autoplay-duration=\\\"5000\\\" (ms) for the progress bar.\"\n\nrequiredMarkup:\n  - \"<ren-carousel> wraps a single <div class=\\\"ren-carousel-viewport\\\"> which contains <div class=\\\"ren-carousel-slide\\\"> children.\"\n  - \"Use .ren-carousel-2 / -3 / -4 / -full on the host to declare slides-per-view; do not set flex-basis inline.\"\n  - \"Arrow buttons use .ren-carousel-prev / .ren-carousel-next; the JS injects them when needed, do not author chevron glyphs by hand.\"\n  - \"Dot list is <ul class=\\\"ren-carousel-dots\\\"> with <li><button class=\\\"ren-carousel-dot\\\" aria-current=\\\"true|false\\\"></button></li> items.\"\n  - \"Each slide carries aria-current=\\\"true\\\" only when active; the component manages this attribute.\"\n\nforbiddenPatterns:\n  - \"Replacing slides with arbitrary <div role=\\\"group\\\"> without scroll-snap-align: start — breaks snap behavior.\"\n  - \"Hardcoded translateX(-N%) transforms to advance slides; use scroll-snap or the fade variant.\"\n  - \"Adding an autoplay timer in consumer code instead of using data-autoplay + data-autoplay-duration.\"\n  - \"Hiding arrows by setting display: none on .ren-carousel-prev/.ren-carousel-next — use the .ren-carousel-no-arrows variant.\"\n  - \"Inline width: 50% on a slide to override slides-per-view — switch to the correct .ren-carousel-N class.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-carousel-arrow-size, --ren-carousel-dot-active, --ren-carousel-dot-color, --ren-carousel-dot-size, --ren-carousel-duration, --ren-carousel-easing, --ren-carousel-gap, --ren-carousel-radius.\"\n    - \"Semantic tokens: --color-accent, --color-border, --color-text, --color-text-muted.\"\n    - \"Layout / motion tokens: --space-*, --radius-full, --touch-min, --duration-enter, --ease-enter, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides (the default rgba arrow scrim lives inside the component; do not duplicate it).\"\n    - \"Hardcoded dot sizes / colors; tune --ren-carousel-dot-size / --ren-carousel-dot-active.\"\n\naccessibility:\n  required:\n    - \"Wrap the carousel with role=\\\"region\\\" (or role=\\\"group\\\") and an accessible name so screen readers can label the collection.\"\n    - \"Active slide / active dot use aria-current=\\\"true\\\"; never communicate the active state through color alone.\"\n    - \"Keyboard: Left/Right arrows traverse slides; arrow buttons and dots are real <button> elements with :focus-visible rings.\"\n    - \"Touch targets on .ren-carousel-prev/.ren-carousel-next stay ≥ 44px (min-width / min-height enforced in CSS).\"\n    - \"Autoplay must pause on hover / focus / data-autoplay removal; aria-busy=\\\"true\\\" disables interaction during transitions.\"\n    - \"Respect prefers-reduced-motion: scroll-behavior becomes auto and the autoplay progress bar shows the final frame instead of animating.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-carousel/ren-carousel.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-carousel/ren-carousel.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-carousel aria-label=\"Featured articles\"><div class=\"ren-carousel-viewport\"><div class=\"ren-carousel-slide\">First article</div><div class=\"ren-carousel-slide\">Second article</div></div></ren-carousel>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-carousel`\n- `.ren-carousel-2`\n- `.ren-carousel-3`\n- `.ren-carousel-4`\n- `.ren-carousel-counter`\n- `.ren-carousel-counter-current`\n- `.ren-carousel-dot`\n- `.ren-carousel-dots`\n- `.ren-carousel-fade`\n- `.ren-carousel-full`\n- `.ren-carousel-next`\n- `.ren-carousel-no-arrows`\n- `.ren-carousel-no-counter`\n- `.ren-carousel-no-dots`\n- `.ren-carousel-prev`\n- `.ren-carousel-slide`\n- `.ren-carousel-viewport`\n\n## States And Attributes\n\n- `[aria-busy]`\n- `[aria-current]`\n- `[data-at-end]`\n- `[data-at-start]`\n- `[data-autoplay-duration]`\n- `[data-autoplay]`\n- `:active`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-carousel-arrow-size`\n- `--ren-carousel-dot-active`\n- `--ren-carousel-dot-color`\n- `--ren-carousel-dot-size`\n- `--ren-carousel-duration`\n- `--ren-carousel-easing`\n- `--ren-carousel-gap`\n- `--ren-carousel-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-carousel/ren-carousel.css`\n- `components/composites/ren-carousel/ren-carousel.js`\n- `docs/components/ren-carousel.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ═══════════════════════════════════════════════════════════════════════════\n   RenDS — <ren-carousel> Web Component\n   ═══════════════════════════════════════════════════════════════════════════\n   Accessible carousel/slider with scroll-snap, keyboard nav, autoplay, and dots.\n\n   Features:\n   - CSS scroll-snap for performant sliding\n   - Configurable slides-per-view (1, 2, 3, 4)\n   - Fade or slide transitions\n   - Touch-friendly swipe support\n   - Keyboard navigation (Arrow keys)\n   - Optional autoplay with progress indicator\n   - Responsive via container queries\n   - Respects prefers-reduced-motion\n   ═════════════════════════════════════════════════════════════════════════ */\n\n/* ═══ CAROUSEL OUTER CONTAINER ═══ */\n.ren-carousel {\n  container-type: inline-size;\n  container-name: ren-carousel;\n  position: relative;\n  width: 100%;\n  height: auto;\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-4);\n}\n\n/* ═══ VIEWPORT (SCROLL CONTAINER) ═══ */\n.ren-carousel-viewport {\n  position: relative;\n  width: 100%;\n  height: auto;\n  overflow-x: auto;\n  overflow-y: hidden;\n  scroll-behavior: smooth;\n  scroll-snap-type: x mandatory;\n  scrollbar-width: none; /* Firefox */\n  -ms-overflow-style: none; /* IE and Edge */\n  touch-action: pan-x pinch-zoom; /* Prevent vertical scroll interference during horizontal swipe */\n}\n\n/* Hide scrollbar in Chrome, Safari, and Opera */\n.ren-carousel-viewport::-webkit-scrollbar {\n  display: none;\n}\n\n/* Container for slides with flexbox */\n.ren-carousel-viewport {\n  display: flex;\n  flex-wrap: nowrap;\n}\n\n/* ═══ INDIVIDUAL SLIDES ═══ */\n.ren-carousel-slide {\n  flex: 0 0 100%;\n  min-width: 100%;\n  scroll-snap-align: start;\n  scroll-snap-stop: always;\n  height: auto;\n}\n\n/* Ensure slide content can be sized */\n.ren-carousel-slide > * {\n  display: block;\n  width: 100%;\n  height: auto;\n}\n\n/* ═══ SLIDES-PER-VIEW VARIANTS ═══ */\n\n/* 2 slides visible (50% each) */\n.ren-carousel-2 .ren-carousel-slide {\n  flex: 0 0 50%;\n  min-width: 50%;\n}\n\n/* 3 slides visible (33.33% each) */\n.ren-carousel-3 .ren-carousel-slide {\n  flex: 0 0 calc(100% / 3);\n  min-width: calc(100% / 3);\n}\n\n/* 4 slides visible (25% each) */\n.ren-carousel-4 .ren-carousel-slide {\n  flex: 0 0 25%;\n  min-width: 25%;\n}\n\n/* Responsive: stack to 1 or 2 on narrow screens via container queries */\n@supports (container-type: inline-size) {\n  @container (max-width: 640px) {\n    .ren-carousel-2 .ren-carousel-slide,\n    .ren-carousel-3 .ren-carousel-slide,\n    .ren-carousel-4 .ren-carousel-slide {\n      flex: 0 0 100%;\n      min-width: 100%;\n    }\n  }\n\n  @container (max-width: 1024px) and (min-width: 641px) {\n    .ren-carousel-3 .ren-carousel-slide,\n    .ren-carousel-4 .ren-carousel-slide {\n      flex: 0 0 50%;\n      min-width: 50%;\n    }\n  }\n}\n\n/* ═══ FADE TRANSITION VARIANT ═══ */\n.ren-carousel-fade {\n  position: relative;\n}\n\n.ren-carousel-fade .ren-carousel-viewport {\n  scroll-behavior: auto;\n}\n\n.ren-carousel-fade .ren-carousel-slide {\n  position: absolute;\n  left: 0;\n  top: 0;\n  width: 100%;\n  opacity: 0;\n  transition: opacity var(--duration-enter) var(--ease-enter);\n  pointer-events: none;\n}\n\n.ren-carousel-fade .ren-carousel-slide[aria-current=\"true\"] {\n  position: relative;\n  opacity: 1;\n  pointer-events: auto;\n}\n\n/* ═══ NAVIGATION ARROWS ═══ */\n.ren-carousel-prev,\n.ren-carousel-next {\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n  z-index: 10;\n\n  width: var(--touch-min);\n  height: var(--touch-min);\n  min-width: 44px;\n  min-height: 44px;\n\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  background-color: rgb(0, 0, 0, 0.4);\n  border: none;\n  border-radius: var(--radius-full);\n  cursor: pointer;\n  color: white;\n  font-size: 20px;\n  transition: var(--transition-tactile);\n}\n\n/* Arrow positioning */\n.ren-carousel-prev {\n  left: var(--space-3);\n}\n\n.ren-carousel-next {\n  right: var(--space-3);\n}\n\n/* Arrow chevron using ::before pseudo-element */\n.ren-carousel-prev::before {\n  content: '‹';\n  font-size: 24px;\n  font-weight: bold;\n  line-height: 1;\n}\n\n.ren-carousel-next::before {\n  content: '›';\n  font-size: 24px;\n  font-weight: bold;\n  line-height: 1;\n}\n\n/* Hover state — increase opacity */\n.ren-carousel-prev:hover:not(:disabled),\n.ren-carousel-next:hover:not(:disabled) {\n  background-color: rgb(0, 0, 0, 0.7);\n}\n\n/* Active/focus state */\n.ren-carousel-prev:focus-visible,\n.ren-carousel-next:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: 2px;\n}\n\n/* Hidden arrows at boundaries */\n.ren-carousel[data-at-start] .ren-carousel-prev,\n.ren-carousel[data-at-end] .ren-carousel-next {\n  opacity: 0;\n  pointer-events: none;\n}\n\n/* Disabled state */\n.ren-carousel-prev:disabled,\n.ren-carousel-next:disabled {\n  opacity: 0.4;\n  cursor: not-allowed;\n}\n\n/* ═══ PROGRESSIVE ENHANCEMENT: CSS-ONLY SCROLL BUTTONS ═══\n   Modern browsers support ::scroll-button() pseudo-elements for native\n   scroll container navigation without JavaScript.\n   When supported, these replace the JS-generated arrow buttons. */\n\n@supports selector(::scroll-button(inline-start)) {\n  .ren-carousel-viewport {\n    /* Enable native scroll buttons */\n    overflow-x: auto;\n  }\n\n  .ren-carousel-viewport::scroll-button(inline-start),\n  .ren-carousel-viewport::scroll-button(inline-end) {\n    position: absolute;\n    top: 50%;\n    transform: translateY(-50%);\n    z-index: 10;\n\n    width: var(--touch-min, 44px);\n    height: var(--touch-min, 44px);\n    min-width: 44px;\n    min-height: 44px;\n\n    display: flex;\n    align-items: center;\n    justify-content: center;\n\n    background-color: rgb(0, 0, 0, 0.4);\n    border: none;\n    border-radius: var(--radius-full, 9999px);\n    cursor: pointer;\n    color: white;\n\n    transition: var(--transition-tactile);\n  }\n\n  .ren-carousel-viewport::scroll-button(inline-start) {\n    content: '‹';\n    left: var(--space-3, 0.75rem);\n  }\n\n  .ren-carousel-viewport::scroll-button(inline-end) {\n    content: '›';\n    right: var(--space-3, 0.75rem);\n  }\n\n  .ren-carousel-viewport::scroll-button(inline-start):hover,\n  .ren-carousel-viewport::scroll-button(inline-end):hover {\n    background-color: rgb(0, 0, 0, 0.7);\n  }\n\n  /* Hide JS-generated arrows when native scroll buttons are available */\n  .ren-carousel .ren-carousel-prev,\n  .ren-carousel .ren-carousel-next {\n    display: none;\n  }\n}\n\n/* ═══ PAGINATION DOTS ═══ */\n.ren-carousel-dots {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--space-2);\n  margin-top: var(--space-3);\n  padding: 0;\n  list-style: none;\n}\n\n.ren-carousel-dot {\n  width: 8px;\n  height: 8px;\n  border-radius: var(--radius-full);\n  background-color: var(--color-border);\n  border: none;\n  cursor: pointer;\n  transition: var(--transition-tactile);\n  padding: 0;\n  margin: 0;\n}\n\n/* Active dot */\n.ren-carousel-dot[aria-current=\"true\"] {\n  background-color: var(--color-accent);\n  transform: scale(1.25);\n}\n\n/* Hover state */\n.ren-carousel-dot:hover:not([aria-current=\"true\"]) {\n  background-color: var(--color-border);\n  opacity: 0.7;\n}\n\n/* Focus visible */\n.ren-carousel-dot:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: 2px;\n  border-radius: var(--radius-full);\n}\n\n/* ═══ AUTOPLAY PROGRESS BAR ═══ */\n.ren-carousel-dot[aria-current=\"true\"]::after {\n  content: '';\n  position: absolute;\n  bottom: -4px;\n  left: 50%;\n  transform: translateX(-50%);\n  width: 6px;\n  height: 2px;\n  background-color: var(--color-accent);\n  border-radius: var(--radius-full);\n  opacity: 0;\n}\n\n/* Show progress bar when autoplay is active */\n.ren-carousel[data-autoplay] .ren-carousel-dot[aria-current=\"true\"]::after {\n  opacity: 1;\n  animation: carousel-progress linear forwards;\n}\n\n@keyframes carousel-progress {\n  from {\n    width: 0;\n  }\n  to {\n    width: 6px;\n  }\n}\n\n/* Dynamic progress bar timing — controlled by JS */\n.ren-carousel[data-autoplay-duration] .ren-carousel-dot[aria-current=\"true\"]::after {\n  animation-duration: inherit;\n}\n\n/* ═══ CAROUSEL COUNTER ═══ */\n.ren-carousel-counter {\n  text-align: center;\n  font-size: 14px;\n  color: var(--color-text-muted);\n  margin-top: var(--space-2);\n  font-variant-numeric: tabular-nums;\n}\n\n.ren-carousel-counter-current {\n  font-weight: 600;\n  color: var(--color-text);\n}\n\n/* ═══ ACCESSIBILITY & ANIMATIONS ═══ */\n\n/* Respect reduced motion preference */\n@media (prefers-reduced-motion: reduce) {\n  .ren-carousel-viewport {\n    scroll-behavior: auto;\n  }\n\n  .ren-carousel-slide,\n  .ren-carousel-dot,\n  .ren-carousel-prev,\n  .ren-carousel-next {\n    transition: none;\n    animation: none;\n  }\n\n  .ren-carousel[data-autoplay] .ren-carousel-dot[aria-current=\"true\"]::after {\n    animation: none;\n    width: 6px;\n    opacity: 1;\n  }\n}\n\n/* ═══ FOCUS VISIBLE FOR KEYBOARD NAVIGATION ═══ */\n.ren-carousel-slide:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: 2px;\n}\n\n/* ═══ RESPONSIVE ADJUSTMENTS (Container Queries) ═══ */\n\n/* Compact carousel */\n@container ren-carousel (max-width: 480px) {\n  .ren-carousel {\n    gap: var(--space-2);\n  }\n\n  .ren-carousel-prev,\n  .ren-carousel-next {\n    width: 40px;\n    height: 40px;\n    font-size: 16px;\n  }\n\n  .ren-carousel-prev {\n    left: var(--space-2);\n  }\n\n  .ren-carousel-next {\n    right: var(--space-2);\n  }\n\n  .ren-carousel-dots {\n    gap: var(--space-1);\n  }\n\n  .ren-carousel-dot {\n    width: 6px;\n    height: 6px;\n  }\n\n  .ren-carousel-dot[aria-current=\"true\"] {\n    transform: scale(1.1);\n  }\n}\n\n/* Wide carousel — hover reveal arrows */\n@container ren-carousel (min-width: 768px) {\n  .ren-carousel-prev,\n  .ren-carousel-next {\n    opacity: 0.6;\n  }\n\n  .ren-carousel:hover .ren-carousel-prev:not(:disabled),\n  .ren-carousel:hover .ren-carousel-next:not(:disabled) {\n    opacity: 1;\n  }\n}\n\n/* ═══ UTILITY CLASSES ═══ */\n\n/* Carousel without dots */\n.ren-carousel-no-dots .ren-carousel-dots {\n  display: none;\n}\n\n/* Carousel without arrows */\n.ren-carousel-no-arrows .ren-carousel-prev,\n.ren-carousel-no-arrows .ren-carousel-next {\n  display: none;\n}\n\n/* Carousel without counter */\n.ren-carousel-no-counter .ren-carousel-counter {\n  display: none;\n}\n\n/* Full width slides (1 per view) */\n.ren-carousel-full .ren-carousel-slide {\n  flex: 0 0 100%;\n  min-width: 100%;\n}\n\n/* Draggable appearance */\n.ren-carousel-viewport {\n  user-select: none;\n  -webkit-user-select: none;\n}\n\n.ren-carousel-slide {\n  user-select: none;\n  -webkit-user-select: none;\n}\n\n/* Touch feedback */\n.ren-carousel-slide {\n  cursor: grab;\n}\n\n.ren-carousel-slide:active {\n  cursor: grabbing;\n}\n\n/* ═══ LOADING STATE ═══ */\n.ren-carousel[aria-busy=\"true\"] {\n  opacity: 0.6;\n  pointer-events: none;\n}\n\n/* ═══ SEMANTIC HTML ADJUSTMENTS ═══ */\n.ren-carousel[role=\"group\"],\n.ren-carousel[role=\"region\"] {\n  /* Already defined above, this is for clarity */\n}\n\n/* Ensure images in slides maintain aspect ratio */\n.ren-carousel-slide img {\n  width: 100%;\n  height: auto;\n  display: block;\n  object-fit: cover;\n}\n\n/* Video in carousel */\n.ren-carousel-slide video {\n  width: 100%;\n  height: auto;\n  display: block;\n}\n/* Appearance API bridge */\n.ren-carousel { gap: var(--ren-carousel-gap, var(--space-4)); border-radius: var(--ren-carousel-radius, var(--radius-lg)); transition-duration: var(--ren-carousel-duration, var(--duration-normal)); transition-timing-function: var(--ren-carousel-easing, var(--ease-out)); }\n.ren-carousel .ren-carousel-arrow { inline-size: var(--ren-carousel-arrow-size, var(--size-lg)); block-size: var(--ren-carousel-arrow-size, var(--size-lg)); }\n.ren-carousel .ren-carousel-dot { inline-size: var(--ren-carousel-dot-size, 8px); block-size: var(--ren-carousel-dot-size, 8px); background: var(--ren-carousel-dot-color, var(--color-fill)); }\n.ren-carousel .ren-carousel-dot[aria-current=\"true\"], .ren-carousel .ren-carousel-dot.is-active { background: var(--ren-carousel-dot-active, var(--color-accent)); }\n\n\n/**\n * RenDS — <ren-carousel> Web Component\n * ═════════════════════════════════════════════════════════════════════════\n * Accessible carousel/slider with native scroll-snap, keyboard navigation,\n * autoplay, pagination dots, and optional next/prev arrows.\n *\n * Features:\n * - Light DOM (no Shadow DOM)\n * - CSS scroll-snap for performant horizontal scrolling\n * - Keyboard navigation (Arrow Left/Right)\n * - Touch swipe support (native scroll-snap)\n * - Auto-generated pagination dots\n * - Optional next/prev navigation arrows\n * - Autoplay with configurable interval\n * - Loop mode (infinite carousel)\n * - Configurable slides-per-view (1, 2, 3, 4)\n * - Fade transition variant\n * - Custom events (ren-slide-change)\n * - Respects prefers-reduced-motion\n *\n * Attributes:\n *   autoplay: milliseconds interval for autoplay (e.g., 5000)\n *   loop: enable infinite looping\n *   slides-per-view: number of slides visible (default: 1)\n *   fade: enable fade transition instead of slide\n *\n * Methods:\n *   next() — go to next slide\n *   prev() — go to previous slide\n *   goTo(index) — jump to specific slide\n *   pause() — pause autoplay\n *   resume() — resume autoplay\n *\n * Events:\n *   ren-slide-change: detail = { index, total, id }\n *\n * Markup:\n *   <ren-carousel autoplay=\"5000\" loop slides-per-view=\"1\">\n *     <div class=\"ren-carousel-viewport\">\n *       <div class=\"ren-carousel-slide\">\n *         <img src=\"slide1.jpg\" alt=\"Slide 1\">\n *       </div>\n *       <div class=\"ren-carousel-slide\">\n *         <img src=\"slide2.jpg\" alt=\"Slide 2\">\n *       </div>\n *       <!-- Dots and arrows auto-generated by JS -->\n *     </div>\n *   </ren-carousel>\n * ═════════════════════════════════════════════════════════════════════════\n */\n\nlet nextId = 0;\n\nexport class RenCarousel extends HTMLElement {\n  static get observedAttributes() {\n    return ['autoplay', 'loop', 'slides-per-view', 'fade'];\n  }\n\n  constructor() {\n    super();\n    this.id = this.id || `ren-carousel-${++nextId}`;\n\n    this._viewport = null;\n    this._slides = [];\n    this._dotsContainer = null;\n    this._dots = [];\n    this._prevButton = null;\n    this._nextButton = null;\n    this._counterElement = null;\n\n    this._currentIndex = 0;\n    this._totalSlides = 0;\n    this._autoplayInterval = null;\n    this._autoplayDuration = null;\n    this._isAutoplayActive = false;\n    this._scrollTimeout = null;\n    this._intersectionObserver = null;\n    this._contentObserver = null;\n    this._refreshQueued = false;\n    this._nextSlideId = 0;\n\n    this._autoplayMs = 0;\n    this._shouldLoop = false;\n    this._slidePerView = 1;\n    this._isFade = false;\n    this._reducedMotionQuery = null;\n    this._handleReducedMotionChange = null;\n  }\n\n  /* ═══ LIFECYCLE ═══ */\n\n  connectedCallback() {\n    this._parseAttributes();\n    this._initialize();\n    this._observeContent();\n    this._attachReducedMotionListener();\n    this._attachEventListeners();\n    if (this._autoplayMs > 0) {\n      this._startAutoplay();\n    }\n  }\n\n  disconnectedCallback() {\n    this._stopAutoplay();\n    if (this._intersectionObserver) {\n      this._intersectionObserver.disconnect();\n    }\n    this._contentObserver?.disconnect();\n    this._contentObserver = null;\n    this._detachReducedMotionListener();\n    this._detachEventListeners();\n  }\n\n  _observeContent() {\n    if (typeof MutationObserver === 'undefined' || this._contentObserver) return;\n    this._contentObserver = new MutationObserver((mutations) => {\n      // Ignore mutations caused by our generated controls; refresh only when\n      // slide/viewport content actually changes.\n      if (!mutations.some((m) => [...m.addedNodes, ...m.removedNodes].some(\n        (node) => node.nodeType === Node.ELEMENT_NODE &&\n          (node.classList?.contains('ren-carousel-slide') ||\n            node.classList?.contains('ren-carousel-viewport'))\n      ))) return;\n      if (this._refreshQueued) return;\n      this._refreshQueued = true;\n      queueMicrotask(() => {\n        this._refreshQueued = false;\n        if (!this.isConnected) return;\n        this._contentObserver?.disconnect();\n        this._initialize();\n        this._contentObserver?.observe(this, { childList: true, subtree: true });\n      });\n    });\n    this._contentObserver.observe(this, { childList: true, subtree: true });\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (!this.isConnected) return;\n\n    switch (name) {\n      case 'autoplay':\n        this._autoplayMs = newValue ? parseInt(newValue, 10) : 0;\n        if (this._autoplayMs > 0) {\n          this._startAutoplay();\n        } else {\n          this._stopAutoplay();\n        }\n        break;\n      case 'loop':\n        this._shouldLoop = this.hasAttribute('loop');\n        this._updateArrowVisibility();\n        break;\n      case 'slides-per-view':\n        this._slidePerView = newValue ? parseInt(newValue, 10) : 1;\n        this._applySlidePerViewClass();\n        break;\n      case 'fade':\n        this._isFade = this.hasAttribute('fade');\n        this._updateFadeVariant();\n        this._setupIntersectionObserver();\n        break;\n    }\n  }\n\n  /* ═══ INITIALIZATION ═══ */\n\n  _parseAttributes() {\n    this._autoplayMs = this.hasAttribute('autoplay')\n      ? parseInt(this.getAttribute('autoplay'), 10)\n      : 0;\n    this._shouldLoop = this.hasAttribute('loop');\n    this._slidePerView = this.hasAttribute('slides-per-view')\n      ? parseInt(this.getAttribute('slides-per-view'), 10)\n      : 1;\n    this._isFade = this.hasAttribute('fade');\n  }\n\n  _initialize() {\n    // Rebuilds must tear down observers and generated navigation first. This\n    // keeps removal-to-empty and mode changes from retaining stale state.\n    this._intersectionObserver?.disconnect();\n    this._intersectionObserver = null;\n    this.querySelector('.ren-carousel-dots')?.remove();\n    this.querySelectorAll('.ren-carousel-prev, .ren-carousel-next').forEach((el) => el.remove());\n    this.querySelector('.ren-carousel-counter')?.remove();\n    // Set up ARIA attributes\n    this.setAttribute('role', 'group');\n    this.setAttribute('aria-roledescription', 'carousel');\n    if (!this.getAttribute('aria-label')) {\n      this.setAttribute('aria-label', 'Image carousel');\n    }\n\n    // Find viewport\n    this._viewport = this.querySelector('.ren-carousel-viewport');\n    if (!this._viewport) {\n      console.warn('RenCarousel: No viewport found');\n      return;\n    }\n\n    // Add scroll-snap to viewport if not already present\n    if (!this._viewport.classList.contains('ren-carousel-viewport')) {\n      this._viewport.classList.add('ren-carousel-viewport');\n    }\n\n    // Collect slides\n    this._slides = Array.from(\n      this.querySelectorAll('.ren-carousel-slide')\n    );\n\n    if (this._slides.length === 0) {\n      this._totalSlides = 0;\n      this._currentIndex = 0;\n      this._dots = [];\n      this._dotsContainer = null;\n      this._prevButton = null;\n      this._nextButton = null;\n      this._counterElement = null;\n      console.warn('RenCarousel: No slides found');\n      return;\n    }\n\n    this._totalSlides = this._slides.length;\n    this._currentIndex = Math.max(0, Math.min(this._currentIndex, this._totalSlides - 1));\n\n    // Set up slides with ARIA attributes\n    this._slides.forEach((slide, index) => {\n      if (!slide.id) {\n        let generatedId;\n        do {\n          generatedId = `${this.id}-slide-${++this._nextSlideId}`;\n        } while (\n          this._slides.some((candidate) => candidate !== slide && candidate.id === generatedId) ||\n          this.ownerDocument?.getElementById(generatedId)\n        );\n        slide.id = generatedId;\n      }\n      slide.setAttribute('role', 'group');\n      slide.setAttribute('aria-roledescription', 'slide');\n      slide.setAttribute('aria-label', `Slide ${index + 1} of ${this._totalSlides}`);\n      if (index === this._currentIndex) {\n        slide.setAttribute('aria-current', 'true');\n      } else {\n        slide.removeAttribute('aria-current');\n      }\n      slide.tabIndex = 0;\n    });\n\n    // Create navigation elements\n    this._createDots();\n    this._createArrows();\n    this._createCounter();\n\n    // Apply variant classes\n    this._applySlidePerViewClass();\n    this._updateFadeVariant();\n\n    // Set up Intersection Observer to track active slide\n    this._setupIntersectionObserver();\n\n    // Initial state\n    this._updateArrowVisibility();\n    this._updateDotState();\n    this._updateCounter();\n  }\n\n  _createDots() {\n    // Remove existing dots if any\n    const existing = this.querySelector('.ren-carousel-dots');\n    if (existing) existing.remove();\n\n    // Create dots container\n    this._dotsContainer = document.createElement('ul');\n    this._dotsContainer.className = 'ren-carousel-dots';\n    this._dotsContainer.setAttribute('aria-label', 'Carousel pagination');\n\n    // Create individual dots\n    this._dots = this._slides.map((_, index) => {\n      const dot = document.createElement('button');\n      dot.className = 'ren-carousel-dot';\n      dot.setAttribute('type', 'button');\n      dot.setAttribute('aria-label', `Go to slide ${index + 1}`);\n      dot.setAttribute('aria-controls', this._slides[index].id);\n      if (index === 0) {\n        dot.setAttribute('aria-current', 'true');\n      }\n\n      dot.addEventListener('click', () => this.goTo(index));\n      const li = document.createElement('li');\n      li.appendChild(dot);\n      this._dotsContainer.appendChild(li);\n\n      return dot;\n    });\n\n    this.appendChild(this._dotsContainer);\n  }\n\n  _createArrows() {\n    // Remove existing arrows if any\n    const existing = this.querySelectorAll('.ren-carousel-prev, .ren-carousel-next');\n    existing.forEach(el => el.remove());\n\n    // Create previous button\n    this._prevButton = document.createElement('button');\n    this._prevButton.className = 'ren-carousel-prev';\n    this._prevButton.setAttribute('type', 'button');\n    this._prevButton.setAttribute('aria-label', 'Previous slide');\n    this._prevButton.addEventListener('click', () => this.prev());\n\n    // Create next button\n    this._nextButton = document.createElement('button');\n    this._nextButton.className = 'ren-carousel-next';\n    this._nextButton.setAttribute('type', 'button');\n    this._nextButton.setAttribute('aria-label', 'Next slide');\n    this._nextButton.addEventListener('click', () => this.next());\n\n    // Insert after viewport\n    this._viewport.parentNode.insertBefore(this._prevButton, this._viewport.nextSibling);\n    this._viewport.parentNode.insertBefore(this._nextButton, this._viewport.nextSibling.nextSibling);\n  }\n\n  _createCounter() {\n    // Remove existing counter if any\n    const existing = this.querySelector('.ren-carousel-counter');\n    if (existing) existing.remove();\n\n    this._counterElement = document.createElement('div');\n    this._counterElement.className = 'ren-carousel-counter';\n    this._counterElement.setAttribute('aria-atomic', 'true');\n    this._syncLiveRegion();\n\n    this.appendChild(this._counterElement);\n  }\n\n  _applySlidePerViewClass() {\n    // Remove all slides-per-view classes\n    this.classList.remove('ren-carousel-1', 'ren-carousel-2', 'ren-carousel-3', 'ren-carousel-4');\n\n    // Add the appropriate class\n    if (this._slidePerView >= 1 && this._slidePerView <= 4) {\n      this.classList.add(`ren-carousel-${this._slidePerView}`);\n    }\n  }\n\n  _updateFadeVariant() {\n    if (this._isFade) {\n      this.classList.add('ren-carousel-fade');\n    } else {\n      this.classList.remove('ren-carousel-fade');\n    }\n  }\n\n  _setupIntersectionObserver() {\n    if (this._intersectionObserver) {\n      this._intersectionObserver.disconnect();\n      this._intersectionObserver = null;\n    }\n    if (this._isFade) {\n      // For fade mode, we'll handle active state differently\n      return;\n    }\n\n    const options = {\n      root: this._viewport,\n      threshold: 0.5,\n    };\n\n    this._intersectionObserver = new IntersectionObserver((entries) => {\n      entries.forEach((entry) => {\n        if (entry.isIntersecting) {\n          const index = this._slides.indexOf(entry.target);\n          if (index !== -1) {\n            this._setCurrentIndex(index);\n          }\n        }\n      });\n    }, options);\n\n    this._slides.forEach((slide) => {\n      this._intersectionObserver.observe(slide);\n    });\n  }\n\n  /* ═══ NAVIGATION METHODS ═══ */\n\n  next() {\n    if (this._currentIndex < this._totalSlides - 1) {\n      this.goTo(this._currentIndex + 1);\n    } else if (this._shouldLoop) {\n      this.goTo(0);\n    }\n  }\n\n  prev() {\n    if (this._currentIndex > 0) {\n      this.goTo(this._currentIndex - 1);\n    } else if (this._shouldLoop) {\n      this.goTo(this._totalSlides - 1);\n    }\n  }\n\n  goTo(index) {\n    // Clamp index\n    index = Math.max(0, Math.min(index, this._totalSlides - 1));\n\n    // Scroll to slide\n    const slide = this._slides[index];\n    if (slide) {\n      slide.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });\n      this._setCurrentIndex(index);\n\n      // Reset autoplay timer when navigating\n      if (this._autoplayMs > 0) {\n        this._stopAutoplay();\n        this._startAutoplay();\n      }\n    }\n  }\n\n  _setCurrentIndex(index) {\n    if (this._currentIndex === index) return;\n\n    // Update slides ARIA\n    this._slides.forEach((slide, i) => {\n      if (i === index) {\n        slide.setAttribute('aria-current', 'true');\n      } else {\n        slide.removeAttribute('aria-current');\n      }\n    });\n\n    this._currentIndex = index;\n    this._updateDotState();\n    this._updateArrowVisibility();\n    this._updateCounter();\n    this._dispatchChangeEvent();\n  }\n\n  /* ═══ STATE UPDATES ═══ */\n\n  _updateDotState() {\n    this._dots.forEach((dot, index) => {\n      if (index === this._currentIndex) {\n        dot.setAttribute('aria-current', 'true');\n      } else {\n        dot.removeAttribute('aria-current');\n      }\n    });\n  }\n\n  _updateArrowVisibility() {\n    if (this._prevButton && this._nextButton) {\n      const atStart = this._currentIndex === 0;\n      const atEnd = this._currentIndex === this._totalSlides - 1;\n\n      if (this._shouldLoop) {\n        // When looping, arrows are always visible\n        this.removeAttribute('data-at-start');\n        this.removeAttribute('data-at-end');\n        this._prevButton.removeAttribute('disabled');\n        this._nextButton.removeAttribute('disabled');\n      } else {\n        // Without loop, hide/disable arrows at boundaries\n        if (atStart) {\n          this.setAttribute('data-at-start', '');\n          this._prevButton.setAttribute('disabled', '');\n        } else {\n          this.removeAttribute('data-at-start');\n          this._prevButton.removeAttribute('disabled');\n        }\n\n        if (atEnd) {\n          this.setAttribute('data-at-end', '');\n          this._nextButton.setAttribute('disabled', '');\n        } else {\n          this.removeAttribute('data-at-end');\n          this._nextButton.removeAttribute('disabled');\n        }\n      }\n    }\n  }\n\n  _updateCounter() {\n    if (this._counterElement) {\n      this._counterElement.innerHTML = `\n        <span class=\"ren-carousel-counter-current\">${this._currentIndex + 1}</span>\n        <span> / ${this._totalSlides}</span>\n      `;\n    }\n  }\n\n  _syncLiveRegion() {\n    this._counterElement?.setAttribute('aria-live', this._isAutoplayActive ? 'off' : 'polite');\n  }\n\n  /* ═══ AUTOPLAY ═══ */\n\n  _prefersReducedMotion() {\n    return window.matchMedia?.('(prefers-reduced-motion: reduce)').matches === true;\n  }\n\n  _attachReducedMotionListener() {\n    this._reducedMotionQuery = window.matchMedia?.('(prefers-reduced-motion: reduce)') ?? null;\n    if (!this._reducedMotionQuery) return;\n\n    this._handleReducedMotionChange = (event) => {\n      if (event.matches) {\n        this._stopAutoplay();\n      } else if (this._autoplayMs > 0) {\n        this._startAutoplay();\n      }\n    };\n\n    this._reducedMotionQuery.addEventListener?.('change', this._handleReducedMotionChange);\n  }\n\n  _detachReducedMotionListener() {\n    if (this._reducedMotionQuery && this._handleReducedMotionChange) {\n      this._reducedMotionQuery.removeEventListener?.('change', this._handleReducedMotionChange);\n    }\n    this._reducedMotionQuery = null;\n    this._handleReducedMotionChange = null;\n  }\n\n  _startAutoplay() {\n    if (this._autoplayInterval || this._autoplayMs <= 0) return;\n    if (this._prefersReducedMotion()) {\n      this._stopAutoplay();\n      return;\n    }\n\n    this._isAutoplayActive = true;\n    this.setAttribute('data-autoplay', '');\n    this.setAttribute('data-autoplay-duration', `${this._autoplayMs}ms`);\n    this._syncLiveRegion();\n\n    // Start autoplay\n    this._autoplayInterval = setInterval(() => {\n      this.next();\n    }, this._autoplayMs);\n  }\n\n  _stopAutoplay() {\n    if (this._autoplayInterval) {\n      clearInterval(this._autoplayInterval);\n      this._autoplayInterval = null;\n    }\n    this._isAutoplayActive = false;\n    this.removeAttribute('data-autoplay');\n    this.removeAttribute('data-autoplay-duration');\n    this._syncLiveRegion();\n  }\n\n  pause() {\n    this._stopAutoplay();\n  }\n\n  resume() {\n    if (this._autoplayMs > 0) {\n      this._startAutoplay();\n    }\n  }\n\n  /* ═══ EVENT LISTENERS ═══ */\n\n  _attachEventListeners() {\n    // Keyboard navigation\n    this._handleKeydown = (e) => this._onKeydown(e);\n    this.addEventListener('keydown', this._handleKeydown);\n\n    // Pause autoplay on hover\n    this._handleMouseEnter = () => {\n      if (this._isAutoplayActive) {\n        this._stopAutoplay();\n      }\n    };\n    this._handleMouseLeave = () => {\n      if (this._autoplayMs > 0) {\n        this._startAutoplay();\n      }\n    };\n    this.addEventListener('mouseenter', this._handleMouseEnter);\n    this.addEventListener('mouseleave', this._handleMouseLeave);\n\n    // Pause autoplay on focus\n    this._handleFocus = () => {\n      if (this._isAutoplayActive) {\n        this._stopAutoplay();\n      }\n    };\n    this._handleBlur = () => {\n      if (this._autoplayMs > 0) {\n        this._startAutoplay();\n      }\n    };\n    this.addEventListener('focus', this._handleFocus, true);\n    this.addEventListener('blur', this._handleBlur, true);\n  }\n\n  _detachEventListeners() {\n    if (this._handleKeydown) {\n      this.removeEventListener('keydown', this._handleKeydown);\n    }\n    if (this._handleMouseEnter) {\n      this.removeEventListener('mouseenter', this._handleMouseEnter);\n    }\n    if (this._handleMouseLeave) {\n      this.removeEventListener('mouseleave', this._handleMouseLeave);\n    }\n    if (this._handleFocus) {\n      this.removeEventListener('focus', this._handleFocus, true);\n    }\n    if (this._handleBlur) {\n      this.removeEventListener('blur', this._handleBlur, true);\n    }\n  }\n\n  _onKeydown(e) {\n    if (e.key === 'ArrowLeft') {\n      e.preventDefault();\n      this.prev();\n    } else if (e.key === 'ArrowRight') {\n      e.preventDefault();\n      this.next();\n    } else if (e.key === ' ' || e.key === 'Enter') {\n      // Allow space/enter on dots\n      if (e.target.classList.contains('ren-carousel-dot')) {\n        e.preventDefault();\n        e.target.click();\n      }\n    }\n  }\n\n  /* ═══ EVENT DISPATCHING ═══ */\n\n  _dispatchChangeEvent() {\n    const event = new CustomEvent('ren-slide-change', {\n      detail: {\n        index: this._currentIndex,\n        total: this._totalSlides,\n        id: this.id,\n      },\n      bubbles: true,\n      composed: true,\n    });\n    this.dispatchEvent(event);\n  }\n}\n\n// Register the component\nif (!customElements.get('ren-carousel')) {\n  customElements.define('ren-carousel', RenCarousel);\n}\n",
      "path": "components/composites/ren-carousel",
      "id": "component:composite:ren-carousel",
      "type": "component",
      "name": "ren-carousel"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-accordion",
          ".ren-collapsible",
          ".ren-collapsible-content",
          ".ren-collapsible-ghost"
        ],
        "tokens": [
          "--body-size",
          "--color-border",
          "--color-fill",
          "--color-focus-ring",
          "--color-surface",
          "--color-text",
          "--color-text-muted",
          "--color-text-secondary",
          "--duration-enter",
          "--duration-normal",
          "--ease-enter",
          "--ease-out",
          "--label-lg-size",
          "--leading-normal",
          "--radius-lg",
          "--radius-md",
          "--ren-collapse-bg",
          "--ren-collapse-border",
          "--ren-collapse-duration",
          "--ren-collapse-easing",
          "--ren-collapse-padding",
          "--ren-collapse-radius",
          "--ren-collapse-trigger-font",
          "--ren-collapse-trigger-weight",
          "--ring-width",
          "--space-2",
          "--space-3",
          "--space-4",
          "--stroke-1",
          "--touch-min",
          "--transition-tactile",
          "--weight-medium",
          "--weight-semibold"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-collapsible Component Contract\n\nExpandable/collapsible region with explicit state.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-collapsible` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-collapsible` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Collapsible composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"A single, standalone expandable section is needed (no sibling exclusivity).\"\n    - \"You want a zero-JS disclosure based on native <details>/<summary>.\"\n    - \"Initial state must be declarable in HTML via the [open] attribute.\"\n    - \"A subtle ghost variant (no border, flush) is desired.\"\n    - \"The content area animates height via CSS interpolate-size (no JS measurement).\"\n  avoidWhen:\n    - \"Multiple grouped disclosures share chrome — use ren-accordion (handles exclusive mode).\"\n    - \"The disclosure floats above content as a popover — use ren-popover.\"\n    - \"The trigger must control a remote panel — use a button + aria-controls on a custom panel.\"\n    - \"The disclosure is a navigation reveal — use ren-sidebar / ren-menu.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-collapsible/ren-collapsible.css\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"No colocated JS — open / close uses the native <details> toggle event; consumers can listen for 'toggle' if needed.\"\n    - \"Add interpolate-size support polyfill only if you require height animation on legacy browsers.\"\n\nrequiredMarkup:\n  - \"Root element is <details class=\\\"ren-collapsible\\\">; do not wrap a <div> with a custom click handler.\"\n  - \"Trigger is the first child <summary>; content lives in a sibling <div class=\\\"ren-collapsible-content\\\">.\"\n  - \"Use the [open] attribute to render expanded on first paint (do not set hidden / display via style).\"\n  - \"Add .ren-collapsible-ghost to the host for the borderless variant; do not invent additional variant classes.\"\n  - \"Keep the chevron decorative — it is rendered via the summary::after pseudo-element.\"\n\nforbiddenPatterns:\n  - \"<div class=\\\"ren-collapsible\\\"><div role=\\\"button\\\">…</div></div> — must be a real <details>/<summary>.\"\n  - \"Custom triangle / chevron inside the summary text content; rely on summary::after rotation.\"\n  - \"Animating with JavaScript-driven max-height; CSS interpolate-size handles auto-to-zero transitions.\"\n  - \"Removing the focus ring (outline: none) without restoring :focus-visible on summary.\"\n  - \"Calling .open = true on a detached <details> outside the DOM — bind it inside .ren-collapsible first.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-collapse-bg, --ren-collapse-border, --ren-collapse-duration, --ren-collapse-easing, --ren-collapse-padding, --ren-collapse-radius, --ren-collapse-trigger-font, --ren-collapse-trigger-weight.\"\n    - \"Semantic tokens: --color-border, --color-text, --color-text-muted, --color-text-secondary, --color-fill, --color-focus-ring.\"\n    - \"Layout / motion tokens: --space-*, --radius-md, --stroke-1, --touch-min, --ring-width, --duration-enter, --ease-enter, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Raw transition values; use --duration-enter / --ease-enter or --ren-collapse-duration / --ren-collapse-easing.\"\n\naccessibility:\n  required:\n    - \"Use the real <details>/<summary> pair so the browser exposes the native disclosure pattern and supports Enter / Space toggle.\"\n    - \"Touch target on the summary stays ≥ var(--touch-min) (44px) — do not shrink below this on touch surfaces.\"\n    - \"Visible :focus-visible ring on summary uses --color-focus-ring and --ring-width; do not remove it.\"\n    - \"Chevron is decorative (::after pseudo) — never expose its rotation to AT via aria-label.\"\n    - \"Communicate open state through the [open] attribute (native semantics) — never rely on color alone.\"\n    - \"Animations respect prefers-reduced-motion (chevron rotation transition is disabled).\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-collapsible/ren-collapsible.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<details class=\"ren-collapsible\"><summary>Advanced settings</summary><div class=\"ren-collapsible-content\">Additional controls</div></details>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-collapsible`\n- `.ren-collapsible-content`\n- `.ren-collapsible-ghost`\n\n## States And Attributes\n\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-collapse-bg`\n- `--ren-collapse-border`\n- `--ren-collapse-duration`\n- `--ren-collapse-easing`\n- `--ren-collapse-padding`\n- `--ren-collapse-radius`\n- `--ren-collapse-trigger-font`\n- `--ren-collapse-trigger-weight`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/composites/ren-collapsible/ren-collapsible.css`\n- `docs/components/ren-collapsible.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Collapsible Component\n   ============================================\n   Expand/collapse content panel.\n   Uses native <details>/<summary> — ZERO JS.\n\n   CSS-only animation with interpolate-size.\n\n   Different from Accordion: standalone, no group\n   exclusivity. For single expand/collapse sections.\n\n   Usage:\n     <details class=\"ren-collapsible\">\n       <summary>Click to expand</summary>\n       <div class=\"ren-collapsible-content\">\n         Hidden content here\n       </div>\n     </details>\n\n   Open by default:\n     <details class=\"ren-collapsible\" open>\n       ...\n     </details>\n   ============================================ */\n\n/* Enable height animation on details */\n.ren-collapsible {\n  interpolate-size: allow-keywords;\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-md);\n  overflow: hidden;\n}\n\n/* Summary (trigger) */\n.ren-collapsible > summary {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--space-2);\n  min-height: var(--touch-min);\n  padding: var(--space-3) var(--space-4);\n  font-size: var(--body-size);\n  font-weight: var(--weight-medium);\n  color: var(--color-text);\n  cursor: pointer;\n  user-select: none;\n  list-style: none; /* Remove default marker */\n  transition: var(--transition-tactile);\n}\n\n/* Remove default marker in WebKit */\n.ren-collapsible > summary::-webkit-details-marker {\n  display: none;\n}\n\n/* Custom chevron */\n.ren-collapsible > summary::after {\n  content: '';\n  width: 0.5rem;\n  height: 0.5rem;\n  border: solid var(--color-text-muted);\n  border-width: 0 2px 2px 0;\n  transform: rotate(45deg);\n  flex-shrink: 0;\n  transition: transform var(--duration-enter) var(--ease-enter);\n}\n\n.ren-collapsible[open] > summary::after {\n  transform: rotate(-135deg);\n}\n\n.ren-collapsible > summary:hover {\n  background-color: var(--color-fill);\n}\n\n.ren-collapsible > summary:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: -2px;\n}\n\n/* Content area */\n.ren-collapsible-content {\n  padding: 0 var(--space-4) var(--space-4);\n  font-size: var(--body-size);\n  color: var(--color-text-secondary);\n  line-height: var(--leading-normal);\n}\n\n/* ─── Ghost variant (no border) ─── */\n.ren-collapsible-ghost {\n  border: none;\n}\n\n.ren-collapsible-ghost > summary {\n  padding-inline: 0;\n}\n\n.ren-collapsible-ghost .ren-collapsible-content {\n  padding-inline: 0;\n}\n\n/* ─── Animate open/close ─── */\n/* Uses CSS content-visibility for animation */\n.ren-collapsible .ren-collapsible-content {\n  content-visibility: visible;\n}\n\n/* Reduced motion — no animation */\n@media (prefers-reduced-motion: reduce) {\n  .ren-collapsible > summary::after {\n    transition: none;\n  }\n}\n/* Appearance API bridge */\n.ren-collapsible, .ren-accordion { border-color: var(--ren-collapse-border, var(--color-border)); border-radius: var(--ren-collapse-radius, var(--radius-lg)); background: var(--ren-collapse-bg, var(--color-surface)); transition-duration: var(--ren-collapse-duration, var(--duration-normal)); transition-timing-function: var(--ren-collapse-easing, var(--ease-out)); }\n.ren-collapsible > summary, .ren-accordion > summary { padding: var(--ren-collapse-padding, var(--space-4)); font-size: var(--ren-collapse-trigger-font, var(--label-lg-size)); font-weight: var(--ren-collapse-trigger-weight, var(--weight-semibold)); }\n",
      "path": "components/composites/ren-collapsible",
      "id": "component:composite:ren-collapsible",
      "type": "component",
      "name": "ren-collapsible"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-color-picker",
          ".ren-color-picker-alpha",
          ".ren-color-picker-alpha-gradient",
          ".ren-color-picker-alpha-handle",
          ".ren-color-picker-channel",
          ".ren-color-picker-channel-input",
          ".ren-color-picker-channel-label",
          ".ren-color-picker-channels",
          ".ren-color-picker-controls",
          ".ren-color-picker-controls-left",
          ".ren-color-picker-controls-right",
          ".ren-color-picker-dropdown",
          ".ren-color-picker-eyedropper",
          ".ren-color-picker-eyedropper-icon",
          ".ren-color-picker-format-toggle",
          ".ren-color-picker-handle",
          ".ren-color-picker-hex-input",
          ".ren-color-picker-hue",
          ".ren-color-picker-hue-handle",
          ".ren-color-picker-inputs",
          ".ren-color-picker-preview",
          ".ren-color-picker-preview-info",
          ".ren-color-picker-preview-label",
          ".ren-color-picker-preview-swatch",
          ".ren-color-picker-preview-value",
          ".ren-color-picker-saturation",
          ".ren-color-picker-saturation-canvas",
          ".ren-color-picker-saturation-handle",
          ".ren-color-picker-swatch",
          ".ren-color-picker-swatch-color",
          ".ren-color-picker-swatches",
          ".ren-color-picker-track",
          ".ren-color-picker-trigger",
          ".ren-color-picker-trigger-swatch",
          ".ren-color-picker-trigger-value"
        ],
        "tokens": [
          "--body-size",
          "--caption-size",
          "--color-accent",
          "--color-border",
          "--color-fill",
          "--color-fill-hover",
          "--color-input-bg",
          "--color-input-border",
          "--color-input-border-focus",
          "--color-input-focus-ring",
          "--color-surface",
          "--color-surface-overlay",
          "--color-surface-raised",
          "--color-text",
          "--color-text-muted",
          "--duration-enter",
          "--ease-enter",
          "--font-mono",
          "--label-size",
          "--label-weight",
          "--radius-lg",
          "--radius-md",
          "--radius-sm",
          "--ren-cp-bg",
          "--ren-cp-handle-size",
          "--ren-cp-radius",
          "--ren-cp-shadow",
          "--ren-cp-swatch-size",
          "--ren-cp-track-height",
          "--ren-cp-width",
          "--shadow-lg",
          "--space-1",
          "--space-2",
          "--space-3",
          "--space-4",
          "--touch-min",
          "--transition-tactile",
          "--white"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-color-picker Component Contract\n\nColor input composite for selecting and previewing colors.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-color-picker` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-color-picker` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Color Picker composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"Users must pick an arbitrary color via canvas (S/B) + hue + optional alpha.\"\n    - \"Multiple output formats are needed (HEX / RGB / HSL) with a toggle.\"\n    - \"Manual entry through hex + channel inputs and preset swatches is required.\"\n    - \"Eyedropper integration (EyeDropper API) is desired when supported.\"\n    - \"The control opens in a popover anchored to a trigger button showing the current color, with placement=\\\"top|right|bottom|left\\\" mirrored to data-side.\"\n  avoidWhen:\n    - \"Users only need to pick from a small fixed palette — render a list of ren-swatch / radio buttons.\"\n    - \"A native <input type=\\\"color\\\"> is sufficient (no alpha, no format toggle, no presets).\"\n    - \"The UI is a brand-theme picker driven by tokens — use a token-aware variant.\"\n    - \"The control must live inline without a dropdown — wrap the dropdown content directly without the trigger.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-color-picker/ren-color-picker.css\"\n  js:\n    - \"rends/components/composites/ren-color-picker/ren-color-picker.js\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"JS is required: HSV ↔ HEX / RGB / HSL conversion, canvas painting, drag handles, and the popover open/close are all implemented in JS.\"\n    - \"The dropdown uses the native Popover API plus CSS anchor positioning via position-area; older browsers fall back to local absolute positioning.\"\n\nrequiredMarkup:\n  - \"Root <ren-color-picker> is the consumer-authored host; its JS generates the trigger button and .ren-color-picker-dropdown popover.\"\n  - \"Trigger is a real <button class=\\\"ren-color-picker-trigger\\\"> with aria-expanded reflecting popover state.\"\n  - \"Saturation/brightness area renders a <canvas class=\\\"ren-color-picker-saturation-canvas\\\"> inside .ren-color-picker-saturation; do not replace with a gradient div.\"\n  - \"Hue handle and alpha handle are decorative thumbs (no tab stops) — keyboard input lives on the hex / channel inputs.\"\n  - \"Swatch grid items are real <button class=\\\"ren-color-picker-swatch\\\"> with aria-selected on the active swatch.\"\n  - \"Use placement=\\\"bottom\\\" by default; the host and .ren-color-picker-dropdown mirror the preferred side to data-side.\"\n\nforbiddenPatterns:\n  - \"Replacing the canvas with a CSS gradient div — drag math relies on canvas pixel sampling.\"\n  - \"Hardcoded hue rainbow gradients in consumer overrides; the rainbow's six hex stops are intentional and locked to HSL pure hues.\"\n  - \"Setting box-shadow / outline overrides that hide the focus ring on .ren-color-picker-trigger or .ren-color-picker-swatch.\"\n  - \"Using window.alert / window.prompt for color input; the component already exposes hex + channel inputs.\"\n  - \"Calling EyeDropper directly from consumer code; the .ren-color-picker-eyedropper button wraps the API with feature detection.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-cp-bg, --ren-cp-handle-size, --ren-cp-radius, --ren-cp-shadow, --ren-cp-swatch-size, --ren-cp-track-height, --ren-cp-width.\"\n    - \"Semantic tokens: --color-surface, --color-surface-raised, --color-border, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-accent, --color-input-bg, --color-input-border, --color-input-border-focus, --color-input-focus-ring.\"\n    - \"Layout / motion tokens: --space-*, --radius-*, --shadow-lg, --touch-min, --font-mono, --duration-enter, --ease-enter, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides (the hue rainbow stops inside the component are the only sanctioned exception).\"\n    - \"Custom drop shadows that bypass --ren-cp-shadow / --shadow-lg.\"\n\naccessibility:\n  required:\n    - \"Trigger button has aria-haspopup=\\\"dialog\\\" (or appropriate) and aria-expanded reflects the dropdown state.\"\n    - \"All inputs (.ren-color-picker-hex-input, .ren-color-picker-channel-input) have associated <label> via .ren-color-picker-channel-label or aria-label.\"\n    - \"The saturation area is operable via keyboard (Arrow keys move the handle in 1% steps; Shift+Arrow in larger steps).\"\n    - \"Focus rings use --color-input-focus-ring; never set outline: none without restoring box-shadow ring.\"\n    - \"Color is never communicated by hue alone — value text appears alongside the swatch (preview label + value).\"\n    - \"Animations honor prefers-reduced-motion (open animation and transitions are clamped to 0.01ms).\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-color-picker/ren-color-picker.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-color-picker/ren-color-picker.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-color-picker value=\"#007aff\" placement=\"bottom\"></ren-color-picker>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-color-picker`\n- `.ren-color-picker-alpha`\n- `.ren-color-picker-alpha-gradient`\n- `.ren-color-picker-alpha-handle`\n- `.ren-color-picker-channel`\n- `.ren-color-picker-channel-input`\n- `.ren-color-picker-channel-label`\n- `.ren-color-picker-channels`\n- `.ren-color-picker-controls`\n- `.ren-color-picker-controls-left`\n- `.ren-color-picker-controls-right`\n- `.ren-color-picker-dropdown`\n- `.ren-color-picker-eyedropper`\n- `.ren-color-picker-eyedropper-icon`\n- `.ren-color-picker-format-toggle`\n- `.ren-color-picker-hex-input`\n- `.ren-color-picker-hue`\n- `.ren-color-picker-hue-handle`\n- `.ren-color-picker-inputs`\n- `.ren-color-picker-preview`\n- `.ren-color-picker-preview-info`\n- `.ren-color-picker-preview-label`\n- `.ren-color-picker-preview-swatch`\n- `.ren-color-picker-preview-value`\n- `...and 9 more in the source files.`\n\n## States And Attributes\n\n- `[aria-expanded]`\n- `[aria-selected]`\n- `[data-side]`\n- `placement`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-cp-bg`\n- `--ren-cp-handle-size`\n- `--ren-cp-radius`\n- `--ren-cp-shadow`\n- `--ren-cp-swatch-size`\n- `--ren-cp-track-height`\n- `--ren-cp-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-color-picker/ren-color-picker.css`\n- `components/composites/ren-color-picker/ren-color-picker.js`\n- `docs/components/ren-color-picker.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ═══════════════════════════════════════════════════════════════════\n   RenDS — Color Picker Composite Component\n   ═══════════════════════════════════════════════════════════════════\n   A fully accessible, feature-rich color picker with support for:\n   - HSV color model (internal) with conversion to/from HEX, RGB, HSL\n   - Saturation/brightness canvas area\n   - Hue slider\n   - Optional alpha/opacity slider\n   - Text inputs for manual color entry\n   - Preset color swatches\n   - EyeDropper API integration (when available)\n   - Keyboard navigation and ARIA labels\n   - Smooth animations with @starting-style support\n\n   Structure:\n   .ren-color-picker (container)\n     .ren-color-picker-trigger (button showing current color)\n     [popover] .ren-color-picker-dropdown\n       .ren-color-picker-saturation (canvas area for S/B)\n         .ren-color-picker-saturation-handle (circular cursor)\n       .ren-color-picker-hue (horizontal rainbow slider)\n         .ren-color-picker-hue-handle (thumb)\n       .ren-color-picker-alpha (opacity slider, optional)\n         .ren-color-picker-alpha-handle (thumb)\n       .ren-color-picker-preview (color display swatch)\n       .ren-color-picker-inputs\n         .ren-color-picker-hex-input\n         .ren-color-picker-channel (R/G/B or H/S/L)\n       .ren-color-picker-swatches (preset colors, optional)\n       .ren-color-picker-format-toggle (HEX/RGB/HSL switcher)\n       .ren-color-picker-eyedropper (screen picker, optional)\n   ═══════════════════════════════════════════════════════════════════ */\n\n/* ═══ COLOR PICKER CONTAINER ═══ */\n.ren-color-picker {\n  position: relative;\n  display: inline-flex;\n  flex-direction: column;\n  gap: var(--space-2);\n  font-family: inherit;\n}\n\n/* ═══ TRIGGER BUTTON (Shows Current Color) ═══ */\n.ren-color-picker-trigger {\n  anchor-name: --ren-color-picker-anchor;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--space-2);\n  width: 100%;\n  min-height: var(--touch-min);\n  padding: var(--space-2) var(--space-3);\n  font-size: var(--body-size);\n  font-family: inherit;\n  color: var(--color-text);\n  background-color: var(--color-input-bg);\n  border: 1px solid var(--color-input-border);\n  border-radius: var(--radius-md);\n  cursor: pointer;\n  transition: var(--transition-tactile);\n}\n\n.ren-color-picker-trigger:hover:not(:disabled) {\n  background-color: var(--color-input-bg);\n  border-color: var(--color-input-border-focus);\n}\n\n.ren-color-picker-trigger:focus-visible {\n  outline: none;\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n}\n\n.ren-color-picker-trigger[aria-expanded=\"true\"] {\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n}\n\n.ren-color-picker-trigger:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n  pointer-events: none;\n}\n\n/* Trigger swatch (small colored square) */\n.ren-color-picker-trigger-swatch {\n  flex-shrink: 0;\n  width: 24px;\n  height: 24px;\n  border-radius: var(--radius-sm);\n  border: 1px solid var(--color-border);\n  background-color: var(--white);\n  background-image: repeating-conic-gradient(\n    var(--color-border) 0deg 90deg,\n    transparent 90deg 180deg\n  ) 50% / 4px 4px;\n}\n\n/* Trigger value text */\n.ren-color-picker-trigger-value {\n  flex: 1;\n  text-align: start;\n  font-family: var(--font-mono);\n  font-size: var(--label-size);\n}\n\n/* ═══ DROPDOWN CONTAINER (Popover) ═══ */\n.ren-color-picker-dropdown {\n  position: absolute;\n  position-anchor: --ren-color-picker-anchor;\n  position-area: bottom span-all;\n  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;\n  inset: auto;\n  margin: 0;\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-3);\n  width: auto;\n  max-width: 300px;\n  padding: var(--space-4);\n  background-color: var(--color-surface-raised);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  z-index: 50;\n\n  /* Popover API defaults */\n  &:popover-open {\n    animation: ren-color-picker-open var(--duration-enter) var(--ease-enter);\n  }\n\n  @supports (animation-timeline: view()) {\n    &:popover-open {\n      animation: ren-color-picker-open var(--duration-enter) var(--ease-enter);\n      transition-behavior: allow-discrete;\n    }\n\n    @starting-style {\n      opacity: 0;\n      transform: scale(0.95);\n    }\n  }\n}\n\n.ren-color-picker-dropdown[data-side=\"bottom\"],\n.ren-color-picker-dropdown:not([data-side]) {\n  position-area: bottom span-all;\n  margin-block-start: var(--space-2);\n  margin-block-end: 0;\n  margin-inline: 0;\n}\n\n.ren-color-picker-dropdown[data-side=\"top\"] {\n  position-area: top span-all;\n  margin-block-start: 0;\n  margin-block-end: var(--space-2);\n  margin-inline: 0;\n}\n\n.ren-color-picker-dropdown[data-side=\"right\"] {\n  position-area: right span-all;\n  margin-block: 0;\n  margin-inline-start: var(--space-2);\n  margin-inline-end: 0;\n}\n\n.ren-color-picker-dropdown[data-side=\"left\"] {\n  position-area: left span-all;\n  margin-block: 0;\n  margin-inline-start: 0;\n  margin-inline-end: var(--space-2);\n}\n\n@supports not ((anchor-name: --ren-anchor) and\n  (position-anchor: --ren-anchor) and\n  (position-area: bottom span-all)) {\n  .ren-color-picker-dropdown {\n    position: absolute;\n    inset: auto;\n  }\n}\n\n@keyframes ren-color-picker-open {\n  from {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n  to {\n    opacity: 1;\n    transform: scale(1);\n  }\n}\n\n/* ═══ SATURATION/BRIGHTNESS AREA ═══ */\n.ren-color-picker-saturation {\n  position: relative;\n  width: 100%;\n  aspect-ratio: 1;\n  max-width: 240px;\n  border-radius: var(--radius-md);\n  border: 1px solid var(--color-border);\n  cursor: crosshair;\n  overflow: hidden;\n  user-select: none;\n  -webkit-user-select: none;\n}\n\n/* Canvas for saturation/brightness */\n.ren-color-picker-saturation-canvas {\n  display: block;\n  width: 100%;\n  height: 100%;\n  border-radius: var(--radius-md);\n}\n\n/* Saturation handle (circular cursor) */\n.ren-color-picker-saturation-handle {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 12px;\n  height: 12px;\n  border-radius: 50%;\n  border: 2px solid white;\n  box-shadow: 0 0 0 1px rgb(0, 0, 0, 0.3);\n  pointer-events: none;\n  transform: translate(-50%, -50%);\n}\n\n/* ═══ HUE SLIDER ═══ */\n.ren-color-picker-hue {\n  position: relative;\n  width: 100%;\n  height: 32px;\n  border-radius: var(--radius-md);\n  border: 1px solid var(--color-border);\n  cursor: pointer;\n  /* Hue rainbow. Intencionalmente hex literals: son los 6 puntos del espectro\n     HSL puros (red→yellow→green→cyan→blue→magenta→red), no colores de UI. */\n  background: linear-gradient(\n    to right,\n    #ff0000 0%,\n    #ffff00 16.67%,\n    #00ff00 33.33%,\n    #00ffff 50%,\n    #0000ff 66.67%,\n    #ff00ff 83.33%,\n    #ff0000 100%\n  );\n  overflow: hidden;\n}\n\n/* Hue slider handle/thumb */\n.ren-color-picker-hue-handle {\n  position: absolute;\n  top: 50%;\n  left: 0;\n  width: 4px;\n  height: 100%;\n  background-color: white;\n  box-shadow: 0 0 0 1px rgb(0, 0, 0, 0.3);\n  pointer-events: none;\n  transform: translateY(-50%);\n}\n\n/* ═══ ALPHA SLIDER (Optional) ═══ */\n.ren-color-picker-alpha {\n  position: relative;\n  width: 100%;\n  height: 32px;\n  border-radius: var(--radius-md);\n  border: 1px solid var(--color-border);\n  cursor: pointer;\n  overflow: hidden;\n\n  /* Checkerboard pattern for transparency */\n  background-image: repeating-conic-gradient(\n    var(--color-border) 0deg 90deg,\n    transparent 90deg 180deg\n  );\n  background-size: 8px 8px;\n  background-position: 0 0;\n}\n\n/* Gradient overlay for alpha */\n.ren-color-picker-alpha-gradient {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  border-radius: var(--radius-md);\n}\n\n/* Alpha slider handle/thumb */\n.ren-color-picker-alpha-handle {\n  position: absolute;\n  top: 50%;\n  left: 0;\n  width: 4px;\n  height: 100%;\n  background-color: white;\n  box-shadow: 0 0 0 1px rgb(0, 0, 0, 0.3);\n  pointer-events: none;\n  transform: translateY(-50%);\n}\n\n/* ═══ COLOR PREVIEW SWATCH ═══ */\n.ren-color-picker-preview {\n  display: flex;\n  gap: var(--space-2);\n  padding: var(--space-3);\n  background-color: var(--color-surface);\n  border-radius: var(--radius-md);\n  border: 1px solid var(--color-border);\n}\n\n.ren-color-picker-preview-swatch {\n  flex-shrink: 0;\n  width: 48px;\n  height: 48px;\n  border-radius: var(--radius-md);\n  border: 1px solid var(--color-border);\n\n  /* Checkerboard for transparency */\n  background-image: repeating-conic-gradient(\n    var(--color-border) 0deg 90deg,\n    transparent 90deg 180deg\n  );\n  background-size: 4px 4px;\n  background-position: 0 0;\n}\n\n.ren-color-picker-preview-info {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  flex: 1;\n}\n\n.ren-color-picker-preview-label {\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  font-weight: var(--label-weight);\n}\n\n.ren-color-picker-preview-value {\n  font-family: var(--font-mono);\n  font-size: var(--label-size);\n  color: var(--color-text);\n  margin-top: var(--space-1);\n}\n\n/* ═══ TEXT INPUTS SECTION ═══ */\n.ren-color-picker-inputs {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-2);\n}\n\n/* Hex input field */\n.ren-color-picker-hex-input {\n  width: 100%;\n  padding: var(--space-2) var(--space-3);\n  font-size: var(--body-size);\n  font-family: var(--font-mono);\n  color: var(--color-text);\n  background-color: var(--color-input-bg);\n  border: 1px solid var(--color-input-border);\n  border-radius: var(--radius-md);\n  transition: var(--transition-tactile);\n}\n\n.ren-color-picker-hex-input:focus {\n  outline: none;\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n}\n\n.ren-color-picker-hex-input::placeholder {\n  color: var(--color-text-muted);\n}\n\n/* Channel inputs container (RGB or HSL) */\n.ren-color-picker-channels {\n  display: grid;\n  grid-template-columns: 1fr 1fr 1fr;\n  gap: var(--space-2);\n}\n\n/* Individual channel input */\n.ren-color-picker-channel {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-1);\n}\n\n.ren-color-picker-channel-label {\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  font-weight: var(--label-weight);\n}\n\n.ren-color-picker-channel-input {\n  padding: var(--space-2) var(--space-2);\n  font-size: var(--body-size);\n  font-family: var(--font-mono);\n  color: var(--color-text);\n  background-color: var(--color-input-bg);\n  border: 1px solid var(--color-input-border);\n  border-radius: var(--radius-md);\n  text-align: center;\n  transition: var(--transition-tactile);\n}\n\n.ren-color-picker-channel-input:focus {\n  outline: none;\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n}\n\n.ren-color-picker-channel-input::-webkit-outer-spin-button,\n.ren-color-picker-channel-input::-webkit-inner-spin-button {\n  -webkit-appearance: none;\n  margin: 0;\n}\n\n.ren-color-picker-channel-input[type=\"number\"] {\n  -moz-appearance: textfield;\n}\n\n/* ═══ FORMAT TOGGLE BUTTON ═══ */\n.ren-color-picker-format-toggle {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  font-size: var(--label-size);\n  font-family: var(--font-mono);\n  color: var(--color-text);\n  background-color: var(--color-fill);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n  cursor: pointer;\n  white-space: nowrap;\n  transition: var(--transition-tactile);\n}\n\n.ren-color-picker-format-toggle:hover {\n  background-color: var(--color-fill-hover);\n  border-color: var(--color-border);\n}\n\n.ren-color-picker-format-toggle:focus-visible {\n  outline: none;\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n}\n\n.ren-color-picker-format-toggle:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n\n/* ═══ EYEDROPPER BUTTON ═══ */\n.ren-color-picker-eyedropper {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  font-size: var(--label-size);\n  color: var(--color-text);\n  background-color: var(--color-fill);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n  cursor: pointer;\n  transition: var(--transition-tactile);\n}\n\n.ren-color-picker-eyedropper:hover {\n  background-color: var(--color-fill-hover);\n  border-color: var(--color-border);\n}\n\n.ren-color-picker-eyedropper:focus-visible {\n  outline: none;\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n}\n\n.ren-color-picker-eyedropper:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n\n.ren-color-picker-eyedropper-icon {\n  width: 16px;\n  height: 16px;\n  display: inline-block;\n}\n\n/* ═══ PRESETS/SWATCHES GRID ═══ */\n.ren-color-picker-swatches {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(40px, 1fr));\n  gap: var(--space-2);\n  padding: var(--space-2);\n  background-color: var(--color-surface);\n  border-radius: var(--radius-md);\n  border: 1px solid var(--color-border);\n}\n\n.ren-color-picker-swatch {\n  position: relative;\n  width: 40px;\n  height: 40px;\n  border-radius: var(--radius-md);\n  border: 2px solid transparent;\n  cursor: pointer;\n  transition: var(--transition-tactile);\n  background-image: repeating-conic-gradient(\n    var(--color-border) 0deg 90deg,\n    transparent 90deg 180deg\n  );\n  background-size: 4px 4px;\n  background-position: 0 0;\n}\n\n.ren-color-picker-swatch:focus-visible {\n  outline: none;\n  border-color: var(--color-accent);\n  box-shadow: 0 0 0 2px var(--color-input-focus-ring);\n}\n\n.ren-color-picker-swatch:hover {\n  transform: scale(1.05);\n  border-color: var(--color-border);\n}\n\n.ren-color-picker-swatch[aria-selected=\"true\"] {\n  border-color: var(--color-accent);\n  box-shadow: 0 0 0 2px var(--color-surface), 0 0 0 4px var(--color-accent);\n}\n\n.ren-color-picker-swatch-color {\n  position: absolute;\n  inset: 0;\n  border-radius: var(--radius-md);\n}\n\n/* ═══ CONTROLS ROW (Format + Eyedropper) ═══ */\n.ren-color-picker-controls {\n  display: flex;\n  gap: var(--space-2);\n}\n\n.ren-color-picker-controls-left {\n  flex: 1;\n  display: flex;\n  gap: var(--space-2);\n}\n\n.ren-color-picker-controls-right {\n  display: flex;\n  gap: var(--space-2);\n}\n\n/* ═══ DISABLED STATE ═══ */\n.ren-color-picker[disabled],\n.ren-color-picker-trigger[disabled] {\n  opacity: 0.5;\n  cursor: not-allowed;\n  pointer-events: none;\n}\n\n/* ═══ ANIMATIONS & TRANSITIONS ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-color-picker-dropdown,\n  .ren-color-picker-trigger,\n  .ren-color-picker-hex-input,\n  .ren-color-picker-channel-input,\n  .ren-color-picker-format-toggle,\n  .ren-color-picker-swatch {\n    animation-duration: 0ms !important;\n    animation-iteration-count: 1 !important;\n    transition-duration: 0ms !important;\n  }\n}\n/* Appearance API bridge */\n.ren-color-picker { inline-size: var(--ren-cp-width, 280px); border-radius: var(--ren-cp-radius, var(--radius-lg)); background: var(--ren-cp-bg, var(--color-surface-overlay)); box-shadow: var(--ren-cp-shadow, var(--shadow-lg)); }\n.ren-color-picker .ren-color-picker-swatch { inline-size: var(--ren-cp-swatch-size, 24px); block-size: var(--ren-cp-swatch-size, 24px); }\n.ren-color-picker .ren-color-picker-handle { inline-size: var(--ren-cp-handle-size, 16px); block-size: var(--ren-cp-handle-size, 16px); }\n.ren-color-picker .ren-color-picker-track { block-size: var(--ren-cp-track-height, 12px); }\n\n\n/**\n * RenDS — Color Picker Composite Component\n * ═════════════════════════════════════════════════════════════════════\n *\n * A fully featured, accessible color picker with support for:\n * - HSV/HSB internal color model with conversion to/from HEX, RGB, HSL\n * - Interactive saturation/brightness canvas area\n * - Hue slider with rainbow gradient\n * - Optional alpha/opacity slider\n * - Text inputs for manual color entry (HEX, RGB, HSL)\n * - Preset color swatches\n * - EyeDropper API for screen color picking (when available)\n * - Full keyboard navigation (arrow keys, Tab, Enter)\n * - ARIA labels and semantic markup\n * - Smooth Popover API animations\n *\n * Features:\n * - Light DOM, no Shadow DOM\n * - Uses design tokens for all UI chrome\n * - Canvas-based saturation area for accurate color rendering\n * - Real-time color conversion and validation\n * - Custom events: ren-change (committed), ren-input (live)\n * - Automatic sRGB color space handling\n *\n * @example\n * <ren-color-picker\n *   value=\"#3b82f6\"\n *   alpha\n *   format=\"hex\"\n *   swatches=\"#ef4444,#f97316,#eab308,#22c55e,#3b82f6,#8b5cf6,#ec4899\">\n * </ren-color-picker>\n *\n * @fires ren-change - Dispatched when color is committed\n * @fires ren-input - Dispatched during interactive changes\n */\n\n/* ═════════════════════════════════════════════════════════════════════\n   COLOR CONVERSION UTILITIES\n   ═════════════════════════════════════════════════════════════════════ */\n\n/**\n * Convert RGB to HSV (hue, saturation, value/brightness)\n * Handles sRGB color space properly\n * Returns: { h: 0-360, s: 0-100, v: 0-100 }\n */\nfunction rgbToHsv(r, g, b, a = 1) {\n  r /= 255;\n  g /= 255;\n  b /= 255;\n\n  const max = Math.max(r, g, b);\n  const min = Math.min(r, g, b);\n  const delta = max - min;\n  let h = 0;\n  let s = 0;\n  let v = max;\n\n  if (delta !== 0) {\n    s = delta / max;\n\n    if (max === r) {\n      h = ((g - b) / delta + (g < b ? 6 : 0)) / 6;\n    } else if (max === g) {\n      h = ((b - r) / delta + 2) / 6;\n    } else {\n      h = ((r - g) / delta + 4) / 6;\n    }\n  }\n\n  return {\n    h: Math.round(h * 360),\n    s: Math.round(s * 100),\n    v: Math.round(v * 100),\n    a,\n  };\n}\n\n/**\n * Convert HSV to RGB\n * Inverse of rgbToHsv\n * Accepts: { h: 0-360, s: 0-100, v: 0-100, a: 0-1 }\n * Returns: { r: 0-255, g: 0-255, b: 0-255, a: 0-1 }\n */\nfunction hsvToRgb(h, s, v, a = 1) {\n  h = (h % 360) / 60;\n  s = s / 100;\n  v = v / 100;\n\n  const c = v * s;\n  const x = c * (1 - Math.abs((h % 2) - 1));\n  const m = v - c;\n\n  let r = 0,\n    g = 0,\n    b = 0;\n\n  if (h >= 0 && h < 1) {\n    r = c;\n    g = x;\n    b = 0;\n  } else if (h >= 1 && h < 2) {\n    r = x;\n    g = c;\n    b = 0;\n  } else if (h >= 2 && h < 3) {\n    r = 0;\n    g = c;\n    b = x;\n  } else if (h >= 3 && h < 4) {\n    r = 0;\n    g = x;\n    b = c;\n  } else if (h >= 4 && h < 5) {\n    r = x;\n    g = 0;\n    b = c;\n  } else if (h >= 5 && h < 6) {\n    r = c;\n    g = 0;\n    b = x;\n  }\n\n  return {\n    r: Math.round((r + m) * 255),\n    g: Math.round((g + m) * 255),\n    b: Math.round((b + m) * 255),\n    a,\n  };\n}\n\n/**\n * Convert RGB to HEX string\n * Returns: \"#RRGGBB\" or \"#RRGGBBAA\" if alpha < 1\n */\nfunction rgbToHex(r, g, b, a = 1) {\n  const toHex = (n) => {\n    const hex = Math.round(n).toString(16);\n    return hex.length === 1 ? '0' + hex : hex;\n  };\n\n  const hex = `#${toHex(r)}${toHex(g)}${toHex(b)}`;\n\n  if (a < 1) {\n    return hex + toHex(a * 255);\n  }\n  return hex;\n}\n\n/**\n * Parse HEX color string to RGB values\n * Accepts: \"#RGB\", \"#RRGGBB\", \"#RGBA\", \"#RRGGBBAA\"\n * Returns: { r, g, b, a } or null if invalid\n */\nfunction hexToRgb(hex) {\n  hex = hex.replace(/^#/, '');\n\n  // Handle shorthand\n  if (hex.length === 3 || hex.length === 4) {\n    hex = hex\n      .split('')\n      .map((c) => c + c)\n      .join('');\n  }\n\n  if (hex.length !== 6 && hex.length !== 8) {\n    return null;\n  }\n\n  const r = parseInt(hex.substring(0, 2), 16);\n  const g = parseInt(hex.substring(2, 4), 16);\n  const b = parseInt(hex.substring(4, 6), 16);\n  const a = hex.length === 8 ? parseInt(hex.substring(6, 8), 16) / 255 : 1;\n\n  if (isNaN(r) || isNaN(g) || isNaN(b)) {\n    return null;\n  }\n\n  return { r, g, b, a };\n}\n\n/**\n * Convert RGB to HSL\n * Returns: { h: 0-360, s: 0-100, l: 0-100, a: 0-1 }\n */\nfunction rgbToHsl(r, g, b, a = 1) {\n  r /= 255;\n  g /= 255;\n  b /= 255;\n\n  const max = Math.max(r, g, b);\n  const min = Math.min(r, g, b);\n  const delta = max - min;\n  let h = 0;\n  let s = 0;\n  const l = (max + min) / 2;\n\n  if (delta !== 0) {\n    s = delta / (1 - Math.abs(2 * l - 1));\n\n    if (max === r) {\n      h = ((g - b) / delta + (g < b ? 6 : 0)) / 6;\n    } else if (max === g) {\n      h = ((b - r) / delta + 2) / 6;\n    } else {\n      h = ((r - g) / delta + 4) / 6;\n    }\n  }\n\n  return {\n    h: Math.round(h * 360),\n    s: Math.round(s * 100),\n    l: Math.round(l * 100),\n    a,\n  };\n}\n\n/**\n * Convert HSL to RGB\n * Accepts: { h: 0-360, s: 0-100, l: 0-100, a: 0-1 }\n * Returns: { r: 0-255, g: 0-255, b: 0-255, a: 0-1 }\n */\nfunction hslToRgb(h, s, l, a = 1) {\n  h = (h % 360) / 360;\n  s = s / 100;\n  l = l / 100;\n\n  const hue2rgb = (p, q, t) => {\n    if (t < 0) t += 1;\n    if (t > 1) t -= 1;\n    if (t < 1 / 6) return p + (q - p) * 6 * t;\n    if (t < 1 / 2) return q;\n    if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;\n    return p;\n  };\n\n  let r, g, b;\n  if (s === 0) {\n    r = g = b = l;\n  } else {\n    const q = l < 0.5 ? l * (1 + s) : l + s - l * s;\n    const p = 2 * l - q;\n    r = hue2rgb(p, q, h + 1 / 3);\n    g = hue2rgb(p, q, h);\n    b = hue2rgb(p, q, h - 1 / 3);\n  }\n\n  return {\n    r: Math.round(r * 255),\n    g: Math.round(g * 255),\n    b: Math.round(b * 255),\n    a,\n  };\n}\n\n/**\n * Validate and normalize hex color string\n */\nfunction normalizeHex(hex) {\n  if (typeof hex !== 'string') return null;\n  hex = hex.trim();\n  if (!hex.startsWith('#')) hex = '#' + hex;\n  const result = hexToRgb(hex);\n  return result ? rgbToHex(result.r, result.g, result.b, result.a) : null;\n}\n\nconst COLOR_PICKER_SIDES = new Set(['top', 'right', 'bottom', 'left']);\n\nfunction normalizeColorPickerSide(value) {\n  const side = String(value || 'bottom').toLowerCase().split('-')[0];\n\n  return COLOR_PICKER_SIDES.has(side) ? side : 'bottom';\n}\n\n/* ═════════════════════════════════════════════════════════════════════\n   COLOR PICKER WEB COMPONENT\n   ═════════════════════════════════════════════════════════════════════ */\n\nexport class RenColorPicker extends HTMLElement {\n  static observedAttributes = ['placement'];\n\n  #isOpen = false;\n  #hsv = { h: 0, s: 100, v: 100, a: 1 };\n  #format = 'hex'; // 'hex', 'rgb', 'hsl'\n  #saturationCanvas = null;\n  #dragState = null;\n  #popover = null;\n  #trigger = null;\n  #isDragging = false;\n  #listenerController = null;\n\n  constructor() {\n    super();\n    this.handleTriggerClick = this.handleTriggerClick.bind(this);\n    this.handleSaturationMouseDown = this.handleSaturationMouseDown.bind(this);\n    this.handleHueMouseDown = this.handleHueMouseDown.bind(this);\n    this.handleAlphaMouseDown = this.handleAlphaMouseDown.bind(this);\n    this.handleMouseMove = this.handleMouseMove.bind(this);\n    this.handleMouseUp = this.handleMouseUp.bind(this);\n    this.handleInputChange = this.handleInputChange.bind(this);\n    this.handleFormatToggle = this.handleFormatToggle.bind(this);\n    this.handleSwatchClick = this.handleSwatchClick.bind(this);\n    this.handleEyedropper = this.handleEyedropper.bind(this);\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'placement' && oldValue !== newValue) {\n      this.#syncPlacement();\n    }\n  }\n\n  connectedCallback() {\n    this.#listenerController?.abort();\n    this.#listenerController = new AbortController();\n    const { signal } = this.#listenerController;\n\n    // ═══ PARSE ATTRIBUTES ═══\n    const value = this.getAttribute('value') || '#3b82f6';\n    const hasAlpha = this.hasAttribute('alpha');\n    const format = this.getAttribute('format') || 'hex';\n    const swatches = this.getAttribute('swatches') || '';\n    const disabled = this.hasAttribute('disabled');\n\n    this.#format = ['hex', 'rgb', 'hsl'].includes(format) ? format : 'hex';\n\n    // ═══ PARSE INITIAL COLOR ═══\n    this.setValue(value);\n\n    // ═══ RENDER TRIGGER ═══\n    this.#renderTrigger(disabled);\n\n    // ═══ SET UP EVENT LISTENERS ═══\n    if (this.#trigger) {\n      this.#trigger.addEventListener('click', this.handleTriggerClick, { signal });\n      this.#trigger.addEventListener('keydown', (e) => {\n        if (e.key === 'Enter' || e.key === ' ') {\n          this.handleTriggerClick();\n          e.preventDefault();\n        }\n      }, { signal });\n    }\n\n    // ═══ RENDER DROPDOWN (will be populated on open) ═══\n    this.#popover = this.querySelector(':scope > .ren-color-picker-dropdown');\n    if (!this.#popover) {\n      this.#popover = document.createElement('div');\n      this.#popover.className = 'ren-color-picker-dropdown';\n      this.appendChild(this.#popover);\n    }\n    const popoverId = this.#popover.id || `ren-color-picker-${Math.random().toString(36).slice(2)}`;\n    this.#popover.id = popoverId;\n    this.#popover.setAttribute('popover', 'auto');\n\n    if (this.#trigger) {\n      this.#trigger.setAttribute('aria-haspopup', 'dialog');\n      this.#trigger.setAttribute('aria-expanded', 'false');\n      this.#trigger.setAttribute('popovertarget', popoverId);\n    }\n\n    this.#syncPlacement();\n\n    // ═══ POPULATE POPOVER ON OPEN ═══\n    this.#popover.addEventListener('toggle', (e) => {\n      if (e.newState === 'open') {\n        this.#isOpen = true;\n        if (this.#trigger) {\n          this.#trigger.setAttribute('aria-expanded', 'true');\n        }\n        this.#renderDropdownContent(hasAlpha, swatches, disabled);\n      } else {\n        this.#isOpen = false;\n        if (this.#trigger) {\n          this.#trigger.setAttribute('aria-expanded', 'false');\n        }\n      }\n    }, { signal });\n  }\n\n  disconnectedCallback() {\n    this.#listenerController?.abort();\n    this.#listenerController = null;\n    document.removeEventListener('mousemove', this.handleMouseMove);\n    document.removeEventListener('mouseup', this.handleMouseUp);\n  }\n\n  /* ═════════════════════════════════════════════════════════════════\n     RENDERING METHODS\n     ═════════════════════════════════════════════════════════════════ */\n\n  #syncPlacement() {\n    const side = normalizeColorPickerSide(this.getAttribute('placement'));\n\n    this.setAttribute('data-side', side);\n    if (this.#popover) {\n      this.#popover.setAttribute('data-side', side);\n    }\n  }\n\n  /**\n   * Render the trigger button showing the selected color\n   */\n  #renderTrigger(disabled = false) {\n    // Remove existing trigger\n    const existing = this.querySelector('.ren-color-picker-trigger');\n    if (existing) existing.remove();\n\n    const trigger = document.createElement('button');\n    trigger.className = 'ren-color-picker-trigger';\n    trigger.type = 'button';\n    if (disabled) trigger.disabled = true;\n\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n    const hex = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n\n    trigger.innerHTML = `\n      <div class=\"ren-color-picker-trigger-swatch\" style=\"background-color: ${this.#hsvToCss()}\"></div>\n      <div class=\"ren-color-picker-trigger-value\">${hex}</div>\n    `;\n\n    trigger.setAttribute('aria-label', `Color picker, current color ${hex}`);\n\n    this.insertBefore(trigger, this.#popover);\n    this.#trigger = trigger;\n  }\n\n  /**\n   * Render the full dropdown content (called when popover opens)\n   */\n  #renderDropdownContent(hasAlpha = false, swatches = '', disabled = false) {\n    if (!this.#popover) return;\n\n    // Clear existing content\n    this.#popover.innerHTML = '';\n\n    // ═══ SATURATION/BRIGHTNESS AREA ═══\n    const saturationContainer = document.createElement('div');\n    saturationContainer.className = 'ren-color-picker-saturation';\n    saturationContainer.setAttribute('aria-label', 'Color saturation and brightness');\n\n    this.#saturationCanvas = document.createElement('canvas');\n    this.#saturationCanvas.className = 'ren-color-picker-saturation-canvas';\n    this.#saturationCanvas.width = 240;\n    this.#saturationCanvas.height = 240;\n\n    saturationContainer.appendChild(this.#saturationCanvas);\n\n    const handle = document.createElement('div');\n    handle.className = 'ren-color-picker-saturation-handle';\n    saturationContainer.appendChild(handle);\n\n    this.#drawSaturationCanvas();\n    this.#updateSaturationHandle();\n\n    saturationContainer.addEventListener('mousedown', this.handleSaturationMouseDown);\n    this.#popover.appendChild(saturationContainer);\n\n    // ═══ HUE SLIDER ═══\n    const hueContainer = document.createElement('div');\n    hueContainer.className = 'ren-color-picker-hue';\n    hueContainer.setAttribute('aria-label', 'Color hue');\n    hueContainer.setAttribute('role', 'slider');\n    hueContainer.setAttribute('aria-valuenow', this.#hsv.h);\n    hueContainer.setAttribute('aria-valuemin', '0');\n    hueContainer.setAttribute('aria-valuemax', '360');\n    hueContainer.setAttribute('aria-orientation', 'horizontal');\n    hueContainer.setAttribute('tabindex', '0');\n\n    const hueHandle = document.createElement('div');\n    hueHandle.className = 'ren-color-picker-hue-handle';\n    hueContainer.appendChild(hueHandle);\n\n    this.#updateHueHandle();\n\n    hueContainer.addEventListener('mousedown', this.handleHueMouseDown);\n    hueContainer.addEventListener('keydown', (e) => {\n      this.#handleHueKeydown(e, hueContainer);\n    });\n\n    this.#popover.appendChild(hueContainer);\n\n    // ═══ ALPHA SLIDER (Optional) ═══\n    if (hasAlpha) {\n      const alphaContainer = document.createElement('div');\n      alphaContainer.className = 'ren-color-picker-alpha';\n      alphaContainer.setAttribute('aria-label', 'Color opacity');\n      alphaContainer.setAttribute('role', 'slider');\n      alphaContainer.setAttribute('aria-valuenow', Math.round(this.#hsv.a * 100));\n      alphaContainer.setAttribute('aria-valuemin', '0');\n      alphaContainer.setAttribute('aria-valuemax', '100');\n      alphaContainer.setAttribute('aria-orientation', 'horizontal');\n      alphaContainer.setAttribute('tabindex', '0');\n\n      const alphaGradient = document.createElement('div');\n      alphaGradient.className = 'ren-color-picker-alpha-gradient';\n      alphaContainer.appendChild(alphaGradient);\n\n      const alphaHandle = document.createElement('div');\n      alphaHandle.className = 'ren-color-picker-alpha-handle';\n      alphaContainer.appendChild(alphaHandle);\n\n      this.#updateAlphaGradient();\n      this.#updateAlphaHandle();\n\n      alphaContainer.addEventListener('mousedown', this.handleAlphaMouseDown);\n      alphaContainer.addEventListener('keydown', (e) => {\n        this.#handleAlphaKeydown(e, alphaContainer);\n      });\n\n      this.#popover.appendChild(alphaContainer);\n    }\n\n    // ═══ COLOR PREVIEW ═══\n    const preview = document.createElement('div');\n    preview.className = 'ren-color-picker-preview';\n\n    const swatch = document.createElement('div');\n    swatch.className = 'ren-color-picker-preview-swatch';\n    swatch.style.backgroundColor = this.#hsvToCss();\n\n    const info = document.createElement('div');\n    info.className = 'ren-color-picker-preview-info';\n\n    const label = document.createElement('div');\n    label.className = 'ren-color-picker-preview-label';\n    label.textContent = 'Current';\n\n    const value = document.createElement('div');\n    value.className = 'ren-color-picker-preview-value';\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n    value.textContent = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n\n    info.appendChild(label);\n    info.appendChild(value);\n    preview.appendChild(swatch);\n    preview.appendChild(info);\n    this.#popover.appendChild(preview);\n\n    // ═══ TEXT INPUTS SECTION ═══\n    const inputsSection = document.createElement('div');\n    inputsSection.className = 'ren-color-picker-inputs';\n\n    // Hex input\n    const hexInput = document.createElement('input');\n    hexInput.className = 'ren-color-picker-hex-input';\n    hexInput.type = 'text';\n    hexInput.placeholder = '#000000';\n    hexInput.value = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n    hexInput.setAttribute('aria-label', 'Hex color value');\n    hexInput.disabled = disabled;\n    hexInput.addEventListener('change', this.handleInputChange);\n    inputsSection.appendChild(hexInput);\n\n    // RGB or HSL channels\n    const channelsContainer = document.createElement('div');\n    channelsContainer.className = 'ren-color-picker-channels';\n\n    let channels = [];\n    if (this.#format === 'rgb') {\n      channels = [\n        { label: 'R', value: rgb.r, min: 0, max: 255, key: 'r' },\n        { label: 'G', value: rgb.g, min: 0, max: 255, key: 'g' },\n        { label: 'B', value: rgb.b, min: 0, max: 255, key: 'b' },\n      ];\n    } else if (this.#format === 'hsl') {\n      const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b, rgb.a);\n      channels = [\n        { label: 'H', value: hsl.h, min: 0, max: 360, key: 'h' },\n        { label: 'S', value: hsl.s, min: 0, max: 100, key: 's' },\n        { label: 'L', value: hsl.l, min: 0, max: 100, key: 'l' },\n      ];\n    } else {\n      channels = [\n        { label: 'R', value: rgb.r, min: 0, max: 255, key: 'r' },\n        { label: 'G', value: rgb.g, min: 0, max: 255, key: 'g' },\n        { label: 'B', value: rgb.b, min: 0, max: 255, key: 'b' },\n      ];\n    }\n\n    channels.forEach(({ label, value, min, max, key }) => {\n      const channel = document.createElement('div');\n      channel.className = 'ren-color-picker-channel';\n\n      const channelLabel = document.createElement('label');\n      channelLabel.className = 'ren-color-picker-channel-label';\n      channelLabel.textContent = label;\n\n      const channelInput = document.createElement('input');\n      channelInput.className = 'ren-color-picker-channel-input';\n      channelInput.type = 'number';\n      channelInput.min = min;\n      channelInput.max = max;\n      channelInput.value = value;\n      channelInput.disabled = disabled;\n      channelInput.setAttribute('aria-label', `${label} channel`);\n      channelInput.addEventListener('change', this.handleInputChange);\n\n      channel.appendChild(channelLabel);\n      channel.appendChild(channelInput);\n      channelsContainer.appendChild(channel);\n    });\n\n    inputsSection.appendChild(channelsContainer);\n    this.#popover.appendChild(inputsSection);\n\n    // ═══ CONTROLS (Format Toggle + Eyedropper) ═══\n    const controls = document.createElement('div');\n    controls.className = 'ren-color-picker-controls';\n\n    const controlsLeft = document.createElement('div');\n    controlsLeft.className = 'ren-color-picker-controls-left';\n\n    const formatToggle = document.createElement('button');\n    formatToggle.className = 'ren-color-picker-format-toggle';\n    formatToggle.type = 'button';\n    formatToggle.textContent = this.#format.toUpperCase();\n    formatToggle.setAttribute('aria-label', 'Toggle color format between HEX, RGB, and HSL');\n    formatToggle.disabled = disabled;\n    formatToggle.addEventListener('click', this.handleFormatToggle);\n\n    controlsLeft.appendChild(formatToggle);\n\n    // Eyedropper (if available)\n    if ('EyeDropper' in window) {\n      const eyedropperBtn = document.createElement('button');\n      eyedropperBtn.className = 'ren-color-picker-eyedropper';\n      eyedropperBtn.type = 'button';\n      eyedropperBtn.setAttribute('aria-label', 'Pick color from screen');\n      eyedropperBtn.disabled = disabled;\n\n      // Simple eyedropper icon (SVG)\n      const icon = document.createElement('span');\n      icon.className = 'ren-color-picker-eyedropper-icon';\n      icon.innerHTML = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 2L22 12L12 22L2 12Z\"></path></svg>`;\n\n      eyedropperBtn.appendChild(icon);\n      eyedropperBtn.addEventListener('click', this.handleEyedropper);\n      controlsLeft.appendChild(eyedropperBtn);\n    }\n\n    controls.appendChild(controlsLeft);\n    this.#popover.appendChild(controls);\n\n    // ═══ PRESETS/SWATCHES (Optional) ═══\n    if (swatches.trim()) {\n      const swatchesContainer = document.createElement('div');\n      swatchesContainer.className = 'ren-color-picker-swatches';\n      swatchesContainer.setAttribute('aria-label', 'Preset color swatches');\n\n      const colors = swatches.split(',').map((s) => s.trim());\n      colors.forEach((color) => {\n        const swatch = document.createElement('button');\n        swatch.className = 'ren-color-picker-swatch';\n        swatch.type = 'button';\n        swatch.setAttribute('aria-label', `Color ${color}`);\n\n        const colorDiv = document.createElement('div');\n        colorDiv.className = 'ren-color-picker-swatch-color';\n        colorDiv.style.backgroundColor = color;\n\n        swatch.appendChild(colorDiv);\n        swatch.addEventListener('click', this.handleSwatchClick);\n\n        // Check if this is the current color\n        const normalized = normalizeHex(color);\n        const currentHex = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n        if (normalized === currentHex) {\n          swatch.setAttribute('aria-selected', 'true');\n        }\n\n        swatchesContainer.appendChild(swatch);\n      });\n\n      this.#popover.appendChild(swatchesContainer);\n    }\n  }\n\n  /* ═════════════════════════════════════════════════════════════════\n     CANVAS & HANDLE UPDATES\n     ═════════════════════════════════════════════════════════════════ */\n\n  /**\n   * Draw the saturation/brightness canvas\n   */\n  #drawSaturationCanvas() {\n    if (!this.#saturationCanvas) return;\n\n    const ctx = this.#saturationCanvas.getContext('2d');\n    const { width, height } = this.#saturationCanvas;\n\n    // Create horizontal gradient: white (left) to hue (right)\n    const rgb = hsvToRgb(this.#hsv.h, 100, 100, 1);\n    const hueColor = `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;\n\n    const hGradient = ctx.createLinearGradient(0, 0, width, 0);\n    hGradient.addColorStop(0, 'white');\n    hGradient.addColorStop(1, hueColor);\n\n    ctx.fillStyle = hGradient;\n    ctx.fillRect(0, 0, width, height);\n\n    // Create vertical gradient overlay: transparent (top) to black (bottom)\n    const vGradient = ctx.createLinearGradient(0, 0, 0, height);\n    vGradient.addColorStop(0, 'rgba(0, 0, 0, 0)');\n    vGradient.addColorStop(1, 'rgba(0, 0, 0, 1)');\n\n    ctx.fillStyle = vGradient;\n    ctx.fillRect(0, 0, width, height);\n  }\n\n  /**\n   * Update saturation handle position\n   */\n  #updateSaturationHandle() {\n    if (!this.#saturationCanvas) return;\n\n    const handle = this.#saturationCanvas.parentElement.querySelector(\n      '.ren-color-picker-saturation-handle'\n    );\n    if (!handle) return;\n\n    const { width, height } = this.#saturationCanvas;\n    const x = (this.#hsv.s / 100) * width;\n    const y = ((100 - this.#hsv.v) / 100) * height;\n\n    handle.style.left = x + 'px';\n    handle.style.top = y + 'px';\n  }\n\n  /**\n   * Update hue handle position\n   */\n  #updateHueHandle() {\n    const hueContainer = this.#popover?.querySelector('.ren-color-picker-hue');\n    if (!hueContainer) return;\n\n    const handle = hueContainer.querySelector('.ren-color-picker-hue-handle');\n    if (!handle) return;\n\n    const percent = (this.#hsv.h / 360) * 100;\n    handle.style.left = percent + '%';\n\n    hueContainer.setAttribute('aria-valuenow', this.#hsv.h);\n  }\n\n  /**\n   * Update alpha gradient and handle\n   */\n  #updateAlphaGradient() {\n    const alphaContainer = this.#popover?.querySelector('.ren-color-picker-alpha');\n    if (!alphaContainer) return;\n\n    const gradient = alphaContainer.querySelector('.ren-color-picker-alpha-gradient');\n    if (!gradient) return;\n\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, 1);\n    const color = `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;\n\n    gradient.style.background = `linear-gradient(to right, rgba(0,0,0,0), ${color})`;\n  }\n\n  /**\n   * Update alpha handle position\n   */\n  #updateAlphaHandle() {\n    const alphaContainer = this.#popover?.querySelector('.ren-color-picker-alpha');\n    if (!alphaContainer) return;\n\n    const handle = alphaContainer.querySelector('.ren-color-picker-alpha-handle');\n    if (!handle) return;\n\n    const percent = this.#hsv.a * 100;\n    handle.style.left = percent + '%';\n\n    alphaContainer.setAttribute('aria-valuenow', Math.round(percent));\n  }\n\n  /* ═════════════════════════════════════════════════════════════════\n     EVENT HANDLERS\n     ═════════════════════════════════════════════════════════════════ */\n\n  handleTriggerClick(event) {\n    event?.preventDefault();\n    if (!this.#popover) return;\n\n    try {\n      if (this.#popover.matches(':popover-open')) {\n        this.#popover.hidePopover?.();\n      } else {\n        this.#popover.showPopover?.();\n      }\n    } catch {\n      // Native popover can throw if browser state changes mid-click.\n    }\n  }\n\n  handleSaturationMouseDown(e) {\n    e.preventDefault();\n    this.#isDragging = true;\n    this.#dragState = { type: 'saturation' };\n\n    this.#updateSaturationFromMouse(e);\n\n    document.addEventListener('mousemove', this.handleMouseMove);\n    document.addEventListener('mouseup', this.handleMouseUp);\n  }\n\n  handleHueMouseDown(e) {\n    e.preventDefault();\n    this.#isDragging = true;\n    this.#dragState = { type: 'hue' };\n\n    this.#updateHueFromMouse(e);\n\n    document.addEventListener('mousemove', this.handleMouseMove);\n    document.addEventListener('mouseup', this.handleMouseUp);\n  }\n\n  handleAlphaMouseDown(e) {\n    e.preventDefault();\n    this.#isDragging = true;\n    this.#dragState = { type: 'alpha' };\n\n    this.#updateAlphaFromMouse(e);\n\n    document.addEventListener('mousemove', this.handleMouseMove);\n    document.addEventListener('mouseup', this.handleMouseUp);\n  }\n\n  handleMouseMove(e) {\n    if (!this.#isDragging || !this.#dragState) return;\n\n    if (this.#dragState.type === 'saturation') {\n      this.#updateSaturationFromMouse(e);\n    } else if (this.#dragState.type === 'hue') {\n      this.#updateHueFromMouse(e);\n    } else if (this.#dragState.type === 'alpha') {\n      this.#updateAlphaFromMouse(e);\n    }\n  }\n\n  handleMouseUp(e) {\n    this.#isDragging = false;\n    this.#dragState = null;\n\n    document.removeEventListener('mousemove', this.handleMouseMove);\n    document.removeEventListener('mouseup', this.handleMouseUp);\n\n    this.#emitChange();\n  }\n\n  handleInputChange(e) {\n    const input = e.target;\n    const value = input.value.trim();\n\n    if (input.classList.contains('ren-color-picker-hex-input')) {\n      const rgb = hexToRgb(value);\n      if (rgb) {\n        const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b, rgb.a);\n        this.#hsv = hsv;\n        this.#updateAllViews();\n        this.#emitChange();\n      }\n    } else if (input.classList.contains('ren-color-picker-channel-input')) {\n      this.#updateFromChannelInputs();\n      this.#emitChange();\n    }\n  }\n\n  handleFormatToggle() {\n    const formats = ['hex', 'rgb', 'hsl'];\n    const currentIndex = formats.indexOf(this.#format);\n    this.#format = formats[(currentIndex + 1) % formats.length];\n\n    // Re-render inputs\n    this.#renderDropdownContent(\n      this.hasAttribute('alpha'),\n      this.getAttribute('swatches') || '',\n      this.hasAttribute('disabled')\n    );\n  }\n\n  handleSwatchClick(e) {\n    const swatch = e.currentTarget;\n    const colorDiv = swatch.querySelector('.ren-color-picker-swatch-color');\n    const color = colorDiv.style.backgroundColor;\n\n    this.setValue(color);\n    this.#updateAllViews();\n    this.#emitChange();\n  }\n\n  async handleEyedropper() {\n    try {\n      const eyeDropper = new EyeDropper();\n      const result = await eyeDropper.open();\n      this.setValue(result.sRGBHex);\n      this.#updateAllViews();\n      this.#emitChange();\n    } catch {\n      // User cancelled or not supported\n    }\n  }\n\n  /* ═════════════════════════════════════════════════════════════════\n     KEYBOARD NAVIGATION\n     ═════════════════════════════════════════════════════════════════ */\n\n  #handleHueKeydown(e, hueContainer) {\n    const step = e.shiftKey ? 10 : 1;\n\n    if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {\n      this.#hsv.h = (this.#hsv.h + step) % 360;\n      this.#updateAllViews();\n      this.#emitInput();\n      e.preventDefault();\n    } else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {\n      this.#hsv.h = (this.#hsv.h - step + 360) % 360;\n      this.#updateAllViews();\n      this.#emitInput();\n      e.preventDefault();\n    }\n  }\n\n  #handleAlphaKeydown(e, alphaContainer) {\n    const step = e.shiftKey ? 0.1 : 0.01;\n\n    if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {\n      this.#hsv.a = Math.min(1, this.#hsv.a + step);\n      this.#updateAllViews();\n      this.#emitInput();\n      e.preventDefault();\n    } else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {\n      this.#hsv.a = Math.max(0, this.#hsv.a - step);\n      this.#updateAllViews();\n      this.#emitInput();\n      e.preventDefault();\n    }\n  }\n\n  /* ═════════════════════════════════════════════════════════════════\n     INTERNAL UPDATES\n     ═════════════════════════════════════════════════════════════════ */\n\n  #updateSaturationFromMouse(e) {\n    if (!this.#saturationCanvas) return;\n\n    const rect = this.#saturationCanvas.getBoundingClientRect();\n    const x = Math.max(0, Math.min(rect.width, e.clientX - rect.left));\n    const y = Math.max(0, Math.min(rect.height, e.clientY - rect.top));\n\n    this.#hsv.s = Math.round((x / rect.width) * 100);\n    this.#hsv.v = Math.round(100 - (y / rect.height) * 100);\n\n    this.#updateSaturationHandle();\n    this.#updateAlphaGradient();\n    this.#updatePreview();\n    this.#emitInput();\n  }\n\n  #updateHueFromMouse(e) {\n    const hueContainer = this.#popover?.querySelector('.ren-color-picker-hue');\n    if (!hueContainer) return;\n\n    const rect = hueContainer.getBoundingClientRect();\n    const x = Math.max(0, Math.min(rect.width, e.clientX - rect.left));\n    this.#hsv.h = Math.round((x / rect.width) * 360) % 360;\n\n    this.#drawSaturationCanvas();\n    this.#updateSaturationHandle();\n    this.#updateHueHandle();\n    this.#updateAlphaGradient();\n    this.#updatePreview();\n    this.#updateChannelInputs();\n    this.#emitInput();\n  }\n\n  #updateAlphaFromMouse(e) {\n    const alphaContainer = this.#popover?.querySelector('.ren-color-picker-alpha');\n    if (!alphaContainer) return;\n\n    const rect = alphaContainer.getBoundingClientRect();\n    const x = Math.max(0, Math.min(rect.width, e.clientX - rect.left));\n    this.#hsv.a = Math.round((x / rect.width) * 100) / 100;\n\n    this.#updateAlphaHandle();\n    this.#updatePreview();\n    this.#emitInput();\n  }\n\n  #updateFromChannelInputs() {\n    const inputs = this.#popover?.querySelectorAll('.ren-color-picker-channel-input');\n    if (!inputs || inputs.length === 0) return;\n\n    const values = Array.from(inputs).map((inp) => parseFloat(inp.value) || 0);\n\n    if (this.#format === 'rgb') {\n      const [r, g, b] = values;\n      const hsv = rgbToHsv(r, g, b, this.#hsv.a);\n      this.#hsv = hsv;\n    } else if (this.#format === 'hsl') {\n      const [h, s, l] = values;\n      const rgb = hslToRgb(h, s, l, this.#hsv.a);\n      const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b, rgb.a);\n      this.#hsv = hsv;\n    } else {\n      const [r, g, b] = values;\n      const hsv = rgbToHsv(r, g, b, this.#hsv.a);\n      this.#hsv = hsv;\n    }\n\n    this.#updateAllViews();\n  }\n\n  #updateAllViews() {\n    this.#drawSaturationCanvas();\n    this.#updateSaturationHandle();\n    this.#updateHueHandle();\n    this.#updateAlphaGradient();\n    this.#updateAlphaHandle();\n    this.#updatePreview();\n    this.#updateChannelInputs();\n    this.#updateHexInput();\n    this.#updateTrigger();\n  }\n\n  #updatePreview() {\n    const preview = this.#popover?.querySelector('.ren-color-picker-preview-swatch');\n    const value = this.#popover?.querySelector('.ren-color-picker-preview-value');\n\n    if (preview) {\n      preview.style.backgroundColor = this.#hsvToCss();\n    }\n\n    if (value) {\n      const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n      value.textContent = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n    }\n  }\n\n  #updateHexInput() {\n    const input = this.#popover?.querySelector('.ren-color-picker-hex-input');\n    if (!input) return;\n\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n    input.value = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n  }\n\n  #updateChannelInputs() {\n    const inputs = this.#popover?.querySelectorAll('.ren-color-picker-channel-input');\n    if (!inputs || inputs.length === 0) return;\n\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n\n    if (this.#format === 'rgb') {\n      const values = [rgb.r, rgb.g, rgb.b];\n      Array.from(inputs).forEach((inp, i) => {\n        inp.value = values[i];\n      });\n    } else if (this.#format === 'hsl') {\n      const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b, rgb.a);\n      const values = [hsl.h, hsl.s, hsl.l];\n      Array.from(inputs).forEach((inp, i) => {\n        inp.value = values[i];\n      });\n    } else {\n      const values = [rgb.r, rgb.g, rgb.b];\n      Array.from(inputs).forEach((inp, i) => {\n        inp.value = values[i];\n      });\n    }\n  }\n\n  #updateTrigger() {\n    if (!this.#trigger) return;\n\n    const swatch = this.#trigger.querySelector('.ren-color-picker-trigger-swatch');\n    const value = this.#trigger.querySelector('.ren-color-picker-trigger-value');\n\n    if (swatch) {\n      swatch.style.backgroundColor = this.#hsvToCss();\n    }\n\n    if (value) {\n      const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n      value.textContent = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n    }\n  }\n\n  #hsvToCss() {\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n    if (this.#hsv.a < 1) {\n      return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`;\n    }\n    return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;\n  }\n\n  /* ═════════════════════════════════════════════════════════════════\n     EVENT DISPATCHERS\n     ═════════════════════════════════════════════════════════════════ */\n\n  #emitInput() {\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n    const hex = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n    const rgbStr = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`;\n    const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b, rgb.a);\n    const hslStr = `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${hsl.a})`;\n\n    this.dispatchEvent(\n      new CustomEvent('ren-input', {\n        detail: { hex, rgb: rgbStr, hsl: hslStr, alpha: rgb.a },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  #emitChange() {\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n    const hex = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n    const rgbStr = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`;\n    const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b, rgb.a);\n    const hslStr = `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${hsl.a})`;\n\n    this.dispatchEvent(\n      new CustomEvent('ren-change', {\n        detail: { hex, rgb: rgbStr, hsl: hslStr, alpha: rgb.a },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═════════════════════════════════════════════════════════════════\n     PUBLIC API\n     ═════════════════════════════════════════════════════════════════ */\n\n  /**\n   * Set the color value\n   * Accepts: hex string, rgb/rgba string, hsl/hsla string, or color name\n   */\n  setValue(color) {\n    let rgb = null;\n\n    if (typeof color === 'string') {\n      // Try to parse as hex\n      rgb = hexToRgb(color);\n\n      // If not hex, assume it's rgb/hsl/color name\n      if (!rgb) {\n        // Use temporary element to parse CSS color\n        const div = document.createElement('div');\n        div.style.color = color;\n        document.body.appendChild(div);\n        const computed = getComputedStyle(div).color;\n        document.body.removeChild(div);\n\n        // Parse rgb() string\n        const match = computed.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*([\\d.]+))?\\)/);\n        if (match) {\n          rgb = {\n            r: parseInt(match[1]),\n            g: parseInt(match[2]),\n            b: parseInt(match[3]),\n            a: match[4] ? parseFloat(match[4]) : 1,\n          };\n        }\n      }\n    }\n\n    if (rgb) {\n      this.#hsv = rgbToHsv(rgb.r, rgb.g, rgb.b, rgb.a);\n    }\n  }\n\n  /**\n   * Get the current color value\n   * Returns: { hex, rgb, hsl, alpha }\n   */\n  getValue() {\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n    const hex = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n    const rgbStr = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`;\n    const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b, rgb.a);\n    const hslStr = `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${hsl.a})`;\n\n    return {\n      hex,\n      rgb: rgbStr,\n      hsl: hslStr,\n      alpha: rgb.a,\n    };\n  }\n}\n\ncustomElements.define('ren-color-picker', RenColorPicker);\n",
      "path": "components/composites/ren-color-picker",
      "id": "component:composite:ren-color-picker",
      "type": "component",
      "name": "ren-color-picker"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-combobox"
        ],
        "tokens": [
          "--color-accent",
          "--color-accent-subtle",
          "--color-border",
          "--color-fill",
          "--color-fill-active",
          "--color-fill-hover",
          "--color-surface",
          "--color-text",
          "--color-text-muted",
          "--duration-enter",
          "--duration-micro",
          "--duration-slow",
          "--ease-enter",
          "--leading-5",
          "--radius-full",
          "--radius-lg",
          "--radius-md",
          "--radius-sm",
          "--shadow-lg",
          "--space-0-5",
          "--space-1",
          "--space-2",
          "--space-3",
          "--space-8",
          "--text-sm",
          "--text-xs",
          "--transition-tactile"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-combobox Component Contract\n\nAutocomplete/select composite with input, listbox, and keyboard behavior.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-combobox` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-combobox` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Combobox composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"Users must filter / autocomplete from a large list using free-text input.\"\n    - \"You need a typeahead with keyboard navigation (Arrow Up/Down, Enter, Escape).\"\n    - \"Grouped options or items with secondary descriptions are needed.\"\n    - \"Async loading states (.ren-combobox-loading) or empty states (.ren-combobox-empty) are required.\"\n    - \"Single-select with a text-input affordance is the right control (input visible at rest).\"\n    - \"The inline list needs a preferred top/bottom opening side via placement without becoming a Popover API overlay.\"\n  avoidWhen:\n    - \"Users pick from a short fixed list without filtering — use ren-select.\"\n    - \"Multi-select with tag chips is required — use a dedicated multi-select / ren-multiselect.\"\n    - \"The input is a free-form search that submits a query — use ren-input + ren-search.\"\n    - \"The list is a navigation menu — use ren-menu / ren-command.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-combobox/ren-combobox.css\"\n  js:\n    - \"rends/components/composites/ren-combobox/ren-combobox.js\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"CSS works standalone on the .ren-combobox structure; <ren-combobox> JS adds ARIA wiring, filtering, highlight bookkeeping, and open/close.\"\n    - \"The component does not define --ren-combobox-* tokens; theme through semantic tokens or scope semantic token overrides.\"\n\nrequiredMarkup:\n  - \"Root <ren-combobox> wraps consumer-authored .ren-combobox-item options; its JS generates the input, listbox, hidden input, and live region.\"\n  - \"Each option is <div class=\\\"ren-combobox-item\\\" role=\\\"option\\\"> with aria-selected toggled by the component and data-highlighted reflecting keyboard focus.\"\n  - \"Use .ren-combobox-item-label and .ren-combobox-item-description inside items for two-line content; do not nest extra layout divs.\"\n  - \"Empty state lives in <div class=\\\"ren-combobox-empty\\\" hidden> and the loading state in <div class=\\\"ren-combobox-loading\\\" hidden>.\"\n  - \"Group items inside <div class=\\\"ren-combobox-group\\\"> with a leading <div class=\\\"ren-combobox-group-label\\\">.\"\n  - \"Use placement=\\\"bottom\\\" by default; the host and .ren-combobox-list mirror placement to data-side. Top changes the absolute list fallback to open above the input.\"\n\nforbiddenPatterns:\n  - \"Replacing <input> with a contentEditable div — breaks IME, autofill, and form submission.\"\n  - \"Using <ul>/<li> for the list when the component's CSS targets .ren-combobox-list / .ren-combobox-item with role=\\\"listbox\\\" / role=\\\"option\\\".\"\n  - \"Using :hover styling alone to indicate keyboard highlight; rely on [data-highlighted] and aria-selected=\\\"true\\\".\"\n  - \"Toggling visibility with display: none on the list; use the [hidden] attribute (CSS handles the close transition).\"\n  - \"Hardcoded dropdown widths; the list uses inset-inline: 0 to match the input width — override --space-* / --radius-* tokens instead.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic tokens: --color-surface, --color-border, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-fill-active, --color-accent, --color-accent-subtle.\"\n    - \"Typography / layout: --text-xs, --text-sm, --leading-5, --space-*, --radius-sm, --radius-md, --radius-lg, --radius-full, --shadow-lg.\"\n    - \"Motion tokens: --duration-micro, --duration-enter, --duration-slow, --ease-enter, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Defining new --ren-combobox-* component tokens — the component intentionally inherits semantic tokens.\"\n\naccessibility:\n  required:\n    - \"Input has role=\\\"combobox\\\", aria-expanded reflecting list visibility, aria-controls pointing to the .ren-combobox-list id, and aria-activedescendant pointing to the highlighted option.\"\n    - \"List has role=\\\"listbox\\\" and each item has role=\\\"option\\\"; do not nest interactive controls inside an option.\"\n    - \"Keyboard contract: Arrow Down opens / moves down, Arrow Up moves up, Enter selects, Escape closes; preserve all four.\"\n    - \"Selected option sets aria-selected=\\\"true\\\"; selection is announced — never communicate via color alone.\"\n    - \"Disabled options set aria-disabled=\\\"true\\\" AND ignore activation in JS (pointer-events styling is not enough).\"\n    - \"Loading state uses an aria-live region (or aria-busy on the input) so AT users know results are pending.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-combobox/ren-combobox.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-combobox/ren-combobox.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-combobox name=\"country\" placeholder=\"Search a country\">\n  <div class=\"ren-combobox-item\" data-value=\"ar\">Argentina</div>\n  <div class=\"ren-combobox-item\" data-value=\"uy\">Uruguay</div>\n</ren-combobox>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-combobox`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[aria-selected]`\n- `[data-side]`\n- `[data-highlighted]`\n- `placement`\n- `:disabled`\n- `:hover`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-combobox/ren-combobox.css`\n- `components/composites/ren-combobox/ren-combobox.js`\n- `docs/components/ren-combobox.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/**\n * RenDS — Combobox\n * =================\n * Light-DOM combobox. CSS works standalone; <ren-combobox> layers ARIA\n * and behavior on top.\n *\n * Markup:\n *   <div class=\"ren-combobox\">\n *     <input class=\"ren-combobox-input\">\n *     <div class=\"ren-combobox-list\" role=\"listbox\" hidden>\n *       <div class=\"ren-combobox-item\" role=\"option\">…</div>\n *     </div>\n *   </div>\n */\n\n.ren-combobox {\n  position: relative;\n  display: block;\n  width: 100%;\n\n  &-input {\n    width: 100%;\n    padding-block: var(--space-2);\n    padding-inline: var(--space-3);\n\n    border: 1px solid var(--color-border);\n    border-radius: var(--radius-md);\n    background: var(--color-surface);\n\n    font-family: inherit;\n    font-size: var(--text-sm);\n    line-height: var(--leading-5);\n    color: var(--color-text);\n\n    transition: var(--transition-tactile);\n\n    &::placeholder {\n      color: var(--color-text-muted);\n    }\n\n    &:focus {\n      outline: none;\n      border-color: var(--color-accent);\n      box-shadow: 0 0 0 3px var(--color-accent-subtle);\n    }\n\n    &:disabled {\n      background: var(--color-fill);\n      color: var(--color-text-muted);\n      cursor: not-allowed;\n    }\n\n    /* Hide native type=\"search\" decorations */\n    &::-webkit-search-cancel-button,\n    &::-webkit-search-decoration {\n      -webkit-appearance: none;\n      appearance: none;\n      display: none;\n    }\n  }\n\n  &-list {\n    position: absolute;\n    inset-inline: 0;\n    top: calc(100% + var(--space-1));\n    z-index: 50;\n\n    max-height: 15rem;\n    padding-block: var(--space-1);\n\n    background: var(--color-surface);\n    border: 1px solid var(--color-border);\n    border-radius: var(--radius-lg);\n    box-shadow: var(--shadow-lg);\n\n    overflow-y: auto;\n    overflow-x: hidden;\n\n    /* Scrollbar styling */\n    scrollbar-width: thin;\n    scrollbar-color: var(--color-fill-active) transparent;\n\n    &::-webkit-scrollbar {\n      width: 6px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      background: var(--color-fill-active);\n      border-radius: var(--radius-full);\n    }\n\n    &::-webkit-scrollbar-thumb:hover {\n      background: var(--color-fill-hover);\n    }\n\n    &[hidden] {\n      display: none;\n    }\n\n    /* Open animation */\n    @starting-style {\n      opacity: 0;\n      translate: 0 -4px;\n    }\n    animation: ren-combobox-open var(--duration-enter) var(--ease-enter);\n\n    @media (prefers-reduced-motion: reduce) {\n      animation: none;\n    }\n  }\n\n  &[data-side=\"top\"] &-list,\n  &-list[data-side=\"top\"] {\n    top: auto;\n    bottom: calc(100% + var(--space-1));\n  }\n\n  &[data-side=\"bottom\"] &-list,\n  &-list[data-side=\"bottom\"] {\n    top: calc(100% + var(--space-1));\n    bottom: auto;\n  }\n\n  &-item {\n    padding: var(--space-2) var(--space-3);\n    cursor: pointer;\n    border-radius: var(--radius-sm);\n\n    display: flex;\n    flex-direction: column;\n    gap: var(--space-0-5, 2px);\n\n    font-size: var(--text-sm);\n    color: var(--color-text);\n\n    transition: background-color var(--duration-micro) var(--ease-enter);\n\n    &[hidden] {\n      display: none;\n    }\n\n    &[data-highlighted] {\n      background: var(--color-fill);\n    }\n\n    &[aria-selected=\"true\"] {\n      background: var(--color-accent-subtle);\n      color: var(--color-accent);\n      font-weight: 500;\n    }\n\n    &[aria-disabled=\"true\"] {\n      opacity: 0.5;\n      cursor: not-allowed;\n\n      &:hover,\n      &[data-highlighted] {\n        background: transparent;\n      }\n    }\n  }\n\n  &-item-label {\n    font-weight: 500;\n    color: var(--color-text);\n  }\n\n  &-item-description {\n    font-size: var(--text-xs);\n    color: var(--color-text-muted);\n  }\n\n  &-empty {\n    padding: var(--space-8) var(--space-3);\n    text-align: center;\n    color: var(--color-text-muted);\n    font-size: var(--text-sm);\n\n    &[hidden] {\n      display: none;\n    }\n  }\n\n  &-loading {\n    padding: var(--space-3);\n    text-align: center;\n    color: var(--color-text-muted);\n    font-size: var(--text-sm);\n\n    &[hidden] {\n      display: none;\n    }\n\n    &::before {\n      content: '';\n      display: inline-block;\n      width: 0.875rem;\n      height: 0.875rem;\n      margin-inline-end: var(--space-2);\n      vertical-align: -2px;\n\n      border: 2px solid var(--color-border);\n      border-top-color: var(--color-accent);\n      border-radius: var(--radius-full);\n\n      animation: ren-combobox-spin var(--duration-slow, 800ms) linear infinite;\n\n      @media (prefers-reduced-motion: reduce) {\n        animation: none;\n      }\n    }\n  }\n\n  &-group {\n    & + & {\n      margin-top: var(--space-1);\n      border-top: 1px solid var(--color-border);\n      padding-top: var(--space-1);\n    }\n\n    &[hidden] {\n      display: none;\n    }\n  }\n\n  &-group-label {\n    padding: var(--space-2) var(--space-3);\n    padding-bottom: var(--space-1);\n\n    font-size: var(--text-xs);\n    font-weight: 600;\n    text-transform: uppercase;\n    letter-spacing: 0.05em;\n    color: var(--color-text-muted);\n  }\n}\n\n@keyframes ren-combobox-open {\n  from {\n    opacity: 0;\n    translate: 0 -4px;\n  }\n  to {\n    opacity: 1;\n    translate: 0 0;\n  }\n}\n\n@keyframes ren-combobox-spin {\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n\n/**\n * RenDS — <ren-combobox> Web Component\n * =====================================\n * Searchable, accessible combobox built on light DOM.\n *\n * Progressive enhancement: the markup ships with classes that work\n * standalone (.ren-combobox, .ren-combobox-input, .ren-combobox-list,\n * .ren-combobox-item). The Web Component layers ARIA wiring, keyboard\n * navigation, filtering, and a hidden form input on top.\n *\n * No Shadow DOM — light DOM only, like the rest of RenDS.\n *\n * Markup:\n *   <ren-combobox name=\"country\" placeholder=\"Search a country…\" value=\"ar\">\n *     <div class=\"ren-combobox-item\" data-value=\"ar\">Argentina</div>\n *     <div class=\"ren-combobox-item\" data-value=\"br\">Brazil</div>\n *     <div class=\"ren-combobox-item\" data-value=\"cl\">Chile</div>\n *   </ren-combobox>\n *\n * Attributes:\n *   name        — form field name (a hidden input with this name carries the value)\n *   value       — initial selected data-value\n *   placeholder — input placeholder (default: \"Search...\")\n *   disabled    — locks the input\n *   async       — suppresses local filtering; consumer provides items via ren-search\n *\n * Public methods:\n *   .value          — getter / setter for the selected value\n *   .open()         — show the list\n *   .close()        — hide the list\n *   .setLoading(b)  — toggle the loading row\n *   .setItems(arr)  — replace items, each { value, label, description? }\n *\n * Events (all bubble):\n *   ren-change  — { value, item }   selection changed\n *   ren-search  — { query }         user typed (use for async loading)\n *   ren-open / ren-close            list visibility\n */\n\nimport { autoId } from '../../../utils/id-generator.js';\n\nconst COMBOBOX_SIDES = new Set(['top', 'right', 'bottom', 'left']);\n\nfunction normalizeComboboxSide(value) {\n  const side = String(value || 'bottom').toLowerCase().split('-')[0];\n\n  return COMBOBOX_SIDES.has(side) ? side : 'bottom';\n}\n\nexport class RenCombobox extends HTMLElement {\n  static get observedAttributes() {\n    return ['value', 'placeholder', 'disabled', 'placement'];\n  }\n\n  #input;\n  #list;\n  #empty;\n  #loading;\n  #hidden;\n  #liveRegion;\n  #isOpen = false;\n  #highlightedIndex = -1;\n  #upgraded = false;\n  #onDocClick;\n\n  connectedCallback() {\n    if (this.#upgraded) return;\n    this.#upgraded = true;\n    this.#enhance();\n    this.#wire();\n\n    // Apply initial value if present\n    const initial = this.getAttribute('value');\n    if (initial) {\n      this.#selectByValue(initial, { silent: true });\n    }\n  }\n\n  disconnectedCallback() {\n    if (this.#onDocClick) {\n      document.removeEventListener('click', this.#onDocClick);\n    }\n  }\n\n  attributeChangedCallback(name, oldVal, newVal) {\n    if (!this.#upgraded) return;\n    if (name === 'placeholder' && this.#input) {\n      this.#input.placeholder = newVal || 'Search...';\n    } else if (name === 'disabled' && this.#input) {\n      this.#input.disabled = this.hasAttribute('disabled');\n    } else if (name === 'value' && newVal !== oldVal) {\n      this.#selectByValue(newVal, { silent: true });\n    } else if (name === 'placement' && newVal !== oldVal) {\n      this.#syncPlacement();\n    }\n  }\n\n  /* ─── Enhancement: take light-DOM children and wire structure ─── */\n\n  #enhance() {\n    this.classList.add('ren-combobox');\n\n    // Items: anything the consumer wrote inside (items, groups, labels).\n    // Move them into a list container.\n    const fragment = document.createDocumentFragment();\n    while (this.firstChild) {\n      fragment.appendChild(this.firstChild);\n    }\n\n    // Input. type=\"search\" + an unguessable autocomplete value defeats the\n    // Chromium heuristic that autofills \"country\"-flavored fields.\n    this.#input = document.createElement('input');\n    this.#input.type = 'search';\n    this.#input.className = 'ren-combobox-input';\n    this.#input.setAttribute('autocomplete', 'ren-combobox-no-autofill');\n    this.#input.setAttribute('autocorrect', 'off');\n    this.#input.setAttribute('autocapitalize', 'off');\n    this.#input.setAttribute('data-1p-ignore', '');\n    this.#input.setAttribute('data-lpignore', 'true');\n    this.#input.spellcheck = false;\n    this.#input.placeholder = this.getAttribute('placeholder') || 'Search...';\n    if (this.hasAttribute('disabled')) this.#input.disabled = true;\n    autoId(this.#input, 'combobox-input');\n\n    // List\n    this.#list = document.createElement('div');\n    this.#list.className = 'ren-combobox-list';\n    this.#list.hidden = true;\n    autoId(this.#list, 'combobox-list');\n\n    // Move user content into the list\n    this.#list.appendChild(fragment);\n\n    // Ensure an empty-state row exists\n    this.#empty = this.#list.querySelector('.ren-combobox-empty');\n    if (!this.#empty) {\n      this.#empty = document.createElement('div');\n      this.#empty.className = 'ren-combobox-empty';\n      this.#empty.textContent = 'No results';\n      this.#empty.hidden = true;\n      this.#list.appendChild(this.#empty);\n    }\n\n    // Loading row (hidden by default)\n    this.#loading = this.#list.querySelector('.ren-combobox-loading');\n    if (!this.#loading) {\n      this.#loading = document.createElement('div');\n      this.#loading.className = 'ren-combobox-loading';\n      this.#loading.textContent = 'Loading…';\n      this.#loading.hidden = true;\n      this.#list.insertBefore(this.#loading, this.#list.firstChild);\n    }\n\n    // Hidden input for form submission\n    this.#hidden = document.createElement('input');\n    this.#hidden.type = 'hidden';\n    if (this.hasAttribute('name')) this.#hidden.name = this.getAttribute('name');\n\n    // Live region for screen-reader announcements\n    this.#liveRegion = document.createElement('div');\n    this.#liveRegion.className = 'ren-sr-only';\n    this.#liveRegion.setAttribute('role', 'status');\n    this.#liveRegion.setAttribute('aria-live', 'polite');\n\n    // ARIA wiring\n    this.#input.setAttribute('role', 'combobox');\n    this.#input.setAttribute('aria-autocomplete', 'list');\n    this.#input.setAttribute('aria-expanded', 'false');\n    this.#input.setAttribute('aria-controls', this.#list.id);\n    this.#list.setAttribute('role', 'listbox');\n\n    this.#decorateItems();\n\n    // Append in canonical order\n    this.appendChild(this.#input);\n    this.appendChild(this.#list);\n    this.appendChild(this.#hidden);\n    this.appendChild(this.#liveRegion);\n\n    this.#syncPlacement();\n  }\n\n  #syncPlacement() {\n    const side = normalizeComboboxSide(this.getAttribute('placement'));\n\n    this.setAttribute('data-side', side);\n    if (this.#list) {\n      this.#list.setAttribute('data-side', side);\n    }\n  }\n\n  #decorateItems() {\n    const items = this.#getItems();\n    items.forEach((item) => {\n      if (!item.hasAttribute('role')) item.setAttribute('role', 'option');\n      autoId(item, 'combobox-opt');\n      if (!item.hasAttribute('aria-selected')) {\n        item.setAttribute('aria-selected', 'false');\n      }\n      if (!item.hasAttribute('tabindex')) item.setAttribute('tabindex', '-1');\n    });\n  }\n\n  #getItems() {\n    return Array.from(this.#list.querySelectorAll('.ren-combobox-item'));\n  }\n\n  #getVisibleItems() {\n    return this.#getItems().filter((i) => !i.hidden);\n  }\n\n  /* ─── Event wiring ─── */\n\n  #wire() {\n    // Open on click (not focus): programmatic .focus() after selection\n    // shouldn't reopen the list.\n    this.#input.addEventListener('mousedown', () => this.open());\n    this.#input.addEventListener('input', (e) => this.#onInput(e));\n    this.#input.addEventListener('keydown', (e) => this.#onKeydown(e));\n\n    // Item selection (delegated)\n    this.#list.addEventListener('click', (e) => {\n      const item = e.target.closest('.ren-combobox-item');\n      if (item && !item.hasAttribute('aria-disabled')) {\n        this.#selectItem(item);\n      }\n    });\n\n    this.#list.addEventListener('mouseover', (e) => {\n      const item = e.target.closest('.ren-combobox-item');\n      if (!item || item.hidden) return;\n      const visible = this.#getVisibleItems();\n      const idx = visible.indexOf(item);\n      if (idx !== -1) this.#highlight(idx);\n    });\n\n    // Close on outside click\n    this.#onDocClick = (e) => {\n      if (!this.contains(e.target)) this.close();\n    };\n    document.addEventListener('click', this.#onDocClick);\n  }\n\n  #onInput(e) {\n    const query = e.target.value;\n\n    if (this.hasAttribute('async')) {\n      // Async mode: emit ren-search and let the consumer replace items\n      this.dispatchEvent(\n        new CustomEvent('ren-search', {\n          detail: { query },\n          bubbles: true,\n        })\n      );\n    } else {\n      this.#filter(query);\n    }\n\n    if (!this.#isOpen) this.open();\n  }\n\n  #onKeydown(e) {\n    const visible = this.#getVisibleItems();\n\n    switch (e.key) {\n      case 'ArrowDown':\n        e.preventDefault();\n        if (!this.#isOpen) this.open();\n        this.#highlight(Math.min(this.#highlightedIndex + 1, visible.length - 1));\n        break;\n      case 'ArrowUp':\n        e.preventDefault();\n        if (this.#isOpen) {\n          this.#highlight(Math.max(this.#highlightedIndex - 1, 0));\n        }\n        break;\n      case 'Home':\n        if (this.#isOpen) {\n          e.preventDefault();\n          this.#highlight(0);\n        }\n        break;\n      case 'End':\n        if (this.#isOpen) {\n          e.preventDefault();\n          this.#highlight(visible.length - 1);\n        }\n        break;\n      case 'Enter':\n        if (this.#isOpen && visible[this.#highlightedIndex]) {\n          e.preventDefault();\n          this.#selectItem(visible[this.#highlightedIndex]);\n        }\n        break;\n      case 'Escape':\n        if (this.#isOpen) {\n          e.preventDefault();\n          this.close();\n        } else if (this.#input.value) {\n          this.#input.value = '';\n          this.#filter('');\n        }\n        break;\n    }\n  }\n\n  /* ─── Filtering ─── */\n\n  #filter(query) {\n    const needle = (query || '').toLowerCase().trim();\n    const items = this.#getItems();\n\n    items.forEach((item) => {\n      const match = !needle || item.textContent.toLowerCase().includes(needle);\n      item.hidden = !match;\n    });\n\n    // Hide groups whose items are all hidden\n    this.querySelectorAll('.ren-combobox-group').forEach((group) => {\n      const groupItems = group.querySelectorAll('.ren-combobox-item');\n      const anyVisible = Array.from(groupItems).some((i) => !i.hidden);\n      group.hidden = !anyVisible;\n    });\n\n    const anyVisible = items.some((i) => !i.hidden);\n    this.#empty.hidden = anyVisible;\n    this.#highlightedIndex = -1;\n\n    // Clear ARIA active descendant\n    this.#input.removeAttribute('aria-activedescendant');\n\n    // Announce result count\n    const count = items.filter((i) => !i.hidden).length;\n    if (this.#liveRegion) {\n      this.#liveRegion.textContent = count\n        ? `${count} result${count === 1 ? '' : 's'} available`\n        : 'No results';\n    }\n  }\n\n  /* ─── Highlight management (aria-activedescendant pattern) ─── */\n\n  #highlight(index) {\n    const visible = this.#getVisibleItems();\n    if (!visible.length) {\n      this.#highlightedIndex = -1;\n      this.#input.removeAttribute('aria-activedescendant');\n      return;\n    }\n\n    this.#getItems().forEach((i) => i.removeAttribute('data-highlighted'));\n\n    const clamped = Math.max(0, Math.min(index, visible.length - 1));\n    const target = visible[clamped];\n    target.setAttribute('data-highlighted', '');\n    this.#input.setAttribute('aria-activedescendant', target.id);\n    this.#highlightedIndex = clamped;\n\n    // Best-effort scroll; not critical if unsupported.\n    if (typeof target.scrollIntoView === 'function') {\n      try {\n        target.scrollIntoView({ block: 'nearest' });\n      } catch {}\n    }\n  }\n\n  /* ─── Selection ─── */\n\n  #selectItem(item) {\n    const value = item.getAttribute('data-value') || item.textContent.trim();\n    const label = item.textContent.trim();\n\n    this.#input.value = label;\n    this.#hidden.value = value;\n    this.setAttribute('value', value);\n\n    this.#getItems().forEach((i) => {\n      i.setAttribute('aria-selected', i === item ? 'true' : 'false');\n    });\n\n    this.close();\n    this.#input.focus();\n\n    this.dispatchEvent(\n      new CustomEvent('ren-change', {\n        detail: { value, item },\n        bubbles: true,\n      })\n    );\n  }\n\n  #selectByValue(value, { silent = false } = {}) {\n    const item = this.#getItems().find(\n      (i) => i.getAttribute('data-value') === value\n    );\n    if (!item) return;\n\n    if (silent) {\n      this.#input.value = item.textContent.trim();\n      this.#hidden.value = value;\n      this.#getItems().forEach((i) => {\n        i.setAttribute('aria-selected', i === item ? 'true' : 'false');\n      });\n    } else {\n      this.#selectItem(item);\n    }\n  }\n\n  /* ─── Public API ─── */\n\n  open() {\n    if (this.#isOpen) return;\n    this.#isOpen = true;\n    this.#list.hidden = false;\n    this.#input.setAttribute('aria-expanded', 'true');\n    this.dispatchEvent(new CustomEvent('ren-open', { bubbles: true }));\n  }\n\n  close() {\n    if (!this.#isOpen) return;\n    this.#isOpen = false;\n    this.#list.hidden = true;\n    this.#input.setAttribute('aria-expanded', 'false');\n    this.#input.removeAttribute('aria-activedescendant');\n    this.#getItems().forEach((i) => i.removeAttribute('data-highlighted'));\n    this.#highlightedIndex = -1;\n    this.dispatchEvent(new CustomEvent('ren-close', { bubbles: true }));\n  }\n\n  setLoading(isLoading) {\n    if (!this.#loading) return;\n    this.#loading.hidden = !isLoading;\n    if (isLoading) this.#empty.hidden = true;\n  }\n\n  setItems(items) {\n    // Clear existing items (preserve groups, empty, loading)\n    this.#getItems().forEach((i) => i.remove());\n\n    const frag = document.createDocumentFragment();\n    items.forEach(({ value, label, description, disabled }) => {\n      const item = document.createElement('div');\n      item.className = 'ren-combobox-item';\n      item.setAttribute('role', 'option');\n      item.setAttribute('aria-selected', 'false');\n      item.setAttribute('tabindex', '-1');\n      autoId(item, 'combobox-opt');\n      item.dataset.value = value;\n      if (disabled) item.setAttribute('aria-disabled', 'true');\n\n      if (description) {\n        const main = document.createElement('div');\n        main.className = 'ren-combobox-item-label';\n        main.textContent = label;\n        const desc = document.createElement('div');\n        desc.className = 'ren-combobox-item-description';\n        desc.textContent = description;\n        item.append(main, desc);\n      } else {\n        item.textContent = label;\n      }\n      frag.appendChild(item);\n    });\n\n    this.#list.insertBefore(frag, this.#empty);\n    this.#empty.hidden = items.length > 0;\n    this.#highlightedIndex = -1;\n  }\n\n  get value() {\n    return this.#hidden ? this.#hidden.value : '';\n  }\n\n  set value(val) {\n    if (val == null || val === '') {\n      if (this.#input) this.#input.value = '';\n      if (this.#hidden) this.#hidden.value = '';\n      this.#getItems().forEach((i) => i.setAttribute('aria-selected', 'false'));\n      this.removeAttribute('value');\n    } else {\n      this.setAttribute('value', val);\n    }\n  }\n}\n\nif (!customElements.get('ren-combobox')) {\n  customElements.define('ren-combobox', RenCombobox);\n}\n",
      "path": "components/composites/ren-combobox",
      "id": "component:composite:ren-combobox",
      "type": "component",
      "name": "ren-combobox"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-context-menu",
          ".ren-menu-item",
          ".ren-menu-separator"
        ],
        "tokens": [
          "--color-border",
          "--color-surface",
          "--duration-enter",
          "--ease-enter",
          "--radius-lg",
          "--shadow-lg",
          "--space-1",
          "--stroke-1"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-context-menu Component Contract\n\nContextual action menu opened from a target or pointer interaction.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-context-menu` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-context-menu` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Context Menu composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"A target area needs a right-click (or long-press) action menu that opens at the pointer.\"\n    - \"The menu hosts imperative actions (cut / copy / paste / delete) rather than navigation.\"\n    - \"You want the native Popover API plus the shared ren-menu item styling.\"\n    - \"Positioning should follow the pointer instead of a static anchor.\"\n    - \"A scoped trigger region is needed (declared via data-context=\\\"<menu-id>\\\").\"\n  avoidWhen:\n    - \"The menu opens from a button click — use ren-menu (dropdown) instead.\"\n    - \"The trigger is a hover affordance — use ren-popover / ren-hover-card.\"\n    - \"The menu is the primary navigation — use ren-menubar / ren-sidebar.\"\n    - \"You need cascading submenus — use ren-menu (supports nested patterns).\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-context-menu/ren-context-menu.css\"\n    - \"rends/components/composites/ren-menu/ren-menu.css\"\n  js:\n    - \"rends/components/composites/ren-context-menu/ren-context-menu.js\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"ren-menu.css must be present alongside this CSS — the menu items (.ren-menu-item, .ren-menu-separator) live there and are reused.\"\n    - \"JS handles contextmenu event interception, pointer-based positioning, and popover open/close.\"\n\nrequiredMarkup:\n  - \"Declare the trigger region with data-context=\\\"<menu-id>\\\" on any element; do not use inline oncontextmenu attributes.\"\n  - \"Menu root is <ren-context-menu id=\\\"<menu-id>\\\">; the component applies the .ren-context-menu, role=menu, and manual popover semantics.\"\n  - \"Items are real <button class=\\\"ren-menu-item\\\"> elements; separators are <hr class=\\\"ren-menu-separator\\\">.\"\n  - \"Destructive actions use .ren-menu-item-danger (defined in ren-menu.css); do not invent custom danger classes.\"\n  - \"Position is set by JS — do not write inline top/left on the menu element.\"\n\nforbiddenPatterns:\n  - \"Opening the menu via custom JS that calls .style.display = 'block' — use showPopover() so the API handles light dismiss + the top layer.\"\n  - \"<div role=\\\"menuitem\\\" tabindex=\\\"0\\\"> styled as a button; use a real <button class=\\\"ren-menu-item\\\">.\"\n  - \"Static anchored positioning (top: 0; left: 0) — context menus must follow the contextmenu event coordinates.\"\n  - \"Suppressing the native context menu globally (document.oncontextmenu = e => e.preventDefault()) — scope prevention to data-context regions only.\"\n  - \"Hardcoded box-shadow / hex backgrounds; use --shadow-lg and --color-surface.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic tokens: --color-surface, --color-border, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-fill-active, --color-danger, --color-accent.\"\n    - \"Layout / motion tokens: --space-*, --stroke-1, --radius-lg, --shadow-lg, --duration-enter, --ease-enter.\"\n    - \"Reuses --ren-menu-* tokens defined by ren-menu (item padding, hover bg, etc.) — override at the ren-menu scope.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Defining new --ren-context-menu-* component tokens — the component inherits from ren-menu intentionally.\"\n\naccessibility:\n  required:\n    - \"Menu root has role=\\\"menu\\\" (set by JS) and each .ren-menu-item has role=\\\"menuitem\\\"; do not nest interactive controls inside items.\"\n    - \"Keyboard: Arrow Up/Down moves focus across items, Home/End jump to first/last, Escape closes the menu, Enter / Space activates.\"\n    - \"Trigger must be reachable by keyboard via Shift+F10 (or the dedicated context-menu key) — never gate the menu behind right-click only.\"\n    - \"Disabled items set aria-disabled=\\\"true\\\" AND skip in keyboard navigation order.\"\n    - \"Focus moves into the menu on open and returns to the trigger on close — preserve this when extending behavior.\"\n    - \"Reduced motion: open transition is disabled under prefers-reduced-motion (already handled).\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-context-menu/ren-context-menu.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-context-menu/ren-context-menu.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div data-context=\"editor-actions\" tabindex=\"0\">Right-click me</div>\n<ren-context-menu id=\"editor-actions\">\n  <button class=\"ren-menu-item\" role=\"menuitem\">Copy</button>\n  <button class=\"ren-menu-item\" role=\"menuitem\">Paste</button>\n</ren-context-menu>\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-context-menu`\n- `.ren-menu-item`\n- `.ren-menu-separator`\n- `.ren-open`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[data-context]`\n- `trigger-id`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-context-menu/ren-context-menu.css`\n- `components/composites/ren-context-menu/ren-context-menu.js`\n- `docs/components/ren-context-menu.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Context Menu Component\n   ============================================\n   Right-click menu using Popover API.\n   Reuses ren-menu styles for items.\n   Minimal JS for right-click trigger + positioning.\n\n   Usage:\n     <div class=\"ren-context-menu-trigger\" data-context=\"my-ctx-menu\">\n       Right-click this area\n     </div>\n\n     <div id=\"my-ctx-menu\" class=\"ren-context-menu\" popover>\n       <button class=\"ren-menu-item\">Cut</button>\n       <button class=\"ren-menu-item\">Copy</button>\n       <button class=\"ren-menu-item\">Paste</button>\n       <hr class=\"ren-menu-separator\">\n       <button class=\"ren-menu-item ren-menu-item-danger\">Delete</button>\n     </div>\n   ============================================ */\n\n.ren-context-menu {\n  /* Reset popover defaults */\n  margin: 0;\n  padding: 0;\n  border: none;\n  background: none;\n  inset: auto;\n\n  /* Menu appearance (matches ren-menu) */\n  background: var(--color-surface);\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  padding-block: var(--space-1);\n  min-width: 10rem;\n  max-height: calc(100vh - 32px);\n  overflow-y: auto;\n  overscroll-behavior: contain;\n\n  /* Position set by JS */\n  position: fixed;\n\n  /* Animation — semantic tokens keep context-menu in sync with\n     ren-menu / ren-popover. Collapses to 0ms under reduce. */\n  opacity: 0;\n  transform: scale(0.95);\n  transform-origin: top left;\n  transition:\n    opacity var(--duration-enter) var(--ease-enter),\n    transform var(--duration-enter) var(--ease-enter),\n    overlay var(--duration-enter) var(--ease-enter) allow-discrete,\n    display var(--duration-enter) var(--ease-enter) allow-discrete;\n}\n\n.ren-context-menu:popover-open,\n.ren-context-menu.ren-open {\n  opacity: 1;\n  transform: scale(1);\n}\n\n@starting-style {\n  .ren-context-menu:popover-open {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n}\n\n/* ─── Reuse menu item styles ─── */\n/* Already defined in ren-menu.css:\n   .ren-menu-item, .ren-menu-separator, etc.\n   Import ren-menu.css alongside this file.\n*/\n\n/* ─── Reduced motion ─── */\n@media (prefers-reduced-motion: reduce) {\n  .ren-context-menu {\n    transition: none;\n  }\n}\n\n\n/* ============================================\n   RenDS — Context Menu Controller\n   ============================================\n   Handles right-click trigger, positioning,\n   and keyboard navigation.\n\n   Usage:\n     import { initContextMenu } from './ren-context-menu.js';\n     initContextMenu('my-ctx-menu');\n\n   Or auto-init all:\n     import { initAllContextMenus } from './ren-context-menu.js';\n     initAllContextMenus();\n   ============================================ */\n\n/**\n * @param {string|HTMLElement} menuOrId - The popover element or its ID\n */\nexport function initContextMenu(menuOrId) {\n  const menu = typeof menuOrId === 'string'\n    ? document.getElementById(menuOrId)\n    : menuOrId;\n  if (!menu) return;\n\n  if (menu.dataset.renContextMenuInitialized === 'true') return;\n  menu.dataset.renContextMenuInitialized = 'true';\n  const controller = new AbortController();\n  menu.__renContextController = controller;\n  let returnTrigger = null;\n\n  menu.setAttribute('popover', 'manual');\n  menu.setAttribute('role', 'menu');\n  menu.classList.add('ren-context-menu');\n\n  // Find all triggers for this menu\n  const triggers = [...document.querySelectorAll(`[data-context=\"${menu.id}\"]`)];\n  const triggerId = menu.getAttribute('trigger-id');\n  const explicitTrigger = triggerId ? document.getElementById(triggerId) : null;\n  if (explicitTrigger && !triggers.includes(explicitTrigger)) triggers.push(explicitTrigger);\n  const itemSelector = [\n    '.ren-menu-item:not(:disabled):not([aria-disabled=\"true\"])',\n    '[role=\"menuitem\"]:not(:disabled):not([aria-disabled=\"true\"])',\n  ].join(', ');\n\n  const isOpen = () => menu.matches(':popover-open') || menu.classList.contains('ren-open');\n\n  const close = () => {\n    if (!isOpen()) return;\n\n    if ('hidePopover' in menu && menu.matches(':popover-open')) {\n      try {\n        menu.hidePopover();\n      } catch {\n        // Popover may have already been closed by the browser.\n      }\n    } else {\n      menu.classList.remove('ren-open');\n    }\n\n    menu.setAttribute('data-state', 'closed');\n    if (returnTrigger && document.contains(returnTrigger)) returnTrigger.focus();\n  };\n\n  const show = (x, y, target) => {\n    if (isOpen()) {\n      close();\n    }\n\n    // Position menu at cursor\n    menu.style.left = `${x}px`;\n    menu.style.top = `${y}px`;\n\n    if ('showPopover' in menu) {\n      try {\n        menu.showPopover();\n      } catch {\n        // Popover may already be open during rapid repeated contextmenu events.\n      }\n    } else {\n      menu.classList.add('ren-open');\n    }\n\n    menu.setAttribute('data-state', 'open');\n\n    // Adjust if overflows viewport\n    const rect = menu.getBoundingClientRect();\n    if (rect.right > window.innerWidth) {\n      menu.style.left = `${Math.max(8, x - rect.width)}px`;\n    }\n    if (rect.bottom > window.innerHeight) {\n      menu.style.top = `${Math.max(8, y - rect.height)}px`;\n    }\n\n    // Focus first item\n    const firstItem = menu.querySelector(itemSelector);\n    firstItem?.focus();\n\n    menu.dispatchEvent(new CustomEvent('ren-context-menu-open', {\n      bubbles: true,\n      composed: true,\n      detail: { x, y, target },\n    }));\n  };\n\n  triggers.forEach((trigger) => {\n    trigger.addEventListener('contextmenu', (e) => {\n      e.preventDefault();\n      returnTrigger = trigger;\n      show(e.clientX, e.clientY, e.target);\n    }, { signal: controller.signal });\n\n    trigger.addEventListener('keydown', (e) => {\n      if (e.key !== 'ContextMenu' && !(e.key === 'F10' && e.shiftKey)) return;\n\n      e.preventDefault();\n      const rect = trigger.getBoundingClientRect();\n      returnTrigger = trigger;\n      show(rect.left + 8, rect.bottom + 8, e.target);\n    }, { signal: controller.signal });\n  });\n\n  // Keyboard navigation inside menu\n  menu.addEventListener('keydown', (e) => {\n    const items = [...menu.querySelectorAll(itemSelector)];\n    if (items.length === 0) return;\n\n    const current = items.indexOf(document.activeElement);\n\n    if (e.key === 'ArrowDown') {\n      e.preventDefault();\n      items[(current + 1) % items.length]?.focus();\n    } else if (e.key === 'ArrowUp') {\n      e.preventDefault();\n      items[(current - 1 + items.length) % items.length]?.focus();\n    } else if (e.key === 'Home') {\n      e.preventDefault();\n      items[0]?.focus();\n    } else if (e.key === 'End') {\n      e.preventDefault();\n      items.at(-1)?.focus();\n    } else if (e.key === 'Escape') {\n      close();\n    } else if (e.key === 'Enter' || e.key === ' ') {\n      e.preventDefault();\n      document.activeElement?.click();\n      close();\n    }\n  }, { signal: controller.signal });\n\n  // Close on item click\n  menu.addEventListener('click', (e) => {\n    if (e.target.closest('.ren-menu-item')) {\n      close();\n    }\n  }, { signal: controller.signal });\n\n  const isMenuOrTrigger = (target) =>\n    target instanceof Node &&\n    (menu.contains(target) || triggers.some((trigger) => trigger.contains(target)));\n\n  document.addEventListener(\n    'pointerdown',\n    (e) => {\n      if (isOpen() && !isMenuOrTrigger(e.target)) {\n        close();\n      }\n    },\n    { capture: true, signal: controller.signal }\n  );\n\n  document.addEventListener(\n    'contextmenu',\n    (e) => {\n      if (isOpen() && !isMenuOrTrigger(e.target)) {\n        close();\n      }\n    },\n    { capture: true, signal: controller.signal }\n  );\n}\n\nexport function destroyContextMenu(menuOrId) {\n  const menu = typeof menuOrId === 'string'\n    ? document.getElementById(menuOrId)\n    : menuOrId;\n  if (!menu) return;\n  menu.__renContextController?.abort();\n  delete menu.__renContextController;\n  delete menu.dataset.renContextMenuInitialized;\n}\n\n/**\n * Auto-init all context menus.\n */\nexport function initAllContextMenus() {\n  const menuIds = new Set();\n  document.querySelectorAll('[data-context]').forEach((el) => {\n    menuIds.add(el.dataset.context);\n  });\n  menuIds.forEach(initContextMenu);\n}\n\n/**\n * Registry-friendly custom element facade over the same controller used by\n * initContextMenu(). There is one behavior owner and one reconnect-safe\n * AbortController regardless of which public entry point consumers use.\n */\nexport class RenContextMenu extends HTMLElement {\n  connectedCallback() {\n    if (!this.id) this.id = `ren-context-menu-${Math.random().toString(36).slice(2, 11)}`;\n    initContextMenu(this);\n  }\n\n  disconnectedCallback() {\n    destroyContextMenu(this);\n  }\n}\n\nif (!customElements.get('ren-context-menu')) {\n  customElements.define('ren-context-menu', RenContextMenu);\n}\n",
      "path": "components/composites/ren-context-menu",
      "id": "component:composite:ren-context-menu",
      "type": "component",
      "name": "ren-context-menu"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-calendar",
          ".ren-date-picker",
          ".ren-date-picker-dropdown",
          ".ren-date-picker-preset",
          ".ren-date-picker-presets",
          ".ren-date-picker-trigger",
          ".ren-date-picker-value"
        ],
        "tokens": [
          "--body-size",
          "--color-accent",
          "--color-border",
          "--color-border-strong",
          "--color-danger",
          "--color-fill",
          "--color-fill-active",
          "--color-fill-hover",
          "--color-success",
          "--color-surface",
          "--color-surface-raised",
          "--color-text",
          "--color-text-muted",
          "--duration-enter",
          "--ease-enter",
          "--label-size",
          "--label-weight",
          "--radius-lg",
          "--radius-md",
          "--shadow-lg",
          "--space-1",
          "--space-1-5",
          "--space-2",
          "--space-3",
          "--touch-min",
          "--transition-tactile"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-date-picker Component Contract\n\nDate input composite using a calendar popover.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-date-picker` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-date-picker` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Date Picker composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User needs to pick a single date (or a single contiguous range) from a calendar popover.\"\n    - \"Trigger should be a button-like control that opens a positioned popover (CSS anchor positioning with position-area) with an embedded <ren-calendar>.\"\n    - \"The form needs an ISO-string value submitted via a hidden input under a given name.\"\n    - \"Preset shortcuts (Today, Tomorrow, This Week) should appear alongside the calendar grid.\"\n    - \"Mobile must adapt to a bottom sheet automatically; desktop floats below the trigger.\"\n  avoidWhen:\n    - \"You need two independent start/end inputs with apply/cancel — use ren-date-range-picker.\"\n    - \"You only need a raw native <input type=\\\"date\\\"> with no popover styling — fall back to base/primitive-zero.\"\n    - \"You need a non-date generic dropdown — use ren-menu, ren-select, or ren-popover.\"\n    - \"You need a multi-month calendar surface without an input trigger — render <ren-calendar> directly.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-date-picker/ren-date-picker.css\"\n  js:\n    - \"rends/components/composites/ren-date-picker/ren-date-picker.js\"\n  notes:\n    - \"Also requires ren-calendar to be registered (the component instantiates <ren-calendar> inside its dropdown).\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Use <ren-date-picker> as the host element so the upgrade lifecycle wires up the trigger, dropdown, and hidden input.\"\n  - \"Trigger must be a real <button class=\\\"ren-date-picker-trigger\\\"> with type=\\\"button\\\", aria-haspopup=\\\"dialog\\\", and aria-expanded; the component sets these if it renders the trigger itself.\"\n  - \"The dropdown surface is a <div class=\\\"ren-date-picker-dropdown\\\" popover=\\\"manual\\\"> — keep the popover attribute so showPopover()/hidePopover() work.\"\n  - \"Inside the dropdown render a <ren-calendar> child; the host syncs its mode/locale/min/max attributes onto it.\"\n  - \"If you need form submission, set name=\\\"…\\\" on <ren-date-picker> so the auto-injected <input type=\\\"hidden\\\"> carries the ISO value.\"\n  - \"Use placement=\\\"bottom\\\" by default; the host and .ren-date-picker-dropdown mirror the preferred side to data-side.\"\n\nforbiddenPatterns:\n  - \"Styling a <div> or <span> as the trigger instead of a real <button> — keyboard activation and aria-expanded depend on the button.\"\n  - \"Replacing the inner <ren-calendar> with raw markup — the host wires ren-date-select events directly to the calendar element.\"\n  - \"Hardcoding popover positioning offsets via inline styles — rely on anchor-name: --ren-date-picker-anchor, position-anchor, and position-area in the stylesheet.\"\n  - \"Toggling the dropdown by setting display: none — use open()/close()/toggle() on the host so aria-expanded and popover state stay in sync.\"\n  - \"Wrapping a native <input type=\\\"date\\\"> in .ren-date-picker — the component expects to own its trigger and popover.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-calendar-bg, --ren-calendar-border, --ren-calendar-day-size, --ren-calendar-radius, --ren-calendar-range-bg, --ren-calendar-selected-bg, --ren-calendar-selected-color, --ren-calendar-today-bg, --ren-calendar-width.\"\n    - \"Semantic surface tokens used by the trigger / dropdown: --color-surface, --color-surface-raised, --color-border, --color-border-strong, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-fill-active, --color-accent, --color-danger, --color-success.\"\n    - \"Layout/motion tokens: --space-*, --radius-md, --radius-lg, --shadow-lg, --duration-enter, --ease-enter, --transition-tactile, --touch-min, --body-size, --label-size.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / rgb() colors for trigger or dropdown chrome; theme via --color-* and the --ren-calendar-* token API.\"\n    - \"Custom transition/animation durations; reuse --duration-enter and --ease-enter so reduced-motion overrides apply.\"\n\naccessibility:\n  required:\n    - \"Trigger is a real <button type=\\\"button\\\"> with aria-haspopup=\\\"dialog\\\" and aria-expanded that flips true/false as the dropdown opens and closes.\"\n    - \"Trigger min-height is var(--touch-min) so the tap target meets 44px; do not reduce it for touch contexts.\"\n    - \":focus-visible on the trigger and presets must show the --color-accent outline; do not remove it without restoring an equivalent ring.\"\n    - \"Escape closes the dropdown; Enter/Space on the trigger opens it; opening focuses the first non-disabled .ren-calendar-day.\"\n    - \"Error/success states (.ren-date-picker-error, .ren-date-picker-success) must be paired with a text message — do not rely on border color alone.\"\n    - \"Closing returns focus to the trigger; do not break this contract when overriding open()/close().\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-date-picker/ren-date-picker.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-date-picker/ren-date-picker.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-date-picker name=\"start-date\" placement=\"bottom\"><button class=\"ren-date-picker-trigger\" type=\"button\" aria-haspopup=\"dialog\" aria-expanded=\"false\">Choose date</button><div class=\"ren-date-picker-dropdown\" popover=\"manual\"><ren-calendar></ren-calendar></div></ren-date-picker>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-calendar`\n- `.ren-date-picker`\n- `.ren-date-picker-dropdown`\n- `.ren-date-picker-empty`\n- `.ren-date-picker-error`\n- `.ren-date-picker-preset`\n- `.ren-date-picker-presets`\n- `.ren-date-picker-range`\n- `.ren-date-picker-success`\n- `.ren-date-picker-trigger`\n- `.ren-date-picker-value`\n\n## States And Attributes\n\n- `[data-side]`\n- `placement`\n- `:active`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-calendar-bg`\n- `--ren-calendar-border`\n- `--ren-calendar-day-size`\n- `--ren-calendar-radius`\n- `--ren-calendar-range-bg`\n- `--ren-calendar-selected-bg`\n- `--ren-calendar-selected-color`\n- `--ren-calendar-today-bg`\n- `--ren-calendar-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-date-picker/ren-date-picker.css`\n- `components/composites/ren-date-picker/ren-date-picker.js`\n- `docs/components/ren-date-picker.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ═══ REN DATE PICKER WEB COMPONENT ═══\n   A composable date picker that wraps the calendar component\n   with an input field and popover dropdown.\n   Uses Popover API for positioning with CSS anchor positioning.\n   ══════════════════════════════════════════════════════════════════ */\n\n/* ═══ BASE WRAPPER ═══ */\n.ren-date-picker {\n  display: inline-flex;\n  flex-direction: column;\n  width: 100%;\n  max-width: 100%;\n  position: relative;\n}\n\n/* ═══ TRIGGER INPUT ═══ */\n.ren-date-picker-trigger {\n  anchor-name: --ren-date-picker-anchor;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--space-2);\n  width: 100%;\n  padding: var(--space-2) var(--space-3);\n  background: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n  color: var(--color-text);\n  font-size: var(--body-size);\n  font-family: inherit;\n  cursor: pointer;\n  transition: var(--transition-tactile);\n  min-height: var(--touch-min);\n\n  &:hover:not([disabled]) {\n    border-color: var(--color-border-strong);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n    border-color: var(--color-accent);\n  }\n\n  &[disabled] {\n    background: var(--color-fill);\n    color: var(--color-text-muted);\n    cursor: not-allowed;\n    opacity: 0.6;\n  }\n\n  /* ═══ PLACEHOLDER TEXT ═══ */\n  &::placeholder {\n    color: var(--color-text-muted);\n  }\n\n  /* ═══ CALENDAR ICON (::after) ═══ */\n  &::after {\n    content: '📅';\n    font-size: 1.25em;\n    flex-shrink: 0;\n    pointer-events: none;\n  }\n}\n\n/* ═══ TRIGGER TEXT CONTENT ═══ */\n.ren-date-picker-value {\n  flex: 1;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n/* ═══ EMPTY STATE ═══ */\n.ren-date-picker-trigger.ren-date-picker-empty {\n  color: var(--color-text-muted);\n\n  .ren-date-picker-value {\n    display: none;\n  }\n\n  &::before {\n    content: attr(data-placeholder);\n    color: var(--color-text-muted);\n  }\n}\n\n/* ═══ DROPDOWN CONTAINER (POPOVER) ═══ */\n.ren-date-picker-dropdown {\n  position-anchor: --ren-date-picker-anchor;\n  position: absolute;\n  position-area: bottom span-all;\n  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;\n  inset: auto;\n  margin: 0;\n  background: var(--color-surface-raised);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  z-index: 1000;\n  max-width: 400px;\n\n  /* ═══ POPOVER API ═══ */\n  &[popover] {\n    padding: 0;\n    margin: 0;\n  }\n\n  &:popover-open {\n    background: var(--color-surface-raised);\n    border: 1px solid var(--color-border);\n    border-radius: var(--radius-lg);\n    box-shadow: var(--shadow-lg);\n    animation: ren-date-picker-popup-in var(--duration-enter) var(--ease-enter) both;\n  }\n}\n\n.ren-date-picker-dropdown[data-side=\"bottom\"],\n.ren-date-picker-dropdown:not([data-side]) {\n  position-area: bottom span-all;\n  margin-block-start: var(--space-2);\n  margin-block-end: 0;\n  margin-inline: 0;\n}\n\n.ren-date-picker-dropdown[data-side=\"top\"] {\n  position-area: top span-all;\n  margin-block-start: 0;\n  margin-block-end: var(--space-2);\n  margin-inline: 0;\n}\n\n.ren-date-picker-dropdown[data-side=\"right\"] {\n  position-area: right span-all;\n  margin-block: 0;\n  margin-inline-start: var(--space-2);\n  margin-inline-end: 0;\n}\n\n.ren-date-picker-dropdown[data-side=\"left\"] {\n  position-area: left span-all;\n  margin-block: 0;\n  margin-inline-start: 0;\n  margin-inline-end: var(--space-2);\n}\n\n/* ═══ FALLBACK POSITIONING (no complete anchor support) ═══ */\n@supports not ((anchor-name: --ren-anchor) and\n  (position-anchor: --ren-anchor) and\n  (position-area: bottom span-all)) {\n  .ren-date-picker-dropdown {\n    position: fixed;\n    top: auto;\n    left: auto;\n  }\n}\n\n/* ═══ ANIMATION: POPUP IN ═══ */\n@starting-style {\n  .ren-date-picker-dropdown:popover-open {\n    opacity: 0;\n    translate: 0 -8px;\n  }\n}\n\n@keyframes ren-date-picker-popup-in {\n  from {\n    opacity: 0;\n    translate: 0 -8px;\n  }\n\n  to {\n    opacity: 1;\n    translate: 0 0;\n  }\n}\n\n/* ═══ CALENDAR INSIDE DROPDOWN ═══ */\n.ren-date-picker-dropdown .ren-calendar {\n  border: none;\n  box-shadow: none;\n  background: var(--color-surface-raised);\n  padding: var(--space-3);\n}\n\n/* ═══ PRESETS SECTION ═══ */\n.ren-date-picker-presets {\n  display: flex;\n  flex-wrap: wrap;\n  gap: var(--space-2);\n  padding: var(--space-3);\n  border-bottom: 1px solid var(--color-border);\n  background: var(--color-surface);\n}\n\n.ren-date-picker-preset {\n  padding: var(--space-1-5) var(--space-3);\n  background: var(--color-fill);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n  color: var(--color-text);\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  cursor: pointer;\n  transition: var(--transition-tactile);\n\n  &:hover {\n    background: var(--color-fill-hover);\n    border-color: var(--color-border-strong);\n  }\n\n  &:active {\n    background: var(--color-fill-active);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  &[disabled] {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n}\n\n/* ═══ RANGE MODE: SHOW START AND END ═══ */\n.ren-date-picker.ren-date-picker-range .ren-date-picker-value::after {\n  content: ' → ';\n  margin: 0 var(--space-1);\n}\n\n/* ═══ DISABLED STATE ═══ */\n.ren-date-picker[disabled] {\n  opacity: 0.6;\n  pointer-events: none;\n\n  .ren-date-picker-trigger {\n    cursor: not-allowed;\n  }\n}\n\n/* ═══ REDUCED MOTION SUPPORT ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-date-picker-dropdown:popover-open {\n    animation: none;\n    opacity: 1;\n    translate: 0 0;\n  }\n\n  .ren-date-picker-dropdown {\n    transition: none;\n  }\n}\n\n/* ═══ DARK MODE SUPPORT ═══ */\n@media (prefers-color-scheme: dark) {\n  .ren-date-picker-trigger,\n  .ren-date-picker-dropdown {\n    /* ═══ USES CSS VARIABLES - AUTO ADAPTS ═══ */\n  }\n}\n\n/* ═══ MOBILE RESPONSIVENESS ═══ */\n@media (max-width: 480px) {\n  .ren-date-picker-dropdown {\n    position: fixed !important;\n    inset: auto 0 0 0 !important;\n    max-width: none;\n    border-radius: var(--radius-lg) var(--radius-lg) 0 0;\n    max-height: 80vh;\n    overflow-y: auto;\n\n    &:popover-open {\n      animation: ren-date-picker-mobile-in var(--duration-enter) var(--ease-enter) both;\n    }\n  }\n\n  @starting-style {\n    .ren-date-picker-dropdown:popover-open {\n      translate: 0 100%;\n      opacity: 0;\n    }\n  }\n\n}\n\n@keyframes ren-date-picker-mobile-in {\n  from {\n    translate: 0 100%;\n    opacity: 0;\n  }\n\n  to {\n    translate: 0 0;\n    opacity: 1;\n  }\n}\n\n/* ═══ ERROR STATE ═══ */\n.ren-date-picker.ren-date-picker-error {\n  .ren-date-picker-trigger {\n    border-color: var(--color-danger);\n\n    &:focus-visible {\n      outline-color: var(--color-danger);\n      border-color: var(--color-danger);\n    }\n  }\n}\n\n/* ═══ SUCCESS STATE ═══ */\n.ren-date-picker.ren-date-picker-success {\n  .ren-date-picker-trigger {\n    border-color: var(--color-success);\n\n    &:focus-visible {\n      outline-color: var(--color-success);\n      border-color: var(--color-success);\n    }\n  }\n}\n\n/* ═══ FORM INTEGRATION ═══ */\n.ren-date-picker {\n  /* ═══ ALLOW FORM STYLING ═══ */\n\n  input[type='hidden'] {\n    display: none;\n  }\n}\n\n\n/* ═══ REN DATE PICKER WEB COMPONENT ═══\n   A composable date picker that combines an input field with a calendar dropdown.\n\n   Features:\n   - Integrates RenCalendar component internally\n   - Single and range date selection modes\n   - Preset shortcuts (Today, Tomorrow, Next Week, etc.)\n   - Popover API for dropdown positioning\n   - Locale-aware date formatting\n   - Focus management with blur/focus trapping\n   - Form integration with hidden input\n\n   Usage:\n   <ren-date-picker placeholder=\"Select date\" format=\"long\" mode=\"single\"></ren-date-picker>\n  ══════════════════════════════════════════════════════════════════ */\n\nconst DATE_PICKER_SIDES = new Set(['top', 'right', 'bottom', 'left']);\n\nfunction normalizeDatePickerSide(value) {\n  const side = String(value || 'bottom').toLowerCase().split('-')[0];\n\n  return DATE_PICKER_SIDES.has(side) ? side : 'bottom';\n}\n\nexport class RenDatePicker extends HTMLElement {\n  static observedAttributes = ['placement'];\n\n  constructor() {\n    super();\n\n    /* ═══ STATE ═══ */\n    this.calendar = null;\n    this.dropdown = null;\n    this.trigger = null;\n    this.isOpen = false;\n    this.selectedValue = null;\n    this.selectedRange = null;\n\n    /* ═══ CONFIGURATION ═══ */\n    this.format = 'short'; // short, medium, long\n    this.locale = 'en-US';\n    this.mode = 'single'; // single, range\n    this.placeholder = 'Select date';\n\n    /* ═══ BIND METHODS ═══ */\n    this.handleTriggerClick = this.handleTriggerClick.bind(this);\n    this.handleTriggerKeyDown = this.handleTriggerKeyDown.bind(this);\n    this.handleCalendarSelect = this.handleCalendarSelect.bind(this);\n    this.handleDocumentClick = this.handleDocumentClick.bind(this);\n  }\n\n  connectedCallback() {\n    /* ═══ READ ATTRIBUTES ═══ */\n    this.mode = this.getAttribute('mode') || 'single';\n    this.format = this.getAttribute('format') || 'short';\n    this.locale = this.getAttribute('locale') || 'en-US';\n    this.placeholder = this.getAttribute('placeholder') || 'Select date';\n\n    const minAttr = this.getAttribute('min');\n    const maxAttr = this.getAttribute('max');\n\n    /* ═══ RENDER COMPONENT ═══ */\n    this.render();\n\n    /* ═══ SET UP TRIGGER (must come after render) ═══ */\n    this.trigger = this.querySelector('.ren-date-picker-trigger');\n    if (this.trigger) {\n      this.trigger.addEventListener('click', this.handleTriggerClick);\n      this.trigger.addEventListener('keydown', this.handleTriggerKeyDown);\n    }\n\n    /* ═══ SET UP DROPDOWN (must come after render, before calendar) ═══ */\n    this.dropdown = this.querySelector('[popover]');\n    if (!this.dropdown) {\n      this.dropdown = document.createElement('div');\n      this.dropdown.className = 'ren-date-picker-dropdown';\n      this.dropdown.setAttribute('popover', 'manual');\n      this.appendChild(this.dropdown);\n    }\n\n    /* ═══ SET UP CALENDAR (must come after dropdown) ═══ */\n    this.calendar = this.querySelector('ren-calendar');\n    if (!this.calendar) {\n      this.calendar = document.createElement('ren-calendar');\n      this.calendar.setAttribute('mode', this.mode);\n      if (this.locale) this.calendar.setAttribute('locale', this.locale);\n      if (minAttr) this.calendar.setAttribute('min', minAttr);\n      if (maxAttr) this.calendar.setAttribute('max', maxAttr);\n      this.dropdown.appendChild(this.calendar);\n    }\n\n    this.syncPlacement();\n\n    /* ═══ LISTEN TO CALENDAR EVENTS ═══ */\n    this.calendar.addEventListener('ren-date-select', this.handleCalendarSelect);\n\n    /* ═══ LISTEN TO DOCUMENT CLICKS ═══ */\n    document.addEventListener('click', this.handleDocumentClick);\n\n    /* ═══ SET INITIAL VALUE ═══ */\n    const valueAttr = this.getAttribute('value');\n    if (valueAttr) {\n      this.setValue(valueAttr);\n    }\n\n    /* ═══ ADD TO FORM IF PARENT IS FORM ═══ */\n    this.addHiddenInput();\n  }\n\n  disconnectedCallback() {\n    if (this.trigger) {\n      this.trigger.removeEventListener('click', this.handleTriggerClick);\n      this.trigger.removeEventListener('keydown', this.handleTriggerKeyDown);\n    }\n\n    if (this.calendar) {\n      this.calendar.removeEventListener('ren-date-select', this.handleCalendarSelect);\n    }\n\n    document.removeEventListener('click', this.handleDocumentClick);\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'placement' && oldValue !== newValue) {\n      this.syncPlacement();\n    }\n  }\n\n  /* ═══ RENDER COMPONENT STRUCTURE ═══ */\n  render() {\n    this.classList.add('ren-date-picker');\n\n    if (this.mode === 'range') {\n      this.classList.add('ren-date-picker-range');\n    }\n\n    if (this.innerHTML.trim() === '') {\n      /* ═══ CREATE TRIGGER ═══ */\n      const trigger = document.createElement('button');\n      trigger.className = 'ren-date-picker-trigger ren-date-picker-empty';\n      trigger.setAttribute('type', 'button');\n      trigger.setAttribute('aria-haspopup', 'dialog');\n      trigger.setAttribute('aria-expanded', 'false');\n      trigger.setAttribute('data-placeholder', this.placeholder);\n\n      const valueSpan = document.createElement('span');\n      valueSpan.className = 'ren-date-picker-value';\n      trigger.appendChild(valueSpan);\n\n      this.appendChild(trigger);\n\n      /* ═══ CREATE DROPDOWN ═══ */\n      const dropdown = document.createElement('div');\n      dropdown.className = 'ren-date-picker-dropdown';\n      dropdown.setAttribute('popover', 'manual');\n\n      this.appendChild(dropdown);\n    }\n  }\n\n  /* ═══ MIRROR PLACEMENT TO PUBLIC DATA ATTRIBUTES ═══ */\n  syncPlacement() {\n    const side = normalizeDatePickerSide(this.getAttribute('placement'));\n\n    this.setAttribute('data-side', side);\n    if (this.dropdown) {\n      this.dropdown.setAttribute('data-side', side);\n    }\n  }\n\n  /* ═══ ADD HIDDEN INPUT FOR FORM SUBMISSION ═══ */\n  addHiddenInput() {\n    let hidden = this.querySelector('input[type=\"hidden\"]');\n    if (!hidden) {\n      hidden = document.createElement('input');\n      hidden.type = 'hidden';\n      hidden.name = this.getAttribute('name') || 'date-picker';\n      this.appendChild(hidden);\n    }\n    this.hiddenInput = hidden;\n  }\n\n  /* ═══ HANDLE TRIGGER CLICK ═══ */\n  handleTriggerClick(event) {\n    event.preventDefault();\n    event.stopPropagation();\n\n    if (this.hasAttribute('disabled')) return;\n\n    if (this.isOpen) {\n      this.close();\n    } else {\n      this.open();\n    }\n  }\n\n  /* ═══ HANDLE TRIGGER KEYBOARD ═══ */\n  handleTriggerKeyDown(event) {\n    if (this.hasAttribute('disabled')) return;\n\n    if (event.key === 'Enter' || event.key === ' ') {\n      event.preventDefault();\n      this.open();\n    }\n\n    if (event.key === 'Escape') {\n      this.close();\n    }\n  }\n\n  /* ═══ HANDLE CALENDAR DATE SELECT ═══ */\n  handleCalendarSelect(event) {\n    const detail = event.detail;\n\n    if (this.mode === 'single') {\n      this.selectedValue = detail.date;\n      this.updateTrigger(this.formatDate(detail.date));\n      this.dispatchChangeEvent();\n      this.close();\n    } else if (this.mode === 'range') {\n      this.selectedRange = detail.range;\n      if (detail.range && detail.range.start && detail.range.end) {\n        const formattedRange = `${this.formatDate(detail.range.start)} → ${this.formatDate(detail.range.end)}`;\n        this.updateTrigger(formattedRange);\n        this.dispatchChangeEvent();\n        this.close();\n      } else {\n        const start = detail.range?.start ? this.formatDate(detail.range.start) : '';\n        this.updateTrigger(start);\n      }\n    }\n  }\n\n  /* ═══ HANDLE DOCUMENT CLICK (CLOSE DROPDOWN ON OUTSIDE CLICK) ═══ */\n  handleDocumentClick(event) {\n    if (!this.contains(event.target) && this.isOpen) {\n      this.close();\n    }\n  }\n\n  /* ═══ OPEN DROPDOWN ═══ */\n  open() {\n    if (this.isOpen || this.hasAttribute('disabled')) return;\n\n    if (!this.dropdown) return;\n\n    /* ═══ SET UP POPOVER AND POSITIONING ═══ */\n    this.syncPlacement();\n\n    try {\n      this.dropdown.showPopover();\n    } catch {\n      /* ═══ FALLBACK FOR UNSUPPORTED BROWSERS ═══ */\n      this.dropdown.style.display = 'block';\n    }\n\n    this.isOpen = true;\n\n    if (this.trigger) {\n      this.trigger.setAttribute('aria-expanded', 'true');\n    }\n\n    /* ═══ FOCUS CALENDAR ═══ */\n    setTimeout(() => {\n      if (this.calendar) {\n        const firstDay = this.calendar.querySelector('.ren-calendar-day:not([disabled])');\n        if (firstDay) {\n          firstDay.focus();\n        }\n      }\n    }, 100);\n\n    this.dispatchEvent(\n      new CustomEvent('ren-date-picker-open', {\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ CLOSE DROPDOWN ═══ */\n  close() {\n    if (!this.isOpen) return;\n\n    if (!this.dropdown) return;\n\n    try {\n      this.dropdown.hidePopover();\n    } catch {\n      /* ═══ FALLBACK FOR UNSUPPORTED BROWSERS ═══ */\n      this.dropdown.style.display = 'none';\n    }\n\n    this.isOpen = false;\n\n    if (this.trigger) {\n      this.trigger.setAttribute('aria-expanded', 'false');\n      this.trigger.focus();\n    }\n\n    this.dispatchEvent(\n      new CustomEvent('ren-date-picker-close', {\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ UPDATE TRIGGER TEXT ═══ */\n  updateTrigger(formattedValue) {\n    if (!this.trigger) return;\n\n    const valueSpan = this.trigger.querySelector('.ren-date-picker-value');\n    if (valueSpan) {\n      valueSpan.textContent = formattedValue;\n    }\n\n    this.trigger.classList.remove('ren-date-picker-empty');\n\n    /* ═══ UPDATE HIDDEN INPUT ═══ */\n    if (this.hiddenInput) {\n      this.hiddenInput.value = formattedValue;\n    }\n  }\n\n  /* ═══ FORMAT DATE FOR DISPLAY ═══ */\n  formatDate(date) {\n    if (!date) return '';\n\n    const formatter = new Intl.DateTimeFormat(this.locale, {\n      year: 'numeric',\n      month: this.format === 'short' ? '2-digit' : this.format === 'long' ? 'long' : 'short',\n      day: '2-digit',\n    });\n\n    return formatter.format(date);\n  }\n\n  /* ═══ DISPATCH CHANGE EVENT ═══ */\n  dispatchChangeEvent() {\n    let value = null;\n    let formattedValue = '';\n\n    if (this.mode === 'single') {\n      value = this.selectedValue ? this.dateToString(this.selectedValue) : null;\n      formattedValue = this.selectedValue ? this.formatDate(this.selectedValue) : '';\n    } else if (this.mode === 'range') {\n      if (this.selectedRange && this.selectedRange.start && this.selectedRange.end) {\n        value = {\n          start: this.dateToString(this.selectedRange.start),\n          end: this.dateToString(this.selectedRange.end),\n        };\n        formattedValue = `${this.formatDate(this.selectedRange.start)} → ${this.formatDate(this.selectedRange.end)}`;\n      }\n    }\n\n    this.dispatchEvent(\n      new CustomEvent('ren-change', {\n        detail: {\n          value,\n          formattedValue,\n        },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ CONVERT DATE TO ISO STRING ═══ */\n  dateToString(date) {\n    const year = date.getFullYear();\n    const month = String(date.getMonth() + 1).padStart(2, '0');\n    const day = String(date.getDate()).padStart(2, '0');\n    return `${year}-${month}-${day}`;\n  }\n\n  parseDate(value) {\n    if (value instanceof Date) return new Date(value.getTime());\n    const match = String(value).match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n    return match ? new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3])) : new Date(value);\n  }\n\n  /* ═══ PUBLIC METHODS ═══ */\n\n  /* ═══ GET VALUE ═══ */\n  getValue() {\n    if (this.mode === 'single') {\n      return this.selectedValue ? this.dateToString(this.selectedValue) : null;\n    } else if (this.mode === 'range') {\n      return this.selectedRange\n        ? {\n            start: this.dateToString(this.selectedRange.start),\n            end: this.dateToString(this.selectedRange.end),\n          }\n        : null;\n    }\n  }\n\n  /* ═══ SET VALUE ═══ */\n  setValue(value) {\n    if (this.mode === 'single' && typeof value === 'string') {\n      this.selectedValue = this.parseDate(value);\n      this.calendar.setValue(value);\n      this.updateTrigger(this.formatDate(this.selectedValue));\n    } else if (this.mode === 'range' && value && typeof value === 'object') {\n      this.selectedRange = {\n        start: this.parseDate(value.start),\n        end: this.parseDate(value.end),\n      };\n      this.calendar.setRange(value.start, value.end);\n      const formatted = `${this.formatDate(this.selectedRange.start)} → ${this.formatDate(this.selectedRange.end)}`;\n      this.updateTrigger(formatted);\n    }\n  }\n\n  /* ═══ TOGGLE DROPDOWN ═══ */\n  toggle() {\n    if (this.isOpen) {\n      this.close();\n    } else {\n      this.open();\n    }\n  }\n\n  /* ═══ GETTERS AND SETTERS ═══ */\n  get disabled() {\n    return this.hasAttribute('disabled');\n  }\n\n  set disabled(val) {\n    if (val) {\n      this.setAttribute('disabled', '');\n      if (this.trigger) this.trigger.disabled = true;\n    } else {\n      this.removeAttribute('disabled');\n      if (this.trigger) this.trigger.disabled = false;\n    }\n  }\n\n  get isOpened() {\n    return this.isOpen;\n  }\n\n  set isOpened(val) {\n    if (val) {\n      this.open();\n    } else {\n      this.close();\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-date-picker')) {\n  customElements.define('ren-date-picker', RenDatePicker);\n}\n",
      "path": "components/composites/ren-date-picker",
      "id": "component:composite:ren-date-picker",
      "type": "component",
      "name": "ren-date-picker"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-calendar",
          ".ren-date-range-actions",
          ".ren-date-range-apply",
          ".ren-date-range-calendars",
          ".ren-date-range-cancel",
          ".ren-date-range-divider",
          ".ren-date-range-dropdown",
          ".ren-date-range-end",
          ".ren-date-range-footer",
          ".ren-date-range-inner",
          ".ren-date-range-picker",
          ".ren-date-range-preset",
          ".ren-date-range-presets",
          ".ren-date-range-separator",
          ".ren-date-range-start",
          ".ren-date-range-summary",
          ".ren-date-range-trigger",
          ".ren-date-range-value"
        ],
        "tokens": [
          "--body-size",
          "--color-accent",
          "--color-accent-hover",
          "--color-accent-subtle",
          "--color-border",
          "--color-border-strong",
          "--color-danger",
          "--color-fill",
          "--color-fill-active",
          "--color-on-accent",
          "--color-success",
          "--color-surface",
          "--color-surface-raised",
          "--color-text",
          "--color-text-muted",
          "--color-text-secondary",
          "--duration-enter",
          "--ease-enter",
          "--label-size",
          "--label-weight",
          "--radius-lg",
          "--radius-md",
          "--shadow-lg",
          "--space-1",
          "--space-1-5",
          "--space-2",
          "--space-3",
          "--touch-min",
          "--transition-tactile",
          "--weight-semibold"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-date-range-picker Component Contract\n\nTwo-date selection composite for ranges.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-date-range-picker` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-date-range-picker` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Date Range Picker composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User needs to pick a contiguous date range (start + end) confirmed by an explicit Apply action.\"\n    - \"UI needs dual side-by-side calendars (left = start month, right = next month) inside a CSS-anchor-positioned popover.\"\n    - \"Preset shortcuts like Last 7 / Last 30 / This Month / Last Month should be selectable as a sidebar listbox.\"\n    - \"Form submission needs two ISO-string fields named `<name>-start` and `<name>-end` via injected hidden inputs.\"\n    - \"Selection has Apply/Cancel semantics — draft state on outside-click or Cancel must revert to the previously confirmed range.\"\n  avoidWhen:\n    - \"Only a single date is needed — use ren-date-picker (mode=\\\"single\\\").\"\n    - \"User picks two unrelated dates (start-of-trip, end-of-warranty) that are not a contiguous range — use two ren-date-picker instances.\"\n    - \"Range needs commit-on-click without Apply/Cancel — use ren-date-picker with mode=\\\"range\\\".\"\n    - \"No calendar UI is needed; a quick \\\"last N days\\\" preset is enough — render ren-select / ren-menu of presets only.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-date-range-picker/ren-date-range-picker.css\"\n  js:\n    - \"rends/components/composites/ren-date-range-picker/ren-date-range-picker.js\"\n  notes:\n    - \"Also requires ren-calendar to be registered — the component instantiates two <ren-calendar mode=\\\"range\\\"> children inside the dropdown.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Use <ren-date-range-picker> as the host so the upgrade lifecycle builds the trigger, dropdown, dual calendars, presets, and hidden inputs.\"\n  - \"The trigger is a real <button class=\\\"ren-date-range-trigger\\\"> with aria-haspopup=\\\"dialog\\\" and aria-expanded; do not replace it with a div.\"\n  - \"The dropdown root carries role=\\\"dialog\\\", aria-label, and popover=\\\"manual\\\"; presets render inside role=\\\"listbox\\\" with each preset as role=\\\"option\\\" + data-preset=\\\"<key>\\\".\"\n  - \"Footer must contain .ren-date-range-summary (aria-live=\\\"polite\\\"), .ren-date-range-cancel, and .ren-date-range-apply — Apply is disabled until both endpoints are set.\"\n  - \"Use `name=\\\"…\\\"` on the host so the component injects <input type=\\\"hidden\\\" name=\\\"<name>-start\\\"> and `…-end` for form submission.\"\n  - \"Use placement=\\\"bottom\\\" by default; the host and .ren-date-range-dropdown mirror the preferred side to data-side.\"\n\nforbiddenPatterns:\n  - \"Wiring presets to a single ren-calendar instead of using both calendarLeft/calendarRight — the host depends on having two ren-calendar children to call setRange on.\"\n  - \"Skipping the Apply button and writing directly to the trigger — Cancel/outside-click revert depends on a draft vs confirmed split.\"\n  - \"Hardcoding dropdown placement offsets or breakpoints — use placement/data-side for side preference, and keep the container-type: inline-size @container ren-date-range layout under 500px.\"\n  - \"Adding extra inline color tokens to .ren-date-range-preset[aria-selected=\\\"true\\\"] — the highlighted preset already styles via --color-accent-subtle / --color-accent.\"\n  - \"Calling showPopover() directly from outside — use the host's open() / close() / handleApply() / handleCancel() so draft/confirmed state stays consistent.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-calendar-bg, --ren-calendar-border, --ren-calendar-day-size, --ren-calendar-radius, --ren-calendar-range-bg, --ren-calendar-selected-bg, --ren-calendar-selected-color, --ren-calendar-today-bg, --ren-calendar-width.\"\n    - \"Semantic surface / text / accent tokens: --color-surface, --color-surface-raised, --color-border, --color-border-strong, --color-text, --color-text-secondary, --color-text-muted, --color-accent, --color-accent-hover, --color-accent-subtle, --color-on-accent, --color-fill, --color-fill-active, --color-danger, --color-success.\"\n    - \"Layout/motion tokens: --space-*, --radius-md, --radius-lg, --shadow-lg, --duration-enter, --ease-enter, --transition-tactile, --touch-min, --body-size, --label-size, --weight-semibold.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgba colors for any of the trigger, dropdown, presets, or actions — theme via the semantic and --ren-calendar-* tokens.\"\n    - \"Custom transition durations; reuse --duration-enter / --ease-enter so reduced-motion handling continues to apply.\"\n\naccessibility:\n  required:\n    - \"Trigger is a real <button type=\\\"button\\\"> with aria-haspopup=\\\"dialog\\\" and aria-expanded synced to the dropdown state.\"\n    - \"Dropdown is role=\\\"dialog\\\" with aria-label=\\\"Date range selection\\\" and focuses the first preset (or first calendar day) on open.\"\n    - \"Preset listbox uses role=\\\"listbox\\\" with each .ren-date-range-preset as role=\\\"option\\\"; the active preset sets aria-selected=\\\"true\\\".\"\n    - \".ren-date-range-summary uses aria-live=\\\"polite\\\" so changes to the draft range are announced.\"\n    - \"Escape closes via Cancel semantics (reverts draft to confirmed); outside-click does the same — do not silently apply.\"\n    - \"Apply button is disabled (real disabled attribute) until both draftStart and draftEnd are present; do not bypass via aria-disabled alone.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-date-range-picker/ren-date-range-picker.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-date-range-picker/ren-date-range-picker.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-date-range-picker name=\"stay\" placement=\"bottom\"><button class=\"ren-date-range-trigger\" type=\"button\" aria-haspopup=\"dialog\" aria-expanded=\"false\">Choose dates</button><div class=\"ren-date-range-dropdown\" role=\"dialog\" aria-label=\"Choose date range\" popover=\"manual\"><div class=\"ren-date-range-summary\" aria-live=\"polite\">No dates selected</div><button class=\"ren-date-range-cancel\" type=\"button\">Cancel</button><button class=\"ren-date-range-apply\" type=\"button\" disabled>Apply</button></div></ren-date-range-picker>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-calendar`\n- `.ren-date-range-actions`\n- `.ren-date-range-apply`\n- `.ren-date-range-calendars`\n- `.ren-date-range-cancel`\n- `.ren-date-range-divider`\n- `.ren-date-range-dropdown`\n- `.ren-date-range-empty`\n- `.ren-date-range-end`\n- `.ren-date-range-error`\n- `.ren-date-range-footer`\n- `.ren-date-range-inner`\n- `.ren-date-range-picker`\n- `.ren-date-range-preset`\n- `.ren-date-range-presets`\n- `.ren-date-range-separator`\n- `.ren-date-range-start`\n- `.ren-date-range-success`\n- `.ren-date-range-summary`\n- `.ren-date-range-trigger`\n- `.ren-date-range-value`\n\n## States And Attributes\n\n- `[aria-selected]`\n- `[data-side]`\n- `placement`\n- `:active`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-calendar-bg`\n- `--ren-calendar-border`\n- `--ren-calendar-day-size`\n- `--ren-calendar-radius`\n- `--ren-calendar-range-bg`\n- `--ren-calendar-selected-bg`\n- `--ren-calendar-selected-color`\n- `--ren-calendar-today-bg`\n- `--ren-calendar-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-date-range-picker/ren-date-range-picker.css`\n- `components/composites/ren-date-range-picker/ren-date-range-picker.js`\n- `docs/components/ren-date-range-picker.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ═══ REN DATE RANGE PICKER WEB COMPONENT ═══\n   A composable date range picker with dual calendars,\n   preset shortcuts, and an input trigger with popover dropdown.\n   Built on top of RenCalendar for consistent behavior.\n\n   Usage:\n     <ren-date-range-picker\n       placeholder=\"Select date range\"\n       format=\"short\"\n       locale=\"en-US\"\n       presets=\"last7,last30,thisMonth,lastMonth\"\n     ></ren-date-range-picker>\n   ══════════════════════════════════════════════════════════════════ */\n\n/* ═══ BASE WRAPPER ═══ */\n.ren-date-range-picker {\n  display: inline-flex;\n  flex-direction: column;\n  width: 100%;\n  max-width: 100%;\n  position: relative;\n}\n\n/* ═══ TRIGGER BUTTON ═══ */\n.ren-date-range-trigger {\n  anchor-name: --ren-date-range-anchor;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--space-2);\n  width: 100%;\n  padding: var(--space-2) var(--space-3);\n  background: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n  color: var(--color-text);\n  font-size: var(--body-size);\n  font-family: inherit;\n  cursor: pointer;\n  transition: var(--transition-tactile);\n  min-height: var(--touch-min);\n\n  &:hover:not([disabled]) {\n    border-color: var(--color-border-strong);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n    border-color: var(--color-accent);\n  }\n\n  &[disabled] {\n    background: var(--color-fill);\n    color: var(--color-text-muted);\n    cursor: not-allowed;\n    opacity: 0.6;\n  }\n\n  /* ═══ CALENDAR ICON ═══ */\n  &::after {\n    content: '📅';\n    font-size: 1.25em;\n    flex-shrink: 0;\n    pointer-events: none;\n  }\n}\n\n/* ═══ TRIGGER VALUE DISPLAY ═══ */\n.ren-date-range-value {\n  flex: 1;\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  overflow: hidden;\n  white-space: nowrap;\n}\n\n.ren-date-range-start,\n.ren-date-range-end {\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.ren-date-range-separator {\n  color: var(--color-text-muted);\n  flex-shrink: 0;\n}\n\n/* ═══ EMPTY STATE ═══ */\n.ren-date-range-trigger.ren-date-range-empty {\n  color: var(--color-text-muted);\n\n  .ren-date-range-value {\n    display: none;\n  }\n\n  &::before {\n    content: attr(data-placeholder);\n    color: var(--color-text-muted);\n    flex: 1;\n  }\n}\n\n/* ═══ DROPDOWN CONTAINER (POPOVER) ═══ */\n.ren-date-range-dropdown {\n  position-anchor: --ren-date-range-anchor;\n  position: absolute;\n  position-area: bottom span-all;\n  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;\n  inset: auto;\n  margin: 0;\n  background: var(--color-surface-raised);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  z-index: 1000;\n\n  /* ═══ POPOVER API ═══ */\n  &[popover] {\n    padding: 0;\n    margin: 0;\n    border: none;\n    background: transparent;\n    box-shadow: none;\n  }\n\n  &:popover-open {\n    animation: ren-date-range-popup-in var(--duration-enter) var(--ease-enter) both;\n  }\n}\n\n.ren-date-range-dropdown[data-side=\"bottom\"],\n.ren-date-range-dropdown:not([data-side]) {\n  position-area: bottom span-all;\n  margin-block-start: var(--space-2);\n  margin-block-end: 0;\n  margin-inline: 0;\n}\n\n.ren-date-range-dropdown[data-side=\"top\"] {\n  position-area: top span-all;\n  margin-block-start: 0;\n  margin-block-end: var(--space-2);\n  margin-inline: 0;\n}\n\n.ren-date-range-dropdown[data-side=\"right\"] {\n  position-area: right span-all;\n  margin-block: 0;\n  margin-inline-start: var(--space-2);\n  margin-inline-end: 0;\n}\n\n.ren-date-range-dropdown[data-side=\"left\"] {\n  position-area: left span-all;\n  margin-block: 0;\n  margin-inline-start: 0;\n  margin-inline-end: var(--space-2);\n}\n\n/* ═══ FALLBACK POSITIONING (no complete anchor support) ═══ */\n@supports not ((anchor-name: --ren-anchor) and\n  (position-anchor: --ren-anchor) and\n  (position-area: bottom span-all)) {\n  .ren-date-range-dropdown {\n    position: fixed;\n    top: auto;\n    left: auto;\n  }\n}\n\n/* ═══ ANIMATION: POPUP IN ═══ */\n@starting-style {\n  .ren-date-range-dropdown:popover-open {\n    opacity: 0;\n    translate: 0 -8px;\n  }\n}\n\n@keyframes ren-date-range-popup-in {\n  from {\n    opacity: 0;\n    translate: 0 -8px;\n  }\n\n  to {\n    opacity: 1;\n    translate: 0 0;\n  }\n}\n\n/* ═══ DROPDOWN INNER WRAPPER ═══ */\n.ren-date-range-inner {\n  container-type: inline-size;\n  container-name: ren-date-range;\n  background: var(--color-surface-raised);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  overflow: hidden;\n}\n\n/* ═══ PRESETS SIDEBAR ═══ */\n.ren-date-range-presets {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-1);\n  padding: var(--space-3);\n  border-bottom: 1px solid var(--color-border);\n  background: var(--color-surface);\n}\n\n.ren-date-range-preset {\n  display: block;\n  width: 100%;\n  padding: var(--space-1-5) var(--space-3);\n  background: transparent;\n  border: none;\n  border-radius: var(--radius-md);\n  color: var(--color-text);\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  text-align: start;\n  cursor: pointer;\n  transition: var(--transition-tactile);\n\n  &:hover {\n    background: var(--color-fill);\n  }\n\n  &:active {\n    background: var(--color-fill-active);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  &[aria-selected=\"true\"] {\n    background: var(--color-accent-subtle);\n    color: var(--color-accent);\n    font-weight: var(--weight-semibold);\n  }\n}\n\n/* ═══ DUAL CALENDAR LAYOUT ═══ */\n.ren-date-range-calendars {\n  display: flex;\n  gap: 0;\n  padding: var(--space-3);\n}\n\n.ren-date-range-calendars > .ren-calendar {\n  border: none;\n  box-shadow: none;\n  background: transparent;\n  flex: 1;\n}\n\n/* ═══ DIVIDER BETWEEN CALENDARS ═══ */\n.ren-date-range-divider {\n  width: 1px;\n  align-self: stretch;\n  background: var(--color-border);\n  margin-block: var(--space-2);\n  flex-shrink: 0;\n}\n\n/* ═══ FOOTER WITH ACTIONS ═══ */\n.ren-date-range-footer {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--space-3);\n  padding: var(--space-3);\n  border-top: 1px solid var(--color-border);\n  background: var(--color-surface);\n}\n\n.ren-date-range-summary {\n  font-size: var(--label-size);\n  color: var(--color-text-secondary);\n}\n\n.ren-date-range-summary strong {\n  color: var(--color-text);\n  font-weight: var(--weight-semibold);\n}\n\n.ren-date-range-actions {\n  display: flex;\n  gap: var(--space-2);\n}\n\n.ren-date-range-cancel,\n.ren-date-range-apply {\n  padding: var(--space-1-5) var(--space-3);\n  border-radius: var(--radius-md);\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  cursor: pointer;\n  transition: var(--transition-tactile);\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n}\n\n.ren-date-range-cancel {\n  background: transparent;\n  border: 1px solid var(--color-border);\n  color: var(--color-text);\n\n  &:hover {\n    background: var(--color-fill);\n    border-color: var(--color-border-strong);\n  }\n}\n\n.ren-date-range-apply {\n  background: var(--color-accent);\n  border: 1px solid var(--color-accent);\n  color: var(--color-on-accent);\n\n  &:hover {\n    background: var(--color-accent-hover);\n  }\n\n  &[disabled] {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n}\n\n/* ═══ DISABLED STATE ═══ */\n.ren-date-range-picker[disabled] {\n  opacity: 0.6;\n  pointer-events: none;\n}\n\n/* ═══ ERROR STATE ═══ */\n.ren-date-range-picker.ren-date-range-error {\n  .ren-date-range-trigger {\n    border-color: var(--color-danger);\n\n    &:focus-visible {\n      outline-color: var(--color-danger);\n    }\n  }\n}\n\n/* ═══ SUCCESS STATE ═══ */\n.ren-date-range-picker.ren-date-range-success {\n  .ren-date-range-trigger {\n    border-color: var(--color-success);\n\n    &:focus-visible {\n      outline-color: var(--color-success);\n    }\n  }\n}\n\n/* ═══ REDUCED MOTION ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-date-range-dropdown:popover-open {\n    animation: none;\n    opacity: 1;\n    translate: 0 0;\n  }\n\n  .ren-date-range-dropdown {\n    transition: none;\n  }\n}\n\n/* ═══ COMPACT LAYOUT (Container Query) ═══\n   Stacks calendars vertically and makes presets\n   horizontal when the dropdown is narrow —\n   works in sidebars, modals, or small viewports. */\n@container ren-date-range (max-width: 500px) {\n  .ren-date-range-calendars {\n    flex-direction: column;\n    gap: var(--space-2);\n  }\n\n  .ren-date-range-divider {\n    width: 100%;\n    height: 1px;\n    margin-inline: var(--space-2);\n    margin-block: 0;\n  }\n\n  .ren-date-range-presets {\n    flex-direction: row;\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n    gap: var(--space-2);\n    padding: var(--space-2) var(--space-3);\n  }\n\n  .ren-date-range-preset {\n    white-space: nowrap;\n    flex-shrink: 0;\n  }\n}\n\n/* ═══ MOBILE BOTTOM SHEET (Media Query) ═══\n   Fixed positioning requires viewport awareness,\n   so this stays as a media query. */\n@media (max-width: 680px) {\n  .ren-date-range-dropdown {\n    position: fixed !important;\n    inset: auto 0 0 0 !important;\n    max-width: none;\n    border-radius: var(--radius-lg) var(--radius-lg) 0 0;\n    max-height: 85vh;\n    overflow-y: auto;\n\n    &:popover-open {\n      animation: ren-date-range-mobile-in var(--duration-enter) var(--ease-enter) both;\n    }\n  }\n\n  @starting-style {\n    .ren-date-range-dropdown:popover-open {\n      translate: 0 100%;\n      opacity: 0;\n    }\n  }\n\n}\n\n@keyframes ren-date-range-mobile-in {\n  from {\n    translate: 0 100%;\n    opacity: 0;\n  }\n\n  to {\n    translate: 0 0;\n    opacity: 1;\n  }\n}\n\n/* ═══ FORM INTEGRATION ═══ */\n.ren-date-range-picker {\n  input[type='hidden'] {\n    display: none;\n  }\n}\n\n\n/* ═══ REN DATE RANGE PICKER WEB COMPONENT ═══\n   A composable date range picker with dual side-by-side calendars,\n   preset shortcuts, and confirmation actions.\n\n   Features:\n   - Dual calendar view (current + next month)\n   - Configurable preset shortcuts (Last 7 days, Last 30 days, etc.)\n   - Apply / Cancel workflow for intentional selection\n   - Locale-aware date formatting via Intl.DateTimeFormat\n   - Popover API dropdown with CSS anchor positioning\n   - Full keyboard navigation (delegates to RenCalendar)\n   - Focus management and outside-click dismissal\n   - Form integration with hidden inputs (start + end)\n   - Min/max date constraints\n\n   Dependencies:\n   - RenCalendar (ren-calendar.js) must be registered\n\n   Usage:\n   <ren-date-range-picker\n     placeholder=\"Select date range\"\n     format=\"short\"\n     locale=\"en-US\"\n     presets=\"last7,last30,thisMonth,lastMonth,thisYear\"\n     min=\"2024-01-01\"\n     max=\"2027-12-31\"\n   ></ren-date-range-picker>\n\n   Events:\n   - ren-change: { value: { start, end }, formattedValue, preset? }\n   - ren-date-range-open\n   - ren-date-range-close\n   ══════════════════════════════════════════════════════════════════ */\n\n/* ═══ PRESET DEFINITIONS ═══ */\nconst PRESETS = {\n  today: {\n    label: 'Today',\n    getRange: () => {\n      const now = new Date();\n      const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n      return { start: today, end: today };\n    },\n  },\n  yesterday: {\n    label: 'Yesterday',\n    getRange: () => {\n      const now = new Date();\n      const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);\n      return { start: yesterday, end: yesterday };\n    },\n  },\n  last7: {\n    label: 'Last 7 days',\n    getRange: () => {\n      const now = new Date();\n      const end = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n      const start = new Date(end);\n      start.setDate(start.getDate() - 6);\n      return { start, end };\n    },\n  },\n  last14: {\n    label: 'Last 14 days',\n    getRange: () => {\n      const now = new Date();\n      const end = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n      const start = new Date(end);\n      start.setDate(start.getDate() - 13);\n      return { start, end };\n    },\n  },\n  last30: {\n    label: 'Last 30 days',\n    getRange: () => {\n      const now = new Date();\n      const end = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n      const start = new Date(end);\n      start.setDate(start.getDate() - 29);\n      return { start, end };\n    },\n  },\n  last90: {\n    label: 'Last 90 days',\n    getRange: () => {\n      const now = new Date();\n      const end = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n      const start = new Date(end);\n      start.setDate(start.getDate() - 89);\n      return { start, end };\n    },\n  },\n  thisWeek: {\n    label: 'This week',\n    getRange: () => {\n      const now = new Date();\n      const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n      const dayOfWeek = today.getDay();\n      const start = new Date(today);\n      start.setDate(start.getDate() - dayOfWeek);\n      const end = new Date(start);\n      end.setDate(end.getDate() + 6);\n      return { start, end };\n    },\n  },\n  lastWeek: {\n    label: 'Last week',\n    getRange: () => {\n      const now = new Date();\n      const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n      const dayOfWeek = today.getDay();\n      const end = new Date(today);\n      end.setDate(end.getDate() - dayOfWeek - 1);\n      const start = new Date(end);\n      start.setDate(start.getDate() - 6);\n      return { start, end };\n    },\n  },\n  thisMonth: {\n    label: 'This month',\n    getRange: () => {\n      const now = new Date();\n      const start = new Date(now.getFullYear(), now.getMonth(), 1);\n      const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);\n      return { start, end };\n    },\n  },\n  lastMonth: {\n    label: 'Last month',\n    getRange: () => {\n      const now = new Date();\n      const start = new Date(now.getFullYear(), now.getMonth() - 1, 1);\n      const end = new Date(now.getFullYear(), now.getMonth(), 0);\n      return { start, end };\n    },\n  },\n  thisQuarter: {\n    label: 'This quarter',\n    getRange: () => {\n      const now = new Date();\n      const quarterStart = Math.floor(now.getMonth() / 3) * 3;\n      const start = new Date(now.getFullYear(), quarterStart, 1);\n      const end = new Date(now.getFullYear(), quarterStart + 3, 0);\n      return { start, end };\n    },\n  },\n  thisYear: {\n    label: 'This year',\n    getRange: () => {\n      const now = new Date();\n      const start = new Date(now.getFullYear(), 0, 1);\n      const end = new Date(now.getFullYear(), 11, 31);\n      return { start, end };\n    },\n  },\n  lastYear: {\n    label: 'Last year',\n    getRange: () => {\n      const now = new Date();\n      const start = new Date(now.getFullYear() - 1, 0, 1);\n      const end = new Date(now.getFullYear() - 1, 11, 31);\n      return { start, end };\n    },\n  },\n};\n\nconst DATE_RANGE_SIDES = new Set(['top', 'right', 'bottom', 'left']);\n\nfunction normalizeDateRangeSide(value) {\n  const side = String(value || 'bottom').toLowerCase().split('-')[0];\n\n  return DATE_RANGE_SIDES.has(side) ? side : 'bottom';\n}\n\nexport class RenDateRangePicker extends HTMLElement {\n  static observedAttributes = ['placement'];\n\n  constructor() {\n    super();\n\n    /* ═══ STATE ═══ */\n    this.calendarLeft = null;\n    this.calendarRight = null;\n    this.dropdown = null;\n    this.trigger = null;\n    this.isOpen = false;\n\n    /* ═══ CONFIRMED (applied) range ═══ */\n    this.confirmedStart = null;\n    this.confirmedEnd = null;\n\n    /* ═══ DRAFT (in-progress) range ═══ */\n    this.draftStart = null;\n    this.draftEnd = null;\n    this.activePreset = null;\n\n    /* ═══ CONFIGURATION ═══ */\n    this.format = 'short';\n    this.locale = 'en-US';\n    this.placeholder = 'Select date range';\n    this.presetKeys = ['last7', 'last30', 'thisMonth', 'lastMonth'];\n    this.minDate = null;\n    this.maxDate = null;\n\n    /* ═══ CALENDAR MONTH OFFSETS ═══ */\n    this.leftMonth = new Date();\n    this.rightMonth = new Date(\n      this.leftMonth.getFullYear(),\n      this.leftMonth.getMonth() + 1\n    );\n\n    /* ═══ BIND METHODS ═══ */\n    this.handleTriggerClick = this.handleTriggerClick.bind(this);\n    this.handleTriggerKeyDown = this.handleTriggerKeyDown.bind(this);\n    this.handleDocumentClick = this.handleDocumentClick.bind(this);\n    this.handleCalendarSelect = this.handleCalendarSelect.bind(this);\n    this.handleApply = this.handleApply.bind(this);\n    this.handleCancel = this.handleCancel.bind(this);\n  }\n\n  connectedCallback() {\n    /* ═══ READ ATTRIBUTES ═══ */\n    this.format = this.getAttribute('format') || 'short';\n    this.locale = this.getAttribute('locale') || 'en-US';\n    this.placeholder = this.getAttribute('placeholder') || 'Select date range';\n\n    const presetsAttr = this.getAttribute('presets');\n    if (presetsAttr) {\n      this.presetKeys = presetsAttr.split(',').map((k) => k.trim());\n    }\n\n    const minAttr = this.getAttribute('min');\n    if (minAttr) this.minDate = minAttr;\n\n    const maxAttr = this.getAttribute('max');\n    if (maxAttr) this.maxDate = maxAttr;\n\n    /* ═══ PARSE INITIAL VALUE ═══ */\n    const startAttr = this.getAttribute('start');\n    const endAttr = this.getAttribute('end');\n    if (startAttr && endAttr) {\n      this.confirmedStart = this.parseDate(startAttr);\n      this.confirmedEnd = this.parseDate(endAttr);\n      this.draftStart = this.parseDate(startAttr);\n      this.draftEnd = this.parseDate(endAttr);\n      this.leftMonth = new Date(this.confirmedStart.getFullYear(), this.confirmedStart.getMonth());\n      this.rightMonth = new Date(this.leftMonth.getFullYear(), this.leftMonth.getMonth() + 1);\n    }\n\n    /* ═══ RENDER ═══ */\n    this.render();\n    this.setupEventListeners();\n    this.addHiddenInputs();\n    this.updateTriggerDisplay();\n  }\n\n  disconnectedCallback() {\n    if (this.trigger) {\n      this.trigger.removeEventListener('click', this.handleTriggerClick);\n      this.trigger.removeEventListener('keydown', this.handleTriggerKeyDown);\n    }\n\n    document.removeEventListener('click', this.handleDocumentClick);\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'placement' && oldValue !== newValue) {\n      this.syncPlacement();\n    }\n  }\n\n  /* ═══ RENDER FULL COMPONENT ═══ */\n  render() {\n    this.innerHTML = '';\n    this.classList.add('ren-date-range-picker');\n\n    /* ═══ TRIGGER BUTTON ═══ */\n    const trigger = document.createElement('button');\n    trigger.className = 'ren-date-range-trigger ren-date-range-empty';\n    trigger.setAttribute('type', 'button');\n    trigger.setAttribute('aria-haspopup', 'dialog');\n    trigger.setAttribute('aria-expanded', 'false');\n    trigger.setAttribute('data-placeholder', this.placeholder);\n\n    const valueEl = document.createElement('span');\n    valueEl.className = 'ren-date-range-value';\n\n    const startEl = document.createElement('span');\n    startEl.className = 'ren-date-range-start';\n\n    const sepEl = document.createElement('span');\n    sepEl.className = 'ren-date-range-separator';\n    sepEl.textContent = '→';\n\n    const endEl = document.createElement('span');\n    endEl.className = 'ren-date-range-end';\n\n    valueEl.appendChild(startEl);\n    valueEl.appendChild(sepEl);\n    valueEl.appendChild(endEl);\n    trigger.appendChild(valueEl);\n    this.appendChild(trigger);\n    this.trigger = trigger;\n\n    /* ═══ DROPDOWN (POPOVER) ═══ */\n    const dropdown = document.createElement('div');\n    dropdown.className = 'ren-date-range-dropdown';\n    dropdown.setAttribute('popover', 'manual');\n    dropdown.setAttribute('role', 'dialog');\n    dropdown.setAttribute('aria-label', 'Date range selection');\n\n    const inner = document.createElement('div');\n    inner.className = 'ren-date-range-inner';\n\n    /* ═══ PRESETS ═══ */\n    if (this.presetKeys.length > 0) {\n      const presetsEl = document.createElement('div');\n      presetsEl.className = 'ren-date-range-presets';\n      presetsEl.setAttribute('role', 'listbox');\n      presetsEl.setAttribute('aria-label', 'Date range presets');\n\n      this.presetKeys.forEach((key) => {\n        const preset = PRESETS[key];\n        if (!preset) return;\n\n        const btn = document.createElement('button');\n        btn.className = 'ren-date-range-preset';\n        btn.setAttribute('type', 'button');\n        btn.setAttribute('role', 'option');\n        btn.setAttribute('data-preset', key);\n        btn.textContent = preset.label;\n\n        btn.addEventListener('click', () => this.applyPreset(key));\n        presetsEl.appendChild(btn);\n      });\n\n      inner.appendChild(presetsEl);\n    }\n\n    /* ═══ DUAL CALENDARS ═══ */\n    const calendarsEl = document.createElement('div');\n    calendarsEl.className = 'ren-date-range-calendars';\n\n    const calLeft = document.createElement('ren-calendar');\n    calLeft.setAttribute('mode', 'range');\n    calLeft.setAttribute('locale', this.locale);\n    if (this.minDate) calLeft.setAttribute('min', this.minDate);\n    if (this.maxDate) calLeft.setAttribute('max', this.maxDate);\n\n    const divider = document.createElement('div');\n    divider.className = 'ren-date-range-divider';\n    divider.setAttribute('aria-hidden', 'true');\n\n    const calRight = document.createElement('ren-calendar');\n    calRight.setAttribute('mode', 'range');\n    calRight.setAttribute('locale', this.locale);\n    if (this.minDate) calRight.setAttribute('min', this.minDate);\n    if (this.maxDate) calRight.setAttribute('max', this.maxDate);\n\n    calendarsEl.appendChild(calLeft);\n    calendarsEl.appendChild(divider);\n    calendarsEl.appendChild(calRight);\n    inner.appendChild(calendarsEl);\n\n    /* ═══ FOOTER WITH SUMMARY + ACTIONS ═══ */\n    const footer = document.createElement('div');\n    footer.className = 'ren-date-range-footer';\n\n    const summary = document.createElement('div');\n    summary.className = 'ren-date-range-summary';\n    summary.setAttribute('aria-live', 'polite');\n\n    const actions = document.createElement('div');\n    actions.className = 'ren-date-range-actions';\n\n    const cancelBtn = document.createElement('button');\n    cancelBtn.className = 'ren-date-range-cancel';\n    cancelBtn.setAttribute('type', 'button');\n    cancelBtn.textContent = 'Cancel';\n\n    const applyBtn = document.createElement('button');\n    applyBtn.className = 'ren-date-range-apply';\n    applyBtn.setAttribute('type', 'button');\n    applyBtn.textContent = 'Apply';\n    applyBtn.disabled = true;\n\n    actions.appendChild(cancelBtn);\n    actions.appendChild(applyBtn);\n    footer.appendChild(summary);\n    footer.appendChild(actions);\n    inner.appendChild(footer);\n\n    dropdown.appendChild(inner);\n    this.appendChild(dropdown);\n    this.dropdown = dropdown;\n\n    /* ═══ STORE REFS ═══ */\n    this.calendarLeft = calLeft;\n    this.calendarRight = calRight;\n    this.summaryEl = summary;\n    this.applyBtn = applyBtn;\n    this.cancelBtn = cancelBtn;\n\n    this.syncPlacement();\n  }\n\n  /* ═══ MIRROR PLACEMENT TO PUBLIC DATA ATTRIBUTES ═══ */\n  syncPlacement() {\n    const side = normalizeDateRangeSide(this.getAttribute('placement'));\n\n    this.setAttribute('data-side', side);\n    if (this.dropdown) {\n      this.dropdown.setAttribute('data-side', side);\n    }\n  }\n\n  /* ═══ SET UP EVENT LISTENERS ═══ */\n  setupEventListeners() {\n    this.trigger.addEventListener('click', this.handleTriggerClick);\n    this.trigger.addEventListener('keydown', this.handleTriggerKeyDown);\n    document.addEventListener('click', this.handleDocumentClick);\n\n    /* ═══ CALENDAR SELECT EVENTS ═══ */\n    this.calendarLeft.addEventListener('ren-date-select', this.handleCalendarSelect);\n    this.calendarRight.addEventListener('ren-date-select', this.handleCalendarSelect);\n\n    /* ═══ FOOTER ACTIONS ═══ */\n    this.applyBtn.addEventListener('click', this.handleApply);\n    this.cancelBtn.addEventListener('click', this.handleCancel);\n\n    /* ═══ SYNC CALENDAR MONTHS ═══ */\n    this.syncCalendarMonths();\n  }\n\n  /* ═══ SYNC LEFT/RIGHT CALENDAR MONTHS ═══ */\n  syncCalendarMonths() {\n    if (this.calendarLeft && this.calendarLeft.goToMonth) {\n      this.calendarLeft.goToMonth(\n        this.leftMonth.getFullYear(),\n        this.leftMonth.getMonth()\n      );\n    }\n\n    if (this.calendarRight && this.calendarRight.goToMonth) {\n      this.calendarRight.goToMonth(\n        this.rightMonth.getFullYear(),\n        this.rightMonth.getMonth()\n      );\n    }\n\n    /* ═══ APPLY DRAFT RANGE TO BOTH CALENDARS ═══ */\n    if (this.draftStart && this.draftEnd) {\n      const startStr = this.dateToString(this.draftStart);\n      const endStr = this.dateToString(this.draftEnd);\n\n      if (this.calendarLeft.setRange) {\n        this.calendarLeft.setRange(startStr, endStr);\n      }\n      if (this.calendarRight.setRange) {\n        this.calendarRight.setRange(startStr, endStr);\n      }\n    }\n  }\n\n  /* ═══ ADD HIDDEN INPUTS FOR FORM SUBMISSION ═══ */\n  addHiddenInputs() {\n    const name = this.getAttribute('name') || 'date-range';\n\n    let hiddenStart = this.querySelector('input[name$=\"-start\"]');\n    if (!hiddenStart) {\n      hiddenStart = document.createElement('input');\n      hiddenStart.type = 'hidden';\n      hiddenStart.name = `${name}-start`;\n      this.appendChild(hiddenStart);\n    }\n\n    let hiddenEnd = this.querySelector('input[name$=\"-end\"]');\n    if (!hiddenEnd) {\n      hiddenEnd = document.createElement('input');\n      hiddenEnd.type = 'hidden';\n      hiddenEnd.name = `${name}-end`;\n      this.appendChild(hiddenEnd);\n    }\n\n    this.hiddenStart = hiddenStart;\n    this.hiddenEnd = hiddenEnd;\n  }\n\n  /* ═══ HANDLE TRIGGER CLICK ═══ */\n  handleTriggerClick(event) {\n    event.preventDefault();\n    event.stopPropagation();\n    if (this.hasAttribute('disabled')) return;\n    this.isOpen ? this.close() : this.open();\n  }\n\n  /* ═══ HANDLE TRIGGER KEYBOARD ═══ */\n  handleTriggerKeyDown(event) {\n    if (this.hasAttribute('disabled')) return;\n\n    if (event.key === 'Enter' || event.key === ' ') {\n      event.preventDefault();\n      this.open();\n    }\n\n    if (event.key === 'Escape') {\n      this.close();\n    }\n  }\n\n  /* ═══ HANDLE OUTSIDE CLICK ═══ */\n  handleDocumentClick(event) {\n    if (!this.contains(event.target) && this.isOpen) {\n      this.handleCancel();\n    }\n  }\n\n  /* ═══ HANDLE CALENDAR DATE SELECT ═══ */\n  handleCalendarSelect(event) {\n    const detail = event.detail;\n\n    if (detail.range) {\n      /* ═══ UPDATE DRAFT RANGE ═══ */\n      if (detail.range.start) {\n        this.draftStart = detail.range.start;\n      }\n      if (detail.range.end) {\n        this.draftEnd = detail.range.end;\n      }\n\n      /* ═══ ENSURE START < END ═══ */\n      if (this.draftStart && this.draftEnd && this.draftStart > this.draftEnd) {\n        const tmp = this.draftStart;\n        this.draftStart = this.draftEnd;\n        this.draftEnd = tmp;\n      }\n\n      /* ═══ SYNC RANGE ACROSS BOTH CALENDARS ═══ */\n      if (this.draftStart && this.draftEnd) {\n        const startStr = this.dateToString(this.draftStart);\n        const endStr = this.dateToString(this.draftEnd);\n\n        [this.calendarLeft, this.calendarRight].forEach((cal) => {\n          if (cal !== event.target && cal.setRange) {\n            cal.setRange(startStr, endStr);\n          }\n        });\n      }\n\n      /* ═══ CLEAR ACTIVE PRESET ═══ */\n      this.activePreset = null;\n      this.updatePresetHighlight();\n\n      /* ═══ UPDATE SUMMARY + APPLY BUTTON ═══ */\n      this.updateSummary();\n      this.updateApplyState();\n    }\n  }\n\n  /* ═══ APPLY PRESET ═══ */\n  applyPreset(key) {\n    const preset = PRESETS[key];\n    if (!preset) return;\n\n    const range = preset.getRange();\n    this.draftStart = range.start;\n    this.draftEnd = range.end;\n    this.activePreset = key;\n\n    /* ═══ NAVIGATE CALENDARS TO SHOW THE RANGE ═══ */\n    this.leftMonth = new Date(range.start.getFullYear(), range.start.getMonth());\n    this.rightMonth = new Date(this.leftMonth.getFullYear(), this.leftMonth.getMonth() + 1);\n\n    /* ═══ IF END IS IN A DIFFERENT MONTH, ADJUST ═══ */\n    if (range.end.getMonth() !== range.start.getMonth() || range.end.getFullYear() !== range.start.getFullYear()) {\n      this.rightMonth = new Date(range.end.getFullYear(), range.end.getMonth());\n      /* ═══ ENSURE LEFT IS BEFORE RIGHT ═══ */\n      if (this.rightMonth <= this.leftMonth) {\n        this.leftMonth = new Date(this.rightMonth.getFullYear(), this.rightMonth.getMonth() - 1);\n      }\n    }\n\n    this.syncCalendarMonths();\n    this.updatePresetHighlight();\n    this.updateSummary();\n    this.updateApplyState();\n  }\n\n  /* ═══ UPDATE PRESET BUTTON HIGHLIGHT ═══ */\n  updatePresetHighlight() {\n    const presetBtns = this.querySelectorAll('.ren-date-range-preset');\n    presetBtns.forEach((btn) => {\n      const key = btn.getAttribute('data-preset');\n      if (key === this.activePreset) {\n        btn.setAttribute('aria-selected', 'true');\n      } else {\n        btn.removeAttribute('aria-selected');\n      }\n    });\n  }\n\n  /* ═══ UPDATE FOOTER SUMMARY ═══ */\n  updateSummary() {\n    if (!this.summaryEl) return;\n\n    if (this.draftStart && this.draftEnd) {\n      const start = this.formatDate(this.draftStart);\n      const end = this.formatDate(this.draftEnd);\n      const days = this.daysBetween(this.draftStart, this.draftEnd);\n      this.summaryEl.innerHTML = `<strong>${start}</strong> → <strong>${end}</strong> (${days} day${days !== 1 ? 's' : ''})`;\n    } else if (this.draftStart) {\n      this.summaryEl.innerHTML = `<strong>${this.formatDate(this.draftStart)}</strong> → ...`;\n    } else {\n      this.summaryEl.textContent = 'Select a start date';\n    }\n  }\n\n  /* ═══ UPDATE APPLY BUTTON STATE ═══ */\n  updateApplyState() {\n    if (this.applyBtn) {\n      this.applyBtn.disabled = !(this.draftStart && this.draftEnd);\n    }\n  }\n\n  /* ═══ HANDLE APPLY ═══ */\n  handleApply() {\n    if (!this.draftStart || !this.draftEnd) return;\n\n    this.confirmedStart = new Date(this.draftStart);\n    this.confirmedEnd = new Date(this.draftEnd);\n\n    this.updateTriggerDisplay();\n    this.updateHiddenInputs();\n    this.dispatchChangeEvent();\n    this.close();\n  }\n\n  /* ═══ HANDLE CANCEL ═══ */\n  handleCancel() {\n    /* ═══ REVERT DRAFT TO CONFIRMED ═══ */\n    this.draftStart = this.confirmedStart ? new Date(this.confirmedStart) : null;\n    this.draftEnd = this.confirmedEnd ? new Date(this.confirmedEnd) : null;\n    this.activePreset = null;\n    this.close();\n  }\n\n  /* ═══ OPEN DROPDOWN ═══ */\n  open() {\n    if (this.isOpen || this.hasAttribute('disabled')) return;\n    if (!this.dropdown) return;\n\n    /* ═══ RESET DRAFT TO CONFIRMED ═══ */\n    this.draftStart = this.confirmedStart ? new Date(this.confirmedStart) : null;\n    this.draftEnd = this.confirmedEnd ? new Date(this.confirmedEnd) : null;\n\n    /* ═══ SET CALENDAR MONTHS ═══ */\n    if (this.confirmedStart) {\n      this.leftMonth = new Date(this.confirmedStart.getFullYear(), this.confirmedStart.getMonth());\n      this.rightMonth = new Date(this.leftMonth.getFullYear(), this.leftMonth.getMonth() + 1);\n    }\n\n    this.syncPlacement();\n\n    try {\n      this.dropdown.showPopover();\n    } catch {\n      this.dropdown.style.display = 'block';\n    }\n\n    this.isOpen = true;\n\n    if (this.trigger) {\n      this.trigger.setAttribute('aria-expanded', 'true');\n    }\n\n    this.syncCalendarMonths();\n    this.updateSummary();\n    this.updateApplyState();\n    this.updatePresetHighlight();\n\n    /* ═══ FOCUS FIRST PRESET OR CALENDAR ═══ */\n    setTimeout(() => {\n      const firstPreset = this.querySelector('.ren-date-range-preset');\n      if (firstPreset) {\n        firstPreset.focus();\n      } else {\n        const firstDay = this.calendarLeft?.querySelector('.ren-calendar-day:not([disabled])');\n        if (firstDay) firstDay.focus();\n      }\n    }, 100);\n\n    this.dispatchEvent(\n      new CustomEvent('ren-date-range-open', {\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ CLOSE DROPDOWN ═══ */\n  close() {\n    if (!this.isOpen) return;\n    if (!this.dropdown) return;\n\n    try {\n      this.dropdown.hidePopover();\n    } catch {\n      this.dropdown.style.display = 'none';\n    }\n\n    this.isOpen = false;\n\n    if (this.trigger) {\n      this.trigger.setAttribute('aria-expanded', 'false');\n      this.trigger.focus();\n    }\n\n    this.dispatchEvent(\n      new CustomEvent('ren-date-range-close', {\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ UPDATE TRIGGER DISPLAY ═══ */\n  updateTriggerDisplay() {\n    if (!this.trigger) return;\n\n    if (this.confirmedStart && this.confirmedEnd) {\n      const startEl = this.trigger.querySelector('.ren-date-range-start');\n      const endEl = this.trigger.querySelector('.ren-date-range-end');\n\n      if (startEl) startEl.textContent = this.formatDate(this.confirmedStart);\n      if (endEl) endEl.textContent = this.formatDate(this.confirmedEnd);\n\n      this.trigger.classList.remove('ren-date-range-empty');\n    } else {\n      this.trigger.classList.add('ren-date-range-empty');\n    }\n  }\n\n  /* ═══ UPDATE HIDDEN INPUTS ═══ */\n  updateHiddenInputs() {\n    if (this.hiddenStart) {\n      this.hiddenStart.value = this.confirmedStart ? this.dateToString(this.confirmedStart) : '';\n    }\n    if (this.hiddenEnd) {\n      this.hiddenEnd.value = this.confirmedEnd ? this.dateToString(this.confirmedEnd) : '';\n    }\n  }\n\n  /* ═══ DISPATCH CHANGE EVENT ═══ */\n  dispatchChangeEvent() {\n    const value =\n      this.confirmedStart && this.confirmedEnd\n        ? {\n            start: this.dateToString(this.confirmedStart),\n            end: this.dateToString(this.confirmedEnd),\n          }\n        : null;\n\n    const formattedValue =\n      this.confirmedStart && this.confirmedEnd\n        ? `${this.formatDate(this.confirmedStart)} → ${this.formatDate(this.confirmedEnd)}`\n        : '';\n\n    this.dispatchEvent(\n      new CustomEvent('ren-change', {\n        detail: {\n          value,\n          formattedValue,\n          preset: this.activePreset || null,\n          days: this.confirmedStart && this.confirmedEnd\n            ? this.daysBetween(this.confirmedStart, this.confirmedEnd)\n            : 0,\n        },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ FORMAT DATE ═══ */\n  formatDate(date) {\n    if (!date) return '';\n    const formatter = new Intl.DateTimeFormat(this.locale, {\n      year: 'numeric',\n      month: this.format === 'short' ? '2-digit' : this.format === 'long' ? 'long' : 'short',\n      day: '2-digit',\n    });\n    return formatter.format(date);\n  }\n\n  /* ═══ DATE TO ISO STRING ═══ */\n  dateToString(date) {\n    const year = date.getFullYear();\n    const month = String(date.getMonth() + 1).padStart(2, '0');\n    const day = String(date.getDate()).padStart(2, '0');\n    return `${year}-${month}-${day}`;\n  }\n\n  parseDate(value) {\n    if (value instanceof Date) return new Date(value.getTime());\n    const match = String(value).match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n    return match ? new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3])) : new Date(value);\n  }\n\n  /* ═══ DAYS BETWEEN TWO DATES ═══ */\n  daysBetween(start, end) {\n    const MS_PER_DAY = 1000 * 60 * 60 * 24;\n    const diff = Math.abs(end.getTime() - start.getTime());\n    return Math.round(diff / MS_PER_DAY) + 1;\n  }\n\n  /* ═══ PUBLIC API ═══ */\n\n  /* ═══ GET VALUE ═══ */\n  getValue() {\n    if (!this.confirmedStart || !this.confirmedEnd) return null;\n    return {\n      start: this.dateToString(this.confirmedStart),\n      end: this.dateToString(this.confirmedEnd),\n    };\n  }\n\n  /* ═══ SET VALUE ═══ */\n  setValue(start, end) {\n    this.confirmedStart = this.parseDate(start);\n    this.confirmedEnd = this.parseDate(end);\n    this.draftStart = this.parseDate(start);\n    this.draftEnd = this.parseDate(end);\n    this.updateTriggerDisplay();\n    this.updateHiddenInputs();\n  }\n\n  /* ═══ CLEAR VALUE ═══ */\n  clear() {\n    this.confirmedStart = null;\n    this.confirmedEnd = null;\n    this.draftStart = null;\n    this.draftEnd = null;\n    this.activePreset = null;\n    this.updateTriggerDisplay();\n    this.updateHiddenInputs();\n  }\n\n  /* ═══ GETTERS/SETTERS ═══ */\n  get disabled() {\n    return this.hasAttribute('disabled');\n  }\n\n  set disabled(val) {\n    if (val) {\n      this.setAttribute('disabled', '');\n      if (this.trigger) this.trigger.disabled = true;\n    } else {\n      this.removeAttribute('disabled');\n      if (this.trigger) this.trigger.disabled = false;\n    }\n  }\n\n  get value() {\n    return this.getValue();\n  }\n\n  set value(val) {\n    if (val && val.start && val.end) {\n      this.setValue(val.start, val.end);\n    } else {\n      this.clear();\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-date-range-picker')) {\n  customElements.define('ren-date-range-picker', RenDateRangePicker);\n}\n",
      "path": "components/composites/ren-date-range-picker",
      "id": "component:composite:ren-date-range-picker",
      "type": "component",
      "name": "ren-date-range-picker"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-alert-dialog",
          ".ren-dialog",
          ".ren-dialog-body",
          ".ren-dialog-close",
          ".ren-dialog-description",
          ".ren-dialog-footer",
          ".ren-dialog-full",
          ".ren-dialog-header",
          ".ren-dialog-lg",
          ".ren-dialog-md",
          ".ren-dialog-sm",
          ".ren-dialog-title",
          ".ren-dialog-wrapper",
          ".ren-dialog-xl"
        ],
        "tokens": [
          "--color-background-active",
          "--color-background-hover",
          "--color-border",
          "--color-border-hover",
          "--color-focus-ring",
          "--color-overlay",
          "--color-surface",
          "--color-surface-overlay",
          "--color-text",
          "--color-text-muted",
          "--dialog-max-width",
          "--duration-enter",
          "--duration-normal",
          "--ease-enter",
          "--ease-out",
          "--radius-lg",
          "--radius-md",
          "--radius-xl",
          "--ren-dialog-backdrop",
          "--ren-dialog-bg",
          "--ren-dialog-border-color",
          "--ren-dialog-duration",
          "--ren-dialog-easing",
          "--ren-dialog-gap",
          "--ren-dialog-padding",
          "--ren-dialog-radius",
          "--ren-dialog-shadow",
          "--ren-dialog-width",
          "--ring-offset-width",
          "--ring-width",
          "--shadow-xl",
          "--space-2",
          "--space-3",
          "--space-4",
          "--space-6",
          "--transition-overlay",
          "--transition-tactile"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-dialog\n\nModal dialog composite built on the native `<dialog>` element with focus\ntrapping, scroll locking, animated open/close, and an alert-mode for\ncritical confirmations.\n\nLoad this file after `ren-design.md` and before generating, editing, or\nreviewing any `<ren-dialog>` UI.\n\n## Purpose\n\nOwns the modal-overlay UX of a `<dialog showModal>` plus keyboard / a11y\nbehavior: focus trap inside the dialog, restore focus on close, dismiss\non Escape (unless `no-escape`), close on backdrop click (unless `alert`),\nand animated open/close that respects `prefers-reduced-motion`. Supports\nsize variants and a mobile sheet adaptation.\n\n## Use When\n\n- The UI requires the user's full attention before continuing — confirms,\n  destructive flows, multi-step settings panes.\n- The interaction blocks the page underneath (background goes inert).\n- A critical alert dialog should not be dismissable by accident\n  (use `alert`).\n- A multi-step settings or onboarding form should appear inline as a modal.\n\n## Do Not Use When\n\n- The disclosure is non-modal — use `ren-popover`, `ren-tooltip`,\n  `ren-hover-card`.\n- The disclosure is a side panel — use `ren-sheet`.\n- The disclosure is a transient notification — use `ren-toast` /\n  `ren-banner`.\n- The control is a select / combobox / menu — use those composites.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The flow blocks the underlying page (modal).\"\n    - \"You need focus trap, scroll lock, Escape-to-close, and backdrop dismiss.\"\n    - \"You need an alert variant that disables accidental dismissal.\"\n    - \"You want the native <dialog> semantics (form method=dialog, returnValue, etc.).\"\n  avoidWhen:\n    - \"The disclosure is non-modal (popover, tooltip).\"\n    - \"The disclosure is a side panel — use ren-sheet.\"\n    - \"The notification should not steal focus — use ren-toast.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-dialog/ren-dialog.css\"\n  js:\n    - \"rends/components/composites/ren-dialog/ren-dialog.js\"\n  notes:\n    - \"JS is required (focus trap, animation, scroll lock). The native <dialog> alone does not provide RenDS' UX guarantees.\"\n\nrequiredMarkup:\n  - \"<ren-dialog> wraps a real <dialog> element. Do not replace the inner <dialog> with a <div>.\"\n  - \"Provide a unique id on <ren-dialog> when triggers reference it via data-dialog-trigger.\"\n  - \"Always include .ren-dialog-title (rendered as a real heading) for the accessible name.\"\n  - \"Close affordances use [data-dialog-close]; do not call dialog.close() directly from arbitrary handlers.\"\n\nforbiddenPatterns:\n  - \"<ren-dialog> wrapping a <div role=\\\"dialog\\\"> instead of a real <dialog>.\"\n  - \"Animating the backdrop manually with hardcoded rgba — use --ren-dialog-backdrop.\"\n  - \"Programmatic open via .show() when the user expects modal behavior — use .open() / .show() per native semantics.\"\n  - \"Putting form submit buttons outside <form method=\\\"dialog\\\"> when the dialog is a confirmation.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: every --ren-dialog-* listed in Public Token API.\"\n    - \"Semantic tokens for content inside the dialog (--color-text, --color-text-muted, etc.).\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, etc.) in consumer code.\"\n    - \"Hardcoded backdrop colors; use --ren-dialog-backdrop.\"\n\naccessibility:\n  required:\n    - \"Use a real <dialog> inside <ren-dialog> so the user agent provides modality semantics.\"\n    - \".ren-dialog-title supplies the accessible label when no aria-label is set.\"\n    - \"Modal dialogs trap focus and inert the rest of the page automatically; do not break the trap.\"\n    - \"Escape closes the dialog unless no-escape is set.\"\n    - \"Backdrop click closes the dialog unless alert is set.\"\n    - \"Restore focus to the trigger after close (handled by the component).\"\n```\n\n## Required CSS / JS Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-dialog/ren-dialog.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-dialog/ren-dialog.js\"></script>\n```\n\n## Canonical Markup\n\nConfirmation dialog with trigger:\n\n```html\n<button class=\"ren-btn\" data-dialog-trigger=\"confirm-delete\">Delete project</button>\n\n<ren-dialog id=\"confirm-delete\" alert>\n  <dialog>\n    <div class=\"ren-dialog-header\">\n      <h2 class=\"ren-dialog-title\">Delete project</h2>\n      <button class=\"ren-dialog-close\" data-dialog-close aria-label=\"Close\"></button>\n    </div>\n    <div class=\"ren-dialog-body\">\n      <p>This action cannot be undone.</p>\n    </div>\n    <div class=\"ren-dialog-footer\">\n      <button class=\"ren-btn ren-btn-secondary\" data-dialog-close>Cancel</button>\n      <button class=\"ren-btn ren-btn-danger\" data-dialog-close=\"delete\">Delete</button>\n    </div>\n  </dialog>\n</ren-dialog>\n```\n\nNative command API (when supported):\n\n```html\n<button commandfor=\"settings\" command=\"show-modal\">Open settings</button>\n<ren-dialog id=\"settings\">\n  <dialog>...</dialog>\n</ren-dialog>\n```\n\nProgrammatic API:\n\n```js\nconst dlg = document.querySelector('#settings');\ndlg.show();              // open\ndlg.close('saved');      // close with returnValue\ndlg.addEventListener('ren-close', (e) => console.log(e.detail.returnValue));\n```\n\n## Attributes, Events, and API\n\n- Host attributes: `open`, `alert`, `size=\"sm|md|lg|xl|full\"`, `no-escape`.\n- Trigger attributes: `[data-dialog-trigger=\"<id>\"]`, native\n  `commandfor` + `command=\"show-modal\"` where supported.\n- Close attributes: `[data-dialog-close]`. Optional value becomes the\n  dialog's `returnValue`.\n- Methods: `show()`, `open()`, `close(returnValue)`.\n- Getters: `isOpen`, `dialog`.\n- Events: `ren-open`, `ren-close` (with `detail.returnValue`).\n\n## Variants\n\n| Class                  | Role                                   |\n|------------------------|----------------------------------------|\n| `.ren-dialog`          | Default surface (applied internally).  |\n| `.ren-dialog-sm`       | Small width.                           |\n| `.ren-dialog-md`       | Medium (default size).                 |\n| `.ren-dialog-lg`       | Large width.                           |\n| `.ren-dialog-xl`       | Extra-large.                           |\n| `.ren-dialog-full`     | Full-screen on desktop.                |\n| `.ren-alert-dialog`    | Critical confirmation styling.         |\n| `.ren-dialog-header`   | Header slot.                           |\n| `.ren-dialog-title`    | Heading slot (renders as `<h*>`).      |\n| `.ren-dialog-description` | Subheading / description slot.      |\n| `.ren-dialog-body`     | Scrollable body slot.                  |\n| `.ren-dialog-footer`   | Action footer.                         |\n| `.ren-dialog-close`    | Close button slot.                     |\n\n## States\n\n| Selector / attr             | Meaning                                  |\n|-----------------------------|------------------------------------------|\n| `[open]`                    | Native `<dialog>` open state.            |\n| `[data-mobile-sheet]`       | Mobile adaptation as a bottom sheet.     |\n| `[data-dialog-trigger]`     | Marks an opener for a target dialog.     |\n| `[data-dialog-close]`       | Marks a close affordance.                |\n\n## Public Token API\n\n- `--ren-dialog-bg`\n- `--ren-dialog-border-color`\n- `--ren-dialog-radius`\n- `--ren-dialog-padding`\n- `--ren-dialog-gap`\n- `--ren-dialog-width`\n- `--ren-dialog-shadow`\n- `--ren-dialog-backdrop`\n- `--ren-dialog-duration`\n- `--ren-dialog-easing`\n\n## Accessibility Contract\n\n- A real `<dialog>` element supplies modality semantics; never substitute a\n  `<div role=\"dialog\">`.\n- `.ren-dialog-title` provides the accessible name; if you omit it, set\n  `aria-label` on `<ren-dialog>`.\n- The component traps focus inside the dialog and inerts background content.\n- Escape closes the dialog unless `no-escape` is set.\n- Backdrop click closes the dialog unless `alert` is set.\n- After close, focus returns to the original trigger.\n\n## Anti-Patterns\n\n- ❌ `<ren-dialog><div role=\"dialog\">…</div></ren-dialog>` — must be a real\n  `<dialog>` inside.\n- ❌ Calling `dialog.show()` when modal behavior is needed — use `.open()` /\n  the native `.showModal()` flow the component provides.\n- ❌ Custom backdrops via `position: fixed; background: rgba(0,0,0,.5)` —\n  use `--ren-dialog-backdrop`.\n- ❌ Skipping `.ren-dialog-title` and not providing `aria-label`.\n\n## Related Files\n\n- `components/composites/ren-dialog/ren-dialog.css`\n- `components/composites/ren-dialog/ren-dialog.js`\n- `docs/components/ren-dialog.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n\n## Test Expectations\n\n- Run component / docs a11y coverage on focus trap, Escape, backdrop click.\n- Run `npm run lint` after token / selector changes.\n- Manually verify reduced-motion: open / close should still work but\n  without animation.\n\n\n/* ═══════════════════════════════════════════════════════════════════════════\n   REN DIALOG / MODAL COMPONENT\n   Native <dialog> element enhancement with CSS animations and modern APIs\n   Uses @starting-style, transition-behavior: allow-discrete, and CSS nesting\n   ═══════════════════════════════════════════════════════════════════════════ */\n\n/* ═══ DIALOG CONTAINER ═══ */\n.ren-dialog {\n  /* Structure */\n  margin: auto;\n  padding: 0;\n  border: none;\n\n  /* Sizing */\n  max-width: var(--dialog-max-width, 32rem);\n  max-height: 85dvh;\n  width: 90vw;\n  overflow: auto;\n\n  /* Appearance */\n  background: var(--color-surface);\n  color: var(--color-text);\n  border-radius: var(--radius-xl);\n  box-shadow: var(--shadow-xl);\n\n  /* Smooth scrolling for body overflow */\n  scrollbar-gutter: stable;\n\n  /* ═══ ANIMATION STATES ═══\n     Uses semantic motion tokens so global motion tuning\n     cascades to every component. See tokens/semantic/motion.css. */\n  opacity: 1;\n  transform: scale(1) translateY(0);\n\n  transition:\n    opacity   var(--duration-enter) var(--ease-enter),\n    transform var(--duration-enter) var(--ease-enter),\n    overlay   var(--duration-enter) allow-discrete,\n    display   var(--duration-enter) allow-discrete;\n\n  /* ═══ ENTRY ANIMATION (CSS Transitions instead of keyframes) ═══ */\n  @starting-style {\n    opacity: 0;\n    transform: scale(0.95) translateY(8px);\n  }\n\n  /* ═══ BACKDROP STYLING ═══\n     Uses --transition-overlay so scrim fade/blur duration\n     is tuned in one place (tokens/semantic/motion.css). */\n  &::backdrop {\n    background-color: rgb(0, 0, 0, 0);\n    backdrop-filter: blur(0px);\n\n    transition: var(--transition-overlay);\n\n    /* ═══ BACKDROP ENTRY ANIMATION ═══ */\n    @starting-style {\n      background-color: rgb(0, 0, 0, 0);\n      backdrop-filter: blur(0px);\n    }\n  }\n\n  /* ═══ OPEN STATE ═══ */\n  &[open] {\n    opacity: 1;\n    transform: scale(1) translateY(0);\n\n    &::backdrop {\n      background-color: var(--color-overlay);\n      backdrop-filter: blur(4px);\n    }\n  }\n\n  /* ═══ REDUCED MOTION ═══ */\n  @media (prefers-reduced-motion: reduce) {\n    transition: none;\n\n    &::backdrop {\n      transition: none;\n    }\n  }\n}\n\n/* ═══ DIALOG HEADER ═══\n   No bottom border — separation from the body comes from spacing, not lines.\n   Header padding-bottom + body padding-top give the breathing room. */\n.ren-dialog-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--space-4);\n  padding: var(--space-6) var(--space-6) var(--space-3);\n  flex-shrink: 0;\n}\n\n/* ═══ DIALOG TITLE ═══ */\n.ren-dialog-title {\n  margin: 0;\n  font-size: 1.25rem; /* ~20px */\n  line-height: 1.5;\n  font-weight: 600;\n  color: var(--color-text);\n}\n\n/* ═══ DIALOG DESCRIPTION ═══ */\n.ren-dialog-description {\n  margin: 0;\n  margin-top: var(--space-2);\n  font-size: 0.875rem; /* caption */\n  line-height: 1.5;\n  color: var(--color-text-muted);\n}\n\n/* ═══ DIALOG BODY ═══ */\n.ren-dialog-body {\n  padding: var(--space-6);\n  overflow-y: auto;\n  flex: 1;\n  min-height: 0; /* Allow flex shrinking */\n\n  /* ═══ SCROLLBAR STYLING ═══ */\n  &::-webkit-scrollbar {\n    width: 8px;\n  }\n\n  &::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background: var(--color-border);\n    border-radius: 4px;\n\n    &:hover {\n      background: var(--color-border-hover);\n    }\n  }\n}\n\n/* ═══ DIALOG FOOTER ═══ */\n.ren-dialog-footer {\n  display: flex;\n  align-items: center;\n  justify-content: flex-end;\n  gap: var(--space-3);\n  padding: var(--space-6);\n  border-top: 1px solid var(--color-border);\n  flex-shrink: 0;\n}\n\n/* ═══ CLOSE BUTTON ═══ */\n.ren-dialog-close {\n  margin-inline-start: auto;\n  padding: var(--space-2);\n  background: transparent;\n  border: none;\n  cursor: pointer;\n  color: var(--color-text-muted);\n  border-radius: var(--radius-md);\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  line-height: 1;\n\n  /* Tactile transitions (hover / focus / press) */\n  transition: var(--transition-tactile);\n\n  &:hover {\n    background-color: var(--color-background-hover);\n    color: var(--color-text);\n  }\n\n  &:active {\n    background-color: var(--color-background-active);\n  }\n\n  &:focus {\n    outline: var(--ring-width) solid var(--color-focus-ring);\n    outline-offset: var(--ring-offset-width);\n  }\n\n  & svg {\n    width: 1.25rem;\n    height: 1.25rem;\n  }\n}\n\n/* ═══ SIZE VARIANTS ═══ */\n\n/* Small dialog (24rem) */\n.ren-dialog-sm {\n  --dialog-max-width: 24rem;\n}\n\n/* Medium dialog (32rem) - DEFAULT */\n.ren-dialog-md {\n  --dialog-max-width: 32rem;\n}\n\n/* Large dialog (42rem) */\n.ren-dialog-lg {\n  --dialog-max-width: 42rem;\n}\n\n/* Extra Large dialog (56rem) */\n.ren-dialog-xl {\n  --dialog-max-width: 56rem;\n}\n\n/* Full width dialog (95dvw) */\n.ren-dialog-full {\n  max-width: 95dvw;\n  max-height: 95dvh;\n  width: 95dvw;\n}\n\n/* ═══ ALERT DIALOG VARIANT ═══ */\n.ren-alert-dialog {\n  /* No additional styling needed - alert behavior handled in JS */\n}\n\n/* ═══ MOBILE RESPONSIVE ═══ */\n@media (max-width: 640px) {\n  .ren-dialog {\n    max-width: 100%;\n    max-height: 100dvh;\n    width: 100%;\n    border-radius: var(--radius-lg) var(--radius-lg) 0 0;\n\n    /* ═══ BOTTOM SHEET ANIMATION (Mobile) ═══ */\n    &[data-mobile-sheet] {\n      position: fixed;\n      bottom: 0;\n      left: 0;\n      right: 0;\n      margin: 0;\n      border-radius: var(--radius-lg) var(--radius-lg) 0 0;\n\n      transform: translateY(100%);\n      transition:\n        transform var(--duration-enter) var(--ease-enter),\n        opacity   var(--duration-enter) var(--ease-enter),\n        overlay   var(--duration-enter) allow-discrete,\n        display   var(--duration-enter) allow-discrete;\n\n      @starting-style {\n        transform: translateY(100%);\n      }\n\n      &[open] {\n        transform: translateY(0);\n      }\n    }\n  }\n\n  .ren-dialog-header,\n  .ren-dialog-body,\n  .ren-dialog-footer {\n    padding: var(--space-4);\n  }\n\n  .ren-dialog-title {\n    font-size: 1.125rem;\n  }\n}\n\n/* ═══ WEB COMPONENT CONTAINER ═══ */\nren-dialog {\n  display: contents;\n}\n\n.ren-dialog-wrapper {\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n}\n/* Appearance API bridge: public component tokens remain overridable at any scope. */\n.ren-dialog,\n.ren-alert-dialog {\n  width: var(--ren-dialog-width, min(90vw, 500px));\n  padding: var(--ren-dialog-padding, var(--space-6));\n  border-radius: var(--ren-dialog-radius, var(--radius-xl));\n  background: var(--ren-dialog-bg, var(--color-surface-overlay));\n  border-color: var(--ren-dialog-border-color, var(--color-border));\n  box-shadow: var(--ren-dialog-shadow, var(--shadow-xl));\n  gap: var(--ren-dialog-gap, var(--space-4));\n  transition-duration: var(--ren-dialog-duration, var(--duration-normal));\n  transition-timing-function: var(--ren-dialog-easing, var(--ease-out));\n}\n.ren-dialog::backdrop,\n.ren-alert-dialog::backdrop { background: var(--ren-dialog-backdrop, var(--color-overlay)); }\n\n\nimport { createFocusTrap } from '../../../utils/focus-trap.js';\n\n/**\n * RenDialog - Modal/Dialog Web Component\n *\n * A minimal, modern dialog component built on the native <dialog> element.\n * Animations are handled entirely by CSS using @starting-style and transitions.\n *\n * Features:\n * - Native <dialog> element with `.ren-dialog` enhancement\n * - Auto-wiring of [data-dialog-trigger] and [data-dialog-close] buttons\n * - Progressive enhancement: [commandfor] and [command] attributes support\n * - Progressive enhancement: closedby attribute for modern browsers\n * - Focus trap management for accessibility\n * - Escape key support and click-outside dismissal via native dialog + simple click handler\n * - Size variants (sm, md, lg, xl, full)\n * - Mobile-responsive bottom sheet behavior\n * - Custom events: ren-open, ren-close\n *\n * @example\n * <ren-dialog size=\"md\">\n *   <dialog>\n *     <div class=\"ren-dialog-header\">\n *       <h2 class=\"ren-dialog-title\">Confirm Action</h2>\n *       <button class=\"ren-dialog-close\" data-dialog-close>&times;</button>\n *     </div>\n *     <div class=\"ren-dialog-body\">Are you sure?</div>\n *     <div class=\"ren-dialog-footer\">\n *       <button data-dialog-close>Cancel</button>\n *       <button>Confirm</button>\n *     </div>\n *   </dialog>\n * </ren-dialog>\n *\n * @example\n * <button data-dialog-trigger=\"my-dialog\">Open</button>\n * <ren-dialog id=\"my-dialog\">...</ren-dialog>\n *\n * @example\n * <button commandfor=\"my-dialog\" command=\"show-modal\">Open</button>\n * <ren-dialog id=\"my-dialog\">...</ren-dialog>\n */\nexport class RenDialog extends HTMLElement {\n  #dialogElement = null;\n  #focusTrap = null;\n  #abortController = null;\n  #returnFocus = null;\n  #inertElements = new WeakMap();\n  #closeReconciled = true;\n\n  static observedAttributes = ['open', 'alert', 'size'];\n\n  constructor() {\n    super();\n    this.#setupDialogElement();\n  }\n\n  connectedCallback() {\n    if (!this.#abortController || this.#abortController.signal.aborted) {\n      this.#abortController = new AbortController();\n    }\n    this.#initializeDialogElement();\n    this.#wireupTriggers();\n  }\n\n  disconnectedCallback() {\n    this.#abortController?.abort();\n    if (this.#dialogElement?.open) this.#setInert(false);\n    this.#teardownFocusTrap();\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (oldValue === newValue) return;\n\n    switch (name) {\n      case 'open':\n        newValue !== null ? this.show() : this.close();\n        break;\n      case 'size':\n        this.#updateSizeClass();\n        break;\n    }\n  }\n\n  /* ═══ PUBLIC API ═══ */\n\n  /**\n   * Show the dialog with modal behavior\n   * @public\n   */\n  show() {\n    if (!this.#dialogElement?.open) {\n      this.#returnFocus = document.activeElement;\n      this.#closeReconciled = false;\n      this.#dialogElement.showModal();\n      this.#setInert(true);\n      this.setAttribute('open', '');\n      this.setAttribute('data-state', 'open');\n      this.#setupFocusTrap();\n      this.#updateMobileSheet();\n      this.dispatchEvent(new CustomEvent('ren-open', { bubbles: true, composed: true }));\n    }\n  }\n\n  /**\n   * Alias for show()\n   * @public\n   */\n  open() {\n    this.show();\n  }\n\n  /**\n   * Close the dialog\n   * CSS transitions handle the exit animation automatically\n   *\n   * @public\n   * @param {string} [returnValue]\n   */\n  close(returnValue = '') {\n    if (this.#dialogElement?.open) {\n      this.#dialogElement.close(returnValue);\n    }\n  }\n\n  /**\n   * Check if dialog is open\n   * @public\n   * @returns {boolean}\n   */\n  get isOpen() {\n    return this.#dialogElement?.open ?? false;\n  }\n\n  /**\n   * Get the underlying dialog element\n   * @public\n   * @returns {HTMLDialogElement | null}\n   */\n  get dialog() {\n    return this.#dialogElement;\n  }\n\n  /* ═══ PRIVATE: INITIALIZATION ═══ */\n\n  /**\n   * Find or create the dialog element\n   * @private\n   */\n  #setupDialogElement() {\n    this.#dialogElement = this.querySelector('dialog');\n    if (!this.#dialogElement) {\n      this.#dialogElement = document.createElement('dialog');\n      this.appendChild(this.#dialogElement);\n    }\n  }\n\n  /**\n   * Initialize dialog with classes and event listeners\n   * @private\n   */\n  #initializeDialogElement() {\n    if (!this.#dialogElement) this.#setupDialogElement();\n\n    this.#dialogElement.classList.add('ren-dialog');\n    this.#updateSizeClass();\n\n    if (this.hasAttribute('alert')) {\n      this.#dialogElement.classList.add('ren-alert-dialog');\n      this.#dialogElement.setAttribute('role', 'alertdialog');\n    }\n\n    // Add aria-label fallback if no title found\n    if (!this.#dialogElement.getAttribute('aria-label')) {\n      const title = this.#dialogElement.querySelector('.ren-dialog-title, [role=\"heading\"]');\n      if (title) {\n        this.#dialogElement.setAttribute('aria-label', title.textContent?.trim() || 'Dialog');\n      } else {\n        this.#dialogElement.setAttribute('aria-label', 'Dialog');\n      }\n    }\n\n    // Block native Escape dismissal only for non-dismissable variants.\n    this.#dialogElement.addEventListener(\n      'cancel',\n      (e) => {\n        if (this.hasAttribute('no-escape') || this.hasAttribute('alert')) {\n          e.preventDefault();\n        }\n      },\n      { signal: this.#abortController.signal }\n    );\n\n    // Native Escape and <form method=\"dialog\"> both land here. All close\n    // paths reconcile through this single listener so lifecycle state and the\n    // public event cannot diverge or fire twice.\n    this.#dialogElement.addEventListener(\n      'close',\n      () => this.#reconcileNativeClose(),\n      { signal: this.#abortController.signal }\n    );\n\n    // Handle click-outside dismissal\n    // Clicking on the dialog itself when event.target === dialog means backdrop was clicked\n    this.#dialogElement.addEventListener(\n      'click',\n      (e) => {\n        if (e.target === this.#dialogElement && !this.hasAttribute('alert')) {\n          this.close();\n        }\n      },\n      { signal: this.#abortController.signal }\n    );\n\n    // Wire up close buttons\n    this.#wireupCloseButtons();\n\n    // Progressive enhancement: set closedby attribute for modern browsers\n    if ('closedBy' in HTMLDialogElement.prototype) {\n      if (this.hasAttribute('alert')) {\n        this.#dialogElement.closedBy = 'closerequest';\n      } else if (this.hasAttribute('no-escape')) {\n        this.#dialogElement.closedBy = 'none';\n      } else {\n        this.#dialogElement.closedBy = 'any';\n      }\n    }\n  }\n\n  /**\n   * Wire up trigger buttons that open this dialog\n   * Supports both [data-dialog-trigger] and native commandfor/command attributes\n   * @private\n   */\n  #wireupTriggers() {\n    const id = this.id;\n    if (!id) return;\n\n    const triggers = document.querySelectorAll(`[data-dialog-trigger=\"${id}\"]`);\n    triggers.forEach((trigger) => {\n      trigger.addEventListener(\n        'click',\n        (e) => {\n          e.preventDefault();\n          this.show();\n        },\n        { signal: this.#abortController.signal }\n      );\n    });\n\n    // Progressive enhancement: support command/commandfor in browsers that don't yet support it natively\n    if (!('commandForElement' in HTMLButtonElement.prototype)) {\n      const commandTriggers = document.querySelectorAll(`[commandfor=\"${id}\"]`);\n      commandTriggers.forEach((trigger) => {\n        const command = trigger.getAttribute('command');\n        if (command === 'show-modal' || command === 'show') {\n          trigger.addEventListener(\n            'click',\n            (e) => {\n              e.preventDefault();\n              this.show();\n            },\n            { signal: this.#abortController.signal }\n          );\n        } else if (command === 'close') {\n          trigger.addEventListener(\n            'click',\n            (e) => {\n              e.preventDefault();\n              this.close();\n            },\n            { signal: this.#abortController.signal }\n          );\n        }\n      });\n    }\n  }\n\n  /**\n   * Wire up close buttons within the dialog\n   * @private\n   */\n  #wireupCloseButtons() {\n    const closeButtons = this.#dialogElement?.querySelectorAll('[data-dialog-close]');\n    closeButtons?.forEach((button) => {\n      button.addEventListener(\n        'click',\n        (e) => {\n          e.preventDefault();\n          // [data-dialog-close=\"value\"] propagates `value` as ren-close.detail.returnValue.\n          // Bare [data-dialog-close] (no value) closes with \"\".\n          const returnValue = button.getAttribute('data-dialog-close') ?? '';\n          this.close(returnValue);\n        },\n        { signal: this.#abortController.signal }\n      );\n    });\n  }\n\n  /* ═══ PRIVATE: FOCUS MANAGEMENT ═══ */\n\n  /**\n   * Setup focus trap for keyboard navigation\n   * @private\n   */\n  #setupFocusTrap() {\n    if (this.#focusTrap) return;\n    this.#focusTrap = createFocusTrap(this.#dialogElement, {\n      initialFocus: this.#dialogElement.querySelector('[autofocus]'),\n      returnFocus: this.#returnFocus,\n    });\n    this.#focusTrap.activate();\n  }\n\n  /**\n   * Teardown focus trap\n   * @private\n   */\n  #teardownFocusTrap() {\n    if (this.#focusTrap) {\n      this.#focusTrap.deactivate();\n      this.#focusTrap = null;\n    }\n  }\n\n  #reconcileNativeClose() {\n    if (this.#closeReconciled) return;\n    this.#closeReconciled = true;\n    const returnValue = this.#dialogElement?.returnValue || '';\n\n    this.#setInert(false);\n    this.removeAttribute('open');\n    this.setAttribute('data-state', 'closed');\n    this.#teardownFocusTrap();\n    if (this.#returnFocus && document.contains(this.#returnFocus)) {\n      this.#returnFocus.focus({ preventScroll: true });\n    }\n    this.#returnFocus = null;\n    this.dispatchEvent(\n      new CustomEvent('ren-close', {\n        bubbles: true,\n        composed: true,\n        detail: { returnValue },\n      })\n    );\n  }\n\n  /* ═══ PRIVATE: UTILITIES ═══ */\n\n  /**\n   * Update size variant class\n   * @private\n   */\n  #updateSizeClass() {\n    if (!this.#dialogElement) return;\n    this.#dialogElement.classList.remove(\n      'ren-dialog-sm',\n      'ren-dialog-md',\n      'ren-dialog-lg',\n      'ren-dialog-xl',\n      'ren-dialog-full'\n    );\n    const size = this.getAttribute('size') || 'md';\n    this.#dialogElement.classList.add(`ren-dialog-${size}`);\n  }\n\n  /**\n   * Apply mobile bottom sheet styling on small screens\n   * @private\n   */\n  #updateMobileSheet() {\n    if (!this.#dialogElement) return;\n    if (window.innerWidth <= 640) {\n      this.#dialogElement.setAttribute('data-mobile-sheet', '');\n    } else {\n      this.#dialogElement.removeAttribute('data-mobile-sheet');\n    }\n  }\n\n  /**\n   * Toggle inert attribute on sibling elements\n   * When active, makes background elements non-interactive\n   * @private\n   * @param {boolean} active\n   */\n  #setInert(active) {\n    const children = document.body.children;\n    for (let i = 0; i < children.length; i++) {\n      const child = children[i];\n\n      // Skip the dialog itself and meta elements\n      if (\n        child === this ||\n        child.tagName === 'SCRIPT' ||\n        child.tagName === 'STYLE' ||\n        child.tagName === 'LINK'\n      ) {\n        continue;\n      }\n\n      if (active) {\n        // Save original inert state and set inert\n        if (!this.#inertElements.has(child)) {\n          this.#inertElements.set(child, child.inert);\n        }\n        child.inert = true;\n      } else {\n        // Restore original inert state\n        const originalInert = this.#inertElements.get(child);\n        if (originalInert !== undefined) {\n          child.inert = originalInert;\n          this.#inertElements.delete(child);\n        }\n      }\n    }\n  }\n}\n\n/**\n * RenAlertDialog - Alert Dialog Variant\n *\n * An alert dialog that prevents click-outside dismissal by default.\n * Escape key and other behavior can be controlled via attributes.\n *\n * @example\n * <ren-alert-dialog>\n *   <dialog>...</dialog>\n * </ren-alert-dialog>\n */\nexport class RenAlertDialog extends RenDialog {\n  constructor() {\n    super();\n    this.setAttribute('alert', '');\n  }\n}\n\n/* ═══ REGISTER CUSTOM ELEMENTS ═══ */\nif (!customElements.get('ren-dialog')) {\n  customElements.define('ren-dialog', RenDialog);\n}\nif (!customElements.get('ren-alert-dialog')) {\n  customElements.define('ren-alert-dialog', RenAlertDialog);\n}\n",
      "path": "components/composites/ren-dialog",
      "id": "component:composite:ren-dialog",
      "type": "component",
      "name": "ren-dialog"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-dropzone",
          ".ren-dropzone-compact",
          ".ren-dropzone-content",
          ".ren-dropzone-description",
          ".ren-dropzone-file",
          ".ren-dropzone-file-name",
          ".ren-dropzone-file-remove",
          ".ren-dropzone-file-size",
          ".ren-dropzone-files",
          ".ren-dropzone-icon",
          ".ren-dropzone-input",
          ".ren-dropzone-title"
        ],
        "tokens": [
          "--body-size",
          "--caption-size",
          "--color-accent",
          "--color-accent-subtle",
          "--color-border",
          "--color-border-strong",
          "--color-danger",
          "--color-fill",
          "--color-focus-ring",
          "--color-surface-raised",
          "--color-surface-sunken",
          "--color-text",
          "--color-text-muted",
          "--radius-full",
          "--radius-lg",
          "--radius-md",
          "--radius-sm",
          "--ren-dropzone-bg",
          "--ren-dropzone-border-color",
          "--ren-dropzone-border-style",
          "--ren-dropzone-border-width",
          "--ren-dropzone-hover-bg",
          "--ren-dropzone-hover-border",
          "--ren-dropzone-padding",
          "--ren-dropzone-radius",
          "--ring-offset-width",
          "--ring-width",
          "--space-1",
          "--space-2",
          "--space-3",
          "--space-4",
          "--space-6",
          "--space-8",
          "--stroke-1",
          "--text-base",
          "--text-sm",
          "--text-xl",
          "--transition-tactile",
          "--weight-medium"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-dropzone Component Contract\n\nFile drop and upload affordance with keyboard fallback.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-dropzone` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-dropzone` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Dropzone composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User must upload files via drag-and-drop OR click-to-browse with a single clear target.\"\n    - \"You want a dashed-border surface that highlights on dragover and lists uploaded files inline.\"\n    - \"Drop must also work for keyboard / pointer users by clicking through to a native <input type=\\\"file\\\">.\"\n    - \"You need a custom event (ren-files-added) carrying File[] instead of wiring the change event yourself.\"\n    - \"Showing accepted types / max size hints next to the icon is part of the flow.\"\n  avoidWhen:\n    - \"The flow is a single hidden file picker triggered by a button — use a plain <input type=\\\"file\\\"> + ren-button label.\"\n    - \"Files come from a remote URL / clipboard image only — dropzone is for OS file drops.\"\n    - \"You need a full upload manager with progress bars, chunked upload, or retries — wrap this dropzone with your own upload UI.\"\n    - \"The drop target is a generic editor surface where the file IS the document body (rich-text editor, canvas).\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-dropzone/ren-dropzone.css\"\n  js:\n    - \"rends/components/composites/ren-dropzone/ren-dropzone.js\"\n  notes:\n    - \"JS is exposed as initDropZone(el) — call it explicitly on each .ren-dropzone, there is no auto-upgrade custom element.\"\n    - \"Without the JS, the CSS still renders a styled file input target, but you lose the dragover state, the dispatched event, and the file array shape.\"\n\nrequiredMarkup:\n  - \"Root is <div class=\\\"ren-dropzone\\\"> (or with .ren-dropzone-compact) and contains a single <input type=\\\"file\\\" class=\\\"ren-dropzone-input\\\"> covering the surface.\"\n  - \"The file input MUST be a real <input type=\\\"file\\\"> with absolute positioning; do not replace it with a custom button — keyboard users rely on the native click.\"\n  - \"Content sits in .ren-dropzone-content with .ren-dropzone-icon, .ren-dropzone-title, .ren-dropzone-description as siblings inside it.\"\n  - \"Uploaded files render as <div class=\\\"ren-dropzone-files\\\"> > .ren-dropzone-file rows with .ren-dropzone-file-name / -size / -remove inside each.\"\n  - \"Disabled state must set aria-disabled=\\\"true\\\" on the wrapper AND disabled on the inner <input> — :has(input:disabled) styles depend on both.\"\n\nforbiddenPatterns:\n  - \"Removing <input type=\\\"file\\\"> and relying on dragover events alone — there is no keyboard fallback without the native input.\"\n  - \"Setting display: none on .ren-dropzone-input — the CSS expects it to overlay the dropzone for click-to-browse.\"\n  - \"Toggling drag highlight via inline styles — let the JS set/unset the [data-dragover] attribute so the state and the icon color react together.\"\n  - \"Custom error tints via hardcoded red — use the [data-error] attribute which switches the border to --color-danger.\"\n  - \"Putting the .ren-dropzone-content above the input in stacking order with pointer-events: auto — content uses pointer-events: none so clicks reach the file input.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-dropzone-bg, --ren-dropzone-border-color, --ren-dropzone-border-style, --ren-dropzone-border-width, --ren-dropzone-hover-bg, --ren-dropzone-hover-border, --ren-dropzone-padding, --ren-dropzone-radius.\"\n    - \"Semantic tokens used by the file list and icon: --color-surface-sunken, --color-surface-raised, --color-border, --color-border-strong, --color-fill, --color-text, --color-text-muted, --color-accent, --color-accent-subtle, --color-danger, --color-focus-ring.\"\n    - \"Layout / motion / type tokens: --space-*, --radius-md, --radius-lg, --radius-sm, --radius-full, --stroke-1, --text-xl, --text-base, --text-sm, --body-size, --caption-size, --weight-medium, --ring-width, --ring-offset-width, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer styles.\"\n    - \"Hardcoded hex / rgb() values for border, dragover background, or remove-button color — theme via the --color-* tokens.\"\n    - \"Custom focus rings on the wrapper that bypass --color-focus-ring / --ring-width / --ring-offset-width.\"\n\naccessibility:\n  required:\n    - \"Dropzone exposes :focus-within outline (--color-focus-ring) sourced from the inner native <input type=\\\"file\\\"> — do not remove the outline.\"\n    - \"Provide a visible label inside .ren-dropzone-title plus a description in .ren-dropzone-description so screen readers see the purpose, not just the icon.\"\n    - \"Disabled state requires aria-disabled=\\\"true\\\" AND the native disabled attribute; pointer-events: none alone leaves the input focusable.\"\n    - \"Remove buttons inside .ren-dropzone-file must include accessible names (aria-label=\\\"Remove <file name>\\\") because the visible glyph alone is decorative.\"\n    - \"Drag visual state ([data-dragover]) must not be the only signal — pair it with text feedback when validation fails ([data-error]).\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-dropzone/ren-dropzone.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-dropzone/ren-dropzone.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div class=\"ren-dropzone\"><input class=\"ren-dropzone-input\" type=\"file\" multiple><div class=\"ren-dropzone-content\"><span class=\"ren-dropzone-icon\" aria-hidden=\"true\">↑</span><strong class=\"ren-dropzone-title\">Upload files</strong><span class=\"ren-dropzone-description\">Drag files here or browse</span></div></div>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-dropzone`\n- `.ren-dropzone-compact`\n- `.ren-dropzone-content`\n- `.ren-dropzone-description`\n- `.ren-dropzone-file`\n- `.ren-dropzone-file-name`\n- `.ren-dropzone-file-remove`\n- `.ren-dropzone-file-size`\n- `.ren-dropzone-files`\n- `.ren-dropzone-icon`\n- `.ren-dropzone-input`\n- `.ren-dropzone-title`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[data-dragover]`\n- `[data-error]`\n- `:disabled`\n- `:hover`\n\n## Public Token API\n\n- `--ren-dropzone-bg`\n- `--ren-dropzone-border-color`\n- `--ren-dropzone-border-style`\n- `--ren-dropzone-border-width`\n- `--ren-dropzone-hover-bg`\n- `--ren-dropzone-hover-border`\n- `--ren-dropzone-padding`\n- `--ren-dropzone-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-dropzone/ren-dropzone.css`\n- `components/composites/ren-dropzone/ren-dropzone.js`\n- `docs/components/ren-dropzone.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — DropZone Component\n   ============================================\n   Drag & drop file upload area.\n   Minimal JS for drag events — CSS handles visuals.\n\n   Uses native drag events, no library needed.\n\n   Usage:\n     <div class=\"ren-dropzone\" data-accept=\".jpg,.png,.pdf\" data-max-size=\"5MB\">\n       <div class=\"ren-dropzone-content\">\n         <span class=\"ren-dropzone-icon\">↑</span>\n         <p class=\"ren-dropzone-title\">Drop files here</p>\n         <p class=\"ren-dropzone-description\">or click to browse</p>\n       </div>\n       <input type=\"file\" class=\"ren-dropzone-input\" multiple accept=\".jpg,.png,.pdf\">\n     </div>\n   ============================================ */\n\n.ren-dropzone {\n  position: relative;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-height: 10rem;\n  padding: var(--space-6);\n  border: 2px dashed var(--color-border-strong);\n  border-radius: var(--radius-lg);\n  background-color: var(--color-surface-sunken);\n  cursor: pointer;\n  transition: var(--transition-tactile);\n}\n\n.ren-dropzone:hover {\n  border-color: var(--color-accent);\n  background-color: var(--color-accent-subtle);\n}\n\n/* Drag over state (set via JS: element.dataset.dragover = true) */\n.ren-dropzone[data-dragover] {\n  border-color: var(--color-accent);\n  background-color: var(--color-accent-subtle);\n  border-style: solid;\n}\n\n.ren-dropzone:focus-within {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: var(--ring-offset-width);\n}\n\n/* Hidden file input covers entire dropzone */\n.ren-dropzone-input {\n  position: absolute;\n  inset: 0;\n  width: 100%;\n  height: 100%;\n  opacity: 0;\n  cursor: pointer;\n}\n\n/* Content */\n.ren-dropzone-content {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: var(--space-2);\n  text-align: center;\n  pointer-events: none;\n}\n\n.ren-dropzone-icon {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 3rem;\n  height: 3rem;\n  font-size: var(--text-xl);\n  color: var(--color-text-muted);\n  background-color: var(--color-fill);\n  border-radius: var(--radius-full);\n  margin-bottom: var(--space-1);\n}\n\n.ren-dropzone[data-dragover] .ren-dropzone-icon {\n  color: var(--color-accent);\n  background-color: color-mix(in oklch, var(--color-accent), transparent 85%);\n}\n\n.ren-dropzone-title {\n  font-size: var(--body-size);\n  font-weight: var(--weight-medium);\n  color: var(--color-text);\n}\n\n.ren-dropzone-description {\n  font-size: var(--caption-size, var(--text-sm));\n  color: var(--color-text-muted);\n}\n\n/* ─── File list (after upload) ─── */\n.ren-dropzone-files {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-2);\n  width: 100%;\n  margin-top: var(--space-3);\n}\n\n.ren-dropzone-file {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  background-color: var(--color-surface-raised);\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-md);\n  font-size: var(--caption-size, var(--text-sm));\n}\n\n.ren-dropzone-file-name {\n  flex: 1;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.ren-dropzone-file-size {\n  color: var(--color-text-muted);\n  white-space: nowrap;\n}\n\n.ren-dropzone-file-remove {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 1.25rem;\n  height: 1.25rem;\n  padding: 0;\n  border: none;\n  background: none;\n  color: var(--color-text-muted);\n  cursor: pointer;\n  border-radius: var(--radius-sm);\n  font-size: var(--text-base);\n}\n\n.ren-dropzone-file-remove:hover {\n  color: var(--color-danger);\n}\n\n/* ─── Compact variant ─── */\n.ren-dropzone-compact {\n  min-height: auto;\n  padding: var(--space-3) var(--space-4);\n  flex-direction: row;\n  gap: var(--space-3);\n}\n\n.ren-dropzone-compact .ren-dropzone-content {\n  flex-direction: row;\n}\n\n.ren-dropzone-compact .ren-dropzone-icon {\n  width: 2rem;\n  height: 2rem;\n  font-size: var(--text-base);\n  margin-bottom: 0;\n}\n\n/* ─── Disabled ─── */\n.ren-dropzone[aria-disabled=\"true\"],\n.ren-dropzone:has(input:disabled) {\n  opacity: 0.5;\n  cursor: not-allowed;\n  pointer-events: none;\n}\n\n/* ─── Error state ─── */\n.ren-dropzone[data-error] {\n  border-color: var(--color-danger);\n}\n/* Appearance API bridge */\n.ren-dropzone { padding: var(--ren-dropzone-padding, var(--space-8)); border-radius: var(--ren-dropzone-radius, var(--radius-lg)); border-color: var(--ren-dropzone-border-color, var(--color-border-strong)); border-style: var(--ren-dropzone-border-style, dashed); border-width: var(--ren-dropzone-border-width, 2px); background: var(--ren-dropzone-bg, transparent); }\n.ren-dropzone:hover, .ren-dropzone[data-drag-over] { background: var(--ren-dropzone-hover-bg, var(--color-accent-subtle)); border-color: var(--ren-dropzone-hover-border, var(--color-accent)); }\n\n\n/* ============================================\n   RenDS — DropZone Drag & Drop Handler\n   ============================================\n   Minimal JS for drag events. Handles:\n   - Drag enter/leave visual state\n   - File drop processing\n   - Custom event dispatch\n\n   Usage:\n     import { initDropZone } from './ren-dropzone.js';\n     const dz = initDropZone(document.querySelector('.ren-dropzone'));\n     dz.addEventListener('ren-files-added', (e) => {\n       console.log(e.detail.files);\n     });\n   ============================================ */\n\n/**\n * @param {HTMLElement} dropzone\n * @returns {HTMLElement}\n */\nconst dropzoneControllers = new WeakMap();\n\nexport function initDropZone(dropzone) {\n  if (!dropzone) return null;\n\n  dropzoneControllers.get(dropzone)?.abort();\n  delete dropzone.dataset.dragover;\n  const controller = new AbortController();\n  dropzoneControllers.set(dropzone, controller);\n  const { signal } = controller;\n\n  let dragCounter = 0;\n  const input = dropzone.querySelector('.ren-dropzone-input');\n\n  const handleFiles = (files) => {\n    dropzone.dispatchEvent(\n      new CustomEvent('ren-files-added', {\n        detail: { files: [...files] },\n        bubbles: true,\n      })\n    );\n  };\n\n  dropzone.addEventListener('dragenter', (e) => {\n    e.preventDefault();\n    dragCounter++;\n    dropzone.dataset.dragover = '';\n  }, { signal });\n\n  dropzone.addEventListener('dragleave', () => {\n    dragCounter--;\n    if (dragCounter <= 0) {\n      dragCounter = 0;\n      delete dropzone.dataset.dragover;\n    }\n  }, { signal });\n\n  dropzone.addEventListener('dragover', (e) => {\n    e.preventDefault();\n  }, { signal });\n\n  dropzone.addEventListener('drop', (e) => {\n    e.preventDefault();\n    dragCounter = 0;\n    delete dropzone.dataset.dragover;\n    if (e.dataTransfer?.files?.length) {\n      handleFiles(e.dataTransfer.files);\n    }\n  }, { signal });\n\n  if (input) {\n    input.addEventListener('change', () => {\n      if (input.files?.length) {\n        handleFiles(input.files);\n      }\n    }, { signal });\n  }\n\n  return dropzone;\n}\n",
      "path": "components/composites/ren-dropzone",
      "id": "component:composite:ren-dropzone",
      "type": "component",
      "name": "ren-dropzone"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-hover-card",
          ".ren-hover-card-body",
          ".ren-hover-card-footer",
          ".ren-hover-card-header",
          ".ren-hover-card-lg",
          ".ren-hover-card-sm",
          ".ren-hover-card-trigger"
        ],
        "tokens": [
          "--color-accent",
          "--color-accent-strong",
          "--color-border",
          "--color-surface",
          "--color-text",
          "--color-text-muted",
          "--duration-enter",
          "--ease-enter",
          "--font-size-body",
          "--font-size-body-sm",
          "--font-size-xs",
          "--radius-lg",
          "--radius-md",
          "--shadow-xl",
          "--space-0-25",
          "--space-0-5",
          "--space-1",
          "--space-2",
          "--space-3",
          "--space-4",
          "--space-5",
          "--transition-tactile"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-hover-card Component Contract\n\nNon-blocking preview surface shown from hover/focus intent.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-hover-card` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-hover-card` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Hover Card composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"Hover or keyboard-focus over a trigger should reveal a richer-than-tooltip preview (header + body + footer).\"\n    - \"The preview is informational and non-essential — user can ignore it without losing access to the action.\"\n    - \"Show/hide should respect intent: 200ms show delay, 300ms hide grace area so the cursor can move into the card.\"\n    - \"Need anchor positioning via position-area + viewport flip via CSS position-try-fallbacks (no JS positioning).\"\n    - \"Trigger keyboard accessibility via focusin/focusout, not just :hover.\"\n  avoidWhen:\n    - \"The trigger is a primary action and the disclosure is required for the task — use ren-popover (click-driven) or ren-dialog.\"\n    - \"You only need a short text annotation — use ren-tooltip (smaller surface, role=\\\"tooltip\\\").\"\n    - \"The disclosure must be operable on touch devices without a hover state — touch has no hover; use ren-popover.\"\n    - \"Content includes form inputs, focusable controls beyond simple links — ren-popover gives proper dialog semantics.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-hover-card/ren-hover-card.css\"\n  js:\n    - \"rends/components/composites/ren-hover-card/ren-hover-card.js\"\n  notes:\n    - \"JS is required: it adds anchor-name/position-anchor inline styles, wires mouseenter/leave + focusin/out, and toggles the native Popover API.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Place the trigger element immediately before <ren-hover-card>, OR set data-hover-trigger=\\\"<selector>\\\" on the host to point at a remote trigger.\"\n  - \"Inside <ren-hover-card> include a real element with class=\\\"ren-hover-card\\\" (the JS will add popover=\\\"manual\\\" and the .ren-hover-card class if missing).\"\n  - \"Use .ren-hover-card-header / -body / -footer as the three layout slots; headers should contain a real <h3> or <h4> for the accessible heading.\"\n  - \"The trigger receives aria-haspopup=\\\"tooltip\\\" and aria-expanded automatically — do not override these in markup.\"\n  - \"Show/hide delays come from show-delay / hide-delay attributes (milliseconds) on the host; do not throttle via inline scripts.\"\n\nforbiddenPatterns:\n  - \"Using ren-hover-card for click-driven disclosure — there is no click handler, only mouseenter/focusin.\"\n  - \"Putting interactive form inputs inside .ren-hover-card-body — the card closes on mouseleave/focusout, so focusing an input would dismiss it.\"\n  - \"Manually setting popover=\\\"auto\\\" on the inner card — the JS uses popover=\\\"manual\\\" so the open/close lifecycle stays in the component's hands.\"\n  - \"Overriding the ::before arrow with custom shapes — keep the inherited background/border so it auto-themes.\"\n  - \"Hardcoding width via inline styles or non-token values — use .ren-hover-card-sm / -lg or override --color-surface / --shadow-xl through semantic tokens.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component-level positioning anchor token: --ren-hover-card-anchor (and the internal --ren-hover-card-anchor anchor-name).\"\n    - \"Semantic tokens that style the card chrome and content: --color-surface, --color-border, --color-text, --color-text-muted, --color-accent, --color-accent-strong, --shadow-xl, --radius-lg, --radius-md.\"\n    - \"Layout / type / motion tokens: --space-* (0-25, 0-5, 1, 2, 3, 4, 5), --font-size-body, --font-size-body-sm, --font-size-xs, --duration-enter, --ease-enter, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded box-shadow / hex colors on the card surface — pipe through --shadow-xl and --color-surface / --color-border.\"\n    - \"Custom transition durations; reuse --duration-enter / --ease-enter to honour reduced-motion.\"\n\naccessibility:\n  required:\n    - \"Trigger must be a focusable element so focusin/focusout reveal the card via keyboard, not only hover.\"\n    - \"Trigger carries aria-haspopup=\\\"tooltip\\\" and aria-expanded that flips true/false in sync with show()/hide().\"\n    - \"The card uses the native Popover API (popover=\\\"manual\\\") so it inerts other content correctly when shown.\"\n    - \"Reduced motion disables the entrance animation and hides the decorative arrow; do not override the @media (prefers-reduced-motion: reduce) rules.\"\n    - \"On viewports under 640px the card stretches to viewport width — do not lock min-width that breaks this responsive behavior.\"\n    - \"Hover cards must never be the only path to an action; provide a click-driven affordance (ren-popover / ren-link) for touch and AT users.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-hover-card/ren-hover-card.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-hover-card/ren-hover-card.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<button id=\"profile-trigger\" type=\"button\">Jane Doe</button><ren-hover-card data-hover-trigger=\"#profile-trigger\"><div class=\"ren-hover-card\"><div class=\"ren-hover-card-header\"><h3>Jane Doe</h3></div><div class=\"ren-hover-card-body\">Product designer</div></div></ren-hover-card>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-hover-card`\n- `.ren-hover-card-body`\n- `.ren-hover-card-disabled`\n- `.ren-hover-card-footer`\n- `.ren-hover-card-header`\n- `.ren-hover-card-lg`\n- `.ren-hover-card-loading`\n- `.ren-hover-card-sm`\n- `.ren-hover-card-trigger`\n\n## States And Attributes\n\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-hover-card-anchor`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-hover-card/ren-hover-card.css`\n- `components/composites/ren-hover-card/ren-hover-card.js`\n- `docs/components/ren-hover-card.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ═══ REN HOVER CARD COMPONENT ═══ */\n\n/* ═══ TRIGGER ELEMENT ═══ */\n.ren-hover-card-trigger {\n  anchor-name: --ren-hover-card-anchor;\n}\n\n/* ═══ BASE CARD ═══ */\n.ren-hover-card {\n  position-anchor: --ren-hover-card-anchor;\n  position: absolute;\n  position-area: bottom span-all;\n  inset: auto;\n  margin-block-start: var(--space-2);\n  background: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-xl);\n  padding: var(--space-4);\n  max-width: 20rem;\n  min-width: 15rem;\n  z-index: 1000;\n\n  /* ═══ POPOVER API ═══\n     Semantic tokens keep hover-card in sync with tooltip + popover. */\n  &:popover-open {\n    animation: ren-hover-card-in var(--duration-enter) var(--ease-enter) both;\n  }\n\n  /* ═══ ARROW STYLING ═══ */\n  &::before {\n    content: '';\n    position: absolute;\n    width: 8px;\n    height: 8px;\n    background: inherit;\n    border: inherit;\n    border-inline-end: none;\n    border-bottom: none;\n    transform: rotate(45deg);\n    z-index: -1;\n    top: -4px;\n    left: 50%;\n    margin-inline-start: -4px;\n  }\n\n  /* ═══ FALLBACK POSITIONING ═══ */\n  @supports not ((anchor-name: --ren-anchor) and\n    (position-anchor: --ren-anchor) and\n    (position-area: bottom span-all)) {\n    position: fixed;\n    top: auto;\n    left: auto;\n  }\n\n  /* ═══ VIEWPORT EDGE HANDLING ═══ */\n  @supports (position-try-fallbacks: flip-block) {\n    position-try-fallbacks: flip-block;\n  }\n}\n\n/* ═══ ANIMATION ═══ */\n@starting-style {\n  .ren-hover-card:popover-open {\n    opacity: 0;\n    translate: 0 -4px;\n  }\n}\n\n@keyframes ren-hover-card-in {\n  from {\n    opacity: 0;\n    translate: 0 -4px;\n  }\n\n  to {\n    opacity: 1;\n    translate: 0 0;\n  }\n}\n\n/* ═══ SECTIONS ═══ */\n.ren-hover-card-header {\n  margin-bottom: var(--space-3);\n\n  & h3,\n  & h4 {\n    margin: 0 0 var(--space-0-5) 0;\n    font-size: var(--font-size-body);\n    font-weight: 600;\n    color: var(--color-text);\n  }\n\n  & p {\n    margin: 0;\n    font-size: var(--font-size-xs);\n    color: var(--color-text-muted);\n  }\n}\n\n.ren-hover-card-body {\n  margin-bottom: var(--space-3);\n  font-size: var(--font-size-body-sm);\n  color: var(--color-text);\n  line-height: 1.5;\n\n  & :last-child {\n    margin-bottom: 0;\n  }\n}\n\n.ren-hover-card-footer {\n  display: flex;\n  gap: var(--space-2);\n  align-items: center;\n\n  & button,\n  & a {\n    font-size: var(--font-size-body-sm);\n    padding: var(--space-1) var(--space-2);\n    border-radius: var(--radius-md);\n    border: none;\n    cursor: pointer;\n    transition: var(--transition-tactile);\n\n    &:focus-visible {\n      outline: 2px solid var(--color-accent);\n      outline-offset: 2px;\n    }\n  }\n}\n\n/* ═══ REDUCED MOTION ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-hover-card {\n    &:popover-open {\n      animation: none;\n      opacity: 1;\n      translate: 0 0;\n    }\n\n    &::before {\n      display: none;\n    }\n  }\n}\n\n/* ═══ DARK MODE SUPPORT (OPTIONAL) ═══ */\n@media (prefers-color-scheme: dark) {\n  .ren-hover-card {\n    /* ═══ USES CSS VARIABLES - AUTO ADAPTS ═══ */\n  }\n}\n\n/* ═══ MOBILE RESPONSIVENESS ═══\n   Hover cards keep a media query here because they're\n   positioned absolutely relative to the viewport, and\n   the constraint is viewport space, not container width. */\n@media (max-width: 640px) {\n  .ren-hover-card {\n    max-width: calc(100vw - var(--space-4));\n    min-width: auto;\n    width: calc(100vw - var(--space-4));\n  }\n}\n\n/* ═══ LOADING STATE ═══ */\n.ren-hover-card.ren-hover-card-loading {\n  pointer-events: none;\n\n  & .ren-hover-card-body {\n    opacity: 0.6;\n  }\n}\n\n/* ═══ DISABLED STATE ═══ */\n.ren-hover-card.ren-hover-card-disabled {\n  display: none;\n}\n\n/* ═══ CUSTOM VARIANTS ═══ */\n.ren-hover-card {\n  &.ren-hover-card-sm {\n    padding: var(--space-2);\n    max-width: 15rem;\n    min-width: 12rem;\n\n    & .ren-hover-card-header {\n      margin-bottom: var(--space-2);\n\n      & h3,\n      & h4 {\n        font-size: var(--font-size-body-sm);\n        margin-bottom: var(--space-0-25);\n      }\n    }\n\n    & .ren-hover-card-body {\n      margin-bottom: var(--space-2);\n      font-size: var(--font-size-xs);\n    }\n  }\n\n  &.ren-hover-card-lg {\n    padding: var(--space-5);\n    max-width: 25rem;\n    min-width: 18rem;\n\n    & .ren-hover-card-header {\n      margin-bottom: var(--space-4);\n\n      & h3,\n      & h4 {\n        font-size: var(--font-size-body);\n      }\n    }\n\n    & .ren-hover-card-body {\n      margin-bottom: var(--space-4);\n      font-size: var(--font-size-body);\n    }\n  }\n}\n\n/* ═══ INNER LINKS STYLING ═══ */\n.ren-hover-card a {\n  color: var(--color-accent);\n  text-decoration: underline;\n  transition: var(--transition-tactile);\n\n  &:hover {\n    color: var(--color-accent-strong);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n}\n\n\n/* ═══ REN HOVER CARD WEB COMPONENT ═══ */\n\nexport class RenHoverCard extends HTMLElement {\n  constructor() {\n    super();\n    this.card = null;\n    this.trigger = null;\n    this.showDelay = 200;\n    this.hideDelay = 300;\n    this.showTimeout = null;\n    this.hideTimeout = null;\n    this.isOpen = false;\n\n    /* ═══ BIND METHODS ═══ */\n    this.handleTriggerMouseEnter = this.handleTriggerMouseEnter.bind(this);\n    this.handleTriggerMouseLeave = this.handleTriggerMouseLeave.bind(this);\n    this.handleTriggerFocusIn = this.handleTriggerFocusIn.bind(this);\n    this.handleTriggerFocusOut = this.handleTriggerFocusOut.bind(this);\n    this.handleCardMouseEnter = this.handleCardMouseEnter.bind(this);\n    this.handleCardMouseLeave = this.handleCardMouseLeave.bind(this);\n  }\n\n  connectedCallback() {\n    /* ═══ FIND OR CREATE CARD ═══ */\n    this.card = this.querySelector('[role=\"tooltip\"]') || this.querySelector('.ren-hover-card');\n\n    if (!this.card) {\n      console.warn('RenHoverCard: No card element found');\n      return;\n    }\n\n    /* ═══ ENSURE CARD HAS CLASS ═══ */\n    this.card.classList.add('ren-hover-card');\n\n    /* ═══ SET UP POPOVER ATTRIBUTE ═══ */\n    if (!this.card.hasAttribute('popover')) {\n      this.card.setAttribute('popover', 'manual');\n    }\n\n    /* ═══ FIND TRIGGER ═══ */\n    const triggerSelector = this.getAttribute('data-hover-trigger');\n    if (triggerSelector) {\n      this.trigger = document.querySelector(triggerSelector);\n    } else {\n      this.trigger = this.previousElementSibling;\n    }\n\n    if (!this.trigger) {\n      console.warn('RenHoverCard: No trigger element found');\n      return;\n    }\n\n    /* ═══ SET UP TRIGGER ARIA ATTRIBUTES ═══ */\n    this.trigger.classList.add('ren-hover-card-trigger');\n    this.trigger.setAttribute('aria-haspopup', 'tooltip');\n    this.trigger.setAttribute('aria-expanded', 'false');\n\n    /* ═══ READ DELAY ATTRIBUTES ═══ */\n    const showDelayAttr = this.getAttribute('show-delay');\n    const hideDelayAttr = this.getAttribute('hide-delay');\n    if (showDelayAttr) this.showDelay = parseInt(showDelayAttr, 10);\n    if (hideDelayAttr) this.hideDelay = parseInt(hideDelayAttr, 10);\n\n    /* ═══ SET ANCHOR NAME ON TRIGGER ═══ */\n    if (!this.trigger.style.anchorName) {\n      this.trigger.style.anchorName = '--ren-hover-card-anchor';\n    }\n\n    /* ═══ SET POSITION ANCHOR ON CARD ═══ */\n    if (!this.card.style.positionAnchor) {\n      this.card.style.positionAnchor = '--ren-hover-card-anchor';\n    }\n\n    /* ═══ ATTACH EVENT LISTENERS ═══ */\n    this.trigger.addEventListener('mouseenter', this.handleTriggerMouseEnter);\n    this.trigger.addEventListener('mouseleave', this.handleTriggerMouseLeave);\n    this.trigger.addEventListener('focusin', this.handleTriggerFocusIn);\n    this.trigger.addEventListener('focusout', this.handleTriggerFocusOut);\n\n    this.card.addEventListener('mouseenter', this.handleCardMouseEnter);\n    this.card.addEventListener('mouseleave', this.handleCardMouseLeave);\n  }\n\n  disconnectedCallback() {\n    if (this.trigger) {\n      this.trigger.removeEventListener('mouseenter', this.handleTriggerMouseEnter);\n      this.trigger.removeEventListener('mouseleave', this.handleTriggerMouseLeave);\n      this.trigger.removeEventListener('focusin', this.handleTriggerFocusIn);\n      this.trigger.removeEventListener('focusout', this.handleTriggerFocusOut);\n    }\n\n    if (this.card) {\n      this.card.removeEventListener('mouseenter', this.handleCardMouseEnter);\n      this.card.removeEventListener('mouseleave', this.handleCardMouseLeave);\n    }\n\n    this.clearTimeouts();\n  }\n\n  /* ═══ TRIGGER MOUSE ENTER ═══ */\n  handleTriggerMouseEnter() {\n    this.clearTimeouts();\n    this.showTimeout = setTimeout(() => this.show(), this.showDelay);\n  }\n\n  /* ═══ TRIGGER MOUSE LEAVE ═══ */\n  handleTriggerMouseLeave() {\n    this.clearShowTimeout();\n    /* ═══ GRACE PERIOD: KEEP OPEN IF MOUSE MOVES TO CARD ═══ */\n    this.hideTimeout = setTimeout(() => this.hide(), this.hideDelay);\n  }\n\n  /* ═══ TRIGGER FOCUS IN (KEYBOARD) ═══ */\n  handleTriggerFocusIn() {\n    this.clearTimeouts();\n    this.show();\n  }\n\n  /* ═══ TRIGGER FOCUS OUT (KEYBOARD) ═══ */\n  handleTriggerFocusOut() {\n    this.clearTimeouts();\n    this.hideTimeout = setTimeout(() => this.hide(), this.hideDelay);\n  }\n\n  /* ═══ CARD MOUSE ENTER (GRACE AREA) ═══ */\n  handleCardMouseEnter() {\n    this.clearHideTimeout();\n  }\n\n  /* ═══ CARD MOUSE LEAVE ═══ */\n  handleCardMouseLeave() {\n    this.hideTimeout = setTimeout(() => this.hide(), this.hideDelay);\n  }\n\n  /* ═══ SHOW CARD ═══ */\n  show() {\n    if (!this.card) return;\n\n    this.clearTimeouts();\n    this.card.showPopover();\n    this.trigger.setAttribute('aria-expanded', 'true');\n    this.isOpen = true;\n\n    this.dispatchEvent(\n      new CustomEvent('ren-hover-card-open', {\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ HIDE CARD ═══ */\n  hide() {\n    if (!this.card) return;\n\n    this.clearTimeouts();\n\n    try {\n      this.card.hidePopover();\n    } catch {\n      /* ═══ POPOVER API MAY NOT EXIST ═══ */\n      this.card.style.display = 'none';\n    }\n\n    this.trigger.setAttribute('aria-expanded', 'false');\n    this.isOpen = false;\n\n    this.dispatchEvent(\n      new CustomEvent('ren-hover-card-close', {\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ TOGGLE CARD ═══ */\n  toggle() {\n    if (this.isOpen) {\n      this.hide();\n    } else {\n      this.show();\n    }\n  }\n\n  /* ═══ CLEAR ALL TIMEOUTS ═══ */\n  clearTimeouts() {\n    this.clearShowTimeout();\n    this.clearHideTimeout();\n  }\n\n  /* ═══ CLEAR SHOW TIMEOUT ═══ */\n  clearShowTimeout() {\n    if (this.showTimeout) {\n      clearTimeout(this.showTimeout);\n      this.showTimeout = null;\n    }\n  }\n\n  /* ═══ CLEAR HIDE TIMEOUT ═══ */\n  clearHideTimeout() {\n    if (this.hideTimeout) {\n      clearTimeout(this.hideTimeout);\n      this.hideTimeout = null;\n    }\n  }\n\n  /* ═══ GETTERS AND SETTERS ═══ */\n  get open() {\n    return this.isOpen;\n  }\n\n  set open(val) {\n    if (val) {\n      this.show();\n    } else {\n      this.hide();\n    }\n  }\n\n  get disabled() {\n    return this.hasAttribute('disabled');\n  }\n\n  set disabled(val) {\n    if (val) {\n      this.setAttribute('disabled', '');\n      this.card?.classList.add('ren-hover-card-disabled');\n    } else {\n      this.removeAttribute('disabled');\n      this.card?.classList.remove('ren-hover-card-disabled');\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-hover-card')) {\n  customElements.define('ren-hover-card', RenHoverCard);\n}\n",
      "path": "components/composites/ren-hover-card",
      "id": "component:composite:ren-hover-card",
      "type": "component",
      "name": "ren-hover-card"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-menu",
          ".ren-menu-checkbox-item",
          ".ren-menu-group",
          ".ren-menu-item",
          ".ren-menu-item-danger",
          ".ren-menu-item-description",
          ".ren-menu-item-icon",
          ".ren-menu-item-shortcut",
          ".ren-menu-label",
          ".ren-menu-radio-item",
          ".ren-menu-separator",
          ".ren-open"
        ],
        "tokens": [
          "--color-accent",
          "--color-border",
          "--color-danger",
          "--color-danger-subtle",
          "--color-fill",
          "--color-fill-hover",
          "--color-surface",
          "--color-surface-overlay",
          "--color-text",
          "--color-text-muted",
          "--duration-enter",
          "--duration-exit",
          "--duration-fast",
          "--ease-enter",
          "--ease-exit",
          "--ease-out",
          "--radius-lg",
          "--radius-sm",
          "--ren-menu-bg",
          "--ren-menu-border",
          "--ren-menu-danger-color",
          "--ren-menu-duration",
          "--ren-menu-easing",
          "--ren-menu-item-height",
          "--ren-menu-item-hover-bg",
          "--ren-menu-item-padding",
          "--ren-menu-item-radius",
          "--ren-menu-padding",
          "--ren-menu-radius",
          "--ren-menu-shadow",
          "--ren-menu-width",
          "--shadow-lg",
          "--size-body",
          "--size-caption",
          "--size-icon-md",
          "--size-md",
          "--space-1",
          "--space-2",
          "--space-3",
          "--transition-tactile",
          "--z-dropdown"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-menu Component Contract\n\nMenu/listbox-style command surface with keyboard navigation.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-menu` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-menu` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Menu composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"A trigger button needs to open a list of imperative commands (Edit, Duplicate, Delete) with role=\\\"menu\\\".\"\n    - \"You need keyboard navigation (Arrow keys, Home/End, typeahead) plus click-outside / Escape dismissal.\"\n    - \"Items must support menuitem, menuitemcheckbox, menuitemradio, separators, group labels, danger styling, and right-aligned shortcut text.\"\n    - \"Right-click context menus reuse the same chrome via the colocated ren-context-menu module at pointer coordinates.\"\n    - \"Positioning needs viewport-aware placement (bottom-start / bottom-end / top-start / top-end, with right/left also accepted) with native Popover API support.\"\n  avoidWhen:\n    - \"User is selecting a single value to commit into a form field — use ren-select.\"\n    - \"User is filtering a combobox of suggestions — use ren-combobox.\"\n    - \"The disclosure is non-list (rich content, header/body/footer) — use ren-popover or ren-hover-card.\"\n    - \"Items are persistent navigation across the app — use ren-sidebar / ren-nav.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-menu/ren-menu.css\"\n  js:\n    - \"rends/components/composites/ren-menu/ren-menu.js\"\n  notes:\n    - \"JS depends on utils/keyboard-nav.js and utils/dismissable.js; its compatibility export delegates ren-context-menu registration to the colocated context-menu module.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Place a trigger element with data-menu-trigger (or referenced via trigger-id=\\\"<id>\\\" on the host) immediately before <ren-menu>.\"\n  - \"Each interactive item is a real <button class=\\\"ren-menu-item\\\"> with role=\\\"menuitem\\\" (the component sets role=\\\"menuitemcheckbox\\\" / \\\"menuitemradio\\\" when those classes are present).\"\n  - \"Visual separators use <div class=\\\"ren-menu-separator\\\"> and group titles use <div class=\\\"ren-menu-label\\\">; do not use <hr> or <h*> for these slots.\"\n  - \"Disabled items set aria-disabled=\\\"true\\\"; the component skips them in keyboard navigation and selection.\"\n  - \"Set data-value=\\\"…\\\" on items so the dispatched ren-menu-select event carries that value in detail.value (falls back to textContent).\"\n  - \"Use placement=\\\"bottom-start\\\" by default; the host mirrors the resolved side/alignment to data-side=\\\"top|right|bottom|left\\\" and data-align=\\\"start|end\\\".\"\n\nforbiddenPatterns:\n  - \"Using <a href> for menu items that perform JS actions — use <button> and listen to ren-menu-select.\"\n  - \"Replacing the trigger with a div that toggles via inline click — the trigger must receive aria-haspopup=\\\"menu\\\" + aria-controls + aria-expanded from the component.\"\n  - \"Manually positioning the menu with custom left/top — let positionMenu() handle viewport collision (or use placement=\\\"bottom-end\\\" etc).\"\n  - \"Toggling visibility with display: none — the menu relies on [popover] / .ren-open and animates via the [data-closing] state.\"\n  - \"Putting non-menu rich content (forms, multi-line text blocks) directly inside <ren-menu> — wrap such content in ren-popover instead.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-menu-bg, --ren-menu-border, --ren-menu-danger-color, --ren-menu-duration, --ren-menu-easing, --ren-menu-item-height, --ren-menu-item-hover-bg, --ren-menu-item-padding, --ren-menu-item-radius, --ren-menu-padding, --ren-menu-radius, --ren-menu-shadow, --ren-menu-width.\"\n    - \"Semantic tokens used inside items: --color-surface, --color-border, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-danger, --color-danger-subtle, --color-accent.\"\n    - \"Layout / size / motion tokens: --space-* (1, 2, 3), --radius-sm, --radius-lg, --shadow-lg, --size-body, --size-caption, --size-icon-md, --z-dropdown, --duration-enter, --ease-enter, --duration-exit, --ease-exit, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / rgb() colors for hover, active, or danger states — pipe through --color-fill / --color-fill-hover / --color-danger / --color-danger-subtle.\"\n    - \"Custom animation timings; reuse --duration-enter / --duration-exit so reduced-motion handling continues to apply.\"\n\naccessibility:\n  required:\n    - \"<ren-menu> sets role=\\\"menu\\\" on itself and forces role=\\\"menuitem\\\" / \\\"menuitemcheckbox\\\" / \\\"menuitemradio\\\" on its children — do not override.\"\n    - \"Trigger receives aria-haspopup=\\\"menu\\\", aria-controls=\\\"<menu-id>\\\", and aria-expanded that flips with open()/close().\"\n    - \"Keyboard contract: ArrowDown/ArrowUp on the trigger opens the menu; Arrow keys navigate vertically with loop; Home/End jump to first/last; typeahead matches printable characters; Enter/Space selects.\"\n    - \"Checkbox / radio items toggle aria-checked; radio items reset siblings within .ren-menu-radio-group (or the menu root) before activating themselves.\"\n    - \"Dismissable layer closes on Escape and outside click via the shared dismissable util; do not block its event listeners.\"\n    - \"Danger items rely on a paired text label (Delete, Remove) — color alone (--color-danger-subtle) is not sufficient.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-menu/ren-menu.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-menu/ren-menu.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<button id=\"actions-trigger\" type=\"button\" data-menu-trigger>Actions</button>\n<ren-menu trigger-id=\"actions-trigger\" placement=\"bottom-start\"><button class=\"ren-menu-item\" role=\"menuitem\" data-value=\"edit\">Edit</button><button class=\"ren-menu-item\" role=\"menuitem\" data-value=\"duplicate\">Duplicate</button></ren-menu>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-menu`\n- `.ren-menu-checkbox-item`\n- `.ren-menu-group`\n- `.ren-menu-item`\n- `.ren-menu-item-danger`\n- `.ren-menu-item-description`\n- `.ren-menu-item-icon`\n- `.ren-menu-item-shortcut`\n- `.ren-menu-label`\n- `.ren-menu-radio-item`\n- `.ren-menu-separator`\n- `.ren-open`\n\n## States And Attributes\n\n- `[aria-checked]`\n- `[aria-disabled]`\n- `[data-closing]`\n- `[data-align]`\n- `[data-highlighted]`\n- `[data-menu-trigger]`\n- `[data-side]`\n- `placement`\n- `:active`\n- `:hover`\n\n## Public Token API\n\n- `--ren-menu-bg`\n- `--ren-menu-border`\n- `--ren-menu-danger-color`\n- `--ren-menu-duration`\n- `--ren-menu-easing`\n- `--ren-menu-item-height`\n- `--ren-menu-item-hover-bg`\n- `--ren-menu-item-padding`\n- `--ren-menu-item-radius`\n- `--ren-menu-padding`\n- `--ren-menu-radius`\n- `--ren-menu-shadow`\n- `--ren-menu-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-menu/ren-menu.css`\n- `components/composites/ren-menu/ren-menu.js`\n- `docs/components/ren-menu.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ═══ MENU CONTAINER ═══ */\n.ren-menu {\n  background: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg, 0.5rem);\n  box-shadow: var(--shadow-lg, 0 10px 25px rgb(0, 0, 0, 0.1));\n  padding-block: var(--space-1, 0.25rem);\n  min-width: 12rem;\n  z-index: var(--z-dropdown, 1000);\n  position: absolute;\n  animation: ren-menu-open var(--duration-enter) var(--ease-enter);\n  visibility: hidden;\n  opacity: 0;\n  inset: auto auto auto auto;\n  margin: 0;\n  max-height: calc(100vh - 32px);\n  overflow-y: auto;\n  overscroll-behavior: contain;\n}\n\n/* ═══ MENU OPEN STATE ═══ */\n.ren-menu[popover]:popover-open,\n.ren-menu.ren-open {\n  visibility: visible;\n  opacity: 1;\n}\n\n/* ═══ MENU ITEM BASE ═══ */\n.ren-menu-item {\n  padding: var(--space-2, 0.5rem) var(--space-3, 0.75rem);\n  display: flex;\n  align-items: center;\n  gap: var(--space-2, 0.5rem);\n  cursor: pointer;\n  font-size: var(--size-body, 1rem);\n  color: var(--color-text);\n  border-radius: var(--radius-sm, 0.25rem);\n  min-height: 2rem;\n  /* Tactile hover/focus — shared preset from semantic motion tokens */\n  transition: var(--transition-tactile);\n  white-space: nowrap;\n  text-decoration: none;\n}\n\n.ren-menu-item:hover,\n.ren-menu-item[data-highlighted] {\n  background-color: var(--color-fill);\n  color: var(--color-text);\n}\n\n.ren-menu-item:active {\n  background-color: var(--color-fill-hover);\n}\n\n/* ═══ MENU ITEM DISABLED STATE ═══ */\n.ren-menu-item[aria-disabled=\"true\"] {\n  opacity: 0.5;\n  cursor: not-allowed;\n  pointer-events: none;\n}\n\n/* ═══ MENU ITEM DANGER VARIANT ═══ */\n.ren-menu-item.ren-menu-item-danger:hover,\n.ren-menu-item.ren-menu-item-danger[data-highlighted] {\n  background-color: var(--color-danger-subtle);\n  color: var(--color-danger);\n}\n\n/* ═══ MENU ITEM ICON ═══ */\n.ren-menu-item-icon {\n  flex-shrink: 0;\n  width: var(--size-icon-md, 1.25rem);\n  height: var(--size-icon-md, 1.25rem);\n  color: var(--color-text-muted);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n/* ═══ MENU ITEM SHORTCUT/LABEL ═══ */\n.ren-menu-item-shortcut {\n  margin-inline-start: auto;\n  font-size: var(--size-caption, 0.75rem);\n  color: var(--color-text-muted);\n  font-family: monospace;\n  white-space: nowrap;\n  flex-shrink: 0;\n}\n\n/* ═══ MENU ITEM DESCRIPTION ═══ */\n.ren-menu-item-description {\n  font-size: var(--size-caption, 0.75rem);\n  color: var(--color-text-muted);\n  line-height: 1.3;\n}\n\n/* ═══ MENU SEPARATOR ═══ */\n.ren-menu-separator {\n  height: 1px;\n  background: var(--color-border);\n  margin-block: var(--space-1, 0.25rem);\n}\n\n/* ═══ MENU LABEL/GROUP HEADER ═══ */\n.ren-menu-label {\n  padding: var(--space-1, 0.25rem) var(--space-3, 0.75rem);\n  font-size: var(--size-caption, 0.75rem);\n  font-weight: 600;\n  color: var(--color-text-muted);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n}\n\n/* ═══ MENU GROUP ═══ */\n.ren-menu-group {\n  display: contents;\n}\n\n/* ═══ CHECKBOX MENU ITEM ═══ */\n.ren-menu-checkbox-item,\n.ren-menu-radio-item {\n  position: relative;\n  padding-inline-start: calc(var(--space-3, 0.75rem) + 20px + var(--space-2, 0.5rem));\n}\n\n.ren-menu-checkbox-item::before,\n.ren-menu-radio-item::before {\n  content: '';\n  position: absolute;\n  inset-inline-start: var(--space-3, 0.75rem);\n  width: 16px;\n  height: 16px;\n  border: 2px solid var(--color-border);\n  border-radius: 2px;\n  background: white;\n  /* Tactile — shared semantic preset */\n  transition: var(--transition-tactile);\n}\n\n.ren-menu-radio-item::before {\n  border-radius: 50%;\n}\n\n/* ═══ CHECKBOX/RADIO CHECKED STATE ═══ */\n.ren-menu-checkbox-item[aria-checked=\"true\"]::before,\n.ren-menu-radio-item[aria-checked=\"true\"]::before {\n  background: var(--color-accent);\n  border-color: var(--color-accent);\n}\n\n.ren-menu-checkbox-item[aria-checked=\"true\"]::after,\n.ren-menu-radio-item[aria-checked=\"true\"]::after {\n  content: '';\n  position: absolute;\n  inset-inline-start: calc(var(--space-3, 0.75rem) + 4px);\n  top: 50%;\n  transform: translateY(-50%);\n}\n\n.ren-menu-checkbox-item[aria-checked=\"true\"]::after {\n  width: 8px;\n  height: 4px;\n  border-inline-start: 2px solid white;\n  border-bottom: 2px solid white;\n  transform: translateY(-55%) rotate(-45deg);\n}\n\n.ren-menu-radio-item[aria-checked=\"true\"]::after {\n  width: 6px;\n  height: 6px;\n  background: white;\n  border-radius: 50%;\n  transform: translateY(-50%);\n}\n\n/* ═══ ANIMATIONS ═══ */\n@keyframes ren-menu-open {\n  from {\n    opacity: 0;\n    transform: translateY(-4px) scale(0.98);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0) scale(1);\n  }\n}\n\n/* ═══ CLOSING STATE WITH ANIMATION ═══ */\n.ren-menu[data-closing] {\n  animation: ren-menu-close var(--duration-exit) var(--ease-exit) forwards;\n}\n\n@keyframes ren-menu-close {\n  from {\n    opacity: 1;\n    transform: translateY(0) scale(1);\n  }\n  to {\n    opacity: 0;\n    transform: translateY(-4px) scale(0.98);\n  }\n}\n\n/* ═══ RESPECTS MOTION PREFERENCES ═══\n   Semantic --duration-* already collapse to 0ms under reduced-motion\n   (see tokens/semantic/motion.css). We keep this block as a\n   belt-and-suspenders fallback and to drop the scale/translate\n   keyframe so the menu simply appears/disappears in place. */\n@media (prefers-reduced-motion: reduce) {\n  .ren-menu {\n    animation: none;\n    transition: opacity var(--duration-enter);\n  }\n\n  .ren-menu:not(.ren-open) {\n    opacity: 0;\n  }\n\n  .ren-menu-item {\n    transition: background-color 0s, color 0s;\n  }\n\n  .ren-menu[data-closing] {\n    animation: none;\n  }\n}\n\n/* ═══ NATIVE POPOVER API SUPPORT ═══ */\n@supports (selector(:popover-open)) {\n  .ren-menu[popover] {\n    visibility: visible;\n    opacity: 0;\n    transition: opacity var(--duration-enter) var(--ease-enter),\n                overlay var(--duration-enter) var(--ease-enter) allow-discrete,\n                display var(--duration-enter) var(--ease-enter) allow-discrete;\n    animation: none;\n  }\n\n  @starting-style {\n    .ren-menu[popover]:popover-open {\n      opacity: 0;\n    }\n  }\n\n  .ren-menu[popover]:popover-open {\n    opacity: 1;\n  }\n}\n\n/* ═══ SCROLLBAR STYLING ═══ */\n.ren-menu::-webkit-scrollbar {\n  width: 6px;\n}\n\n.ren-menu::-webkit-scrollbar-track {\n  background: transparent;\n}\n\n.ren-menu::-webkit-scrollbar-thumb {\n  background: var(--color-border);\n  border-radius: 3px;\n}\n\n.ren-menu::-webkit-scrollbar-thumb:hover {\n  background: var(--color-text-muted);\n}\n/* Appearance API bridge */\n.ren-menu {\n  min-inline-size: var(--ren-menu-width, 200px);\n  padding: var(--ren-menu-padding, var(--space-1));\n  border-radius: var(--ren-menu-radius, var(--radius-lg));\n  background: var(--ren-menu-bg, var(--color-surface-overlay));\n  border-color: var(--ren-menu-border, var(--color-border));\n  box-shadow: var(--ren-menu-shadow, var(--shadow-lg));\n  transition-duration: var(--ren-menu-duration, var(--duration-fast));\n  transition-timing-function: var(--ren-menu-easing, var(--ease-out));\n}\n.ren-menu-item { min-block-size: var(--ren-menu-item-height, var(--size-md)); padding: var(--ren-menu-item-padding, var(--space-2) var(--space-3)); border-radius: var(--ren-menu-item-radius, var(--radius-sm)); }\n.ren-menu-item:hover, .ren-menu-item[data-highlighted] { background: var(--ren-menu-item-hover-bg, var(--color-fill)); }\n.ren-menu-item-danger { color: var(--ren-menu-danger-color, var(--color-danger)); }\n\n\n/**\n * RenDS — <ren-menu> Web Component\n * ==================================\n * Dropdown menu component with keyboard navigation, accessibility, and theming.\n * Supports items, groups, separators, checkboxes, and radio items.\n *\n * Uses Light DOM — no Shadow DOM.\n * Native Popover API when available, falls back to class-based positioning.\n *\n * Attributes:\n *   placement:     'bottom-start' (default) | 'bottom-end' | 'top-start' | 'top-end'\n *                  | 'right-start' | 'right-end' | 'left-start' | 'left-end'\n *                  - Controls menu position relative to trigger\n *\n * Markup:\n *   <button data-menu-trigger id=\"menu-trigger\">Menu</button>\n *   <ren-menu trigger-id=\"menu-trigger\" placement=\"bottom-start\">\n *     <button class=\"ren-menu-item\" role=\"menuitem\">Option 1</button>\n *     <button class=\"ren-menu-item\" role=\"menuitem\" data-value=\"opt2\">Option 2</button>\n *     <div class=\"ren-menu-separator\"></div>\n *     <button class=\"ren-menu-item ren-menu-item-danger\" role=\"menuitem\">Delete</button>\n *   </ren-menu>\n *\n * Features:\n *   - Arrow key navigation (vertical)\n *   - Home/End to first/last item\n *   - Enter/Space to select\n *   - Typeahead search\n *   - Click outside to close\n *   - Escape key to close\n *   - Focus management\n *   - Checkbox and radio item support\n *\n * Events:\n *   ren-menu-select:  Dispatched when an item is selected\n *   - detail: { item, value }\n *   ren-menu-open:    Dispatched when menu opens\n *   ren-menu-close:   Dispatched when menu closes\n */\n\nimport { createKeyboardNav } from '../../../utils/keyboard-nav.js';\nimport { createDismissable } from '../../../utils/dismissable.js';\n\nconst MENU_SIDES = new Set(['top', 'right', 'bottom', 'left']);\nconst MENU_ALIGNS = new Set(['start', 'end']);\n\nfunction normalizeMenuPlacement(value) {\n  const [sideValue, alignValue] = String(value || 'bottom-start')\n    .toLowerCase()\n    .split('-');\n  const side = MENU_SIDES.has(sideValue) ? sideValue : 'bottom';\n  const align = MENU_ALIGNS.has(alignValue) ? alignValue : 'start';\n\n  return { side, align };\n}\n\nexport class RenMenu extends HTMLElement {\n  static observedAttributes = ['placement'];\n\n  #trigger = null;\n  #isOpen = false;\n  #keyboardNav = null;\n  #dismissable = null;\n  #animationFrame = null;\n  #boundItemClick = null;\n  #boundTriggerClick = null;\n  #boundTriggerKeydown = null;\n  #returnFocus = true;\n  #closeTimer = null;\n\n  constructor() {\n    super();\n  }\n\n  /**\n   * Lifecycle: Element inserted into DOM\n   */\n  connectedCallback() {\n    this.loadStyles();\n    this.setupMenu();\n    this.findTrigger();\n    this.attachTriggerListeners();\n    this.attachItemListeners();\n  }\n\n  /**\n   * Lifecycle: Element removed from DOM\n   */\n  disconnectedCallback() {\n    this.cleanup();\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'placement' && oldValue !== newValue) {\n      this.syncPlacement();\n      if (this.#isOpen) {\n        this.positionMenu();\n      }\n    }\n  }\n\n  /* ═══ INITIALIZATION ═══ */\n\n  /**\n   * Load component styles\n   * @private\n   */\n  loadStyles() {\n    if (!document.getElementById('ren-menu-styles')) {\n      const style = document.createElement('style');\n      style.id = 'ren-menu-styles';\n      style.textContent = this.constructor.styles || '';\n      document.head.appendChild(style);\n    }\n  }\n\n  /**\n   * Setup menu element with required attributes and structure\n   * @private\n   */\n  setupMenu() {\n    this.classList.add('ren-menu');\n\n    // Set accessibility attributes\n    this.setAttribute('role', 'menu');\n    this.id = this.id || `ren-menu-${Math.random().toString(36).substr(2, 9)}`;\n\n    // Check for native Popover API support\n    if ('popover' in HTMLElement.prototype) {\n      this.setAttribute('popover', 'manual');\n    }\n\n    this.syncPlacement();\n\n    // Ensure all items have correct roles if not already set\n    this.querySelectorAll(\n      '.ren-menu-item, .ren-menu-checkbox-item, .ren-menu-radio-item, [role=\"menuitem\"], [role=\"menuitemcheckbox\"], [role=\"menuitemradio\"]'\n    ).forEach((item) => {\n      if (!item.hasAttribute('role')) {\n        if (item.classList.contains('ren-menu-checkbox-item')) {\n          item.setAttribute('role', 'menuitemcheckbox');\n        } else if (item.classList.contains('ren-menu-radio-item')) {\n          item.setAttribute('role', 'menuitemradio');\n        } else {\n          item.setAttribute('role', 'menuitem');\n        }\n      }\n\n      if (!item.hasAttribute('tabindex')) {\n        item.setAttribute('tabindex', '-1');\n      }\n    });\n  }\n\n  /**\n   * Find the trigger element\n   * Searches for: trigger-id attribute, data-menu-trigger, or previous sibling\n   * @private\n   */\n  findTrigger() {\n    const triggerId = this.getAttribute('trigger-id');\n    if (triggerId) {\n      this.#trigger = document.getElementById(triggerId);\n    }\n\n    if (!this.#trigger) {\n      this.#trigger = document.querySelector('[data-menu-trigger]');\n    }\n\n    if (!this.#trigger) {\n      this.#trigger = this.previousElementSibling;\n    }\n\n    if (this.#trigger) {\n      this.#trigger.setAttribute('aria-haspopup', 'menu');\n      this.#trigger.setAttribute('aria-controls', this.id);\n    }\n  }\n\n  /**\n   * Attach event listeners to trigger element\n   * @private\n   */\n  attachTriggerListeners() {\n    if (!this.#trigger) return;\n    this.#boundTriggerClick = (e) => this.handleTriggerClick(e);\n    this.#boundTriggerKeydown = (e) => this.handleTriggerKeydown(e);\n    this.#trigger.addEventListener('click', this.#boundTriggerClick);\n    this.#trigger.addEventListener('keydown', this.#boundTriggerKeydown);\n  }\n\n  /**\n   * Attach delegated item listeners\n   * @private\n   */\n  attachItemListeners() {\n    this.#boundItemClick = this.#boundItemClick || ((e) => this.handleItemClick(e));\n    this.removeEventListener('click', this.#boundItemClick);\n    this.addEventListener('click', this.#boundItemClick);\n  }\n\n  /* ═══ TRIGGER HANDLING ═══ */\n\n  /**\n   * Handle trigger click to open menu\n   * @private\n   */\n  handleTriggerClick(e) {\n    e.preventDefault();\n    if (this.#isOpen) {\n      this.close();\n    } else {\n      this.open();\n    }\n  }\n\n  /**\n   * Handle trigger keydown (arrow down to open)\n   * @private\n   */\n  handleTriggerKeydown(e) {\n    if ((e.key === 'ArrowDown' || e.key === 'ArrowUp') && !this.#isOpen) {\n      e.preventDefault();\n      this.open();\n    }\n  }\n\n  /**\n   * Handle item click selection\n   * @private\n   */\n  handleItemClick(e) {\n    const item = e.target.closest('[role=\"menuitem\"], [role=\"menuitemcheckbox\"], [role=\"menuitemradio\"]');\n\n    if (!item || !this.contains(item) || item.getAttribute('aria-disabled') === 'true') {\n      return;\n    }\n\n    e.preventDefault();\n    e.stopPropagation();\n    this.handleItemSelect(item);\n  }\n\n  /* ═══ POSITIONING ═══ */\n\n  /**\n   * Mirror preferred placement to public data attributes.\n   */\n  syncPlacement() {\n    const { side, align } = normalizeMenuPlacement(this.getAttribute('placement'));\n\n    this.setAttribute('data-side', side);\n    this.setAttribute('data-align', align);\n  }\n\n  /**\n   * Position the menu relative to trigger element\n   * @private\n   */\n  positionMenu() {\n    if (!this.#trigger) return;\n\n    const placement = normalizeMenuPlacement(this.getAttribute('placement'));\n    const triggerRect = this.#trigger.getBoundingClientRect();\n    const menuRect = this.getBoundingClientRect();\n    const gap = 8;\n    const margin = 8;\n\n    let finalSide = placement.side;\n    let left = triggerRect.left;\n    let top = triggerRect.bottom + gap;\n\n    if (placement.align === 'end') {\n      left = triggerRect.right - menuRect.width;\n    }\n\n    if (placement.side === 'top') {\n      top = triggerRect.top - menuRect.height - gap;\n    } else if (placement.side === 'right') {\n      left = triggerRect.right + gap;\n      top = placement.align === 'end'\n        ? triggerRect.bottom - menuRect.height\n        : triggerRect.top;\n    } else if (placement.side === 'left') {\n      left = triggerRect.left - menuRect.width - gap;\n      top = placement.align === 'end'\n        ? triggerRect.bottom - menuRect.height\n        : triggerRect.top;\n    }\n\n    // Viewport collision detection\n    const viewport = {\n      width: window.innerWidth,\n      height: window.innerHeight,\n    };\n\n    if (\n      placement.side === 'bottom' &&\n      top + menuRect.height > viewport.height - margin &&\n      triggerRect.top - menuRect.height - gap >= margin\n    ) {\n      finalSide = 'top';\n      top = triggerRect.top - menuRect.height - gap;\n    } else if (\n      placement.side === 'top' &&\n      top < margin &&\n      triggerRect.bottom + menuRect.height + gap <= viewport.height - margin\n    ) {\n      finalSide = 'bottom';\n      top = triggerRect.bottom + gap;\n    } else if (\n      placement.side === 'right' &&\n      left + menuRect.width > viewport.width - margin &&\n      triggerRect.left - menuRect.width - gap >= margin\n    ) {\n      finalSide = 'left';\n      left = triggerRect.left - menuRect.width - gap;\n    } else if (\n      placement.side === 'left' &&\n      left < margin &&\n      triggerRect.right + menuRect.width + gap <= viewport.width - margin\n    ) {\n      finalSide = 'right';\n      left = triggerRect.right + gap;\n    }\n\n    // Clamp horizontal and vertical position to viewport.\n    if (left < margin) {\n      left = margin;\n    } else if (left + menuRect.width > viewport.width - margin) {\n      left = viewport.width - menuRect.width - margin;\n    }\n\n    if (top < margin) {\n      top = margin;\n    } else if (top + menuRect.height > viewport.height - margin) {\n      top = viewport.height - menuRect.height - margin;\n    }\n\n    this.setAttribute('data-side', finalSide);\n    this.setAttribute('data-align', placement.align);\n    this.style.left = `${left}px`;\n    this.style.top = `${top}px`;\n  }\n\n  /* ═══ KEYBOARD NAVIGATION ═══ */\n\n  /**\n   * Setup keyboard navigation for menu items\n   * @private\n   */\n  setupKeyboardNav() {\n    if (this.#keyboardNav) {\n      this.#keyboardNav.detach();\n    }\n\n    this.#keyboardNav = createKeyboardNav(this, {\n      selector: '[role=\"menuitem\"]:not([aria-disabled=\"true\"]), [role=\"menuitemcheckbox\"]:not([aria-disabled=\"true\"]), [role=\"menuitemradio\"]:not([aria-disabled=\"true\"])',\n      orientation: 'vertical',\n      loop: true,\n      typeahead: true,\n      focusOnHover: true,\n      onActivate: (item) => {\n        item.focus();\n      },\n      onSelect: (item) => {\n        this.handleItemSelect(item);\n      },\n    });\n\n    this.#keyboardNav.attach();\n  }\n\n  /**\n   * Teardown keyboard navigation\n   * @private\n   */\n  teardownKeyboardNav() {\n    if (this.#keyboardNav) {\n      this.#keyboardNav.detach();\n      this.#keyboardNav = null;\n    }\n  }\n\n  /* ═══ DISMISSABLE LAYER ═══ */\n\n  /**\n   * Setup dismissable layer (click-outside, Escape key)\n   * @private\n   */\n  setupDismissable() {\n    if (this.#dismissable) {\n      this.#dismissable.deactivate();\n    }\n\n    this.#dismissable = createDismissable(this, {\n      onDismiss: () => this.close(),\n      triggerElement: this.#trigger,\n      escapeKey: true,\n      clickOutside: true,\n    });\n\n    this.#dismissable.activate();\n  }\n\n  /**\n   * Teardown dismissable layer\n   * @private\n   */\n  teardownDismissable() {\n    if (this.#dismissable) {\n      this.#dismissable.deactivate();\n      this.#dismissable = null;\n    }\n  }\n\n  /* ═══ ITEM SELECTION ═══ */\n\n  /**\n   * Handle menu item selection\n   * @private\n   */\n  handleItemSelect(item) {\n    const role = item.getAttribute('role');\n\n    // Handle checkbox items\n    if (item.classList.contains('ren-menu-checkbox-item') || role === 'menuitemcheckbox') {\n      const isChecked = item.getAttribute('aria-checked') === 'true';\n      item.setAttribute('aria-checked', !isChecked ? 'true' : 'false');\n      this.dispatchSelectEvent(item);\n      return;\n    }\n\n    // Handle radio items (exclusive selection within group)\n    if (item.classList.contains('ren-menu-radio-item') || role === 'menuitemradio') {\n      const group = item.closest('.ren-menu-radio-group') || this;\n      group\n        .querySelectorAll('.ren-menu-radio-item, [role=\"menuitemradio\"]')\n        .forEach((radioItem) => radioItem.setAttribute('aria-checked', 'false'));\n      item.setAttribute('aria-checked', 'true');\n      this.dispatchSelectEvent(item);\n      this.close();\n      return;\n    }\n\n    // Regular menu item: dispatch event and close\n    this.dispatchSelectEvent(item);\n    this.close();\n  }\n\n  /**\n   * Dispatch ren-menu-select event\n   * @private\n   */\n  dispatchSelectEvent(item) {\n    const value = item.getAttribute('data-value') || item.textContent.trim();\n    this.dispatchEvent(\n      new CustomEvent('ren-menu-select', {\n        bubbles: true,\n        detail: { item, value },\n      })\n    );\n  }\n\n  /* ═══ PUBLIC API ═══ */\n\n  /**\n   * Open the menu\n   */\n  open() {\n    if (this.#isOpen) return;\n\n    this.#isOpen = true;\n    this.#returnFocus = true;\n    this.setAttribute('data-state', 'open');\n    this.setupKeyboardNav();\n    this.setupDismissable();\n\n    this.positionMenu();\n\n    if ('popover' in HTMLElement.prototype) {\n      try {\n        this.showPopover();\n      } catch {\n        // Popover might already be open\n      }\n    } else {\n      this.classList.add('ren-open');\n    }\n\n    // Reposition on next frame for accuracy\n    this.#animationFrame = requestAnimationFrame(() => {\n      this.positionMenu();\n\n      // Focus first item\n      const firstItem = this.querySelector('[role=\"menuitem\"], [role=\"menuitemcheckbox\"], [role=\"menuitemradio\"]');\n      if (firstItem) {\n        firstItem.focus();\n      }\n    });\n\n    this.#trigger?.setAttribute('aria-expanded', 'true');\n    this.dispatchEvent(new CustomEvent('ren-menu-open', { bubbles: true }));\n  }\n\n  /**\n   * Close the menu\n   */\n  close() {\n    if (!this.#isOpen) return;\n\n    this.#isOpen = false;\n    this.setAttribute('data-state', 'closed');\n    this.teardownKeyboardNav();\n    this.teardownDismissable();\n\n    // Add closing animation class\n    this.setAttribute('data-closing', '');\n\n    // Wait for animation to finish before actually closing\n    const animationDuration = getComputedStyle(this).animationDuration;\n    const duration = parseFloat(animationDuration) * 1000;\n\n    if (this.#closeTimer) clearTimeout(this.#closeTimer);\n    this.#closeTimer = setTimeout(() => {\n      this.removeAttribute('data-closing');\n\n      if ('popover' in HTMLElement.prototype) {\n        try {\n          this.hidePopover();\n        } catch {\n          // Popover might already be closed\n        }\n      } else {\n        this.classList.remove('ren-open');\n      }\n\n      this.#trigger?.setAttribute('aria-expanded', 'false');\n      if (this.#returnFocus && this.#trigger && document.contains(this.#trigger)) {\n        this.#trigger.focus();\n      }\n      this.#closeTimer = null;\n    }, Math.min(duration, 150)); // Cap at 150ms\n\n    this.dispatchEvent(new CustomEvent('ren-menu-close', { bubbles: true }));\n  }\n\n  /**\n   * Check if menu is currently open\n   * @returns {boolean}\n   */\n  isOpen() {\n    return this.#isOpen;\n  }\n\n  /**\n   * Get the trigger element\n   * @returns {HTMLElement|null}\n   */\n  getTrigger() {\n    return this.#trigger;\n  }\n\n  /**\n   * Cleanup event listeners and resources\n   * @private\n   */\n  cleanup() {\n    this.close();\n    if (this.#closeTimer) clearTimeout(this.#closeTimer);\n    this.teardownKeyboardNav();\n    this.teardownDismissable();\n\n    if (this.#animationFrame) {\n      cancelAnimationFrame(this.#animationFrame);\n    }\n\n    if (this.#boundItemClick) {\n      this.removeEventListener('click', this.#boundItemClick);\n    }\n\n    if (this.#trigger) {\n      this.#trigger.removeEventListener('click', this.#boundTriggerClick);\n      this.#trigger.removeEventListener('keydown', this.#boundTriggerKeydown);\n    }\n  }\n}\n\nif (!customElements.get('ren-menu')) {\n  customElements.define('ren-menu', RenMenu);\n}\n\n// Compatibility export: context-menu behavior and registration are owned by\n// the colocated context-menu module so importing either historical path cannot\n// install a second implementation.\nexport { RenContextMenu } from '../ren-context-menu/ren-context-menu.js';\n",
      "path": "components/composites/ren-menu",
      "id": "component:composite:ren-menu",
      "type": "component",
      "name": "ren-menu"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-number-field",
          ".ren-number-field-decrement",
          ".ren-number-field-increment",
          ".ren-number-field-input",
          ".ren-number-field-lg",
          ".ren-number-field-sm"
        ],
        "tokens": [
          "--body-size",
          "--color-border",
          "--color-danger",
          "--color-disabled-bg",
          "--color-disabled-text",
          "--color-fill-active",
          "--color-fill-hover",
          "--color-input-bg",
          "--color-input-bg-hover",
          "--color-input-border",
          "--color-input-border-focus",
          "--color-input-focus-ring",
          "--color-input-placeholder",
          "--color-success",
          "--color-text",
          "--font-mono",
          "--radius-lg",
          "--radius-md",
          "--radius-sm",
          "--size-lg",
          "--size-sm",
          "--space-1",
          "--space-2",
          "--space-3",
          "--space-4",
          "--stroke-1",
          "--text-lg",
          "--text-sm",
          "--touch-min",
          "--transition-tactile"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-number-field Component Contract\n\nNumeric input composite with increment/decrement affordances.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-number-field` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-number-field` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Number Field composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User needs a discrete numeric value with visible − / + stepper buttons (quantities, counts, ratings out of N).\"\n    - \"Need long-press auto-repeat with acceleration on the stepper buttons (mouse and touch).\"\n    - \"Need keyboard contract: ArrowUp/Down for ±step, Home/End for min/max, clamped to min/max/step bounds.\"\n    - \"Need data-invalid / data-valid states wired to the wrapper (single source of truth for the focus ring).\"\n    - \"Need :has(:focus) to color the whole group, not just the inner input.\"\n  avoidWhen:\n    - \"Free-form numeric typing without bounds — use a plain <input type=\\\"number\\\"> styled via ren-input.\"\n    - \"Continuous value selection with visible track — use ren-slider.\"\n    - \"Numeric value paired with a unit selector (10 px / 1 rem) — compose ren-input with a ren-select.\"\n    - \"Currency / locale-formatted amount with thousands separators — that needs a masked input, not a stepper.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-number-field/ren-number-field.css\"\n  js:\n    - \"rends/components/composites/ren-number-field/ren-number-field.js\"\n  notes:\n    - \"Public Token API has no --ren-number-field-* tokens; theme through semantic input/fill tokens listed below.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Use <ren-number-field min=\\\"…\\\" max=\\\"…\\\" step=\\\"…\\\" value=\\\"…\\\"> as the host so attributes wire min/max/step on the inner input.\"\n  - \"Inner input is <input type=\\\"number\\\" class=\\\"ren-number-field-input\\\"> — keep type=\\\"number\\\" so mobile keyboards switch to digits.\"\n  - \"Decrement / increment must be real type=\\\"button\\\" steppers with class=\\\"ren-number-field-decrement\\\" / class=\\\"ren-number-field-increment\\\" and aria-label=\\\"Decrease\\\" / \\\"Increase\\\" (the component fills these if missing).\"\n  - \"Validation state goes on the wrapper as [data-invalid] or [data-valid]; the inner focus ring color reads from --color-danger / --color-success.\"\n  - \"Size variants are .ren-number-field-sm and .ren-number-field-lg on the wrapper; do not size buttons or input independently.\"\n\nforbiddenPatterns:\n  - \"Wrapping a <span role=\\\"button\\\"> instead of a real <button> for the steppers — long-press auto-repeat hooks depend on native button events.\"\n  - \"Showing the native spinner UI — the CSS already hides ::-webkit-inner-spin-button / ::-webkit-outer-spin-button and uses -moz-appearance: textfield.\"\n  - \"Bypassing the host's clamp() by writing this.value directly — call setValue(), increment(), or decrement() so min/max/step clamping runs and ren-change dispatches.\"\n  - \"Removing aria-label from the stepper buttons — the visible ± glyph alone has no accessible name.\"\n  - \"Custom outline on the inner input — focus styling is owned by the wrapper via :has(.ren-number-field-input:focus).\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic input tokens: --color-input-bg, --color-input-bg-hover, --color-input-border, --color-input-border-focus, --color-input-focus-ring, --color-input-placeholder, --color-disabled-bg, --color-disabled-text.\"\n    - \"Semantic neutral / state tokens: --color-text, --color-border, --color-fill-hover, --color-fill-active, --color-danger, --color-success.\"\n    - \"Layout / type / motion tokens: --space-1, --space-2, --space-3, --space-4, --stroke-1, --radius-sm, --radius-md, --radius-lg, --touch-min, --size-sm, --size-lg, --body-size, --text-sm, --text-lg, --font-mono, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / rgb() values; the current CSS still has two rgb() calls for focus-ring tints — do not propagate that pattern, theme via tokens.\"\n    - \"Inventing --ren-number-field-* custom properties not present in the source until they ship in the Public Token API.\"\n\naccessibility:\n  required:\n    - \"Stepper buttons keep min-width / height = var(--touch-min) (44px) — do not shrink below this except inside .ren-number-field-sm for non-touch contexts.\"\n    - \"Each stepper has an aria-label (\\\"Decrease\\\" / \\\"Increase\\\"); the icon is decorative content.\"\n    - \"ArrowUp / ArrowDown on the input dispatch increment()/decrement(); Home / End jump to min / max; values are always clamped before dispatching ren-change.\"\n    - \"Disabled state sets disabled on the inner input AND both buttons so they are removed from the tab order — do not rely on opacity alone.\"\n    - \"data-invalid / data-valid changes border AND focus ring color, but always pair with text feedback near the field — color is not the only signal.\"\n    - \"Long-press auto-repeat must stop on pointerup/touchend AND mouseleave so dragging off the button cancels acceleration.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-number-field/ren-number-field.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-number-field/ren-number-field.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-number-field min=\"0\" max=\"10\" step=\"1\" value=\"2\"><button class=\"ren-number-field-decrement\" type=\"button\" aria-label=\"Decrease\">−</button><input class=\"ren-number-field-input\" type=\"number\" aria-label=\"Quantity\"><button class=\"ren-number-field-increment\" type=\"button\" aria-label=\"Increase\">+</button></ren-number-field>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-number-field`\n- `.ren-number-field-decrement`\n- `.ren-number-field-increment`\n- `.ren-number-field-input`\n- `.ren-number-field-lg`\n- `.ren-number-field-sm`\n\n## States And Attributes\n\n- `[data-invalid]`\n- `[data-valid]`\n- `:active`\n- `:disabled`\n- `:hover`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-number-field/ren-number-field.css`\n- `components/composites/ren-number-field/ren-number-field.js`\n- `docs/components/ren-number-field.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ═══════════════════════════════════════════════════════════════\n   REN NUMBER FIELD (STEPPER) COMPONENT\n   ═══════════════════════════════════════════════════════════════\n   A controlled number input with increment/decrement buttons.\n   Supports keyboard navigation, long-press acceleration, and\n   min/max bounds. Follows RenDS design token system.\n\n   Usage:\n   <ren-number-field min=\"0\" max=\"100\" step=\"1\" value=\"5\">\n     <button class=\"ren-number-field-decrement\" aria-label=\"Decrease\">−</button>\n     <input class=\"ren-number-field-input\" type=\"number\" value=\"5\">\n     <button class=\"ren-number-field-increment\" aria-label=\"Increase\">+</button>\n   </ren-number-field>\n   ═══════════════════════════════════════════════════════════════ */\n\n/* ═══ BASE WRAPPER ═══ */\n.ren-number-field {\n  display: inline-flex;\n  align-items: center;\n  gap: 0;\n  border: var(--stroke-1) solid var(--color-input-border);\n  border-radius: var(--radius-md);\n  background-color: var(--color-input-bg);\n  transition: var(--transition-tactile);\n\n  /* ═══ FOCUS STATE ON GROUP ═══ */\n  &:has(.ren-number-field-input:focus) {\n    border-color: var(--color-input-border-focus);\n    box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n    background-color: var(--color-input-bg-hover);\n  }\n\n  /* ═══ HOVER STATE ═══ */\n  &:hover:not(:has(:disabled)) {\n    background-color: var(--color-input-bg-hover);\n  }\n\n  /* ═══ DISABLED STATE ═══ */\n  &:has(:disabled) {\n    background-color: var(--color-disabled-bg);\n    border-color: var(--color-border);\n    cursor: not-allowed;\n    opacity: 0.7;\n  }\n}\n\n/* ═══ STEPPER BUTTONS ═══ */\n.ren-number-field-decrement,\n.ren-number-field-increment {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-width: var(--touch-min);\n  height: var(--touch-min);\n  padding: 0 var(--space-2);\n  background: transparent;\n  border: none;\n  color: var(--color-text);\n  cursor: pointer;\n  font-size: var(--text-lg);\n  font-weight: 600;\n  font-family: var(--font-mono);\n  transition: var(--transition-tactile);\n\n  /* ═══ REMOVE FOCUS OUTLINE - GROUP HANDLES IT ═══ */\n  &:focus {\n    outline: none;\n  }\n\n  /* ═══ HOVER STATE ═══ */\n  &:hover:not(:disabled) {\n    background-color: var(--color-fill-hover);\n  }\n\n  /* ═══ ACTIVE STATE ═══ */\n  &:active:not(:disabled) {\n    background-color: var(--color-fill-active);\n  }\n\n  /* ═══ DISABLED STATE ═══ */\n  &:disabled {\n    color: var(--color-disabled-text);\n    cursor: not-allowed;\n    opacity: 0.5;\n  }\n}\n\n/* ═══ DECREMENT BUTTON (LEFT) ═══ */\n.ren-number-field-decrement {\n  border-inline-end: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-md) 0 0 var(--radius-md);\n\n  /* ═══ MINUS ICON ═══ */\n  &::before {\n    content: '−';\n  }\n}\n\n/* ═══ INCREMENT BUTTON (RIGHT) ═══ */\n.ren-number-field-increment {\n  border-inline-start: var(--stroke-1) solid var(--color-border);\n  border-radius: 0 var(--radius-md) var(--radius-md) 0;\n\n  /* ═══ PLUS ICON ═══ */\n  &::before {\n    content: '+';\n  }\n}\n\n/* ═══ INPUT FIELD (CENTER) ═══ */\n.ren-number-field-input {\n  flex: 1;\n  min-width: 3rem;\n  padding: var(--space-2) var(--space-3);\n  font-size: var(--body-size);\n  font-family: var(--font-mono);\n  font-weight: 500;\n  color: var(--color-text);\n  text-align: center;\n  background: transparent;\n  border: none;\n  outline: none;\n  cursor: text;\n\n  /* ═══ HIDE NATIVE NUMBER SPINNER ═══ */\n  &::-webkit-outer-spin-button,\n  &::-webkit-inner-spin-button {\n    -webkit-appearance: none;\n    margin: 0;\n  }\n\n  &[type='number'] {\n    -moz-appearance: textfield;\n  }\n\n  /* ═══ PLACEHOLDER ═══ */\n  &::placeholder {\n    color: var(--color-input-placeholder);\n  }\n\n  /* ═══ DISABLED ═══ */\n  &:disabled {\n    cursor: not-allowed;\n    color: var(--color-disabled-text);\n  }\n}\n\n/* ═══ VARIANT: SMALL ═══ */\n.ren-number-field-sm {\n  height: var(--size-sm);\n  border-radius: var(--radius-sm);\n\n  & .ren-number-field-decrement,\n  & .ren-number-field-increment {\n    min-width: var(--size-sm);\n    height: var(--size-sm);\n    padding: 0 var(--space-1);\n    font-size: var(--text-sm);\n  }\n\n  & .ren-number-field-decrement {\n    border-radius: var(--radius-sm) 0 0 var(--radius-sm);\n  }\n\n  & .ren-number-field-increment {\n    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;\n  }\n\n  & .ren-number-field-input {\n    padding: var(--space-1) var(--space-2);\n    font-size: var(--text-sm);\n  }\n}\n\n/* ═══ VARIANT: LARGE ═══ */\n.ren-number-field-lg {\n  height: var(--size-lg);\n  border-radius: var(--radius-lg);\n\n  & .ren-number-field-decrement,\n  & .ren-number-field-increment {\n    min-width: var(--size-lg);\n    height: var(--size-lg);\n    padding: 0 var(--space-3);\n    font-size: var(--text-lg);\n  }\n\n  & .ren-number-field-decrement {\n    border-radius: var(--radius-lg) 0 0 var(--radius-lg);\n  }\n\n  & .ren-number-field-increment {\n    border-radius: 0 var(--radius-lg) var(--radius-lg) 0;\n  }\n\n  & .ren-number-field-input {\n    padding: var(--space-3) var(--space-4);\n    font-size: var(--text-lg);\n  }\n}\n\n/* ═══ ERROR STATE ═══ */\n.ren-number-field[data-invalid] {\n  border-color: var(--color-danger);\n\n  &:focus-within {\n    box-shadow: 0 0 0 3px rgb(255, 59, 48, 0.15);\n  }\n}\n\n/* ═══ SUCCESS STATE ═══ */\n.ren-number-field[data-valid] {\n  border-color: var(--color-success);\n\n  &:focus-within {\n    box-shadow: 0 0 0 3px rgb(52, 199, 89, 0.15);\n  }\n}\n\n/* ═══ REDUCED MOTION ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-number-field,\n  .ren-number-field-decrement,\n  .ren-number-field-increment {\n    transition: none;\n  }\n}\n\n\n/* ═══════════════════════════════════════════════════════════════\n   REN NUMBER FIELD (STEPPER) WEB COMPONENT\n   ═══════════════════════════════════════════════════════════════\n   A light-DOM web component that enhances a number input with\n   increment/decrement buttons. Handles keyboard navigation,\n   long-press auto-repeat with acceleration, and min/max bounds.\n\n   Attributes:\n   - min: Minimum value (default: 0)\n   - max: Maximum value (default: 100)\n   - step: Step size (default: 1)\n   - value: Current value\n   - disabled: Disable the field\n\n   Events:\n   - ren-change: Dispatched on value change { value, oldValue }\n\n   Methods:\n   - increment(): Increase value by step\n   - decrement(): Decrease value by step\n   - getValue(): Return current numeric value\n   - setValue(val): Set value and clamp to bounds\n   ═══════════════════════════════════════════════════════════════ */\n\nexport class RenNumberField extends HTMLElement {\n  constructor() {\n    super();\n\n    /* ═══ STATE ═══ */\n    this.min = 0;\n    this.max = 100;\n    this.step = 1;\n    this.value = 0;\n    this.pressTimeout = null;\n    this.pressInterval = null;\n    this.pressRepeatDelay = 500;\n    this.pressRepeatInterval = 100;\n    this.pressRepeatAcceleration = 0.95;\n\n    /* ═══ BIND METHODS ═══ */\n    this.handleDecrementPress = this.handleDecrementPress.bind(this);\n    this.handleIncrementPress = this.handleIncrementPress.bind(this);\n    this.handleDecrementRelease = this.handleDecrementRelease.bind(this);\n    this.handleIncrementRelease = this.handleIncrementRelease.bind(this);\n    this.handleInputChange = this.handleInputChange.bind(this);\n    this.handleInputKeydown = this.handleInputKeydown.bind(this);\n  }\n\n  connectedCallback() {\n    /* ═══ READ ATTRIBUTES ═══ */\n    const minAttr = this.getAttribute('min');\n    const maxAttr = this.getAttribute('max');\n    const stepAttr = this.getAttribute('step');\n    const valueAttr = this.getAttribute('value');\n    const disabledAttr = this.hasAttribute('disabled');\n\n    if (minAttr !== null) this.min = parseFloat(minAttr);\n    if (maxAttr !== null) this.max = parseFloat(maxAttr);\n    if (stepAttr !== null) this.step = parseFloat(stepAttr);\n    if (valueAttr !== null) this.value = parseFloat(valueAttr);\n\n    /* ═══ GET OR CREATE ELEMENTS ═══ */\n    this.input = this.querySelector('.ren-number-field-input');\n    this.decrementBtn = this.querySelector('.ren-number-field-decrement');\n    this.incrementBtn = this.querySelector('.ren-number-field-increment');\n\n    /* ═══ CREATE IF MISSING ═══ */\n    if (!this.input) {\n      this.input = document.createElement('input');\n      this.input.type = 'number';\n      this.input.className = 'ren-number-field-input';\n      this.appendChild(this.input);\n    }\n\n    if (!this.decrementBtn) {\n      this.decrementBtn = document.createElement('button');\n      this.decrementBtn.className = 'ren-number-field-decrement';\n      this.decrementBtn.setAttribute('aria-label', 'Decrease');\n      this.insertBefore(this.decrementBtn, this.input);\n    }\n\n    if (!this.incrementBtn) {\n      this.incrementBtn = document.createElement('button');\n      this.incrementBtn.className = 'ren-number-field-increment';\n      this.incrementBtn.setAttribute('aria-label', 'Increase');\n      this.appendChild(this.incrementBtn);\n    }\n\n    // Steppers are controls for the number input, never form submitters.\n    this.decrementBtn.type = 'button';\n    this.incrementBtn.type = 'button';\n\n    /* ═══ SET INITIAL VALUES ═══ */\n    this.input.value = this.value.toString();\n    this.input.min = this.min.toString();\n    this.input.max = this.max.toString();\n    this.input.step = this.step.toString();\n\n    if (disabledAttr) {\n      this.input.disabled = true;\n      this.decrementBtn.disabled = true;\n      this.incrementBtn.disabled = true;\n    }\n\n    /* ═══ ATTACH EVENT LISTENERS ═══ */\n    this.input.addEventListener('change', this.handleInputChange);\n    this.input.addEventListener('keydown', this.handleInputKeydown);\n\n    /* ═══ BUTTON PRESS & HOLD ═══ */\n    this.decrementBtn.addEventListener('mousedown', this.handleDecrementPress);\n    this.decrementBtn.addEventListener('mouseup', this.handleDecrementRelease);\n    this.decrementBtn.addEventListener('mouseleave', this.handleDecrementRelease);\n    this.decrementBtn.addEventListener('touchstart', this.handleDecrementPress);\n    this.decrementBtn.addEventListener('touchend', this.handleDecrementRelease);\n\n    this.incrementBtn.addEventListener('mousedown', this.handleIncrementPress);\n    this.incrementBtn.addEventListener('mouseup', this.handleIncrementRelease);\n    this.incrementBtn.addEventListener('mouseleave', this.handleIncrementRelease);\n    this.incrementBtn.addEventListener('touchstart', this.handleIncrementPress);\n    this.incrementBtn.addEventListener('touchend', this.handleIncrementRelease);\n  }\n\n  disconnectedCallback() {\n    /* ═══ CLEAN UP EVENT LISTENERS ═══ */\n    if (this.input) {\n      this.input.removeEventListener('change', this.handleInputChange);\n      this.input.removeEventListener('keydown', this.handleInputKeydown);\n    }\n\n    if (this.decrementBtn) {\n      this.decrementBtn.removeEventListener('mousedown', this.handleDecrementPress);\n      this.decrementBtn.removeEventListener('mouseup', this.handleDecrementRelease);\n      this.decrementBtn.removeEventListener('mouseleave', this.handleDecrementRelease);\n      this.decrementBtn.removeEventListener('touchstart', this.handleDecrementPress);\n      this.decrementBtn.removeEventListener('touchend', this.handleDecrementRelease);\n    }\n\n    if (this.incrementBtn) {\n      this.incrementBtn.removeEventListener('mousedown', this.handleIncrementPress);\n      this.incrementBtn.removeEventListener('mouseup', this.handleIncrementRelease);\n      this.incrementBtn.removeEventListener('mouseleave', this.handleIncrementRelease);\n      this.incrementBtn.removeEventListener('touchstart', this.handleIncrementPress);\n      this.incrementBtn.removeEventListener('touchend', this.handleIncrementRelease);\n    }\n\n    /* ═══ CLEAR TIMERS ═══ */\n    this.clearPressTimers();\n  }\n\n  /* ═══════════════════════════════════════════════════════════════\n     DECREMENT BUTTON HANDLERS\n     ═══════════════════════════════════════════════════════════════ */\n  handleDecrementPress = (e) => {\n    if (this.input.disabled || this.decrementBtn.disabled) return;\n    e.preventDefault();\n\n    this.decrement();\n\n    /* ═══ START LONG PRESS REPEAT ═══ */\n    this.pressTimeout = setTimeout(() => {\n      let repeatInterval = this.pressRepeatInterval;\n\n      this.pressInterval = setInterval(() => {\n        this.decrement();\n        /* ═══ ACCELERATE ═══ */\n        repeatInterval *= this.pressRepeatAcceleration;\n        clearInterval(this.pressInterval);\n\n        this.pressInterval = setInterval(\n          () => this.decrement(),\n          Math.max(50, repeatInterval)\n        );\n      }, this.pressRepeatInterval);\n    }, this.pressRepeatDelay);\n  };\n\n  handleDecrementRelease = () => {\n    this.clearPressTimers();\n  };\n\n  /* ═══════════════════════════════════════════════════════════════\n     INCREMENT BUTTON HANDLERS\n     ═══════════════════════════════════════════════════════════════ */\n  handleIncrementPress = (e) => {\n    if (this.input.disabled || this.incrementBtn.disabled) return;\n    e.preventDefault();\n\n    this.increment();\n\n    /* ═══ START LONG PRESS REPEAT ═══ */\n    this.pressTimeout = setTimeout(() => {\n      let repeatInterval = this.pressRepeatInterval;\n\n      this.pressInterval = setInterval(() => {\n        this.increment();\n        /* ═══ ACCELERATE ═══ */\n        repeatInterval *= this.pressRepeatAcceleration;\n        clearInterval(this.pressInterval);\n\n        this.pressInterval = setInterval(\n          () => this.increment(),\n          Math.max(50, repeatInterval)\n        );\n      }, this.pressRepeatInterval);\n    }, this.pressRepeatDelay);\n  };\n\n  handleIncrementRelease = () => {\n    this.clearPressTimers();\n  };\n\n  /* ═══════════════════════════════════════════════════════════════\n     INPUT HANDLERS\n     ═══════════════════════════════════════════════════════════════ */\n  handleInputChange = (e) => {\n    const newValue = parseFloat(e.target.value);\n\n    if (!isNaN(newValue)) {\n      const oldValue = this.value;\n      this.value = this.clamp(newValue);\n      this.input.value = this.value.toString();\n\n      this.dispatchChangeEvent(oldValue);\n    }\n  };\n\n  handleInputKeydown = (e) => {\n    if (e.key === 'ArrowUp') {\n      e.preventDefault();\n      this.increment();\n    } else if (e.key === 'ArrowDown') {\n      e.preventDefault();\n      this.decrement();\n    } else if (e.key === 'Home') {\n      e.preventDefault();\n      const oldValue = this.value;\n      this.value = this.min;\n      this.input.value = this.value.toString();\n      this.dispatchChangeEvent(oldValue);\n    } else if (e.key === 'End') {\n      e.preventDefault();\n      const oldValue = this.value;\n      this.value = this.max;\n      this.input.value = this.value.toString();\n      this.dispatchChangeEvent(oldValue);\n    }\n  };\n\n  /* ═══════════════════════════════════════════════════════════════\n     PUBLIC METHODS\n     ═══════════════════════════════════════════════════════════════ */\n  increment() {\n    const oldValue = this.value;\n    this.value = this.clamp(this.value + this.step);\n    this.input.value = this.value.toString();\n    this.dispatchChangeEvent(oldValue);\n  }\n\n  decrement() {\n    const oldValue = this.value;\n    this.value = this.clamp(this.value - this.step);\n    this.input.value = this.value.toString();\n    this.dispatchChangeEvent(oldValue);\n  }\n\n  getValue() {\n    return this.value;\n  }\n\n  setValue(val) {\n    const oldValue = this.value;\n    this.value = this.clamp(parseFloat(val) || 0);\n    this.input.value = this.value.toString();\n    this.dispatchChangeEvent(oldValue);\n  }\n\n  /* ═══════════════════════════════════════════════════════════════\n     UTILITY METHODS\n     ═══════════════════════════════════════════════════════════════ */\n  clamp(val) {\n    return Math.max(this.min, Math.min(this.max, val));\n  }\n\n  dispatchChangeEvent(oldValue) {\n    if (this.value !== oldValue) {\n      this.dispatchEvent(\n        new CustomEvent('ren-change', {\n          bubbles: true,\n          composed: true,\n          detail: {\n            value: this.value,\n            oldValue: oldValue,\n          },\n        })\n      );\n    }\n  }\n\n  clearPressTimers() {\n    if (this.pressTimeout) {\n      clearTimeout(this.pressTimeout);\n      this.pressTimeout = null;\n    }\n\n    if (this.pressInterval) {\n      clearInterval(this.pressInterval);\n      this.pressInterval = null;\n    }\n  }\n\n  /* ═══════════════════════════════════════════════════════════════\n     PROPERTIES\n     ═══════════════════════════════════════════════════════════════ */\n  get disabled() {\n    return this.hasAttribute('disabled');\n  }\n\n  set disabled(val) {\n    if (val) {\n      this.setAttribute('disabled', '');\n      if (this.input) this.input.disabled = true;\n      if (this.decrementBtn) this.decrementBtn.disabled = true;\n      if (this.incrementBtn) this.incrementBtn.disabled = true;\n    } else {\n      this.removeAttribute('disabled');\n      if (this.input) this.input.disabled = false;\n      if (this.decrementBtn) this.decrementBtn.disabled = false;\n      if (this.incrementBtn) this.incrementBtn.disabled = false;\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-number-field')) {\n  customElements.define('ren-number-field', RenNumberField);\n}\n",
      "path": "components/composites/ren-number-field",
      "id": "component:composite:ren-number-field",
      "type": "component",
      "name": "ren-number-field"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-otp",
          ".ren-otp-lg",
          ".ren-otp-separator",
          ".ren-otp-slot",
          ".ren-otp-sm"
        ],
        "tokens": [
          "--body-size",
          "--color-accent",
          "--color-border",
          "--color-danger",
          "--color-disabled-bg",
          "--color-disabled-text",
          "--color-fill",
          "--color-input-bg",
          "--color-input-bg-hover",
          "--color-input-border",
          "--color-input-border-focus",
          "--color-input-focus-ring",
          "--color-input-placeholder",
          "--color-success",
          "--color-text",
          "--color-text-muted",
          "--font-mono",
          "--radius-lg",
          "--radius-md",
          "--radius-sm",
          "--size-lg",
          "--size-sm",
          "--space-0-5",
          "--space-1",
          "--space-2",
          "--space-3",
          "--stroke-1",
          "--text-lg",
          "--text-sm",
          "--touch-min",
          "--transition-tactile"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-otp Component Contract\n\nOne-time-passcode segmented input composite.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-otp` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-otp` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Otp composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User must enter a short fixed-length code split across N single-character slots (SMS OTP, 2FA, email PIN).\"\n    - \"Need auto-advance to the next slot, Backspace to previous slot, Arrow keys to navigate, Home/End to jump to first/last.\"\n    - \"Need clipboard paste to distribute the pasted string across consecutive slots and focus the next empty one.\"\n    - \"Need numeric vs alphanumeric filtering (inputmode=\\\"numeric\\\" or \\\"text\\\") and a per-slot data-filled state.\"\n    - \"Need explicit data-valid / data-invalid states on the wrapper plus a ren-complete event when all slots fill.\"\n  avoidWhen:\n    - \"User pastes a long password / token in one go — use a single <input type=\\\"password\\\"> with autocomplete=\\\"one-time-code\\\" if it is a single field.\"\n    - \"Free-form numeric value with unit / decimals — use ren-number-field.\"\n    - \"Code length is variable or unknown — use a single text input.\"\n    - \"Code is alphanumeric AND case-sensitive in a way the slot UI cannot make obvious — fall back to a single masked input.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-otp/ren-otp.css\"\n  js:\n    - \"rends/components/composites/ren-otp/ren-otp.js\"\n  notes:\n    - \"Public Token API has no --ren-otp-* tokens; theme through the semantic input tokens and size scale listed below.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Use <ren-otp length=\\\"6\\\" type=\\\"numeric\\\"> as the host element — slots are generated by JS, do not author them by hand.\"\n  - \"If you must author slots, each must be a real <input class=\\\"ren-otp-slot\\\" type=\\\"text\\\" maxlength=\\\"1\\\" inputmode=\\\"numeric|text\\\" aria-label=\\\"Code digit N of M\\\" data-index=\\\"N\\\">.\"\n  - \"Optional separators are <div class=\\\"ren-otp-separator\\\"> placed between slot groups; they are pointer-events: none and decorative.\"\n  - \"Size variants .ren-otp-sm and .ren-otp-lg go on the wrapper; do not size individual .ren-otp-slot.\"\n  - \"Validation state goes on the wrapper as [data-valid] or [data-invalid]; the per-slot [data-filled] is set automatically by the component.\"\n\nforbiddenPatterns:\n  - \"Using a single <input maxlength=\\\"6\\\"> styled to look segmented — keyboard caret control and paste-distribute won't work.\"\n  - \"Removing maxlength=\\\"1\\\" / inputmode — without them mobile keyboards switch back to letters and the auto-advance breaks.\"\n  - \"Manually wiring focus on slot blur / change — let handleSlotInput / handleSlotKeydown manage focus transitions to keep Backspace and paste flows correct.\"\n  - \"Hardcoding the slot size — use --touch-min for the default, --size-sm via .ren-otp-sm, --size-lg via .ren-otp-lg.\"\n  - \"Mixing data-valid and data-invalid on the wrapper simultaneously — setValid() removes the opposite attribute by design.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic input tokens: --color-input-bg, --color-input-bg-hover, --color-input-border, --color-input-border-focus, --color-input-focus-ring, --color-input-placeholder, --color-disabled-bg, --color-disabled-text.\"\n    - \"Semantic state / text tokens: --color-text, --color-text-muted, --color-border, --color-fill, --color-accent, --color-danger, --color-success.\"\n    - \"Layout / type / motion tokens: --space-0-5, --space-1, --space-2, --space-3, --stroke-1, --radius-sm, --radius-md, --radius-lg, --touch-min, --size-sm, --size-lg, --text-sm, --text-lg, --body-size, --font-mono, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / rgb() colors for focus rings or borders; the current CSS still uses rgb() for the danger / success ring tints — do not replicate, prefer tokens.\"\n    - \"Inventing --ren-otp-* custom properties not present in the source until they ship in the Public Token API.\"\n\naccessibility:\n  required:\n    - \"Slot dimensions default to var(--touch-min) (44px) for thumb accuracy; --size-sm only for non-touch surfaces.\"\n    - \"Each slot exposes aria-label=\\\"Code digit N of M\\\" so screen readers announce position; do not strip these labels.\"\n    - \"Backspace, ArrowLeft/Right, Home, End move focus across slots — keep the keydown handler intact when extending the component.\"\n    - \"Paste handler must run on every slot so users can paste from any slot, not only the first.\"\n    - \"data-valid and data-invalid are paired with text feedback elsewhere on the form (do not communicate validation through color alone).\"\n    - \":focus-visible draws --color-accent at 2px outline; do not remove it without restoring an equivalent ring.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-otp/ren-otp.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-otp/ren-otp.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-otp length=\"6\" type=\"numeric\" aria-label=\"Verification code\"></ren-otp>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-otp`\n- `.ren-otp-lg`\n- `.ren-otp-separator`\n- `.ren-otp-slot`\n- `.ren-otp-sm`\n\n## States And Attributes\n\n- `[data-filled]`\n- `[data-invalid]`\n- `[data-valid]`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-otp/ren-otp.css`\n- `components/composites/ren-otp/ren-otp.js`\n- `docs/components/ren-otp.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ═══════════════════════════════════════════════════════════════\n   REN OTP (ONE-TIME PASSWORD) INPUT COMPONENT\n   ═══════════════════════════════════════════════════════════════\n   A flexible OTP input with auto-advancing slots, paste support,\n   and validation states. Handles numeric and alphanumeric codes.\n   Follows RenDS design token system.\n\n   Usage:\n   <ren-otp length=\"6\" type=\"numeric\">\n     \\3c !-- Slots auto-generated by JS -->\n   </ren-otp>\n\n   Attributes:\n   - length: Number of OTP digits (default: 6)\n   - type: \"numeric\" or \"alphanumeric\" (default: \"numeric\")\n   - disabled: Disable all slots\n\n   Data Attributes:\n   - data-valid: Success state\n   - data-invalid: Error state\n   - data-filled: On individual slots when filled\n   ═══════════════════════════════════════════════════════════════ */\n\n/* ═══ BASE WRAPPER ═══ */\n.ren-otp {\n  container-type: inline-size;\n  container-name: ren-otp;\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  flex-wrap: wrap;\n}\n\n/* ═══ INDIVIDUAL SLOT ═══ */\n.ren-otp-slot {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: var(--touch-min);\n  height: var(--touch-min);\n  padding: 0;\n  font-size: var(--text-lg);\n  font-family: var(--font-mono);\n  font-weight: 600;\n  color: var(--color-text);\n  background-color: var(--color-input-bg);\n  border: var(--stroke-1) solid var(--color-input-border);\n  border-radius: var(--radius-md);\n  outline: none;\n  cursor: text;\n  caret-color: var(--color-accent);\n  text-align: center;\n  transition: var(--transition-tactile);\n\n  /* ═══ PLACEHOLDER STYLING ═══ */\n  &::placeholder {\n    color: var(--color-input-placeholder);\n  }\n\n  /* ═══ HOVER STATE ═══ */\n  &:hover:not(:disabled):not(:focus) {\n    background-color: var(--color-input-bg-hover);\n  }\n\n  /* ═══ FOCUS STATE ═══ */\n  &:focus {\n    border-color: var(--color-input-border-focus);\n    box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n    background-color: var(--color-input-bg-hover);\n  }\n\n  /* ═══ FILLED STATE ═══ */\n  &[data-filled] {\n    background-color: var(--color-fill);\n    border-color: var(--color-input-border);\n  }\n\n  /* ═══ DISABLED STATE ═══ */\n  &:disabled {\n    background-color: var(--color-disabled-bg);\n    color: var(--color-disabled-text);\n    cursor: not-allowed;\n    opacity: 0.7;\n    border-color: var(--color-border);\n  }\n}\n\n/* ═══ SEPARATOR ═══ */\n.ren-otp-separator {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: auto;\n  height: var(--touch-min);\n  padding: 0 var(--space-1);\n  color: var(--color-text-muted);\n  font-size: var(--text-lg);\n  font-weight: 300;\n  user-select: none;\n  pointer-events: none;\n}\n\n/* ═══════════════════════════════════════════════════════════════\n   VALIDATION STATES\n   ═══════════════════════════════════════════════════════════════ */\n\n/* ═══ ERROR STATE ═══ */\n.ren-otp[data-invalid] .ren-otp-slot {\n  border-color: var(--color-danger);\n\n  &:focus {\n    box-shadow: 0 0 0 3px rgb(255, 59, 48, 0.15);\n    border-color: var(--color-danger);\n  }\n}\n\n/* ═══ SUCCESS STATE ═══ */\n.ren-otp[data-valid] .ren-otp-slot {\n  border-color: var(--color-success);\n\n  &:focus {\n    box-shadow: 0 0 0 3px rgb(52, 199, 89, 0.15);\n    border-color: var(--color-success);\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════\n   SIZE VARIANTS\n   ═══════════════════════════════════════════════════════════════ */\n\n/* ═══ SMALL VARIANT ═══ */\n.ren-otp-sm {\n  gap: var(--space-1);\n\n  & .ren-otp-slot {\n    width: var(--size-sm);\n    height: var(--size-sm);\n    font-size: var(--text-sm);\n    border-radius: var(--radius-sm);\n  }\n\n  & .ren-otp-separator {\n    height: var(--size-sm);\n    font-size: var(--text-sm);\n  }\n}\n\n/* ═══ LARGE VARIANT ═══ */\n.ren-otp-lg {\n  gap: var(--space-3);\n\n  & .ren-otp-slot {\n    width: var(--size-lg);\n    height: var(--size-lg);\n    font-size: var(--body-size);\n    border-radius: var(--radius-lg);\n  }\n\n  & .ren-otp-separator {\n    height: var(--size-lg);\n    font-size: var(--body-size);\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════\n   ACCESSIBILITY\n   ═══════════════════════════════════════════════════════════════ */\n\n/* ═══ FOCUS VISIBLE (KEYBOARD NAVIGATION) ═══ */\n.ren-otp-slot:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: 2px;\n}\n\n/* ═══ DISABLED WRAPPER ═══ */\n.ren-otp[disabled] .ren-otp-slot {\n  cursor: not-allowed;\n  opacity: 0.6;\n}\n\n/* ═══════════════════════════════════════════════════════════════\n   REDUCED MOTION\n   ═══════════════════════════════════════════════════════════════ */\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-otp-slot {\n    transition: none;\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════\n   MOBILE RESPONSIVENESS\n   ═══════════════════════════════════════════════════════════════ */\n\n@container ren-otp (max-width: 360px) {\n  .ren-otp {\n    gap: var(--space-1);\n\n    & .ren-otp-slot {\n      width: var(--size-sm);\n      height: var(--size-sm);\n      font-size: var(--text-sm);\n    }\n\n    & .ren-otp-separator {\n      height: var(--size-sm);\n      font-size: var(--text-sm);\n      padding: 0 var(--space-0-5);\n    }\n  }\n}\n\n\n/* ═══════════════════════════════════════════════════════════════\n   REN OTP (ONE-TIME PASSWORD) INPUT WEB COMPONENT\n   ═══════════════════════════════════════════════════════════════\n   A light-DOM web component for collecting OTP/PIN codes.\n   Auto-creates input slots, manages focus transitions,\n   handles paste events, and validates completion.\n\n   Attributes:\n   - length: Number of code slots (default: 6)\n   - type: \"numeric\" or \"alphanumeric\" (default: \"numeric\")\n   - disabled: Disable all slots\n\n   Data Attributes:\n   - data-valid: Set to mark as success\n   - data-invalid: Set to mark as error\n   - data-filled: Set on individual slots when they have content\n\n   Events:\n   - ren-change: Dispatched on any slot change { value: partial }\n   - ren-complete: Dispatched when all slots filled { value: complete }\n\n   Methods:\n   - getValue(): Return current combined code\n   - reset(): Clear all slots\n   - focus(): Focus first slot\n   ═══════════════════════════════════════════════════════════════ */\n\nexport class RenOtp extends HTMLElement {\n  constructor() {\n    super();\n\n    /* ═══ STATE ═══ */\n    this.length = 6;\n    this.type = 'numeric';\n    this.slots = [];\n\n    /* ═══ BIND METHODS ═══ */\n    this.handleSlotInput = this.handleSlotInput.bind(this);\n    this.handleSlotKeydown = this.handleSlotKeydown.bind(this);\n    this.handleSlotPaste = this.handleSlotPaste.bind(this);\n    this.handleSlotFocus = this.handleSlotFocus.bind(this);\n  }\n\n  connectedCallback() {\n    /* ═══ READ ATTRIBUTES ═══ */\n    const lengthAttr = this.getAttribute('length');\n    const typeAttr = this.getAttribute('type');\n    const disabledAttr = this.hasAttribute('disabled');\n\n    if (lengthAttr !== null) this.length = parseInt(lengthAttr, 10);\n    if (typeAttr !== null) this.type = typeAttr;\n\n    /* ═══ CREATE SLOTS ═══ */\n    this.createSlots(disabledAttr);\n  }\n\n  /* ═══════════════════════════════════════════════════════════════\n     SLOT CREATION & SETUP\n     ═══════════════════════════════════════════════════════════════ */\n  createSlots(disabled = false) {\n    /* ═══ CLEAR EXISTING SLOTS ═══ */\n    this.slots = [];\n    const existingSlots = this.querySelectorAll('.ren-otp-slot');\n    existingSlots.forEach((slot) => slot.remove());\n\n    /* ═══ CREATE NEW SLOTS ═══ */\n    for (let i = 0; i < this.length; i++) {\n      const slot = document.createElement('input');\n      slot.type = 'text';\n      slot.className = 'ren-otp-slot';\n      slot.setAttribute('inputmode', this.type === 'numeric' ? 'numeric' : 'text');\n      slot.setAttribute('maxlength', '1');\n      slot.setAttribute('placeholder', '•');\n      slot.setAttribute('autocomplete', 'off');\n      slot.setAttribute('data-index', i.toString());\n      slot.setAttribute('aria-label', `Code digit ${i + 1} of ${this.length}`);\n\n      if (disabled) {\n        slot.disabled = true;\n      }\n\n      /* ═══ ATTACH EVENT LISTENERS ═══ */\n      slot.addEventListener('input', this.handleSlotInput);\n      slot.addEventListener('keydown', this.handleSlotKeydown);\n      slot.addEventListener('paste', this.handleSlotPaste);\n      slot.addEventListener('focus', this.handleSlotFocus);\n\n      this.appendChild(slot);\n      this.slots.push(slot);\n    }\n  }\n\n  /* ═══════════════════════════════════════════════════════════════\n     EVENT HANDLERS\n     ═══════════════════════════════════════════════════════════════ */\n\n  handleSlotInput = (e) => {\n    const slot = e.target;\n    const index = parseInt(slot.getAttribute('data-index'), 10);\n    let value = slot.value;\n\n    /* ═══ VALIDATE INPUT ═══ */\n    if (this.type === 'numeric') {\n      value = value.replace(/[^0-9]/g, '');\n    } else if (this.type === 'alphanumeric') {\n      value = value.replace(/[^a-zA-Z0-9]/g, '');\n    }\n\n    /* ═══ ENFORCE MAX LENGTH ═══ */\n    if (value.length > 1) {\n      value = value.slice(0, 1);\n    }\n\n    slot.value = value;\n\n    /* ═══ UPDATE FILLED STATE ═══ */\n    if (value) {\n      slot.setAttribute('data-filled', '');\n    } else {\n      slot.removeAttribute('data-filled');\n    }\n\n    /* ═══ AUTO-ADVANCE TO NEXT SLOT ═══ */\n    if (value && index < this.slots.length - 1) {\n      this.slots[index + 1].focus();\n    }\n\n    /* ═══ DISPATCH CHANGE EVENT ═══ */\n    this.dispatchChangeEvent();\n\n    /* ═══ CHECK COMPLETION ═══ */\n    this.checkCompletion();\n  };\n\n  handleSlotKeydown = (e) => {\n    const slot = e.target;\n    const index = parseInt(slot.getAttribute('data-index'), 10);\n\n    /* ═══ BACKSPACE: MOVE TO PREVIOUS SLOT ═══ */\n    if (e.key === 'Backspace') {\n      if (slot.value) {\n        slot.value = '';\n        slot.removeAttribute('data-filled');\n        this.dispatchChangeEvent();\n      } else if (index > 0) {\n        e.preventDefault();\n        const prevSlot = this.slots[index - 1];\n        prevSlot.value = '';\n        prevSlot.removeAttribute('data-filled');\n        prevSlot.focus();\n        this.dispatchChangeEvent();\n      }\n    }\n\n    /* ═══ ARROW KEYS: NAVIGATE SLOTS ═══ */\n    if (e.key === 'ArrowLeft' && index > 0) {\n      e.preventDefault();\n      this.slots[index - 1].focus();\n    } else if (e.key === 'ArrowRight' && index < this.slots.length - 1) {\n      e.preventDefault();\n      this.slots[index + 1].focus();\n    }\n\n    /* ═══ HOME/END: FIRST/LAST SLOT ═══ */\n    if (e.key === 'Home') {\n      e.preventDefault();\n      this.slots[0].focus();\n    } else if (e.key === 'End') {\n      e.preventDefault();\n      this.slots[this.slots.length - 1].focus();\n    }\n  };\n\n  handleSlotPaste = (e) => {\n    e.preventDefault();\n    const pastedData = e.clipboardData?.getData('text') || '';\n\n    if (!pastedData) return;\n\n    /* ═══ VALIDATE PASTED DATA ═══ */\n    let sanitized = pastedData;\n    if (this.type === 'numeric') {\n      sanitized = pastedData.replace(/[^0-9]/g, '');\n    } else if (this.type === 'alphanumeric') {\n      sanitized = pastedData.replace(/[^a-zA-Z0-9]/g, '');\n    }\n\n    /* ═══ DISTRIBUTE ACROSS SLOTS ═══ */\n    const index = parseInt(e.target.getAttribute('data-index'), 10);\n    for (let i = 0; i < sanitized.length && index + i < this.slots.length; i++) {\n      const slot = this.slots[index + i];\n      slot.value = sanitized[i];\n      slot.setAttribute('data-filled', '');\n    }\n\n    /* ═══ FOCUS NEXT EMPTY OR LAST SLOT ═══ */\n    const nextEmptyIndex = this.slots.findIndex(\n      (s, i) => i >= index && s.value === ''\n    );\n    if (nextEmptyIndex !== -1) {\n      this.slots[nextEmptyIndex].focus();\n    } else {\n      this.slots[this.slots.length - 1].focus();\n    }\n\n    /* ═══ DISPATCH CHANGE & CHECK COMPLETION ═══ */\n    this.dispatchChangeEvent();\n    this.checkCompletion();\n  };\n\n  handleSlotFocus = (e) => {\n    /* ═══ SELECT ALL TEXT WHEN FOCUSED ═══ */\n    e.target.select();\n  };\n\n  /* ═══════════════════════════════════════════════════════════════\n     PUBLIC METHODS\n     ═══════════════════════════════════════════════════════════════ */\n\n  getValue() {\n    return this.slots.map((slot) => slot.value).join('');\n  }\n\n  reset() {\n    this.slots.forEach((slot) => {\n      slot.value = '';\n      slot.removeAttribute('data-filled');\n    });\n\n    this.removeAttribute('data-valid');\n    this.removeAttribute('data-invalid');\n    this.dispatchChangeEvent();\n\n    if (this.slots.length > 0) {\n      this.slots[0].focus();\n    }\n  }\n\n  setValid(isValid) {\n    if (isValid) {\n      this.setAttribute('data-valid', '');\n      this.removeAttribute('data-invalid');\n    } else {\n      this.setAttribute('data-invalid', '');\n      this.removeAttribute('data-valid');\n    }\n  }\n\n  clearValidation() {\n    this.removeAttribute('data-valid');\n    this.removeAttribute('data-invalid');\n  }\n\n  /* ═══════════════════════════════════════════════════════════════\n     UTILITY METHODS\n     ═══════════════════════════════════════════════════════════════ */\n\n  checkCompletion() {\n    const value = this.getValue();\n    const isComplete = value.length === this.length;\n\n    if (isComplete) {\n      this.dispatchCompleteEvent(value);\n    }\n  }\n\n  dispatchChangeEvent() {\n    const value = this.getValue();\n\n    this.dispatchEvent(\n      new CustomEvent('ren-change', {\n        bubbles: true,\n        composed: true,\n        detail: {\n          value: value,\n        },\n      })\n    );\n  }\n\n  dispatchCompleteEvent(value) {\n    this.dispatchEvent(\n      new CustomEvent('ren-complete', {\n        bubbles: true,\n        composed: true,\n        detail: {\n          value: value,\n        },\n      })\n    );\n  }\n\n  /* ═══════════════════════════════════════════════════════════════\n     PROPERTIES\n     ═══════════════════════════════════════════════════════════════ */\n\n  get disabled() {\n    return this.hasAttribute('disabled');\n  }\n\n  set disabled(val) {\n    if (val) {\n      this.setAttribute('disabled', '');\n      this.slots.forEach((slot) => {\n        slot.disabled = true;\n      });\n    } else {\n      this.removeAttribute('disabled');\n      this.slots.forEach((slot) => {\n        slot.disabled = false;\n      });\n    }\n  }\n\n  /* ═══ OVERRIDE FOCUS METHOD ═══ */\n  focus() {\n    if (this.slots.length > 0) {\n      this.slots[0].focus();\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-otp')) {\n  customElements.define('ren-otp', RenOtp);\n}\n",
      "path": "components/composites/ren-otp",
      "id": "component:composite:ren-otp",
      "type": "component",
      "name": "ren-otp"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-popover",
          ".ren-popover-arrow",
          ".ren-popover-body",
          ".ren-popover-footer",
          ".ren-popover-header",
          ".ren-popover-trigger"
        ],
        "tokens": [
          "--color-border",
          "--color-surface",
          "--color-surface-overlay",
          "--color-text-primary",
          "--color-text-secondary",
          "--duration-enter",
          "--duration-normal",
          "--ease-enter",
          "--ease-out",
          "--radius-lg",
          "--ren-popover-bg",
          "--ren-popover-border-color",
          "--ren-popover-duration",
          "--ren-popover-easing",
          "--ren-popover-padding",
          "--ren-popover-radius",
          "--ren-popover-shadow",
          "--ren-popover-width",
          "--shadow-lg",
          "--size-body-sm",
          "--space-2",
          "--space-3",
          "--space-4",
          "--z-popover"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-popover Component Contract\n\nNon-modal floating surface for contextual controls or content.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-popover` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-popover` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Popover composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"Click on a trigger should reveal a small contextual surface (filters, mini-form, info card) anchored to that trigger.\"\n    - \"You need anchor-positioned placement (top/right/bottom/left) via position-area with automatic viewport flip via position-try-fallbacks.\"\n    - \"Need click-outside + Escape dismissal and a visible arrow caret that re-rotates per [data-side].\"\n    - \"Need progressive enhancement: anchor positioning in modern browsers, JS computePosition() fallback elsewhere.\"\n    - \"Need header / body / footer slots inside the popover surface, with native role=\\\"dialog\\\" + aria-modal=\\\"false\\\".\"\n  avoidWhen:\n    - \"The disclosure blocks the page and demands the user's attention — use ren-dialog (modal).\"\n    - \"Disclosure is reveal-on-hover-intent for non-essential preview content — use ren-hover-card.\"\n    - \"Disclosure is a short text annotation only — use ren-tooltip.\"\n    - \"Disclosure is a list of commands (menu) — use ren-menu / ren-context-menu.\"\n    - \"Disclosure is a side sheet — use ren-sheet.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-popover/ren-popover.css\"\n  js:\n    - \"rends/components/composites/ren-popover/ren-popover.js\"\n  notes:\n    - \"JS attaches click + Escape + outside-click handlers and computes JS positioning only when CSS anchor positioning is unsupported.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Use <ren-popover placement=\\\"bottom\\\" offset=\\\"8\\\"> as the host so it adds .ren-popover, role=\\\"dialog\\\", and popover=\\\"manual\\\" automatically.\"\n  - \"The trigger is referenced via trigger-id=\\\"<id>\\\", or by a child / previous-sibling with [data-popover-trigger]; the component picks them in that order.\"\n  - \"Inside the popover, group content into .ren-popover-header / .ren-popover-body / .ren-popover-footer; a .ren-popover-arrow div is injected if missing.\"\n  - \"Triggers should be real interactive elements (<button> or <a>) so click + keyboard activation work; the component does not click-style arbitrary nodes.\"\n  - \"When using multiple popovers in the same view, set unique trigger-id values so the click handlers do not cross-wire.\"\n\nforbiddenPatterns:\n  - \"Manually wiring trigger.addEventListener('click', popover.show) — the host already attaches its click + dismissable listeners.\"\n  - \"Calling showPopover() directly while bypassing open() — aria-modal, data-state, and ren-open event won't fire.\"\n  - \"Hardcoding inline left/top values — let CSS position-anchor + position-area + position-try-fallbacks handle placement and flips (or the JS computePosition fallback).\"\n  - \"Removing the .ren-popover-arrow element — the [data-side] rules rotate it per side; without it the visual anchor cue disappears.\"\n  - \"Using ren-popover purely as a click-bound tooltip — accessibility expectations differ; use ren-tooltip for short, non-interactive text.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-popover-bg, --ren-popover-border-color, --ren-popover-duration, --ren-popover-easing, --ren-popover-padding, --ren-popover-radius, --ren-popover-shadow, --ren-popover-width.\"\n    - \"Semantic tokens used inside content: --color-surface, --color-border, --color-text-primary, --color-text-secondary, --shadow-lg, --radius-lg.\"\n    - \"Layout / type / motion tokens: --space-2, --space-3, --space-4, --size-body-sm, --z-popover, --duration-enter, --ease-enter.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded backdrop / surface colors via inline styles or rgba().\"\n    - \"Custom animation timings; reuse --duration-enter / --ease-enter (or --ren-popover-duration / --ren-popover-easing) so reduced-motion overrides apply.\"\n\naccessibility:\n  required:\n    - \"<ren-popover> sets role=\\\"dialog\\\" with aria-modal=\\\"false\\\" (non-modal by design). Do not set aria-modal=\\\"true\\\" or apply focus trapping.\"\n    - \"Trigger must be a focusable, real interactive element; click + Enter/Space activation come from native button semantics.\"\n    - \"Escape closes the popover via the document keydown handler; outside-click closes via the document click handler — do not stopPropagation on container click outside these targets.\"\n    - \"When CSS anchor positioning is unsupported the host applies left/top to the popover element; do not overlay your own positioning style or the flip logic breaks.\"\n    - \"Reduced motion strips the transform animation (opacity-only); keep the @media (prefers-reduced-motion: reduce) block intact.\"\n    - \"Avoid putting form submit buttons that affect the page outside the popover surface — keep the interaction self-contained within the dialog role.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-popover/ren-popover.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-popover/ren-popover.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<button id=\"account-trigger\" type=\"button\" data-popover-trigger>Account</button>\n<ren-popover trigger-id=\"account-trigger\" placement=\"bottom\">\n  <div class=\"ren-popover-header\"><strong>Account</strong></div>\n  <div class=\"ren-popover-body\">Signed in as Jane</div>\n</ren-popover>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-open`\n- `.ren-popover`\n- `.ren-popover-arrow`\n- `.ren-popover-body`\n- `.ren-popover-footer`\n- `.ren-popover-header`\n- `.ren-popover-trigger`\n\n## States And Attributes\n\n- `[data-popover-trigger]`\n- `[data-side]`\n\n## Public Token API\n\n- `--ren-popover-bg`\n- `--ren-popover-border-color`\n- `--ren-popover-duration`\n- `--ren-popover-easing`\n- `--ren-popover-padding`\n- `--ren-popover-radius`\n- `--ren-popover-shadow`\n- `--ren-popover-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-popover/ren-popover.css`\n- `components/composites/ren-popover/ren-popover.js`\n- `docs/components/ren-popover.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ═══ ANCHOR POSITIONING & BASE STYLES ═══ */\n.ren-popover {\n  position: absolute;\n  background: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  padding: var(--space-4);\n  max-width: 20rem;\n  z-index: var(--z-popover, 1000);\n  pointer-events: auto;\n  margin: 0;\n  inset: auto;\n  border-top: none;\n\n  /* Anchor positioning (modern browsers) */\n  position-anchor: --popover-anchor;\n\n  /* Default: below the trigger with automatic fallback */\n  position-area: bottom span-all;\n\n  /* Automatic flip when near viewport edges */\n  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;\n\n  /* Enter/exit transitions driven by semantic motion tokens. */\n  opacity: 0;\n  transform: translateY(-4px) scale(0.95);\n  transition:\n    opacity   var(--duration-enter) var(--ease-enter),\n    transform var(--duration-enter) var(--ease-enter),\n    overlay   var(--duration-enter) var(--ease-enter) allow-discrete,\n    display   var(--duration-enter) var(--ease-enter) allow-discrete;\n}\n\n/* ═══ POPOVER OPEN STATE ═══ */\n.ren-popover:popover-open,\n.ren-popover.ren-open {\n  opacity: 1;\n  transform: translateY(0) scale(1);\n}\n\n/* ═══ POPOVER OPEN WITH @starting-style ═══ */\n@starting-style {\n  .ren-popover:popover-open {\n    opacity: 0;\n    transform: translateY(-4px) scale(0.95);\n  }\n}\n\n/* ═══ POPOVER ARROW/CARET ═══ */\n.ren-popover-arrow {\n  position: absolute;\n  width: 8px;\n  height: 8px;\n  background: inherit;\n  border: inherit;\n  border-top: none;\n  border-inline-start: none;\n  transform: rotate(45deg);\n  z-index: -1;\n}\n\n/* ═══ PLACEMENT VARIANTS ═══ */\n/* Bottom (default) */\n.ren-popover[data-side=\"bottom\"],\n.ren-popover:not([data-side]) {\n  position-area: bottom span-all;\n  margin-block-start: var(--space-2);\n  margin-block-end: 0;\n  margin-inline: 0;\n\n  & .ren-popover-arrow {\n    top: -4px;\n    left: 50%;\n    transform: translateX(-50%) rotate(225deg);\n  }\n}\n\n/* Top */\n.ren-popover[data-side=\"top\"] {\n  position-area: top span-all;\n  margin-block-start: 0;\n  margin-block-end: var(--space-2);\n  margin-inline: 0;\n\n  & .ren-popover-arrow {\n    bottom: -4px;\n    left: 50%;\n    transform: translateX(-50%) rotate(45deg);\n  }\n}\n\n/* Right */\n.ren-popover[data-side=\"right\"] {\n  position-area: right span-all;\n  margin-block: 0;\n  margin-inline-start: var(--space-2);\n  margin-inline-end: 0;\n\n  & .ren-popover-arrow {\n    left: -4px;\n    top: 50%;\n    transform: translateY(-50%) rotate(135deg);\n  }\n}\n\n/* Left */\n.ren-popover[data-side=\"left\"] {\n  position-area: left span-all;\n  margin-block: 0;\n  margin-inline-start: 0;\n  margin-inline-end: var(--space-2);\n\n  & .ren-popover-arrow {\n    right: -4px;\n    top: 50%;\n    transform: translateY(-50%) rotate(315deg);\n  }\n}\n\n/* ═══ POPOVER SECTIONS ═══ */\n.ren-popover-header {\n  margin-bottom: var(--space-2);\n  font-weight: 600;\n  color: var(--color-text-primary);\n}\n\n.ren-popover-body {\n  color: var(--color-text-secondary);\n  font-size: var(--size-body-sm);\n  line-height: 1.5;\n}\n\n.ren-popover-footer {\n  margin-top: var(--space-3);\n  padding-top: var(--space-3);\n  border-top: 1px solid var(--color-border);\n  display: flex;\n  gap: var(--space-2);\n}\n\n/* ═══ RESPECTS MOTION PREFERENCES ═══\n   Semantic --duration-enter already collapses to 0ms under\n   reduced-motion. We keep this block to drop the transform\n   (scale/translate) in the starting-style, so the popover\n   fades in place instead of moving. */\n@media (prefers-reduced-motion: reduce) {\n  .ren-popover {\n    transition:\n      opacity var(--duration-enter),\n      overlay var(--duration-enter) allow-discrete,\n      display var(--duration-enter) allow-discrete;\n    transform: none;\n  }\n\n  @starting-style {\n    .ren-popover:popover-open {\n      opacity: 0;\n      transform: none;\n    }\n  }\n}\n\n/* ═══ FALLBACK FOR BROWSERS WITHOUT COMPLETE ANCHOR POSITIONING ═══ */\n@supports not ((anchor-name: --ren-anchor) and\n  (position-anchor: --ren-anchor) and\n  (position-area: bottom span-all)) {\n  .ren-popover {\n    position: absolute;\n    inset: auto auto auto auto;\n  }\n\n  [data-popover-trigger],\n  .ren-popover-trigger {\n    position: relative;\n  }\n\n  /* Fallback animations */\n  .ren-popover {\n    animation: ren-popover-fallback-open var(--duration-enter) var(--ease-enter);\n  }\n\n  .ren-popover:popover-open,\n  .ren-popover.ren-open {\n    animation: ren-popover-fallback-open var(--duration-enter) var(--ease-enter);\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    .ren-popover {\n      animation: none;\n    }\n  }\n}\n\n@keyframes ren-popover-fallback-open {\n  from {\n    opacity: 0;\n    transform: translateY(-4px) scale(0.95);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0) scale(1);\n  }\n}\n\n/* Appearance API bridge */\n.ren-popover {\n  width: var(--ren-popover-width, 280px);\n  max-width: var(--ren-popover-width, 280px);\n  padding: var(--ren-popover-padding, var(--space-4));\n  border-radius: var(--ren-popover-radius, var(--radius-lg));\n  background: var(--ren-popover-bg, var(--color-surface-overlay));\n  border-color: var(--ren-popover-border-color, var(--color-border));\n  box-shadow: var(--ren-popover-shadow, var(--shadow-lg));\n  transition-duration: var(--ren-popover-duration, var(--duration-normal));\n  transition-timing-function: var(--ren-popover-easing, var(--ease-out));\n}\n\n\nlet nextPopoverId = 0;\nconst FOCUSABLE_SELECTOR = [\n  'a[href]',\n  'button:not([disabled])',\n  'input:not([disabled]):not([type=\"hidden\"])',\n  'select:not([disabled])',\n  'textarea:not([disabled])',\n  '[tabindex]:not([tabindex=\"-1\"])',\n].join(', ');\nconst PLACEMENTS = new Set(['top', 'right', 'bottom', 'left']);\n\nfunction supportsAnchorPositioning() {\n  return (\n    typeof CSS !== 'undefined' &&\n    typeof CSS.supports === 'function' &&\n    CSS.supports('anchor-name', '--ren-anchor') &&\n    CSS.supports('position-anchor', '--ren-anchor') &&\n    CSS.supports('position-area', 'bottom span-all')\n  );\n}\n\nfunction normalizePlacement(value, fallback = 'bottom') {\n  return PLACEMENTS.has(value) ? value : fallback;\n}\n\n/**\n * Fallback position computation for browsers without CSS anchor positioning.\n * Used only when full CSS anchor positioning support is unavailable.\n *\n * @param {HTMLElement} trigger - The trigger element\n * @param {HTMLElement} popover - The popover element\n * @param {string} placement - Placement: 'top', 'right', 'bottom', 'left'\n * @param {number} offset - Offset in pixels between trigger and popover\n * @returns {Object} Position object with x, y, and finalPlacement properties\n */\nfunction computePosition(trigger, popover, placement = 'bottom', offset = 8) {\n  const triggerRect = trigger.getBoundingClientRect();\n  const popoverRect = popover.getBoundingClientRect();\n  const viewport = { width: window.innerWidth, height: window.innerHeight };\n\n  let x = 0;\n  let y = 0;\n  let finalPlacement = placement;\n\n  const placements = {\n    top: () => {\n      x = triggerRect.left + (triggerRect.width - popoverRect.width) / 2;\n      y = triggerRect.top - popoverRect.height - offset;\n      if (y < 0) {\n        finalPlacement = 'bottom';\n        return placements.bottom();\n      }\n      return { x, y };\n    },\n    bottom: () => {\n      x = triggerRect.left + (triggerRect.width - popoverRect.width) / 2;\n      y = triggerRect.bottom + offset;\n      if (y + popoverRect.height > viewport.height) {\n        finalPlacement = 'top';\n        return placements.top();\n      }\n      return { x, y };\n    },\n    left: () => {\n      x = triggerRect.left - popoverRect.width - offset;\n      y = triggerRect.top + (triggerRect.height - popoverRect.height) / 2;\n      if (x < 0) {\n        finalPlacement = 'right';\n        return placements.right();\n      }\n      return { x, y };\n    },\n    right: () => {\n      x = triggerRect.right + offset;\n      y = triggerRect.top + (triggerRect.height - popoverRect.height) / 2;\n      if (x + popoverRect.width > viewport.width) {\n        finalPlacement = 'left';\n        return placements.left();\n      }\n      return { x, y };\n    },\n  };\n\n  const result = placements[placement]?.() || placements.bottom();\n\n  // Clamp X position within viewport\n  if (result.x < 0) result.x = 8;\n  else if (result.x + popoverRect.width > viewport.width)\n    result.x = viewport.width - popoverRect.width - 8;\n\n  return { ...result, finalPlacement };\n}\n\n/**\n * Popover component with CSS Anchor Positioning and native Popover API\n *\n * Displays content relative to a trigger element with automatic viewport collision\n * handling via CSS anchor positioning. Falls back to JS positioning in unsupported browsers.\n *\n * @example\n * <button data-popover-trigger>Open Popover</button>\n * <ren-popover placement=\"bottom\" offset=\"8\">\n *   <div class=\"ren-popover-header\">Title</div>\n *   <div class=\"ren-popover-body\">Content</div>\n * </ren-popover>\n *\n * @fires ren-open - Fired when popover opens\n * @fires ren-close - Fired when popover closes\n */\nexport class RenPopover extends HTMLElement {\n  static observedAttributes = ['placement'];\n  static supportsAnchor = supportsAnchorPositioning();\n\n  #trigger = null;\n  #triggerController = null;\n  #dismissController = null;\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'placement' && oldValue !== newValue) {\n      this.#syncPlacement();\n    }\n  }\n\n  connectedCallback() {\n    this.setupPopover();\n    this.findTrigger();\n    this.attachTriggerListener();\n  }\n\n  disconnectedCallback() {\n    this.cleanup();\n  }\n\n  /**\n   * Setup popover element with required attributes\n   * @private\n   */\n  setupPopover() {\n    this.classList.add('ren-popover');\n\n    if (!this.id) {\n      this.id = `ren-popover-${++nextPopoverId}`;\n    }\n\n    // Add arrow if not present\n    if (!this.querySelector('.ren-popover-arrow')) {\n      const arrow = document.createElement('div');\n      arrow.className = 'ren-popover-arrow';\n      this.appendChild(arrow);\n    }\n\n    // Setup native Popover API\n    if ('popover' in HTMLElement.prototype) {\n      this.setAttribute('popover', 'manual');\n    }\n\n    // Set accessibility attributes\n    this.setAttribute('role', 'dialog');\n    this.setAttribute('aria-modal', 'false');\n    this.#syncPlacement();\n  }\n\n  /**\n   * Find the trigger element\n   * @private\n   */\n  findTrigger() {\n    // Explicit trigger ID\n    const triggerId = this.getAttribute('trigger-id');\n    if (triggerId) {\n      this.#trigger = document.getElementById(triggerId);\n    }\n\n    // Query selector for data-popover-trigger\n    if (!this.#trigger) {\n      this.#trigger = this.querySelector('[data-popover-trigger]');\n    }\n\n    // Fall back to previous sibling\n    if (!this.#trigger) {\n      this.#trigger = this.previousElementSibling;\n    }\n  }\n\n  /**\n   * Attach trigger listener and setup anchor relationship\n   * @private\n   */\n  attachTriggerListener() {\n    if (!this.#trigger) return;\n\n    this.#triggerController?.abort();\n    this.#triggerController = new AbortController();\n\n    // Set up anchor relationship if CSS anchors are supported\n    if (RenPopover.supportsAnchor) {\n      this.#trigger.style.anchorName = '--popover-anchor';\n    } else {\n      // Fallback: ensure trigger can be positioned relative to\n      if (getComputedStyle(this.#trigger.parentElement).position === 'static') {\n        this.#trigger.parentElement.style.position = 'relative';\n      }\n    }\n\n    // Wire up popovertarget if not already set\n    if (!this.#trigger.hasAttribute('popovertarget')) {\n      this.#trigger.setAttribute('popovertarget', this.id);\n    }\n    this.#trigger.setAttribute('aria-haspopup', 'dialog');\n    this.#trigger.setAttribute('aria-controls', this.id);\n    this.#trigger.setAttribute('aria-expanded', this.isOpen() ? 'true' : 'false');\n\n    // Click handler\n    this.#trigger.addEventListener(\n      'click',\n      (e) => {\n        e.preventDefault();\n        e.stopPropagation();\n        this.toggle();\n      },\n      { signal: this.#triggerController.signal }\n    );\n\n    // Setup dismiss behavior (click outside, Escape key)\n    this.#dismissController?.abort();\n    this.#dismissController = new AbortController();\n\n    document.addEventListener(\n      'click',\n      (e) => {\n        if (\n          this.isOpen() &&\n          e.target !== this &&\n          !this.contains(e.target) &&\n          e.target !== this.#trigger &&\n          !this.#trigger?.contains(e.target)\n        ) {\n          this.close();\n        }\n      },\n      { signal: this.#dismissController.signal }\n    );\n\n    document.addEventListener(\n      'keydown',\n      (e) => {\n        if (e.key === 'Escape' && this.isOpen()) {\n          this.close();\n        }\n      },\n      { signal: this.#dismissController.signal }\n    );\n  }\n\n  /**\n   * Position popover relative to trigger (fallback for non-anchor browsers)\n   * @private\n   */\n  positionPopover() {\n    if (!this.#trigger || RenPopover.supportsAnchor) return;\n\n    const placement = normalizePlacement(this.getAttribute('placement'), 'bottom');\n    const offset = parseInt(this.getAttribute('offset')) || 8;\n\n    const { x, y, finalPlacement } = computePosition(\n      this.#trigger,\n      this,\n      placement,\n      offset\n    );\n\n    this.style.left = `${x}px`;\n    this.style.top = `${y}px`;\n    this.setAttribute('data-side', finalPlacement);\n  }\n\n  #syncPlacement() {\n    const placement = normalizePlacement(this.getAttribute('placement'), 'bottom');\n    this.setAttribute('data-side', placement);\n  }\n\n  /**\n   * Open the popover\n   */\n  open() {\n    if (this.isOpen()) return;\n\n    this.positionPopover();\n    this.setAttribute('data-state', 'open');\n\n    if ('popover' in HTMLElement.prototype) {\n      try {\n        this.showPopover();\n      } catch {\n        // Already open or other error\n      }\n    } else {\n      this.classList.add('ren-open');\n    }\n\n    // Popover is explicitly non-modal (see component.md).\n    // No aria-modal or focus trap — those are for ren-dialog / ren-sheet.\n    this.setAttribute('aria-modal', 'false');\n    this.#trigger?.setAttribute('aria-expanded', 'true');\n    // Note: no focus trap for non-modal popover.\n    requestAnimationFrame(() => this.#focusInitialElement());\n    this.dispatchEvent(new CustomEvent('ren-open', { bubbles: true }));\n  }\n\n  /**\n   * Close the popover\n   */\n  close() {\n    if (!this.isOpen()) return;\n\n    const activeElement = document.activeElement;\n    const shouldRestoreFocus =\n      this.#trigger &&\n      activeElement instanceof HTMLElement &&\n      this.contains(activeElement);\n\n    this.setAttribute('data-state', 'closed');\n\n    if ('popover' in HTMLElement.prototype) {\n      try {\n        this.hidePopover();\n      } catch {\n        // Already closed or other error\n      }\n    } else {\n      this.classList.remove('ren-open');\n    }\n\n    this.setAttribute('aria-modal', 'false');\n    this.#trigger?.setAttribute('aria-expanded', 'false');\n    if (shouldRestoreFocus) {\n      this.#trigger.focus({ preventScroll: true });\n    }\n    this.dispatchEvent(new CustomEvent('ren-close', { bubbles: true }));\n  }\n\n  /**\n   * Toggle popover open/closed state\n   */\n  toggle() {\n    if (this.isOpen()) {\n      this.close();\n    } else {\n      this.open();\n    }\n  }\n\n  /**\n   * Check if popover is currently open\n   * @returns {boolean}\n   */\n  isOpen() {\n    if ('popover' in HTMLElement.prototype) {\n      return this.matches(':popover-open');\n    }\n    return this.classList.contains('ren-open');\n  }\n\n  /**\n   * Cleanup event listeners\n   * @private\n   */\n  cleanup() {\n    this.#triggerController?.abort();\n    this.#triggerController = null;\n    this.#dismissController?.abort();\n    this.#dismissController = null;\n  }\n\n  #focusInitialElement() {\n    const target = this.querySelector(FOCUSABLE_SELECTOR) || this;\n    if (target === this && !this.hasAttribute('tabindex')) {\n      this.setAttribute('tabindex', '-1');\n    }\n    target.focus({ preventScroll: true });\n  }\n\n  /**\n   * Get the trigger element\n   * @returns {HTMLElement|null}\n   */\n  getTrigger() {\n    return this.#trigger;\n  }\n\n  /**\n   * Set the trigger element\n   * @param {HTMLElement} trigger\n   */\n  setTrigger(trigger) {\n    this.#trigger = trigger;\n    this.attachTriggerListener();\n  }\n}\n\nif (!customElements.get('ren-popover')) {\n  customElements.define('ren-popover', RenPopover);\n}\n",
      "path": "components/composites/ren-popover",
      "id": "component:composite:ren-popover",
      "type": "component",
      "name": "ren-popover"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-scroll-area",
          ".ren-scroll-area-full",
          ".ren-scroll-area-lg",
          ".ren-scroll-area-md",
          ".ren-scroll-area-sm",
          ".ren-scroll-area-xl"
        ],
        "tokens": [
          "--color-fill-active",
          "--color-fill-hover",
          "--color-surface",
          "--duration-enter",
          "--ease-enter",
          "--radius-full",
          "--scroll-max",
          "--transition-tactile"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-scroll-area Component Contract\n\nScrollable region styling that preserves native scrolling.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-scroll-area` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-scroll-area` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Scroll Area composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"A bounded region needs themed native scrollbars (Firefox scrollbar-color + WebKit ::-webkit-scrollbar).\"\n    - \"You want consistent scrollbar styling across light/dark themes without injecting JS or shadow DOM.\"\n    - \"Need preset max-height utilities (.ren-scroll-area-sm/-md/-lg/-xl/-full) and direction variants (.-x, .-y).\"\n    - \"Need fade-edge masks (.-fade, .-fade-x) to hint at overflow without resorting to JS observers.\"\n    - \"Need an \\\"auto\\\" variant where the scrollbar only appears on hover / focus-within.\"\n  avoidWhen:\n    - \"You need virtualization, restore-scroll-position, or scroll-into-view APIs — those need bespoke JS, not this CSS contract.\"\n    - \"You need a custom-drawn track / thumb (e.g. mini-map, position indicator) — native scrollbar styling cannot do this.\"\n    - \"The container should never scroll (clip overflow) — use overflow: clip / hidden directly.\"\n    - \"The content scrolls horizontally only and needs snap behavior — use a dedicated carousel/scroll-snap pattern.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-scroll-area/ren-scroll-area.css\"\n  notes:\n    - \"CSS-only component: no JavaScript file is colocated. Do not introduce JS unless the source component grows that responsibility.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Apply class=\\\"ren-scroll-area\\\" to a real block element that owns overflow (commonly <div>, <section>, <main>).\"\n  - \"Constrain height/width explicitly — either via .ren-scroll-area-sm/-md/-lg/-xl/-full, an inline --scroll-max custom property, or a parent flex/grid track.\"\n  - \"Pair direction with intent: .-x for horizontal-only, .-y for vertical-only; omit for auto in both axes.\"\n  - \"When using .-fade or .-fade-x the masking ::before/::after read from --color-surface; ensure the scroll area sits on a surface that matches that token.\"\n  - \"Children that should be focusable for keyboard scrolling need tabindex=\\\"0\\\" on the .ren-scroll-area itself when it has no focusable descendants.\"\n\nforbiddenPatterns:\n  - \"Overriding overflow with overflow: hidden — that defeats the entire component (no scrollbar, no fade mask alignment).\"\n  - \"Wrapping a <ren-scroll-area> custom element instead of the .ren-scroll-area class — no custom element exists for this composite.\"\n  - \"Stacking position: absolute children that escape the scroll context — the fade overlays (::before/::after at z-index: 10) assume in-flow content underneath.\"\n  - \"Hardcoding scrollbar-color or ::-webkit-scrollbar-thumb hex values — theme via --color-fill-active / --color-fill-hover so dark mode adapts.\"\n  - \"Setting --scroll-max on the parent expecting cascade — the rule is .ren-scroll-area[--scroll-max] { max-height: var(--scroll-max) } so the variable must be authored on the same element.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic tokens used by the scrollbar and fade masks: --color-fill-active, --color-fill-hover, --color-surface.\"\n    - \"Layout / shape / motion tokens: --radius-full, --duration-enter, --ease-enter, --transition-tactile.\"\n    - \"Authoring custom max-height via the --scroll-max custom property on the .ren-scroll-area element.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / rgb() values for scrollbar thumb or fade-mask gradients.\"\n    - \"Inventing --ren-scroll-area-* tokens — none are part of the Public Token API today.\"\n\naccessibility:\n  required:\n    - \"If the scrollable region contains no focusable child, set tabindex=\\\"0\\\" on the .ren-scroll-area so keyboard users can scroll it with Arrow / PageUp / PageDown.\"\n    - \"Provide an accessible name (aria-label / aria-labelledby) when the region is a discrete content landmark; e.g. <section class=\\\"ren-scroll-area\\\" aria-label=\\\"Logs\\\">.\"\n    - \"Honor prefers-reduced-motion: the component disables scroll-behavior: smooth via the existing @media rule — do not re-enable smooth scrolling under reduced-motion.\"\n    - \"Do not rely on the fade-edge gradients alone to signal more content; pair with a visible affordance (chevron, scroll-hint) when content overflow is decision-critical.\"\n    - \"Scrollbar contrast: --color-fill-active and --color-fill-hover must remain distinguishable from --color-surface in both themes; do not theme so the thumb disappears.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-scroll-area/ren-scroll-area.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<section class=\"ren-scroll-area ren-scroll-area-md\" tabindex=\"0\" aria-label=\"Activity log\"><p>Activity entry</p></section>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-scroll-area`\n- `.ren-scroll-area-full`\n- `.ren-scroll-area-lg`\n- `.ren-scroll-area-md`\n- `.ren-scroll-area-sm`\n- `.ren-scroll-area-xl`\n\n## States And Attributes\n\n- `:hover`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/composites/ren-scroll-area/ren-scroll-area.css`\n- `docs/components/ren-scroll-area.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n.ren-scroll-area {\n  position: relative;\n  overflow: auto;\n\n  scrollbar-width: thin;\n  scrollbar-color: var(--color-fill-active) transparent;\n\n  transition: scrollbar-color var(--duration-enter) var(--ease-enter);\n\n  &::-webkit-scrollbar {\n    width: 8px;\n    height: 8px;\n  }\n\n  &::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background: var(--color-fill-active);\n    border-radius: var(--radius-full);\n    border: 2px solid transparent;\n    background-clip: content-box;\n\n    transition: var(--transition-tactile);\n  }\n\n  &::-webkit-scrollbar-thumb:hover {\n    background-color: var(--color-fill-hover);\n    background-clip: content-box;\n  }\n\n  &::-webkit-scrollbar-corner {\n    background: transparent;\n  }\n\n  /* Hover state for standard scrollbar */\n  &:hover {\n    scrollbar-color: var(--color-fill-hover) transparent;\n  }\n\n  /* Variants */\n  &.-auto {\n    scrollbar-width: none;\n\n    &::-webkit-scrollbar {\n      display: none;\n    }\n\n    /* Show on scroll */\n    &:is(:hover, :focus-within) {\n      scrollbar-width: thin;\n      scrollbar-color: var(--color-fill-active) transparent;\n\n      &::-webkit-scrollbar {\n        display: block;\n      }\n    }\n  }\n\n  &.-always {\n    scrollbar-width: thin;\n    scrollbar-color: var(--color-fill-active) transparent;\n\n    &::-webkit-scrollbar {\n      display: block;\n    }\n  }\n\n  &.-hidden {\n    scrollbar-width: none;\n\n    &::-webkit-scrollbar {\n      display: none;\n    }\n  }\n\n  /* Direction variants */\n  &.-x {\n    overflow-x: auto;\n    overflow-y: hidden;\n  }\n\n  &.-y {\n    overflow-x: hidden;\n    overflow-y: auto;\n  }\n\n  /* Fade edges for smooth content transitions */\n  &.-fade {\n    position: relative;\n\n    &::before {\n      content: '';\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      height: 20px;\n\n      pointer-events: none;\n      background: linear-gradient(\n        to bottom,\n        var(--color-surface) 0%,\n        transparent 100%\n      );\n\n      z-index: 10;\n    }\n\n    &::after {\n      content: '';\n      position: absolute;\n      bottom: 0;\n      left: 0;\n      right: 0;\n      height: 20px;\n\n      pointer-events: none;\n      background: linear-gradient(\n        to top,\n        var(--color-surface) 0%,\n        transparent 100%\n      );\n\n      z-index: 10;\n    }\n  }\n\n  /* Horizontal fade */\n  &.-fade-x {\n    position: relative;\n\n    &::before {\n      content: '';\n      position: absolute;\n      top: 0;\n      left: 0;\n      bottom: 0;\n      width: 20px;\n\n      pointer-events: none;\n      background: linear-gradient(\n        to right,\n        var(--color-surface) 0%,\n        transparent 100%\n      );\n\n      z-index: 10;\n    }\n\n    &::after {\n      content: '';\n      position: absolute;\n      top: 0;\n      right: 0;\n      bottom: 0;\n      width: 20px;\n\n      pointer-events: none;\n      background: linear-gradient(\n        to left,\n        var(--color-surface) 0%,\n        transparent 100%\n      );\n\n      z-index: 10;\n    }\n  }\n\n  /* Max-height with custom property */\n  &[--scroll-max] {\n    max-height: var(--scroll-max, 20rem);\n  }\n\n  /* Smooth scrolling behavior */\n  scroll-behavior: smooth;\n\n  @media (prefers-reduced-motion: reduce) {\n    scroll-behavior: auto;\n  }\n}\n\n/* Utility class for common heights */\n.ren-scroll-area-sm {\n  max-height: 12rem;\n}\n\n.ren-scroll-area-md {\n  max-height: 20rem;\n}\n\n.ren-scroll-area-lg {\n  max-height: 30rem;\n}\n\n.ren-scroll-area-xl {\n  max-height: 40rem;\n}\n\n.ren-scroll-area-full {\n  height: 100%;\n  max-height: none;\n}\n",
      "path": "components/composites/ren-scroll-area",
      "id": "component:composite:ren-scroll-area",
      "type": "component",
      "name": "ren-scroll-area"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-select",
          ".ren-select-chip",
          ".ren-select-chip-remove",
          ".ren-select-chips",
          ".ren-select-content",
          ".ren-select-empty",
          ".ren-select-group",
          ".ren-select-icon",
          ".ren-select-item",
          ".ren-select-label",
          ".ren-select-lg",
          ".ren-select-placeholder",
          ".ren-select-separator",
          ".ren-select-sm",
          ".ren-select-trigger",
          ".ren-select-value"
        ],
        "tokens": [
          "--body-size",
          "--caption-size",
          "--color-accent",
          "--color-accent-subtle",
          "--color-border",
          "--color-fill",
          "--color-input-bg",
          "--color-input-bg-hover",
          "--color-input-border",
          "--color-input-border-focus",
          "--color-input-disabled-bg",
          "--color-input-disabled-text",
          "--color-input-focus-ring",
          "--color-input-placeholder",
          "--color-surface",
          "--color-text",
          "--color-text-muted",
          "--duration-enter",
          "--duration-exit",
          "--duration-micro",
          "--ease-enter",
          "--ease-exit",
          "--radius-lg",
          "--radius-md",
          "--radius-sm",
          "--shadow-lg",
          "--size-sm",
          "--size-xl",
          "--space-1",
          "--space-2",
          "--space-3",
          "--space-4",
          "--stroke-1",
          "--text-lg",
          "--text-sm",
          "--touch-min",
          "--transition-tactile",
          "--z-dropdown"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-select Component Contract\n\nCustom select/listbox composite with trigger, options, and keyboard navigation.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-select` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-select` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Select composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a styled single- or multi-select with a button trigger and a listbox popup.\"\n    - \"You need full keyboard navigation (Arrow keys, Home/End, typeahead, Enter/Space, Escape).\"\n    - \"You need ARIA combobox/listbox semantics with aria-expanded, aria-selected, and data-highlighted.\"\n    - \"You need optional grouping (.ren-select-group + .ren-select-label) or separators between options.\"\n    - \"You need multi-select chips (.ren-select-chips, .ren-select-chip) with removal affordances.\"\n    - \"You need hidden form-input integration for submission alongside custom dropdown UI.\"\n    - \"The dropdown needs a preferred placement via placement=\\\"top|right|bottom|left\\\" while preserving viewport flip fallback.\"\n  avoidWhen:\n    - \"A bare <select> styled via base/primitive-zero would meet the requirements.\"\n    - \"The control is binary state — use ren-checkbox, ren-switch, or ren-toggle.\"\n    - \"The disclosure is a free-form menu of commands, not value selection — use ren-menu / ren-menubar.\"\n    - \"The disclosure is a navigation list — use ren-nav or ren-sidebar.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-select/ren-select.css\"\n  js:\n    - \"rends/components/composites/ren-select/ren-select.js\"\n  notes:\n    - \"JS is required: it owns keyboard nav, dismissable popup, popover positioning, and the hidden form input.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"<ren-select> wraps a real <button data-select-trigger> and a [data-select-content] listbox container.\"\n  - \"Each option is a <div data-select-item data-value=\\\"...\\\"> inside .ren-select-content; rely on data-select-item, not arbitrary children.\"\n  - \"The trigger displays .ren-select-value when something is chosen and .ren-select-placeholder when empty — do not collapse them into one node.\"\n  - \"Set name on <ren-select> when the value must submit with a form; the component injects the hidden input automatically.\"\n  - \"With multiple, value is an ordered array and the component injects one same-name hidden input per selected value so FormData preserves repeated entries. Removable chips render as a sibling of the button trigger so interactive controls are never nested.\"\n  - \"Use .ren-select-group + .ren-select-label for grouped options and .ren-select-separator between groups; do not invent dividers.\"\n  - \"Use placement=\\\"bottom\\\" by default; the host and .ren-select-content mirror the resolved side to data-side and data-align.\"\n\nforbiddenPatterns:\n  - \"Substituting a <div role=\\\"button\\\"> for the [data-select-trigger] <button> — the trigger must be a real button.\"\n  - \"Animating the dropdown manually with display: none / display: block — use the .ren-open class and the popover open state.\"\n  - \"Removing the focus ring on .ren-select-trigger:focus-visible without restoring an equivalent visible ring.\"\n  - \"Hardcoding option backgrounds via inline style — use --color-accent-subtle / --color-fill via the documented selectors.\"\n  - \"Putting interactive children (links, buttons) inside .ren-select-item; items must remain single-action options.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic input tokens: --color-input-bg, --color-input-bg-hover, --color-input-border, --color-input-border-focus, --color-input-focus-ring, --color-input-placeholder, --color-input-disabled-bg, --color-input-disabled-text.\"\n    - \"Surface and content tokens: --color-surface, --color-fill, --color-accent, --color-accent-subtle, --color-text, --color-text-muted, --color-border.\"\n    - \"Shape / motion tokens: --radius-sm, --radius-md, --radius-lg, --space-*, --shadow-lg, --duration-enter, --duration-exit, --duration-micro, --ease-enter, --ease-exit, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named color values in overrides.\"\n    - \"Hardcoded transition durations; use --duration-enter / --duration-exit / --duration-micro with their paired easings.\"\n\naccessibility:\n  required:\n    - \"Trigger exposes aria-expanded on open/close and aria-disabled when inert; native <button> Enter/Space activation must be preserved.\"\n    - \"Listbox container carries role=\\\"listbox\\\"; each option carries role=\\\"option\\\" and aria-selected reflects the selected value.\"\n    - \"Roving highlight on options uses [data-highlighted]; only one option may be highlighted at a time.\"\n    - \"Trigger meets the 44px touch target via min-height: var(--touch-min); .ren-select-sm is only acceptable in non-touch contexts.\"\n    - \"Escape closes the listbox and returns focus to the trigger; outside click dismisses without stealing focus elsewhere.\"\n    - \"Disabled state combines :disabled (or aria-disabled=\\\"true\\\") with pointer-events: none on items so click and keypress both no-op.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-select/ren-select.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-select/ren-select.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-select name=\"country\"><button type=\"button\" data-select-trigger><span class=\"ren-select-value\"></span><span class=\"ren-select-placeholder\">Choose a country</span></button><div class=\"ren-select-content\" data-select-content role=\"listbox\"><div data-select-item data-value=\"ar\">Argentina</div><div data-select-item data-value=\"uy\">Uruguay</div></div></ren-select>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-open`\n- `.ren-select`\n- `.ren-select-chip`\n- `.ren-select-chip-remove`\n- `.ren-select-chips`\n- `.ren-select-content`\n- `.ren-select-empty`\n- `.ren-select-group`\n- `.ren-select-icon`\n- `.ren-select-item`\n- `.ren-select-label`\n- `.ren-select-lg`\n- `.ren-select-placeholder`\n- `.ren-select-separator`\n- `.ren-select-sm`\n- `.ren-select-trigger`\n- `.ren-select-value`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[aria-expanded]`\n- `[aria-selected]`\n- `[data-highlighted]`\n- `[data-align]`\n- `[data-flipped]`\n- `[data-select-content]`\n- `[data-select-item]`\n- `[data-select-trigger]`\n- `[data-side]`\n- `placement`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## JavaScript API\n\n- `value` / `setValue(value)` use a string (or `null`) in single-select mode.\n- `value` / `setValue(values)` use an ordered array in `multiple` mode.\n- Multiple selection accumulates and removes values without closing the\n  listbox, renders `.ren-select-chip` entries, and submits repeated same-name\n  hidden inputs.\n- `aria-disabled=\"false\"` remains operable; only native `disabled` or\n  `aria-disabled=\"true\"` disables an option.\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-select/ren-select.css`\n- `components/composites/ren-select/ren-select.js`\n- `docs/components/ren-select.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ═══════════════════════════════════════════════════════════════════\n   RenDS — Select Component\n   ═══════════════════════════════════════════════════════════════════\n   Custom, accessible select/dropdown component with full keyboard nav,\n   ARIA support, and multi-select capability.\n\n   Structure:\n   .ren-select (container)\n     .ren-select-trigger (button showing selected value)\n       .ren-select-value (the displayed text)\n       .ren-select-placeholder (when nothing selected)\n       .ren-select-icon (chevron)\n     .ren-select-content (dropdown listbox)\n       .ren-select-item (option)\n       .ren-select-group (optional grouping)\n       .ren-select-label (group header)\n       .ren-select-separator (divider between groups)\n   ═══════════════════════════════════════════════════════════════════ */\n\n/* ═══ SELECT CONTAINER ═══ */\n.ren-select {\n  position: relative;\n  display: inline-flex;\n  flex-direction: column;\n  width: 100%;\n  font-family: inherit;\n}\n\n/* ═══ SELECT TRIGGER (Button) ═══ */\n.ren-select-trigger {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--space-2);\n\n  /* Input styling */\n  width: 100%;\n  min-height: var(--touch-min);\n  padding: var(--space-2) var(--space-3);\n  font-size: var(--body-size);\n  font-family: inherit;\n  color: var(--color-text);\n  background-color: var(--color-input-bg);\n  border: var(--stroke-1) solid var(--color-input-border);\n  border-radius: var(--radius-md);\n  cursor: pointer;\n  text-align: start;\n  white-space: nowrap;\n\n  /* Semantic preset keeps trigger hover/focus in sync with other\n     tactile inputs (button, checkbox, text field). */\n  transition: var(--transition-tactile);\n}\n\n.ren-select-trigger:hover:not(:disabled):not([aria-expanded=\"true\"]) {\n  background-color: var(--color-input-bg-hover);\n}\n\n.ren-select-trigger:focus-visible {\n  outline: none;\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n  background-color: var(--color-input-bg-hover);\n}\n\n.ren-select-trigger[aria-expanded=\"true\"] {\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n  background-color: var(--color-input-bg-hover);\n}\n\n.ren-select-trigger:disabled {\n  background-color: var(--color-input-disabled-bg);\n  color: var(--color-input-disabled-text);\n  cursor: not-allowed;\n  opacity: 0.7;\n}\n\n/* ═══ SELECT VALUE / PLACEHOLDER ═══ */\n.ren-select-value {\n  flex: 1;\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  color: var(--color-text);\n}\n\n.ren-select-placeholder {\n  color: var(--color-input-placeholder);\n  flex: 1;\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n/* ═══ SELECT ICON (Chevron) ═══ */\n.ren-select-icon {\n  flex-shrink: 0;\n  width: 1.25rem;\n  height: 1.25rem;\n  color: var(--color-text-muted);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  transition: transform var(--duration-micro) var(--ease-enter);\n  pointer-events: none;\n}\n\n.ren-select-trigger[aria-expanded=\"true\"] .ren-select-icon {\n  transform: rotate(180deg);\n}\n\n/* SVG chevron icon styling */\n.ren-select-icon svg {\n  width: 100%;\n  height: 100%;\n  stroke-width: 2;\n  stroke-linecap: round;\n  stroke-linejoin: round;\n}\n\n/* ═══ SELECT CONTENT (Dropdown Listbox) ═══ */\n.ren-select-content {\n  position: absolute;\n  top: 100%;\n  left: 0;\n  right: 0;\n  margin-top: var(--space-1);\n  background: var(--color-surface);\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  padding-block: var(--space-2);\n  max-height: 15rem;\n  overflow-y: auto;\n  overflow-x: hidden;\n  overscroll-behavior: contain;\n  z-index: var(--z-dropdown, 1000);\n\n  visibility: hidden;\n  opacity: 0;\n  transform: translateY(-4px) scale(0.98);\n  animation: ren-select-close var(--duration-exit) var(--ease-exit) forwards;\n}\n\n/* ═══ SELECT CONTENT OPEN STATE ═══ */\n.ren-select-content[popover]:popover-open,\n.ren-select-content.ren-open {\n  visibility: visible;\n  opacity: 1;\n  animation: ren-select-open var(--duration-enter) var(--ease-enter);\n}\n\n@keyframes ren-select-open {\n  from {\n    opacity: 0;\n    transform: translateY(-4px) scale(0.98);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0) scale(1);\n  }\n}\n\n@keyframes ren-select-close {\n  from {\n    opacity: 1;\n    transform: translateY(0) scale(1);\n  }\n  to {\n    opacity: 0;\n    transform: translateY(-4px) scale(0.98);\n  }\n}\n\n/* ═══ SELECT ITEM (Option) ═══ */\n.ren-select-item {\n  padding: var(--space-2) var(--space-3);\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  cursor: pointer;\n  color: var(--color-text);\n  border-radius: var(--radius-sm);\n  /* Micro-duration keeps option hover snappy; tokens/semantic/motion\n     collapses this to 0ms under reduced-motion. */\n  transition:\n    background-color var(--duration-micro) var(--ease-enter),\n    color var(--duration-micro) var(--ease-enter);\n  white-space: nowrap;\n  user-select: none;\n  min-height: 2rem;\n}\n\n.ren-select-item:hover:not([aria-disabled=\"true\"]),\n.ren-select-item[data-highlighted] {\n  background-color: var(--color-fill);\n  color: var(--color-text);\n}\n\n.ren-select-item[aria-selected=\"true\"] {\n  background-color: var(--color-accent-subtle);\n  color: var(--color-accent);\n  font-weight: 500;\n}\n\n.ren-select-item[aria-selected=\"true\"]::before {\n  content: '';\n  flex-shrink: 0;\n  width: 1.25rem;\n  height: 1.25rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  background: var(--color-accent);\n  border-radius: var(--radius-sm);\n  position: relative;\n}\n\n.ren-select-item[aria-selected=\"true\"]::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");\n  background-repeat: no-repeat;\n  background-position: center;\n  background-size: 70%;\n}\n\n.ren-select-item[aria-disabled=\"true\"] {\n  opacity: 0.5;\n  cursor: not-allowed;\n  pointer-events: none;\n}\n\n/* ═══ SELECT GROUP ═══ */\n.ren-select-group {\n  display: block;\n  padding-block: var(--space-1);\n}\n\n/* ═══ SELECT GROUP LABEL ═══ */\n.ren-select-label {\n  padding: var(--space-1) var(--space-3);\n  font-size: var(--caption-size);\n  font-weight: 600;\n  color: var(--color-text-muted);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  white-space: nowrap;\n  user-select: none;\n}\n\n/* ═══ SELECT SEPARATOR ═══ */\n.ren-select-separator {\n  height: var(--stroke-1);\n  background: var(--color-border);\n  margin-block: var(--space-1);\n}\n\n/* ═══ SELECT EMPTY STATE ═══ */\n.ren-select-empty {\n  padding: var(--space-3) var(--space-4);\n  text-align: center;\n  color: var(--color-text-muted);\n  font-size: var(--body-size);\n  white-space: nowrap;\n}\n\n/* ═══ SIZE VARIANTS ═══ */\n.ren-select-sm .ren-select-trigger {\n  min-height: var(--size-sm);\n  padding: var(--space-1) var(--space-2);\n  font-size: var(--text-sm);\n  border-radius: var(--radius-sm);\n}\n\n.ren-select-lg .ren-select-trigger {\n  min-height: var(--size-xl);\n  padding: var(--space-3) var(--space-4);\n  font-size: var(--text-lg);\n}\n\n/* ═══ MULTI-SELECT CHIPS/TAGS ═══ */\n.ren-select-chips {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: var(--space-1);\n  width: 100%;\n}\n\n.ren-select-chip {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-1);\n  padding: var(--space-1) var(--space-2);\n  background: var(--color-fill);\n  border-radius: var(--radius-sm);\n  font-size: var(--text-sm);\n  color: var(--color-text);\n  white-space: nowrap;\n}\n\n.ren-select-chip-remove {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 1rem;\n  height: 1rem;\n  cursor: pointer;\n  color: var(--color-text-muted);\n  transition: var(--transition-tactile);\n}\n\n.ren-select-chip-remove:hover {\n  color: var(--color-text);\n}\n\n/* ═══ REDUCED MOTION ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-select-trigger,\n  .ren-select-icon,\n  .ren-select-item {\n    transition: none;\n  }\n\n  .ren-select-content {\n    animation: none;\n    opacity: 0;\n  }\n\n  .ren-select-content.ren-open {\n    opacity: 1;\n  }\n}\n\n/* ═══ SCROLLBAR STYLING ═══ */\n.ren-select-content::-webkit-scrollbar {\n  width: 6px;\n}\n\n.ren-select-content::-webkit-scrollbar-track {\n  background: transparent;\n}\n\n.ren-select-content::-webkit-scrollbar-thumb {\n  background: var(--color-border);\n  border-radius: 3px;\n}\n\n.ren-select-content::-webkit-scrollbar-thumb:hover {\n  background: var(--color-text-muted);\n}\n\n/* ═══ NATIVE POPOVER API SUPPORT ═══ */\n@supports (selector(:popover-open)) {\n  .ren-select-content[popover] {\n    visibility: visible;\n    opacity: 0;\n    transition: opacity var(--duration-enter) var(--ease-enter),\n                overlay var(--duration-enter) var(--ease-enter) allow-discrete,\n                display var(--duration-enter) var(--ease-enter) allow-discrete;\n    animation: none;\n  }\n\n  @starting-style {\n    .ren-select-content[popover]:popover-open {\n      opacity: 0;\n    }\n  }\n\n  .ren-select-content[popover]:popover-open {\n    opacity: 1;\n  }\n}\n\n\n/**\n * RenDS — Select Component\n * ========================\n *\n * A custom, fully accessible select/dropdown component that extends native\n * form capabilities with keyboard navigation, ARIA support, grouping,\n * and multi-select capability.\n *\n * Features:\n * - Keyboard navigation (arrow keys, Enter, Space, Escape, Typeahead)\n * - Full ARIA support (combobox, listbox, role=\"option\")\n * - Click-outside dismissal with roving tabindex\n * - Optional multi-select mode\n * - Option groups and separators\n * - Form submission integration\n * - Preferred placement with viewport-aware positioning\n * - Fallback styling for native select elements\n * - Respects prefers-reduced-motion\n *\n * @example\n * <ren-select placeholder=\"Choose an option\" name=\"country\">\n *   <button data-select-trigger>Select a country</button>\n *   <div data-select-content>\n *     <div data-select-item data-value=\"us\">United States</div>\n *     <div data-select-item data-value=\"ca\">Canada</div>\n *   </div>\n * </ren-select>\n *\n * @fires ren-select-change - Dispatched when selection changes\n * @fires change - Standard change event for form submission\n */\n\nimport { createKeyboardNav } from '../../../utils/keyboard-nav.js';\nimport { createDismissable } from '../../../utils/dismissable.js';\nimport { autoId } from '../../../utils/id-generator.js';\n\nconst SELECT_SIDES = new Set(['top', 'right', 'bottom', 'left']);\nconst SELECT_ALIGNS = new Set(['start', 'end']);\n\nfunction normalizeSelectPlacement(value) {\n  const [sideValue, alignValue] = String(value || 'bottom')\n    .toLowerCase()\n    .split('-');\n  const side = SELECT_SIDES.has(sideValue) ? sideValue : 'bottom';\n  const align = SELECT_ALIGNS.has(alignValue) ? alignValue : 'start';\n\n  return { side, align };\n}\n\n/**\n * Compute dropdown position relative to trigger element.\n * Falls back above if not enough space below.\n *\n * @private\n * @param {HTMLElement} trigger - Trigger button element\n * @param {HTMLElement} content - Dropdown content element\n * @param {Object} placement - Normalized placement object\n * @returns {Object} Position with top, left, flipped, side, and align properties\n */\nfunction computePosition(trigger, content, placement = normalizeSelectPlacement()) {\n  const triggerRect = trigger.getBoundingClientRect();\n  const contentRect = content.getBoundingClientRect();\n  const viewport = {\n    width: window.innerWidth,\n    height: window.innerHeight,\n  };\n  const gap = 8;\n  const margin = 16;\n\n  let finalSide = placement.side;\n  let flipped = false;\n  let top = triggerRect.bottom + gap;\n  let left = triggerRect.left;\n\n  if (placement.align === 'end') {\n    left = triggerRect.right - contentRect.width;\n  }\n\n  if (placement.side === 'top') {\n    top = triggerRect.top - contentRect.height - gap;\n  } else if (placement.side === 'right') {\n    top = placement.align === 'end'\n      ? triggerRect.bottom - contentRect.height\n      : triggerRect.top;\n    left = triggerRect.right + gap;\n  } else if (placement.side === 'left') {\n    top = placement.align === 'end'\n      ? triggerRect.bottom - contentRect.height\n      : triggerRect.top;\n    left = triggerRect.left - contentRect.width - gap;\n  }\n\n  if (\n    placement.side === 'bottom' &&\n    top + contentRect.height > viewport.height - margin &&\n    triggerRect.top - contentRect.height - gap >= margin\n  ) {\n    top = triggerRect.top - contentRect.height - gap;\n    finalSide = 'top';\n    flipped = true;\n  } else if (\n    placement.side === 'top' &&\n    top < margin &&\n    triggerRect.bottom + contentRect.height + gap <= viewport.height - margin\n  ) {\n    top = triggerRect.bottom + gap;\n    finalSide = 'bottom';\n    flipped = true;\n  } else if (\n    placement.side === 'right' &&\n    left + contentRect.width > viewport.width - margin &&\n    triggerRect.left - contentRect.width - gap >= margin\n  ) {\n    left = triggerRect.left - contentRect.width - gap;\n    finalSide = 'left';\n    flipped = true;\n  } else if (\n    placement.side === 'left' &&\n    left < margin &&\n    triggerRect.right + contentRect.width + gap <= viewport.width - margin\n  ) {\n    left = triggerRect.right + gap;\n    finalSide = 'right';\n    flipped = true;\n  }\n\n  if (left + contentRect.width > viewport.width - margin) {\n    left = viewport.width - contentRect.width - margin;\n  }\n  if (left < margin) {\n    left = margin;\n  }\n\n  if (top + contentRect.height > viewport.height - margin) {\n    top = viewport.height - contentRect.height - margin;\n  }\n  if (top < margin) {\n    top = margin;\n  }\n\n  return { top, left, flipped, side: finalSide, align: placement.align };\n}\n\n/**\n * Select Component\n *\n * Custom, accessible select/dropdown with keyboard nav and ARIA support.\n *\n * @class RenSelect\n * @extends HTMLElement\n */\nexport class RenSelect extends HTMLElement {\n  static observedAttributes = ['placement'];\n\n  #trigger = null;\n  #content = null;\n  #items = [];\n  #isOpen = false;\n  #selectedValue = null;\n  #selectedItem = null;\n  #keyboardNav = null;\n  #dismissable = null;\n  #hiddenInputs = [];\n  #animationFrame = null;\n  #listenerController = null;\n  #scrollController = null;\n\n  constructor() {\n    super();\n    this.positionContent = this.positionContent.bind(this);\n  }\n\n  /* ─────────────────────────────────────────────────────────────\n     LIFECYCLE\n     ───────────────────────────────────────────────────────────── */\n\n  connectedCallback() {\n    if (this.hasAttribute('multiple') && !Array.isArray(this.#selectedValue)) {\n      this.#selectedValue = [];\n    }\n    this.loadStyles();\n    this.setupComponent();\n    this.bindElements();\n    this.setupARIA();\n    this.setupHiddenInput();\n    this.attachListeners();\n  }\n\n  disconnectedCallback() {\n    this.cleanup();\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'placement' && oldValue !== newValue) {\n      this.syncPlacement();\n      if (this.#isOpen) {\n        this.positionContent();\n      }\n    }\n  }\n\n  /* ─────────────────────────────────────────────────────────────\n     SETUP & INITIALIZATION\n     ───────────────────────────────────────────────────────────── */\n\n  /**\n   * Load component CSS styles into document\n   * @private\n   */\n  loadStyles() {\n    if (!document.getElementById('ren-select-styles')) {\n      const style = document.createElement('style');\n      style.id = 'ren-select-styles';\n      style.textContent = this.constructor.styles || '';\n      document.head.appendChild(style);\n    }\n  }\n\n  /**\n   * Setup container and trigger elements\n   * @private\n   */\n  setupComponent() {\n    this.classList.add('ren-select');\n    this.syncPlacement();\n\n    // Check for size variant\n    if (this.hasAttribute('size')) {\n      this.classList.add(`ren-select-${this.getAttribute('size')}`);\n    }\n  }\n\n  /**\n   * Find and cache trigger and content elements\n   * @private\n   */\n  bindElements() {\n    // Try to find explicitly marked elements\n    this.#trigger = this.querySelector('[data-select-trigger], .ren-select-trigger');\n    this.#content = this.querySelector('[data-select-content], .ren-select-content');\n\n    // If not found, look for first button and first div (fallback)\n    if (!this.#trigger) {\n      this.#trigger = this.querySelector('button');\n    }\n\n    if (!this.#content) {\n      this.#content = this.querySelector('[role=\"listbox\"]');\n    }\n\n    // If still not found, create default structure\n    if (!this.#trigger || !this.#content) {\n      this.createDefaultStructure();\n    }\n\n    // Cache items\n    this.updateItems();\n    this.syncPlacement();\n  }\n\n  /**\n   * Create default trigger and content if not provided\n   * @private\n   */\n  createDefaultStructure() {\n    // Create trigger button\n    if (!this.#trigger) {\n      this.#trigger = document.createElement('button');\n      this.#trigger.className = 'ren-select-trigger';\n      this.#trigger.type = 'button';\n      this.appendChild(this.#trigger);\n    }\n\n    // Create content container\n    if (!this.#content) {\n      this.#content = document.createElement('div');\n      this.#content.className = 'ren-select-content';\n      this.#content.setAttribute('role', 'listbox');\n      this.appendChild(this.#content);\n    }\n  }\n\n  /**\n   * Setup ARIA attributes on trigger and content\n   * @private\n   */\n  setupARIA() {\n    autoId(this.#trigger, 'select-trigger');\n    const contentId = autoId(this.#content, 'select-content');\n\n    // Trigger setup\n    this.#trigger.setAttribute('role', 'combobox');\n    this.#trigger.setAttribute('aria-expanded', 'false');\n    this.#trigger.setAttribute('aria-haspopup', 'listbox');\n    this.#trigger.setAttribute('aria-controls', contentId);\n    this.#trigger.type = 'button';\n\n    // Content setup\n    this.#content.setAttribute('role', 'listbox');\n    this.#content.id = contentId;\n    if (this.hasAttribute('multiple')) {\n      this.#content.setAttribute('aria-multiselectable', 'true');\n    }\n\n    // Set aria-label on content based on trigger text or placeholder\n    const placeholder = this.getAttribute('placeholder') || 'Select an option';\n    if (!this.#content.getAttribute('aria-label')) {\n      this.#content.setAttribute('aria-label', placeholder);\n    }\n\n    // Wire up items\n    this.updateItemsARIA();\n  }\n\n  /**\n   * Update items ARIA attributes\n   * @private\n   */\n  updateItemsARIA() {\n    this.#items.forEach((item, index) => {\n      if (item.hasAttribute('disabled') || item.getAttribute('aria-disabled') === 'true') {\n        item.setAttribute('aria-disabled', 'true');\n      }\n\n      if (!item.getAttribute('role')) {\n        item.setAttribute('role', 'option');\n      }\n\n      // Ensure item has tabindex\n      if (!item.hasAttribute('tabindex')) {\n        item.setAttribute('tabindex', '-1');\n      }\n\n      // Set aria-selected based on current selection\n      const value = item.getAttribute('data-value');\n      const isSelected = this.hasAttribute('multiple')\n        ? this.#selectedValue.includes(value)\n        : value === this.#selectedValue;\n      item.setAttribute('aria-selected', isSelected ? 'true' : 'false');\n    });\n  }\n\n  /**\n   * Setup hidden input for form submission\n   * @private\n   */\n  setupHiddenInput() {\n    const name = this.getAttribute('name');\n    if (!name) return;\n\n    this.syncHiddenInputs();\n  }\n\n  /**\n   * Attach event listeners to trigger and content\n   * @private\n   */\n  attachListeners() {\n    this.#listenerController?.abort();\n    this.#listenerController = new AbortController();\n    const { signal } = this.#listenerController;\n\n    // Trigger click\n    this.#trigger.addEventListener('click', (e) => this.handleTriggerClick(e), { signal });\n\n    // Keyboard on trigger\n    this.#trigger.addEventListener('keydown', (e) => this.handleTriggerKeyDown(e), { signal });\n\n    // Item selection\n    this.#content.addEventListener('click', (e) => this.handleItemClick(e), { signal });\n\n    // Setup keyboard navigation in content\n    this.setupKeyboardNav();\n\n    // Setup click-outside dismissal\n    this.setupDismissable();\n\n    // Initialize selected value\n    const initialValue = this.getAttribute('value');\n    if (initialValue) {\n      this.selectValue(initialValue, false);\n    }\n  }\n\n  /**\n   * Setup keyboard navigation for select items\n   * @private\n   */\n  setupKeyboardNav() {\n    this.#keyboardNav = createKeyboardNav(this.#content, {\n      selector: '[role=\"option\"]',\n      orientation: 'vertical',\n      loop: true,\n      typeahead: true,\n      focusOnHover: true,\n      onActivate: (item) => {\n        // Item is highlighted\n      },\n      onSelect: (item) => {\n        this.selectItem(item);\n      },\n    });\n\n    // Don't attach yet; will attach when dropdown opens\n  }\n\n  /**\n   * Setup click-outside and Escape key dismissal\n   * @private\n   */\n  setupDismissable() {\n    this.#dismissable = createDismissable(this.#content, {\n      triggerElement: this.#trigger,\n      escapeKey: true,\n      clickOutside: true,\n      onDismiss: (reason) => {\n        this.close();\n      },\n    });\n  }\n\n  /**\n   * Update the list of items from the content element\n   * @private\n   */\n  updateItems() {\n    this.#items = Array.from(\n      this.#content.querySelectorAll('[role=\"option\"], [data-select-item], .ren-select-item')\n    );\n  }\n\n  /* ─────────────────────────────────────────────────────────────\n     EVENT HANDLERS\n     ───────────────────────────────────────────────────────────── */\n\n  /**\n   * Handle trigger button click\n   * @private\n   */\n  handleTriggerClick(e) {\n    e.preventDefault();\n    e.stopPropagation();\n\n    if (this.#isOpen) {\n      this.close();\n    } else {\n      this.open();\n    }\n  }\n\n  /**\n   * Handle keyboard events on trigger\n   * @private\n   */\n  handleTriggerKeyDown(e) {\n    const isOpen = this.#isOpen;\n\n    switch (e.key) {\n      case 'Enter':\n      case ' ':\n        e.preventDefault();\n        e.stopPropagation();\n\n        if (!isOpen) {\n          this.open();\n        }\n        break;\n\n      case 'ArrowDown':\n        e.preventDefault();\n        e.stopPropagation();\n\n        if (!isOpen) {\n          this.open();\n          // Focus first item\n          setTimeout(() => {\n            this.#keyboardNav?.activateFirst();\n          }, 0);\n        } else {\n          this.#keyboardNav?.activateNext();\n        }\n        break;\n\n      case 'ArrowUp':\n        e.preventDefault();\n        e.stopPropagation();\n\n        if (!isOpen) {\n          this.open();\n          // Focus last item\n          setTimeout(() => {\n            this.#keyboardNav?.activateLast();\n          }, 0);\n        } else {\n          this.#keyboardNav?.activatePrevious();\n        }\n        break;\n\n      case 'Escape':\n        if (isOpen) {\n          e.preventDefault();\n          e.stopPropagation();\n          this.close();\n        }\n        break;\n\n      case 'ArrowLeft':\n      case 'ArrowRight':\n      case 'Tab':\n        // Don't intercept these; let default behavior\n        break;\n\n      default:\n        // Let other keys through\n        break;\n    }\n  }\n\n  /**\n   * Handle item click\n   * @private\n   */\n  handleItemClick(e) {\n    const item = e.target.closest('[role=\"option\"]');\n    if (item && !item.hasAttribute('disabled') && item.getAttribute('aria-disabled') !== 'true') {\n      e.preventDefault();\n      e.stopPropagation();\n      this.selectItem(item);\n    }\n  }\n\n  /* ─────────────────────────────────────────────────────────────\n     OPENING / CLOSING\n     ───────────────────────────────────────────────────────────── */\n\n  /**\n   * Open the dropdown\n   */\n  open() {\n    if (this.#isOpen || this.#trigger.disabled) return;\n\n    this.#isOpen = true;\n\n    // Show content\n    this.#content.classList.add('ren-open');\n    this.setAttribute('data-state', 'open');\n    this.#trigger.setAttribute('aria-expanded', 'true');\n\n    // Try native popover API\n    if ('popover' in HTMLElement.prototype && this.#content.hasAttribute('popover')) {\n      try {\n        this.#content.showPopover();\n      } catch {\n        // Fallback to CSS class\n      }\n    }\n\n    // Attach keyboard nav\n    if (this.#keyboardNav) {\n      this.#keyboardNav.attach();\n      // Activate selected or first item\n      const selectedIndex = this.#items.findIndex((item) => {\n        const value = item.getAttribute('data-value');\n        return this.hasAttribute('multiple')\n          ? this.#selectedValue.includes(value)\n          : value === this.#selectedValue;\n      });\n      if (selectedIndex !== -1) {\n        this.#keyboardNav.setActiveIndex(selectedIndex);\n      } else if (this.#items.length > 0) {\n        this.#keyboardNav.activateFirst();\n      }\n    }\n\n    // Activate dismissable layer\n    if (this.#dismissable) {\n      this.#dismissable.activate();\n    }\n\n    // Position content\n    this.positionContent();\n\n    // Reposition on scroll\n    this.#scrollController?.abort();\n    this.#scrollController = new AbortController();\n    window.addEventListener('scroll', this.positionContent, {\n      capture: true,\n      signal: this.#scrollController.signal,\n    });\n\n    // Announce results count for accessibility\n    const resultCount = this.#items.length;\n    const announcement = document.createElement('div');\n    announcement.setAttribute('role', 'status');\n    announcement.setAttribute('aria-live', 'polite');\n    announcement.className = 'ren-sr-only';\n    announcement.textContent = `${resultCount} option${resultCount !== 1 ? 's' : ''} available`;\n    this.appendChild(announcement);\n    setTimeout(() => announcement.remove(), 1000);\n\n    this.dispatchEvent(new CustomEvent('ren-select-open', { bubbles: true }));\n  }\n\n  /**\n   * Close the dropdown\n   */\n  close() {\n    if (!this.#isOpen) return;\n\n    this.#isOpen = false;\n\n    // Hide content\n    this.#content.classList.remove('ren-open');\n    this.setAttribute('data-state', 'closed');\n    this.#trigger.setAttribute('aria-expanded', 'false');\n\n    // Try native popover API\n    if ('popover' in HTMLElement.prototype && this.#content.hasAttribute('popover')) {\n      try {\n        this.#content.hidePopover();\n      } catch {\n        // Fallback\n      }\n    }\n\n    // Detach keyboard nav\n    if (this.#keyboardNav) {\n      this.#keyboardNav.detach();\n    }\n\n    // Deactivate dismissable layer\n    if (this.#dismissable) {\n      this.#dismissable.deactivate();\n    }\n\n    // Remove scroll listener\n    this.#scrollController?.abort();\n    this.#scrollController = null;\n\n    // Return focus to trigger\n    this.#trigger.focus();\n\n    this.dispatchEvent(new CustomEvent('ren-select-close', { bubbles: true }));\n  }\n\n  /**\n   * Position dropdown content below or above trigger\n   * @private\n   */\n  positionContent() {\n    const placement = normalizeSelectPlacement(this.getAttribute('placement'));\n    const { top, left, flipped, side, align } = computePosition(this.#trigger, this.#content, placement);\n\n    this.#content.style.position = 'fixed';\n    this.#content.style.top = `${top}px`;\n    this.#content.style.left = `${left}px`;\n    this.#content.style.width = `${this.#trigger.offsetWidth}px`;\n    this.#content.setAttribute('data-flipped', flipped ? 'true' : 'false');\n    this.#content.setAttribute('data-side', side);\n    this.#content.setAttribute('data-align', align);\n    this.setAttribute('data-side', side);\n    this.setAttribute('data-align', align);\n  }\n\n  /**\n   * Mirror preferred placement to public data attributes.\n   */\n  syncPlacement() {\n    const { side, align } = normalizeSelectPlacement(this.getAttribute('placement'));\n\n    this.setAttribute('data-side', side);\n    this.setAttribute('data-align', align);\n    if (this.#content) {\n      this.#content.setAttribute('data-side', side);\n      this.#content.setAttribute('data-align', align);\n    }\n  }\n\n  /* ─────────────────────────────────────────────────────────────\n     SELECTION\n     ───────────────────────────────────────────────────────────── */\n\n  /**\n   * Select an item by element\n   * @private\n   */\n  selectItem(item) {\n    const value = item.getAttribute('data-value');\n    const label = item.textContent?.trim() || '';\n\n    if (this.hasAttribute('multiple')) {\n      const values = [...this.#selectedValue];\n      const selectedIndex = values.indexOf(value);\n      if (selectedIndex === -1) values.push(value);\n      else values.splice(selectedIndex, 1);\n      this.selectValue(values, true);\n    } else {\n      this.selectValue(value, true);\n    }\n\n    this.dispatchEvent(\n      new CustomEvent('ren-select-change', {\n        bubbles: true,\n        detail: { value: this.value, label, item },\n      })\n    );\n\n    // Emit standard change event for forms\n    this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));\n\n    // Close dropdown (unless multi-select)\n    if (!this.hasAttribute('multiple')) {\n      this.close();\n    }\n  }\n\n  /**\n   * Select by value\n   * @private\n   */\n  selectValue(value, updateTrigger = true) {\n    if (this.hasAttribute('multiple')) {\n      const values = Array.isArray(value) ? value : (value == null ? [] : [value]);\n      this.#selectedValue = [...new Set(values.map(String))];\n    } else {\n      this.#selectedValue = value == null ? null : String(value);\n    }\n\n    // Find and update the corresponding item\n    const selectedValues = this.hasAttribute('multiple') ? this.#selectedValue : [this.#selectedValue];\n    const item = this.#items.find((el) => selectedValues.includes(el.getAttribute('data-value')));\n    if (item) {\n      this.#selectedItem = item;\n    } else {\n      this.#selectedItem = null;\n    }\n\n    // Update ARIA\n    this.updateItemsARIA();\n\n    // Update trigger display\n    if (updateTrigger) {\n      this.updateTriggerDisplay();\n    }\n\n    this.syncHiddenInputs();\n  }\n\n  syncHiddenInputs() {\n    const name = this.getAttribute('name');\n    this.querySelectorAll('input[type=\"hidden\"][data-ren-select-input]').forEach((input) => input.remove());\n    this.#hiddenInputs = [];\n    if (!name) return;\n\n    const values = this.hasAttribute('multiple')\n      ? this.#selectedValue\n      : [this.#selectedValue || ''];\n    values.forEach((value) => {\n      const input = document.createElement('input');\n      input.type = 'hidden';\n      input.name = name;\n      input.value = value;\n      input.setAttribute('data-ren-select-input', '');\n      this.appendChild(input);\n      this.#hiddenInputs.push(input);\n    });\n  }\n\n  /**\n   * Update the trigger button text/icon display\n   * @private\n   */\n  updateTriggerDisplay() {\n    const placeholder = this.getAttribute('placeholder') || 'Select an option';\n\n    this.querySelector(':scope > .ren-select-chips[data-ren-select-chips]')?.remove();\n\n    // Clear trigger content\n    this.#trigger.innerHTML = '';\n\n    if (this.hasAttribute('multiple') && this.#selectedValue.length > 0) {\n      const chips = document.createElement('span');\n      chips.className = 'ren-select-chips';\n      chips.setAttribute('data-ren-select-chips', '');\n      const selectedLabels = [];\n      this.#selectedValue.forEach((value) => {\n        const item = this.#items.find((option) => option.getAttribute('data-value') === value);\n        if (!item) return;\n\n        const chip = document.createElement('span');\n        chip.className = 'ren-select-chip';\n        const label = document.createElement('span');\n        label.textContent = item.textContent?.trim() || value;\n        selectedLabels.push(label.textContent);\n        const remove = document.createElement('button');\n        remove.type = 'button';\n        remove.className = 'ren-select-chip-remove';\n        remove.dataset.value = value;\n        remove.setAttribute('aria-label', `Remove ${label.textContent}`);\n        remove.textContent = '×';\n        remove.addEventListener('click', (event) => {\n          event.preventDefault();\n          event.stopPropagation();\n          this.selectValue(this.#selectedValue.filter((selected) => selected !== value), true);\n          this.dispatchEvent(new CustomEvent('ren-select-change', {\n            bubbles: true,\n            detail: { value: this.value, label: label.textContent, item },\n          }));\n          this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));\n        });\n        chip.append(label, remove);\n        chips.appendChild(chip);\n      });\n      this.insertBefore(chips, this.#trigger);\n\n      const valueSpan = document.createElement('span');\n      valueSpan.className = 'ren-select-value';\n      valueSpan.textContent = selectedLabels.join(', ');\n      this.#trigger.appendChild(valueSpan);\n    } else if (!this.#selectedValue || !this.#selectedItem) {\n      // Show placeholder\n      const placeholderSpan = document.createElement('span');\n      placeholderSpan.className = 'ren-select-placeholder';\n      placeholderSpan.textContent = placeholder;\n      this.#trigger.appendChild(placeholderSpan);\n    } else {\n      // Show selected value\n      const valueSpan = document.createElement('span');\n      valueSpan.className = 'ren-select-value';\n      valueSpan.textContent = this.#selectedItem.textContent?.trim() || '';\n      this.#trigger.appendChild(valueSpan);\n    }\n\n    // Add chevron icon\n    const icon = document.createElement('span');\n    icon.className = 'ren-select-icon';\n    icon.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"></polyline></svg>`;\n    this.#trigger.appendChild(icon);\n  }\n\n  /* ─────────────────────────────────────────────────────────────\n     CLEANUP\n     ───────────────────────────────────────────────────────────── */\n\n  /**\n   * Cleanup event listeners and resources\n   * @private\n   */\n  cleanup() {\n    if (this.#keyboardNav) {\n      this.#keyboardNav.detach();\n    }\n\n    if (this.#dismissable) {\n      this.#dismissable.deactivate();\n    }\n\n    if (this.#animationFrame) {\n      cancelAnimationFrame(this.#animationFrame);\n    }\n\n    this.#scrollController?.abort();\n    this.#scrollController = null;\n    this.#listenerController?.abort();\n    this.#listenerController = null;\n  }\n\n  /* ─────────────────────────────────────────────────────────────\n     PUBLIC API\n     ───────────────────────────────────────────────────────────── */\n\n  /**\n   * Get current selected value\n   * @returns {string|string[]|null} Selected value, ordered values, or null\n   */\n  get value() {\n    return Array.isArray(this.#selectedValue) ? [...this.#selectedValue] : this.#selectedValue;\n  }\n\n  /**\n   * Set value programmatically\n   * @param {string|string[]} value - Value or ordered values to select\n   */\n  set value(value) {\n    this.selectValue(value, true);\n  }\n\n  /**\n   * Get current selected item element\n   * @returns {HTMLElement|null}\n   */\n  get selectedOption() {\n    return this.#selectedItem || null;\n  }\n\n  /**\n   * Check if dropdown is open\n   * @returns {boolean}\n   */\n  get isOpen() {\n    return this.#isOpen;\n  }\n\n  /**\n   * Get all option items\n   * @returns {HTMLElement[]}\n   */\n  get options() {\n    return [...this.#items];\n  }\n\n  /**\n   * Refresh items from DOM (call after dynamically adding items)\n   */\n  refresh() {\n    this.updateItems();\n    this.updateItemsARIA();\n\n    if (this.#keyboardNav) {\n      this.#keyboardNav.refresh?.();\n    }\n  }\n}\n\n// Register the custom element\nif (!customElements.get('ren-select')) {\n  customElements.define('ren-select', RenSelect);\n}\n",
      "path": "components/composites/ren-select",
      "id": "component:composite:ren-select",
      "type": "component",
      "name": "ren-select"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-sheet",
          ".ren-sheet-body",
          ".ren-sheet-close",
          ".ren-sheet-description",
          ".ren-sheet-footer",
          ".ren-sheet-handle",
          ".ren-sheet-header",
          ".ren-sheet-title"
        ],
        "tokens": [
          "--color-border",
          "--color-fill",
          "--color-fill-active",
          "--color-fill-hover",
          "--color-overlay",
          "--color-surface",
          "--color-surface-overlay",
          "--color-text",
          "--color-text-muted",
          "--duration-enter",
          "--duration-exit",
          "--duration-normal",
          "--ease-enter",
          "--ease-exit",
          "--ease-out",
          "--radius-full",
          "--ren-sheet-backdrop",
          "--ren-sheet-bg",
          "--ren-sheet-border",
          "--ren-sheet-duration",
          "--ren-sheet-easing",
          "--ren-sheet-padding",
          "--ren-sheet-radius",
          "--ren-sheet-shadow",
          "--ren-sheet-width",
          "--shadow-xl",
          "--space-2",
          "--space-3",
          "--space-4",
          "--space-6",
          "--space-8",
          "--text-lg",
          "--text-sm",
          "--transition-tactile"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-sheet Component Contract\n\nEdge-attached dialog/surface for secondary workflows.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-sheet` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-sheet` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Sheet composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The disclosure is an edge-anchored panel (right / left / top / bottom) over the main app.\"\n    - \"You need the native <dialog> behavior — modal focus trap, ::backdrop, Escape to dismiss — on a side surface.\"\n    - \"You need a slide-in transition controlled by [data-side] with @starting-style fallback.\"\n    - \"You need swipe-to-dismiss on touch and outside-click / Escape dismissal.\"\n    - \"You need a mobile bottom sheet with a drag handle (.ren-sheet-handle).\"\n  avoidWhen:\n    - \"The disclosure should be centered and block the page — use ren-dialog.\"\n    - \"The panel is persistent app-shell navigation — use ren-sidebar.\"\n    - \"The disclosure is a small anchored popup — use ren-popover, ren-tooltip, or ren-menu.\"\n    - \"The notification is transient and must not steal focus — use ren-toast.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-sheet/ren-sheet.css\"\n  js:\n    - \"rends/components/composites/ren-sheet/ren-sheet.js\"\n  notes:\n    - \"JS is required: it wraps the consumer's children in a real <dialog class=\\\"ren-sheet\\\"> and wires triggers, swipe, and lifecycle events.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"<ren-sheet id=\\\"...\\\" side=\\\"right|left|top|bottom\\\"> is the host; do not replace the inner <dialog> the component generates with a <div>.\"\n  - \"Header / body / footer slots use .ren-sheet-header, .ren-sheet-body, .ren-sheet-footer in that order so the body grows and the footer pins to the bottom.\"\n  - \"Provide .ren-sheet-title as a real heading; it supplies the accessible name when no aria-label is set on <ren-sheet>.\"\n  - \"Close affordances use [data-sheet-close]; triggers elsewhere on the page use [data-sheet-trigger=\\\"<id>\\\"].\"\n  - \"Use size variants via the .-sm / .-md / .-lg / .-xl / .-full classes — do not inline width on the host.\"\n\nforbiddenPatterns:\n  - \"<ren-sheet> rendered as a <div role=\\\"dialog\\\"> — the component must use the real <dialog> it generates.\"\n  - \"Calling .show() / .close() on the inner <dialog> directly; always use the <ren-sheet> .show() / .close() methods.\"\n  - \"Hardcoded backdrop colors (rgba(0,0,0,...)); use --color-overlay via the component's ::backdrop rule.\"\n  - \"Animating slide-in with custom @keyframes; use the documented [data-side] + @starting-style + --duration-enter / --ease-enter pipeline.\"\n  - \"Toggling visibility via display: none / display: block — the [open] attribute drives the @starting-style translate.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-sheet-backdrop, --ren-sheet-bg, --ren-sheet-border, --ren-sheet-duration, --ren-sheet-easing, --ren-sheet-padding, --ren-sheet-radius, --ren-sheet-shadow, --ren-sheet-width.\"\n    - \"Semantic surface tokens: --color-surface, --color-text, --color-text-muted, --color-border, --color-overlay, --color-fill, --color-fill-active, --color-fill-hover.\"\n    - \"Spacing / motion tokens: --space-*, --radius-full, --duration-enter, --duration-exit, --ease-enter, --ease-exit, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named color values in overrides — even for the box-shadow on the leading edge (use semantic tokens or override --ren-sheet-shadow).\"\n    - \"Hardcoded transition durations; route through --duration-enter / --duration-exit and their paired easings.\"\n\naccessibility:\n  required:\n    - \"Modality is provided by the inner real <dialog> (focus trap + inert background); do not break the trap with manual focus moves.\"\n    - \".ren-sheet-title supplies the accessible label; if omitted, set aria-label on <ren-sheet>.\"\n    - \"Escape closes the sheet (unless dismissible=\\\"false\\\"); the close button [data-sheet-close] has a visible aria-label.\"\n    - \"Restore focus to the original trigger element after close (handled by the component via the returnFocus reference).\"\n    - \"Swipe-to-dismiss must coexist with keyboard dismissal; do not override touch-action on the dialog.\"\n    - \"Respect prefers-reduced-motion: the slide-in collapses to a plain fade automatically — do not reintroduce custom animations there.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-sheet/ren-sheet.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-sheet/ren-sheet.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<button type=\"button\" data-sheet-trigger=\"filters\">Open filters</button><ren-sheet id=\"filters\" side=\"right\"><header class=\"ren-sheet-header\"><h2 class=\"ren-sheet-title\">Filters</h2><button type=\"button\" data-sheet-close aria-label=\"Close\">×</button></header><div class=\"ren-sheet-body\">Filter controls</div><footer class=\"ren-sheet-footer\"><button type=\"button\" data-sheet-close=\"apply\">Apply</button></footer></ren-sheet>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-sheet`\n- `.ren-sheet-body`\n- `.ren-sheet-close`\n- `.ren-sheet-description`\n- `.ren-sheet-footer`\n- `.ren-sheet-handle`\n- `.ren-sheet-header`\n- `.ren-sheet-title`\n\n## States And Attributes\n\n- `[data-sheet-close]`\n- `[data-sheet-trigger]`\n- `[data-side]`\n- `:active`\n- `:hover`\n\n`close(returnValue)` and native `<form method=\"dialog\">` closure emit exactly\none `ren-close` with `detail.returnValue`, while clearing host `open` and\nrestoring focus to the opener.\n\n## Public Token API\n\n- `--ren-sheet-backdrop`\n- `--ren-sheet-bg`\n- `--ren-sheet-border`\n- `--ren-sheet-duration`\n- `--ren-sheet-easing`\n- `--ren-sheet-padding`\n- `--ren-sheet-radius`\n- `--ren-sheet-shadow`\n- `--ren-sheet-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-sheet/ren-sheet.css`\n- `components/composites/ren-sheet/ren-sheet.js`\n- `docs/components/ren-sheet.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* The custom-element host is structural only. Keeping it out of layout prevents\n   an empty grid/flex item around the real dialog generated in light DOM. */\nren-sheet {\n  display: contents;\n}\n\n/* Hide the dialog when it isn't open. The browser default for\n   `<dialog>` without `open` is `display: none`, but the rule below\n   sets `display: flex` for layout, so without this explicit reset\n   the sheet would render in-flow on every page load. */\n.ren-sheet:not([open]) {\n  display: none;\n}\n\n.ren-sheet {\n  position: fixed;\n  margin: 0;\n  padding: 0;\n  border: none;\n\n  background: var(--color-surface);\n  color: var(--color-text);\n  border-radius: 0;\n\n  /* Header/body/footer stack: body grows, footer pins to the bottom */\n  display: flex;\n  flex-direction: column;\n\n  z-index: 50;\n  max-width: 100%;\n  max-height: 100%;\n  touch-action: none; /* Prevent scroll chaining; swipe-to-dismiss handled by JS */\n\n  &[data-side='right'] {\n    inset-inline-end: 0;\n    top: 0;\n    bottom: 0;\n\n    width: min(24rem, 90vw);\n    height: 100dvh;\n\n    @starting-style {\n      translate: 100% 0;\n    }\n\n    translate: 0 0;\n  }\n\n  &[data-side='left'] {\n    inset-inline-start: 0;\n    top: 0;\n    bottom: 0;\n\n    width: min(24rem, 90vw);\n    height: 100dvh;\n\n    @starting-style {\n      translate: -100% 0;\n    }\n\n    translate: 0 0;\n  }\n\n  &[data-side='bottom'] {\n    bottom: 0;\n    inset-inline: 0;\n\n    width: 100vw;\n    max-height: 85dvh;\n\n    @starting-style {\n      translate: 0 100%;\n    }\n\n    translate: 0 0;\n  }\n\n  &[data-side='top'] {\n    top: 0;\n    inset-inline: 0;\n\n    width: 100vw;\n    max-height: 85dvh;\n\n    @starting-style {\n      translate: 0 -100%;\n    }\n\n    translate: 0 0;\n  }\n\n  /* Size variants */\n  &.-sm {\n    width: min(18rem, 90vw);\n  }\n\n  &.-md {\n    width: min(24rem, 90vw);\n  }\n\n  &.-lg {\n    width: min(32rem, 90vw);\n  }\n\n  &.-xl {\n    width: min(42rem, 90vw);\n  }\n\n  &.-full {\n    width: 100vw;\n    height: 100dvh;\n  }\n\n  /* Transitions — semantic tokens keep sheet in sync with ren-dialog and\n     friends; --duration-enter / --ease-enter collapse to 0ms under\n     prefers-reduced-motion via tokens/semantic/motion.css. */\n  transition: translate var(--duration-enter) var(--ease-enter),\n    opacity var(--duration-enter) var(--ease-enter);\n  transition-behavior: allow-discrete;\n\n  /* Reduce motion */\n  @media (prefers-reduced-motion: reduce) {\n    transition: none;\n\n    @starting-style {\n      opacity: 0;\n    }\n\n    opacity: 1;\n  }\n\n  /* Shadow on appropriate edge */\n  &[data-side='right'] {\n    box-shadow: -12px 0 32px rgb(0, 0, 0, 0.12);\n  }\n\n  &[data-side='left'] {\n    box-shadow: 12px 0 32px rgb(0, 0, 0, 0.12);\n  }\n\n  &[data-side='bottom'] {\n    box-shadow: 0 -12px 32px rgb(0, 0, 0, 0.12);\n  }\n\n  &[data-side='top'] {\n    box-shadow: 0 12px 32px rgb(0, 0, 0, 0.12);\n  }\n\n  /* Backdrop animations — enter uses --duration-enter / --ease-enter,\n     close uses the exit pair so the scrim fades at a slightly brisker\n     clip than the entry, matching the rest of the overlay family. */\n  &[open]::backdrop {\n    animation: ren-sheet-backdrop-open var(--duration-enter) var(--ease-enter) forwards;\n  }\n\n  &::backdrop {\n    @starting-style {\n      opacity: 0;\n    }\n\n    opacity: 1;\n    background: var(--color-overlay);\n    backdrop-filter: blur(4px);\n\n    animation: ren-sheet-backdrop-close var(--duration-exit) var(--ease-exit) forwards;\n  }\n\n  &:modal::backdrop {\n    background: var(--color-overlay);\n    backdrop-filter: blur(4px);\n  }\n}\n\n.ren-sheet-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n\n  padding: var(--space-6);\n  padding-block: var(--space-4);\n\n  border-bottom: 1px solid var(--color-border);\n}\n\n.ren-sheet-title {\n  font-size: var(--text-lg);\n  font-weight: 600;\n  color: var(--color-text);\n  margin: 0;\n}\n\n.ren-sheet-description {\n  font-size: var(--text-sm);\n  color: var(--color-text-muted);\n  margin: var(--space-2) 0 0 0;\n}\n\n.ren-sheet-close {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  width: var(--space-8);\n  height: var(--space-8);\n\n  padding: 0;\n  border: none;\n  background: transparent;\n  cursor: pointer;\n\n  font-size: 1.5rem;\n  color: var(--color-text-muted);\n\n  transition: var(--transition-tactile);\n\n  &:hover {\n    color: var(--color-text);\n  }\n\n  &:active {\n    color: var(--color-text-muted);\n  }\n}\n\n.ren-sheet-body {\n  flex: 1;\n  padding: var(--space-6);\n\n  overflow-y: auto;\n  overflow-x: hidden;\n\n  /* Scrollbar styling */\n  scrollbar-width: thin;\n  scrollbar-color: var(--color-fill-active) transparent;\n\n  &::-webkit-scrollbar {\n    width: 6px;\n  }\n\n  &::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background: var(--color-fill-active);\n    border-radius: var(--radius-full);\n  }\n\n  &::-webkit-scrollbar-thumb:hover {\n    background: var(--color-fill-hover);\n  }\n}\n\n.ren-sheet-footer {\n  display: flex;\n  gap: var(--space-3);\n\n  padding: var(--space-6);\n  padding-block: var(--space-4);\n\n  border-top: 1px solid var(--color-border);\n}\n\n.ren-sheet-handle {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  width: 100%;\n  height: var(--space-3);\n\n  margin-bottom: var(--space-4);\n\n  &::before {\n    content: '';\n    display: block;\n\n    width: 40px;\n    height: 4px;\n\n    background: var(--color-fill);\n    border-radius: var(--radius-full);\n  }\n}\n\n@keyframes ren-sheet-backdrop-open {\n  from {\n    opacity: 0;\n  }\n\n  to {\n    opacity: 1;\n  }\n}\n\n@keyframes ren-sheet-backdrop-close {\n  from {\n    opacity: 1;\n  }\n\n  to {\n    opacity: 0;\n  }\n}\n/* Appearance API bridge */\n.ren-sheet {\n  width: var(--ren-sheet-width, 320px);\n  padding: var(--ren-sheet-padding, var(--space-6));\n  border-radius: var(--ren-sheet-radius, 0);\n  background: var(--ren-sheet-bg, var(--color-surface-overlay));\n  border-color: var(--ren-sheet-border, var(--color-border));\n  box-shadow: var(--ren-sheet-shadow, var(--shadow-xl));\n  transition-duration: var(--ren-sheet-duration, var(--duration-normal));\n  transition-timing-function: var(--ren-sheet-easing, var(--ease-out));\n}\n.ren-sheet::backdrop { background: var(--ren-sheet-backdrop, var(--color-overlay)); }\n\n\n/**\n * RenDS — <ren-sheet> Web Component\n * ==================================\n * Edge-anchored modal panel. Light DOM only — wraps the consumer's children\n * in a native <dialog class=\"ren-sheet\"> so we get focus trap, ::backdrop,\n * Esc handling, and inert-on-body for free.\n *\n * Markup:\n *   <ren-sheet side=\"right\" id=\"filters\">\n *     <header class=\"ren-sheet-header\">\n *       <h2 class=\"ren-sheet-title\">Filters</h2>\n *       <button class=\"ren-sheet-close\" data-sheet-close aria-label=\"Close\">×</button>\n *     </header>\n *     <div class=\"ren-sheet-body\"> … </div>\n *     <footer class=\"ren-sheet-footer\"> … </footer>\n *   </ren-sheet>\n *\n *   <button data-sheet-trigger=\"filters\">Open</button>\n *\n * Attributes:\n *   side         — \"right\" | \"left\" | \"top\" | \"bottom\" (default \"right\")\n *   size         — \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\" (default \"md\")\n *   open         — reflects show()/close()\n *   dismissible  — if \"false\", only the close button dismisses\n *\n * Methods:\n *   .show()\n *   .close()\n *   .open  (getter)\n *\n * Events (bubble):\n *   ren-open\n *   ren-close\n *\n * Light-DOM hooks:\n *   [data-sheet-trigger=\"<id>\"]  anywhere on the page  → opens the sheet\n *   [data-sheet-close]           inside the sheet      → closes the sheet\n */\n\nimport { autoId } from '../../../utils/id-generator.js';\n\nexport class RenSheet extends HTMLElement {\n  static get observedAttributes() {\n    return ['side', 'size', 'data-side', 'data-size', 'open', 'dismissible'];\n  }\n\n  #dialog;\n  #titleId;\n  #upgraded = false;\n  #returnFocus = null;\n  #abortController = null;\n\n  // Touch swipe\n  #startX = 0;\n  #startY = 0;\n  #dragging = false;\n\n  connectedCallback() {\n    if (!this.#abortController || this.#abortController.signal.aborted) {\n      this.#abortController = new AbortController();\n    }\n    if (this.#upgraded) {\n      this.#wire();\n      return;\n    }\n    this.#upgraded = true;\n    autoId(this, 'sheet');\n    this.#enhance();\n    this.#wire();\n\n    // If consumer set open as initial attribute, show\n    if (this.hasAttribute('open')) {\n      // Defer to ensure dialog is connected\n      queueMicrotask(() => this.show());\n    }\n  }\n\n  disconnectedCallback() {\n    this.#dragging = false;\n    if (this.#dialog?.open) {\n      this.#dialog.close();\n      this.#reconcileNativeClose();\n    }\n    this.#abortController?.abort();\n  }\n\n  attributeChangedCallback(name, oldVal, newVal) {\n    if (!this.#upgraded || !this.#dialog) return;\n    if (name === 'side' || name === 'data-side') {\n      this.#dialog.setAttribute('data-side', this.#getSide());\n    } else if (name === 'size' || name === 'data-size') {\n      this.#syncSize();\n    } else if (name === 'open') {\n      const should = this.hasAttribute('open');\n      if (should && !this.#dialog.open) this.show();\n      if (!should && this.#dialog.open) this.close();\n    }\n  }\n\n  /* ─── Enhancement ─── */\n\n  #enhance() {\n    // Move existing children into a <dialog>\n    const fragment = document.createDocumentFragment();\n    while (this.firstChild) fragment.appendChild(this.firstChild);\n\n    this.#dialog = document.createElement('dialog');\n    this.#dialog.className = 'ren-sheet';\n    this.#dialog.setAttribute('role', 'dialog');\n    this.#dialog.setAttribute('data-side', this.#getSide());\n    this.#syncSize();\n\n    this.#dialog.appendChild(fragment);\n\n    // Wire aria-labelledby to a heading inside the sheet, if any\n    const heading =\n      this.#dialog.querySelector('.ren-sheet-title') ||\n      this.#dialog.querySelector('h1, h2, h3');\n    if (heading) {\n      this.#titleId = autoId(heading, 'sheet-title');\n      this.#dialog.setAttribute('aria-labelledby', this.#titleId);\n    }\n\n    this.appendChild(this.#dialog);\n  }\n\n  #getSide() {\n    return (\n      this.getAttribute('side') ||\n      this.getAttribute('data-side') ||\n      'right'\n    );\n  }\n\n  #getSize() {\n    return (\n      this.getAttribute('size') ||\n      this.getAttribute('data-size') ||\n      'md'\n    );\n  }\n\n  #syncSize() {\n    if (!this.#dialog) return;\n    // Drop any prior -sm/-md/-lg/-xl/-full classes\n    this.#dialog.classList.remove('-sm', '-md', '-lg', '-xl', '-full');\n    this.#dialog.classList.add(`-${this.#getSize()}`);\n  }\n\n  /* ─── Wiring ─── */\n\n  #wire() {\n    const { signal } = this.#abortController;\n    // Backdrop click: native <dialog> dispatches click on the dialog itself\n    // when the user clicks on the backdrop. e.target === dialog → it was the\n    // backdrop, not content inside.\n    this.#dialog.addEventListener('click', (e) => {\n      if (!this.#isDismissible()) return;\n      if (e.target === this.#dialog) this.close();\n    }, { signal });\n\n    // Esc — handled natively, but we intercept to honor dismissible=false\n    this.#dialog.addEventListener('cancel', (e) => {\n      if (!this.#isDismissible()) e.preventDefault();\n    }, { signal });\n\n    // Escape and <form method=\"dialog\"> close the native dialog directly.\n    // Reconcile the host and public lifecycle from the native close event.\n    this.#dialog.addEventListener('close', () => {\n      this.#reconcileNativeClose();\n    }, { signal });\n\n    // [data-sheet-close] anywhere inside\n    this.#dialog.addEventListener('click', (e) => {\n      const closer = e.target.closest('[data-sheet-close]');\n      if (closer && this.#dialog.contains(closer)) {\n        this.close(closer.getAttribute('data-sheet-close') || '');\n      }\n    }, { signal });\n\n    // Swipe to dismiss\n    this.#dialog.addEventListener('touchstart', (e) => {\n      if (!this.#isDismissible()) return;\n      this.#startX = e.touches[0].clientX;\n      this.#startY = e.touches[0].clientY;\n      this.#dragging = true;\n    }, { passive: true, signal });\n\n    this.#dialog.addEventListener('touchmove', (e) => {\n      if (!this.#dragging) return;\n      const dx = e.touches[0].clientX - this.#startX;\n      const dy = e.touches[0].clientY - this.#startY;\n      const side = this.#getSide();\n      const threshold = 50;\n      if (\n        (side === 'right' && dx > threshold) ||\n        (side === 'left' && dx < -threshold) ||\n        (side === 'bottom' && dy > threshold) ||\n        (side === 'top' && dy < -threshold)\n      ) {\n        this.#dragging = false;\n        this.close();\n      }\n    }, { passive: true, signal });\n\n    this.#dialog.addEventListener('touchend', () => {\n      this.#dragging = false;\n    }, { signal });\n  }\n\n  #isDismissible() {\n    return this.getAttribute('dismissible') !== 'false';\n  }\n\n  /* ─── Public API ─── */\n\n  show() {\n    if (!this.#dialog || this.#dialog.open) return;\n    this.#returnFocus = document.activeElement;\n    this.#dialog.showModal();\n    this.setAttribute('open', '');\n\n    // Focus the first usable element inside, or the close button\n    const focusable =\n      this.#dialog.querySelector('[autofocus]') ||\n      this.#dialog.querySelector(\n        'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n      );\n    if (focusable) {\n      focusable.focus({ preventScroll: true });\n    }\n\n    this.dispatchEvent(new CustomEvent('ren-open', { bubbles: true }));\n  }\n\n  close(returnValue = '') {\n    if (!this.#dialog || !this.#dialog.open) return;\n    this.#dialog.close(returnValue);\n  }\n\n  #reconcileNativeClose() {\n    this.removeAttribute('open');\n\n    // Restore focus to the element that opened the sheet\n    if (this.#returnFocus && document.contains(this.#returnFocus)) {\n      this.#returnFocus.focus({ preventScroll: true });\n    }\n    this.#returnFocus = null;\n\n    this.dispatchEvent(new CustomEvent('ren-close', {\n      bubbles: true,\n      composed: true,\n      detail: { returnValue: this.#dialog.returnValue || '' },\n    }));\n  }\n\n  get open() {\n    return !!(this.#dialog && this.#dialog.open);\n  }\n}\n\nif (!customElements.get('ren-sheet')) {\n  customElements.define('ren-sheet', RenSheet);\n}\n\n/* ─── Global trigger delegation ─── */\n/**\n * <button data-sheet-trigger=\"some-id\"> opens <ren-sheet id=\"some-id\">.\n *\n * One global listener handles every trigger on the page so consumers don't\n * need any wiring of their own. Idempotent across module re-imports.\n */\nif (typeof window !== 'undefined' && !window.__renSheetTriggerWired) {\n  window.__renSheetTriggerWired = true;\n  document.addEventListener('click', (e) => {\n    const trigger = e.target.closest('[data-sheet-trigger]');\n    if (!trigger) return;\n    const targetId = trigger.getAttribute('data-sheet-trigger');\n    if (!targetId) return;\n    const sheet = document.getElementById(targetId);\n    if (sheet && typeof sheet.show === 'function') {\n      e.preventDefault();\n      sheet.show();\n    }\n  });\n}\n",
      "path": "components/composites/ren-sheet",
      "id": "component:composite:ren-sheet",
      "type": "component",
      "name": "ren-sheet"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-slider",
          ".ren-slider-danger",
          ".ren-slider-label",
          ".ren-slider-lg",
          ".ren-slider-mark",
          ".ren-slider-mark-label",
          ".ren-slider-mark-tick",
          ".ren-slider-marks",
          ".ren-slider-range",
          ".ren-slider-sm",
          ".ren-slider-success",
          ".ren-slider-track",
          ".ren-slider-track-input",
          ".ren-slider-value",
          ".ren-slider-vertical",
          ".ren-slider-warning"
        ],
        "tokens": [
          "--color-accent",
          "--color-danger",
          "--color-fill-active",
          "--color-fill-subtle",
          "--color-success",
          "--color-surface",
          "--color-text",
          "--color-text-muted",
          "--color-warning",
          "--duration-fast",
          "--ease-out",
          "--font-size-body-sm",
          "--font-size-label",
          "--font-size-xs",
          "--radius-full",
          "--ren-slider-duration",
          "--ren-slider-easing",
          "--ren-slider-fill-color",
          "--ren-slider-thumb-bg",
          "--ren-slider-thumb-border",
          "--ren-slider-thumb-shadow",
          "--ren-slider-thumb-size",
          "--ren-slider-track-bg",
          "--ren-slider-track-height",
          "--ren-slider-track-radius",
          "--shadow-sm",
          "--space-0-5",
          "--space-1",
          "--transition-tactile",
          "--value",
          "--value-end",
          "--value-start"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-slider Component Contract\n\nRange input composite for numeric adjustment.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-slider` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-slider` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Slider composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User needs to pick a continuous numeric value within a known min/max range (volume, opacity, brightness, percentage).\"\n    - \"You need a single-thumb slider or a dual-thumb range slider (.ren-slider-range) with two inputs.\"\n    - \"You need a visible value readout (.ren-slider-value via show-value attribute) and/or tick marks (.ren-slider-marks).\"\n    - \"You need semantic color variants for status: .ren-slider-success / .ren-slider-warning / .ren-slider-danger.\"\n    - \"You need a size variant (.ren-slider-sm / .ren-slider-lg) or a vertical orientation (.ren-slider-vertical).\"\n    - \"Native <input type=\\\"range\\\"> ARIA and keyboard support is sufficient (Arrow keys, Home/End, PageUp/PageDown).\"\n  avoidWhen:\n    - \"The choice is from a discrete enumerated list — use ren-select, ren-radio-group, or ren-toggle-group.\"\n    - \"The value is text, number-typed, or formatted (currency, date) — use ren-field with a typed input.\"\n    - \"The control toggles binary state — use ren-switch, ren-checkbox, or ren-toggle.\"\n    - \"You need rating stars — use ren-rating.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-slider/ren-slider.css\"\n  js:\n    - \"rends/components/composites/ren-slider/ren-slider.js\"\n  notes:\n    - \"JS is required to compute the --value percentage CSS variable that paints the track fill; without it the gradient stays at 0%.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"<ren-slider> wraps a real <input type=\\\"range\\\"> with min / max / step / value set as attributes — never style a <div> as a slider.\"\n  - \"Pair the input with a real <label for=\\\"...\\\"> or set aria-label / aria-labelledby on the input for an accessible name.\"\n  - \"Use show-value attribute on <ren-slider> to opt into the .ren-slider-value readout; use label attribute to inject a .ren-slider-label.\"\n  - \"Dual-thumb range mode wraps two <input type=\\\"range\\\"> elements inside .ren-slider-range with a .ren-slider-track-input visual rail.\"\n  - \"In dual-thumb mode the value property and ren-slider-input / ren-slider-change detail.value are a two-number tuple in input order.\"\n  - \"Tick marks use .ren-slider-marks > .ren-slider-mark > (.ren-slider-mark-tick + .ren-slider-mark-label); preserve that structure.\"\n\nforbiddenPatterns:\n  - \"Replacing <input type=\\\"range\\\"> with a <div> + JS drag — accessibility and form submission depend on the native input.\"\n  - \"Setting the track fill via inline background gradients; the JS updates --value and the CSS handles the gradient automatically.\"\n  - \"Hardcoding thumb colors (background: #fff / blue) on ::-webkit-slider-thumb or ::-moz-range-thumb — use --color-accent or the .ren-slider-success/warning/danger variants.\"\n  - \"Removing the focus-visible outline on ::-webkit-slider-thumb / ::-moz-range-thumb without restoring an equivalent visible focus indicator.\"\n  - \"Stripping aria-valuemin / aria-valuemax / aria-valuenow (native input provides these — do not block them via role overrides).\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-slider-duration, --ren-slider-easing, --ren-slider-fill-color, --ren-slider-thumb-bg, --ren-slider-thumb-border, --ren-slider-thumb-shadow, --ren-slider-thumb-size, --ren-slider-track-bg, --ren-slider-track-height, --ren-slider-track-radius.\"\n    - \"Semantic state tokens: --color-accent, --color-success, --color-warning, --color-danger, --color-fill-active, --color-fill-subtle, --color-text, --color-text-muted.\"\n    - \"Shape / motion tokens: --radius-full, --space-*, --shadow-sm, --transition-tactile, font-size tokens (--font-size-label, --font-size-body, --font-size-xs).\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named color values in overrides — including the white thumb border (keep it as a token override if you need to change it).\"\n    - \"Hardcoded transition durations on the thumb hover/active scale; route through --transition-tactile.\"\n\naccessibility:\n  required:\n    - \"Use a real <input type=\\\"range\\\"> so native ARIA (role=slider, aria-valuemin/max/now) and keyboard (Arrow, Home/End, PageUp/Down) work.\"\n    - \"Visible focus ring on the thumb via outline: 2px solid var(--color-accent) with outline-offset; never set outline: none without an equivalent.\"\n    - \"Provide an accessible name via <label for> or aria-label; the .ren-slider-label class alone does not associate the label.\"\n    - \"Color variants must not be the only signal of state — accompany .ren-slider-danger with text or an icon for non-color cues.\"\n    - \"Disabled state uses :disabled on the input plus cursor: not-allowed; opacity changes alone are not sufficient on their own for state.\"\n    - \"Touch / drag interactions must not block keyboard nudges — both must drive the same change/input events the component dispatches.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-slider/ren-slider.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-slider/ren-slider.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<label for=\"volume\">Volume</label><ren-slider show-value><div class=\"ren-slider-track\"><input id=\"volume\" type=\"range\" min=\"0\" max=\"100\" step=\"1\" value=\"50\"></div></ren-slider>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-slider`\n- `.ren-slider-danger`\n- `.ren-slider-label`\n- `.ren-slider-lg`\n- `.ren-slider-mark`\n- `.ren-slider-mark-label`\n- `.ren-slider-mark-tick`\n- `.ren-slider-marks`\n- `.ren-slider-range`\n- `.ren-slider-sm`\n- `.ren-slider-success`\n- `.ren-slider-track`\n- `.ren-slider-track-input`\n- `.ren-slider-value`\n- `.ren-slider-vertical`\n- `.ren-slider-warning`\n\n## States And Attributes\n\n- `:active`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n- `type=\"range\"`\n\n## Public Token API\n\n- `--ren-slider-duration`\n- `--ren-slider-easing`\n- `--ren-slider-fill-color`\n- `--ren-slider-thumb-bg`\n- `--ren-slider-thumb-border`\n- `--ren-slider-thumb-shadow`\n- `--ren-slider-thumb-size`\n- `--ren-slider-track-bg`\n- `--ren-slider-track-height`\n- `--ren-slider-track-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-slider/ren-slider.css`\n- `components/composites/ren-slider/ren-slider.js`\n- `docs/components/ren-slider.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ═══ REN SLIDER COMPONENT ═══ */\n\n/* ═══ BASE WRAPPER ═══ */\n.ren-slider {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-1);\n}\n\n/* ═══ TRACK CONTAINER ═══ */\n.ren-slider-track {\n  position: relative;\n  display: flex;\n  align-items: center;\n  width: 100%;\n  height: 20px;\n  cursor: pointer;\n}\n\n/* ═══ RANGE INPUT STYLING ═══ */\n.ren-slider-track input[type=\"range\"] {\n  appearance: none;\n  -webkit-appearance: none;\n  width: 100%;\n  height: 6px;\n  border-radius: var(--radius-full);\n  background: var(--color-fill-active);\n  outline: none;\n  margin: 0;\n  padding: 0;\n\n  /* ═══ TRACK FILL WITH CSS VARIABLE ═══ */\n  background: linear-gradient(\n    to right,\n    var(--color-accent) 0%,\n    var(--color-accent) var(--value, 0%),\n    var(--color-fill-active) var(--value, 0%),\n    var(--color-fill-active) 100%\n  );\n\n  & ::-webkit-slider-thumb {\n    appearance: none;\n    -webkit-appearance: none;\n    width: 20px;\n    height: 20px;\n    border-radius: 50%;\n    background: var(--color-accent);\n    border: 2px solid white;\n    box-shadow: var(--shadow-sm);\n    cursor: pointer;\n    transition: var(--transition-tactile);\n\n    &:hover {\n      transform: scale(1.1);\n    }\n\n    &:active {\n      transform: scale(0.95);\n    }\n  }\n\n  & ::-moz-range-thumb {\n    width: 20px;\n    height: 20px;\n    border-radius: 50%;\n    background: var(--color-accent);\n    border: 2px solid white;\n    box-shadow: var(--shadow-sm);\n    cursor: pointer;\n    transition: var(--transition-tactile);\n\n    &:hover {\n      transform: scale(1.1);\n    }\n\n    &:active {\n      transform: scale(0.95);\n    }\n  }\n\n  & ::-webkit-slider-thumb:focus-visible,\n  & ::-moz-range-thumb:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  &:disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n\n    & ::-webkit-slider-thumb,\n    & ::-moz-range-thumb {\n      cursor: not-allowed;\n    }\n  }\n}\n\n/* ═══ LABEL ═══ */\n.ren-slider-label {\n  font-size: var(--font-size-label);\n  font-weight: 500;\n  color: var(--color-text);\n}\n\n/* ═══ VALUE DISPLAY ═══ */\n.ren-slider-value {\n  font-size: var(--font-size-body-sm);\n  color: var(--color-text-muted);\n  font-variant-numeric: tabular-nums;\n}\n\n/* ═══ TICK MARKS ═══ */\n.ren-slider-marks {\n  display: flex;\n  justify-content: space-between;\n  gap: 0;\n  width: 100%;\n  margin-top: var(--space-1);\n\n  & .ren-slider-mark {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    flex: 0 0 auto;\n\n    & .ren-slider-mark-tick {\n      width: 2px;\n      height: 6px;\n      background: var(--color-fill-subtle);\n      border-radius: 1px;\n      margin-bottom: var(--space-0-5);\n    }\n\n    & .ren-slider-mark-label {\n      font-size: var(--font-size-xs);\n      color: var(--color-text-muted);\n    }\n  }\n}\n\n/* ═══ SIZE VARIANTS ═══ */\n.ren-slider-track {\n  &.ren-slider-sm input[type=\"range\"] {\n    height: 4px;\n    background: linear-gradient(\n      to right,\n      var(--color-accent) 0%,\n      var(--color-accent) var(--value, 0%),\n      var(--color-fill-active) var(--value, 0%),\n      var(--color-fill-active) 100%\n    );\n\n    & ::-webkit-slider-thumb {\n      width: 16px;\n      height: 16px;\n    }\n\n    & ::-moz-range-thumb {\n      width: 16px;\n      height: 16px;\n    }\n  }\n\n  &.ren-slider-lg input[type=\"range\"] {\n    height: 8px;\n    background: linear-gradient(\n      to right,\n      var(--color-accent) 0%,\n      var(--color-accent) var(--value, 0%),\n      var(--color-fill-active) var(--value, 0%),\n      var(--color-fill-active) 100%\n    );\n\n    & ::-webkit-slider-thumb {\n      width: 24px;\n      height: 24px;\n    }\n\n    & ::-moz-range-thumb {\n      width: 24px;\n      height: 24px;\n    }\n  }\n}\n\n/* ═══ COLOR VARIANTS ═══ */\n.ren-slider-track {\n  &.ren-slider-success input[type=\"range\"] {\n    background: linear-gradient(\n      to right,\n      var(--color-success) 0%,\n      var(--color-success) var(--value, 0%),\n      var(--color-fill-active) var(--value, 0%),\n      var(--color-fill-active) 100%\n    );\n\n    & ::-webkit-slider-thumb {\n      background: var(--color-success);\n    }\n\n    & ::-moz-range-thumb {\n      background: var(--color-success);\n    }\n  }\n\n  &.ren-slider-warning input[type=\"range\"] {\n    background: linear-gradient(\n      to right,\n      var(--color-warning) 0%,\n      var(--color-warning) var(--value, 0%),\n      var(--color-fill-active) var(--value, 0%),\n      var(--color-fill-active) 100%\n    );\n\n    & ::-webkit-slider-thumb {\n      background: var(--color-warning);\n    }\n\n    & ::-moz-range-thumb {\n      background: var(--color-warning);\n    }\n  }\n\n  &.ren-slider-danger input[type=\"range\"] {\n    background: linear-gradient(\n      to right,\n      var(--color-danger) 0%,\n      var(--color-danger) var(--value, 0%),\n      var(--color-fill-active) var(--value, 0%),\n      var(--color-fill-active) 100%\n    );\n\n    & ::-webkit-slider-thumb {\n      background: var(--color-danger);\n    }\n\n    & ::-moz-range-thumb {\n      background: var(--color-danger);\n    }\n  }\n}\n\n/* ═══ VERTICAL ORIENTATION ═══ */\n.ren-slider-track {\n  &.ren-slider-vertical {\n    width: auto;\n    height: 200px;\n    writing-mode: vertical-lr;\n\n    & input[type=\"range\"] {\n      width: auto;\n      height: 200px;\n      writing-mode: vertical-lr;\n      direction: rtl;\n    }\n  }\n}\n\n/* ═══ RANGE MODE (DUAL THUMBS) ═══ */\n.ren-slider-range {\n  position: relative;\n  display: flex;\n  align-items: center;\n  width: 100%;\n  height: 20px;\n  cursor: pointer;\n\n  & .ren-slider-track-input {\n    position: absolute;\n    width: 100%;\n    height: 6px;\n    border-radius: var(--radius-full);\n    background: linear-gradient(\n      to right,\n      var(--color-fill-active) 0 var(--value-start, 0%),\n      var(--ren-slider-fill-color, var(--color-accent)) var(--value-start, 0%) var(--value-end, 100%),\n      var(--color-fill-active) var(--value-end, 100%) 100%\n    );\n    pointer-events: none;\n    margin: 0;\n    padding: 0;\n  }\n\n  & input[type=\"range\"] {\n    position: absolute;\n    inset: 0;\n    width: 100%;\n    z-index: 5;\n    margin: 0;\n    padding: 0;\n    appearance: none;\n    -webkit-appearance: none;\n    background: none;\n    height: 20px;\n    border-radius: var(--radius-full);\n    outline: none;\n    pointer-events: none;\n\n    &::-webkit-slider-thumb {\n      appearance: none;\n      -webkit-appearance: none;\n      width: 20px;\n      height: 20px;\n      border-radius: 50%;\n      background: var(--color-accent);\n      border: 2px solid white;\n      box-shadow: var(--shadow-sm);\n      cursor: pointer;\n      transition: var(--transition-tactile);\n      z-index: 6;\n      pointer-events: auto;\n\n      &:hover {\n        transform: scale(1.1);\n      }\n\n      &:active {\n        transform: scale(0.95);\n      }\n    }\n\n    &::-moz-range-thumb {\n      width: 20px;\n      height: 20px;\n      border-radius: 50%;\n      background: var(--color-accent);\n      border: 2px solid white;\n      box-shadow: var(--shadow-sm);\n      cursor: pointer;\n      transition: var(--transition-tactile);\n      z-index: 6;\n      pointer-events: auto;\n\n      &:hover {\n        transform: scale(1.1);\n      }\n\n      &:active {\n        transform: scale(0.95);\n      }\n    }\n  }\n}\n\n/* ═══ REDUCED MOTION ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-slider,\n  .ren-slider * {\n    animation-duration: 0ms !important;\n    transition-duration: 0ms !important;\n  }\n}\n/* Appearance API bridge; pseudo-elements intentionally have no selector whitespace. */\n.ren-slider-track input[type=\"range\"] {\n  block-size: var(--ren-slider-track-height, 6px);\n  border-radius: var(--ren-slider-track-radius, var(--radius-full));\n  background: var(--ren-slider-track-bg, var(--color-fill-active));\n  transition-duration: var(--ren-slider-duration, var(--duration-fast));\n  transition-timing-function: var(--ren-slider-easing, var(--ease-out));\n}\n.ren-slider-track input[type=\"range\"]::-webkit-slider-thumb { inline-size: var(--ren-slider-thumb-size, 20px); block-size: var(--ren-slider-thumb-size, 20px); background: var(--ren-slider-thumb-bg, var(--color-surface)); border-color: var(--ren-slider-thumb-border, var(--color-accent)); box-shadow: var(--ren-slider-thumb-shadow, var(--shadow-sm)); }\n.ren-slider-track input[type=\"range\"]::-moz-range-thumb { inline-size: var(--ren-slider-thumb-size, 20px); block-size: var(--ren-slider-thumb-size, 20px); background: var(--ren-slider-thumb-bg, var(--color-surface)); border-color: var(--ren-slider-thumb-border, var(--color-accent)); box-shadow: var(--ren-slider-thumb-shadow, var(--shadow-sm)); }\n.ren-slider-track input[type=\"range\"] { background-image: linear-gradient(to right, var(--ren-slider-fill-color, var(--color-accent)) 0%, var(--ren-slider-fill-color, var(--color-accent)) var(--value, 0%), var(--ren-slider-track-bg, var(--color-fill-active)) var(--value, 0%), var(--ren-slider-track-bg, var(--color-fill-active)) 100%); }\n\n\n/* ═══ REN SLIDER WEB COMPONENT ═══ */\n\nexport class RenSlider extends HTMLElement {\n  constructor() {\n    super();\n    this.handleInput = this.handleInput.bind(this);\n    this.handleChange = this.handleChange.bind(this);\n    this.handleRangePointerDown = this.handleRangePointerDown.bind(this);\n    this.handleRangePointerMove = this.handleRangePointerMove.bind(this);\n    this.handleRangePointerEnd = this.handleRangePointerEnd.bind(this);\n  }\n\n  connectedCallback() {\n    this.inputs = [...this.querySelectorAll('input[type=\"range\"]')];\n    this.input = this.inputs[0] || null;\n\n    if (!this.input) {\n      console.warn('RenSlider: No input[type=\"range\"] found');\n      return;\n    }\n\n    /* ═══ SET UP TRACK CLASS ═══ */\n    const trackContainer = this.input.parentElement || this.input;\n    trackContainer.classList.add('ren-slider-track');\n\n    /* ═══ INITIALIZE VALUE ═══ */\n    this.updateValue();\n\n    /* ═══ EVENT LISTENERS ═══ */\n    this.inputs.forEach((input) => {\n      input.addEventListener('input', this.handleInput);\n      input.addEventListener('change', this.handleChange);\n    });\n\n    this.range = this.inputs.length === 2 ? this.querySelector('.ren-slider-range') : null;\n    this.range?.addEventListener('pointerdown', this.handleRangePointerDown, true);\n    this.range?.addEventListener('pointermove', this.handleRangePointerMove, true);\n    this.range?.addEventListener('pointerup', this.handleRangePointerEnd, true);\n    this.range?.addEventListener('pointercancel', this.handleRangePointerEnd, true);\n\n    /* ═══ HANDLE LABEL DISPLAY ═══ */\n    const label = this.getAttribute('label');\n    if (label && !this.querySelector('.ren-slider-label')) {\n      const labelEl = document.createElement('div');\n      labelEl.className = 'ren-slider-label';\n      labelEl.textContent = label;\n      this.insertBefore(labelEl, this.input.parentElement);\n    }\n\n    /* ═══ HANDLE VALUE DISPLAY ═══ */\n    if (this.hasAttribute('show-value')) {\n      this.valueDisplay = this.querySelector('.ren-slider-value');\n      if (!this.valueDisplay) {\n        const valueEl = document.createElement('div');\n        valueEl.className = 'ren-slider-value';\n        this.valueDisplay = valueEl;\n        this.input.parentElement.appendChild(valueEl);\n      }\n      this.updateValueDisplay();\n    }\n\n    /* ═══ TRANSFER VARIANT CLASSES ═══ */\n    this.transferVariantClasses();\n  }\n\n  disconnectedCallback() {\n    this.inputs?.forEach((input) => {\n      input.removeEventListener('input', this.handleInput);\n      input.removeEventListener('change', this.handleChange);\n    });\n    this.range?.removeEventListener('pointerdown', this.handleRangePointerDown, true);\n    this.range?.removeEventListener('pointermove', this.handleRangePointerMove, true);\n    this.range?.removeEventListener('pointerup', this.handleRangePointerEnd, true);\n    this.range?.removeEventListener('pointercancel', this.handleRangePointerEnd, true);\n    this.pointerDrag = null;\n  }\n\n  /* ═══ UPDATE CSS VARIABLE FOR TRACK FILL ═══ */\n  updateValue() {\n    if (!this.input) return;\n\n    const percentages = this.inputs.map((input) => {\n      const min = Number(input.min || 0);\n      const max = Number(input.max || 100);\n      const value = input.valueAsNumber;\n      const percentage = ((value - min) / (max - min)) * 100;\n      input.style.setProperty('--value', `${percentage}%`);\n      return percentage;\n    });\n\n    if (percentages.length === 2) {\n      const track = this.querySelector('.ren-slider-track-input');\n      track?.style.setProperty('--value-start', `${Math.min(...percentages)}%`);\n      track?.style.setProperty('--value-end', `${Math.max(...percentages)}%`);\n    }\n  }\n\n  /* ═══ UPDATE VALUE DISPLAY TEXT ═══ */\n  updateValueDisplay() {\n    if (!this.valueDisplay || !this.input) return;\n\n    const value = this.inputs.length === 2\n      ? this.inputs.map((input) => input.value).join(' – ')\n      : this.input.value;\n    const unit = this.getAttribute('unit') || '';\n    this.valueDisplay.textContent = `${value}${unit}`;\n  }\n\n  /* ═══ INPUT EVENT HANDLER ═══ */\n  handleInput(event) {\n    if (this.inputs.length === 2) {\n      const [lower, upper] = this.inputs;\n      if (event.target === lower && lower.valueAsNumber > upper.valueAsNumber) {\n        lower.value = upper.value;\n      } else if (event.target === upper && upper.valueAsNumber < lower.valueAsNumber) {\n        upper.value = lower.value;\n      }\n    }\n    this.updateValue();\n    this.updateValueDisplay();\n\n    this.dispatchEvent(\n      new CustomEvent('ren-slider-input', {\n        detail: { value: this.value },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  isVerticalRange() {\n    return this.classList.contains('ren-slider-vertical');\n  }\n\n  pointerPosition(event) {\n    return this.isVerticalRange() ? -event.clientY : event.clientX;\n  }\n\n  valueFromPointer(input, event) {\n    const rect = this.range.getBoundingClientRect();\n    const min = Number(input.min || 0);\n    const max = Number(input.max || 100);\n    const step = input.step === 'any' ? 0 : Number(input.step || 1);\n    const ratio = this.isVerticalRange()\n      ? Math.max(0, Math.min(1, (rect.bottom - event.clientY) / rect.height))\n      : Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width));\n    let value = min + ratio * (max - min);\n    if (step > 0) value = min + Math.round((value - min) / step) * step;\n    return Math.max(min, Math.min(max, value));\n  }\n\n  handleRangePointerDown(event) {\n    if (this.inputs.length !== 2 || this.disabled) return;\n    const [lower, upper] = this.inputs;\n    if (lower.valueAsNumber !== upper.valueAsNumber) return;\n    const enabledInputs = this.inputs.filter((input) => !input.disabled);\n    if (enabledInputs.length === 0) return;\n\n    event.preventDefault();\n    this.pointerDrag = {\n      pointerId: event.pointerId,\n      startPosition: this.pointerPosition(event),\n      enabledInputs,\n      activeInput: null,\n    };\n    this.range.setPointerCapture?.(event.pointerId);\n  }\n\n  handleRangePointerMove(event) {\n    const drag = this.pointerDrag;\n    if (!drag || drag.pointerId !== event.pointerId) return;\n    event.preventDefault();\n\n    if (!drag.activeInput) {\n      const position = this.pointerPosition(event);\n      if (position === drag.startPosition) return;\n      const directionalInput = position < drag.startPosition ? this.inputs[0] : this.inputs[1];\n      drag.activeInput = directionalInput.disabled ? drag.enabledInputs[0] : directionalInput;\n      drag.activeInput.focus();\n    }\n\n    drag.activeInput.value = this.valueFromPointer(drag.activeInput, event);\n    drag.activeInput.dispatchEvent(new Event('input', { bubbles: true }));\n  }\n\n  handleRangePointerEnd(event) {\n    const drag = this.pointerDrag;\n    if (!drag || drag.pointerId !== event.pointerId) return;\n    event.preventDefault();\n    if (drag.activeInput) drag.activeInput.dispatchEvent(new Event('change', { bubbles: true }));\n    this.range.releasePointerCapture?.(event.pointerId);\n    this.pointerDrag = null;\n  }\n\n  /* ═══ CHANGE EVENT HANDLER ═══ */\n  handleChange(event) {\n    this.dispatchEvent(\n      new CustomEvent('ren-slider-change', {\n        detail: { value: this.value },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ TRANSFER VARIANT CLASSES TO TRACK ═══ */\n  transferVariantClasses() {\n    if (!this.input) return;\n\n    const trackContainer = this.input.parentElement || this.input;\n    const variants = ['sm', 'lg', 'success', 'warning', 'danger', 'vertical'];\n\n    variants.forEach((variant) => {\n      if (this.classList.contains(`ren-slider-${variant}`)) {\n        trackContainer.classList.add(`ren-slider-${variant}`);\n      }\n    });\n  }\n\n  /* ═══ GETTERS AND SETTERS ═══ */\n  get value() {\n    if (!this.input) return null;\n    const values = this.inputs.map((input) => input.valueAsNumber);\n    return values.length === 2 ? values : values[0];\n  }\n\n  set value(val) {\n    if (!this.input) return;\n    const values = Array.isArray(val) ? [...val] : [val];\n    if (this.inputs.length === 2 && values.length >= 2) {\n      values.splice(0, 2, ...values.slice(0, 2).map(Number).sort((a, b) => a - b));\n    }\n    this.inputs.forEach((input, index) => {\n      if (values[index] !== undefined) input.value = values[index];\n    });\n    this.updateValue();\n    this.updateValueDisplay();\n  }\n\n  get min() {\n    return this.input ? parseFloat(this.input.min) : null;\n  }\n\n  set min(val) {\n    if (this.input) {\n      this.inputs.forEach((input) => input.min = val);\n      this.updateValue();\n    }\n  }\n\n  get max() {\n    return this.input ? parseFloat(this.input.max) : null;\n  }\n\n  set max(val) {\n    if (this.input) {\n      this.inputs.forEach((input) => input.max = val);\n      this.updateValue();\n    }\n  }\n\n  get disabled() {\n    return this.input ? this.inputs.every((input) => input.disabled) : false;\n  }\n\n  set disabled(val) {\n    if (this.input) {\n      this.inputs.forEach((input) => input.disabled = val);\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-slider')) {\n  customElements.define('ren-slider', RenSlider);\n}\n",
      "path": "components/composites/ren-slider",
      "id": "component:composite:ren-slider",
      "type": "component",
      "name": "ren-slider"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-tab",
          ".ren-tab-list",
          ".ren-tab-list-enclosed",
          ".ren-tab-list-pills",
          ".ren-tab-list-underline",
          ".ren-tab-panel",
          ".ren-tabs",
          ".ren-tabs-indicator"
        ],
        "tokens": [
          "--color-accent",
          "--color-border",
          "--color-fill",
          "--color-fill-hover",
          "--color-surface",
          "--color-text",
          "--color-text-muted",
          "--duration-fast",
          "--ease-out",
          "--label-lg-weight",
          "--label-md-size",
          "--label-md-weight",
          "--label-size",
          "--label-weight",
          "--radius-md",
          "--radius-sm",
          "--ren-tabs-active-color",
          "--ren-tabs-border-color",
          "--ren-tabs-color",
          "--ren-tabs-duration",
          "--ren-tabs-easing",
          "--ren-tabs-font-size",
          "--ren-tabs-font-weight",
          "--ren-tabs-gap",
          "--ren-tabs-height",
          "--ren-tabs-indicator-color",
          "--size-lg",
          "--space-1",
          "--space-2",
          "--space-3",
          "--space-4",
          "--transition-tactile"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-tabs Component Contract\n\nTabbed interface with trigger list and panels.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-tabs` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-tabs` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Tabs composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a tablist that shows one panel at a time with mutually exclusive triggers.\"\n    - \"You need ARIA tablist semantics (role=tablist, role=tab, role=tabpanel) plus arrow-key keyboard navigation.\"\n    - \"You need a default-value (index or tab id) to control which panel renders on first paint.\"\n    - \"You need manual activation (arrow moves focus, Enter/Space selects) or automatic activation (arrow selects).\"\n    - \"You need a visual variant — .ren-tab-list-underline (default), .ren-tab-list-pills, or .ren-tab-list-enclosed.\"\n    - \"The layout must adapt to narrow widths via container query (horizontal scroll on the .ren-tab-list).\"\n  avoidWhen:\n    - \"Each section should be visible simultaneously — use ren-accordion (multiple) or simple sections.\"\n    - \"The control switches the entire page route, not in-page content — use ren-nav or anchor links.\"\n    - \"Only two states are needed — use ren-toggle-group with a single selection.\"\n    - \"You need a stepper/wizard with sequential progress — use ren-stepper.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-tabs/ren-tabs.css\"\n  js:\n    - \"rends/components/composites/ren-tabs/ren-tabs.js\"\n  notes:\n    - \"JS is required: it sets up roving tabindex, ARIA wiring, and the ren-tab-change event. CSS-only renders triggers but not the selection state.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"<ren-tabs> wraps a <div role=\\\"tablist\\\" class=\\\"ren-tab-list\\\"> followed by sibling <div role=\\\"tabpanel\\\" class=\\\"ren-tab-panel\\\"> elements.\"\n  - \"Each tab trigger is a real <button role=\\\"tab\\\" class=\\\"ren-tab\\\">; never use <a> or <div> for the trigger.\"\n  - \"Tab triggers and tabpanels must match 1:1 in document order; the component pairs them by index.\"\n  - \"Inactive panels carry the hidden attribute (the component toggles it); do not use display: none manually.\"\n  - \"Provide aria-label on <ren-tabs> when there are multiple tablists on the page, so screen-readers can distinguish them.\"\n\nforbiddenPatterns:\n  - \"Wrapping a <a href> as the tab trigger — tabs change the visible panel, they are not navigation links.\"\n  - \"Setting aria-selected manually on a tab; let the component drive it via _selectedIndex.\"\n  - \"Hiding inactive panels with display: none in CSS overrides — use the hidden attribute the JS manages.\"\n  - \"Adding interactive elements (buttons, links) directly inside .ren-tab — keep the trigger a single activation surface.\"\n  - \"Removing the .ren-tab:focus-visible outline without restoring a visible focus indicator inside the inset offset.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-tabs-active-color, --ren-tabs-border-color, --ren-tabs-color, --ren-tabs-duration, --ren-tabs-easing, --ren-tabs-font-size, --ren-tabs-font-weight, --ren-tabs-gap, --ren-tabs-height, --ren-tabs-indicator-color.\"\n    - \"Semantic tokens: --color-text, --color-text-muted, --color-accent, --color-border, --color-fill, --color-fill-hover, --color-surface.\"\n    - \"Shape / motion tokens: --radius-sm, --radius-md, --space-*, --size-lg, --label-md-size, --label-md-weight, --label-lg-weight, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named color values in overrides for the active underline or pill background.\"\n    - \"Hardcoded transition durations on the .ren-tab hover; route through --transition-tactile.\"\n\naccessibility:\n  required:\n    - \"Triggers are real <button role=\\\"tab\\\"> with aria-selected reflecting state and tabindex managed by roving (only the selected tab is tabindex=0).\"\n    - \"Tabpanels carry role=\\\"tabpanel\\\" and an aria-labelledby pointing at their trigger id (the component fills this in).\"\n    - \"Arrow Left/Right (horizontal) or Up/Down (vertical) moves focus; Home / End jump to first / last; Enter / Space activate in manual mode.\"\n    - \"The .ren-tab meets touch target via min-height: var(--size-lg); do not shrink it below this in touch contexts.\"\n    - \"Active tab is distinguished by color AND the underline / pill / enclosed visual — never rely on color alone.\"\n    - \"Focus-visible ring is provided by outline + outline-offset on .ren-tab and the panel; preserve both.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-tabs/ren-tabs.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-tabs/ren-tabs.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-tabs aria-label=\"Account settings\">\n  <div class=\"ren-tab-list\" role=\"tablist\"><button class=\"ren-tab\" role=\"tab\">Profile</button><button class=\"ren-tab\" role=\"tab\">Security</button></div>\n  <div class=\"ren-tab-panel\" role=\"tabpanel\">Profile settings</div><div class=\"ren-tab-panel\" role=\"tabpanel\" hidden>Security settings</div>\n</ren-tabs>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-tab`\n- `.ren-tab-list`\n- `.ren-tab-list-enclosed`\n- `.ren-tab-list-pills`\n- `.ren-tab-list-underline`\n- `.ren-tab-panel`\n- `.ren-tabs`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[aria-selected]`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-tabs-active-color`\n- `--ren-tabs-border-color`\n- `--ren-tabs-color`\n- `--ren-tabs-duration`\n- `--ren-tabs-easing`\n- `--ren-tabs-font-size`\n- `--ren-tabs-font-weight`\n- `--ren-tabs-gap`\n- `--ren-tabs-height`\n- `--ren-tabs-indicator-color`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-tabs/ren-tabs.css`\n- `components/composites/ren-tabs/ren-tabs.js`\n- `docs/components/ren-tabs.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ═══ TABS CONTAINER ═══ */\n.ren-tabs {\n  container-type: inline-size;\n  container-name: ren-tabs;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n}\n\n/* ═══ TAB LIST ═══ */\n.ren-tab-list {\n  display: flex;\n  flex-direction: row;\n  gap: var(--space-2);\n  border-bottom: 1px solid var(--color-border);\n  overflow-x: auto;\n  -webkit-overflow-scrolling: touch;\n}\n\n/* Smooth scrolling for horizontal tabs */\n.ren-tab-list {\n  scroll-behavior: smooth;\n}\n\n/* ═══ TAB ITEM (UNDERLINE VARIANT - DEFAULT) ═══ */\n.ren-tab {\n  position: relative;\n  padding: var(--space-2) var(--space-4);\n  font-size: var(--label-md-size);\n  font-weight: var(--label-md-weight);\n  line-height: 1;\n  color: var(--color-text-muted);\n  cursor: pointer;\n  border-bottom: 2px solid transparent;\n  min-height: var(--size-lg);\n  display: flex;\n  align-items: center;\n  white-space: nowrap;\n  user-select: none;\n  transition: var(--transition-tactile);\n}\n\n/* Active tab */\n.ren-tab[aria-selected=\"true\"] {\n  color: var(--color-text);\n  border-bottom-color: var(--color-accent);\n  font-weight: var(--label-lg-weight);\n}\n\n/* Tab hover state */\n.ren-tab:hover:not([aria-disabled=\"true\"]):not(:disabled) {\n  color: var(--color-text);\n  background-color: var(--color-fill);\n}\n\n/* Tab focus ring */\n.ren-tab:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: -2px;\n  border-radius: var(--radius-sm);\n}\n\n/* Disabled tab */\n.ren-tab:disabled,\n.ren-tab[aria-disabled=\"true\"] {\n  opacity: 0.5;\n  cursor: not-allowed;\n  color: var(--color-text-muted);\n}\n\n.ren-tab:disabled:hover,\n.ren-tab[aria-disabled=\"true\"]:hover {\n  background-color: transparent;\n}\n\n/* ═══ TAB PANELS ═══ */\n.ren-tab-panel {\n  padding-block: var(--space-4);\n}\n\n.ren-tab-panel[hidden] {\n  display: none;\n}\n\n/* Panel focus ring */\n.ren-tab-panel:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: 2px;\n  border-radius: var(--radius-md);\n}\n\n/* ═══ VARIANT: PILLS ═══ */\n.ren-tab-list-pills {\n  border-bottom: none;\n  gap: var(--space-1);\n}\n\n.ren-tab-list-pills .ren-tab {\n  border-bottom: none;\n  border-radius: var(--radius-md);\n  background-color: transparent;\n  padding: var(--space-2) var(--space-3);\n}\n\n.ren-tab-list-pills .ren-tab[aria-selected=\"true\"] {\n  background-color: var(--color-fill);\n  border-bottom-color: transparent;\n  font-weight: var(--label-lg-weight);\n}\n\n.ren-tab-list-pills .ren-tab:hover:not([aria-disabled=\"true\"]):not(:disabled) {\n  background-color: var(--color-fill-hover);\n}\n\n/* ═══ VARIANT: UNDERLINE (DEFAULT) ═══ */\n.ren-tab-list-underline {\n  border-bottom: 1px solid var(--color-border);\n  gap: var(--space-2);\n}\n\n.ren-tab-list-underline .ren-tab {\n  border-bottom: 2px solid transparent;\n}\n\n.ren-tab-list-underline .ren-tab[aria-selected=\"true\"] {\n  border-bottom-color: var(--color-accent);\n}\n\n/* ═══ VARIANT: ENCLOSED ═══ */\n.ren-tab-list-enclosed {\n  border-bottom: none;\n  gap: var(--space-2);\n}\n\n.ren-tab-list-enclosed .ren-tab {\n  border: 1px solid var(--color-border);\n  border-bottom: 1px solid var(--color-border);\n  border-radius: var(--radius-md) var(--radius-md) 0 0;\n  background-color: var(--color-fill);\n  padding: var(--space-3) var(--space-4);\n}\n\n.ren-tab-list-enclosed .ren-tab[aria-selected=\"true\"] {\n  border-color: var(--color-border);\n  border-bottom-color: var(--color-surface);\n  background-color: var(--color-surface);\n  color: var(--color-text);\n  position: relative;\n  z-index: 1;\n}\n\n.ren-tab-list-enclosed .ren-tab[aria-selected=\"true\"]::after {\n  content: '';\n  position: absolute;\n  bottom: -1px;\n  left: -1px;\n  right: -1px;\n  height: 1px;\n  background-color: var(--color-surface);\n}\n\n.ren-tab-list-enclosed .ren-tab:hover:not([aria-disabled=\"true\"]):not(:disabled) {\n  background-color: var(--color-fill-hover);\n}\n\n/* ═══ COMPACT LAYOUT (Container Query) ═══ */\n@container ren-tabs (max-width: 480px) {\n  .ren-tab-list {\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n    /* Add scrollbar styling for better UX */\n    scrollbar-width: thin;\n    scrollbar-color: var(--color-border) transparent;\n  }\n\n  .ren-tab-list::-webkit-scrollbar {\n    height: 4px;\n  }\n\n  .ren-tab-list::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  .ren-tab-list::-webkit-scrollbar-thumb {\n    background-color: var(--color-border);\n    border-radius: 2px;\n  }\n}\n\n/* ═══ REDUCED MOTION ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-tab {\n    transition: none;\n  }\n}\n/* Appearance API bridge */\n.ren-tabs {\n  min-block-size: var(--ren-tabs-height, var(--size-lg));\n  font-size: var(--ren-tabs-font-size, var(--label-size));\n  font-weight: var(--ren-tabs-font-weight, var(--label-weight));\n  color: var(--ren-tabs-color, var(--color-text-muted));\n  border-color: var(--ren-tabs-border-color, var(--color-border));\n  gap: var(--ren-tabs-gap, 0);\n  transition-duration: var(--ren-tabs-duration, var(--duration-fast));\n  transition-timing-function: var(--ren-tabs-easing, var(--ease-out));\n}\n.ren-tabs [aria-selected=\"true\"], .ren-tabs [data-state=\"active\"] { color: var(--ren-tabs-active-color, var(--color-text)); }\n.ren-tabs [data-indicator], .ren-tabs-indicator { background: var(--ren-tabs-indicator-color, var(--color-accent)); }\n\n\n/**\n * RenDS — <ren-tabs> Web Component\n * ==================================\n * Accessible tabbed interface with keyboard navigation.\n * Implements ARIA tablist pattern with manual or automatic activation.\n *\n * Uses Light DOM — no Shadow DOM.\n * Works with semantic HTML or custom tab markup.\n *\n * Attributes:\n *   activation:    'manual' (default) | 'automatic'\n *                  - manual: arrow keys move focus, Enter/Space selects\n *                  - automatic: arrow keys immediately select tabs\n *   default-value: index or tab ID to select initially\n *   orientation:   'horizontal' (default) | 'vertical'\n *\n * Markup:\n *   <ren-tabs>\n *     <div role=\"tablist\" class=\"ren-tab-list\">\n *       <button role=\"tab\" class=\"ren-tab\">Tab 1</button>\n *       <button role=\"tab\" class=\"ren-tab\">Tab 2</button>\n *     </div>\n *     <div role=\"tabpanel\" class=\"ren-tab-panel\">\n *       Panel 1 content\n *     </div>\n *     <div role=\"tabpanel\" class=\"ren-tab-panel\">\n *       Panel 2 content\n *     </div>\n *   </ren-tabs>\n *\n * Events:\n *   ren-tab-change: Dispatched when a tab is selected\n *   - detail: { tab, panel, index, id }\n */\n\nimport { createKeyboardNav } from '../../../utils/keyboard-nav.js';\nimport { autoId } from '../../../utils/id-generator.js';\n\nexport class RenTabs extends HTMLElement {\n  static get observedAttributes() {\n    return ['activation', 'orientation', 'default-value'];\n  }\n\n  constructor() {\n    super();\n    this._nav = null;\n    this._tabList = null;\n    this._tabs = [];\n    this._panels = [];\n    this._selectedIndex = -1;\n  }\n\n  /**\n   * Lifecycle: Element inserted into DOM\n   */\n  connectedCallback() {\n    this._initialize();\n  }\n\n  /**\n   * Lifecycle: Element removed from DOM\n   */\n  disconnectedCallback() {\n    if (this._nav) {\n      this._nav.detach();\n      this._nav = null;\n    }\n  }\n\n  /**\n   * Lifecycle: Observed attribute changed\n   */\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'default-value' && this.isConnected) {\n      this._selectTabByValue(newValue);\n    }\n  }\n\n  /* ═══ INITIALIZATION ═══ */\n\n  /**\n   * Initialize the tabs component\n   * @private\n   */\n  _initialize() {\n    // Find tablist container\n    this._tabList = this.querySelector('[role=\"tablist\"]') || this.querySelector('.ren-tab-list');\n    if (!this._tabList) {\n      console.warn('RenTabs: No tablist found');\n      return;\n    }\n\n    // Collect tabs and panels\n    this._tabs = Array.from(this.querySelectorAll('[role=\"tab\"], .ren-tab'));\n    this._panels = Array.from(this.querySelectorAll('[role=\"tabpanel\"], .ren-tab-panel'));\n\n    if (this._tabs.length === 0) {\n      console.warn('RenTabs: No tabs found');\n      return;\n    }\n\n    // Ensure all elements have proper ARIA roles\n    this._tabList.setAttribute('role', 'tablist');\n    this._tabs.forEach((tab) => {\n      if (!tab.hasAttribute('role')) {\n        tab.setAttribute('role', 'tab');\n      }\n    });\n    this._panels.forEach((panel) => {\n      if (!panel.hasAttribute('role')) {\n        panel.setAttribute('role', 'tabpanel');\n      }\n    });\n\n    // Wire up ARIA relationships\n    this._wireAria();\n\n    // Set up keyboard navigation\n    this._setupKeyboardNav();\n\n    // Select initial tab\n    const defaultValue = this.getAttribute('default-value');\n    if (defaultValue !== null) {\n      this._selectTabByValue(defaultValue);\n    } else {\n      // Select first tab by default\n      this._selectTab(0);\n    }\n\n    // Listen for clicks on tabs\n    this._tabList.addEventListener('click', this._handleTabClick.bind(this));\n  }\n\n  /**\n   * Wire up ARIA attributes between tabs and panels\n   * @private\n   */\n  _wireAria() {\n    this._tabs.forEach((tab, index) => {\n      // Ensure tab has an ID\n      autoId(tab, `tab-${index}`);\n\n      // Find corresponding panel\n      const panel = this._panels[index];\n      if (panel) {\n        // Ensure panel has an ID\n        autoId(panel, `tabpanel-${index}`);\n\n        // Wire aria-controls: tab controls panel\n        tab.setAttribute('aria-controls', panel.id);\n\n        // Wire aria-labelledby: panel is labeled by tab\n        panel.setAttribute('aria-labelledby', tab.id);\n      }\n\n      // Initialize aria-selected state\n      tab.setAttribute('aria-selected', index === this._selectedIndex ? 'true' : 'false');\n    });\n  }\n\n  /**\n   * Set up keyboard navigation using createKeyboardNav\n   * @private\n   */\n  _setupKeyboardNav() {\n    const activation = this.getAttribute('activation') || 'manual';\n    const orientation = this.getAttribute('orientation') || 'horizontal';\n\n    this._nav = createKeyboardNav(this._tabList, {\n      selector: '[role=\"tab\"]',\n      orientation,\n      loop: true,\n      typeahead: false,\n      focusOnHover: false,\n      onActivate: (tab, index) => {\n        // Arrow key navigation\n        if (activation === 'automatic') {\n          // Automatic: immediately select the tab\n          this._selectTab(index);\n        }\n        // Manual: just move focus, don't select\n        // (User must press Enter/Space to select)\n      },\n      onSelect: (tab, index) => {\n        // Enter/Space pressed on focused tab\n        this._selectTab(index);\n      },\n    });\n\n    this._nav.attach();\n  }\n\n  /**\n   * Handle direct click on a tab\n   * @private\n   */\n  _handleTabClick(event) {\n    const tab = event.target.closest('[role=\"tab\"]');\n    if (!tab) return;\n\n    const index = this._tabs.indexOf(tab);\n    if (index !== -1) {\n      this._selectTab(index);\n      // Ensure focus is on the clicked tab\n      tab.focus();\n    }\n  }\n\n  /* ═══ TAB SELECTION ═══ */\n\n  /**\n   * Select a tab by index\n   * @param {number} index - Tab index\n   * @private\n   */\n  _selectTab(index) {\n    if (index < 0 || index >= this._tabs.length) {\n      return;\n    }\n\n    // Deselect all tabs and hide all panels\n    this._tabs.forEach((tab, i) => {\n      const isSelected = i === index;\n      tab.setAttribute('aria-selected', isSelected ? 'true' : 'false');\n      tab.setAttribute('data-state', isSelected ? 'active' : 'inactive');\n      if (this._panels[i]) {\n        this._panels[i].toggleAttribute('hidden', !isSelected);\n      }\n    });\n\n    this._selectedIndex = index;\n\n    // Dispatch custom event\n    const tab = this._tabs[index];\n    const panel = this._panels[index];\n    this.dispatchEvent(\n      new CustomEvent('ren-tab-change', {\n        detail: {\n          tab,\n          panel,\n          index,\n          id: tab.id,\n        },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /**\n   * Select a tab by ID or index value\n   * @param {string|number} value - Tab ID or index string\n   * @private\n   */\n  _selectTabByValue(value) {\n    // Try parsing as index first\n    const indexValue = parseInt(value, 10);\n    if (!isNaN(indexValue) && indexValue >= 0 && indexValue < this._tabs.length) {\n      this._selectTab(indexValue);\n      return;\n    }\n\n    // Try finding by ID\n    const tab = this._tabs.find((t) => t.id === value);\n    if (tab) {\n      const index = this._tabs.indexOf(tab);\n      this._selectTab(index);\n    }\n  }\n\n  /* ═══ PUBLIC API ═══ */\n\n  /**\n   * Get the currently selected tab index\n   * @returns {number}\n   */\n  get selectedIndex() {\n    return this._selectedIndex;\n  }\n\n  /**\n   * Get the currently selected tab element\n   * @returns {HTMLElement|null}\n   */\n  get selectedTab() {\n    return this._selectedIndex >= 0 ? this._tabs[this._selectedIndex] : null;\n  }\n\n  /**\n   * Get the currently selected panel element\n   * @returns {HTMLElement|null}\n   */\n  get selectedPanel() {\n    return this._selectedIndex >= 0 ? this._panels[this._selectedIndex] : null;\n  }\n\n  /**\n   * Programmatically select a tab by index\n   * @param {number} index\n   */\n  selectTabByIndex(index) {\n    this._selectTab(index);\n  }\n\n  /**\n   * Programmatically select a tab by ID\n   * @param {string} id\n   */\n  selectTabById(id) {\n    const index = this._tabs.findIndex((t) => t.id === id);\n    if (index !== -1) {\n      this._selectTab(index);\n    }\n  }\n\n  /**\n   * Get all tabs\n   * @returns {HTMLElement[]}\n   */\n  get tabs() {\n    return [...this._tabs];\n  }\n\n  /**\n   * Get all panels\n   * @returns {HTMLElement[]}\n   */\n  get panels() {\n    return [...this._panels];\n  }\n}\n\n// Register the component\nif (!customElements.get('ren-tabs')) {\n  customElements.define('ren-tabs', RenTabs);\n}\n",
      "path": "components/composites/ren-tabs",
      "id": "component:composite:ren-tabs",
      "type": "component",
      "name": "ren-tabs"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-btn",
          ".ren-toast",
          ".ren-toast-action",
          ".ren-toast-actions",
          ".ren-toast-body",
          ".ren-toast-close",
          ".ren-toast-danger",
          ".ren-toast-description",
          ".ren-toast-error",
          ".ren-toast-icon",
          ".ren-toast-info",
          ".ren-toast-loading",
          ".ren-toast-progress",
          ".ren-toast-success",
          ".ren-toast-title",
          ".ren-toast-viewport",
          ".ren-toast-warning"
        ],
        "tokens": [
          "--color-accent",
          "--color-border",
          "--color-danger",
          "--color-danger-subtle",
          "--color-info",
          "--color-info-subtle",
          "--color-success",
          "--color-success-subtle",
          "--color-surface-active",
          "--color-surface-hover",
          "--color-surface-raised",
          "--color-text",
          "--color-text-muted",
          "--color-warning",
          "--color-warning-subtle",
          "--duration-enter",
          "--duration-exit",
          "--duration-micro",
          "--duration-normal",
          "--ease-enter",
          "--ease-exit",
          "--ease-out",
          "--font-weight-semibold",
          "--leading-5",
          "--radius-lg",
          "--radius-sm",
          "--ren-toast-anim-duration",
          "--ren-toast-bg",
          "--ren-toast-border",
          "--ren-toast-easing",
          "--ren-toast-gap",
          "--ren-toast-padding",
          "--ren-toast-radius",
          "--ren-toast-shadow",
          "--ren-toast-width",
          "--shadow-lg",
          "--space-1",
          "--space-2",
          "--space-3",
          "--space-4",
          "--text-sm",
          "--text-xs",
          "--touch-min",
          "--transition-tactile",
          "--z-toast"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-toast Component Contract\n\nTransient notification system with live-region announcements.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-toast` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-toast` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Toast composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a transient notification anchored to a viewport corner that auto-dismisses after a duration.\"\n    - \"The notification must NOT steal focus or block the page (use ren-dialog for that).\"\n    - \"You need a status variant — success / info / warning / danger / loading — with matching icon and subtle status surface.\"\n    - \"You need a polite or assertive live-region announcement for screen-readers (handled via utils/live-region).\"\n    - \"You need the imperative window.toast API (toast.success, toast.danger, toast.promise(...)) instead of declarative markup.\"\n    - \"You need swipe-to-dismiss, hover-to-pause timers, and an optional inline action button.\"\n  avoidWhen:\n    - \"The message blocks the workflow and demands a decision — use ren-dialog (alert variant).\"\n    - \"The message is persistent / inline page-level — use ren-banner or ren-alert.\"\n    - \"The message describes a form field state — use the helper-text / error slot on ren-field.\"\n    - \"The disclosure is a contextual popover from a trigger — use ren-popover or ren-tooltip.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-toast/ren-toast.css\"\n  js:\n    - \"rends/components/composites/ren-toast/ren-toast.js\"\n  notes:\n    - \"JS is required: it owns the viewport queue, timers, live-region announcements, and swipe handling. window.toast is exposed as the imperative API.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Place one <ren-toast-viewport> (or <div class=\\\"ren-toast-viewport\\\" role=\\\"region\\\" aria-label=\\\"Notifications\\\">) per position; data-position defaults to bottom-right.\"\n  - \"Toasts themselves are appended by the JS as .ren-toast nodes with .ren-toast-icon, .ren-toast-body (.ren-toast-title + .ren-toast-description), optional .ren-toast-actions, and .ren-toast-close.\"\n  - \"Status variants are applied as a sibling class on .ren-toast: .ren-toast-success / .ren-toast-info / .ren-toast-warning / .ren-toast-danger / .ren-toast-error / .ren-toast-loading.\"\n  - \"Action buttons inside .ren-toast-actions reuse .ren-btn (typically .ren-btn-ghost .ren-btn-sm); .ren-toast-action is only an override hook.\"\n  - \"Set duration: 0 (or status: 'loading') for persistent toasts; expose a close button so users can dismiss them.\"\n\nforbiddenPatterns:\n  - \"Calling toast.show() with status: 'danger' AND focusing the toast — toasts must not steal focus.\"\n  - \"Rendering toasts inline in document body without a .ren-toast-viewport host (timers and stacking will not work).\"\n  - \"Hardcoded position offsets via inline style — use the [data-position] attribute and rely on var(--space-4) inset.\"\n  - \"Custom slide animations via @keyframes overriding ren-toast-slide-in-*; route motion through --duration-enter / --ease-enter.\"\n  - \"Using colored side borders for status emphasis; variants use a subtle status surface plus icon/text instead.\"\n  - \"Using a toast to ask for confirmation (\\\"Are you sure?\\\") — confirmations belong in ren-dialog.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-toast-anim-duration, --ren-toast-bg, --ren-toast-border, --ren-toast-duration, --ren-toast-easing, --ren-toast-gap, --ren-toast-padding, --ren-toast-radius, --ren-toast-shadow, --ren-toast-width, plus the shared --ren-btn-* family for inline action buttons.\"\n    - \"Semantic state tokens: --color-success, --color-success-subtle, --color-info, --color-info-subtle, --color-warning, --color-warning-subtle, --color-danger, --color-danger-subtle, --color-accent (progress bar), --color-surface, --color-surface-raised, --color-surface-hover, --color-surface-active, --color-text, --color-text-muted, --color-border.\"\n    - \"Shape / motion tokens: --space-*, --radius-sm, --radius-lg, --shadow-lg, --duration-enter, --duration-exit, --duration-micro, --ease-enter, --ease-exit, --transition-tactile, --z-toast.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named color values in overrides for the status surface, border, or icon color.\"\n    - \"Hardcoded animation timings; route through --duration-enter / --duration-exit and the paired easings.\"\n\naccessibility:\n  required:\n    - \"Viewport is a role=\\\"region\\\" with aria-label=\\\"Notifications\\\" so screen-readers can locate it as a landmark.\"\n    - \"Success / info messages announce via aria-live=\\\"polite\\\"; danger / error messages announce via aria-live=\\\"assertive\\\" (handled by announcePolite / announceAssertive).\"\n    - \"Status must be communicated by icon AND text/title — color alone does not satisfy the contrast contract.\"\n    - \"Hover and keyboard focus pause auto-dismiss timers; restoring focus elsewhere resumes them.\"\n    - \"The .ren-toast-close button has an accessible name (aria-label=\\\"Close\\\") and is reachable via keyboard.\"\n    - \"Toasts never trap focus; they receive focus only when the user tabs into the viewport region intentionally.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-toast/ren-toast.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-toast/ren-toast.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-toast-viewport data-position=\"bottom-right\" aria-label=\"Notifications\"></ren-toast-viewport>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-btn`\n- `.ren-toast`\n- `.ren-toast-action`\n- `.ren-toast-actions`\n- `.ren-toast-body`\n- `.ren-toast-close`\n- `.ren-toast-danger`\n- `.ren-toast-description`\n- `.ren-toast-error`\n- `.ren-toast-icon`\n- `.ren-toast-info`\n- `.ren-toast-loading`\n- `.ren-toast-progress`\n- `.ren-toast-success`\n- `.ren-toast-title`\n- `.ren-toast-viewport`\n- `.ren-toast-warning`\n\n## States And Attributes\n\n- `[data-closing]`\n- `[data-position]`\n- `[data-swiping]`\n- `[data-toast-id]`\n- `:active`\n- `:hover`\n\n## Public Token API\n\n- `--ren-btn-bg`\n- `--ren-btn-bg-active`\n- `--ren-btn-bg-hover`\n- `--ren-btn-border-color`\n- `--ren-btn-border-width`\n- `--ren-btn-color`\n- `--ren-btn-duration`\n- `--ren-btn-easing`\n- `--ren-btn-font-size`\n- `--ren-btn-font-weight`\n- `--ren-btn-gap`\n- `--ren-btn-height`\n- `--ren-btn-padding-x`\n- `--ren-btn-padding-y`\n- `--ren-btn-radius`\n- `--ren-btn-ring-color`\n- `--ren-btn-ring-offset`\n- `--ren-btn-ring-width`\n- `--ren-toast-anim-duration`\n- `--ren-toast-bg`\n- `--ren-toast-border`\n- `--ren-toast-duration`\n- `--ren-toast-easing`\n- `--ren-toast-gap`\n- `--ren-toast-padding`\n- `--ren-toast-radius`\n- `--ren-toast-shadow`\n- `--ren-toast-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-toast/ren-toast.css`\n- `components/composites/ren-toast/ren-toast.js`\n- `docs/components/ren-toast.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/**\n * RenDS — Toast Notification System CSS\n * =====================================\n * Styles for notification toasts with animations,\n * positioning, variants, and responsive behavior.\n */\n\n/* ═══ VIEWPORT CONTAINER ═══ */\n.ren-toast-viewport {\n  position: fixed;\n  z-index: var(--z-toast, 9999);\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-3);\n  max-height: 100dvh;\n  overflow: hidden;\n  pointer-events: none;\n  max-width: min(420px, calc(100vw - 2rem));\n}\n\n/* Position variants */\n.ren-toast-viewport[data-position=\"top-right\"] {\n  top: var(--space-4);\n  right: var(--space-4);\n  flex-direction: column;\n}\n\n.ren-toast-viewport[data-position=\"top-left\"] {\n  top: var(--space-4);\n  left: var(--space-4);\n  flex-direction: column;\n}\n\n.ren-toast-viewport[data-position=\"top-center\"] {\n  top: var(--space-4);\n  left: 50%;\n  transform: translateX(-50%);\n  flex-direction: column;\n}\n\n.ren-toast-viewport[data-position=\"bottom-right\"] {\n  bottom: var(--space-4);\n  right: var(--space-4);\n  flex-direction: column-reverse;\n}\n\n.ren-toast-viewport[data-position=\"bottom-left\"] {\n  bottom: var(--space-4);\n  left: var(--space-4);\n  flex-direction: column-reverse;\n}\n\n.ren-toast-viewport[data-position=\"bottom-center\"] {\n  bottom: var(--space-4);\n  left: 50%;\n  transform: translateX(-50%);\n  flex-direction: column-reverse;\n}\n\n/* ═══ TOAST CONTAINER ═══ */\n.ren-toast {\n  pointer-events: auto;\n  background: var(--color-surface-raised);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  padding: var(--space-3) var(--space-4);\n  display: flex;\n  gap: var(--space-3);\n  align-items: flex-start;\n  min-width: 20rem;\n  max-width: 100%;\n  position: relative;\n  overflow: hidden;\n  /* Enter + exit use semantic tokens so global motion tuning cascades here. */\n  animation: ren-toast-fade-in var(--duration-enter) var(--ease-enter);\n}\n\n.ren-toast[data-closing] {\n  animation: ren-toast-fade-out var(--duration-exit) var(--ease-exit);\n  opacity: 0;\n}\n\n/* Slide-in animations by position */\n.ren-toast-viewport[data-position=\"top-right\"] .ren-toast,\n.ren-toast-viewport[data-position=\"bottom-right\"] .ren-toast {\n  animation: ren-toast-slide-in-right var(--duration-enter) var(--ease-enter);\n}\n\n.ren-toast-viewport[data-position=\"top-right\"] .ren-toast[data-closing],\n.ren-toast-viewport[data-position=\"bottom-right\"] .ren-toast[data-closing] {\n  animation: ren-toast-slide-out-right var(--duration-exit) var(--ease-exit);\n}\n\n.ren-toast-viewport[data-position=\"top-left\"] .ren-toast,\n.ren-toast-viewport[data-position=\"bottom-left\"] .ren-toast {\n  animation: ren-toast-slide-in-left var(--duration-enter) var(--ease-enter);\n}\n\n.ren-toast-viewport[data-position=\"top-left\"] .ren-toast[data-closing],\n.ren-toast-viewport[data-position=\"bottom-left\"] .ren-toast[data-closing] {\n  animation: ren-toast-slide-out-left var(--duration-exit) var(--ease-exit);\n}\n\n.ren-toast-viewport[data-position=\"top-center\"] .ren-toast,\n.ren-toast-viewport[data-position=\"bottom-center\"] .ren-toast {\n  animation: ren-toast-fade-in var(--duration-enter) var(--ease-enter);\n}\n\n/* ═══ TOAST CONTENT STRUCTURE ═══ */\n.ren-toast-icon {\n  flex-shrink: 0;\n  width: 20px;\n  height: 20px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 16px;\n  line-height: 1;\n}\n\n/* Body wraps title + description + actions */\n.ren-toast-body {\n  flex: 1;\n  min-width: 0;\n}\n\n.ren-toast-title {\n  font-weight: var(--font-weight-semibold, 600);\n  font-size: var(--text-sm);\n  line-height: var(--leading-5);\n  color: var(--color-text);\n  margin: 0;\n}\n\n.ren-toast-description {\n  font-size: var(--text-xs);\n  line-height: 1.5;\n  color: var(--color-text-muted);\n  margin: var(--space-1) 0 0;\n}\n\n.ren-toast-actions {\n  display: flex;\n  gap: var(--space-2);\n  margin-top: var(--space-2);\n  flex-wrap: wrap;\n}\n\n/* The action button itself (the docs name it .ren-toast-action; we layer it\n   on top of the standard .ren-btn variants for consistent styling). */\n.ren-toast-action {\n  /* Inherits its visual from .ren-btn ren-btn-ghost ren-btn-sm — this rule\n     is just a hook for consumers who want to override per-toast. */\n}\n\n.ren-toast-close {\n  flex-shrink: 0;\n  width: 32px;\n  height: 32px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  background: transparent;\n  border: none;\n  border-radius: var(--radius-sm);\n  color: var(--color-text-muted);\n  cursor: pointer;\n  padding: var(--space-1);\n  font-size: 18px;\n  line-height: 1;\n  /* Tactile (hover/focus/press) — uses shared semantic preset */\n  transition: var(--transition-tactile);\n}\n\n.ren-toast-close:hover {\n  background-color: var(--color-surface-hover);\n  color: var(--color-text);\n}\n\n.ren-toast-close:active {\n  background-color: var(--color-surface-active);\n}\n\n/* ═══ TOAST VARIANTS ═══ */\n/* Success */\n.ren-toast-success {\n  background: color-mix(in srgb, var(--color-success-subtle) 65%, var(--color-surface-raised));\n}\n\n.ren-toast-success .ren-toast-icon {\n  color: var(--color-success);\n}\n\n/* Error / Danger */\n.ren-toast-error,\n.ren-toast-danger {\n  background: color-mix(in srgb, var(--color-danger-subtle) 65%, var(--color-surface-raised));\n}\n\n.ren-toast-error .ren-toast-icon,\n.ren-toast-danger .ren-toast-icon {\n  color: var(--color-danger);\n}\n\n/* Warning */\n.ren-toast-warning {\n  background: color-mix(in srgb, var(--color-warning-subtle) 65%, var(--color-surface-raised));\n}\n\n.ren-toast-warning .ren-toast-icon {\n  color: var(--color-warning);\n}\n\n/* Info */\n.ren-toast-info {\n  background: color-mix(in srgb, var(--color-info-subtle) 65%, var(--color-surface-raised));\n}\n\n.ren-toast-info .ren-toast-icon {\n  color: var(--color-info);\n}\n\n/* Loading */\n.ren-toast-loading .ren-toast-icon {\n  animation: ren-spinner-rotate 0.8s linear infinite;\n}\n\n/* ═══ PROGRESS BAR ═══ */\n.ren-toast-progress {\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  height: 2px;\n  background-color: var(--color-accent);\n  transition: width linear;\n}\n\n/* ═══ SWIPE STATE ═══ */\n.ren-toast[data-swiping] {\n  transition: none;\n}\n\n/* ═══ ANIMATIONS ═══ */\n@keyframes ren-toast-slide-in-right {\n  from {\n    transform: translateX(100%);\n    opacity: 0;\n  }\n  to {\n    transform: translateX(0);\n    opacity: 1;\n  }\n}\n\n@keyframes ren-toast-slide-in-left {\n  from {\n    transform: translateX(-100%);\n    opacity: 0;\n  }\n  to {\n    transform: translateX(0);\n    opacity: 1;\n  }\n}\n\n@keyframes ren-toast-slide-out-right {\n  from {\n    transform: translateX(0);\n    opacity: 1;\n  }\n  to {\n    transform: translateX(100%);\n    opacity: 0;\n  }\n}\n\n@keyframes ren-toast-slide-out-left {\n  from {\n    transform: translateX(0);\n    opacity: 1;\n  }\n  to {\n    transform: translateX(-100%);\n    opacity: 0;\n  }\n}\n\n@keyframes ren-toast-fade-in {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n\n@keyframes ren-toast-fade-out {\n  from {\n    opacity: 1;\n  }\n  to {\n    opacity: 0;\n  }\n}\n\n@keyframes ren-spinner-rotate {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n/* ═══ REDUCED MOTION ═══\n   Belt-and-suspenders: semantic --duration-* already collapse\n   to 0ms under reduced-motion (see tokens/semantic/motion.css),\n   so the animations above become instant. We keep this block\n   to force a plain fade (no slide) as an Apple-HIG alternative,\n   and to stop the loading spinner. */\n@media (prefers-reduced-motion: reduce) {\n  .ren-toast,\n  .ren-toast[data-closing],\n  .ren-toast-viewport[data-position=\"top-right\"] .ren-toast,\n  .ren-toast-viewport[data-position=\"bottom-right\"] .ren-toast,\n  .ren-toast-viewport[data-position=\"top-left\"] .ren-toast,\n  .ren-toast-viewport[data-position=\"bottom-left\"] .ren-toast,\n  .ren-toast-viewport[data-position=\"top-center\"] .ren-toast,\n  .ren-toast-viewport[data-position=\"bottom-center\"] .ren-toast {\n    animation: ren-toast-fade-in var(--duration-micro) linear !important;\n  }\n\n  .ren-toast[data-closing] {\n    animation: ren-toast-fade-out var(--duration-micro) linear !important;\n  }\n\n  .ren-toast-loading .ren-toast-icon {\n    animation: none;\n    opacity: 0.6;\n  }\n}\n\n/* ═══ RESPONSIVE ═══ */\n@media (max-width: 640px) {\n  .ren-toast-viewport {\n    max-width: calc(100vw - 1rem);\n    bottom: var(--space-3);\n    left: var(--space-2);\n    right: var(--space-2);\n    width: auto;\n  }\n\n  .ren-toast-viewport[data-position=\"top-right\"],\n  .ren-toast-viewport[data-position=\"top-left\"],\n  .ren-toast-viewport[data-position=\"top-center\"] {\n    bottom: auto;\n    left: var(--space-2);\n    right: var(--space-2);\n    width: auto;\n    transform: none;\n  }\n\n  .ren-toast-viewport[data-position=\"bottom-right\"],\n  .ren-toast-viewport[data-position=\"bottom-left\"],\n  .ren-toast-viewport[data-position=\"bottom-center\"] {\n    bottom: var(--space-3);\n    left: var(--space-2);\n    right: var(--space-2);\n    width: auto;\n    transform: none;\n  }\n\n  .ren-toast {\n    min-width: unset;\n    width: 100%;\n  }\n}\n/* Appearance API bridge */\n.ren-toast { inline-size: var(--ren-toast-width, 360px); padding: var(--ren-toast-padding, var(--space-4)); border-radius: var(--ren-toast-radius, var(--radius-lg)); background: var(--ren-toast-bg, var(--color-surface-raised)); border-color: var(--ren-toast-border, var(--color-border)); box-shadow: var(--ren-toast-shadow, var(--shadow-lg)); gap: var(--ren-toast-gap, var(--space-3)); animation-duration: var(--ren-toast-anim-duration, var(--duration-normal)); animation-timing-function: var(--ren-toast-easing, var(--ease-out)); }\n\n@media (any-pointer: coarse), (pointer: coarse) {\n  .ren-toast-close {\n    min-inline-size: var(--touch-min);\n    min-block-size: var(--touch-min);\n  }\n}\n\n\n/**\n * RenDS — Toast Notification System\n * ==================================\n * Transient notifications anchored to a corner of the viewport.\n *\n * Light DOM only. The viewport is a plain element with class\n * `.ren-toast-viewport` (or the convenience custom element\n * `<ren-toast-viewport>`). Toasts are plain `.ren-toast` divs\n * appended to it.\n *\n * Public API (also available as `window.toast`):\n *   toast.success(title, options?)\n *   toast.info(title, options?)\n *   toast.warning(title, options?)\n *   toast.danger(title, options?)\n *   toast.show(options)            — { title, description, status, duration, action }\n *   toast.dismiss(id)\n *   toast.dismissAll()\n *   toast.promise(promise, { loading, success, error })\n *\n * Options:\n *   title        — required string\n *   description  — optional secondary line\n *   status       — \"success\" | \"info\" | \"warning\" | \"danger\" | \"loading\"\n *   duration     — auto-dismiss ms (0 = persistent, default 4000 / 8000 for danger)\n *   dismissible  — show close button (default true)\n *   action       — { label, onClick } inline button\n */\n\nimport { announceAssertive, announcePolite } from '../../../utils/live-region.js';\n\n/* ─── Defaults ─── */\n\nconst DEFAULT_DURATIONS = {\n  success: 4000,\n  info: 4000,\n  warning: 5000,\n  danger: 8000,\n  loading: 0, // persistent\n};\n\nconst ICONS = {\n  success: '✓',\n  info: 'ℹ',\n  warning: '⚠',\n  danger: '✕',\n  loading: '⟳',\n};\n\nconst VALID_POSITIONS = new Set([\n  'top-right',\n  'top-left',\n  'top-center',\n  'bottom-right',\n  'bottom-left',\n  'bottom-center',\n]);\n\n/* ─── Custom element (convenience) ─── */\n\nexport class RenToastViewport extends HTMLElement {\n  connectedCallback() {\n    this._listenerController?.abort();\n    this._listenerController = new AbortController();\n    const { signal } = this._listenerController;\n\n    this.classList.add('ren-toast-viewport');\n    if (!this.hasAttribute('data-position')) {\n      this.setAttribute('data-position', 'bottom-right');\n    }\n    if (!VALID_POSITIONS.has(this.getAttribute('data-position'))) {\n      this.setAttribute('data-position', 'bottom-right');\n    }\n    if (!this.hasAttribute('aria-label')) {\n      this.setAttribute('aria-label', 'Notifications');\n    }\n    if (!this.hasAttribute('role')) this.setAttribute('role', 'region');\n\n    // Pause timers on hover or keyboard focus\n    this.addEventListener('mouseenter', () => pauseTimersFor(this), { signal });\n    this.addEventListener('mouseleave', () => resumeTimersFor(this), { signal });\n    this.addEventListener('focusin', () => pauseTimersFor(this), { signal });\n    this.addEventListener('focusout', (e) => {\n      if (!this.contains(e.relatedTarget)) resumeTimersFor(this);\n    }, { signal });\n  }\n\n  disconnectedCallback() {\n    this._listenerController?.abort();\n    this._listenerController = null;\n    resumeTimersFor(this);\n  }\n}\n\nif (!customElements.get('ren-toast-viewport')) {\n  customElements.define('ren-toast-viewport', RenToastViewport);\n}\n\n/* ─── Per-viewport timer book-keeping ─── */\n\nconst timerStore = new WeakMap(); // viewport → { timers: Map<id, {timerId, remaining, startedAt}>, paused: bool }\n\nfunction getStore(viewport) {\n  let store = timerStore.get(viewport);\n  if (!store) {\n    store = { timers: new Map(), paused: false };\n    timerStore.set(viewport, store);\n  }\n  return store;\n}\n\nfunction pauseTimersFor(viewport) {\n  const store = getStore(viewport);\n  if (store.paused) return;\n  store.paused = true;\n  for (const [id, info] of store.timers.entries()) {\n    if (info.timerId == null) continue;\n    clearTimeout(info.timerId);\n    info.timerId = null;\n    if (info.startedAt && info.remaining) {\n      info.remaining = Math.max(0, info.remaining - (Date.now() - info.startedAt));\n    }\n    // Pause progress bar (freeze its current width)\n    const toastEl = viewport.querySelector(`[data-toast-id=\"${CSS.escape(id)}\"]`);\n    if (toastEl) {\n      const progress = toastEl.querySelector('.ren-toast-progress');\n      if (progress) {\n        const width = getComputedStyle(progress).width;\n        progress.style.transition = 'none';\n        progress.style.width = width;\n      }\n    }\n  }\n}\n\nfunction resumeTimersFor(viewport) {\n  const store = getStore(viewport);\n  if (!store.paused) return;\n  store.paused = false;\n  for (const [id, info] of store.timers.entries()) {\n    if (info.remaining > 0) {\n      info.startedAt = Date.now();\n      info.timerId = setTimeout(() => dismissToast(viewport, id), info.remaining);\n      // Resume progress bar\n      const toastEl = viewport.querySelector(`[data-toast-id=\"${CSS.escape(id)}\"]`);\n      if (toastEl) {\n        const progress = toastEl.querySelector('.ren-toast-progress');\n        if (progress) {\n          // Force reflow then animate to 0\n          progress.style.transition = `width ${info.remaining}ms linear`;\n          requestAnimationFrame(() => {\n            progress.style.width = '0%';\n          });\n        }\n      }\n    }\n  }\n}\n\n/* ─── Viewport resolution ─── */\n\nlet _autoViewport = null;\n\nfunction resolveViewport(preferredId) {\n  if (preferredId) {\n    const v = document.getElementById(preferredId);\n    if (v) return v;\n  }\n  if (_autoViewport && document.body.contains(_autoViewport)) {\n    return _autoViewport;\n  }\n  // Look for any existing viewport in the page\n  const existing = document.querySelector('.ren-toast-viewport, ren-toast-viewport');\n  if (existing) {\n    _autoViewport = existing;\n    return existing;\n  }\n  // Auto-create\n  _autoViewport = document.createElement('ren-toast-viewport');\n  _autoViewport.setAttribute('data-position', 'bottom-right');\n  document.body.appendChild(_autoViewport);\n  return _autoViewport;\n}\n\n/* ─── Toast construction ─── */\n\nfunction generateId() {\n  return `toast-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction normalize(input, statusOverride) {\n  const opts = typeof input === 'string' ? { title: input } : { ...input };\n  opts.statusExplicit = statusOverride != null || opts.status != null;\n  if (statusOverride) opts.status = statusOverride;\n  if (!opts.status) opts.status = 'info';\n  opts.durationExplicit = opts.duration != null;\n  if (opts.duration == null) opts.duration = DEFAULT_DURATIONS[opts.status] ?? 4000;\n  if (opts.dismissible == null) opts.dismissible = true;\n  return opts;\n}\n\nfunction buildToast(id, opts) {\n  const { title, description, status, duration, dismissible, action } = opts;\n\n  const toast = document.createElement('div');\n  toast.className = `ren-toast ren-toast-${status}`;\n  toast.setAttribute('data-toast-id', id);\n  if (duration > 0) toast.setAttribute('data-duration', String(duration));\n  toast.setAttribute('role', status === 'danger' ? 'alert' : 'status');\n  toast.setAttribute('tabindex', '0'); // focusable for Esc/F6\n\n  // Icon\n  const icon = document.createElement('span');\n  icon.className = 'ren-toast-icon';\n  icon.setAttribute('aria-hidden', 'true');\n  icon.textContent = ICONS[status] ?? ICONS.info;\n  toast.appendChild(icon);\n\n  // Body (title + description + action)\n  const body = document.createElement('div');\n  body.className = 'ren-toast-body';\n\n  if (title) {\n    const t = document.createElement('p');\n    t.className = 'ren-toast-title';\n    t.textContent = title;\n    body.appendChild(t);\n  }\n\n  if (description) {\n    const d = document.createElement('p');\n    d.className = 'ren-toast-description';\n    d.textContent = description;\n    body.appendChild(d);\n  }\n\n  if (action && action.label) {\n    const actions = document.createElement('div');\n    actions.className = 'ren-toast-actions';\n\n    const btn = document.createElement('button');\n    btn.type = 'button';\n    btn.className = 'ren-toast-action ren-btn ren-btn-ghost ren-btn-sm';\n    btn.textContent = action.label;\n    btn.addEventListener('click', () => {\n      try {\n        action.onClick?.();\n      } finally {\n        // Find the host viewport at click time (toast may have moved? unlikely)\n        const viewport = toast.closest('.ren-toast-viewport, ren-toast-viewport');\n        if (viewport) dismissToast(viewport, id);\n      }\n    });\n    actions.appendChild(btn);\n    body.appendChild(actions);\n  }\n\n  toast.appendChild(body);\n\n  // Close button\n  if (dismissible) {\n    const close = document.createElement('button');\n    close.type = 'button';\n    close.className = 'ren-toast-close';\n    close.setAttribute('aria-label', 'Dismiss notification');\n    close.textContent = '×';\n    close.addEventListener('click', () => {\n      const viewport = toast.closest('.ren-toast-viewport, ren-toast-viewport');\n      if (viewport) dismissToast(viewport, id);\n    });\n    toast.appendChild(close);\n  }\n\n  // Progress bar (auto-dismiss countdown)\n  if (duration > 0) {\n    const progress = document.createElement('div');\n    progress.className = 'ren-toast-progress';\n    progress.style.width = '100%';\n    progress.style.transition = `width ${duration}ms linear`;\n    toast.appendChild(progress);\n    requestAnimationFrame(() => {\n      progress.style.width = '0%';\n    });\n  }\n\n  // Esc dismisses when toast itself has focus\n  toast.addEventListener('keydown', (e) => {\n    if (e.key === 'Escape') {\n      const viewport = toast.closest('.ren-toast-viewport, ren-toast-viewport');\n      if (viewport) dismissToast(viewport, id);\n    }\n  });\n\n  return toast;\n}\n\n/* ─── Show / dismiss ─── */\n\nfunction showOnViewport(viewport, opts) {\n  const id = opts.id || generateId();\n\n  // If a toast with this id exists, replace it\n  const existing = viewport.querySelector(`[data-toast-id=\"${CSS.escape(id)}\"]`);\n  if (existing) {\n    existing.remove();\n    const store = getStore(viewport);\n    const t = store.timers.get(id);\n    if (t?.timerId) clearTimeout(t.timerId);\n    store.timers.delete(id);\n  }\n\n  const toast = buildToast(id, opts);\n  viewport.appendChild(toast);\n\n  // Resolve the closest effective token scope, including overrides on the\n  // generated toast itself. Explicit duration/status options remain\n  // authoritative.\n  if (!opts.durationExplicit && !opts.statusExplicit) {\n    const tokenDuration = getComputedStyle(toast)\n      .getPropertyValue('--ren-toast-duration')\n      .trim();\n    const parsedDuration = parseFloat(tokenDuration);\n    if (Number.isFinite(parsedDuration) && parsedDuration >= 0) {\n      opts.duration = parsedDuration;\n      const progress = toast.querySelector('.ren-toast-progress');\n      if (opts.duration > 0) {\n        toast.setAttribute('data-duration', String(opts.duration));\n        if (progress) progress.style.transition = `width ${opts.duration}ms linear`;\n      } else if (progress) {\n        progress.remove();\n        toast.removeAttribute('data-duration');\n      }\n    }\n  }\n\n  if (!opts.durationExplicit && opts.duration > 0) toast.setAttribute('data-duration', String(opts.duration));\n\n  // Schedule auto-dismiss\n  if (opts.duration > 0) {\n    const store = getStore(viewport);\n    const startedAt = Date.now();\n    const timerId = store.paused\n      ? null\n      : setTimeout(() => dismissToast(viewport, id), opts.duration);\n    store.timers.set(id, { timerId, remaining: opts.duration, startedAt });\n  }\n\n  // Announce via live region (separate from the toast DOM so SRs don't double-up)\n  const announcement = [opts.title, opts.description].filter(Boolean).join('. ');\n  if (announcement) {\n    if (opts.status === 'danger') announceAssertive(announcement);\n    else announcePolite(announcement);\n  }\n\n  // Enforce optional max-toasts\n  enforceMax(viewport);\n\n  return id;\n}\n\nfunction enforceMax(viewport) {\n  const max = parseInt(viewport.getAttribute('data-max'), 10);\n  if (!max || isNaN(max)) return;\n  const toasts = viewport.querySelectorAll('.ren-toast:not([data-closing])');\n  if (toasts.length <= max) return;\n  for (let i = 0; i < toasts.length - max; i++) {\n    dismissToast(viewport, toasts[i].dataset.toastId);\n  }\n}\n\nfunction dismissToast(viewport, id) {\n  if (!viewport || !id) return;\n  const toast = viewport.querySelector(`[data-toast-id=\"${CSS.escape(id)}\"]`);\n  if (!toast) return;\n\n  const store = getStore(viewport);\n  const t = store.timers.get(id);\n  if (t?.timerId) clearTimeout(t.timerId);\n  store.timers.delete(id);\n\n  toast.setAttribute('data-closing', '');\n\n  // Wait for the exit animation, then remove\n  const dur = parseFloat(getComputedStyle(toast).animationDuration) * 1000 || 200;\n  setTimeout(() => toast.remove(), dur);\n}\n\n/* ─── F6: focus viewport globally ─── */\n\nfunction setupGlobalKeys() {\n  if (window.__renToastF6Wired) return;\n  window.__renToastF6Wired = true;\n  document.addEventListener('keydown', (e) => {\n    if (e.key === 'F6') {\n      const viewport = document.querySelector('ren-toast-viewport, .ren-toast-viewport');\n      if (!viewport) return;\n      const firstToast = viewport.querySelector('.ren-toast');\n      if (firstToast) {\n        e.preventDefault();\n        firstToast.focus();\n      }\n    }\n  });\n}\n\nsetupGlobalKeys();\n\n/* ─── Public API ─── */\n\nfunction show(input) {\n  const opts = normalize(input);\n  const viewport = resolveViewport();\n  return showOnViewport(viewport, opts);\n}\n\nfunction variantFn(status) {\n  return function (title, options = {}) {\n    const opts =\n      typeof title === 'string'\n        ? { ...options, title }\n        : { ...title, ...options };\n    return show(normalize(opts, status));\n  };\n}\n\nexport const toast = Object.assign(show, {\n  show,\n  success: variantFn('success'),\n  info: variantFn('info'),\n  warning: variantFn('warning'),\n  danger: variantFn('danger'),\n  // Common alias for users coming from other libs\n  error: variantFn('danger'),\n  loading: variantFn('loading'),\n\n  dismiss(id) {\n    const viewport = resolveViewport();\n    dismissToast(viewport, id);\n  },\n\n  dismissAll() {\n    const viewport = resolveViewport();\n    viewport.querySelectorAll('.ren-toast:not([data-closing])').forEach((t) => {\n      dismissToast(viewport, t.dataset.toastId);\n    });\n  },\n\n  update(id, updates) {\n    const viewport = resolveViewport();\n    const toastEl = viewport.querySelector(`[data-toast-id=\"${CSS.escape(id)}\"]`);\n    if (!toastEl) return;\n    if (updates.status) {\n      toastEl.className = `ren-toast ren-toast-${updates.status}`;\n      toastEl.setAttribute('data-toast-id', id);\n      toastEl.setAttribute(\n        'role',\n        updates.status === 'danger' ? 'alert' : 'status'\n      );\n      const icon = toastEl.querySelector('.ren-toast-icon');\n      if (icon) icon.textContent = ICONS[updates.status] ?? ICONS.info;\n    }\n    if (updates.title !== undefined) {\n      const t = toastEl.querySelector('.ren-toast-title');\n      if (t) t.textContent = updates.title;\n    }\n    if (updates.description !== undefined) {\n      const d = toastEl.querySelector('.ren-toast-description');\n      if (d) d.textContent = updates.description;\n    }\n  },\n\n  promise(promise, options = {}) {\n    const id = generateId();\n    const loadingOpts = normalize(options.loading || 'Loading...', 'loading');\n    loadingOpts.id = id;\n    const viewport = resolveViewport();\n    showOnViewport(viewport, loadingOpts);\n\n    return promise\n      .then((data) => {\n        const successInput =\n          typeof options.success === 'function'\n            ? options.success(data)\n            : options.success;\n        const successOpts = normalize(successInput || 'Done', 'success');\n        successOpts.id = id;\n        showOnViewport(viewport, successOpts);\n        return data;\n      })\n      .catch((err) => {\n        const errorInput =\n          typeof options.error === 'function' ? options.error(err) : options.error;\n        const errorOpts = normalize(errorInput || 'Failed', 'danger');\n        errorOpts.id = id;\n        showOnViewport(viewport, errorOpts);\n        throw err;\n      });\n  },\n});\n\n// Expose globally — the docs show `window.toast.success(...)` as the primary usage.\nif (typeof window !== 'undefined') {\n  window.toast = toast;\n}\n",
      "path": "components/composites/ren-toast",
      "id": "component:composite:ren-toast",
      "type": "component",
      "name": "ren-toast"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-toggle-group",
          ".ren-toggle-group-full",
          ".ren-toggle-group-item",
          ".ren-toggle-group-lg",
          ".ren-toggle-group-sm"
        ],
        "tokens": [
          "--color-accent",
          "--color-border",
          "--color-border-strong",
          "--color-fill",
          "--color-surface",
          "--color-text",
          "--color-text-muted",
          "--font-size-body",
          "--font-size-label",
          "--font-size-xs",
          "--label-size",
          "--label-weight",
          "--radius-lg",
          "--radius-md",
          "--ren-toggle-active-bg",
          "--ren-toggle-active-color",
          "--ren-toggle-bg",
          "--ren-toggle-color",
          "--ren-toggle-font-size",
          "--ren-toggle-font-weight",
          "--ren-toggle-height",
          "--ren-toggle-padding-x",
          "--ren-toggle-radius",
          "--shadow-sm",
          "--size-lg",
          "--space-0-5",
          "--space-1",
          "--space-2",
          "--space-3",
          "--space-4",
          "--touch-min",
          "--transition-tactile"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-toggle-group Component Contract\n\nGrouped toggle controls for mutually exclusive or multi-select modes.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-toggle-group` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-toggle-group` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Toggle Group composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User picks from a small set of mutually exclusive options shown as visually grouped buttons (single mode).\"\n    - \"User toggles multiple independent options that share a control (multiple mode, type=\\\"multiple\\\").\"\n    - \"The choices are short labels or icons (text alignment, view density, list/grid mode) better surfaced than in a select.\"\n    - \"You want a visual variant — default filled pill group or .ren-toggle-group-outline.\"\n    - \"You need size variants (.ren-toggle-group-sm / .ren-toggle-group-lg), full-width fill, or vertical orientation.\"\n  avoidWhen:\n    - \"The choice set is large or sortable — use ren-select.\"\n    - \"The choice is binary on/off for a single concept — use ren-switch or ren-checkbox.\"\n    - \"Items are commands rather than state — use ren-toolbar.\"\n    - \"Items are pages or routes — use ren-tabs or ren-nav.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-toggle-group/ren-toggle-group.css\"\n  js:\n    - \"rends/components/composites/ren-toggle-group/ren-toggle-group.js\"\n  notes:\n    - \"JS is required: it wires role=button, roving tabindex, aria-pressed updates, keyboard navigation, and the ren-toggle-group-change event.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"<ren-toggle-group type=\\\"single|multiple\\\" value=\\\"...\\\"> wraps real <button class=\\\"ren-toggle-group-item\\\" data-value=\\\"...\\\"> elements (the component will also accept [role=button] nodes).\"\n  - \"Each item must carry a data-value attribute or a stable textContent — the component uses it to drive selection and the change event.\"\n  - \"Set type=\\\"single\\\" for mutually-exclusive selection (the component clears other aria-pressed values) or type=\\\"multiple\\\" for independent toggles.\"\n  - \"Variants are applied on the host: .ren-toggle-group-outline, .ren-toggle-group-sm / -lg, .ren-toggle-group-full, .ren-toggle-group-vertical.\"\n  - \"Icons inside items must be <svg> or [role=\\\"img\\\"] children sized to 1em (handled by the CSS); accompany icons with text or aria-label.\"\n\nforbiddenPatterns:\n  - \"<a href> as items — toggle-group is for state, not navigation.\"\n  - \"Setting aria-pressed manually on items; let the component drive it via setValue / click handling.\"\n  - \"Using ren-btn inside the group as items — they bring their own chrome; use plain <button class=\\\"ren-toggle-group-item\\\"> instead.\"\n  - \"Hardcoding the pressed background (background: #fff) — use --color-surface / --color-accent semantic tokens and the outline variant for accent fills.\"\n  - \"Removing the focus-visible outline without restoring an outline-offset visible ring.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-toggle-active-bg, --ren-toggle-active-color, --ren-toggle-bg, --ren-toggle-color, --ren-toggle-font-size, --ren-toggle-font-weight, --ren-toggle-height, --ren-toggle-padding-x, --ren-toggle-radius.\"\n    - \"Semantic tokens: --color-fill, --color-surface, --color-text, --color-text-muted, --color-accent, --color-border, --color-border-strong.\"\n    - \"Shape / motion tokens: --radius-md, --radius-lg, --space-*, --shadow-sm, --touch-min, --transition-tactile, font-size tokens (--font-size-label, --font-size-body, --font-size-xs).\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named color values in overrides for the pressed background or accent border.\"\n    - \"Hardcoded transition durations on item hover; route through --transition-tactile.\"\n\naccessibility:\n  required:\n    - \"Items are real <button> elements with role=\\\"button\\\" and aria-pressed reflecting state; roving tabindex ensures only one item is tabindex=0 at a time.\"\n    - \"Arrow Right/Down moves focus to the next item, Arrow Left/Up to the previous (wrapping); Home / End jump to first / last.\"\n    - \"Touch target meets calc(var(--touch-min) - 4px); .ren-toggle-group-sm should only be used in non-touch contexts.\"\n    - \"Active state communicates via background AND font-weight / box-shadow — never rely on color alone.\"\n    - \"Focus-visible outline is 2px solid var(--color-accent) with outline-offset; preserve it across all variants.\"\n    - \"Disabled items use :disabled with pointer-events: none so they remain in tab order semantics but cannot be activated.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-toggle-group/ren-toggle-group.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-toggle-group/ren-toggle-group.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-toggle-group type=\"single\" value=\"left\"><button class=\"ren-toggle-group-item\" type=\"button\" data-value=\"left\">Left</button><button class=\"ren-toggle-group-item\" type=\"button\" data-value=\"center\">Center</button><button class=\"ren-toggle-group-item\" type=\"button\" data-value=\"right\">Right</button></ren-toggle-group>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-toggle-group`\n- `.ren-toggle-group-full`\n- `.ren-toggle-group-item`\n- `.ren-toggle-group-lg`\n- `.ren-toggle-group-outline`\n- `.ren-toggle-group-sm`\n- `.ren-toggle-group-vertical`\n\n## States And Attributes\n\n- `[aria-pressed]`\n- `[data-state]`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-toggle-active-bg`\n- `--ren-toggle-active-color`\n- `--ren-toggle-bg`\n- `--ren-toggle-color`\n- `--ren-toggle-font-size`\n- `--ren-toggle-font-weight`\n- `--ren-toggle-height`\n- `--ren-toggle-padding-x`\n- `--ren-toggle-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-toggle-group/ren-toggle-group.css`\n- `components/composites/ren-toggle-group/ren-toggle-group.js`\n- `docs/components/ren-toggle-group.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ═══ REN TOGGLE GROUP COMPONENT ═══ */\n\n/* ═══ BASE CONTAINER ═══ */\n.ren-toggle-group {\n  display: inline-flex;\n  gap: var(--space-0-5);\n  background: var(--color-fill);\n  border-radius: var(--radius-lg);\n  padding: 2px;\n  align-items: center;\n\n  & .ren-toggle-group-item {\n    padding: var(--space-1) var(--space-3);\n    border-radius: var(--radius-md);\n    font-size: var(--font-size-label);\n    color: var(--color-text-muted);\n    cursor: pointer;\n    min-height: calc(var(--touch-min, 44px) - 4px);\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    gap: var(--space-1);\n    transition: var(--transition-tactile);\n    user-select: none;\n    border: none;\n    background: transparent;\n    font-family: inherit;\n    font-weight: 400;\n    white-space: nowrap;\n\n    /* ═══ HOVER STATE ═══ */\n    &:hover:not([aria-pressed=\"true\"]) {\n      color: var(--color-text);\n    }\n\n    /* ═══ ACTIVE/PRESSED STATE ═══ */\n    &[aria-pressed=\"true\"],\n    &[data-state=\"on\"] {\n      background: var(--color-surface);\n      color: var(--color-text);\n      font-weight: 500;\n      box-shadow: var(--shadow-sm);\n    }\n\n    /* ═══ FOCUS VISIBLE ═══ */\n    &:focus-visible {\n      outline: 2px solid var(--color-accent);\n      outline-offset: 2px;\n    }\n\n    /* ═══ DISABLED STATE ═══ */\n    &:disabled {\n      opacity: 0.5;\n      cursor: not-allowed;\n      pointer-events: none;\n    }\n  }\n}\n\n/* ═══ OUTLINE VARIANT ═══ */\n.ren-toggle-group.ren-toggle-group-outline {\n  background: transparent;\n  padding: 0;\n  gap: var(--space-1);\n\n  & .ren-toggle-group-item {\n    border: 1px solid var(--color-border);\n    background: transparent;\n    color: var(--color-text-muted);\n\n    &:hover:not([aria-pressed=\"true\"]) {\n      border-color: var(--color-border-strong);\n      color: var(--color-text);\n    }\n\n    &[aria-pressed=\"true\"],\n    &[data-state=\"on\"] {\n      border-color: var(--color-accent);\n      background: color-mix(in srgb, var(--color-accent) 10%, transparent);\n      color: var(--color-accent);\n      font-weight: 500;\n      box-shadow: none;\n    }\n  }\n}\n\n/* ═══ SIZE VARIANTS ═══ */\n.ren-toggle-group {\n  &.ren-toggle-group-sm {\n    gap: 1px;\n    padding: 1px;\n\n    & .ren-toggle-group-item {\n      padding: var(--space-0-5) var(--space-2);\n      font-size: var(--font-size-xs);\n      min-height: calc(var(--touch-min, 44px) - 12px);\n    }\n  }\n\n  &.ren-toggle-group-lg {\n    gap: var(--space-1);\n    padding: var(--space-0-5);\n\n    & .ren-toggle-group-item {\n      padding: var(--space-2) var(--space-4);\n      font-size: var(--font-size-body);\n      min-height: calc(var(--touch-min, 44px) + 4px);\n    }\n  }\n}\n\n/* ═══ FULL WIDTH ═══ */\n.ren-toggle-group.ren-toggle-group-full {\n  display: flex;\n  width: 100%;\n\n  & .ren-toggle-group-item {\n    flex: 1;\n  }\n}\n\n/* ═══ VERTICAL ORIENTATION ═══ */\n.ren-toggle-group.ren-toggle-group-vertical {\n  flex-direction: column;\n  display: inline-flex;\n\n  &.ren-toggle-group-full {\n    display: flex;\n    width: 100%;\n  }\n}\n\n/* ═══ ICON SUPPORT ═══ */\n.ren-toggle-group-item {\n  & [role=\"img\"],\n  & svg {\n    width: 1em;\n    height: 1em;\n    display: inline-block;\n    vertical-align: middle;\n  }\n}\n\n/* ═══ TRANSITIONS ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-toggle-group-item {\n    transition: none;\n  }\n}\n/* Appearance API bridge */\n.ren-toggle-group { min-block-size: var(--ren-toggle-height, var(--size-lg)); padding-inline: var(--ren-toggle-padding-x, var(--space-4)); font-size: var(--ren-toggle-font-size, var(--label-size)); font-weight: var(--ren-toggle-font-weight, var(--label-weight)); border-radius: var(--ren-toggle-radius, var(--radius-md)); background: var(--ren-toggle-bg, var(--color-fill)); color: var(--ren-toggle-color, var(--color-text-muted)); }\n.ren-toggle-group [aria-pressed=\"true\"], .ren-toggle-group [data-state=\"on\"] { background: var(--ren-toggle-active-bg, var(--color-surface)); color: var(--ren-toggle-active-color, var(--color-text)); }\n\n@media (any-pointer: coarse), (pointer: coarse) {\n  .ren-toggle-group.ren-toggle-group-sm .ren-toggle-group-item,\n  .ren-toggle-group .ren-toggle-group-item {\n    min-inline-size: var(--touch-min);\n    min-block-size: var(--touch-min);\n  }\n}\n\n\n/* ═══ REN TOGGLE GROUP WEB COMPONENT ═══ */\n\nexport class RenToggleGroup extends HTMLElement {\n  constructor() {\n    super();\n    this.type = 'single';\n    this.selectedValue = null;\n    this.items = [];\n    this.handleItemClick = this.handleItemClick.bind(this);\n    this.handleKeyDown = this.handleKeyDown.bind(this);\n  }\n\n  connectedCallback() {\n    /* ═══ READ ATTRIBUTES ═══ */\n    this.type = this.getAttribute('type') || 'single';\n    const initialValue = this.getAttribute('value');\n\n    /* ═══ FIND ALL ITEMS ═══ */\n    this.items = Array.from(\n      this.querySelectorAll('.ren-toggle-group-item, [role=\"button\"]')\n    );\n\n    if (this.items.length === 0) {\n      console.warn('RenToggleGroup: No items found');\n      return;\n    }\n\n    /* ═══ SET UP ARIA ATTRIBUTES ═══ */\n    this.items.forEach((item, index) => {\n      item.setAttribute('role', 'button');\n      item.setAttribute('tabindex', index === 0 ? '0' : '-1');\n      item.setAttribute('aria-pressed', 'false');\n\n      /* ═══ ADD CLASSES ═══ */\n      item.classList.add('ren-toggle-group-item');\n\n      /* ═══ EVENT LISTENERS ═══ */\n      item.addEventListener('click', this.handleItemClick);\n      item.addEventListener('keydown', this.handleKeyDown);\n    });\n\n    /* ═══ SET INITIAL VALUE ═══ */\n    if (initialValue) {\n      this.setValue(initialValue);\n    }\n\n    /* ═══ ENSURE CONTAINER HAS CLASS ═══ */\n    this.classList.add('ren-toggle-group');\n  }\n\n  disconnectedCallback() {\n    this.items.forEach((item) => {\n      item.removeEventListener('click', this.handleItemClick);\n      item.removeEventListener('keydown', this.handleKeyDown);\n    });\n  }\n\n  /* ═══ ITEM CLICK HANDLER ═══ */\n  handleItemClick(event) {\n    const item = event.currentTarget;\n    const value = item.getAttribute('data-value') || item.textContent;\n\n    if (this.type === 'single') {\n      this.setValue(value);\n    } else {\n      /* ═══ MULTIPLE MODE: TOGGLE ═══ */\n      const isPressed = item.getAttribute('aria-pressed') === 'true';\n      item.setAttribute('aria-pressed', !isPressed);\n      this.dispatchChangeEvent();\n    }\n  }\n\n  /* ═══ KEYBOARD NAVIGATION ═══ */\n  handleKeyDown(event) {\n    const currentIndex = this.items.indexOf(event.currentTarget);\n    let nextIndex = currentIndex;\n\n    if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {\n      event.preventDefault();\n      nextIndex = (currentIndex + 1) % this.items.length;\n    } else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {\n      event.preventDefault();\n      nextIndex = (currentIndex - 1 + this.items.length) % this.items.length;\n    } else if (event.key === ' ' || event.key === 'Enter') {\n      event.preventDefault();\n      event.currentTarget.click();\n      return;\n    }\n\n    if (nextIndex !== currentIndex) {\n      this.items[currentIndex].setAttribute('tabindex', '-1');\n      const nextItem = this.items[nextIndex];\n      nextItem.setAttribute('tabindex', '0');\n      nextItem.focus();\n    }\n  }\n\n  /* ═══ SET SINGLE VALUE ═══ */\n  setValue(value) {\n    if (this.type !== 'single') {\n      console.warn('RenToggleGroup: setValue only works in single mode');\n      return;\n    }\n\n    /* ═══ CLEAR PREVIOUS SELECTION ═══ */\n    this.items.forEach((item) => {\n      item.setAttribute('aria-pressed', 'false');\n    });\n\n    /* ═══ SET NEW SELECTION ═══ */\n    const targetItem = this.items.find(\n      (item) =>\n        (item.getAttribute('data-value') || item.textContent) === value\n    );\n\n    if (targetItem) {\n      targetItem.setAttribute('aria-pressed', 'true');\n      this.selectedValue = value;\n      this.dispatchChangeEvent();\n    }\n  }\n\n  /* ═══ DISPATCH CHANGE EVENT ═══ */\n  dispatchChangeEvent() {\n    const selectedItems =\n      this.type === 'single'\n        ? [this.items.find((item) => item.getAttribute('aria-pressed') === 'true')]\n        : this.items.filter((item) => item.getAttribute('aria-pressed') === 'true');\n\n    const values = selectedItems\n      .filter(Boolean)\n      .map((item) => item.getAttribute('data-value') || item.textContent);\n\n    this.dispatchEvent(\n      new CustomEvent('ren-toggle-change', {\n        detail: {\n          value: this.type === 'single' ? values[0] : values,\n          items: selectedItems.filter(Boolean),\n        },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ GETTERS AND SETTERS ═══ */\n  get value() {\n    if (this.type === 'single') {\n      return this.selectedValue;\n    } else {\n      return this.items\n        .filter((item) => item.getAttribute('aria-pressed') === 'true')\n        .map((item) => item.getAttribute('data-value') || item.textContent);\n    }\n  }\n\n  set value(val) {\n    if (this.type === 'single') {\n      this.setValue(val);\n    }\n  }\n\n  get disabled() {\n    return this.hasAttribute('disabled');\n  }\n\n  set disabled(val) {\n    if (val) {\n      this.setAttribute('disabled', '');\n      this.items.forEach((item) => item.setAttribute('disabled', ''));\n    } else {\n      this.removeAttribute('disabled');\n      this.items.forEach((item) => item.removeAttribute('disabled'));\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-toggle-group')) {\n  customElements.define('ren-toggle-group', RenToggleGroup);\n}\n",
      "path": "components/composites/ren-toggle-group",
      "id": "component:composite:ren-toggle-group",
      "type": "component",
      "name": "ren-toggle-group"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-toolbar",
          ".ren-toolbar-ghost",
          ".ren-toolbar-item",
          ".ren-toolbar-item-icon",
          ".ren-toolbar-separator",
          ".ren-toolbar-vertical"
        ],
        "tokens": [
          "--caption-size",
          "--color-accent",
          "--color-accent-subtle",
          "--color-border",
          "--color-disabled-text",
          "--color-fill",
          "--color-fill-active",
          "--color-fill-hover",
          "--color-focus-ring",
          "--color-surface",
          "--color-surface-raised",
          "--color-surface-sunken",
          "--color-text",
          "--radius-lg",
          "--radius-md",
          "--radius-sm",
          "--ren-toolbar-active-bg",
          "--ren-toolbar-bg",
          "--ren-toolbar-border",
          "--ren-toolbar-height",
          "--ren-toolbar-item-hover",
          "--ren-toolbar-item-radius",
          "--ren-toolbar-item-size",
          "--ren-toolbar-padding",
          "--ren-toolbar-radius",
          "--ring-width",
          "--shadow-xs",
          "--size-lg",
          "--size-md",
          "--size-sm",
          "--space-1",
          "--space-2",
          "--stroke-1",
          "--text-sm",
          "--transition-tactile",
          "--weight-medium"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-toolbar Component Contract\n\nGrouped command bar with roving keyboard navigation.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-toolbar` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-toolbar` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Toolbar composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a grouped command bar (formatting actions, edit operations, view controls) co-located near content.\"\n    - \"You need the WAI-ARIA Toolbar pattern: Arrow keys move between items, Tab moves focus in / out of the toolbar.\"\n    - \"You need Home / End to jump to first / last item and skip disabled ones.\"\n    - \"Some items are toggleable (aria-pressed / [data-active] for sticky on-states like Bold).\"\n    - \"You need a visual separator (.ren-toolbar-separator) to group related commands within the bar.\"\n    - \"You need icon-only items via .ren-toolbar-item-icon (aspect-ratio 1) or a ghost variant (.ren-toolbar-ghost).\"\n  avoidWhen:\n    - \"Items represent value selection rather than commands — use ren-toggle-group.\"\n    - \"Items represent navigation to other pages — use ren-nav or ren-sidebar.\"\n    - \"You need to switch between content panels — use ren-tabs.\"\n    - \"There is only one action — use ren-button directly.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-toolbar/ren-toolbar.css\"\n  js:\n    - \"rends/components/composites/ren-toolbar/ren-toolbar.js\"\n  notes:\n    - \"JS is required for the roving tabindex pattern: import initToolbar / initAllToolbars from ren-toolbar.js.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"The host is <div class=\\\"ren-toolbar\\\" role=\\\"toolbar\\\" aria-label=\\\"<purpose>\\\">; the role and an aria-label are required so AT can announce it.\"\n  - \"Items are real <button class=\\\"ren-toolbar-item\\\"> with tabindex=\\\"0\\\" on the first item and tabindex=\\\"-1\\\" on the rest (roving tabindex).\"\n  - \"Separators are <div class=\\\"ren-toolbar-separator\\\" role=\\\"separator\\\"> between logical groups within the toolbar.\"\n  - \"Sticky toggle items reflect state via aria-pressed=\\\"true|false\\\" or [data-active]; do not mix the two on the same item.\"\n  - \"Use .ren-toolbar-vertical on the host to switch arrow keys to Up/Down and turn separators horizontal.\"\n\nforbiddenPatterns:\n  - \"Omitting role=\\\"toolbar\\\" or aria-label on the host — initToolbar bails out and the keyboard pattern will not attach.\"\n  - \"Setting tabindex=\\\"0\\\" on every item — only the focused item should be tabindex=\\\"0\\\"; the JS rewrites the rest to -1.\"\n  - \"Using <a href> as items — toolbar items invoke commands, not navigate; use ren-nav for links.\"\n  - \"Hardcoded gap / padding in inline styles; use --space-1 via the documented selectors.\"\n  - \"Mounting toolbar buttons as .ren-btn — toolbar owns its own item chrome; mixing the two doubles the focus rings and padding.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-toolbar-active-bg, --ren-toolbar-bg, --ren-toolbar-border, --ren-toolbar-height, --ren-toolbar-item-hover, --ren-toolbar-item-radius, --ren-toolbar-item-size, --ren-toolbar-padding, --ren-toolbar-radius.\"\n    - \"Semantic tokens: --color-text, --color-accent, --color-border, --color-fill-hover, --color-fill-active, --color-surface, --color-surface-raised, --color-surface-sunken, --color-disabled-text, --color-focus-ring.\"\n    - \"Shape / motion tokens: --radius-sm, --radius-md, --space-1, --space-2, --size-sm, --stroke-1, --ring-width, --shadow-xs, --transition-tactile, --weight-medium, --caption-size, --text-sm.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named color values in overrides for the pressed-state background or active accent color.\"\n    - \"Hardcoded transition durations on item hover; route through --transition-tactile.\"\n\naccessibility:\n  required:\n    - \"Host carries role=\\\"toolbar\\\" plus an aria-label describing its purpose (e.g. \\\"Formatting\\\").\"\n    - \"Roving tabindex: only one .ren-toolbar-item is tabindex=\\\"0\\\" at any time; Tab enters/exits the toolbar instead of cycling items.\"\n    - \"Arrow Left/Right (horizontal) or Up/Down (.ren-toolbar-vertical) moves focus; Home / End jump to first / last; disabled items are skipped.\"\n    - \"Toggle items expose aria-pressed and a visible non-color cue (box-shadow + accent color) in addition to background fill.\"\n    - \"Focus-visible ring is var(--ring-width) solid var(--color-focus-ring) with outline-offset: -1px; preserve it across variants.\"\n    - \"Disabled items use :disabled or aria-disabled=\\\"true\\\"; both block keyboard activation and are filtered out of the focus rotation.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-toolbar/ren-toolbar.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-toolbar/ren-toolbar.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div class=\"ren-toolbar\" role=\"toolbar\" aria-label=\"Formatting\"><button class=\"ren-toolbar-item\" type=\"button\" tabindex=\"0\">Bold</button><div class=\"ren-toolbar-separator\" role=\"separator\"></div><button class=\"ren-toolbar-item\" type=\"button\" tabindex=\"-1\">Italic</button></div>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-toolbar`\n- `.ren-toolbar-ghost`\n- `.ren-toolbar-item`\n- `.ren-toolbar-item-icon`\n- `.ren-toolbar-separator`\n- `.ren-toolbar-vertical`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[aria-pressed]`\n- `[data-active]`\n- `:active`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-toolbar-active-bg`\n- `--ren-toolbar-bg`\n- `--ren-toolbar-border`\n- `--ren-toolbar-height`\n- `--ren-toolbar-item-hover`\n- `--ren-toolbar-item-radius`\n- `--ren-toolbar-item-size`\n- `--ren-toolbar-padding`\n- `--ren-toolbar-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-toolbar/ren-toolbar.css`\n- `components/composites/ren-toolbar/ren-toolbar.js`\n- `docs/components/ren-toolbar.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Toolbar Component\n   ============================================\n   Grouped actions with roving tabindex pattern.\n   CSS-only for layout. JS only for keyboard nav.\n\n   Follows WAI-ARIA Toolbar pattern:\n   - Arrow keys move between items\n   - Tab moves focus in/out of toolbar\n   - Home/End go to first/last item\n\n   Usage:\n     <div class=\"ren-toolbar\" role=\"toolbar\" aria-label=\"Formatting\">\n       <button class=\"ren-toolbar-item\" tabindex=\"0\">Bold</button>\n       <button class=\"ren-toolbar-item\" tabindex=\"-1\">Italic</button>\n       <button class=\"ren-toolbar-item\" tabindex=\"-1\">Underline</button>\n       <div class=\"ren-toolbar-separator\" role=\"separator\"></div>\n       <button class=\"ren-toolbar-item\" tabindex=\"-1\">Left</button>\n       <button class=\"ren-toolbar-item\" tabindex=\"-1\">Center</button>\n       <button class=\"ren-toolbar-item\" tabindex=\"-1\">Right</button>\n     </div>\n\n   Note: Set tabindex=\"0\" on first item, \"-1\" on rest.\n   ren-toolbar.js handles roving tabindex automatically.\n   ============================================ */\n\n.ren-toolbar {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-1);\n  padding: var(--space-1);\n  background-color: var(--color-surface-sunken);\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-md);\n}\n\n/* ─── Toolbar Items ─── */\n.ren-toolbar-item {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  min-width: var(--size-sm);\n  min-height: var(--size-sm);\n  padding: var(--space-1) var(--space-2);\n  border: none;\n  background: none;\n  color: var(--color-text);\n  font-size: var(--caption-size, var(--text-sm));\n  font-weight: var(--weight-medium);\n  cursor: pointer;\n  border-radius: var(--radius-sm);\n  transition: var(--transition-tactile);\n}\n\n.ren-toolbar-item:hover {\n  background-color: var(--color-fill-hover);\n}\n\n.ren-toolbar-item:active {\n  background-color: var(--color-fill-active);\n}\n\n.ren-toolbar-item:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: -1px;\n}\n\n/* Active / Pressed state */\n.ren-toolbar-item[aria-pressed=\"true\"],\n.ren-toolbar-item[data-active] {\n  background-color: var(--color-surface-raised, var(--color-surface));\n  box-shadow: var(--shadow-xs);\n  color: var(--color-accent);\n}\n\n/* Disabled */\n.ren-toolbar-item:disabled,\n.ren-toolbar-item[aria-disabled=\"true\"] {\n  color: var(--color-disabled-text);\n  cursor: not-allowed;\n}\n\n/* ─── Separator ─── */\n.ren-toolbar-separator {\n  width: 1px;\n  height: 1.25rem;\n  background-color: var(--color-border);\n  margin-inline: var(--space-1);\n  flex-shrink: 0;\n}\n\n/* ─── Icon-only items ─── */\n.ren-toolbar-item-icon {\n  min-width: var(--size-sm);\n  padding: var(--space-1);\n  aspect-ratio: 1;\n}\n\n/* ─── Variants ─── */\n\n/* Ghost — no background on container */\n.ren-toolbar-ghost {\n  background: none;\n  border: none;\n  padding: 0;\n  gap: var(--space-1);\n}\n\n/* Vertical toolbar */\n.ren-toolbar-vertical {\n  flex-direction: column;\n}\n\n.ren-toolbar-vertical .ren-toolbar-separator {\n  width: auto;\n  height: 1px;\n  align-self: stretch;\n  margin-block: var(--space-1);\n  margin-inline: 0;\n}\n/* Appearance API bridge */\n.ren-toolbar { min-block-size: var(--ren-toolbar-height, var(--size-lg)); padding: var(--ren-toolbar-padding, var(--space-1)); border-radius: var(--ren-toolbar-radius, var(--radius-lg)); background: var(--ren-toolbar-bg, var(--color-surface-raised)); border-color: var(--ren-toolbar-border, var(--color-border)); }\n.ren-toolbar button, .ren-toolbar [role=\"button\"] { inline-size: var(--ren-toolbar-item-size, var(--size-md)); block-size: var(--ren-toolbar-item-size, var(--size-md)); border-radius: var(--ren-toolbar-item-radius, var(--radius-sm)); }\n.ren-toolbar button:hover, .ren-toolbar [role=\"button\"]:hover { background: var(--ren-toolbar-item-hover, var(--color-fill)); }\n.ren-toolbar button[aria-pressed=\"true\"], .ren-toolbar [data-state=\"on\"] { background: var(--ren-toolbar-active-bg, var(--color-accent-subtle)); }\n\n\n/* ============================================\n   RenDS — Toolbar Keyboard Navigation\n   ============================================\n   Implements WAI-ARIA Toolbar pattern:\n   - Arrow Left/Right: move between items\n   - Home: first item\n   - End: last item\n   - Tab: exits toolbar\n\n   Usage:\n     import { initToolbar } from './ren-toolbar.js';\n     initToolbar(document.querySelector('.ren-toolbar'));\n\n   Or auto-init all:\n     import { initAllToolbars } from './ren-toolbar.js';\n     initAllToolbars();\n   ============================================ */\n\n/**\n * @param {HTMLElement} toolbar\n */\nconst toolbarControllers = new WeakMap();\n\nexport function initToolbar(toolbar) {\n  if (!toolbar || toolbar.getAttribute('role') !== 'toolbar') return;\n\n  toolbarControllers.get(toolbar)?.abort();\n  const controller = new AbortController();\n  toolbarControllers.set(toolbar, controller);\n\n  const getItems = () =>\n    [...toolbar.querySelectorAll('.ren-toolbar-item:not(:disabled):not([aria-disabled=\"true\"])')];\n\n  const isVertical = toolbar.classList.contains('ren-toolbar-vertical');\n  const prevKey = isVertical ? 'ArrowUp' : 'ArrowLeft';\n  const nextKey = isVertical ? 'ArrowDown' : 'ArrowRight';\n\n  toolbar.addEventListener('keydown', (e) => {\n    const items = getItems();\n    const current = items.indexOf(document.activeElement);\n    if (current === -1) return;\n\n    let next = -1;\n\n    if (e.key === nextKey) {\n      next = (current + 1) % items.length;\n    } else if (e.key === prevKey) {\n      next = (current - 1 + items.length) % items.length;\n    } else if (e.key === 'Home') {\n      next = 0;\n    } else if (e.key === 'End') {\n      next = items.length - 1;\n    }\n\n    if (next !== -1) {\n      e.preventDefault();\n      items.forEach((item) => item.setAttribute('tabindex', '-1'));\n      items[next].setAttribute('tabindex', '0');\n      items[next].focus();\n    }\n  }, { signal: controller.signal });\n}\n\n/**\n * Auto-init all toolbars in the document.\n */\nexport function initAllToolbars() {\n  document.querySelectorAll('[role=\"toolbar\"]').forEach(initToolbar);\n}\n",
      "path": "components/composites/ren-toolbar",
      "id": "component:composite:ren-toolbar",
      "type": "component",
      "name": "ren-toolbar"
    },
    {
      "data": {
        "kind": "composite",
        "selectors": [
          ".ren-tooltip",
          ".ren-tooltip-arrow",
          ".ren-tooltip-trigger"
        ],
        "tokens": [
          "--caption-sm-size",
          "--color-text",
          "--color-text-inverted",
          "--duration-enter",
          "--duration-fast",
          "--ease-enter",
          "--ease-out",
          "--radius-md",
          "--ren-tooltip-bg",
          "--ren-tooltip-color",
          "--ren-tooltip-duration",
          "--ren-tooltip-easing",
          "--ren-tooltip-font-size",
          "--ren-tooltip-padding-x",
          "--ren-tooltip-padding-y",
          "--ren-tooltip-radius",
          "--ren-tooltip-shadow",
          "--shadow-sm",
          "--size-caption",
          "--space-1",
          "--space-2",
          "--space-3",
          "--z-tooltip"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-tooltip Component Contract\n\nShort accessible description attached to a trigger.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-tooltip` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-tooltip` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Tooltip composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a short, non-interactive description attached to a hoverable / focusable trigger (icon button label, truncated text expansion).\"\n    - \"The content is plain text under ~15rem and provides supplementary info, not essential interaction.\"\n    - \"Disclosure is driven by hover, focus, and the popover API with anchor positioning + flip-block fallback.\"\n    - \"You need the dark high-contrast pill with an optional arrow caret (.ren-tooltip-arrow).\"\n    - \"You can place [data-side=\\\"top|right|bottom|left\\\"] on the .ren-tooltip to indicate preferred side; CSS handles flipping.\"\n  avoidWhen:\n    - \"The content is interactive (links, buttons, form fields) — use ren-popover or ren-hover-card.\"\n    - \"The content is critical for completing the task — use inline helper text or a visible label.\"\n    - \"The disclosure is value selection — use ren-select or ren-menu.\"\n    - \"The trigger has no accessible name and the tooltip is the only label — give the trigger a real aria-label or visible text instead.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-tooltip/ren-tooltip.css\"\n  js:\n    - \"rends/components/composites/ren-tooltip/ren-tooltip.js\"\n  notes:\n    - \"JS is required: it owns the popover toggle, hover/focus timing (--ren-tooltip-delay), and the fallback positioning for browsers without CSS anchor-name support.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Pair a .ren-tooltip-trigger (real <button> or focusable element) with a .ren-tooltip element referenced via the popover API (popovertarget / id linkage).\"\n  - \"The tooltip carries popover attribute so it lives in the top layer; do not implement it as a plain absolutely-positioned <div>.\"\n  - \"Include .ren-tooltip-arrow as a child of .ren-tooltip when an arrow is desired; position is driven by [data-side].\"\n  - \"Set the desired side on the tooltip via data-side=\\\"top|right|bottom|left\\\"; CSS uses position-area + position-try-fallbacks: flip-block for safe placement.\"\n  - \"Tooltip content must be plain text — no buttons, links, or form fields (those break the contract; use ren-popover instead).\"\n\nforbiddenPatterns:\n  - \"Putting interactive elements inside .ren-tooltip — pointer-events: none on the tooltip means clicks would not work and screen readers would not focus them anyway.\"\n  - \"Showing the tooltip only on hover without keyboard focus support — every trigger must reveal the tooltip on :focus-visible.\"\n  - \"Using a tooltip as the sole accessible name for an icon button — provide aria-label on the trigger and let the tooltip echo it.\"\n  - \"Animating the tooltip via custom @keyframes; rely on the documented opacity + transform transition with @starting-style.\"\n  - \"Hardcoding background / color on .ren-tooltip — override --ren-tooltip-bg / --ren-tooltip-color tokens instead.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-tooltip-bg, --ren-tooltip-color, --ren-tooltip-delay, --ren-tooltip-duration, --ren-tooltip-easing, --ren-tooltip-font-size, --ren-tooltip-padding-x, --ren-tooltip-padding-y, --ren-tooltip-radius, --ren-tooltip-shadow.\"\n    - \"Shape / motion tokens: --radius-md, --space-1, --space-2, --size-caption, --duration-enter, --ease-enter, --z-tooltip.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code. Note: the current default uses --color-gray-900 as the surface; consumers should override --ren-tooltip-bg with a semantic token (e.g. --color-text) rather than reaching for another --gray-* directly.\"\n    - \"Hardcoded hex / rgb / named color values (e.g. background: #111, color: white) in overrides — use --ren-tooltip-bg and --ren-tooltip-color.\"\n    - \"Hardcoded transition durations; route through --duration-enter / --ren-tooltip-duration and the paired easings.\"\n\naccessibility:\n  required:\n    - \"Trigger has its own accessible name (visible text or aria-label); the tooltip supplements but does not replace it.\"\n    - \"Tooltip opens on both :hover and keyboard :focus-visible; closing on blur / mouseleave / Escape.\"\n    - \"Hover delay respects --ren-tooltip-delay; do not show tooltips instantly on mouse jitter or hide them on the next mousemove.\"\n    - \"Tooltip text must meet contrast for the chosen --ren-tooltip-bg / --ren-tooltip-color pair (default dark bg / white text is WCAG AA against typical surfaces).\"\n    - \"Touch users: tooltips should not be the only way to reveal essential info — provide a visible label or alternate disclosure for touch.\"\n    - \"Tooltip is pointer-events: none and never receives focus — if the content needs interaction, switch to ren-popover.\"\n    - \"The component appends its ID to any existing aria-describedby token list and removes only that owned token on disconnect.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-tooltip/ren-tooltip.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-tooltip/ren-tooltip.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<button class=\"ren-tooltip-trigger\" type=\"button\" aria-label=\"More information\">Info<ren-tooltip id=\"info-tip\" placement=\"top\">More information</ren-tooltip></button>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-open`\n- `.ren-tooltip`\n- `.ren-tooltip-arrow`\n- `.ren-tooltip-trigger`\n\n## States And Attributes\n\n- `[data-side]`\n\n## Public Token API\n\n- `--ren-tooltip-bg`\n- `--ren-tooltip-color`\n- `--ren-tooltip-delay`\n- `--ren-tooltip-duration`\n- `--ren-tooltip-easing`\n- `--ren-tooltip-font-size`\n- `--ren-tooltip-padding-x`\n- `--ren-tooltip-padding-y`\n- `--ren-tooltip-radius`\n- `--ren-tooltip-shadow`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-tooltip/ren-tooltip.css`\n- `components/composites/ren-tooltip/ren-tooltip.js`\n- `docs/components/ren-tooltip.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ═══ ANCHOR POSITIONING & BASE STYLES ═══ */\n.ren-tooltip {\n  position: absolute;\n  background: var(--ren-tooltip-bg, var(--color-text));\n  color: var(--ren-tooltip-color, var(--color-text-inverted));\n  border-radius: var(--radius-md);\n  padding: var(--space-1) var(--space-2);\n  font-size: var(--size-caption, 0.75rem);\n  max-width: 15rem;\n  pointer-events: none;\n  z-index: var(--z-tooltip, 2000);\n  overflow-wrap: break-word;\n  white-space: normal;\n  line-height: 1.4;\n  margin: 0;\n  inset: auto;\n  overflow: visible;\n\n  /* Anchor positioning (modern browsers) */\n  position-anchor: --tooltip-anchor;\n\n  /* Default: above the trigger with automatic fallback */\n  position-area: top span-all;\n\n  /* Automatic flip when near viewport edges */\n  position-try-fallbacks: flip-block;\n\n  /* Smooth transitions — semantic tokens keep tooltip in sync with the\n     rest of the overlay family (popover, menu, dialog). Collapses to\n     0ms under prefers-reduced-motion via tokens/semantic/motion.css. */\n  opacity: 0;\n  transform: translateY(4px) scale(0.95);\n  transition:\n    opacity var(--duration-enter) var(--ease-enter),\n    transform var(--duration-enter) var(--ease-enter),\n    overlay var(--duration-enter) var(--ease-enter) allow-discrete,\n    display var(--duration-enter) var(--ease-enter) allow-discrete;\n}\n\n/* ═══ TOOLTIP OPEN STATE ═══ */\n.ren-tooltip:popover-open,\n.ren-tooltip.ren-open {\n  opacity: 1;\n  transform: translateY(0) scale(1);\n}\n\n/* ═══ TOOLTIP OPEN WITH @starting-style ═══ */\n@starting-style {\n  .ren-tooltip:popover-open {\n    opacity: 0;\n    transform: translateY(4px) scale(0.95);\n  }\n}\n\n/* ═══ TOOLTIP ARROW/CARET ═══ */\n.ren-tooltip-arrow {\n  position: absolute;\n  width: 6px;\n  height: 6px;\n  background: var(--ren-tooltip-bg, var(--color-text));\n  transform: rotate(45deg);\n  z-index: -1;\n}\n\n/* ═══ PLACEMENT VARIANTS ═══ */\n/* Bottom */\n.ren-tooltip[data-side=\"bottom\"] {\n  position-area: bottom span-all;\n  margin-block-start: var(--space-1);\n  margin-block-end: 0;\n  margin-inline: 0;\n\n  & .ren-tooltip-arrow {\n    top: -3px;\n    left: 50%;\n    transform: translateX(-50%) rotate(225deg);\n  }\n}\n\n/* Top (default) */\n.ren-tooltip[data-side=\"top\"],\n.ren-tooltip:not([data-side]) {\n  position-area: top span-all;\n  margin-block-start: 0;\n  margin-block-end: var(--space-1);\n  margin-inline: 0;\n\n  & .ren-tooltip-arrow {\n    bottom: -3px;\n    left: 50%;\n    transform: translateX(-50%) rotate(45deg);\n  }\n}\n\n/* Right */\n.ren-tooltip[data-side=\"right\"] {\n  position-area: right span-all;\n  margin-block: 0;\n  margin-inline-start: var(--space-1);\n  margin-inline-end: 0;\n\n  & .ren-tooltip-arrow {\n    left: -3px;\n    top: 50%;\n    transform: translateY(-50%) rotate(135deg);\n  }\n}\n\n/* Left */\n.ren-tooltip[data-side=\"left\"] {\n  position-area: left span-all;\n  margin-block: 0;\n  margin-inline-start: 0;\n  margin-inline-end: var(--space-1);\n\n  & .ren-tooltip-arrow {\n    right: -3px;\n    top: 50%;\n    transform: translateY(-50%) rotate(315deg);\n  }\n}\n\n/* ═══ RESPECTS MOTION PREFERENCES ═══\n   Semantic tokens already collapse to 0ms under reduce, but we keep\n   this belt-and-suspenders block so the transform-half can be skipped\n   entirely (no scale bounce) while overlay/display discrete swaps\n   still happen. */\n@media (prefers-reduced-motion: reduce) {\n  .ren-tooltip {\n    transition: opacity var(--duration-enter), transform var(--duration-enter),\n                overlay var(--duration-enter) allow-discrete,\n                display var(--duration-enter) allow-discrete;\n  }\n\n  @starting-style {\n    .ren-tooltip:popover-open {\n      opacity: 0;\n      transform: scale(1);\n    }\n  }\n}\n\n/* ═══ FALLBACK FOR BROWSERS WITHOUT COMPLETE ANCHOR POSITIONING ═══ */\n@supports not ((anchor-name: --ren-anchor) and\n  (position-anchor: --ren-anchor) and\n  (position-area: top span-all)) {\n  .ren-tooltip {\n    position: absolute;\n    inset: auto auto auto auto;\n  }\n\n  .ren-tooltip-trigger {\n    position: relative;\n  }\n\n  /* Fallback animations */\n  .ren-tooltip {\n    animation: ren-tooltip-fallback-open var(--duration-enter) var(--ease-enter);\n  }\n\n  .ren-tooltip:popover-open,\n  .ren-tooltip.ren-open {\n    animation: ren-tooltip-fallback-open var(--duration-enter) var(--ease-enter);\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    .ren-tooltip {\n      animation: none;\n    }\n  }\n}\n\n@keyframes ren-tooltip-fallback-open {\n  from {\n    opacity: 0;\n    transform: translateY(4px) scale(0.95);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0) scale(1);\n  }\n}\n\n/* Appearance API bridge */\n.ren-tooltip {\n  background: var(--ren-tooltip-bg, var(--color-text));\n  color: var(--ren-tooltip-color, var(--color-text-inverted));\n  font-size: var(--ren-tooltip-font-size, var(--caption-sm-size));\n  padding-inline: var(--ren-tooltip-padding-x, var(--space-3));\n  padding-block: var(--ren-tooltip-padding-y, var(--space-1));\n  border-radius: var(--ren-tooltip-radius, var(--radius-md));\n  box-shadow: var(--ren-tooltip-shadow, var(--shadow-sm));\n  transition-duration: var(--ren-tooltip-duration, var(--duration-fast));\n  transition-timing-function: var(--ren-tooltip-easing, var(--ease-out));\n}\n\n\nconst PLACEMENTS = new Set(['top', 'right', 'bottom', 'left']);\n\nfunction supportsAnchorPositioning() {\n  return (\n    typeof CSS !== 'undefined' &&\n    typeof CSS.supports === 'function' &&\n    CSS.supports('anchor-name', '--ren-anchor') &&\n    CSS.supports('position-anchor', '--ren-anchor') &&\n    CSS.supports('position-area', 'top span-all')\n  );\n}\n\nfunction normalizePlacement(value, fallback = 'top') {\n  return PLACEMENTS.has(value) ? value : fallback;\n}\n\n/**\n * Fallback position computation for browsers without CSS anchor positioning.\n * Used only when full CSS anchor positioning support is unavailable.\n *\n * @param {HTMLElement} trigger - The trigger element\n * @param {HTMLElement} tooltip - The tooltip element\n * @param {string} placement - Placement: 'top', 'right', 'bottom', 'left'\n * @param {number} offset - Offset in pixels between trigger and tooltip\n * @returns {Object} Position object with x, y, and finalPlacement properties\n */\nfunction computePosition(trigger, tooltip, placement = 'top', offset = 8) {\n  const triggerRect = trigger.getBoundingClientRect();\n  const tooltipRect = tooltip.getBoundingClientRect();\n  const viewport = { width: window.innerWidth, height: window.innerHeight };\n\n  let x = 0;\n  let y = 0;\n  let finalPlacement = placement;\n\n  const placements = {\n    top: () => {\n      x = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;\n      y = triggerRect.top - tooltipRect.height - offset;\n      if (y < 0) {\n        finalPlacement = 'bottom';\n        return placements.bottom();\n      }\n      return { x, y };\n    },\n    bottom: () => {\n      x = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;\n      y = triggerRect.bottom + offset;\n      if (y + tooltipRect.height > viewport.height) {\n        finalPlacement = 'top';\n        return placements.top();\n      }\n      return { x, y };\n    },\n    left: () => {\n      x = triggerRect.left - tooltipRect.width - offset;\n      y = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;\n      if (x < 0) {\n        finalPlacement = 'right';\n        return placements.right();\n      }\n      return { x, y };\n    },\n    right: () => {\n      x = triggerRect.right + offset;\n      y = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;\n      if (x + tooltipRect.width > viewport.width) {\n        finalPlacement = 'left';\n        return placements.left();\n      }\n      return { x, y };\n    },\n  };\n\n  const result = placements[placement]?.() || placements.top();\n\n  // Clamp X position within viewport\n  if (result.x < 0) result.x = 8;\n  else if (result.x + tooltipRect.width > viewport.width)\n    result.x = viewport.width - tooltipRect.width - 8;\n\n  return { ...result, finalPlacement };\n}\n\n/**\n * Tooltip component with CSS Anchor Positioning and native Popover API\n *\n * Lightweight tooltip that displays helpful text on hover/focus with automatic\n * viewport collision handling via CSS anchor positioning.\n *\n * @example\n * <button>Hover me\n *   <ren-tooltip placement=\"top\" show-delay=\"500\">Helpful text</ren-tooltip>\n * </button>\n *\n * @fires ren-open - Fired when tooltip opens\n * @fires ren-close - Fired when tooltip closes\n */\nexport class RenTooltip extends HTMLElement {\n  static observedAttributes = ['placement'];\n  static supportsAnchor = supportsAnchorPositioning();\n\n  #trigger = null;\n  #showTimeout = null;\n  #hideTimeout = null;\n  #touchTimer = null;\n  #listenerController = null;\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'placement' && oldValue !== newValue) {\n      this.#syncPlacement();\n    }\n  }\n\n  connectedCallback() {\n    this.setupTooltip();\n    this.findTrigger();\n    this.attachTriggerListeners();\n  }\n\n  disconnectedCallback() {\n    this.cleanup();\n  }\n\n  /**\n   * Setup tooltip element with required attributes and structure\n   * @private\n   */\n  setupTooltip() {\n    this.classList.add('ren-tooltip');\n\n    // Add arrow if not present\n    if (!this.querySelector('.ren-tooltip-arrow')) {\n      const arrow = document.createElement('div');\n      arrow.className = 'ren-tooltip-arrow';\n      this.appendChild(arrow);\n    }\n\n    // Set accessibility attributes\n    this.setAttribute('role', 'tooltip');\n    this.id = this.id || `ren-tooltip-${Math.random().toString(36).substr(2, 9)}`;\n\n    // Setup native Popover API with manual mode\n    if ('popover' in HTMLElement.prototype) {\n      this.setAttribute('popover', 'manual');\n    }\n\n    this.#syncPlacement();\n  }\n\n  /**\n   * Find the trigger element (parent that contains the tooltip)\n   * @private\n   */\n  findTrigger() {\n    this.#trigger = this.parentElement;\n\n    if (this.#trigger) {\n      // Wire aria-describedby relationship\n      const describedBy = new Set(\n        (this.#trigger.getAttribute('aria-describedby') || '').split(/\\s+/).filter(Boolean)\n      );\n      describedBy.add(this.id);\n      this.#trigger.setAttribute('aria-describedby', [...describedBy].join(' '));\n\n      // Set up anchor relationship if CSS anchors are supported\n      if (RenTooltip.supportsAnchor) {\n        this.#trigger.style.anchorName = '--tooltip-anchor';\n      } else {\n        // Fallback: ensure trigger can be positioned relative to\n        if (getComputedStyle(this.#trigger).position === 'static') {\n          this.#trigger.style.position = 'relative';\n        }\n      }\n    }\n  }\n\n  /**\n   * Attach event listeners to trigger\n   * @private\n   */\n  attachTriggerListeners() {\n    if (!this.#trigger) return;\n\n    this.#listenerController?.abort();\n    this.#listenerController = new AbortController();\n    const { signal } = this.#listenerController;\n\n    // Mouse events\n    this.#trigger.addEventListener('mouseenter', () => this.scheduleShow(), { signal });\n    this.#trigger.addEventListener('mouseleave', () => this.scheduleHide(), { signal });\n\n    // Focus events (use capture for better timing)\n    this.#trigger.addEventListener('focus', () => this.scheduleShow(), { capture: true, signal });\n    this.#trigger.addEventListener('blur', () => this.scheduleHide(), { capture: true, signal });\n\n    // Touch events for long-press detection\n    this.#trigger.addEventListener('touchstart', (e) => this.handleTouchStart(e), { signal });\n    this.#trigger.addEventListener('touchend', (e) => this.handleTouchEnd(e), { signal });\n  }\n\n  /**\n   * Handle touch start (long press detection)\n   * @private\n   */\n  handleTouchStart(e) {\n    this.#touchTimer = setTimeout(() => {\n      this.show();\n    }, 500);\n  }\n\n  /**\n   * Handle touch end\n   * @private\n   */\n  handleTouchEnd(e) {\n    if (this.#touchTimer) {\n      clearTimeout(this.#touchTimer);\n      this.#touchTimer = null;\n    }\n\n    if (this.isOpen()) {\n      this.hide();\n    }\n  }\n\n  /**\n   * Schedule tooltip show with delay\n   * @private\n   */\n  scheduleShow() {\n    this.clearTimeouts();\n\n    // Reduced-motion users should not wait through an animation-oriented\n    // reveal delay; the tooltip remains available on focus/hover.\n    if (typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches) {\n      this.show();\n      return;\n    }\n\n    const tokenDelay = getComputedStyle(this).getPropertyValue('--ren-tooltip-delay').trim();\n    const attrDelay = this.getAttribute('show-delay');\n    const parsedAttrDelay = attrDelay == null ? Number.NaN : parseInt(attrDelay, 10);\n    const parsedTokenDelay = parseInt(tokenDelay, 10);\n    const delay = Number.isFinite(parsedAttrDelay)\n      ? parsedAttrDelay\n      : Number.isFinite(parsedTokenDelay)\n        ? parsedTokenDelay\n        : 500;\n    this.#showTimeout = setTimeout(() => {\n      this.show();\n    }, delay);\n  }\n\n  /**\n   * Schedule tooltip hide with delay\n   * @private\n   */\n  scheduleHide() {\n    this.clearTimeouts();\n\n    const delay = parseInt(this.getAttribute('hide-delay')) || 0;\n    this.#hideTimeout = setTimeout(() => {\n      this.hide();\n    }, delay);\n  }\n\n  /**\n   * Clear all pending timeouts\n   * @private\n   */\n  clearTimeouts() {\n    if (this.#showTimeout) clearTimeout(this.#showTimeout);\n    if (this.#hideTimeout) clearTimeout(this.#hideTimeout);\n    if (this.#touchTimer) clearTimeout(this.#touchTimer);\n\n    this.#showTimeout = null;\n    this.#hideTimeout = null;\n    this.#touchTimer = null;\n  }\n\n  /**\n   * Position the tooltip relative to trigger (fallback for non-anchor browsers)\n   * @private\n   */\n  positionTooltip() {\n    if (!this.#trigger || RenTooltip.supportsAnchor) return;\n\n    const placement = normalizePlacement(this.getAttribute('placement'), 'top');\n    const offset = parseInt(this.getAttribute('offset')) || 8;\n\n    const { x, y, finalPlacement } = computePosition(\n      this.#trigger,\n      this,\n      placement,\n      offset\n    );\n\n    this.style.left = `${x}px`;\n    this.style.top = `${y}px`;\n    this.setAttribute('data-side', finalPlacement);\n  }\n\n  #syncPlacement() {\n    const placement = normalizePlacement(this.getAttribute('placement'), 'top');\n    this.setAttribute('data-side', placement);\n  }\n\n  /**\n   * Show the tooltip\n   */\n  show() {\n    if (this.isOpen()) return;\n\n    this.clearTimeouts();\n    this.positionTooltip();\n    this.setAttribute('data-state', 'open');\n\n    if ('popover' in HTMLElement.prototype) {\n      try {\n        this.showPopover();\n      } catch {\n        // Already open or other error\n      }\n    } else {\n      this.classList.add('ren-open');\n    }\n\n    this.dispatchEvent(new CustomEvent('ren-open', { bubbles: true }));\n  }\n\n  /**\n   * Hide the tooltip\n   */\n  hide() {\n    if (!this.isOpen()) return;\n\n    this.clearTimeouts();\n    this.setAttribute('data-state', 'closed');\n\n    if ('popover' in HTMLElement.prototype) {\n      try {\n        this.hidePopover();\n      } catch {\n        // Already closed or other error\n      }\n    } else {\n      this.classList.remove('ren-open');\n    }\n\n    this.dispatchEvent(new CustomEvent('ren-close', { bubbles: true }));\n  }\n\n  /**\n   * Check if tooltip is currently open\n   * @returns {boolean}\n   */\n  isOpen() {\n    if ('popover' in HTMLElement.prototype) {\n      return this.matches(':popover-open');\n    }\n    return this.classList.contains('ren-open');\n  }\n\n  /**\n   * Cleanup event listeners and timeouts\n   * @private\n   */\n  cleanup() {\n    this.clearTimeouts();\n    this.#listenerController?.abort();\n    this.#listenerController = null;\n    if (this.#trigger) {\n      const describedBy = (this.#trigger.getAttribute('aria-describedby') || '')\n        .split(/\\s+/)\n        .filter((token) => token && token !== this.id);\n      if (describedBy.length > 0) {\n        this.#trigger.setAttribute('aria-describedby', describedBy.join(' '));\n      } else {\n        this.#trigger.removeAttribute('aria-describedby');\n      }\n    }\n  }\n\n  /**\n   * Get the trigger element\n   * @returns {HTMLElement|null}\n   */\n  getTrigger() {\n    return this.#trigger;\n  }\n}\n\nif (!customElements.get('ren-tooltip')) {\n  customElements.define('ren-tooltip', RenTooltip);\n}\n",
      "path": "components/composites/ren-tooltip",
      "id": "component:composite:ren-tooltip",
      "type": "component",
      "name": "ren-tooltip"
    },
    {
      "data": {
        "kind": "pattern",
        "selectors": [
          ".ren-ai-action",
          ".ren-ai-action-edit",
          ".ren-ai-action-regenerate",
          ".ren-ai-action-thumbs",
          ".ren-ai-actions",
          ".ren-ai-citation",
          ".ren-ai-confidence",
          ".ren-ai-confidence-bar",
          ".ren-ai-confidence-fill",
          ".ren-ai-error",
          ".ren-ai-error-message",
          ".ren-ai-file-chip",
          ".ren-ai-file-chip-icon",
          ".ren-ai-file-chip-name",
          ".ren-ai-file-chip-remove",
          ".ren-ai-file-chip-size",
          ".ren-ai-message",
          ".ren-ai-message-content",
          ".ren-ai-message-footer",
          ".ren-ai-message-header",
          ".ren-ai-prompt",
          ".ren-ai-prompt-input",
          ".ren-ai-prompt-send",
          ".ren-ai-skeleton",
          ".ren-ai-skeleton-line",
          ".ren-ai-slug",
          ".ren-ai-source",
          ".ren-ai-source-number",
          ".ren-ai-source-title",
          ".ren-ai-source-url",
          ".ren-ai-sources",
          ".ren-ai-streaming",
          ".ren-ai-tool-call",
          ".ren-ai-tool-call-args",
          ".ren-ai-tool-call-header",
          ".ren-ai-tool-call-name",
          ".ren-ai-tool-call-result",
          ".ren-ai-tool-call-status",
          ".ren-ai-typing",
          ".ren-ai-typing-dots"
        ],
        "tokens": [
          "--body-size",
          "--caption-size",
          "--caption-sm-size",
          "--color-ai",
          "--color-ai-hover",
          "--color-ai-subtle",
          "--color-border",
          "--color-border-muted",
          "--color-danger",
          "--color-fill",
          "--color-focus-ring",
          "--color-on-ai",
          "--color-success",
          "--color-surface",
          "--color-surface-raised",
          "--color-surface-sunken",
          "--color-text",
          "--color-text-muted",
          "--color-text-secondary",
          "--color-warning",
          "--duration-state",
          "--duration-tactile",
          "--ease-enter",
          "--font-mono",
          "--label-size",
          "--leading-normal",
          "--leading-relaxed",
          "--radius-full",
          "--radius-lg",
          "--radius-md",
          "--radius-sm",
          "--radius-xl",
          "--size-md",
          "--size-sm",
          "--space-0-5",
          "--space-1",
          "--space-2",
          "--space-3",
          "--space-4",
          "--stroke-1",
          "--text-lg",
          "--tracking-wide",
          "--weight-bold",
          "--weight-medium",
          "--weight-semibold"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-ai Pattern Contract\n\nAI interaction pattern for prompts, responses, suggestions, and assistant surfaces.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-ai` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-ai` pattern.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Ai pattern behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this pattern.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The UI surfaces AI-generated content (chat reply, completion, summary, suggestion).\"\n    - \"You need an AI message bubble with streaming caret (.ren-ai-streaming) or typing indicator.\"\n    - \"You need to render citations / source references attached to AI output (.ren-ai-citation, .ren-ai-sources).\"\n    - \"You need a confidence indicator with high/medium/low levels (data-level=\\\"high|medium|low\\\").\"\n    - \"You need a chat-style prompt input (.ren-ai-prompt + .ren-ai-prompt-input + .ren-ai-prompt-send).\"\n    - \"You need to mark non-AI content as AI-augmented via an inline slug (.ren-ai-slug).\"\n  avoidWhen:\n    - \"The content is a generic human message — use a plain card / message bubble.\"\n    - \"The control is a generic submit button — use ren-button, not .ren-ai-prompt-send.\"\n    - \"The loading state is for non-AI fetch/page — use ren-skeleton, not .ren-ai-skeleton.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-ai/ren-ai.css\"\n  notes:\n    - \"CSS-only pattern. No colocated JS — streaming text and feedback handlers live in consumer code.\"\n    - \"Animations respect prefers-reduced-motion via fallback rules; do not override.\"\n\nrequiredMarkup:\n  - \"Wrap AI replies in <div class=\\\"ren-ai-message\\\"> with a .ren-ai-message-header (slug + meta) and .ren-ai-message-content.\"\n  - \"Streaming responses add the .ren-ai-streaming class on .ren-ai-message; the blinking caret is the ::after on .ren-ai-message-content.\"\n  - \"Citations are inline <a class=\\\"ren-ai-citation\\\"> with vertical-align: super; source lists use <a class=\\\"ren-ai-source\\\"> rows inside .ren-ai-sources.\"\n  - \".ren-ai-confidence MUST carry data-level=\\\"high|medium|low\\\"; the fill width and color are driven by that attribute.\"\n  - \"The prompt composer must be .ren-ai-prompt wrapping a <textarea class=\\\"ren-ai-prompt-input\\\"> and a <button class=\\\"ren-ai-prompt-send\\\"> with an accessible name.\"\n\nforbiddenPatterns:\n  - \"Using .ren-ai-message for non-AI chat content (use a neutral message component instead).\"\n  - \"Faking confidence levels by inline-styling .ren-ai-confidence-fill width — set data-level.\"\n  - \"Replacing .ren-ai-prompt-send with a bare <button> styled as a circle — use the documented selector.\"\n  - \"Hardcoding the AI accent (e.g. #8B5CF6, --purple-*) instead of using --color-ai / --color-ai-subtle.\"\n  - \"Adding colored side borders to mark AI content; use the AI surface, icon/slug, and semantic tokens instead.\"\n  - \"Animating the typing dots or skeleton manually; the pattern ships keyframes + reduced-motion fallbacks.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic tokens: --color-ai, --color-ai-hover, --color-ai-subtle, --color-on-ai, --color-text, --color-text-muted, --color-surface-raised, --color-surface-sunken, --color-fill, --color-border-muted, --color-success, --color-warning, --color-danger.\"\n    - \"Layout / type tokens: --space-*, --radius-*, --stroke-1, --body-size, --caption-size, --caption-sm-size, --label-size, --leading-relaxed, --weight-medium, --weight-semibold, --weight-bold.\"\n    - \"Motion tokens: --duration-tactile, --duration-state, --ease-enter.\"\n    - \"Size tokens for the send button and action chrome: --size-sm, --size-md, --text-lg.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code or inside the pattern CSS.\"\n    - \"Hardcoded hex / named colors (e.g. #8B5CF6, purple, rgb(...)).\"\n    - \"Raw transition durations or easings; use --duration-* / --ease-*.\"\n\naccessibility:\n  required:\n    - \"AI messages must include a visible AI slug or .ren-ai-message-header so screen-reader users know the source is AI-generated, not human.\"\n    - \"Streaming caret and typing dots respect prefers-reduced-motion; never strip those fallbacks.\"\n    - \".ren-ai-action-thumbs feedback buttons need accessible names (aria-label=\\\"Helpful\\\" / \\\"Not helpful\\\"); icon-only is not enough.\"\n    - \".ren-ai-prompt-input is a real <textarea> with an associated <label> (or aria-label); the send button must reflect disabled state via the disabled attribute, not opacity alone.\"\n    - \"Confidence must not rely on color alone — keep the numeric / textual level visible next to .ren-ai-confidence-bar.\"\n    - \".ren-ai-citation must be a real <a> with href and an accessible target; do not implement as a styled <span>.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-ai/ren-ai.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div class=\"ren-ai-message\"><div class=\"ren-ai-message-header\"><span class=\"ren-ai-slug\">AI</span></div><div class=\"ren-ai-message-content\">Here is the summary.</div></div><form class=\"ren-ai-prompt\"><textarea class=\"ren-ai-prompt-input\" aria-label=\"Message\"></textarea><button class=\"ren-ai-prompt-send\" type=\"submit\" aria-label=\"Send message\">Send</button></form>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-ai-action`\n- `.ren-ai-action-edit`\n- `.ren-ai-action-regenerate`\n- `.ren-ai-action-thumbs`\n- `.ren-ai-actions`\n- `.ren-ai-citation`\n- `.ren-ai-confidence`\n- `.ren-ai-confidence-bar`\n- `.ren-ai-confidence-fill`\n- `.ren-ai-error`\n- `.ren-ai-error-message`\n- `.ren-ai-file-chip`\n- `.ren-ai-file-chip-icon`\n- `.ren-ai-file-chip-name`\n- `.ren-ai-file-chip-remove`\n- `.ren-ai-file-chip-size`\n- `.ren-ai-message`\n- `.ren-ai-message-content`\n- `.ren-ai-message-footer`\n- `.ren-ai-message-header`\n- `.ren-ai-prompt`\n- `.ren-ai-prompt-input`\n- `.ren-ai-prompt-send`\n- `.ren-ai-skeleton`\n- `.ren-ai-skeleton-line`\n- `.ren-ai-slug`\n- `.ren-ai-source`\n- `.ren-ai-source-number`\n- `.ren-ai-source-title`\n- `.ren-ai-source-url`\n- `.ren-ai-sources`\n- `.ren-ai-streaming`\n- `.ren-ai-tool-call`\n- `.ren-ai-tool-call-args`\n- `.ren-ai-tool-call-header`\n- `.ren-ai-tool-call-name`\n- `.ren-ai-tool-call-result`\n- `.ren-ai-tool-call-status`\n- `.ren-ai-typing`\n- `.ren-ai-typing-dots`\n\nAll selectors are CSS-only — the consumer wires retry handlers, file\nupload state, tool execution, and edit-and-resend logic. The pattern\ngives you the visuals (and a sane `<details>` baseline for tool calls\nso collapse works without JS); state belongs to your app.\n\n## States And Attributes\n\n- `[data-active]`\n- `[data-level]` — on `.ren-ai-confidence` (high|medium|low)\n- `[data-status]` — on `.ren-ai-tool-call` (pending|running|success|error)\n- `[open]` — native on `.ren-ai-tool-call` when expanded\n- `:active`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/patterns/ren-ai/ren-ai.css`\n- `docs/components/ren-ai.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Carbon for AI Patterns\n   ============================================\n   Design patterns for AI-generated content.\n   Inspired by IBM Carbon for AI.\n\n   Components:\n   - AI Message bubble (streaming + complete)\n   - AI Typing indicator\n   - AI Citation / Source reference\n   - AI Confidence indicator\n   - AI Regenerate action\n   - AI Prompt input\n   - AI Slug (inline AI label)\n\n   All CSS-only where possible, minimal JS for\n   streaming text animation.\n   ============================================ */\n\n/* ═══════════════════════════════════════════════\n   AI SLUG — Inline \"AI\" label\n   ═══════════════════════════════════════════════ */\n.ren-ai-slug {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-1);\n  padding: var(--space-0-5) var(--space-2);\n  font-size: var(--caption-sm-size);\n  font-weight: var(--weight-semibold);\n  letter-spacing: var(--tracking-wide);\n  text-transform: uppercase;\n  color: var(--color-ai);\n  background: color-mix(in srgb, var(--color-ai) 10%, transparent);\n  border-radius: var(--radius-sm);\n  line-height: 1;\n}\n\n.ren-ai-slug::before {\n  content: '✦';\n  font-size: 0.75em;\n}\n\n/* ═══════════════════════════════════════════════\n   AI MESSAGE — Chat bubble for AI responses\n   ═══════════════════════════════════════════════ */\n.ren-ai-message {\n  position: relative;\n  padding: var(--space-4);\n  border-radius: var(--radius-lg);\n  background: color-mix(in srgb, var(--color-ai-subtle) 70%, var(--color-surface-raised));\n  border: var(--stroke-1) solid var(--color-border);\n  line-height: var(--leading-relaxed);\n}\n\n.ren-ai-message-header {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  margin-bottom: var(--space-3);\n  font-size: var(--label-size);\n}\n\n.ren-ai-message-content {\n  font-size: var(--body-size);\n  color: var(--color-text);\n}\n\n.ren-ai-message-content p:last-child {\n  margin-bottom: 0;\n}\n\n.ren-ai-message-footer {\n  display: flex;\n  align-items: center;\n  gap: var(--space-3);\n  margin-top: var(--space-3);\n  padding-top: var(--space-3);\n  border-top: var(--stroke-1) solid var(--color-border-muted);\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n}\n\n/* ═══════════════════════════════════════════════\n   AI STREAMING — Typing animation\n   ═══════════════════════════════════════════════ */\n.ren-ai-streaming .ren-ai-message-content::after {\n  content: '▊';\n  display: inline;\n  animation: ren-ai-blink 0.8s step-end infinite;\n  color: var(--color-ai);\n  margin-inline-start: 1px;\n}\n\n@keyframes ren-ai-blink {\n  0%, 100% { opacity: 1; }\n  50%      { opacity: 0; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-ai-streaming .ren-ai-message-content::after {\n    animation: none;\n    opacity: 0.7;\n  }\n}\n\n/* ═══════════════════════════════════════════════\n   AI TYPING INDICATOR — \"AI is thinking...\"\n   ═══════════════════════════════════════════════ */\n.ren-ai-typing {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n}\n\n.ren-ai-typing-dots {\n  display: inline-flex;\n  gap: 3px;\n}\n\n.ren-ai-typing-dots span {\n  width: 6px;\n  height: 6px;\n  border-radius: var(--radius-full);\n  background: var(--color-ai);\n  animation: ren-ai-dot-bounce 1.4s ease-in-out infinite;\n}\n\n.ren-ai-typing-dots span:nth-child(2) { animation-delay: 0.2s; }\n.ren-ai-typing-dots span:nth-child(3) { animation-delay: 0.4s; }\n\n@keyframes ren-ai-dot-bounce {\n  0%, 80%, 100% { transform: translateY(0); opacity: 0.4; }\n  40%           { transform: translateY(-4px); opacity: 1; }\n}\n\n@keyframes ren-ai-dot-fade {\n  0%, 80%, 100% { opacity: 0.3; }\n  40%           { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-ai-typing-dots span {\n    animation: ren-ai-dot-fade 1.4s ease-in-out infinite;\n  }\n}\n\n/* ═══════════════════════════════════════════════\n   AI CITATION — Source reference\n   ═══════════════════════════════════════════════ */\n.ren-ai-citation {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-1);\n  padding: var(--space-0-5) var(--space-2);\n  font-size: var(--caption-sm-size);\n  font-weight: var(--weight-medium);\n  color: var(--color-ai);\n  background: color-mix(in srgb, var(--color-ai) 8%, transparent);\n  border-radius: var(--radius-sm);\n  text-decoration: none;\n  cursor: pointer;\n  transition: background var(--duration-tactile) var(--ease-enter);\n  vertical-align: super;\n  line-height: 1;\n}\n\n.ren-ai-citation:hover {\n  background: color-mix(in srgb, var(--color-ai) 14%, transparent);\n}\n\n/* Citation list at bottom of message */\n.ren-ai-sources {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-2);\n  margin-top: var(--space-3);\n  padding-top: var(--space-3);\n  border-top: var(--stroke-1) solid var(--color-border-muted);\n}\n\n.ren-ai-source {\n  display: flex;\n  align-items: flex-start;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  border-radius: var(--radius-md);\n  background: var(--color-surface-sunken);\n  font-size: var(--caption-size);\n  text-decoration: none;\n  color: var(--color-text);\n  transition: background var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-ai-source:hover {\n  background: var(--color-fill);\n}\n\n.ren-ai-source-number {\n  flex-shrink: 0;\n  width: 1.25rem;\n  height: 1.25rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: var(--caption-sm-size);\n  font-weight: var(--weight-bold);\n  color: var(--color-ai);\n  background: color-mix(in srgb, var(--color-ai) 10%, transparent);\n  border-radius: var(--radius-sm);\n}\n\n.ren-ai-source-title {\n  font-weight: var(--weight-medium);\n}\n\n.ren-ai-source-url {\n  color: var(--color-text-muted);\n  font-size: var(--caption-sm-size);\n}\n\n/* ═══════════════════════════════════════════════\n   AI CONFIDENCE — Certainty indicator\n   ═══════════════════════════════════════════════ */\n.ren-ai-confidence {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-2);\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n}\n\n.ren-ai-confidence-bar {\n  width: 3rem;\n  height: 4px;\n  border-radius: var(--radius-full);\n  background: var(--color-fill);\n  overflow: hidden;\n}\n\n.ren-ai-confidence-fill {\n  height: 100%;\n  border-radius: var(--radius-full);\n  transition: width var(--duration-state) var(--ease-enter);\n}\n\n.ren-ai-confidence[data-level=\"high\"] .ren-ai-confidence-fill {\n  width: 100%;\n  background: var(--color-success);\n}\n\n.ren-ai-confidence[data-level=\"medium\"] .ren-ai-confidence-fill {\n  width: 66%;\n  background: var(--color-warning);\n}\n\n.ren-ai-confidence[data-level=\"low\"] .ren-ai-confidence-fill {\n  width: 33%;\n  background: var(--color-danger);\n}\n\n/* ═══════════════════════════════════════════════\n   AI ACTIONS — Regenerate, Copy, Feedback\n   ═══════════════════════════════════════════════ */\n.ren-ai-actions {\n  display: flex;\n  align-items: center;\n  gap: var(--space-1);\n}\n\n.ren-ai-action {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--space-1);\n  padding: var(--space-1) var(--space-2);\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n  background: transparent;\n  border: none;\n  border-radius: var(--radius-sm);\n  cursor: pointer;\n  transition: all var(--duration-tactile) var(--ease-enter);\n  min-height: var(--size-sm);\n}\n\n.ren-ai-action:hover {\n  color: var(--color-text);\n  background: var(--color-fill);\n}\n\n.ren-ai-action:active {\n  transform: scale(0.95);\n}\n\n.ren-ai-action[data-active=\"true\"] {\n  color: var(--color-ai);\n}\n\n/* Feedback buttons (thumbs up/down) */\n.ren-ai-action-thumbs {\n  display: inline-flex;\n  gap: 0;\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-md);\n  overflow: hidden;\n}\n\n.ren-ai-action-thumbs .ren-ai-action {\n  border-radius: 0;\n  padding: var(--space-1) var(--space-2);\n}\n\n.ren-ai-action-thumbs .ren-ai-action + .ren-ai-action {\n  border-inline-start: var(--stroke-1) solid var(--color-border);\n}\n\n/* ═══════════════════════════════════════════════\n   AI PROMPT INPUT — Chat-style input\n   ═══════════════════════════════════════════════ */\n.ren-ai-prompt {\n  display: flex;\n  align-items: flex-end;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-xl);\n  background: var(--color-surface);\n  transition: border-color var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-ai-prompt:focus-within {\n  border-color: var(--color-ai);\n  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-ai) 15%, transparent);\n}\n\n.ren-ai-prompt-input {\n  flex: 1;\n  border: none;\n  background: transparent;\n  font-family: inherit;\n  font-size: var(--body-size);\n  color: var(--color-text);\n  resize: none;\n  outline: none;\n  min-height: 1.5rem;\n  max-height: 8rem;\n  field-sizing: content;\n  line-height: var(--leading-normal);\n}\n\n.ren-ai-prompt-input::placeholder {\n  color: var(--color-text-muted);\n}\n\n.ren-ai-prompt-send {\n  flex-shrink: 0;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: var(--size-md);\n  height: var(--size-md);\n  border: none;\n  border-radius: var(--radius-full);\n  background: var(--color-ai);\n  color: var(--color-on-ai);\n  cursor: pointer;\n  transition: all var(--duration-tactile) var(--ease-enter);\n  font-size: var(--text-lg);\n}\n\n.ren-ai-prompt-send:hover {\n  background: var(--color-ai-hover);\n  transform: scale(1.05);\n}\n\n.ren-ai-prompt-send:disabled {\n  opacity: 0.4;\n  cursor: not-allowed;\n  transform: none;\n}\n\n/* ═══════════════════════════════════════════════\n   AI SKELETON — Loading placeholder for AI\n   ═══════════════════════════════════════════════ */\n.ren-ai-skeleton {\n  padding: var(--space-4);\n  border-radius: var(--radius-lg);\n  background: color-mix(in srgb, var(--color-ai-subtle) 55%, var(--color-surface-raised));\n  border: var(--stroke-1) solid var(--color-border-muted);\n}\n\n.ren-ai-skeleton-line {\n  height: 0.875rem;\n  border-radius: var(--radius-sm);\n  background: linear-gradient(\n    90deg,\n    var(--color-fill) 25%,\n    color-mix(in srgb, var(--color-ai) 6%, var(--color-fill)) 50%,\n    var(--color-fill) 75%\n  );\n  background-size: 200% 100%;\n  animation: ren-ai-shimmer 1.5s ease-in-out infinite;\n}\n\n.ren-ai-skeleton-line + .ren-ai-skeleton-line {\n  margin-top: var(--space-2);\n}\n\n.ren-ai-skeleton-line:last-child {\n  width: 60%;\n}\n\n@keyframes ren-ai-shimmer {\n  0%   { background-position: 200% 0; }\n  100% { background-position: -200% 0; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-ai-skeleton-line {\n    animation: none;\n  }\n}\n\n/* ═══════════════════════════════════════════════\n   AI ERROR — Message failure state\n   ═══════════════════════════════════════════════ */\n.ren-ai-error {\n  position: relative;\n  padding: var(--space-4);\n  border-radius: var(--radius-lg);\n  background: color-mix(in srgb, var(--color-danger) 6%, var(--color-surface-raised));\n  border: var(--stroke-1) solid color-mix(in srgb, var(--color-danger) 30%, transparent);\n  display: grid;\n  gap: var(--space-2);\n}\n\n.ren-ai-error-message {\n  color: var(--color-text);\n  font-size: var(--body-size);\n  line-height: 1.5;\n}\n\n.ren-ai-error-message::before {\n  content: '⚠ ';\n  color: var(--color-danger);\n  margin-inline-end: var(--space-1);\n}\n\n/* ═══════════════════════════════════════════════\n   AI ACTION VARIANTS — Regenerate, edit-and-resend\n   These extend the existing .ren-ai-action base.\n   ═══════════════════════════════════════════════ */\n.ren-ai-action-regenerate::before {\n  content: '↻';\n  margin-inline-end: var(--space-1);\n  font-size: 1.1em;\n  display: inline-block;\n  /* Subtle hint that this re-runs inference */\n  transition: transform var(--duration-state, 200ms) var(--ease-enter, ease);\n}\n\n.ren-ai-action-regenerate:hover::before {\n  transform: rotate(-45deg);\n}\n\n.ren-ai-action-edit::before {\n  content: '✎';\n  margin-inline-end: var(--space-1);\n  font-size: 1.05em;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-ai-action-regenerate::before {\n    transition: none;\n  }\n}\n\n/* ═══════════════════════════════════════════════\n   AI FILE CHIP — Uploaded file reference\n   Compact pill showing a file the user attached,\n   with icon, name, size, and remove affordance.\n   ═══════════════════════════════════════════════ */\n.ren-ai-file-chip {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-2);\n  padding: var(--space-1) var(--space-2);\n  background: var(--color-surface-raised);\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-md);\n  font-size: var(--caption-size);\n  max-width: 220px;\n}\n\n.ren-ai-file-chip-icon {\n  flex-shrink: 0;\n  width: 1rem;\n  height: 1rem;\n  color: var(--color-text-muted);\n  display: grid;\n  place-items: center;\n}\n\n.ren-ai-file-chip-name {\n  flex: 1 1 auto;\n  min-width: 0;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  color: var(--color-text);\n  font-weight: var(--weight-medium);\n}\n\n.ren-ai-file-chip-size {\n  flex-shrink: 0;\n  color: var(--color-text-muted);\n  font-family: var(--font-mono);\n  font-size: var(--caption-sm-size);\n}\n\n.ren-ai-file-chip-remove {\n  flex-shrink: 0;\n  width: 1.25rem;\n  height: 1.25rem;\n  display: grid;\n  place-items: center;\n  border: 0;\n  background: transparent;\n  border-radius: var(--radius-sm);\n  color: var(--color-text-muted);\n  font-size: 1rem;\n  line-height: 1;\n  cursor: pointer;\n  padding: 0;\n}\n\n.ren-ai-file-chip-remove:hover,\n.ren-ai-file-chip-remove:focus-visible {\n  background: var(--color-fill);\n  color: var(--color-text);\n}\n\n.ren-ai-file-chip-remove:focus-visible {\n  outline: 2px solid var(--color-focus-ring);\n  outline-offset: 2px;\n}\n\n/* ═══════════════════════════════════════════════\n   AI TOOL CALL — Model invoking a tool / function\n   Collapsible block built on native <details>\n   so JS is optional. Status via data-status.\n   ═══════════════════════════════════════════════ */\n.ren-ai-tool-call {\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-md);\n  background: var(--color-surface-raised);\n  margin-block: var(--space-2);\n  overflow: hidden;\n}\n\n.ren-ai-tool-call-header {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  background: var(--color-surface-sunken);\n  font-size: var(--caption-size);\n  font-weight: var(--weight-medium);\n  color: var(--color-text);\n  cursor: pointer;\n  user-select: none;\n  list-style: none;\n}\n\n.ren-ai-tool-call-header::-webkit-details-marker { display: none; }\n\n.ren-ai-tool-call-header::before {\n  content: '▸';\n  font-size: 0.75em;\n  color: var(--color-text-muted);\n  transition: transform var(--duration-state, 200ms) var(--ease-enter, ease);\n}\n\n.ren-ai-tool-call[open] > .ren-ai-tool-call-header::before {\n  transform: rotate(90deg);\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-ai-tool-call-header::before {\n    transition: none;\n  }\n}\n\n.ren-ai-tool-call-name {\n  font-family: var(--font-mono);\n  color: var(--color-text);\n}\n\n.ren-ai-tool-call-status {\n  margin-inline-start: auto;\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-1);\n  padding: 2px var(--space-2);\n  border-radius: var(--radius-full);\n  font-size: var(--caption-sm-size);\n  font-weight: var(--weight-medium);\n  text-transform: uppercase;\n  letter-spacing: 0.04em;\n  border: var(--stroke-1) solid transparent;\n}\n\n.ren-ai-tool-call[data-status=\"pending\"] .ren-ai-tool-call-status {\n  color: var(--color-text-muted);\n  background: var(--color-fill);\n}\n\n.ren-ai-tool-call[data-status=\"running\"] .ren-ai-tool-call-status {\n  color: var(--color-warning);\n  background: color-mix(in srgb, var(--color-warning) 15%, transparent);\n}\n\n.ren-ai-tool-call[data-status=\"success\"] .ren-ai-tool-call-status {\n  color: var(--color-success);\n  background: color-mix(in srgb, var(--color-success) 15%, transparent);\n}\n\n.ren-ai-tool-call[data-status=\"error\"] .ren-ai-tool-call-status {\n  color: var(--color-danger);\n  background: color-mix(in srgb, var(--color-danger) 15%, transparent);\n}\n\n.ren-ai-tool-call-args,\n.ren-ai-tool-call-result {\n  padding: var(--space-3);\n  font-family: var(--font-mono);\n  font-size: var(--caption-size);\n  line-height: 1.5;\n  color: var(--color-text-secondary);\n  white-space: pre-wrap;\n  overflow-x: auto;\n}\n\n.ren-ai-tool-call-args {\n  border-bottom: var(--stroke-1) solid var(--color-border-muted);\n}\n\n.ren-ai-tool-call-result {\n  background: var(--color-surface);\n}\n\n.ren-ai-tool-call[data-status=\"error\"] .ren-ai-tool-call-result {\n  background: color-mix(in srgb, var(--color-danger) 4%, var(--color-surface));\n  color: var(--color-text);\n}\n",
      "path": "components/patterns/ren-ai",
      "id": "component:pattern:ren-ai",
      "type": "component",
      "name": "ren-ai"
    },
    {
      "data": {
        "kind": "pattern",
        "selectors": [
          ".ren-command",
          ".ren-command-empty",
          ".ren-command-footer",
          ".ren-command-footer-hint",
          ".ren-command-group",
          ".ren-command-group-heading",
          ".ren-command-input",
          ".ren-command-input-wrapper",
          ".ren-command-item",
          ".ren-command-item-content",
          ".ren-command-item-description",
          ".ren-command-item-icon",
          ".ren-command-item-shortcut",
          ".ren-command-item-title",
          ".ren-command-kbd",
          ".ren-command-list",
          ".ren-command-separator"
        ],
        "tokens": [
          "--color-border",
          "--color-fill",
          "--color-fill-hover",
          "--color-separator",
          "--color-surface",
          "--color-surface-overlay",
          "--color-text",
          "--color-text-faint",
          "--color-text-muted",
          "--duration-enter",
          "--duration-overlay",
          "--duration-state",
          "--ease-enter",
          "--ease-state-change",
          "--radius-md",
          "--radius-xl",
          "--ren-body-size",
          "--ren-caption-size",
          "--ren-command-bg",
          "--ren-command-border",
          "--ren-command-item-height",
          "--ren-command-item-radius",
          "--ren-command-max-height",
          "--ren-command-radius",
          "--ren-command-shadow",
          "--ren-command-width",
          "--ren-font-mono",
          "--ren-radius-xl",
          "--ren-space-1",
          "--ren-space-2",
          "--ren-space-3",
          "--ren-space-4",
          "--ren-space-8",
          "--ren-z-modal",
          "--shadow-xl",
          "--size-lg"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-command Pattern Contract\n\nCommand palette pattern for searchable application actions.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-command` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-command` pattern.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Command pattern behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this pattern.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The product needs a Ctrl/Cmd+K command palette / Spotlight-style action launcher.\"\n    - \"Actions are searchable by title, description, or data-keywords, with keyboard up/down navigation and Enter to activate.\"\n    - \"Items are grouped (e.g. \\\"Navigation\\\", \\\"Settings\\\") via .ren-command-group with .ren-command-group-heading.\"\n    - \"You want a global keyboard shortcut (data-shortcut=\\\"ctrl+k\\\") that opens the palette from anywhere on the page.\"\n    - \"You need a modal overlay with backdrop + Escape-to-close dismissal driven by a real <dialog>.\"\n  avoidWhen:\n    - \"The list is a navigation menu — use ren-nav or ren-menubar.\"\n    - \"The list is a static dropdown of options — use ren-menu / ren-select.\"\n    - \"The list is a filter for a data table — use ren-table-toolbar-search inside ren-table.\"\n    - \"There is no search input (just a static action list) — use ren-menu.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-command/ren-command.css\"\n  js:\n    - \"rends/components/patterns/ren-command/ren-command.js\"\n  notes:\n    - \"JS registers customElements.define('ren-command', ...) and owns the global shortcut listener, filtering, roving highlight, and ren-command-select event.\"\n    - \"The pattern is dialog-based; the underlying <dialog> must be present so showModal() / close() work.\"\n\nrequiredMarkup:\n  - \"Use a <ren-command> custom element wrapping a real <dialog class=\\\"ren-command\\\"> (the JS calls dialog.showModal()).\"\n  - \"Inside, include a .ren-command-input-wrapper with a <input class=\\\"ren-command-input\\\" type=\\\"text\\\"> and an optional .ren-command-kbd hint.\"\n  - \"Wrap action rows in <ul class=\\\"ren-command-list\\\"> (or container) of <button class=\\\"ren-command-item\\\"> rows; items may carry data-keywords, data-value, data-action.\"\n  - \"Group rows under <div class=\\\"ren-command-group\\\"><div class=\\\"ren-command-group-heading\\\">…</div>…</div>; the JS toggles [data-empty] on empty groups.\"\n  - \"Provide a .ren-command-empty fallback element; the JS shows it when the filter produces zero matches.\"\n  - \"Each .ren-command-item that has a shortcut puts the keys inside .ren-command-item-shortcut with <kbd> elements.\"\n\nforbiddenPatterns:\n  - \"Implementing as a <div> with click handlers — must be <ren-command> + <dialog> so Escape, focus, and a11y come from the platform.\"\n  - \"Filtering items via display: none from external code — the component owns visibility via _filterItems().\"\n  - \"Building roving focus / highlight manually — the [data-highlighted] attribute is set by the component on the active item.\"\n  - \"Binding a second Ctrl+K listener at the document level — use the data-shortcut attribute or registerAction() API.\"\n  - \"Submitting selection from a custom click handler that bypasses the ren-command-select event.\"\n\ntokenPolicy:\n  allowed:\n    - \"Public component tokens listed in Public Token API: --ren-command-bg, --ren-command-border, --ren-command-item-height, --ren-command-item-radius, --ren-command-max-height, --ren-command-radius, --ren-command-shadow, --ren-command-width.\"\n    - \"Semantic surface / text tokens consumed by selectors: --color-surface, --color-border, --color-text, --color-text-muted, --color-text-faint, --color-fill, --color-fill-hover, --color-separator.\"\n    - \"Spacing / motion tokens: --ren-space-*, --ren-radius-*, --duration-enter, --duration-overlay, --duration-state, --ease-enter, --ease-state-change.\"\n    - \"Z-index token: --ren-z-modal.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / named colors for surface, border, fill, or text.\"\n    - \"Custom z-index values that bypass --ren-z-modal (the palette must layer above content but below toasts).\"\n\naccessibility:\n  required:\n    - \"Render as a real <dialog> so focus trap, Escape, and inert background come from the browser.\"\n    - \"The input is a real <input type=\\\"text\\\"> with an accessible name (label or aria-label like \\\"Search commands\\\").\"\n    - \"The component injects a [role=\\\"status\\\"][aria-live=\\\"polite\\\"] region announcing result counts on every filter — do not remove it.\"\n    - \"Items must be real <button> elements (or role=\\\"option\\\" rows) so Enter / Space / click all work uniformly.\"\n    - \"Disabled items use aria-disabled=\\\"true\\\" AND pointer-events: none (the CSS handles the latter).\"\n    - \"The shortcut hint inside .ren-command-kbd is decorative; do not rely on it as the only affordance — Escape and click-outside must both close.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-command/ren-command.css\">\n<script type=\"module\" src=\"rends/components/patterns/ren-command/ren-command.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-command><dialog class=\"ren-command\"><div class=\"ren-command-input-wrapper\"><input class=\"ren-command-input\" type=\"text\" aria-label=\"Search commands\"></div><ul class=\"ren-command-list\"><li><button class=\"ren-command-item\" type=\"button\" data-value=\"save\">Save</button></li></ul><div class=\"ren-command-empty\" hidden>No commands found</div></dialog></ren-command>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-command`\n- `.ren-command-empty`\n- `.ren-command-footer`\n- `.ren-command-footer-hint`\n- `.ren-command-group`\n- `.ren-command-group-heading`\n- `.ren-command-input`\n- `.ren-command-input-wrapper`\n- `.ren-command-item`\n- `.ren-command-item-content`\n- `.ren-command-item-description`\n- `.ren-command-item-icon`\n- `.ren-command-item-shortcut`\n- `.ren-command-item-title`\n- `.ren-command-kbd`\n- `.ren-command-list`\n- `.ren-command-separator`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[aria-live]`\n- `[data-empty]`\n- `[data-highlighted]`\n- `:active`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-command-bg`\n- `--ren-command-border`\n- `--ren-command-item-height`\n- `--ren-command-item-radius`\n- `--ren-command-max-height`\n- `--ren-command-radius`\n- `--ren-command-shadow`\n- `--ren-command-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/patterns/ren-command/ren-command.css`\n- `components/patterns/ren-command/ren-command.js`\n- `docs/components/ren-command.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n.ren-command {\n  position: fixed;\n  top: 50%;\n  inset-inline-start: 50%;\n  transform: translate(-50%, -50%);\n  max-width: 32rem;\n  width: 100%;\n  max-height: 24rem;\n  background-color: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--ren-radius-xl, 0.75rem);\n  box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);\n  padding: 0;\n  overflow: hidden;\n  display: flex;\n  flex-direction: column;\n  z-index: var(--ren-z-modal, 1000);\n  margin: auto;\n  animation: ren-command-in var(--duration-enter) var(--ease-enter);\n\n  @starting-style {\n    opacity: 0;\n    transform: translate(-50%, -50%) scale(0.98);\n  }\n\n  @media (max-width: 32rem) {\n    width: calc(100% - 2rem);\n    max-height: 18rem;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    animation: none;\n  }\n\n  &::backdrop {\n    background-color: rgb(0 0 0 / 0.5);\n    backdrop-filter: blur(4px);\n    animation: ren-command-backdrop-in var(--duration-overlay) var(--ease-enter);\n\n    @starting-style {\n      background-color: rgb(0 0 0 / 0);\n      backdrop-filter: blur(0);\n    }\n\n    @media (prefers-reduced-motion: reduce) {\n      animation: none;\n    }\n  }\n}\n\n@keyframes ren-command-in {\n  from {\n    opacity: 0;\n    transform: translate(-50%, -50%) scale(0.98);\n  }\n\n  to {\n    opacity: 1;\n    transform: translate(-50%, -50%) scale(1);\n  }\n}\n\n@keyframes ren-command-backdrop-in {\n  from {\n    background-color: rgb(0 0 0 / 0);\n    backdrop-filter: blur(0);\n  }\n\n  to {\n    background-color: rgb(0 0 0 / 0.5);\n    backdrop-filter: blur(4px);\n  }\n}\n\n.ren-command-input-wrapper {\n  display: flex;\n  align-items: center;\n  border-bottom: 1px solid var(--color-border);\n  padding: var(--ren-space-2, 0.5rem) var(--ren-space-3, 0.75rem);\n  gap: var(--ren-space-2, 0.5rem);\n  flex-shrink: 0;\n\n  & svg {\n    width: 18px;\n    height: 18px;\n    color: var(--color-text-muted);\n    flex-shrink: 0;\n  }\n}\n\n.ren-command-input {\n  flex: 1;\n  border: none;\n  background-color: transparent;\n  font-size: var(--ren-body-size, 0.875rem);\n  color: var(--color-text);\n  outline: none;\n  padding: 0;\n  font-family: inherit;\n\n  &::placeholder {\n    color: var(--color-text-muted);\n  }\n\n  &:autofill {\n    box-shadow: inset 0 0 0 1000px var(--color-surface);\n    -webkit-text-fill-color: var(--color-text);\n  }\n}\n\n.ren-command-kbd {\n  margin-inline-start: auto;\n  display: flex;\n  gap: var(--ren-space-1, 0.25rem);\n  align-items: center;\n  font-size: var(--ren-caption-size, 0.75rem);\n  color: var(--color-text-muted);\n  font-family: var(--ren-font-mono, 'Monaco', 'Menlo', monospace);\n  white-space: nowrap;\n\n  & kbd {\n    padding: 0.125rem 0.375rem;\n    background-color: var(--color-fill);\n    border: 1px solid var(--color-border);\n    border-radius: 0.25rem;\n    font-size: inherit;\n  }\n\n  @media (max-width: 32rem) {\n    display: none;\n  }\n}\n\n.ren-command-list {\n  overflow-y: auto;\n  max-height: 18rem;\n  padding: var(--ren-space-1, 0.25rem);\n  flex: 1;\n  scrollbar-width: thin;\n  scrollbar-color: var(--color-border) transparent;\n\n  &::-webkit-scrollbar {\n    width: 6px;\n  }\n\n  &::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background-color: var(--color-border);\n    border-radius: 3px;\n\n    &:hover {\n      background-color: var(--color-text-muted);\n    }\n  }\n}\n\n.ren-command-group {\n  margin-bottom: var(--ren-space-2, 0.5rem);\n  overflow: hidden;\n\n  &[data-empty] {\n    display: none;\n  }\n}\n\n.ren-command-group-heading {\n  padding: var(--ren-space-1, 0.25rem) var(--ren-space-3, 0.75rem);\n  font-size: var(--ren-caption-size, 0.75rem);\n  font-weight: 600;\n  color: var(--color-text-faint);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  user-select: none;\n}\n\n.ren-command-item {\n  display: flex;\n  align-items: center;\n  gap: var(--ren-space-3, 0.75rem);\n  padding: var(--ren-space-2, 0.5rem) var(--ren-space-3, 0.75rem);\n  border-radius: 0.375rem;\n  cursor: pointer;\n  transition: background-color var(--duration-state) var(--ease-state-change);\n  user-select: none;\n  background-color: transparent;\n  border: none;\n  width: 100%;\n  text-align: start;\n  font-family: inherit;\n\n  &:hover {\n    background-color: var(--color-fill);\n  }\n\n  &:active {\n    background-color: var(--color-fill-hover);\n  }\n\n  &[data-highlighted] {\n    background-color: var(--color-fill);\n  }\n\n  &[aria-disabled=\"true\"] {\n    opacity: 0.5;\n    cursor: not-allowed;\n    pointer-events: none;\n  }\n\n  &:focus-visible {\n    outline: none;\n    background-color: var(--color-fill);\n  }\n}\n\n.ren-command-item-icon {\n  flex-shrink: 0;\n  width: 20px;\n  height: 20px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  color: var(--color-text-muted);\n\n  & svg {\n    width: 100%;\n    height: 100%;\n  }\n}\n\n.ren-command-item-content {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  gap: 0.125rem;\n  min-width: 0;\n}\n\n.ren-command-item-title {\n  font-size: var(--ren-body-size, 0.875rem);\n  color: var(--color-text);\n  font-weight: 500;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.ren-command-item-description {\n  font-size: var(--ren-caption-size, 0.75rem);\n  color: var(--color-text-muted);\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.ren-command-item-shortcut {\n  margin-inline-start: auto;\n  display: flex;\n  gap: var(--ren-space-1, 0.25rem);\n  font-family: var(--ren-font-mono, 'Monaco', 'Menlo', monospace);\n  font-size: var(--ren-caption-size, 0.75rem);\n  color: var(--color-text-muted);\n  white-space: nowrap;\n  flex-shrink: 0;\n\n  @media (max-width: 40rem) {\n    display: none;\n  }\n}\n\n.ren-command-separator {\n  height: 1px;\n  background-color: var(--color-separator);\n  margin: var(--ren-space-1, 0.25rem) 0;\n}\n\n.ren-command-empty {\n  padding: var(--ren-space-8, 2rem);\n  text-align: center;\n  color: var(--color-text-muted);\n  font-size: var(--ren-body-size, 0.875rem);\n}\n\n.ren-command-footer {\n  border-top: 1px solid var(--color-border);\n  padding: var(--ren-space-2, 0.5rem);\n  display: flex;\n  gap: var(--ren-space-4, 1rem);\n  font-size: var(--ren-caption-size, 0.75rem);\n  color: var(--color-text-muted);\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  justify-content: center;\n\n  @media (max-width: 32rem) {\n    padding: var(--ren-space-1, 0.25rem) var(--ren-space-2, 0.5rem);\n    gap: var(--ren-space-2, 0.5rem);\n  }\n}\n\n.ren-command-footer-hint {\n  display: flex;\n  gap: var(--ren-space-1, 0.25rem);\n  align-items: center;\n\n  & kbd {\n    padding: 0.125rem 0.375rem;\n    background-color: var(--color-fill);\n    border: 1px solid var(--color-border);\n    border-radius: 0.25rem;\n    font-size: inherit;\n    font-family: var(--ren-font-mono, 'Monaco', 'Menlo', monospace);\n  }\n}\n/* Appearance API bridge */\n.ren-command { inline-size: var(--ren-command-width, min(95vw, 560px)); max-block-size: var(--ren-command-max-height, 340px); border-radius: var(--ren-command-radius, var(--radius-xl)); background: var(--ren-command-bg, var(--color-surface-overlay)); border-color: var(--ren-command-border, var(--color-border)); box-shadow: var(--ren-command-shadow, var(--shadow-xl)); }\n.ren-command [role=\"option\"], .ren-command-item { min-block-size: var(--ren-command-item-height, var(--size-lg)); border-radius: var(--ren-command-item-radius, var(--radius-md)); }\n\n\n/**\n * RenCommand Component - Command Palette / Spotlight search\n * Modern dialog-based command palette with keyboard navigation\n */\n\nconst KEYBOARD_CODES = {\n  Escape: 'Escape',\n  Enter: 'Enter',\n  ArrowDown: 'ArrowDown',\n  ArrowUp: 'ArrowUp'\n};\n\nclass RenCommand extends HTMLElement {\n  constructor() {\n    super();\n    this._dialog = null;\n    this._input = null;\n    this._list = null;\n    this._items = [];\n    this._groups = [];\n    this._highlightedIndex = 0;\n    this._shortcuts = new Map();\n    this._focusTrap = null;\n    this._lastQuery = '';\n    this._shortcutKey = 'k';\n    this._shortcutMeta = 'ctrl';\n    this._listenerController = null;\n  }\n\n  connectedCallback() {\n    this._initDialog();\n    this._initElements();\n    this._attachEventListeners();\n    this._setupGlobalShortcut();\n  }\n\n  disconnectedCallback() {\n    this._removeEventListeners();\n  }\n\n  _initDialog() {\n    this._dialog = this.querySelector('dialog') || this;\n    if (this._dialog !== this && this._dialog.tagName !== 'DIALOG') {\n      // Fallback: ensure we have a dialog\n      this._dialog = this;\n    }\n  }\n\n  _initElements() {\n    this._input = this.querySelector('.ren-command-input');\n    this._list = this.querySelector('.ren-command-list');\n    this._items = Array.from(this.querySelectorAll('.ren-command-item'));\n    this._groups = Array.from(this.querySelectorAll('.ren-command-group'));\n  }\n\n  _attachEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = new AbortController();\n    const { signal } = this._listenerController;\n\n    // Input events\n    if (this._input) {\n      this._input.addEventListener('input', (e) => this._handleInput(e), { signal });\n      this._input.addEventListener('keydown', (e) => this._handleKeydown(e), { signal });\n    }\n\n    // Item selection\n    this._items.forEach((item, index) => {\n      item.addEventListener('click', () => this._selectItem(index), { signal });\n      item.addEventListener('mouseenter', () => this._setHighlighted(index), { signal });\n      item.addEventListener('focus', () => this._setHighlighted(index), { signal });\n    });\n\n    // Dialog close\n    if (this._dialog && this._dialog.tagName === 'DIALOG') {\n      this._dialog.addEventListener('cancel', () => this._close(), { signal });\n    }\n\n    // Close on Escape\n    document.addEventListener('keydown', (e) => {\n      if (e.key === KEYBOARD_CODES.Escape && this._isOpen()) {\n        this._close();\n      }\n    }, { signal });\n  }\n\n  _removeEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = null;\n  }\n\n  _setupGlobalShortcut() {\n    const shortcut = this.getAttribute('data-shortcut') || 'ctrl+k';\n    const [meta, key] = shortcut.toLowerCase().split('+');\n    this._shortcutKey = key || 'k';\n    this._shortcutMeta = meta || 'ctrl';\n\n    document.addEventListener('keydown', (e) => {\n      const isMetaKey = this._shortcutMeta === 'ctrl' ? e.ctrlKey : e.metaKey;\n      if (isMetaKey && e.key.toLowerCase() === this._shortcutKey) {\n        e.preventDefault();\n        this._open();\n      }\n    }, { signal: this._listenerController?.signal });\n  }\n\n  _open() {\n    if (this._dialog.tagName === 'DIALOG') {\n      this._dialog.showModal?.();\n    } else {\n      this.style.display = 'block';\n    }\n\n    if (this._input) {\n      this._input.focus();\n      this._input.select();\n    }\n\n    this._highlightedIndex = 0;\n    this._updateUI();\n  }\n\n  _close() {\n    if (this._dialog.tagName === 'DIALOG') {\n      this._dialog.close?.();\n    } else {\n      this.style.display = 'none';\n    }\n\n    this._input?.blur();\n  }\n\n  _isOpen() {\n    if (this._dialog.tagName === 'DIALOG') {\n      return this._dialog.open;\n    }\n    return this.style.display !== 'none';\n  }\n\n  _handleInput(e) {\n    const query = e.target.value.toLowerCase().trim();\n    this._lastQuery = query;\n    this._filterItems(query);\n    this._highlightedIndex = 0;\n    this._updateUI();\n  }\n\n  _handleKeydown(e) {\n    switch (e.key) {\n      case KEYBOARD_CODES.ArrowDown:\n        e.preventDefault();\n        this._moveHighlight(1);\n        break;\n      case KEYBOARD_CODES.ArrowUp:\n        e.preventDefault();\n        this._moveHighlight(-1);\n        break;\n      case KEYBOARD_CODES.Enter:\n        e.preventDefault();\n        const visibleItem = this._getVisibleItems()[this._highlightedIndex];\n        if (visibleItem) {\n          this._selectItem(this._items.indexOf(visibleItem));\n        }\n        break;\n      case KEYBOARD_CODES.Escape:\n        e.preventDefault();\n        this._close();\n        break;\n    }\n  }\n\n  _filterItems(query) {\n    this._items.forEach(item => {\n      if (!query) {\n        item.style.display = '';\n        return;\n      }\n\n      const title = item.querySelector('.ren-command-item-title')?.textContent || '';\n      const description = item.querySelector('.ren-command-item-description')?.textContent || '';\n      const keywords = item.getAttribute('data-keywords') || '';\n      const searchableText = `${title} ${description} ${keywords}`.toLowerCase();\n\n      const matches = query.split(' ').every(term => searchableText.includes(term));\n      item.style.display = matches ? '' : 'none';\n    });\n\n    this._updateGroupVisibility();\n    this._announceResults();\n  }\n\n  _updateGroupVisibility() {\n    this._groups.forEach(group => {\n      const visibleItems = Array.from(group.querySelectorAll('.ren-command-item'))\n        .filter(item => item.style.display !== 'none');\n\n      if (visibleItems.length === 0) {\n        group.setAttribute('data-empty', '');\n      } else {\n        group.removeAttribute('data-empty');\n      }\n    });\n\n    const hasVisibleItems = this._getVisibleItems().length > 0;\n    const empty = this.querySelector('.ren-command-empty');\n    if (empty) {\n      empty.style.display = hasVisibleItems ? 'none' : 'block';\n    }\n  }\n\n  _getVisibleItems() {\n    return this._items.filter(item => item.style.display !== 'none');\n  }\n\n  /**\n   * Announce number of search results to screen readers\n   * @private\n   */\n  _announceResults() {\n    const visibleItems = this._getVisibleItems();\n    const resultCount = visibleItems.length;\n\n    // Find or create announcement region\n    let announcement = this.querySelector('[role=\"status\"][aria-live=\"polite\"]');\n    if (!announcement) {\n      announcement = document.createElement('div');\n      announcement.setAttribute('role', 'status');\n      announcement.setAttribute('aria-live', 'polite');\n      announcement.className = 'ren-sr-only';\n      this.appendChild(announcement);\n    }\n\n    if (resultCount === 0) {\n      announcement.textContent = 'No results found';\n    } else {\n      announcement.textContent = `${resultCount} result${resultCount !== 1 ? 's' : ''} available`;\n    }\n  }\n\n  _moveHighlight(direction) {\n    const visibleItems = this._getVisibleItems();\n    if (visibleItems.length === 0) return;\n\n    let newIndex = this._highlightedIndex + direction;\n    if (newIndex < 0) newIndex = visibleItems.length - 1;\n    if (newIndex >= visibleItems.length) newIndex = 0;\n\n    this._highlightedIndex = newIndex;\n    this._updateUI({ focus: true });\n\n    const item = visibleItems[newIndex];\n    item?.scrollIntoView({ block: 'nearest' });\n  }\n\n  _setHighlighted(itemIndex) {\n    this._highlightedIndex = this._getVisibleItems().indexOf(this._items[itemIndex]);\n    if (this._highlightedIndex === -1) this._highlightedIndex = 0;\n    this._updateUI();\n  }\n\n  _updateUI({ focus = false } = {}) {\n    const visibleItems = this._getVisibleItems();\n    visibleItems.forEach((item, index) => {\n      if (index === this._highlightedIndex) {\n        item.setAttribute('data-highlighted', '');\n        if (focus) {\n          item.focus();\n        }\n      } else {\n        item.removeAttribute('data-highlighted');\n      }\n    });\n  }\n\n  _selectItem(itemIndex) {\n    const item = this._items[itemIndex];\n    if (!item || item.style.display === 'none') return;\n\n    const value = item.getAttribute('data-value') || item.textContent;\n    const action = item.getAttribute('data-action');\n\n    const event = new CustomEvent('ren-command-select', {\n      detail: { item, value, action },\n      bubbles: true,\n      composed: true\n    });\n    this.dispatchEvent(event);\n\n    // Call registered action handler\n    if (action && this._shortcuts.has(action)) {\n      this._shortcuts.get(action)();\n    }\n\n    this._resetCommand();\n    this._close();\n  }\n\n  _resetCommand() {\n    if (this._input) {\n      this._input.value = '';\n      this._lastQuery = '';\n    }\n    this._filterItems('');\n    this._highlightedIndex = 0;\n    this._updateUI();\n  }\n\n  // Public API\n  open() {\n    this._open();\n  }\n\n  close() {\n    this._close();\n  }\n\n  registerAction(id, handler) {\n    if (typeof handler === 'function') {\n      this._shortcuts.set(id, handler);\n    }\n  }\n\n  unregisterAction(id) {\n    this._shortcuts.delete(id);\n  }\n\n  setItems(items) {\n    if (Array.isArray(items)) {\n      this._items = items;\n      this._initElements();\n      this._updateUI();\n    }\n  }\n\n  get query() {\n    return this._lastQuery;\n  }\n\n  get isOpen() {\n    return this._isOpen();\n  }\n}\n\ncustomElements.define('ren-command', RenCommand);\n",
      "path": "components/patterns/ren-command",
      "id": "component:pattern:ren-command",
      "type": "component",
      "name": "ren-command"
    },
    {
      "data": {
        "kind": "pattern",
        "selectors": [
          ".ren-empty-state",
          ".ren-empty-state-actions",
          ".ren-empty-state-bordered",
          ".ren-empty-state-compact",
          ".ren-empty-state-description",
          ".ren-empty-state-icon",
          ".ren-empty-state-title"
        ],
        "tokens": [
          "--body-size",
          "--caption-size",
          "--color-border",
          "--color-fill",
          "--color-text",
          "--color-text-muted",
          "--leading-normal",
          "--radius-full",
          "--radius-lg",
          "--ren-empty-desc-color",
          "--ren-empty-gap",
          "--ren-empty-icon-size",
          "--ren-empty-max-width",
          "--ren-empty-padding",
          "--ren-empty-title-size",
          "--space-10",
          "--space-2",
          "--space-3",
          "--space-4",
          "--space-6",
          "--space-8",
          "--text-3xl",
          "--text-lg",
          "--text-sm",
          "--text-xl",
          "--title-sm-size",
          "--weight-semibold",
          "--width-prose"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-empty-state Pattern Contract\n\nEmpty state pattern for no-data, first-run, and recovery surfaces.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-empty-state` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-empty-state` pattern.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Empty State pattern behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this pattern.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"A data container (ren-table, list, ren-card, search result) renders zero items and needs a no-data surface.\"\n    - \"A first-run experience needs an icon + title + description + primary action (e.g. \\\"Add your first product\\\").\"\n    - \"A search / filter result is empty and the surface should explain how to recover (.ren-empty-state-compact).\"\n    - \"An error / recovery state needs the same centered layout as zero-data (e.g. \\\"Nothing here yet\\\").\"\n    - \"You want a dashed-bordered placeholder inside a card or table body (.ren-empty-state-bordered).\"\n  avoidWhen:\n    - \"The UI is loading data (still pending) — use ren-skeleton, not an empty state.\"\n    - \"The page is a real 404 / 500 error route — use the error-page template, not this inline component.\"\n    - \"The container is full but a row is unselected — that is a selection-prompt, not an empty state.\"\n    - \"You need a toast / inline alert about a failed action — use ren-alert / ren-toast.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-empty-state/ren-empty-state.css\"\n  notes:\n    - \"CSS-only pattern. No JS — actions inside .ren-empty-state-actions are real <button> / ren-button elements wired by the consumer.\"\n    - \"Drop directly into <tbody>, <ul>, or any container — it centers itself with max-width: 28rem and auto inline margins.\"\n\nrequiredMarkup:\n  - \"Root is <div class=\\\"ren-empty-state\\\"> (or a <section> for a landmark).\"\n  - \"Use an .ren-empty-state-icon block for the glyph/illustration; mark decorative icons aria-hidden=\\\"true\\\".\"\n  - \"Heading is an <h2> / <h3> with class .ren-empty-state-title — never put plain text without a heading element.\"\n  - \"Description text is a <p class=\\\"ren-empty-state-description\\\">; keep within the 28rem prose width by not overriding the max-width.\"\n  - \"Actions live in <div class=\\\"ren-empty-state-actions\\\"> and should be real <button class=\\\"ren-btn\\\"> or <a class=\\\"ren-btn\\\"> — not links styled as buttons via <span>.\"\n\nforbiddenPatterns:\n  - \"Using .ren-empty-state as a generic centered hero — it is reserved for no-data / zero-state surfaces.\"\n  - \"Combining .ren-empty-state-compact with .ren-empty-state-bordered when the container is already a bordered card (double border).\"\n  - \"Decorative icon as the only message — always include .ren-empty-state-title so screen readers understand the state.\"\n  - \"Hardcoding padding / max-width via inline style — override --ren-empty-padding / --ren-empty-max-width instead.\"\n  - \"Embedding form fields directly inside .ren-empty-state — pair with ren-form / ren-button actions only.\"\n\ntokenPolicy:\n  allowed:\n    - \"Public component tokens listed in Public Token API: --ren-empty-padding, --ren-empty-gap, --ren-empty-icon-size, --ren-empty-title-size, --ren-empty-desc-color, --ren-empty-max-width.\"\n    - \"Semantic tokens consumed by selectors: --color-text, --color-text-muted, --color-fill, --color-border.\"\n    - \"Layout / type tokens: --space-*, --radius-full, --radius-lg, --width-prose, --text-xl, --text-3xl, --body-size, --caption-size, --weight-semibold, --leading-normal.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) for the icon background or borders.\"\n    - \"Hardcoded hex / named colors for text, fill, or border.\"\n    - \"Custom widths / margins that bypass --ren-empty-max-width; the component is meant to self-center.\"\n\naccessibility:\n  required:\n    - \"Title is a real heading element (<h2> / <h3>) so the empty state appears in the document outline.\"\n    - \"Decorative icons / emoji use aria-hidden=\\\"true\\\"; meaningful illustrations need alt text or aria-label.\"\n    - \"Primary action is a real <button> or <a> with keyboard activation and a 44px touch target (inherited from .ren-btn).\"\n    - \"Do not communicate the empty state through icon color alone — the title text must remain the source of truth.\"\n    - \"If the empty state is announced after an async load, wrap it in role=\\\"status\\\" or aria-live=\\\"polite\\\" so screen readers learn the new state.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-empty-state/ren-empty-state.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<section class=\"ren-empty-state\"><div class=\"ren-empty-state-icon\" aria-hidden=\"true\">□</div><h2 class=\"ren-empty-state-title\">No projects</h2><p class=\"ren-empty-state-description\">Create a project to get started.</p><div class=\"ren-empty-state-actions\"><button class=\"ren-btn\" type=\"button\">Create project</button></div></section>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-empty-state`\n- `.ren-empty-state-actions`\n- `.ren-empty-state-bordered`\n- `.ren-empty-state-compact`\n- `.ren-empty-state-description`\n- `.ren-empty-state-icon`\n- `.ren-empty-state-title`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-empty-desc-color`\n- `--ren-empty-gap`\n- `--ren-empty-icon-size`\n- `--ren-empty-max-width`\n- `--ren-empty-padding`\n- `--ren-empty-title-size`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/patterns/ren-empty-state/ren-empty-state.css`\n- `docs/components/ren-empty-state.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Empty State Pattern\n   ============================================\n   Reusable pattern for empty/no-data states.\n   CSS-only. No JS needed.\n\n   Use inside any data container (table, list,\n   card) when there's nothing to display.\n\n   Inspired by Polaris: every data component\n   should have a documented empty state.\n\n   Usage:\n     <div class=\"ren-empty-state\">\n       <div class=\"ren-empty-state-icon\">📦</div>\n       <h3 class=\"ren-empty-state-title\">No products yet</h3>\n       <p class=\"ren-empty-state-description\">\n         Add your first product to get started.\n       </p>\n       <div class=\"ren-empty-state-actions\">\n         <button class=\"ren-btn\">Add product</button>\n       </div>\n     </div>\n\n   Compact (inline):\n     <div class=\"ren-empty-state ren-empty-state-compact\">\n       <p class=\"ren-empty-state-description\">No results found.</p>\n     </div>\n   ============================================ */\n\n.ren-empty-state {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  text-align: center;\n  padding: var(--space-8) var(--space-4);\n  max-width: 28rem;\n  margin-inline: auto;\n}\n\n/* ─── Icon / Illustration ─── */\n.ren-empty-state-icon {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 4rem;\n  height: 4rem;\n  font-size: var(--text-3xl);\n  color: var(--color-text-muted);\n  background-color: var(--color-fill);\n  border-radius: var(--radius-full);\n  margin-bottom: var(--space-4);\n}\n\n/* ─── Title ─── */\n.ren-empty-state-title {\n  font-size: var(--title-sm-size, var(--text-lg));\n  font-weight: var(--weight-semibold);\n  color: var(--color-text);\n  margin-bottom: var(--space-2);\n}\n\n/* ─── Description ─── */\n.ren-empty-state-description {\n  font-size: var(--body-size);\n  color: var(--color-text-muted);\n  line-height: var(--leading-normal);\n  margin-bottom: var(--space-4);\n  max-width: var(--width-prose);\n}\n\n.ren-empty-state-description:last-child {\n  margin-bottom: 0;\n}\n\n/* ─── Actions ─── */\n.ren-empty-state-actions {\n  display: flex;\n  gap: var(--space-2);\n  flex-wrap: wrap;\n  justify-content: center;\n}\n\n/* ─── Compact variant (for inline empty states) ─── */\n.ren-empty-state-compact {\n  padding: var(--space-6) var(--space-4);\n}\n\n.ren-empty-state-compact .ren-empty-state-icon {\n  width: 2.5rem;\n  height: 2.5rem;\n  font-size: var(--text-xl);\n  margin-bottom: var(--space-2);\n}\n\n.ren-empty-state-compact .ren-empty-state-title {\n  font-size: var(--body-size);\n}\n\n.ren-empty-state-compact .ren-empty-state-description {\n  font-size: var(--caption-size, var(--text-sm));\n}\n\n/* ─── Bordered variant (for use inside cards/tables) ─── */\n.ren-empty-state-bordered {\n  border: 2px dashed var(--color-border);\n  border-radius: var(--radius-lg);\n  margin: var(--space-4);\n}\n/* Appearance API bridge */\n.ren-empty-state { max-inline-size: var(--ren-empty-max-width, 24rem); padding: var(--ren-empty-padding, var(--space-10)); gap: var(--ren-empty-gap, var(--space-3)); }\n.ren-empty-state .ren-empty-state-icon { inline-size: var(--ren-empty-icon-size, 3rem); block-size: var(--ren-empty-icon-size, 3rem); }\n.ren-empty-state h1, .ren-empty-state h2, .ren-empty-state h3 { font-size: var(--ren-empty-title-size, var(--title-sm-size)); }\n.ren-empty-state p { color: var(--ren-empty-desc-color, var(--color-text-muted)); }\n",
      "path": "components/patterns/ren-empty-state",
      "id": "component:pattern:ren-empty-state",
      "type": "component",
      "name": "ren-empty-state"
    },
    {
      "data": {
        "kind": "pattern",
        "selectors": [
          ".ren-field",
          ".ren-form",
          ".ren-form-actions",
          ".ren-form-error-summary",
          ".ren-form-progress",
          ".ren-form-row",
          ".ren-form-section",
          ".ren-form-section-title",
          ".ren-form-step",
          ".ren-form-step-label",
          ".ren-form-success"
        ],
        "tokens": [
          "--body-size",
          "--body-sm-size",
          "--caption-size",
          "--color-accent",
          "--color-accent-subtle",
          "--color-border",
          "--color-border-interactive",
          "--color-danger",
          "--color-danger-subtle",
          "--color-fill",
          "--color-input-bg",
          "--color-input-border-focus",
          "--color-input-error-border",
          "--color-input-placeholder",
          "--color-on-accent",
          "--color-on-success",
          "--color-success",
          "--color-success-subtle",
          "--color-text",
          "--color-text-faint",
          "--color-text-muted",
          "--duration-enter",
          "--duration-state",
          "--ease-enter",
          "--label-size",
          "--label-weight",
          "--leading-normal",
          "--radius-md",
          "--ren-field-bg",
          "--ren-field-border-color",
          "--ren-field-border-width",
          "--ren-field-color",
          "--ren-field-error-color",
          "--ren-field-focus-color",
          "--ren-field-font-size",
          "--ren-field-height",
          "--ren-field-padding-x",
          "--ren-field-padding-y",
          "--ren-field-placeholder",
          "--ren-field-radius",
          "--ren-field-success-color",
          "--size-lg",
          "--space-1",
          "--space-2",
          "--space-3",
          "--space-4",
          "--space-6",
          "--stroke-1",
          "--title-sm-leading",
          "--title-sm-size",
          "--title-sm-weight",
          "--weight-bold",
          "--weight-semibold"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-form\n\nForm validation + layout pattern. Wraps a native `<form>` with declarative\nfield rules, error summaries, multi-step support, and emit-on-submit\nevents. Pairs with `ren-field` for control-level wiring.\n\nLoad this file after `ren-design.md` and before generating, editing, or\nreviewing any `<ren-form>` UI.\n\n## Purpose\n\nThe form-level pattern in RenDS. Owns:\n- Declarative validation (`data-rules`) with built-in validators\n  (`required`, `email`, `min`, `max`, `pattern`, `match`, etc.).\n- Validation timing modes (`onSubmit`, `onBlur`, `onChange`, `onTouched`).\n- Error summary management at the top of the form.\n- Multi-step (wizard) progression via `data-steps`.\n- Submitting / disabled state propagation across the form.\n\nField-level ARIA wiring is delegated to `ren-field`. Layout uses\n`ren-stack` / `ren-form-row` / `ren-form-section`.\n\n## Use When\n\n- The page is a real form (login, signup, settings, checkout, multi-step\n  wizard) that needs declarative validation rules.\n- You want a single error summary at the top of the form, focusable on\n  invalid submit.\n- You want consistent submitting state and event-driven submission.\n- You are using `ren-field` to wrap the controls.\n\n## Do Not Use When\n\n- The \"form\" is a single search input — use `<form>` + `ren-field` directly.\n- The \"form\" is a chat composer or freeform editor — use a custom flow.\n- You need fully-custom field validation that does not fit the\n  `data-rules` DSL — use a plain `<form>` with your own JS.\n- The interaction is a step-by-step UI without form fields — use\n  `ren-tabs` or `ren-empty-state` flows.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The UI is a real form with multiple fields and validation.\"\n    - \"You want declarative rules (data-rules=\\\"required|email|min:8\\\").\"\n    - \"You want a single error summary that focuses on invalid submit.\"\n    - \"You want submitting state to disable the form during async submission.\"\n    - \"You want a multi-step wizard with progress indicator.\"\n  avoidWhen:\n    - \"It is a single-input search/filter — use ren-field alone.\"\n    - \"Validation logic is irreducibly custom and the data-rules DSL would not express it.\"\n    - \"The flow is not actually a form (chat composer, command palette, etc.).\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-form/ren-form.css\"\n  js:\n    - \"rends/components/patterns/ren-form/ren-form.js\"\n  notes:\n    - \"Always pair with rends/components/primitives/ren-field/ren-field.css + ren-field.js for field wiring.\"\n\nrequiredMarkup:\n  - \"<ren-form> wraps a real <form>. Do not omit the <form> element.\"\n  - \"Each input lives inside <ren-field>; do not use bare <input> at the top level of the form.\"\n  - \"Add data-validate=\\\"onSubmit|onBlur|onChange|onTouched\\\" on <ren-form>; default is onSubmit.\"\n  - \"If using rules, attach data-rules to each <ren-field> (or to the inner control), not to the <form>.\"\n  - \"Submit button must be type=\\\"submit\\\" inside the <form>.\"\n  - \"Provide an empty .ren-form-error-summary with role=\\\"alert\\\" tabindex=\\\"-1\\\" so the component can populate AND focus it on invalid submit. The component sets tabindex=\\\"-1\\\" automatically if you forget.\"\n\nforbiddenPatterns:\n  - \"<ren-form> wrapping a <div> instead of a <form>.\"\n  - \"Manual aria-invalid / aria-describedby — ren-field owns those.\"\n  - \"Submitting via fetch from a custom click handler that bypasses the ren-submit event.\"\n  - \"Using a placeholder as the only label.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-field-* for input chrome, --ren-btn-* for actions.\"\n    - \"Layout tokens (--space-*) and semantic colors for content.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, etc.).\"\n    - \"Hardcoded colors / sizes that bypass tokens.\"\n\naccessibility:\n  required:\n    - \"Native <form> semantics; preserve the submit event.\"\n    - \"Each control has a <label> via ren-field.\"\n    - \"Error summary has role=\\\"alert\\\" AND tabindex=\\\"-1\\\" so .focus() actually shifts focus on invalid submit. The component sets tabindex=\\\"-1\\\" if missing.\"\n    - \"Required-state must be explicit (required attribute), not placeholder-only.\"\n    - \"Disabled / submitting state cannot strip focus rings.\"\n```\n\n## Required CSS / JS Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-form/ren-form.css\">\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-field/ren-field.css\">\n<script type=\"module\" src=\"rends/components/patterns/ren-form/ren-form.js\"></script>\n<script type=\"module\" src=\"rends/components/primitives/ren-field/ren-field.js\"></script>\n```\n\n## Canonical Markup\n\nSingle-step login form with onTouched validation:\n\n```html\n<ren-form data-validate=\"onTouched\">\n  <form class=\"ren-form ren-stack\" novalidate>\n    <div class=\"ren-form-error-summary\" role=\"alert\" tabindex=\"-1\" hidden>\n      <strong>Please fix the following errors:</strong>\n      <ul></ul>\n    </div>\n\n    <ren-field data-rules=\"required|email\">\n      <label>Email</label>\n      <input class=\"ren-input\" type=\"email\" name=\"email\" autocomplete=\"email\" required>\n      <span data-error></span>\n    </ren-field>\n\n    <ren-field data-rules=\"required|min:8\">\n      <label>Password</label>\n      <input class=\"ren-input\" type=\"password\" name=\"password\" autocomplete=\"current-password\" required minlength=\"8\">\n      <span data-error></span>\n    </ren-field>\n\n    <div class=\"ren-form-actions\">\n      <button class=\"ren-btn ren-btn-full\" type=\"submit\">Sign in</button>\n    </div>\n  </form>\n</ren-form>\n\n<script type=\"module\">\n  document.querySelector('ren-form').addEventListener('ren-submit', (e) => {\n    e.preventDefault();\n    fetch('/api/login', { method: 'POST', body: JSON.stringify(e.detail.values) });\n  });\n</script>\n```\n\nMulti-step form with progress:\n\n```html\n<ren-form data-validate=\"onSubmit\" data-steps=\"3\">\n  <form class=\"ren-form ren-stack\">\n    <ol class=\"ren-form-progress\" aria-label=\"Progress\">\n      <li class=\"ren-form-step\" data-active>\n        <span class=\"ren-form-step-label\">Account</span>\n      </li>\n      <li class=\"ren-form-step\">\n        <span class=\"ren-form-step-label\">Profile</span>\n      </li>\n      <li class=\"ren-form-step\">\n        <span class=\"ren-form-step-label\">Review</span>\n      </li>\n    </ol>\n    <!-- step sections … -->\n  </form>\n</ren-form>\n```\n\n## Attributes, Events, and API\n\n- Host attributes:\n  - `data-validate=\"onSubmit|onBlur|onChange|onTouched\"` (default: `onSubmit`).\n  - `data-steps=\"N\"` enables multi-step mode.\n- Field rules: `data-rules=\"required|email|min:8|max:64|pattern:^[A-Z]+$|match:password\"`.\n- Submitting state: component sets `[data-submitting]` on `<ren-form>` and\n  disables submit buttons while the `ren-submit` consumer is async.\n- Events:\n  - `ren-submit` — `detail.values` (validated form data). Repeated names are arrays.\n    Register async work with `event.detail.waitUntil(promise)`; the\n    `[data-submitting]` state remains until every registered promise settles.\n    The event is cancelable so consumers may call `preventDefault()` when they\n    own submission. A rejected promise emits `ren-submit-error` with\n    `detail.error`.\n  - `ren-invalid` — `detail.errors` (rule failures).\n- Static API: `RenForm.registerValidator(name, fn)` for custom validators.\n  Validators may return a Promise; `validateAsync()` and submit await it.\n  Use `RenForm.registerMessages(locale, messages)` to localize `required`,\n  `email`, `pattern`, `min`, `max`, and `invalid` messages. Set `lang` on\n  `<ren-form>` (or `<html>`) for locale selection. `data-persist=\"key\"`\n  stores/restores values in `localStorage`; call `reset()` to clear it.\n\n## Variants and Public Selectors\n\n| Class                          | Role                                   |\n|--------------------------------|----------------------------------------|\n| `.ren-form`                    | Form root chrome.                      |\n| `.ren-form-section`            | Visually grouped section.              |\n| `.ren-form-section-title`      | Section heading.                       |\n| `.ren-form-row`                | Horizontal row of fields.              |\n| `.ren-form-actions`            | Footer of action buttons.              |\n| `.ren-form-error-summary`      | Top-of-form error summary.             |\n| `.ren-form-success`            | Post-submit confirmation slot.         |\n| `.ren-form-progress`           | Multi-step progress bar.               |\n| `.ren-form-step`               | Single step.                           |\n| `.ren-form-step-label`         | Step label text.                       |\n\n## States\n\n| Selector / attr        | Meaning                                                |\n|------------------------|--------------------------------------------------------|\n| `[data-submitting]`    | Form is processing; submit buttons disabled.           |\n| `[data-has-errors]`    | One or more fields are currently invalid.              |\n| `[data-invalid]`       | A specific field is invalid.                           |\n| `[data-active]`        | Active step in a multi-step form.                      |\n| `[data-completed]`     | Completed step.                                        |\n| `[data-disabled]`      | A step is locked.                                      |\n\n## Public Token API\n\nForm layout uses `ren-stack` / `--space-*`. Field chrome inherits from\n`ren-field`:\n\n- `--ren-field-bg`\n- `--ren-field-border-color`\n- `--ren-field-border-width`\n- `--ren-field-radius`\n- `--ren-field-height`\n- `--ren-field-padding-x`\n- `--ren-field-padding-y`\n- `--ren-field-color`\n- `--ren-field-placeholder`\n- `--ren-field-focus-color`\n- `--ren-field-error-color`\n- `--ren-field-success-color`\n\n## Accessibility Contract\n\n- The form is a real `<form>` element. `ren-form` is an enhancement.\n- Each control lives in `ren-field` (auto-wires label / description / error\n  IDs).\n- The error summary has `role=\"alert\"` and `tabindex=\"-1\"` (the component\n  sets `tabindex=\"-1\"` automatically if it is missing). It receives focus\n  on invalid submit so screen readers and keyboard users land on the\n  error list.\n- `required` is the source of truth for required state; visual indicators\n  pair with it.\n- Submit buttons are real `<button type=\"submit\">`.\n- During submitting state, focus stays on the form (do not yank focus).\n\n## Anti-Patterns\n\n- ❌ `<ren-form><div>…</div></ren-form>` — wrap a real `<form>`.\n- ❌ Bare `<input>` at the top of the form without a `ren-field`.\n- ❌ Bypassing `ren-submit` and POSTing from the click handler.\n- ❌ Hand-writing `aria-invalid` / `aria-describedby` — `ren-field` owns\n  these.\n- ❌ Using placeholder as a label.\n\n## Related Files\n\n- `components/patterns/ren-form/ren-form.css`\n- `components/patterns/ren-form/ren-form.js`\n- `components/primitives/ren-field/component.md`\n- `docs/components/ren-form.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n\n## Test Expectations\n\n- Run component / docs a11y coverage on submit / invalid flows.\n- Run `npm run lint` after token / selector changes.\n- Manually verify keyboard-only submit and error summary focus.\n\n\n/* ============================================\n   RenDS — Form Validation System Organism\n   ============================================\n   Multi-step form with field validation,\n   error summaries, and accessibility features.\n\n   Includes:\n   - Form container with field stack layout\n   - Section grouping and horizontal rows\n   - Error summary boxes (CSS shown/hidden)\n   - Multi-step progress indicators\n   - Loading/submitting states\n   - Responsive design\n\n   Light DOM — no Shadow DOM.\n   ============================================ */\n\n/* ─── Form Container ─── */\n.ren-form {\n  container-type: inline-size;\n  container-name: ren-form;\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-6);\n  width: 100%;\n  max-width: 100%;\n}\n\n/* Submitting state — disable interactions */\n.ren-form[data-submitting] {\n  opacity: 0.6;\n  pointer-events: none;\n}\n\n/* ─── Error Summary ─── */\n.ren-form-error-summary {\n  padding: var(--space-4);\n  background-color: var(--color-danger-subtle);\n  border: 1px solid var(--color-danger);\n  border-radius: var(--radius-md);\n  transition: all var(--duration-enter) var(--ease-enter);\n}\n\n.ren-form-error-summary[hidden],\n.ren-form-error-summary:not([data-has-errors]) {\n  display: none;\n}\n\n.ren-form-error-summary strong {\n  display: block;\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  color: var(--color-danger);\n  margin-bottom: var(--space-2);\n}\n\n.ren-form-error-summary ul {\n  list-style: none;\n  margin: 0;\n  padding: 0;\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-1);\n}\n\n.ren-form-error-summary li {\n  font-size: var(--caption-size);\n  color: var(--color-danger);\n  line-height: var(--leading-normal);\n}\n\n.ren-form-error-summary a {\n  color: var(--color-danger);\n  text-decoration: underline;\n  cursor: pointer;\n}\n\n.ren-form-error-summary a:hover {\n  text-decoration: none;\n}\n\n/* ─── Success Message ─── */\n.ren-form-success {\n  padding: var(--space-4);\n  background-color: var(--color-success-subtle);\n  border: 1px solid var(--color-success);\n  border-radius: var(--radius-md);\n  font-size: var(--body-sm-size);\n  color: var(--color-success);\n  display: flex;\n  align-items: center;\n  gap: var(--space-3);\n  transition: all var(--duration-enter) var(--ease-enter);\n}\n\n.ren-form-success[hidden] {\n  display: none;\n}\n\n/* ─── Form Sections ─── */\n.ren-form-section {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-4);\n}\n\n.ren-form-section-title {\n  font-size: var(--title-sm-size);\n  font-weight: var(--title-sm-weight);\n  color: var(--color-text);\n  margin: 0;\n  padding: 0;\n  line-height: var(--title-sm-leading);\n}\n\n/* ─── Form Row (Horizontal Layout) ─── */\n.ren-form-row {\n  display: flex;\n  flex-wrap: wrap;\n  gap: var(--space-4);\n}\n\n.ren-form-row > .ren-field {\n  flex: 1;\n  min-width: 250px;\n}\n\n/* Stack on narrow containers */\n@container ren-form (max-width: 480px) {\n  .ren-form-row {\n    flex-direction: column;\n    gap: var(--space-4);\n  }\n\n  .ren-form-row > .ren-field {\n    min-width: 100%;\n    flex: none;\n  }\n}\n\n/* ─── Form Actions ─── */\n.ren-form-actions {\n  display: flex;\n  align-items: flex-end;\n  justify-content: flex-end;\n  gap: var(--space-3);\n  margin-top: var(--space-4);\n  flex-wrap: wrap;\n}\n\n.ren-form-actions button {\n  white-space: nowrap;\n}\n\n@container ren-form (max-width: 480px) {\n  .ren-form-actions {\n    flex-direction: column-reverse;\n  }\n\n  .ren-form-actions button {\n    width: 100%;\n  }\n}\n\n/* ─── Multi-Step Progress Indicator ─── */\n.ren-form-progress {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--space-4);\n  margin-bottom: var(--space-6);\n}\n\n.ren-form-step {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n}\n\n/* Step circle */\n.ren-form-step::before {\n  content: attr(data-step);\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem;\n  height: 2.5rem;\n  min-width: 2.5rem;\n  border-radius: 50%;\n  background-color: var(--color-border);\n  color: var(--color-text-muted);\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  transition: all var(--duration-state) var(--ease-enter);\n  border: 2px solid transparent;\n}\n\n/* Step label */\n.ren-form-step-label {\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  color: var(--color-text-muted);\n  white-space: nowrap;\n  transition: all var(--duration-state) var(--ease-enter);\n}\n\n/* Divider between steps */\n.ren-form-step:not(:last-child)::after {\n  content: '';\n  flex: 1;\n  height: 2px;\n  background-color: var(--color-border);\n  min-width: 1rem;\n  transition: background-color var(--duration-state) var(--ease-enter);\n}\n\n/* Active step */\n.ren-form-step[data-active] {\n  /* Step circle */\n}\n\n.ren-form-step[data-active]::before {\n  background-color: var(--color-accent);\n  color: var(--color-on-accent);\n  border-color: var(--color-accent);\n  box-shadow: 0 0 0 3px var(--color-accent-subtle);\n}\n\n.ren-form-step[data-active] .ren-form-step-label {\n  color: var(--color-accent);\n  font-weight: var(--weight-semibold);\n}\n\n/* Completed step */\n.ren-form-step[data-completed] {\n  /* Step circle */\n}\n\n.ren-form-step[data-completed]::before {\n  background-color: var(--color-success);\n  color: var(--color-on-success);\n  border-color: var(--color-success);\n  content: '✓';\n  font-size: 1.25rem;\n  font-weight: var(--weight-bold);\n}\n\n.ren-form-step[data-completed] .ren-form-step-label {\n  color: var(--color-text-muted);\n}\n\n/* Divider after completed step is colored */\n.ren-form-step[data-completed]:not(:last-child)::after {\n  background-color: var(--color-success);\n}\n\n/* ─── Disabled step ─── */\n.ren-form-step[data-disabled] {\n  pointer-events: none;\n}\n\n.ren-form-step[data-disabled]::before {\n  background-color: var(--color-fill);\n  color: var(--color-text-faint);\n  opacity: 0.5;\n}\n\n.ren-form-step[data-disabled] .ren-form-step-label {\n  color: var(--color-text-faint);\n  opacity: 0.5;\n}\n\n/* Stack steps vertically on narrow containers */\n@container ren-form (max-width: 360px) {\n  .ren-form-progress {\n    flex-direction: column;\n    gap: var(--space-3);\n  }\n\n  .ren-form-step:not(:last-child)::after {\n    content: '';\n    width: 2px;\n    height: 1.5rem;\n    background-color: var(--color-border);\n    min-width: 0;\n  }\n}\n\n/* ─── Smooth focus management ─── */\n.ren-form:has(.ren-field[data-invalid]) {\n  scroll-behavior: smooth;\n}\n\n/* ─── Animation for error entry ─── */\n@keyframes slideInError {\n  from {\n    opacity: 0;\n    transform: translateY(-0.5rem);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n.ren-form-error-summary[data-has-errors] {\n  animation: slideInError var(--duration-enter) var(--ease-enter);\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-form-error-summary[data-has-errors],\n  .ren-form-step::before,\n  .ren-form-step-label,\n  .ren-form-step:not(:last-child)::after {\n    animation: none;\n    transition: none;\n  }\n}\n/* Appearance API bridge delegates field chrome to the primitive family. */\n.ren-form input, .ren-form select, .ren-form textarea { min-block-size: var(--ren-field-height, var(--size-lg)); padding-inline: var(--ren-field-padding-x, var(--space-3)); padding-block: var(--ren-field-padding-y, var(--space-2)); font-size: var(--ren-field-font-size, var(--body-size)); border-radius: var(--ren-field-radius, var(--radius-md)); border: var(--ren-field-border-width, var(--stroke-1)) solid var(--ren-field-border-color, var(--color-border-interactive)); background: var(--ren-field-bg, var(--color-input-bg)); color: var(--ren-field-color, var(--color-text)); }\n.ren-form input::placeholder, .ren-form textarea::placeholder { color: var(--ren-field-placeholder, var(--color-input-placeholder)); }\n.ren-form input:focus, .ren-form select:focus, .ren-form textarea:focus { border-color: var(--ren-field-focus-color, var(--color-input-border-focus)); }\n.ren-form [aria-invalid=\"true\"] { border-color: var(--ren-field-error-color, var(--color-input-error-border)); }\n.ren-form [aria-invalid=\"false\"] { border-color: var(--ren-field-success-color, var(--color-success)); }\n\n\nimport { serializeFormEntries } from './serialize.js';\n\n/**\n * RenDS — <ren-form> Form Validation System\n * ==========================================\n * Multi-step form with field validation, error summaries,\n * and accessibility features. Supports custom validators,\n * multiple validation modes, and server-side errors.\n *\n * Light DOM — no Shadow DOM.\n *\n * Usage:\n *   <ren-form data-validate=\"onTouched\" data-steps=\"3\">\n *     <form class=\"ren-form\">\n *       <div class=\"ren-form-error-summary\" hidden>\n *         <strong>Please fix the following errors:</strong>\n *         <ul></ul>\n *       </div>\n *\n *       <div class=\"ren-form-progress\">\n *         <div class=\"ren-form-step\" data-step=\"1\" data-active>\n *           <span class=\"ren-form-step-label\">Account</span>\n *         </div>\n *         <!-- ... -->\n *       </div>\n *\n *       <div class=\"ren-form-section\">\n *         <div class=\"ren-field\" data-rules=\"required|email\">\n *           <label class=\"ren-field-label\" data-required>Email</label>\n *           <input class=\"ren-input\" type=\"email\" name=\"email\" required>\n *           <span class=\"ren-field-error\" hidden></span>\n *         </div>\n *       </div>\n *\n *       <div class=\"ren-form-actions\">\n *         <button type=\"button\">Previous</button>\n *         <button type=\"submit\">Submit</button>\n *       </div>\n *     </form>\n *   </ren-form>\n */\n\nconst DEFAULT_VALIDATION_MODE = 'onSubmit';\nconst DEBOUNCE_DELAY = 300;\nconst DEFAULT_MESSAGES = { required: 'This field is required', email: 'Please enter a valid email address', pattern: 'Invalid format', invalid: 'Please correct this field', min: (n) => `Must be at least ${n} characters`, max: (n) => `Must be no more than ${n} characters` };\n\n\n/** Built-in validators */\nconst builtInValidators = {\n  required: (value) => {\n    return value && value.trim().length > 0\n      ? null\n      : 'This field is required';\n  },\n\n  email: (value) => {\n    if (!value) return null;\n    const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n    return emailRegex.test(value) ? null : 'Please enter a valid email address';\n  },\n\n  min: (min) => (value) => {\n    if (!value) return null;\n    return value.length >= parseInt(min, 10)\n      ? null\n      : `Must be at least ${min} characters`;\n  },\n\n  max: (max) => (value) => {\n    if (!value) return null;\n    return value.length <= parseInt(max, 10)\n      ? null\n      : `Must be no more than ${max} characters`;\n  },\n\n  pattern: (pattern) => (value) => {\n    if (!value) return null;\n    try {\n      const regex = new RegExp(pattern);\n      return regex.test(value) ? null : 'Invalid format';\n    } catch {\n      return 'Invalid regex pattern';\n    }\n  },\n\n  match: (fieldName) => (value, form) => {\n    const targetField = Array.from(form?.elements || []).find(\n      (element) => element.name === fieldName\n    );\n    if (!targetField) return 'Target field not found';\n    return value === targetField.value ? null : 'Fields do not match';\n  },\n};\n\nexport class RenForm extends HTMLElement {\n  static #customValidators = new Map();\n  static #messages = new Map();\n\n  constructor() {\n    super();\n    this._form = null;\n    this._fields = [];\n    this._errors = new Map();\n    this._touched = new Set();\n    this._validationMode = DEFAULT_VALIDATION_MODE;\n    this._debounceTimers = new Map();\n    this._currentStep = 1;\n    this._totalSteps = 0;\n    this._isSubmitting = false;\n    this._errorSummary = null;\n    this._successMessage = null;\n    this._listenerController = null;\n    this._persistKey = null;\n    this._submitButtonStates = new Map();\n  }\n\n  static registerValidator(name, fn) {\n    RenForm.#customValidators.set(name, fn);\n  }\n  static registerMessages(locale, messages) { RenForm.#messages.set(locale, { ...messages }); }\n\n  connectedCallback() {\n    this._form = this.querySelector('form.ren-form');\n    if (!this._form) return;\n    if (!this.hasAttribute('dir')) {\n      const locale = this.getAttribute('lang') || document.documentElement.lang || '';\n      if (/^(ar|fa|he|ur)(-|$)/i.test(locale)) this.setAttribute('dir', 'rtl');\n    }\n\n    this._initElements();\n    this._attachEventListeners();\n    this._initMultiStep();\n    this._restorePersisted();\n  }\n\n  disconnectedCallback() {\n    this._removeEventListeners();\n    this._clearDebounceTimers();\n  }\n\n  _initElements() {\n    this._validationMode = this.getAttribute('data-validate') ?? DEFAULT_VALIDATION_MODE;\n    this._fields = Array.from(this._form.querySelectorAll('ren-field, .ren-field'))\n      .filter((field, index, fields) => fields.indexOf(field) === index);\n    this._errorSummary = this._form.querySelector('.ren-form-error-summary');\n    this._successMessage = this._form.querySelector('.ren-form-success');\n\n    // The error summary must be focusable so .focus() actually moves caret /\n    // SR position when validation fails. A plain <div role=\"alert\"> is not\n    // focusable by default; set tabindex=\"-1\" if the author did not.\n    if (this._errorSummary && !this._errorSummary.hasAttribute('tabindex')) {\n      this._errorSummary.setAttribute('tabindex', '-1');\n    }\n  }\n\n  _initMultiStep() {\n    const stepsAttr = this.getAttribute('data-steps');\n    if (!stepsAttr) return;\n\n    this._totalSteps = parseInt(stepsAttr, 10);\n    this._currentStep = 1;\n    this._updateProgressIndicators();\n  }\n\n  _attachEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = new AbortController();\n    const { signal } = this._listenerController;\n\n    // Form submit\n    this._form.addEventListener('submit', (e) => this._handleSubmit(e), { signal });\n\n    // Field validation\n    this._fields.forEach((field) => {\n      const input = field.querySelector('input, select, textarea');\n      if (!input) return;\n      input.addEventListener('input', () => this._persist(), { signal });\n\n      switch (this._validationMode) {\n        case 'onBlur':\n          input.addEventListener('blur', () => this._validateField(field, input), { signal });\n          break;\n\n        case 'onChange':\n          input.addEventListener('input', () => {\n            this._debounceValidation(field, input);\n          }, { signal });\n          break;\n\n        case 'onTouched':\n          input.addEventListener('blur', () => {\n            this._touched.add(input.name);\n            this._validateField(field, input);\n          }, { signal });\n          input.addEventListener('input', () => {\n            if (this._touched.has(input.name)) {\n              this._debounceValidation(field, input);\n            }\n          }, { signal });\n          break;\n\n        case 'onSubmit':\n        default:\n          // Only validate on submit\n          break;\n      }\n    });\n  }\n\n  _debounceValidation(field, input) {\n    // Clear existing timer\n    if (this._debounceTimers.has(input.name)) {\n      clearTimeout(this._debounceTimers.get(input.name));\n    }\n\n    // Set new timer\n    const timer = setTimeout(() => {\n      this._validateField(field, input);\n      this._debounceTimers.delete(input.name);\n    }, DEBOUNCE_DELAY);\n\n    this._debounceTimers.set(input.name, timer);\n  }\n\n  _clearDebounceTimers() {\n    this._debounceTimers.forEach((timer) => clearTimeout(timer));\n    this._debounceTimers.clear();\n  }\n\n  _removeEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = null;\n  }\n\n  async _handleSubmit(e) {\n    e.preventDefault();\n    if (this._isSubmitting) return;\n\n    this._isSubmitting = true;\n\n    let result;\n    try {\n      result = await this.validateAsync();\n    } catch (error) {\n      this._isSubmitting = false;\n      this._setSubmittingState(false);\n      this.dispatchEvent(new CustomEvent('ren-submit-error', {\n        detail: { error },\n        bubbles: true,\n        composed: true,\n      }));\n      return;\n    }\n\n    if (!result.valid) {\n      this._isSubmitting = false;\n      this.dispatchEvent(\n        new CustomEvent('ren-invalid', {\n          detail: { errors: result.errors },\n          bubbles: true,\n          composed: true,\n        })\n      );\n      this._showErrorSummary(result.errors);\n      this._scrollToFirstError();\n      return;\n    }\n\n    // Success\n    this._hideErrorSummary();\n    this._setSubmittingState(true);\n\n    const values = this.getValues();\n    const completions = [];\n    const event = new CustomEvent('ren-submit', {\n      detail: { values, form: this._form, waitUntil: (promise) => {\n        if (promise && typeof promise.then === 'function') completions.push(promise);\n      } },\n      bubbles: true,\n      composed: true,\n      cancelable: true,\n    });\n\n    this.dispatchEvent(event);\n\n    try { await Promise.all(completions); }\n    catch (error) {\n      this.dispatchEvent(new CustomEvent('ren-submit-error', { detail: { error }, bubbles: true, composed: true }));\n    } finally {\n      this._setSubmittingState(false);\n      this._isSubmitting = false;\n    }\n  }\n\n  _setSubmittingState(isSubmitting) {\n    this.toggleAttribute('data-submitting', isSubmitting);\n    this._form?.toggleAttribute('data-submitting', isSubmitting);\n\n    if (isSubmitting) {\n      this._submitButtonStates.clear();\n      const submitButtons = this._form?.querySelectorAll(\n        'button:not([type]), button[type=\"submit\"], input[type=\"submit\"], input[type=\"image\"]'\n      ) || [];\n      submitButtons.forEach((button) => {\n        this._submitButtonStates.set(button, button.disabled);\n        button.disabled = true;\n      });\n      return;\n    }\n\n    this._submitButtonStates.forEach((wasDisabled, button) => {\n      if (button.isConnected) button.disabled = wasDisabled;\n    });\n    this._submitButtonStates.clear();\n  }\n\n  _validateField(field, input) {\n    const rules = field.getAttribute('data-rules') ?? input.getAttribute('data-rules') ?? '';\n    const error = this._nativeValidationError(input) || this._runValidators(rules, input.value, input.name);\n\n    if (error) {\n      this._setFieldError(field, input, error);\n      this._errors.set(input.name, error);\n    } else {\n      this._clearFieldError(field, input);\n      this._errors.delete(input.name);\n    }\n\n    // Dispatch field validation event\n    this.dispatchEvent(\n      new CustomEvent('ren-field-validated', {\n        detail: {\n          name: input.name,\n          valid: !error,\n          error,\n        },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  _runValidators(rulesString, value, fieldName) {\n    if (!rulesString) return null;\n\n    const rules = rulesString.split('|').map((r) => r.trim());\n\n    for (const rule of rules) {\n      let validator = null;\n      let param = null;\n\n      if (rule.includes(':')) {\n        [validator, param] = rule.split(':');\n      } else {\n        validator = rule;\n      }\n\n      // Get validator function\n      let validatorFn = builtInValidators[validator];\n      if (!validatorFn && RenForm.#customValidators.has(validator)) {\n        validatorFn = RenForm.#customValidators.get(validator);\n      }\n\n      if (!validatorFn) continue;\n\n      // Run validator\n      let error;\n      if (param) {\n        if (validator === 'match') {\n          error = validatorFn(param)(value, this._form);\n        } else {\n          error = validatorFn(param)(value);\n        }\n      } else {\n        error = validatorFn(value);\n      }\n\n      if (error) return this._message(error, validator, param);\n    }\n\n    return null;\n  }\n\n  async _runValidatorsAsync(rulesString, value) {\n    if (!rulesString) return null;\n    for (const rule of rulesString.split('|').map((r) => r.trim())) {\n      const [name, param] = rule.split(':');\n      const fn = builtInValidators[name] || RenForm.#customValidators.get(name);\n      if (!fn) continue;\n      let result = param ? fn(param)(value, this._form) : fn(value, this._form);\n      if (result && typeof result.then === 'function') result = await result;\n      if (result) return this._message(result, name, param);\n    }\n    return null;\n  }\n\n  _message(message, rule, param) {\n    const locale = this.getAttribute('lang') || document.documentElement.lang || 'en';\n    const custom = RenForm.#messages.get(locale) || RenForm.#messages.get(locale.split('-')[0]);\n    const value = custom?.[rule] ?? DEFAULT_MESSAGES[rule];\n    return typeof value === 'function' ? value(param) : (value || message);\n  }\n\n  _setFieldError(field, input, message) {\n    field.setAttribute('data-invalid', '');\n    input.setAttribute('aria-invalid', 'true');\n\n    const errorEl = field.querySelector('.ren-field-error');\n    if (errorEl) {\n      errorEl.textContent = message;\n      errorEl.hidden = false;\n      if (!errorEl.id) {\n        errorEl.id = `error-${input.name}`;\n      }\n      input.setAttribute('aria-errormessage', errorEl.id);\n    }\n  }\n\n  _clearFieldError(field, input) {\n    field.removeAttribute('data-invalid');\n    input.removeAttribute('aria-invalid');\n\n    const errorEl = field.querySelector('.ren-field-error');\n    if (errorEl) {\n      errorEl.hidden = true;\n      errorEl.textContent = '';\n    }\n  }\n\n  _showErrorSummary(errors) {\n    if (!this._errorSummary) return;\n\n    const ul = this._errorSummary.querySelector('ul');\n    if (!ul) return;\n\n    ul.replaceChildren();\n    errors.forEach((error) => {\n      const li = document.createElement('li');\n      const link = document.createElement('a');\n      const field = this._findFieldInput(error.name);\n\n      link.href = field?.id ? `#${field.id}` : '#';\n      link.textContent = `${error.name}: ${error.message}`;\n      link.addEventListener('click', (e) => {\n        e.preventDefault();\n        if (field) field.focus();\n      });\n\n      li.appendChild(link);\n      ul.appendChild(li);\n    });\n\n    this._errorSummary.setAttribute('data-has-errors', '');\n    this._errorSummary.removeAttribute('hidden');\n    this._errorSummary.focus();\n  }\n\n  _findFieldInput(name) {\n    return Array.from(this._form?.elements || []).find(\n      (element) => element.name === name\n    );\n  }\n\n  _hideErrorSummary() {\n    if (!this._errorSummary) return;\n    this._errorSummary.removeAttribute('data-has-errors');\n    this._errorSummary.setAttribute('hidden', '');\n  }\n\n  _scrollToFirstError() {\n    const firstInvalid = this._form.querySelector('[data-invalid]');\n    if (firstInvalid) {\n      firstInvalid.scrollIntoView?.({ behavior: 'smooth', block: 'center' });\n    }\n  }\n\n  _updateProgressIndicators() {\n    const steps = this.querySelectorAll('.ren-form-step');\n    steps.forEach((step, idx) => {\n      const stepNum = idx + 1;\n      step.setAttribute('data-step', stepNum);\n\n      if (stepNum < this._currentStep) {\n        step.setAttribute('data-completed', '');\n        step.removeAttribute('data-active');\n        step.removeAttribute('data-disabled');\n      } else if (stepNum === this._currentStep) {\n        step.removeAttribute('data-completed');\n        step.setAttribute('data-active', '');\n        step.removeAttribute('data-disabled');\n      } else {\n        step.removeAttribute('data-completed');\n        step.removeAttribute('data-active');\n        step.setAttribute('data-disabled', '');\n      }\n    });\n    this.querySelectorAll('.ren-form-section[data-step], [data-ren-step-panel]').forEach((panel) => {\n      const step = Number(panel.getAttribute('data-step') || panel.getAttribute('data-ren-step-panel'));\n      panel.hidden = step !== this._currentStep;\n      panel.toggleAttribute('data-active', step === this._currentStep);\n    });\n  }\n\n  // ─── Public API ───\n\n  validate() {\n    const errors = [];\n\n    this._fields.forEach((field) => {\n      const input = field.querySelector('input, select, textarea');\n      if (!input) return;\n\n      const rules = field.getAttribute('data-rules') ?? input.getAttribute('data-rules') ?? '';\n      const error = this._nativeValidationError(input) || this._runValidators(rules, input.value, input.name);\n\n      if (error) {\n        this._setFieldError(field, input, error);\n        errors.push({ name: input.name, message: error });\n      } else {\n        this._clearFieldError(field, input);\n      }\n    });\n\n    return {\n      valid: errors.length === 0,\n      errors,\n    };\n  }\n\n  async validateAsync() {\n    const errors = [];\n    for (const field of this._fields) {\n      const input = field.querySelector('input, select, textarea');\n      if (!input) continue;\n      const rules = field.getAttribute('data-rules') ?? input.getAttribute('data-rules') ?? '';\n      const error = this._nativeValidationError(input) || await this._runValidatorsAsync(rules, input.value);\n      if (error) { this._setFieldError(field, input, error); this._errors.set(input.name, error); errors.push({ name: input.name, message: error }); }\n      else this._clearFieldError(field, input);\n    }\n    return { valid: errors.length === 0, errors };\n  }\n\n  reset() {\n    this._form.reset();\n    this._setSubmittingState(false);\n    this._isSubmitting = false;\n    this._errors.clear();\n    this._touched.clear();\n    this._debounceTimers.forEach((timer) => clearTimeout(timer));\n    this._debounceTimers.clear();\n\n    this._fields.forEach((field) => {\n      const input = field.querySelector('input, select, textarea');\n      if (input) {\n        this._clearFieldError(field, input);\n      }\n    });\n\n    this._hideErrorSummary();\n    this._updateProgressIndicators();\n    this._clearPersisted();\n  }\n\n  _restorePersisted() {\n    this._persistKey = this.getAttribute('data-persist');\n    if (!this._persistKey) return;\n    try {\n      const values = JSON.parse(localStorage.getItem(this._persistKey) || '{}');\n      this._form.querySelectorAll('[name]').forEach((input) => {\n        const value = values[input.name]; if (value == null) return;\n        if (input.type === 'checkbox' || input.type === 'radio') input.checked = Array.isArray(value) ? value.includes(input.value) : Boolean(value);\n        else input.value = Array.isArray(value) ? value[0] : value;\n      });\n    } catch { /* storage is optional */ }\n  }\n\n  _persist() {\n    if (!this._persistKey) return;\n    try { localStorage.setItem(this._persistKey, JSON.stringify(this.getValues())); } catch { /* private mode/quota */ }\n  }\n\n  _clearPersisted() { if (this._persistKey) { try { localStorage.removeItem(this._persistKey); } catch { /* optional */ } } }\n\n  getValues() {\n    const formData = new FormData(this._form);\n    return serializeFormEntries(formData.entries());\n  }\n\n  setErrors(errors) {\n    this._errors.clear();\n\n    errors.forEach((error) => {\n      const field = this._form.querySelector(\n        `.ren-field:has([name=\"${error.name}\"])`\n      );\n      const input = field?.querySelector('input, select, textarea');\n\n      if (field && input) {\n        this._setFieldError(field, input, error.message);\n        this._errors.set(error.name, error.message);\n      }\n    });\n\n    this._showErrorSummary(errors);\n  }\n\n  setFieldError(name, message) {\n    const field = this._form.querySelector(\n      `.ren-field:has([name=\"${name}\"])`\n    );\n    const input = field?.querySelector('input, select, textarea');\n\n    if (field && input) {\n      this._setFieldError(field, input, message);\n      this._errors.set(name, message);\n    }\n  }\n\n  clearFieldError(name) {\n    const field = this._form.querySelector(\n      `.ren-field:has([name=\"${name}\"])`\n    );\n    const input = field?.querySelector('input, select, textarea');\n\n    if (field && input) {\n      this._clearFieldError(field, input);\n      this._errors.delete(name);\n    }\n  }\n\n  // ─── Multi-Step API ───\n\n  nextStep() {\n    if (this._currentStep >= this._totalSteps) return false;\n\n    // Validate current step fields\n    const currentStepFields = this._getStepFields(this._currentStep);\n    const errors = [];\n\n    currentStepFields.forEach((field) => {\n      const input = field.querySelector('input, select, textarea');\n      if (!input) return;\n\n      const rules = field.getAttribute('data-rules') ?? input.getAttribute('data-rules') ?? '';\n      const error = this._nativeValidationError(input) || this._runValidators(rules, input.value, input.name);\n\n      if (error) {\n        this._setFieldError(field, input, error);\n        errors.push({ name: input.name, message: error });\n      } else {\n        this._clearFieldError(field, input);\n      }\n    });\n\n    if (errors.length > 0) {\n      return false;\n    }\n\n    this._currentStep++;\n    this._updateProgressIndicators();\n    this._hideErrorSummary();\n\n    this.dispatchEvent(\n      new CustomEvent('ren-step-change', {\n        detail: { step: this._currentStep, totalSteps: this._totalSteps },\n        bubbles: true,\n        composed: true,\n      })\n    );\n\n    return true;\n  }\n\n  prevStep() {\n    if (this._currentStep <= 1) return false;\n\n    this._currentStep--;\n    this._updateProgressIndicators();\n    this._hideErrorSummary();\n\n    this.dispatchEvent(\n      new CustomEvent('ren-step-change', {\n        detail: { step: this._currentStep, totalSteps: this._totalSteps },\n        bubbles: true,\n        composed: true,\n      })\n    );\n\n    return true;\n  }\n\n  goToStep(stepNum) {\n    if (stepNum < 1 || stepNum > this._totalSteps) return false;\n\n    this._currentStep = stepNum;\n    this._updateProgressIndicators();\n    this._hideErrorSummary();\n\n    this.dispatchEvent(\n      new CustomEvent('ren-step-change', {\n        detail: { step: this._currentStep, totalSteps: this._totalSteps },\n        bubbles: true,\n        composed: true,\n      })\n    );\n\n    return true;\n  }\n\n  get currentStep() {\n    return this._currentStep;\n  }\n\n  get totalSteps() {\n    return this._totalSteps;\n  }\n\n  _getStepFields(stepNum) {\n    const panel = this.querySelector(`.ren-form-section[data-step=\"${stepNum}\"], [data-ren-step-panel=\"${stepNum}\"]`);\n    if (panel) return this._fields.filter((field) => panel.contains(field));\n    return this._fields.filter((field) => {\n      const owner = field.closest('[data-step]');\n      return !owner || owner.getAttribute('data-step') === String(stepNum);\n    });\n  }\n\n  _nativeValidationError(input) {\n    if (input.validity?.valid) return null;\n    if (input.validity?.valueMissing) return this._message('', 'required');\n    if (input.validity?.typeMismatch) return this._message('', 'email');\n    if (input.validity?.patternMismatch) return this._message('', 'pattern');\n    if (input.validity?.tooShort) return this._message('', 'min', input.minLength);\n    if (input.validity?.tooLong) return this._message('', 'max', input.maxLength);\n    if (input.validity?.rangeUnderflow) return this._message(`Value must be at least ${input.min}`, 'invalid');\n    if (input.validity?.rangeOverflow) return this._message(`Value must be no more than ${input.max}`, 'invalid');\n    return this._message('', 'invalid');\n  }\n}\n\nif (!customElements.get('ren-form')) {\n  customElements.define('ren-form', RenForm);\n}\n",
      "path": "components/patterns/ren-form",
      "id": "component:pattern:ren-form",
      "type": "component",
      "name": "ren-form"
    },
    {
      "data": {
        "kind": "pattern",
        "selectors": [
          ".ren-menubar",
          ".ren-menubar-checkbox",
          ".ren-menubar-item",
          ".ren-menubar-label",
          ".ren-menubar-menu",
          ".ren-menubar-radio",
          ".ren-menubar-separator",
          ".ren-menubar-shortcut",
          ".ren-menubar-submenu",
          ".ren-menubar-trigger"
        ],
        "tokens": [
          "--body-size",
          "--caption-size",
          "--color-accent",
          "--color-border",
          "--color-fill-active",
          "--color-fill-hover",
          "--color-on-accent",
          "--color-separator",
          "--color-surface",
          "--color-surface-raised",
          "--color-text",
          "--color-text-muted",
          "--duration-exit",
          "--duration-tactile",
          "--ease-enter",
          "--label-size",
          "--label-weight",
          "--radius-md",
          "--radius-sm",
          "--shadow-lg",
          "--space-1",
          "--space-2",
          "--space-3"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-menubar Pattern Contract\n\nApplication menubar pattern for top-level menu navigation.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-menubar` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-menubar` pattern.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Menubar pattern behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this pattern.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The UI is a desktop-style application menubar with persistent top-level menus (File / Edit / View / Help).\"\n    - \"You need WAI-ARIA Menubar semantics: role=\\\"menubar\\\", role=\\\"menu\\\", role=\\\"menuitem|menuitemcheckbox|menuitemradio\\\".\"\n    - \"You need keyboard navigation (Arrow keys, Enter, Space, Escape, Home, End) with roving focus and \\\"menubar glide\\\" between triggers.\"\n    - \"You need typeahead character matching for fast item selection.\"\n    - \"You need checkbox / radio menu items (.ren-menubar-checkbox, .ren-menubar-radio) with aria-checked state.\"\n    - \"You need nested submenus with chevron and lateral keyboard navigation.\"\n  avoidWhen:\n    - \"The nav is a horizontal site bar (Home / About / Pricing) — use ren-nav.\"\n    - \"It is a single dropdown / context menu — use ren-menu or ren-popover.\"\n    - \"It is a side rail with sections — use ren-sidebar.\"\n    - \"It is a Ctrl+K searchable launcher — use ren-command.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-menubar/ren-menubar.css\"\n  js:\n    - \"rends/components/patterns/ren-menubar/ren-menubar.js\"\n  notes:\n    - \"JS owns the keyboard model, roving focus, submenu opening, and ren-menubar-select event. CSS-only fallback only renders static chrome.\"\n    - \"Custom element registers as <ren-menubar>; menus are toggled via the [hidden] attribute on .ren-menubar-menu.\"\n\nrequiredMarkup:\n  - \"Root is <ren-menubar> wrapping <div class=\\\"ren-menubar\\\" role=\\\"menubar\\\">.\"\n  - \"Each top-level menu uses <button class=\\\"ren-menubar-trigger\\\" aria-haspopup=\\\"menu\\\" aria-expanded=\\\"false\\\"> + a sibling <div class=\\\"ren-menubar-menu\\\" role=\\\"menu\\\" hidden>.\"\n  - \"Each item is a <button class=\\\"ren-menubar-item\\\" role=\\\"menuitem\\\"> (or role=\\\"menuitemcheckbox|menuitemradio\\\" for stateful variants).\"\n  - \"Checkbox / radio items carry aria-checked=\\\"true|false\\\"; the ::before pseudo renders the check / dot — do not hand-draw it.\"\n  - \"Separators are <div class=\\\"ren-menubar-separator\\\" role=\\\"separator\\\">; submenu chevrons are added automatically by .ren-menubar-submenu::after.\"\n  - \"Keyboard shortcuts shown via <span class=\\\"ren-menubar-shortcut\\\">⌘N</span>; mirror with a real document-level keydown handler.\"\n\nforbiddenPatterns:\n  - \"Building from <a> tags — items must be <button> so Space activates them.\"\n  - \"Hiding menus with display: none from external code — toggle the [hidden] attribute; the component owns animation states.\"\n  - \"Setting aria-expanded manually from consumer code — the menubar JS manages it on the trigger.\"\n  - \"Wrapping items in <li> without role=\\\"none\\\" parent — keep the menu role tree flat or wrap with role=\\\"none\\\".\"\n  - \"Reimplementing keyboard navigation; the component already handles arrows, typeahead, Home/End, and Escape.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic surface / text tokens: --color-surface, --color-surface-raised, --color-border, --color-separator, --color-text, --color-text-muted, --color-fill-hover, --color-fill-active, --color-accent, --color-on-accent.\"\n    - \"Spacing / radius / shadow tokens: --space-1, --space-2, --space-3, --radius-sm, --radius-md, --shadow-lg.\"\n    - \"Type tokens: --body-size, --label-size, --label-weight, --caption-size.\"\n    - \"Motion tokens: --duration-tactile, --duration-exit, --ease-enter.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / named colors for hover, active, or accent.\"\n    - \"Replacing the focus outline color with anything other than --color-accent.\"\n\naccessibility:\n  required:\n    - \"Container has role=\\\"menubar\\\" and each <button> is a real menuitem with role=\\\"menuitem|menuitemcheckbox|menuitemradio\\\".\"\n    - \"Triggers expose aria-haspopup=\\\"menu\\\" and aria-expanded reflects the open/closed state of the associated menu.\"\n    - \"Roving tabindex: only one focusable item at a time; arrow keys move focus. The component manages this — do not set tabindex manually on every item.\"\n    - \"Checkbox / radio items expose aria-checked; the visual check (::before) must not be the only state cue.\"\n    - \"Disabled items use [data-disabled] AND pointer-events: none (CSS handles the latter).\"\n    - \"Focus-visible outline uses --color-accent — never strip without restoring a visible alternative.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-menubar/ren-menubar.css\">\n<script type=\"module\" src=\"rends/components/patterns/ren-menubar/ren-menubar.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-menubar><div class=\"ren-menubar\" role=\"menubar\"><button class=\"ren-menubar-trigger\" type=\"button\" aria-haspopup=\"menu\" aria-expanded=\"false\">File</button><div class=\"ren-menubar-menu\" role=\"menu\" hidden><button class=\"ren-menubar-item\" type=\"button\" role=\"menuitem\">New</button></div></div></ren-menubar>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-menubar`\n- `.ren-menubar-checkbox`\n- `.ren-menubar-item`\n- `.ren-menubar-label`\n- `.ren-menubar-menu`\n- `.ren-menubar-radio`\n- `.ren-menubar-separator`\n- `.ren-menubar-shortcut`\n- `.ren-menubar-submenu`\n- `.ren-menubar-trigger`\n\n## States And Attributes\n\n- `[aria-checked]`\n- `[aria-expanded]`\n- `[data-disabled]`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/patterns/ren-menubar/ren-menubar.css`\n- `components/patterns/ren-menubar/ren-menubar.js`\n- `docs/components/ren-menubar.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/**\n * RenDS Menubar\n * Horizontal menu bar component (File, Edit, View, etc.)\n *\n * Implements WAI-ARIA Menubar pattern with keyboard navigation,\n * roving focus, submenu support, and checkbox/radio items.\n *\n * Classes:\n * - .ren-menubar: Root horizontal menu bar\n * - .ren-menubar-trigger: Top-level menu button\n * - .ren-menubar-menu: Dropdown menu panel\n * - .ren-menubar-item: Individual menu item\n * - .ren-menubar-separator: Divider between item groups\n * - .ren-menubar-shortcut: Keyboard shortcut indicator\n * - .ren-menubar-label: Non-interactive group label\n * - .ren-menubar-submenu: Submenu with chevron\n * - .ren-menubar-checkbox: Checkbox menu item\n * - .ren-menubar-radio: Radio menu item\n */\n\n/* =================================================================\n   ROOT MENUBAR\n   ================================================================= */\n\n.ren-menubar {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  background-color: var(--color-surface);\n  border-bottom: 1px solid var(--color-border);\n  position: relative;\n}\n\n/* =================================================================\n   MENUBAR TRIGGER (Top-level Menu Button)\n   ================================================================= */\n\n.ren-menubar-trigger {\n  position: relative;\n  padding: var(--space-1) var(--space-2);\n  background-color: transparent;\n  color: var(--color-text);\n  border: none;\n  border-radius: var(--radius-sm);\n  font-size: var(--body-size);\n  font-weight: var(--label-weight);\n  cursor: pointer;\n  transition: background-color var(--duration-tactile) var(--ease-enter);\n  white-space: nowrap;\n}\n\n.ren-menubar-trigger:hover:not([aria-expanded=\"true\"]) {\n  background-color: var(--color-fill-hover);\n}\n\n.ren-menubar-trigger[aria-expanded=\"true\"] {\n  background-color: var(--color-fill-active);\n  color: var(--color-text);\n}\n\n.ren-menubar-trigger:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: 2px;\n}\n\n.ren-menubar-trigger:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n\n/* =================================================================\n   MENUBAR MENU (Dropdown Panel)\n   ================================================================= */\n\n.ren-menubar-menu {\n  position: absolute;\n  top: 100%;\n  inset-inline-start: 0;\n  margin-top: var(--space-1);\n  min-width: 200px;\n  background-color: var(--color-surface-raised);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n  box-shadow: var(--shadow-lg);\n  z-index: 1000;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n}\n\n/* Visible state for menu open */\n.ren-menubar-menu:not([hidden]) {\n  display: flex;\n  animation: menubar-menu-open var(--duration-exit) var(--ease-enter);\n}\n\n/* Hidden state for menu closed */\n.ren-menubar-menu[hidden] {\n  display: none;\n  animation: menubar-menu-close var(--duration-exit) var(--ease-enter);\n}\n\n@starting-style {\n  .ren-menubar-menu:not([hidden]) {\n    opacity: 0;\n    transform: translateY(-4px);\n  }\n}\n\n@keyframes menubar-menu-open {\n  from {\n    opacity: 0;\n    transform: translateY(-4px);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n@keyframes menubar-menu-close {\n  from {\n    opacity: 1;\n    transform: translateY(0);\n  }\n  to {\n    opacity: 0;\n    transform: translateY(-4px);\n  }\n}\n\n/* =================================================================\n   MENUBAR ITEM (Menu Item)\n   ================================================================= */\n\n.ren-menubar-item {\n  display: flex;\n  align-items: center;\n  width: 100%;\n  padding: var(--space-2) var(--space-3);\n  background-color: transparent;\n  color: var(--color-text);\n  border: none;\n  font-size: var(--body-size);\n  font-weight: normal;\n  text-align: start;\n  cursor: pointer;\n  transition: background-color var(--duration-tactile) var(--ease-enter);\n  position: relative;\n  gap: var(--space-2);\n}\n\n.ren-menubar-item:hover:not([data-disabled]) {\n  background-color: var(--color-fill-hover);\n}\n\n.ren-menubar-item:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: -2px;\n}\n\n.ren-menubar-item[data-disabled] {\n  color: var(--color-text-muted);\n  cursor: not-allowed;\n}\n\n.ren-menubar-item[data-disabled]:hover {\n  background-color: transparent;\n}\n\n/* =================================================================\n   MENUBAR SHORTCUT (Keyboard Shortcut Indicator)\n   ================================================================= */\n\n.ren-menubar-shortcut {\n  display: inline-block;\n  margin-inline-start: auto;\n  padding-inline-start: var(--space-3);\n  color: var(--color-text-muted);\n  font-size: var(--caption-size);\n  font-family: monospace;\n  flex-shrink: 0;\n}\n\n/* =================================================================\n   MENUBAR SEPARATOR (Divider)\n   ================================================================= */\n\n.ren-menubar-separator {\n  height: 1px;\n  background-color: var(--color-separator);\n  margin: var(--space-1) 0;\n}\n\n/* =================================================================\n   MENUBAR LABEL (Group Label)\n   ================================================================= */\n\n.ren-menubar-label {\n  padding: var(--space-2) var(--space-3);\n  color: var(--color-text-muted);\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  user-select: none;\n  pointer-events: none;\n}\n\n/* =================================================================\n   MENUBAR SUBMENU (With Chevron)\n   ================================================================= */\n\n.ren-menubar-submenu {\n  position: relative;\n}\n\n/* Chevron indicator via ::after */\n.ren-menubar-submenu::after {\n  content: '›';\n  display: inline-block;\n  margin-inline-start: auto;\n  padding-inline-start: var(--space-3);\n  color: var(--color-text-muted);\n  font-size: var(--body-size);\n  flex-shrink: 0;\n}\n\n/* Submenu panel positioning (higher z-index for layering) */\n.ren-menubar-submenu > .ren-menubar-menu {\n  top: 0;\n  inset-inline-start: calc(100% + var(--space-1));\n  margin-top: 0;\n  z-index: 1100;\n}\n\n/* =================================================================\n   MENUBAR CHECKBOX (Checkbox Menu Item)\n   ================================================================= */\n\n.ren-menubar-checkbox {\n  position: relative;\n}\n\n/* Checkmark via ::before */\n.ren-menubar-checkbox::before {\n  content: '';\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 1em;\n  height: 1em;\n  margin-inline-end: var(--space-1);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-sm);\n  background-color: transparent;\n  flex-shrink: 0;\n}\n\n.ren-menubar-checkbox[aria-checked=\"true\"]::before {\n  content: '✓';\n  background-color: var(--color-accent);\n  border-color: var(--color-accent);\n  color: var(--color-on-accent);\n  font-size: 0.75em;\n  font-weight: bold;\n}\n\n/* =================================================================\n   MENUBAR RADIO (Radio Menu Item)\n   ================================================================= */\n\n.ren-menubar-radio {\n  position: relative;\n}\n\n/* Radio indicator via ::before */\n.ren-menubar-radio::before {\n  content: '';\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 1em;\n  height: 1em;\n  margin-inline-end: var(--space-1);\n  border: 1px solid var(--color-border);\n  border-radius: 50%;\n  background-color: transparent;\n  flex-shrink: 0;\n}\n\n.ren-menubar-radio[aria-checked=\"true\"]::before {\n  content: '';\n  background-color: var(--color-accent);\n  border-color: var(--color-accent);\n  box-shadow: inset 0 0 0 2px var(--color-surface-raised);\n}\n\n/* =================================================================\n   ACCESSIBILITY & INTERACTION STATES\n   ================================================================= */\n\n/* Hover fill for interactive items */\n.ren-menubar-item:not([data-disabled]):not(.ren-menubar-label) {\n  user-select: none;\n}\n\n/* Focus management for submenus */\n.ren-menubar-menu [role=\"menuitem\"]:focus-visible,\n.ren-menubar-menu [role=\"menuitemcheckbox\"]:focus-visible,\n.ren-menubar-menu [role=\"menuitemradio\"]:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: -2px;\n}\n\n/* Ensure disabled items don't respond to pointer events */\n.ren-menubar-item[data-disabled] {\n  pointer-events: none;\n}\n\n/* Active/selected state */\n.ren-menubar-item.is-focused {\n  background-color: var(--color-fill-hover);\n}\n\n/* =================================================================\n   MEDIA QUERIES & RESPONSIVE\n   ================================================================= */\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-menubar-trigger,\n  .ren-menubar-item,\n  .ren-menubar-menu {\n    transition: none;\n    animation: none;\n  }\n}\n\n\n/**\n * RenDS Menubar Component\n * Horizontal menu bar with WAI-ARIA Menubar pattern support\n *\n * Features:\n * - Keyboard navigation (Arrow keys, Enter, Space, Escape, Home, End)\n * - Roving focus with \"menubar glide\" (hover opens adjacent menus when one is open)\n * - Typeahead character matching for quick item selection\n * - Checkbox and radio menu items with state management\n * - Submenu support with nested navigation\n * - Full accessibility compliance with ARIA roles and attributes\n *\n * Usage:\n * <ren-menubar>\n *   <div class=\"ren-menubar\" role=\"menubar\">\n *     <button class=\"ren-menubar-trigger\">File</button>\n *     <div class=\"ren-menubar-menu\" role=\"menu\">\n *       <button class=\"ren-menubar-item\" role=\"menuitem\">New</button>\n *     </div>\n *   </div>\n * </ren-menubar>\n *\n * Events:\n * - ren-menubar-select: Fired when a menu item is activated\n *   detail: { item: Element, value: string, checked?: boolean }\n *\n * Public Methods:\n * - closeAll(): Close all open menus\n * - openMenu(triggerIndex): Open menu at specified trigger index\n */\n\nexport class RenMenubar extends HTMLElement {\n  constructor() {\n    super();\n    this.triggers = [];\n    this.menus = [];\n    this.activeMenuIndex = -1;\n    this.currentMenu = null;\n    this.focusedItem = null;\n    this.typeaheadBuffer = '';\n    this.typeaheadTimeout = null;\n  }\n\n  /* ===================================================================\n     LIFECYCLE HOOKS\n     =================================================================== */\n\n  connectedCallback() {\n    this.initialize();\n  }\n\n  /* ===================================================================\n     INITIALIZATION\n     =================================================================== */\n\n  /**\n   * Initialize the menubar:\n   * - Cache triggers and menus\n   * - Set up ARIA attributes\n   * - Attach event listeners\n   */\n  initialize() {\n    const menubarEl = this.querySelector('[role=\"menubar\"]');\n    if (!menubarEl) return;\n\n    // Find all triggers and their associated menus\n    this.triggers = Array.from(menubarEl.querySelectorAll('.ren-menubar-trigger'));\n    this.menus = this.triggers.map((trigger) => {\n      // Menu is the next sibling after the trigger\n      const menu = trigger.nextElementSibling;\n      if (menu && menu.classList.contains('ren-menubar-menu')) {\n        return menu;\n      }\n      return null;\n    }).filter(Boolean);\n\n    // Ensure triggers and menus have proper ARIA attributes\n    this.triggers.forEach((trigger, index) => {\n      trigger.setAttribute('aria-haspopup', 'true');\n      trigger.setAttribute('aria-expanded', 'false');\n      trigger.setAttribute('role', 'button');\n      trigger.addEventListener('click', () => this.toggleMenu(index));\n      trigger.addEventListener('keydown', (e) => this.handleTriggerKeydown(e, index));\n      trigger.addEventListener('mouseenter', () => this.handleTriggerMouseenter(index));\n    });\n\n    // Set up menu items\n    this.menus.forEach((menu, menuIndex) => {\n      if (menu) {\n        const items = this.getMenuItems(menu);\n        items.forEach((item, itemIndex) => {\n          item.addEventListener('click', (e) => this.handleItemClick(e, menuIndex, itemIndex));\n          item.addEventListener('keydown', (e) => this.handleItemKeydown(e, menuIndex, itemIndex));\n          item.addEventListener('mouseenter', () => this.handleItemMouseenter(menuIndex, itemIndex));\n        });\n      }\n    });\n\n    // Close menu when clicking outside\n    document.addEventListener('click', (e) => {\n      if (!this.contains(e.target)) {\n        this.closeAll();\n      }\n    });\n  }\n\n  /* ===================================================================\n     MENU MANAGEMENT\n     =================================================================== */\n\n  /**\n   * Toggle menu visibility for trigger at index\n   */\n  toggleMenu(index) {\n    if (this.activeMenuIndex === index) {\n      this.closeAll();\n    } else {\n      this.openMenu(index);\n    }\n  }\n\n  /**\n   * Open menu at specified trigger index and focus first item\n   */\n  openMenu(index) {\n    if (index < 0 || index >= this.triggers.length) return;\n\n    // Close any currently open menu\n    if (this.activeMenuIndex !== -1) {\n      this.closeMenu(this.activeMenuIndex);\n    }\n\n    this.activeMenuIndex = index;\n    const trigger = this.triggers[index];\n    const menu = this.menus[index];\n\n    if (menu) {\n      menu.removeAttribute('hidden');\n      trigger.setAttribute('aria-expanded', 'true');\n      this.currentMenu = menu;\n\n      // Focus first focusable item in menu\n      const items = this.getMenuItems(menu);\n      if (items.length > 0) {\n        this.focusItem(items[0]);\n      }\n    }\n  }\n\n  /**\n   * Close menu at specified index\n   */\n  closeMenu(index) {\n    if (index < 0 || index >= this.triggers.length) return;\n\n    const trigger = this.triggers[index];\n    const menu = this.menus[index];\n\n    if (menu) {\n      menu.setAttribute('hidden', '');\n      trigger.setAttribute('aria-expanded', 'false');\n    }\n\n    if (this.activeMenuIndex === index) {\n      this.activeMenuIndex = -1;\n      this.currentMenu = null;\n      this.focusedItem = null;\n      trigger.focus();\n    }\n  }\n\n  /**\n   * Close all open menus and return focus to the last active trigger\n   */\n  closeAll() {\n    if (this.activeMenuIndex !== -1) {\n      this.closeMenu(this.activeMenuIndex);\n    }\n  }\n\n  /* ===================================================================\n     ITEM NAVIGATION & FOCUS\n     =================================================================== */\n\n  /**\n   * Get focusable menu items (excluding separators and labels)\n   */\n  getMenuItems(menu) {\n    const items = Array.from(menu.querySelectorAll(\n      '[role=\"menuitem\"], [role=\"menuitemcheckbox\"], [role=\"menuitemradio\"]'\n    ));\n    return items.filter(item => !item.hasAttribute('data-disabled'));\n  }\n\n  /**\n   * Focus a specific menu item and update aria-selected state\n   */\n  focusItem(item) {\n    if (!item) return;\n\n    // Remove focus class from previously focused item\n    if (this.focusedItem && this.focusedItem !== item) {\n      this.focusedItem.classList.remove('is-focused');\n    }\n\n    this.focusedItem = item;\n    item.classList.add('is-focused');\n    item.focus();\n  }\n\n  /**\n   * Get the next focusable item in menu (wraps around)\n   */\n  getNextItem(menu, currentItem, direction = 1) {\n    const items = this.getMenuItems(menu);\n    if (items.length === 0) return null;\n\n    const currentIndex = items.indexOf(currentItem);\n    const nextIndex = (currentIndex + direction + items.length) % items.length;\n    return items[nextIndex];\n  }\n\n  /* ===================================================================\n     EVENT HANDLERS: TRIGGERS\n     =================================================================== */\n\n  /**\n   * Handle keyboard events on trigger buttons\n   */\n  handleTriggerKeydown(event, triggerIndex) {\n    const { key } = event;\n\n    // ArrowRight: move to next trigger\n    if (key === 'ArrowRight') {\n      event.preventDefault();\n      const nextIndex = (triggerIndex + 1) % this.triggers.length;\n      this.openMenu(nextIndex);\n    }\n\n    // ArrowLeft: move to previous trigger\n    if (key === 'ArrowLeft') {\n      event.preventDefault();\n      const prevIndex = (triggerIndex - 1 + this.triggers.length) % this.triggers.length;\n      this.openMenu(prevIndex);\n    }\n\n    // ArrowDown: open menu\n    if (key === 'ArrowDown' || key === ' ' || key === 'Enter') {\n      event.preventDefault();\n      this.openMenu(triggerIndex);\n    }\n\n    // Home: first trigger\n    if (key === 'Home') {\n      event.preventDefault();\n      this.openMenu(0);\n    }\n\n    // End: last trigger\n    if (key === 'End') {\n      event.preventDefault();\n      this.openMenu(this.triggers.length - 1);\n    }\n  }\n\n  /**\n   * Handle mouse enter on trigger (roving focus \"glide\" behavior)\n   * If a menu is already open, open the new menu instead\n   */\n  handleTriggerMouseenter(triggerIndex) {\n    if (this.activeMenuIndex !== -1 && this.activeMenuIndex !== triggerIndex) {\n      this.openMenu(triggerIndex);\n    }\n  }\n\n  /* ===================================================================\n     EVENT HANDLERS: MENU ITEMS\n     =================================================================== */\n\n  /**\n   * Handle click on menu item\n   */\n  handleItemClick(event, menuIndex, itemIndex) {\n    event.preventDefault();\n    const menu = this.menus[menuIndex];\n    const items = this.getMenuItems(menu);\n    const item = items[itemIndex];\n\n    if (!item || item.hasAttribute('data-disabled')) return;\n\n    this.activateItem(item);\n  }\n\n  /**\n   * Handle keyboard events within menu\n   */\n  handleItemKeydown(event, menuIndex, itemIndex) {\n    const { key } = event;\n    const menu = this.menus[menuIndex];\n    const items = this.getMenuItems(menu);\n    const item = items[itemIndex];\n\n    if (!item) return;\n\n    // ArrowDown: next item\n    if (key === 'ArrowDown') {\n      event.preventDefault();\n      const nextItem = this.getNextItem(menu, item, 1);\n      if (nextItem) this.focusItem(nextItem);\n    }\n\n    // ArrowUp: previous item\n    if (key === 'ArrowUp') {\n      event.preventDefault();\n      const prevItem = this.getNextItem(menu, item, -1);\n      if (prevItem) this.focusItem(prevItem);\n    }\n\n    // ArrowRight: open submenu or move to next trigger\n    if (key === 'ArrowRight') {\n      event.preventDefault();\n      const submenu = item.querySelector('.ren-menubar-menu');\n      if (submenu && item.classList.contains('ren-menubar-submenu')) {\n        submenu.removeAttribute('hidden');\n        const subItems = this.getMenuItems(submenu);\n        if (subItems.length > 0) this.focusItem(subItems[0]);\n      } else {\n        // Move to next trigger\n        const nextIndex = (menuIndex + 1) % this.triggers.length;\n        this.openMenu(nextIndex);\n      }\n    }\n\n    // ArrowLeft: close submenu or go back to previous menu\n    if (key === 'ArrowLeft') {\n      event.preventDefault();\n      const parentMenu = item.closest('.ren-menubar-menu');\n      const parentTrigger = parentMenu?.previousElementSibling;\n      if (parentTrigger && parentTrigger.classList.contains('ren-menubar-submenu')) {\n        const submenu = parentTrigger.querySelector('.ren-menubar-menu');\n        if (submenu) submenu.setAttribute('hidden', '');\n        this.focusItem(parentTrigger);\n      } else {\n        const prevIndex = (menuIndex - 1 + this.triggers.length) % this.triggers.length;\n        this.openMenu(prevIndex);\n      }\n    }\n\n    // Enter or Space: activate item\n    if (key === 'Enter' || key === ' ') {\n      event.preventDefault();\n      this.activateItem(item);\n    }\n\n    // Escape: close menu\n    if (key === 'Escape') {\n      event.preventDefault();\n      this.closeMenu(menuIndex);\n    }\n\n    // Home: first item\n    if (key === 'Home') {\n      event.preventDefault();\n      if (items.length > 0) this.focusItem(items[0]);\n    }\n\n    // End: last item\n    if (key === 'End') {\n      event.preventDefault();\n      if (items.length > 0) this.focusItem(items[items.length - 1]);\n    }\n\n    // Typeahead: match item by first letter\n    if (key.length === 1 && /^[a-z0-9]$/i.test(key)) {\n      event.preventDefault();\n      this.handleTypeahead(key, menu);\n    }\n  }\n\n  /**\n   * Handle mouse enter on item (focus and close typeahead)\n   */\n  handleItemMouseenter(menuIndex, itemIndex) {\n    const menu = this.menus[menuIndex];\n    const items = this.getMenuItems(menu);\n    const item = items[itemIndex];\n\n    if (item) {\n      this.focusItem(item);\n    }\n\n    // Clear typeahead buffer on mouse movement\n    this.typeaheadBuffer = '';\n    if (this.typeaheadTimeout) {\n      clearTimeout(this.typeaheadTimeout);\n    }\n  }\n\n  /* ===================================================================\n     ITEM ACTIVATION & STATE MANAGEMENT\n     =================================================================== */\n\n  /**\n   * Activate a menu item (handle checkbox/radio states and emit event)\n   */\n  activateItem(item) {\n    if (item.hasAttribute('data-disabled')) return;\n\n    const role = item.getAttribute('role');\n\n    // Handle checkbox items\n    if (role === 'menuitemcheckbox') {\n      const isChecked = item.getAttribute('aria-checked') === 'true';\n      item.setAttribute('aria-checked', !isChecked ? 'true' : 'false');\n      this.emitSelectEvent(item, !isChecked);\n    }\n    // Handle radio items\n    else if (role === 'menuitemradio') {\n      const name = item.getAttribute('name');\n      const menu = item.closest('[role=\"menu\"]');\n      const radioGroup = menu.querySelectorAll(`[role=\"menuitemradio\"][name=\"${name}\"]`);\n      radioGroup.forEach(radio => radio.setAttribute('aria-checked', 'false'));\n      item.setAttribute('aria-checked', 'true');\n      this.emitSelectEvent(item, true);\n    }\n    // Handle standard menu items\n    else {\n      this.emitSelectEvent(item);\n    }\n\n    // Close menu after activation (unless it's a checkbox/radio)\n    if (role !== 'menuitemcheckbox' && role !== 'menuitemradio') {\n      this.closeAll();\n    }\n  }\n\n  /**\n   * Emit ren-menubar-select event with item details\n   */\n  emitSelectEvent(item, checked = undefined) {\n    const detail = {\n      item,\n      value: item.textContent?.trim() || item.getAttribute('value') || '',\n    };\n\n    if (checked !== undefined) {\n      detail.checked = checked;\n    }\n\n    this.dispatchEvent(new CustomEvent('ren-menubar-select', {\n      detail,\n      bubbles: true,\n      composed: true,\n    }));\n  }\n\n  /* ===================================================================\n     TYPEAHEAD SUPPORT\n     =================================================================== */\n\n  /**\n   * Handle typeahead character matching\n   * Type characters to jump to matching items\n   */\n  handleTypeahead(char, menu) {\n    // Clear buffer after 500ms of inactivity\n    if (this.typeaheadTimeout) {\n      clearTimeout(this.typeaheadTimeout);\n    }\n\n    this.typeaheadBuffer += char.toLowerCase();\n\n    // Find item matching buffer\n    const items = this.getMenuItems(menu);\n    const currentIndex = items.indexOf(this.focusedItem);\n    const startIndex = (currentIndex + 1) % items.length;\n\n    let matchedItem = null;\n    for (let i = 0; i < items.length; i++) {\n      const index = (startIndex + i) % items.length;\n      const item = items[index];\n      const text = item.textContent?.trim().toLowerCase() || '';\n      if (text.startsWith(this.typeaheadBuffer)) {\n        matchedItem = item;\n        break;\n      }\n    }\n\n    if (matchedItem) {\n      this.focusItem(matchedItem);\n    }\n\n    // Clear buffer after 500ms\n    this.typeaheadTimeout = setTimeout(() => {\n      this.typeaheadBuffer = '';\n    }, 500);\n  }\n\n  /* ===================================================================\n     PUBLIC API\n     =================================================================== */\n\n  /**\n   * Close all open menus\n   * @public\n   */\n  closeAll() {\n    if (this.activeMenuIndex !== -1) {\n      this.closeMenu(this.activeMenuIndex);\n    }\n  }\n\n  /**\n   * Open menu at specified trigger index\n   * @param {number} triggerIndex - Index of the trigger to open\n   * @public\n   */\n  openMenu(triggerIndex) {\n    if (triggerIndex < 0 || triggerIndex >= this.triggers.length) {\n      throw new RangeError(`Invalid trigger index: ${triggerIndex}`);\n    }\n    this.openMenu(triggerIndex);\n  }\n}\n\n// Register custom element\nif (!customElements.get('ren-menubar')) {\n  customElements.define('ren-menubar', RenMenubar);\n}\n",
      "path": "components/patterns/ren-menubar",
      "id": "component:pattern:ren-menubar",
      "type": "component",
      "name": "ren-menubar"
    },
    {
      "data": {
        "kind": "pattern",
        "selectors": [
          ".ren-nav",
          ".ren-nav-actions",
          ".ren-nav-brand",
          ".ren-nav-dropdown",
          ".ren-nav-link",
          ".ren-nav-links",
          ".ren-nav-sticky",
          ".ren-nav-toggle",
          ".ren-nav-transparent"
        ],
        "tokens": [
          "--color-accent",
          "--color-accent-subtle",
          "--color-border",
          "--color-fill",
          "--color-surface",
          "--color-text",
          "--color-text-muted",
          "--duration-enter",
          "--duration-overlay",
          "--duration-route",
          "--duration-state",
          "--ease-enter",
          "--ease-state-change",
          "--ren-label-size",
          "--ren-nav-bg",
          "--ren-nav-border",
          "--ren-nav-gap",
          "--ren-nav-height",
          "--ren-nav-link-active",
          "--ren-nav-link-color",
          "--ren-nav-padding-x",
          "--ren-space-1",
          "--ren-space-2",
          "--ren-space-3",
          "--ren-space-4",
          "--ren-title-sm-size",
          "--ren-z-sticky",
          "--space-4",
          "--space-6"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-nav Pattern Contract\n\nNavigation pattern for responsive site/app nav and active states.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-nav` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-nav` pattern.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Nav pattern behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this pattern.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The product needs a horizontal site / app top navigation bar (brand + links + actions).\"\n    - \"The nav must collapse to a hamburger menu below 48rem (768px) with an animated <span>-stripe toggle.\"\n    - \"Active route should be reflected via aria-current=\\\"page\\\" (or the .active class) on a .ren-nav-link.\"\n    - \"You need optional sticky-on-scroll behavior with a translucent backdrop blur (.ren-nav-sticky).\"\n    - \"You need a transparent variant for hero sections (.ren-nav-transparent).\"\n    - \"You need top-level dropdown menus inside the nav using the native [popover] API (.ren-nav-dropdown).\"\n  avoidWhen:\n    - \"The nav is a persistent left/right rail — use ren-sidebar.\"\n    - \"The UI is a desktop-style application menubar (File / Edit / View) — use ren-menubar.\"\n    - \"The disclosure is a one-off dropdown unrelated to site nav — use ren-menu or ren-popover.\"\n    - \"The nav lives inside a card or pattern as secondary tabs — use ren-tabs.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-nav/ren-nav.css\"\n  js:\n    - \"rends/components/patterns/ren-nav/ren-nav.js\"\n  notes:\n    - \"JS registers <ren-nav> and owns the mobile toggle, Escape-to-close, resize handler, and popover-based dropdowns.\"\n    - \"CSS-only fallback renders the desktop layout; mobile menu requires JS to toggle [aria-expanded] on .ren-nav-toggle.\"\n\nrequiredMarkup:\n  - \"Wrap the bar in <ren-nav> containing a <nav class=\\\"ren-nav\\\" aria-label=\\\"…\\\">.\"\n  - \"Brand block uses <a class=\\\"ren-nav-brand\\\" href=\\\"/\\\"> with a logo + product name; it sits at the inline-start.\"\n  - \"Links live in <ul class=\\\"ren-nav-links\\\"> of <li><a class=\\\"ren-nav-link\\\" href=\\\"…\\\"></a></li>; active item adds aria-current=\\\"page\\\".\"\n  - \"Mobile hamburger is <button class=\\\"ren-nav-toggle\\\" aria-expanded=\\\"false\\\" aria-controls=\\\"…\\\" aria-label=\\\"Toggle menu\\\"> with three <span> stripes inside.\"\n  - \"Action area (CTA buttons, theme toggle, sign-in) goes in <div class=\\\"ren-nav-actions\\\"> which floats to the inline-end via margin-inline-start: auto.\"\n  - \"Dropdowns use .ren-nav-dropdown wrapping a trigger <a class=\\\"ren-nav-link\\\" aria-expanded=\\\"false\\\" popovertarget=\\\"…\\\"> and a [popover] panel.\"\n\nforbiddenPatterns:\n  - \"Using <div> stacks instead of <nav> + <ul>/<li> — landmarks and list semantics matter.\"\n  - \"Manually toggling display: none on .ren-nav-links for mobile — set [data-open] on .ren-nav or aria-expanded on .ren-nav-toggle.\"\n  - \"Styling links with ren-btn — sidebar / nav items own their own chrome via .ren-nav-link.\"\n  - \"Replacing the hamburger with a custom toggle that lacks aria-expanded / aria-controls.\"\n  - \"Hardcoding the sticky backdrop color — the .ren-nav-sticky variant already uses color-mix on --color-surface.\"\n\ntokenPolicy:\n  allowed:\n    - \"Public component tokens listed in Public Token API: --ren-nav-bg, --ren-nav-border, --ren-nav-gap, --ren-nav-height, --ren-nav-link-active, --ren-nav-link-color, --ren-nav-padding-x.\"\n    - \"Semantic tokens consumed by selectors: --color-surface, --color-border, --color-text, --color-text-muted, --color-accent, --color-accent-subtle, --color-fill (for sticky color-mix).\"\n    - \"Spacing / radius tokens: --ren-space-1, --ren-space-2, --ren-space-3, --ren-space-4, --ren-title-sm-size, --ren-label-size.\"\n    - \"Motion tokens: --duration-overlay, --duration-state, --duration-route, --duration-enter, --ease-state-change, --ease-enter.\"\n    - \"Z-index token: --ren-z-sticky.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / named colors for bg, border, active, or hover.\"\n    - \"Custom z-index values that bypass --ren-z-sticky for sticky mode.\"\n\naccessibility:\n  required:\n    - \"Root is a real <nav> with aria-label (e.g. \\\"Primary\\\") so screen readers can list landmarks.\"\n    - \"Active route uses aria-current=\\\"page\\\" (the .active class is a styling alias, not the source of truth).\"\n    - \".ren-nav-toggle has aria-expanded reflecting menu state, aria-controls pointing to the links list, and an aria-label (icon-only is not a name).\"\n    - \"Mobile menu closes on Escape and on link activation (the component owns both behaviors).\"\n    - \"Dropdown triggers expose aria-expanded; popover panels are real [popover] elements so the platform manages focus return.\"\n    - \"Focus-visible outlines use --color-accent at 2px offset — do not remove without a visible alternative.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-nav/ren-nav.css\">\n<script type=\"module\" src=\"rends/components/patterns/ren-nav/ren-nav.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-nav><nav class=\"ren-nav\" aria-label=\"Primary\"><a class=\"ren-nav-brand\" href=\"/\">RenDS</a><ul id=\"primary-links\" class=\"ren-nav-links\"><li><a class=\"ren-nav-link\" href=\"/docs\" aria-current=\"page\">Docs</a></li></ul><button class=\"ren-nav-toggle\" type=\"button\" aria-expanded=\"false\" aria-controls=\"primary-links\" aria-label=\"Toggle menu\"><span></span><span></span><span></span></button></nav></ren-nav>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-nav`\n- `.ren-nav-actions`\n- `.ren-nav-brand`\n- `.ren-nav-dropdown`\n- `.ren-nav-link`\n- `.ren-nav-links`\n- `.ren-nav-sticky`\n- `.ren-nav-toggle`\n- `.ren-nav-transparent`\n\n## States And Attributes\n\n- `[aria-current]`\n- `[aria-expanded]`\n- `[data-dropdown]`\n- `[data-open]`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-nav-bg`\n- `--ren-nav-border`\n- `--ren-nav-gap`\n- `--ren-nav-height`\n- `--ren-nav-link-active`\n- `--ren-nav-link-color`\n- `--ren-nav-padding-x`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/patterns/ren-nav/ren-nav.css`\n- `components/patterns/ren-nav/ren-nav.js`\n- `docs/components/ren-nav.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n.ren-nav {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: var(--ren-space-2, 0.5rem) var(--ren-space-4, 1rem);\n  background-color: var(--color-surface);\n  border-bottom: 1px solid var(--color-border);\n  min-height: 3.5rem;\n  transition: backdrop-filter var(--duration-overlay) var(--ease-state-change), background-color var(--duration-overlay) var(--ease-state-change);\n\n  &[data-open] .ren-nav-links,\n  &:has(.ren-nav-toggle[aria-expanded=\"true\"]) .ren-nav-links {\n    display: flex;\n  }\n\n  &.ren-nav-sticky {\n    position: sticky;\n    top: 0;\n    z-index: var(--ren-z-sticky, 100);\n    backdrop-filter: blur(8px);\n    background-color: color-mix(in srgb, var(--color-surface) 95%, transparent);\n  }\n\n  &.ren-nav-transparent {\n    background-color: transparent;\n    border-bottom-color: transparent;\n  }\n}\n\n.ren-nav-brand {\n  display: flex;\n  align-items: center;\n  gap: var(--ren-space-2, 0.5rem);\n  font-weight: 600;\n  font-size: var(--ren-title-sm-size, 1.125rem);\n  color: var(--color-text);\n  text-decoration: none;\n  flex-shrink: 0;\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n    border-radius: 0.375rem;\n  }\n}\n\n.ren-nav-links {\n  display: flex;\n  align-items: center;\n  gap: var(--ren-space-1, 0.25rem);\n  list-style: none;\n  margin: 0;\n  padding: 0;\n\n  @media (max-width: 48rem) {\n    display: none;\n    position: absolute;\n    top: 3.5rem;\n    inset-inline: 0;\n    flex-direction: column;\n    gap: 0;\n    background-color: var(--color-surface);\n    border-bottom: 1px solid var(--color-border);\n    padding: var(--ren-space-2, 0.5rem) 0;\n    animation: ren-nav-slide-down var(--duration-enter) var(--ease-enter);\n    z-index: 1000;\n\n    @media (prefers-reduced-motion: reduce) {\n      animation: none;\n    }\n  }\n}\n\n@keyframes ren-nav-slide-down {\n  from {\n    opacity: 0;\n    transform: translateY(-0.5rem);\n  }\n\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n.ren-nav-link {\n  padding: var(--ren-space-2, 0.5rem) var(--ren-space-3, 0.75rem);\n  color: var(--color-text-muted);\n  font-size: var(--ren-label-size, 0.875rem);\n  border-radius: 0.375rem;\n  transition: background-color var(--duration-state) var(--ease-state-change), color var(--duration-state) var(--ease-state-change);\n  text-decoration: none;\n  white-space: nowrap;\n  cursor: pointer;\n\n  &:hover {\n    background-color: var(--color-fill);\n    color: var(--color-text);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: -2px;\n  }\n\n  &[aria-current=\"page\"],\n  &.active {\n    color: var(--color-accent);\n    font-weight: 500;\n    background-color: var(--color-accent-subtle);\n  }\n\n  @media (max-width: 48rem) {\n    padding: var(--ren-space-2, 0.5rem) var(--ren-space-4, 1rem);\n    border-radius: 0;\n\n    &:hover {\n      background-color: var(--color-fill);\n    }\n  }\n}\n\n.ren-nav-actions {\n  display: flex;\n  align-items: center;\n  gap: var(--ren-space-2, 0.5rem);\n  margin-inline-start: auto;\n}\n\n.ren-nav-toggle {\n  display: none;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  background: none;\n  border: none;\n  padding: var(--ren-space-2, 0.5rem);\n  cursor: pointer;\n  color: var(--color-text);\n  min-width: 44px;\n  min-height: 44px;\n  border-radius: 0.375rem;\n  transition: background-color var(--duration-state) var(--ease-state-change);\n\n  &:hover {\n    background-color: var(--color-fill);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  & span {\n    width: 1.5rem;\n    height: 2px;\n    background-color: currentColor;\n    border-radius: 1px;\n    transition: transform var(--duration-route) var(--ease-state-change), opacity var(--duration-route) var(--ease-state-change);\n    transform-origin: center;\n  }\n\n  &[aria-expanded=\"true\"] {\n    & span:nth-child(1) {\n      transform: translateY(0.5rem) rotate(45deg);\n    }\n\n    & span:nth-child(2) {\n      opacity: 0;\n      transform: scaleX(0);\n    }\n\n    & span:nth-child(3) {\n      transform: translateY(-0.5rem) rotate(-45deg);\n    }\n  }\n\n  @media (max-width: 48rem) {\n    display: flex;\n  }\n}\n\n.ren-nav-dropdown {\n  position: relative;\n\n  & [popover] {\n    padding: var(--ren-space-1, 0.25rem);\n    background-color: var(--color-surface);\n    border: 1px solid var(--color-border);\n    border-radius: 0.5rem;\n    box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);\n    margin-top: var(--ren-space-1, 0.25rem);\n    list-style: none;\n    padding-inline-start: 0;\n\n    &[popover] {\n      inset: auto auto auto auto;\n    }\n  }\n\n  & .ren-nav-link[aria-expanded=\"true\"] {\n    background-color: var(--color-fill);\n    color: var(--color-accent);\n  }\n}\n/* Appearance API bridge */\n.ren-nav { min-block-size: var(--ren-nav-height, 3.5rem); padding-inline: var(--ren-nav-padding-x, var(--space-4)); background: var(--ren-nav-bg, var(--color-surface)); border-color: var(--ren-nav-border, var(--color-border)); gap: var(--ren-nav-gap, var(--space-6)); }\n.ren-nav a { color: var(--ren-nav-link-color, var(--color-text-muted)); }\n.ren-nav a[aria-current=\"page\"], .ren-nav a[data-active] { color: var(--ren-nav-link-active, var(--color-text)); }\n\n\n/**\n * RenNav Component - Responsive Navigation Bar\n * Modern web component with keyboard navigation and mobile support\n */\n\nconst MOBILE_BREAKPOINT = 768;\nconst KEYBOARD_CODES = { Escape: 'Escape' };\n\nclass RenNav extends HTMLElement {\n  constructor() {\n    super();\n    this._toggleBtn = null;\n    this._linksContainer = null;\n    this._links = [];\n    this._isOpen = false;\n    this._dropdowns = new Map();\n    this._listenerController = null;\n  }\n\n  connectedCallback() {\n    this._initElements();\n    this._attachEventListeners();\n    this._initDropdowns();\n    this._updateMobileState();\n  }\n\n  disconnectedCallback() {\n    this._removeEventListeners();\n  }\n\n  _initElements() {\n    this._toggleBtn = this.querySelector('.ren-nav-toggle');\n    this._linksContainer = this.querySelector('.ren-nav-links');\n    this._links = Array.from(this.querySelectorAll('.ren-nav-link'));\n  }\n\n  _attachEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = new AbortController();\n    const { signal } = this._listenerController;\n\n    // Toggle button click\n    if (this._toggleBtn) {\n      this._toggleBtn.addEventListener('click', () => this._toggleMenu(), { signal });\n    }\n\n    // Close menu on link click\n    this._links.forEach(link => {\n      link.addEventListener('click', () => {\n        if (this._isOpen) {\n          this._closeMenu();\n        }\n      }, { signal });\n    });\n\n    // Close on Escape\n    document.addEventListener('keydown', (e) => {\n      if (e.key === KEYBOARD_CODES.Escape && this._isOpen) {\n        this._closeMenu();\n      }\n    }, { signal });\n\n    // Handle window resize\n    window.addEventListener('resize', () => {\n      this._updateMobileState();\n    }, { signal });\n\n    // Close menu on outside click\n    document.addEventListener('click', (e) => {\n      if (this._isOpen && !this.contains(e.target)) {\n        this._closeMenu();\n      }\n    }, { signal });\n  }\n\n  _initDropdowns() {\n    const signal = this._listenerController?.signal;\n    const dropdownToggles = this.querySelectorAll('[data-dropdown]');\n    dropdownToggles.forEach(toggle => {\n      const popoverId = toggle.getAttribute('aria-controls');\n      if (popoverId) {\n        const popover = this.querySelector(`#${popoverId}`);\n        if (popover) {\n          this._dropdowns.set(toggle, popover);\n          toggle.addEventListener('click', (e) => {\n            e.stopPropagation();\n            this._toggleDropdown(toggle, popover);\n          }, { signal });\n\n          // Close dropdown on item click\n          popover.querySelectorAll('a, button').forEach(item => {\n            item.addEventListener('click', () => {\n              this._closeDropdown(toggle, popover);\n            }, { signal });\n          });\n        }\n      }\n    });\n  }\n\n  _toggleDropdown(toggle, popover) {\n    const isOpen = toggle.getAttribute('aria-expanded') === 'true';\n    if (isOpen) {\n      this._closeDropdown(toggle, popover);\n    } else {\n      // Close other dropdowns\n      this._dropdowns.forEach((pop, tog) => {\n        if (tog !== toggle) {\n          this._closeDropdown(tog, pop);\n        }\n      });\n\n      toggle.setAttribute('aria-expanded', 'true');\n      popover.showPopover?.();\n    }\n  }\n\n  _closeDropdown(toggle, popover) {\n    toggle.setAttribute('aria-expanded', 'false');\n    popover.hidePopover?.();\n  }\n\n  _toggleMenu() {\n    if (this._isOpen) {\n      this._closeMenu();\n    } else {\n      this._openMenu();\n    }\n  }\n\n  _openMenu() {\n    this._isOpen = true;\n    this.setAttribute('data-open', '');\n    if (this._toggleBtn) {\n      this._toggleBtn.setAttribute('aria-expanded', 'true');\n    }\n  }\n\n  _closeMenu() {\n    this._isOpen = false;\n    this.removeAttribute('data-open');\n    if (this._toggleBtn) {\n      this._toggleBtn.setAttribute('aria-expanded', 'false');\n    }\n  }\n\n  _updateMobileState() {\n    const isMobile = window.innerWidth < MOBILE_BREAKPOINT;\n    if (!isMobile && this._isOpen) {\n      this._closeMenu();\n    }\n  }\n\n  _removeEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = null;\n  }\n\n  // Public API\n  setActiveLink(href) {\n    this._links.forEach(link => {\n      if (link.getAttribute('href') === href) {\n        link.setAttribute('aria-current', 'page');\n        link.classList.add('active');\n      } else {\n        link.removeAttribute('aria-current');\n        link.classList.remove('active');\n      }\n    });\n  }\n\n  get isOpen() {\n    return this._isOpen;\n  }\n}\n\ncustomElements.define('ren-nav', RenNav);\n",
      "path": "components/patterns/ren-nav",
      "id": "component:pattern:ren-nav",
      "type": "component",
      "name": "ren-nav"
    },
    {
      "data": {
        "kind": "pattern",
        "selectors": [
          ".ren-sidebar",
          ".ren-sidebar-content",
          ".ren-sidebar-divider",
          ".ren-sidebar-footer",
          ".ren-sidebar-header",
          ".ren-sidebar-item",
          ".ren-sidebar-item-icon",
          ".ren-sidebar-item-text",
          ".ren-sidebar-layout",
          ".ren-sidebar-main",
          ".ren-sidebar-nav",
          ".ren-sidebar-overlay",
          ".ren-sidebar-section",
          ".ren-sidebar-section-label",
          ".ren-sidebar-toggle"
        ],
        "tokens": [
          "--color-accent",
          "--color-accent-subtle",
          "--color-border",
          "--color-fill",
          "--color-separator",
          "--color-surface",
          "--color-surface-sunken",
          "--color-text",
          "--color-text-faint",
          "--color-text-muted",
          "--duration-enter",
          "--duration-normal",
          "--duration-state",
          "--ease-out",
          "--ease-state-change",
          "--radius-md",
          "--ren-caption-size",
          "--ren-label-size",
          "--ren-sidebar-active-bg",
          "--ren-sidebar-active-color",
          "--ren-sidebar-bg",
          "--ren-sidebar-border",
          "--ren-sidebar-collapsed-width",
          "--ren-sidebar-duration",
          "--ren-sidebar-easing",
          "--ren-sidebar-item-height",
          "--ren-sidebar-item-padding",
          "--ren-sidebar-item-radius",
          "--ren-sidebar-width",
          "--ren-space-1",
          "--ren-space-2",
          "--ren-space-3",
          "--ren-space-4",
          "--sidebar-collapsed-width",
          "--sidebar-width",
          "--size-lg",
          "--space-2",
          "--space-3",
          "--touch-min"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-sidebar\n\nApplication sidebar pattern: persistent left/right navigation with desktop\ncollapse, mobile overlay, active-item state, and keyboard accessibility.\nPairs with a main content slot to form an app shell layout.\n\nLoad this file after `ren-design.md` and before generating, editing, or\nreviewing any `<ren-sidebar>` UI.\n\n## Purpose\n\nThe dashboard / app-shell sidebar pattern in RenDS. Owns:\n- Persistent layout with `.ren-sidebar-layout` (sidebar + main content).\n- Desktop collapsed / expanded states (`[data-collapsed]`).\n- Mobile overlay state (`[data-open]`) with backdrop and Escape / outside\n  click dismissal.\n- Active item state via `aria-current=\"page\"` plus `.active`.\n- Persistence of collapse state in `localStorage`.\n- Toggle button + keyboard support.\n\nSidebar items are real `<a>` (for routes) or `<button>` (for actions); they\nare *not* `ren-link` / `ren-btn` — sidebar styling owns their chrome.\n\n## Use When\n\n- The product has persistent left/right navigation across multiple pages.\n- The page is an app shell with sidebar + main layout (`.ren-sidebar-layout`).\n- The sidebar must collapse on desktop and overlay on mobile.\n- Persistence of collapse state across sessions matters.\n\n## Do Not Use When\n\n- The navigation is a top horizontal bar — use `ren-nav`.\n- The navigation is a one-off menu inside another component — use\n  `ren-menu` / `ren-menubar`.\n- The sidebar is a transient panel — use `ren-sheet`.\n- The sidebar is a single floating button toolbar — use `ren-toolbar`.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The product needs persistent navigation across many pages.\"\n    - \"Desktop should support a collapsed (icon-only) and expanded state.\"\n    - \"Mobile should overlay with backdrop dismissal.\"\n    - \"Active route should be reflected via aria-current=\\\"page\\\".\"\n    - \"Collapse state should persist in localStorage.\"\n  avoidWhen:\n    - \"The navigation is a horizontal top bar — use ren-nav.\"\n    - \"The disclosure is transient or context-specific — use ren-sheet / ren-menu.\"\n    - \"The page does not have a multi-section nav.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-sidebar/ren-sidebar.css\"\n  js:\n    - \"rends/components/patterns/ren-sidebar/ren-sidebar.js\"\n  notes:\n    - \"JS handles toggle, persistence, and overlay dismissal. CSS-only fallback only renders the expanded desktop state.\"\n\nrequiredMarkup:\n  - \"Wrap the app shell in <div class=\\\"ren-sidebar-layout\\\">.\"\n  - \"<ren-sidebar> is the sidebar root; main content goes in a sibling <main class=\\\"ren-sidebar-main\\\">.\"\n  - \"Wrap items in <nav class=\\\"ren-sidebar-nav\\\"> with <a class=\\\"ren-sidebar-item\\\"> children.\"\n  - \"Use aria-current=\\\"page\\\" on the active item; the .active class is for styling parity.\"\n  - \"Provide an aria-label on <ren-sidebar> when multiple navigation landmarks exist on the page.\"\n  - \"Toggle button uses .ren-sidebar-toggle and aria-label (the icon alone is not a name).\"\n\nforbiddenPatterns:\n  - \"Wrapping non-nav content in <nav class=\\\"ren-sidebar-nav\\\">.\"\n  - \"Using ren-btn / ren-link as sidebar items — use .ren-sidebar-item directly.\"\n  - \"Hardcoded sidebar widths in inline styles; use --ren-sidebar-width.\"\n  - \"Manually toggling display: none for the mobile overlay; use the [data-open] state.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: every --ren-sidebar-* listed in Public Token API.\"\n    - \"Semantic tokens for content (--color-text, --color-text-muted, etc.).\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, etc.) in consumer code.\"\n    - \"Hardcoded shadow / overlay colors that bypass tokens.\"\n\naccessibility:\n  required:\n    - \"Wrap items in a real <nav> landmark.\"\n    - \"Active item uses aria-current=\\\"page\\\".\"\n    - \"Toggle button has aria-label and toggles aria-expanded if it controls disclosure.\"\n    - \"Collapsed icon-only items must keep accessible names from their link text (do not hide it from AT).\"\n    - \"Mobile overlay closes on Escape and outside click.\"\n```\n\n## Required CSS / JS Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-sidebar/ren-sidebar.css\">\n<script type=\"module\" src=\"rends/components/patterns/ren-sidebar/ren-sidebar.js\"></script>\n```\n\n## Canonical Markup\n\nApp shell with sidebar + main:\n\n```html\n<div class=\"ren-sidebar-layout\">\n  <ren-sidebar class=\"ren-sidebar\" aria-label=\"Primary\">\n    <div class=\"ren-sidebar-header\">\n      <a class=\"ren-sidebar-item\" href=\"/\" aria-label=\"Home\">\n        <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">⌂</span>\n        <span class=\"ren-sidebar-item-text\">Acme</span>\n      </a>\n    </div>\n\n    <nav class=\"ren-sidebar-nav\" aria-label=\"Main\">\n      <a class=\"ren-sidebar-item active\" href=\"/dashboard\" aria-current=\"page\">\n        <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">📊</span>\n        <span class=\"ren-sidebar-item-text\">Dashboard</span>\n      </a>\n      <a class=\"ren-sidebar-item\" href=\"/projects\">\n        <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">📁</span>\n        <span class=\"ren-sidebar-item-text\">Projects</span>\n      </a>\n    </nav>\n\n    <div class=\"ren-sidebar-section\">\n      <div class=\"ren-sidebar-section-label\">Account</div>\n      <a class=\"ren-sidebar-item\" href=\"/settings\">\n        <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">⚙</span>\n        <span class=\"ren-sidebar-item-text\">Settings</span>\n      </a>\n    </div>\n\n    <button class=\"ren-sidebar-toggle\" type=\"button\" aria-label=\"Toggle sidebar\"></button>\n  </ren-sidebar>\n\n  <main class=\"ren-sidebar-main\">\n    <!-- page content -->\n  </main>\n</div>\n```\n\nProgrammatic API:\n\n```js\nconst sidebar = document.querySelector('ren-sidebar');\nsidebar.toggleMenu();\nsidebar.setActiveItem('/projects');\nsidebar.addEventListener('ren-sidebar-toggle', (e) => console.log(e.detail.collapsed));\n```\n\n## Attributes, Events, and API\n\n- Host states:\n  - `[data-collapsed]` — desktop collapsed (icon-only).\n  - `[data-open]` — mobile overlay open.\n- Active item: `.active` class plus `aria-current=\"page\"` on the\n  current `.ren-sidebar-item`.\n- Methods: `toggleMenu()`, `openMobileMenu()`, `closeMobileMenu()`,\n  `setActiveItem(href)`.\n- Getters: `isCollapsed`, `isMobileOpen`.\n- Event: `ren-sidebar-toggle` (`detail.collapsed`).\n- Persistence: collapse state stored in `localStorage` under\n  `ren-sidebar-collapsed`.\n\n## Variants and Public Selectors\n\n| Class                            | Role                                |\n|----------------------------------|-------------------------------------|\n| `.ren-sidebar`                   | Sidebar root chrome.                |\n| `.ren-sidebar-layout`            | Layout wrapper (sidebar + main).    |\n| `.ren-sidebar-main`              | Main-content slot.                  |\n| `.ren-sidebar-header`            | Header slot.                        |\n| `.ren-sidebar-content`           | Scrollable middle slot.             |\n| `.ren-sidebar-footer`            | Footer slot.                        |\n| `.ren-sidebar-nav`               | `<nav>` wrapper.                    |\n| `.ren-sidebar-section`           | Grouped section.                    |\n| `.ren-sidebar-section-label`     | Section heading text.               |\n| `.ren-sidebar-item`              | Single nav item.                    |\n| `.ren-sidebar-item-icon`         | Icon slot inside an item.           |\n| `.ren-sidebar-item-text`         | Text slot inside an item.           |\n| `.ren-sidebar-divider`           | Visual divider between sections.    |\n| `.ren-sidebar-toggle`            | Collapse button.                    |\n| `.ren-sidebar-overlay`           | Mobile backdrop.                    |\n\n## States\n\n| Selector / attr        | Meaning                                |\n|------------------------|----------------------------------------|\n| `[aria-current=\"page\"]`| Active route on the current item.      |\n| `[aria-disabled]`      | Disabled item (still focusable).       |\n| `[data-collapsed]`     | Desktop collapsed (icon-only) state.   |\n| `[data-open]`          | Mobile overlay open.                   |\n| `:focus-visible`       | Keyboard focus on an item or toggle.   |\n\n## Public Token API\n\n- `--ren-sidebar-bg`\n- `--ren-sidebar-border`\n- `--ren-sidebar-width`\n- `--ren-sidebar-collapsed-width`\n- `--ren-sidebar-item-height`\n- `--ren-sidebar-item-padding`\n- `--ren-sidebar-item-radius`\n- `--ren-sidebar-active-bg`\n- `--ren-sidebar-active-color`\n- `--ren-sidebar-duration`\n- `--ren-sidebar-easing`\n\n## Accessibility Contract\n\n- Wrap items in a real `<nav>` landmark and label the sidebar with\n  `aria-label` when multiple nav landmarks exist on the page.\n- The current route uses `aria-current=\"page\"`.\n- Collapsed icon-only items must keep accessible names — do not hide the\n  text label from screen readers.\n- The toggle button has an `aria-label` (`\"Toggle sidebar\"`).\n- Mobile overlay closes on Escape and outside click.\n- Persistence respects user choice; do not auto-collapse on every visit.\n\n## Anti-Patterns\n\n- ❌ Wrapping a `<nav>` of non-RenDS items inside `.ren-sidebar-nav`.\n- ❌ Replacing items with `ren-btn` / `ren-link` and re-styling.\n- ❌ `<ren-sidebar style=\"width:240px\">` — set `--ren-sidebar-width` on a\n  parent scope.\n- ❌ Using `display: none` to hide the sidebar on mobile — toggle the\n  `[data-open]` state instead.\n- ❌ Decorative-icon-only items without text labels.\n\n## Related Files\n\n- `components/patterns/ren-sidebar/ren-sidebar.css`\n- `components/patterns/ren-sidebar/ren-sidebar.js`\n- `docs/components/ren-sidebar.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n\n## Test Expectations\n\n- Run component / docs a11y coverage on collapse / mobile overlay flows.\n- Run `npm run lint` after token / selector changes.\n- Manually verify keyboard navigation: Tab through items, Escape closes\n  the mobile overlay.\n\n\n.ren-sidebar-layout {\n  display: flex;\n  min-height: 100dvh;\n  background-color: var(--color-surface);\n}\n\n.ren-sidebar {\n  --sidebar-width: var(--ren-sidebar-width, 16rem);\n  --sidebar-collapsed-width: var(--ren-sidebar-collapsed-width, 4rem);\n\n  width: var(--sidebar-width);\n  flex-shrink: 0;\n  background-color: var(--color-surface-sunken);\n  border-inline-end: 1px solid var(--color-border);\n  display: flex;\n  flex-direction: column;\n  transition: width var(--duration-enter) var(--ease-state-change), transform var(--duration-enter) var(--ease-state-change);\n  overflow: hidden;\n  z-index: 50;\n\n  &[data-collapsed] {\n    width: var(--sidebar-collapsed-width);\n\n    & .ren-sidebar-item-text {\n      opacity: 0;\n      width: 0;\n      overflow: hidden;\n      transition: opacity var(--duration-enter) var(--ease-state-change), width var(--duration-enter) var(--ease-state-change);\n    }\n\n    & .ren-sidebar-section-label {\n      opacity: 0;\n      width: 0;\n      overflow: hidden;\n      transition: opacity var(--duration-enter) var(--ease-state-change), width var(--duration-enter) var(--ease-state-change);\n    }\n\n    & .ren-sidebar-item {\n      justify-content: center;\n      position: relative;\n\n      &:hover::after {\n        content: attr(data-tooltip);\n        position: absolute;\n        inset-inline-start: 100%;\n        top: 50%;\n        transform: translateY(-50%);\n        margin-inline-start: var(--ren-space-2, 0.5rem);\n        padding: var(--ren-space-2, 0.5rem) var(--ren-space-3, 0.75rem);\n        background-color: var(--color-text);\n        color: var(--color-surface);\n        border-radius: 0.375rem;\n        font-size: var(--ren-caption-size, 0.75rem);\n        white-space: nowrap;\n        z-index: 1000;\n        pointer-events: none;\n      }\n    }\n  }\n\n  @media (max-width: 48rem) {\n    position: fixed;\n    top: 0;\n    inset-inline-start: 0;\n    height: 100dvh;\n    width: var(--sidebar-width);\n    transform: translateX(-100%);\n    transition: transform var(--duration-enter) var(--ease-state-change);\n\n    &[data-open] {\n      transform: translateX(0);\n      box-shadow: 4px 0 12px rgb(0 0 0 / 0.15);\n    }\n\n    &[data-collapsed] {\n      transform: translateX(-100%);\n      width: var(--sidebar-width);\n    }\n  }\n}\n\n.ren-sidebar-header {\n  padding: var(--ren-space-3, 0.75rem) var(--ren-space-4, 1rem);\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  border-bottom: 1px solid var(--color-border);\n  flex-shrink: 0;\n\n  @media (max-width: 48rem) {\n    padding: var(--ren-space-3, 0.75rem) var(--ren-space-4, 1rem);\n  }\n}\n\n.ren-sidebar-content {\n  flex: 1;\n  overflow-y: auto;\n  padding: var(--ren-space-2, 0.5rem);\n  scrollbar-width: thin;\n  scrollbar-color: var(--color-border) transparent;\n\n  &::-webkit-scrollbar {\n    width: 6px;\n  }\n\n  &::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background-color: var(--color-border);\n    border-radius: 3px;\n\n    &:hover {\n      background-color: var(--color-text-muted);\n    }\n  }\n}\n\n.ren-sidebar-footer {\n  padding: var(--ren-space-3, 0.75rem) var(--ren-space-4, 1rem);\n  border-top: 1px solid var(--color-border);\n  flex-shrink: 0;\n\n  @media (max-width: 48rem) {\n    padding: var(--ren-space-3, 0.75rem) var(--ren-space-4, 1rem);\n  }\n}\n\n.ren-sidebar-nav {\n  list-style: none;\n  padding: 0;\n  margin: 0;\n  display: flex;\n  flex-direction: column;\n  gap: var(--ren-space-1, 0.25rem);\n}\n\n.ren-sidebar-section {\n  margin-top: var(--ren-space-4, 1rem);\n\n  &:first-child {\n    margin-top: 0;\n  }\n}\n\n.ren-sidebar-section-label {\n  padding: var(--ren-space-1, 0.25rem) var(--ren-space-3, 0.75rem);\n  font-size: var(--ren-caption-size, 0.75rem);\n  font-weight: 600;\n  color: var(--color-text-faint);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  transition: opacity var(--duration-enter) var(--ease-state-change);\n  white-space: nowrap;\n  overflow: hidden;\n}\n\n.ren-sidebar-item {\n  display: flex;\n  align-items: center;\n  gap: var(--ren-space-3, 0.75rem);\n  padding: var(--ren-space-2, 0.5rem) var(--ren-space-3, 0.75rem);\n  border-radius: 0.375rem;\n  color: var(--color-text-muted);\n  font-size: var(--ren-label-size, 0.875rem);\n  cursor: pointer;\n  transition: background-color var(--duration-state) var(--ease-state-change), color var(--duration-state) var(--ease-state-change);\n  text-decoration: none;\n  white-space: nowrap;\n  user-select: none;\n\n  &:hover {\n    background-color: var(--color-fill);\n    color: var(--color-text);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: -2px;\n  }\n\n  &.active,\n  &[aria-current=\"page\"] {\n    background-color: var(--color-accent-subtle);\n    color: var(--color-accent);\n    font-weight: 500;\n\n    & .ren-sidebar-item-icon {\n      color: var(--color-accent);\n    }\n  }\n\n  &[aria-disabled=\"true\"] {\n    opacity: 0.5;\n    cursor: not-allowed;\n    pointer-events: none;\n  }\n}\n\n.ren-sidebar-item-icon {\n  flex-shrink: 0;\n  width: 20px;\n  height: 20px;\n  color: var(--color-text-muted);\n  transition: color var(--duration-state) var(--ease-state-change);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.ren-sidebar-item-text {\n  flex: 1;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  transition: opacity var(--duration-enter) var(--ease-state-change), width var(--duration-enter) var(--ease-state-change);\n}\n\n.ren-sidebar-divider {\n  height: 1px;\n  background-color: var(--color-separator);\n  margin: var(--ren-space-2, 0.5rem) 0;\n}\n\n.ren-sidebar-toggle {\n  background: none;\n  border: none;\n  padding: var(--ren-space-2, 0.5rem);\n  cursor: pointer;\n  color: var(--color-text-muted);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-width: 32px;\n  min-height: 32px;\n  border-radius: 0.375rem;\n  transition: background-color var(--duration-state) var(--ease-state-change), color var(--duration-state) var(--ease-state-change);\n\n  &:hover {\n    background-color: var(--color-fill);\n    color: var(--color-text);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  @media (max-width: 48rem) {\n    display: none;\n  }\n}\n\n.ren-sidebar-main {\n  flex: 1;\n  overflow-y: auto;\n  display: flex;\n  flex-direction: column;\n  scrollbar-width: thin;\n  scrollbar-color: var(--color-border) transparent;\n\n  &::-webkit-scrollbar {\n    width: 6px;\n  }\n\n  &::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background-color: var(--color-border);\n    border-radius: 3px;\n\n    &:hover {\n      background-color: var(--color-text-muted);\n    }\n  }\n\n  @media (max-width: 48rem) {\n    margin-inline-start: 0;\n  }\n}\n\n.ren-sidebar-overlay {\n  position: fixed;\n  inset: 0;\n  background-color: rgb(0 0 0 / 0.5);\n  opacity: 0;\n  pointer-events: none;\n  transition: opacity var(--duration-enter) var(--ease-state-change);\n  z-index: 40;\n\n  @media (max-width: 48rem) {\n    .ren-sidebar[data-open] ~ & {\n      opacity: 1;\n      pointer-events: auto;\n    }\n  }\n}\n\n/* ═══ REDUCED MOTION ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-sidebar,\n  .ren-sidebar * {\n    animation-duration: 0ms !important;\n    transition-duration: 0ms !important;\n  }\n}\n/* Appearance API bridge */\n.ren-sidebar { inline-size: var(--ren-sidebar-width, 260px); background: var(--ren-sidebar-bg, var(--color-surface-sunken)); border-color: var(--ren-sidebar-border, var(--color-border)); transition-duration: var(--ren-sidebar-duration, var(--duration-normal)); transition-timing-function: var(--ren-sidebar-easing, var(--ease-out)); }\n.ren-sidebar.is-collapsed, .ren-sidebar[data-collapsed=\"true\"] { inline-size: var(--ren-sidebar-collapsed-width, 60px); }\n.ren-sidebar a, .ren-sidebar button { min-block-size: var(--ren-sidebar-item-height, var(--size-lg)); padding: var(--ren-sidebar-item-padding, var(--space-2) var(--space-3)); border-radius: var(--ren-sidebar-item-radius, var(--radius-md)); }\n.ren-sidebar a[aria-current=\"page\"], .ren-sidebar a[data-active] { background: var(--ren-sidebar-active-bg, var(--color-accent-subtle)); color: var(--ren-sidebar-active-color, var(--color-accent)); }\n\n@media (any-pointer: coarse), (pointer: coarse) {\n  .ren-sidebar-toggle {\n    min-inline-size: var(--touch-min);\n    min-block-size: var(--touch-min);\n  }\n}\n\n\n/**\n * RenSidebar Component - Collapsible sidebar for app layouts\n * Supports desktop collapse and mobile overlay modes\n */\n\nconst MOBILE_BREAKPOINT = 768;\nconst STORAGE_KEY = 'ren-sidebar-collapsed';\nconst KEYBOARD_CODES = { Escape: 'Escape' };\n\nclass RenSidebar extends HTMLElement {\n  constructor() {\n    super();\n    this._toggleBtn = null;\n    this._isCollapsed = false;\n    this._isOpen = false;\n    this._isMobile = false;\n    this._navItems = [];\n    this._listenerController = null;\n    this._storageKey = STORAGE_KEY;\n  }\n\n  connectedCallback() {\n    this._initElements();\n    this._restoreState();\n    this._attachEventListeners();\n    this._updateMobileState();\n  }\n\n  disconnectedCallback() {\n    this._removeEventListeners();\n  }\n\n  _initElements() {\n    this._toggleBtn = this.querySelector('.ren-sidebar-toggle');\n    this._navItems = Array.from(this.querySelectorAll('.ren-sidebar-item'));\n    this._storageKey = this.getAttribute('storage-key') || STORAGE_KEY;\n  }\n\n  _attachEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = new AbortController();\n    const { signal } = this._listenerController;\n\n    // Toggle collapse button\n    if (this._toggleBtn) {\n      this._toggleBtn.addEventListener('click', () => this._toggleCollapse(), { signal });\n    }\n\n    // Nav item clicks\n    this._navItems.forEach(item => {\n      item.addEventListener('click', (e) => {\n        if (e.currentTarget.hasAttribute('href')) {\n          this._setActiveItem(e.currentTarget);\n        }\n        if (this._isMobile && this._isOpen) {\n          this._closeMenu();\n        }\n      }, { signal });\n    });\n\n    // Close on Escape (mobile)\n    document.addEventListener('keydown', (e) => {\n      if (e.key === KEYBOARD_CODES.Escape && this._isMobile && this._isOpen) {\n        this._closeMenu();\n      }\n    }, { signal });\n\n    // Handle window resize\n    window.addEventListener('resize', () => {\n      this._updateMobileState();\n    }, { signal });\n\n    // Close menu on outside click (mobile)\n    document.addEventListener('click', (e) => {\n      if (this._isMobile && this._isOpen && !this.contains(e.target)) {\n        const overlay = this.nextElementSibling;\n        if (overlay && overlay.classList.contains('ren-sidebar-overlay')) {\n          if (overlay.contains(e.target)) {\n            this._closeMenu();\n          }\n        }\n      }\n    }, { signal });\n\n    // Expose toggle menu for mobile\n    this._setupMobileToggle();\n  }\n\n  _setupMobileToggle() {\n    // Allow external control via method\n    this.toggleMenu = () => {\n      if (this._isOpen) {\n        this._closeMenu();\n      } else {\n        this._openMenu();\n      }\n    };\n  }\n\n  _removeEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = null;\n  }\n\n  _toggleCollapse() {\n    if (this._isCollapsed) {\n      this._expand();\n    } else {\n      this._collapse();\n    }\n  }\n\n  _collapse() {\n    this._isCollapsed = true;\n    this.setAttribute('data-collapsed', '');\n    this._writeCollapsedState(true);\n    this._dispatchToggleEvent();\n  }\n\n  _expand() {\n    this._isCollapsed = false;\n    this.removeAttribute('data-collapsed');\n    this._writeCollapsedState(false);\n    this._dispatchToggleEvent();\n  }\n\n  _toggleMenu() {\n    if (this._isOpen) {\n      this._closeMenu();\n    } else {\n      this._openMenu();\n    }\n  }\n\n  _openMenu() {\n    if (!this._isMobile) return;\n    this._isOpen = true;\n    this.setAttribute('data-open', '');\n  }\n\n  _closeMenu() {\n    if (!this._isMobile) return;\n    this._isOpen = false;\n    this.removeAttribute('data-open');\n  }\n\n  _updateMobileState() {\n    const wasMobile = this._isMobile;\n    this._isMobile = window.innerWidth < MOBILE_BREAKPOINT;\n\n    // Close menu when transitioning to desktop\n    if (wasMobile && !this._isMobile && this._isOpen) {\n      this._closeMenu();\n    }\n\n    // Reset collapse on mobile\n    if (this._isMobile && this._isCollapsed) {\n      this._expand();\n    }\n  }\n\n  _restoreState() {\n    const isCollapsed = this._readCollapsedState();\n    if (isCollapsed && !this._isMobile) {\n      this._collapse();\n    }\n  }\n\n  _readCollapsedState() {\n    try {\n      return localStorage.getItem(this._storageKey) === 'true';\n    } catch {\n      return false;\n    }\n  }\n\n  _writeCollapsedState(isCollapsed) {\n    try {\n      localStorage.setItem(this._storageKey, isCollapsed ? 'true' : 'false');\n    } catch {\n      // Storage can be blocked in private contexts; visual state still updates.\n    }\n  }\n\n  _setActiveItem(item) {\n    this._navItems.forEach(navItem => {\n      navItem.classList.remove('active');\n      navItem.removeAttribute('aria-current');\n    });\n\n    item.classList.add('active');\n    item.setAttribute('aria-current', 'page');\n  }\n\n  _dispatchToggleEvent() {\n    const event = new CustomEvent('ren-sidebar-toggle', {\n      detail: { collapsed: this._isCollapsed },\n      bubbles: true,\n      composed: true\n    });\n    this.dispatchEvent(event);\n  }\n\n  // Public API\n  get isCollapsed() {\n    return this._isCollapsed;\n  }\n\n  get isMobileOpen() {\n    return this._isOpen;\n  }\n\n  setActiveItem(href) {\n    const item = this._navItems.find((navItem) => navItem.getAttribute('href') === href);\n    if (item && item.classList.contains('ren-sidebar-item')) {\n      this._setActiveItem(item);\n    }\n  }\n\n  openMobileMenu() {\n    if (this._isMobile) {\n      this._openMenu();\n    }\n  }\n\n  closeMobileMenu() {\n    if (this._isMobile) {\n      this._closeMenu();\n    }\n  }\n}\n\ncustomElements.define('ren-sidebar', RenSidebar);\n",
      "path": "components/patterns/ren-sidebar",
      "id": "component:pattern:ren-sidebar",
      "type": "component",
      "name": "ren-sidebar"
    },
    {
      "data": {
        "kind": "pattern",
        "selectors": [
          ".ren-table",
          ".ren-table-body",
          ".ren-table-comfortable",
          ".ren-table-compact",
          ".ren-table-empty",
          ".ren-table-empty-description",
          ".ren-table-empty-icon",
          ".ren-table-empty-title",
          ".ren-table-header",
          ".ren-table-loading-overlay",
          ".ren-table-pagination",
          ".ren-table-pagination-button",
          ".ren-table-pagination-controls",
          ".ren-table-pagination-info",
          ".ren-table-select",
          ".ren-table-toolbar",
          ".ren-table-toolbar-search",
          ".ren-table-wrapper",
          ".ren-td",
          ".ren-td-pinned",
          ".ren-th",
          ".ren-th-pinned",
          ".ren-th-resize",
          ".ren-th-sortable",
          ".ren-tr"
        ],
        "tokens": [
          "--body-size",
          "--caption-size",
          "--color-accent",
          "--color-accent-subtle",
          "--color-border",
          "--color-border-muted",
          "--color-fill",
          "--color-fill-hover",
          "--color-on-accent",
          "--color-surface",
          "--color-surface-raised",
          "--color-surface-sunken",
          "--color-text",
          "--color-text-muted",
          "--duration-state",
          "--duration-tactile",
          "--ease-enter",
          "--label-size",
          "--label-weight",
          "--radius-md",
          "--radius-sm",
          "--ren-table-border-color",
          "--ren-table-cell-padding",
          "--ren-table-header-bg",
          "--ren-table-header-color",
          "--ren-table-header-size",
          "--ren-table-header-weight",
          "--ren-table-hover-bg",
          "--ren-table-row-height",
          "--ren-table-selected-bg",
          "--ren-table-stripe-bg",
          "--size-lg",
          "--space-10",
          "--space-2",
          "--space-3",
          "--space-4",
          "--space-5",
          "--stroke-1",
          "--touch-min",
          "--weight-semibold"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-table Pattern Contract\n\nData table pattern with sorting, row state, selection, and dense scanning.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-table` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-table` pattern.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Table pattern behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this pattern.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The UI is a tabular dataset with rows + columns that needs sortable headers (.ren-th-sortable + data-sort).\"\n    - \"You need row selection (multi-select with shift-click range) using a leading <input type=\\\"checkbox\\\"> in .ren-table-select cells.\"\n    - \"You need client-side pagination (.ren-table-pagination + data-page-prev/data-page-next/data-page-size-select buttons).\"\n    - \"You need search / filter via a [data-table-search] input inside .ren-table-toolbar.\"\n    - \"You need density variants (.ren-table-compact, .ren-table-comfortable) or sticky / pinned headers and columns.\"\n    - \"You need integrated empty state (.ren-table-empty) and loading shimmer ([data-loading] + .ren-table-loading-overlay).\"\n  avoidWhen:\n    - \"The data is a list of cards / tiles — use ren-card grid, not a table.\"\n    - \"There are only 2-3 key/value pairs per item — use ren-description-list.\"\n    - \"It is a tree / hierarchical view — use ren-tree.\"\n    - \"It is a single read-only stat — use ren-stat / ren-metric.\"\n    - \"The dataset is bigger than what fits in memory — pair with server-side pagination, not the built-in client pager.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-table/ren-table.css\"\n  js:\n    - \"rends/components/patterns/ren-table/ren-table.js\"\n  notes:\n    - \"JS registers <ren-table> and owns sorting, selection, pagination, keyboard nav, column resize, and search filter.\"\n    - \"Use real <table>, <thead>, <tbody>, <tr>, <th>, <td> — the JS hooks the standard elements; do not replace with divs.\"\n\nrequiredMarkup:\n  - \"Root is <ren-table data-page-size=\\\"…\\\"> wrapping a <div class=\\\"ren-table-wrapper\\\"> > <table class=\\\"ren-table\\\">.\"\n  - \"Header row uses <thead class=\\\"ren-table-header\\\"><tr><th class=\\\"ren-th\\\">…</th></tr></thead>; sortable headers add .ren-th-sortable + data-column=\\\"<key>\\\".\"\n  - \"Body uses <tbody class=\\\"ren-table-body\\\">; its rows are <tr class=\\\"ren-tr\\\" data-row-id=\\\"…\\\"> with <td class=\\\"ren-td\\\"> cells. Selection uses class .ren-table-select on both th/td and a real <input type=\\\"checkbox\\\" aria-label=\\\"Select row\\\">.\"\n  - \"Pagination chrome goes in <div class=\\\"ren-table-pagination\\\"> with .ren-table-pagination-info + .ren-table-pagination-controls containing buttons carrying [data-page-prev] / [data-page-next] / [data-page-size-select].\"\n  - \"Toolbar lives in <div class=\\\"ren-table-toolbar\\\"> and the search box must include the [data-table-search] attribute so the JS wires filtering.\"\n  - \"Empty state is <div class=\\\"ren-table-empty\\\"> with .ren-table-empty-icon / .ren-table-empty-title / .ren-table-empty-description (or reuse the ren-empty-state pattern).\"\n\nforbiddenPatterns:\n  - \"Replacing <table> with <div role=\\\"table\\\"> — kills native keyboard / screen-reader support.\"\n  - \"Implementing sort by clicking a non-th element — sort affordance must live on <th class=\\\"ren-th-sortable\\\">.\"\n  - \"Setting [data-sort] manually from consumer code — the component owns asc/desc cycling.\"\n  - \"Custom checkbox UI inside .ren-table-select — keep the native <input type=\\\"checkbox\\\"> (accent-color is themed via --color-accent).\"\n  - \"Toggling rows with display: none for filtering — let the JS manage row visibility through its filter pipeline.\"\n  - \"Hardcoding pinned column offsets — use .ren-th-pinned / .ren-td-pinned which already set sticky positioning.\"\n\ntokenPolicy:\n  allowed:\n    - \"Public component tokens listed in Public Token API: --ren-table-border-color, --ren-table-cell-padding, --ren-table-header-bg, --ren-table-header-color, --ren-table-header-size, --ren-table-header-weight, --ren-table-hover-bg, --ren-table-row-height, --ren-table-selected-bg, --ren-table-stripe-bg.\"\n    - \"Semantic tokens consumed by selectors: --color-surface, --color-surface-raised, --color-border, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-accent, --color-on-accent, --color-accent-subtle.\"\n    - \"Spacing / radius / type tokens: --space-2, --space-3, --space-4, --space-10, --radius-sm, --radius-md, --stroke-1, --body-size, --label-size, --label-weight, --caption-size, --touch-min.\"\n    - \"Motion tokens: --duration-tactile, --duration-state, --ease-enter.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) for stripes, borders, or selection.\"\n    - \"Hardcoded hex / named colors for hover, selected, or accent.\"\n    - \"Inline transition values — use --duration-* / --ease-* tokens.\"\n\naccessibility:\n  required:\n    - \"Use a real <table> with <thead> / <tbody>; sortable <th> elements set aria-sort=\\\"ascending|descending|none\\\" mirroring [data-sort].\"\n    - \"Selection checkboxes are real <input type=\\\"checkbox\\\"> with aria-label (\\\"Select all\\\" / \\\"Select row N\\\"); rows reflect state via aria-selected=\\\"true\\\" on <tr>.\"\n    - \"Pagination buttons are real <button> with min-width/height of 44px (--touch-min); disabled state uses the disabled attribute, not opacity alone.\"\n    - \"Loading state sets [data-loading] on <ren-table> and exposes pointer-events: none on rows; pair with an aria-live announcement so SR users hear \\\"Loading…\\\" / \\\"Loaded N rows\\\".\"\n    - \"Empty state must include a visible .ren-table-empty-title text; do not rely on icon alone.\"\n    - \"Focus rings on <th>, <td>, .ren-tr (:focus-within), and pagination buttons all use --color-accent — never strip without restoring a visible alternative.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-table/ren-table.css\">\n<script type=\"module\" src=\"rends/components/patterns/ren-table/ren-table.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-table data-page-size=\"10\"><div class=\"ren-table-wrapper\"><table class=\"ren-table\"><thead class=\"ren-table-header\"><tr><th class=\"ren-th ren-th-sortable\" data-column=\"name\">Name</th></tr></thead><tbody class=\"ren-table-body\"><tr class=\"ren-tr\" data-row-id=\"1\"><td class=\"ren-td\">Jane Doe</td></tr></tbody></table></div></ren-table>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-table`\n- `.ren-table-body`\n- `.ren-table-comfortable`\n- `.ren-table-compact`\n- `.ren-table-empty`\n- `.ren-table-empty-description`\n- `.ren-table-empty-icon`\n- `.ren-table-empty-title`\n- `.ren-table-header`\n- `.ren-table-loading-overlay`\n- `.ren-table-pagination`\n- `.ren-table-pagination-button`\n- `.ren-table-pagination-controls`\n- `.ren-table-pagination-info`\n- `.ren-table-select`\n- `.ren-table-toolbar`\n- `.ren-table-toolbar-search`\n- `.ren-table-wrapper`\n- `.ren-td`\n- `.ren-td-pinned`\n- `.ren-th`\n- `.ren-th-pinned`\n- `.ren-th-resize`\n- `.ren-th-sortable`\n- `...and 1 more in the source files.`\n\n## States And Attributes\n\n- `[aria-selected]`\n- `[data-loading]`\n- `[data-page-next]`\n- `[data-page-prev]`\n- `[data-page-size-select]`\n- `[data-sort]`\n- `[data-table-search]`\n- `:active`\n- `:checked`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-table-border-color`\n- `--ren-table-cell-padding`\n- `--ren-table-header-bg`\n- `--ren-table-header-color`\n- `--ren-table-header-size`\n- `--ren-table-header-weight`\n- `--ren-table-hover-bg`\n- `--ren-table-row-height`\n- `--ren-table-selected-bg`\n- `--ren-table-stripe-bg`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Events\n\n- `ren-sort` — `{ column, direction }` after the sort direction changes.\n- `ren-select` — `{ selected }` after row selection changes or is cleared.\n- `ren-filter` — `{ value }` after the toolbar search filter changes.\n\nAll three events bubble and are composed; they are not cancelable.\n\n## Related Files\n\n- `components/patterns/ren-table/ren-table.css`\n- `components/patterns/ren-table/ren-table.js`\n- `docs/components/ren-table.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/**\n * RenDS — Data Table Organism\n * ============================\n *\n * A comprehensive, accessible data table component supporting:\n * - Sortable columns with directional indicators\n * - Row selection with multi-select and shift+click range selection\n * - Client-side pagination with configurable page size\n * - Keyboard navigation (arrow keys, Enter, Space)\n * - Column resizing via drag handle\n * - Pinned/sticky columns and headers\n * - Multiple density variants (compact, comfortable)\n * - Loading states with skeleton shimmer\n * - Empty state messaging\n * - Responsive horizontal scrolling\n * - Search/filter toolbar integration\n * - Full ARIA support for accessibility\n *\n * @example\n * <ren-table data-page-size=\"10\">\n *   <div class=\"ren-table-toolbar\">\n *     <input class=\"ren-input ren-input-sm\" placeholder=\"Search...\" data-table-search>\n *   </div>\n *   <div class=\"ren-table-wrapper\">\n *     <table class=\"ren-table\">\n *       <thead class=\"ren-table-header\">\n *         <tr>\n *           <th class=\"ren-th ren-table-select\"><input type=\"checkbox\" aria-label=\"Select all\"></th>\n *           <th class=\"ren-th ren-th-sortable\" data-column=\"name\">Name</th>\n *         </tr>\n *       </thead>\n *       <tbody class=\"ren-table-body\">\n *         <tr class=\"ren-tr\" data-row-id=\"1\">\n *           <td class=\"ren-td ren-table-select\"><input type=\"checkbox\" aria-label=\"Select row\"></td>\n *           <td class=\"ren-td\">John Doe</td>\n *         </tr>\n *       </tbody>\n *     </table>\n *   </div>\n *   <div class=\"ren-table-pagination\"></div>\n * </ren-table>\n */\n\n/* ===== Base Table Structure ===== */\n\nren-table {\n  container-type: inline-size;\n  container-name: ren-table;\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n  width: 100%;\n}\n\n.ren-table-wrapper {\n  flex: 1;\n  overflow-x: auto;\n  overflow-y: auto;\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n  scroll-behavior: smooth;\n  scrollbar-width: thin;\n  scrollbar-color: var(--color-border) transparent;\n\n  &::-webkit-scrollbar {\n    height: 8px;\n    width: 8px;\n  }\n\n  &::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background-color: var(--color-border);\n    border-radius: var(--radius-sm);\n    transition: background-color var(--duration-state) var(--ease-enter);\n\n    &:hover {\n      background-color: var(--color-text-muted);\n    }\n  }\n}\n\n.ren-table {\n  width: 100%;\n  border-collapse: collapse;\n  background-color: var(--color-surface);\n  font-size: var(--body-size);\n  color: var(--color-text);\n}\n\n/* ===== Header Styles ===== */\n\n.ren-table-header {\n  position: sticky;\n  top: 0;\n  z-index: 10;\n  background-color: var(--color-surface-raised);\n}\n\n.ren-table-header tr {\n  border-bottom: 1px solid var(--color-border);\n}\n\n.ren-th {\n  padding: var(--space-3) var(--space-4);\n  text-align: start;\n  font-weight: var(--label-weight);\n  font-size: var(--label-size);\n  color: var(--color-text);\n  user-select: none;\n  white-space: nowrap;\n  background-color: var(--color-surface-raised);\n  border-inline-end: 1px solid var(--color-border);\n  height: 44px;\n  display: table-cell;\n  vertical-align: middle;\n\n  &:last-child {\n    border-inline-end: none;\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: -2px;\n  }\n}\n\n.ren-th-sortable {\n  cursor: pointer;\n  position: relative;\n  transition: background-color var(--duration-tactile) var(--ease-enter);\n\n  &:hover {\n    background-color: var(--color-fill);\n  }\n\n  &::after {\n    content: '';\n    display: inline-block;\n    width: 12px;\n    height: 12px;\n    margin-inline-start: var(--space-2);\n    background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 12 12\" fill=\"none\"><path d=\"M3 8L6 5l3 3M3 4L6 7l3-3\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>');\n    background-size: contain;\n    background-repeat: no-repeat;\n    opacity: 0.4;\n    transition: opacity var(--duration-tactile) var(--ease-enter), transform var(--duration-tactile) var(--ease-enter);\n    vertical-align: middle;\n    color: var(--color-text-muted);\n  }\n\n  &[data-sort=\"asc\"]::after {\n    opacity: 1;\n    transform: scaleY(1);\n    background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 12 12\" fill=\"none\"><path d=\"M6 2v6M3 5l3-3 3 3\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>');\n  }\n\n  &[data-sort=\"desc\"]::after {\n    opacity: 1;\n    transform: scaleY(-1);\n    background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 12 12\" fill=\"none\"><path d=\"M6 2v6M3 5l3-3 3 3\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>');\n  }\n}\n\n.ren-th-resize {\n  position: absolute;\n  inset-inline-end: 0;\n  top: 0;\n  height: 100%;\n  width: 4px;\n  cursor: col-resize;\n  user-select: none;\n  background-color: transparent;\n  padding: 0;\n  margin: 0;\n  transition: background-color var(--duration-tactile) var(--ease-enter);\n\n  &:hover,\n  &:active {\n    background-color: var(--color-accent);\n  }\n}\n\n/* Pinned columns */\n.ren-th-pinned {\n  position: sticky;\n  z-index: 11;\n}\n\n/* ===== Body & Row Styles ===== */\n\n.ren-table-body {\n  background-color: var(--color-surface);\n}\n\n.ren-tr {\n  border-bottom: var(--stroke-1) solid var(--color-border);\n  transition: background-color var(--duration-tactile) var(--ease-enter);\n  height: 44px;\n\n  &:nth-child(even) {\n    background-color: var(--color-fill);\n  }\n\n  &:hover {\n    background-color: var(--color-fill-hover);\n  }\n\n  &[aria-selected=\"true\"] {\n    background-color: var(--color-accent-subtle);\n\n    .ren-td {\n      background-color: var(--color-accent-subtle);\n    }\n  }\n\n  &:focus-within {\n    outline: 2px solid var(--color-accent);\n    outline-offset: -1px;\n  }\n}\n\n.ren-td {\n  padding: var(--space-3) var(--space-4);\n  border-inline-end: 1px solid var(--color-border);\n  color: var(--color-text);\n  vertical-align: middle;\n  height: 44px;\n\n  &:last-child {\n    border-inline-end: none;\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: -2px;\n  }\n}\n\n/* Pinned column cells */\n.ren-td-pinned {\n  position: sticky;\n  z-index: 9;\n  background-color: inherit;\n}\n\n/* ===== Selection Styles ===== */\n\n.ren-table-select {\n  width: var(--space-10);\n  padding: var(--space-3);\n  text-align: center;\n\n  input[type=\"checkbox\"] {\n    width: 18px;\n    height: 18px;\n    cursor: pointer;\n    accent-color: var(--color-accent);\n    border: 1px solid var(--color-border);\n    border-radius: var(--radius-sm);\n    transition: background-color var(--duration-tactile) var(--ease-enter),\n                border-color var(--duration-tactile) var(--ease-enter);\n\n    &:hover:not(:disabled) {\n      border-color: var(--color-accent);\n    }\n\n    &:checked {\n      background-color: var(--color-accent);\n      border-color: var(--color-accent);\n      color: var(--color-on-accent);\n    }\n\n    &:focus-visible {\n      outline: 2px solid var(--color-accent);\n      outline-offset: 2px;\n    }\n\n    &:disabled {\n      opacity: 0.5;\n      cursor: not-allowed;\n    }\n  }\n}\n\n/* ===== Pagination ===== */\n\n.ren-table-pagination {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: var(--space-3) var(--space-4);\n  border-top: 1px solid var(--color-border);\n  background-color: var(--color-surface);\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n  gap: var(--space-4);\n\n  &:empty {\n    display: none;\n  }\n}\n\n.ren-table-pagination-info {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  font-size: var(--body-size);\n  color: var(--color-text);\n}\n\n.ren-table-pagination-controls {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n}\n\n.ren-table-pagination-button {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: var(--touch-min);\n  height: var(--touch-min);\n  padding: var(--space-2);\n  background-color: var(--color-fill);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-sm);\n  color: var(--color-text);\n  font-size: var(--body-size);\n  cursor: pointer;\n  transition: background-color var(--duration-tactile) var(--ease-enter),\n              border-color var(--duration-tactile) var(--ease-enter);\n  user-select: none;\n  min-width: 44px;\n  min-height: 44px;\n\n  &:hover:not(:disabled) {\n    background-color: var(--color-fill-hover);\n    border-color: var(--color-accent);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  &:disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n    background-color: var(--color-fill);\n  }\n}\n\n/* ===== Toolbar Styles ===== */\n\n.ren-table-toolbar {\n  display: flex;\n  align-items: center;\n  gap: var(--space-3);\n  padding: var(--space-3) var(--space-4);\n  background-color: var(--color-surface);\n  border-bottom: 1px solid var(--color-border);\n  flex-wrap: wrap;\n\n  &:empty {\n    display: none;\n  }\n}\n\n.ren-table-toolbar-search {\n  flex: 1;\n  min-width: 200px;\n  max-width: 300px;\n}\n\n/* ===== Empty State ===== */\n\n.ren-table-empty {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  padding: var(--space-10);\n  gap: var(--space-4);\n  text-align: center;\n  color: var(--color-text-muted);\n  background-color: var(--color-surface);\n  min-height: 200px;\n\n  .ren-table-empty-icon {\n    font-size: 40px;\n    opacity: 0.4;\n  }\n\n  .ren-table-empty-title {\n    font-size: var(--label-size);\n    font-weight: var(--label-weight);\n    color: var(--color-text);\n  }\n\n  .ren-table-empty-description {\n    font-size: var(--caption-size);\n    color: var(--color-text-muted);\n    max-width: 300px;\n  }\n}\n\n/* ===== Loading State ===== */\n\nren-table[data-loading] {\n  .ren-table-body {\n    position: relative;\n  }\n\n  .ren-tr {\n    opacity: 0.5;\n    pointer-events: none;\n  }\n}\n\n.ren-table-loading-overlay {\n  position: absolute;\n  inset: 0;\n  background: linear-gradient(\n    90deg,\n    transparent,\n    rgb(255, 255, 255, 0.4),\n    transparent\n  );\n  background-size: 200% 100%;\n  animation: shimmer 1.5s infinite;\n  z-index: 20;\n}\n\n@keyframes shimmer {\n  0% {\n    background-position: -200% 0;\n  }\n\n  100% {\n    background-position: 200% 0;\n  }\n}\n\n/* ===== Density Variants ===== */\n\n.ren-table-compact {\n  .ren-th,\n  .ren-td {\n    padding: var(--space-2) var(--space-3);\n    height: 32px;\n  }\n\n  .ren-tr {\n    height: 32px;\n  }\n}\n\n.ren-table-comfortable {\n  .ren-th,\n  .ren-td {\n    padding: var(--space-4) var(--space-5);\n    height: 56px;\n  }\n\n  .ren-tr {\n    height: 56px;\n  }\n}\n\n/* ===== Responsive Design (Container Query) ===== */\n\n@container ren-table (max-width: 600px) {\n  .ren-table {\n    min-width: 600px;\n  }\n\n  .ren-table-pagination {\n    flex-direction: column;\n    align-items: flex-start;\n    gap: var(--space-3);\n  }\n\n  .ren-table-pagination-info {\n    width: 100%;\n  }\n\n  .ren-table-pagination-controls {\n    width: 100%;\n    justify-content: space-between;\n  }\n\n  .ren-table-toolbar {\n    flex-direction: column;\n  }\n\n  .ren-table-toolbar-search {\n    min-width: 100%;\n    max-width: 100%;\n  }\n}\n\n/* ===== Accessibility ===== */\n\n/* High contrast mode support */\n@media (prefers-contrast: more) {\n  .ren-th {\n    border: 1px solid var(--color-text);\n  }\n\n  .ren-td {\n    border: var(--stroke-1) solid var(--color-text);\n  }\n\n  .ren-th-sortable:hover {\n    background-color: var(--color-text);\n    color: var(--color-surface);\n  }\n}\n\n/* Reduced motion support */\n@media (prefers-reduced-motion: reduce) {\n  .ren-tr,\n  .ren-th-sortable,\n  .ren-table-pagination-button,\n  .ren-table-select input[type=\"checkbox\"] {\n    transition: none;\n  }\n\n  .ren-table-loading-overlay {\n    animation: none;\n    background: rgb(255, 255, 255, 0.2);\n  }\n}\n\n/* Dark mode support (if light/dark theming is used) */\n@media (prefers-color-scheme: dark) {\n  .ren-table {\n    /* Uses CSS custom properties from design tokens */\n    /* No additional overrides needed if tokens are properly defined */\n  }\n}\n/* Appearance API bridge */\n.ren-table { font-size: var(--ren-table-header-size, var(--body-size)); }\n.ren-table thead { background: var(--ren-table-header-bg, var(--color-surface-sunken)); color: var(--ren-table-header-color, var(--color-text-muted)); font-weight: var(--ren-table-header-weight, var(--weight-semibold)); }\n.ren-table tr { min-block-size: var(--ren-table-row-height, var(--size-lg)); block-size: var(--ren-table-row-height, var(--size-lg)); }\n.ren-table th, .ren-table td { padding: var(--ren-table-cell-padding, var(--space-3)); }\n.ren-table { border-color: var(--ren-table-border-color, var(--color-border-muted)); }\n.ren-table tbody tr:nth-child(even) { background: var(--ren-table-stripe-bg, var(--color-surface-sunken)); }\n.ren-table tbody tr:hover { background: var(--ren-table-hover-bg, var(--color-fill)); }\n.ren-table tbody tr[aria-selected=\"true\"], .ren-table tbody tr[data-selected] { background: var(--ren-table-selected-bg, var(--color-accent-subtle)); }\n\n\n/**\n * RenDS — Data Table Organism\n * ============================\n *\n * A comprehensive, accessible data table web component that extends native\n * HTML tables with powerful features while maintaining semantic structure.\n *\n * Features:\n * - Click-to-sort columns with asc/desc/none cycling\n * - Multi-select with select-all checkbox and shift+click range selection\n * - Client-side pagination with configurable page size\n * - Keyboard navigation (arrow keys, Enter to sort, Space to select)\n * - Client-side filtering/search\n * - Column resizing via drag handle\n * - Pinned/sticky column support\n * - Multiple density variants (compact, comfortable, default)\n * - Loading state with shimmer effect\n * - Empty state messaging\n * - Full ARIA support for accessibility\n * - Light DOM (no Shadow DOM) for easy styling and integration\n *\n * @example\n * <ren-table data-page-size=\"10\">\n *   <div class=\"ren-table-wrapper\">\n *     <table class=\"ren-table\">\n *       <thead class=\"ren-table-header\">\n *         <tr>\n *           <th class=\"ren-th ren-table-select\"><input type=\"checkbox\"></th>\n *           <th class=\"ren-th ren-th-sortable\" data-column=\"name\">Name</th>\n *         </tr>\n *       </thead>\n *       <tbody class=\"ren-table-body\">\n *         <tr class=\"ren-tr\" data-row-id=\"1\">\n *           <td class=\"ren-td ren-table-select\"><input type=\"checkbox\"></td>\n *           <td class=\"ren-td\">John Doe</td>\n *         </tr>\n *       </tbody>\n *     </table>\n *   </div>\n *   <div class=\"ren-table-pagination\"></div>\n * </ren-table>\n *\n * @fires ren-sort - Dispatched when column sort changes: { column, direction }\n * @fires ren-select - Dispatched when row selection changes: { selected }\n * @fires ren-page - Dispatched when page changes: { page, pageSize }\n * @fires ren-filter - Dispatched when filter changes: { columnIndex, value }\n */\n\nexport class RenTable extends HTMLElement {\n  #table = null;\n  #thead = null;\n  #tbody = null;\n  #headerCheckbox = null;\n  #rowCheckboxes = [];\n  #headerCells = [];\n  #rows = [];\n  #visibleRows = [];\n  #sortColumn = null;\n  #sortDirection = null;\n  #selectedRows = new Set();\n  #currentPage = 1;\n  #pageSize = 10;\n  #filterValues = new Map();\n  #columnWidths = new Map();\n  #resizingColumn = null;\n  #resizeStartX = 0;\n  #resizeStartWidth = 0;\n  #lastSelectedRowIndex = -1;\n  #listenerController = null;\n  #rowCheckboxController = null;\n\n  constructor() {\n    super();\n  }\n\n  connectedCallback() {\n    this._initializeComponent();\n    this._attachEventListeners();\n    this._setupPagination();\n  }\n\n  disconnectedCallback() {\n    this._removeEventListeners();\n  }\n\n  /**\n   * Initialize table references and attributes\n   * @private\n   */\n  _initializeComponent() {\n    this.#table = this.querySelector('.ren-table');\n    this.#thead = this.querySelector('.ren-table-header');\n    this.#tbody = this.querySelector('.ren-table-body');\n\n    if (!this.#table || !this.#thead || !this.#tbody) {\n      console.warn('RenTable: Missing required table structure');\n      return;\n    }\n\n    // Get page size from attribute\n    const pageSize = this.getAttribute('data-page-size');\n    if (pageSize) {\n      this.#pageSize = parseInt(pageSize, 10);\n    }\n\n    // Initialize header cells\n    this.#headerCells = Array.from(this.#thead.querySelectorAll('.ren-th'));\n    this.#headerCheckbox = this.#thead.querySelector('input[type=\"checkbox\"]');\n\n    // Initialize rows\n    this._refreshRows();\n\n    // Add ARIA roles\n    this.#table.setAttribute('role', 'table');\n    if (!this.#thead.hasAttribute('role')) {\n      this.#thead.setAttribute('role', 'rowgroup');\n    }\n    if (!this.#tbody.hasAttribute('role')) {\n      this.#tbody.setAttribute('role', 'rowgroup');\n    }\n  }\n\n  /**\n   * Attach all event listeners\n   * @private\n   */\n  _attachEventListeners() {\n    this.#listenerController?.abort();\n    this.#listenerController = new AbortController();\n    const { signal } = this.#listenerController;\n\n    // Sorting\n    this.#headerCells.forEach((cell, index) => {\n      if (cell.classList.contains('ren-th-sortable')) {\n        cell.addEventListener('click', () => this._handleSort(cell, index), { signal });\n        cell.addEventListener('keydown', (e) => {\n          if (e.key === 'Enter' || e.key === ' ') {\n            e.preventDefault();\n            this._handleSort(cell, index);\n          }\n        }, { signal });\n        cell.setAttribute('tabindex', '0');\n        cell.setAttribute('role', 'columnheader');\n        cell.setAttribute('aria-sort', 'none');\n      }\n    });\n\n    // Selection - header checkbox\n    if (this.#headerCheckbox) {\n      this.#headerCheckbox.addEventListener('change', () => this._handleSelectAll(), { signal });\n    }\n\n    // Selection - row checkboxes\n    this._attachRowCheckboxListeners();\n\n    // Keyboard navigation\n    this.#table.addEventListener('keydown', (e) => this._handleKeyboard(e), { signal });\n\n    // Column resizing\n    this._setupColumnResize(signal);\n\n    // Search input\n    const searchInput = this.querySelector('[data-table-search]');\n    if (searchInput) {\n      searchInput.addEventListener('input', (e) => {\n        this._filterRows(e.target.value);\n      }, { signal });\n    }\n\n    // Pagination button clicks\n    const paginationCtrls = this.querySelector('.ren-table-pagination-controls');\n    if (paginationCtrls) {\n      const prevBtn = paginationCtrls.querySelector('[data-page-prev]');\n      const nextBtn = paginationCtrls.querySelector('[data-page-next]');\n\n      if (prevBtn) prevBtn.addEventListener('click', () => this._previousPage(), { signal });\n      if (nextBtn) nextBtn.addEventListener('click', () => this._nextPage(), { signal });\n    }\n\n    // Pagination page size selector\n    const pageSizeSelect = this.querySelector('[data-page-size-select]');\n    if (pageSizeSelect) {\n      pageSizeSelect.addEventListener('change', (e) => {\n        this.#pageSize = parseInt(e.target.value, 10);\n        this.#currentPage = 1;\n        this._renderTable();\n      }, { signal });\n    }\n  }\n\n  /**\n   * Remove event listeners\n   * @private\n   */\n  _removeEventListeners() {\n    this.#listenerController?.abort();\n    this.#listenerController = null;\n    this.#rowCheckboxController?.abort();\n    this.#rowCheckboxController = null;\n    this.#resizingColumn = null;\n  }\n\n  /**\n   * Refresh the list of all rows\n   * @private\n   */\n  _refreshRows() {\n    this.#rows = Array.from(this.#tbody.querySelectorAll('.ren-tr'));\n    this.#visibleRows = [...this.#rows];\n    this._attachRowCheckboxListeners();\n  }\n\n  /**\n   * Attach listeners to row checkboxes\n   * @private\n   */\n  _attachRowCheckboxListeners() {\n    this.#rowCheckboxController?.abort();\n    this.#rowCheckboxController = new AbortController();\n    const { signal } = this.#rowCheckboxController;\n\n    const checkboxes = Array.from(this.#tbody.querySelectorAll('.ren-table-select input[type=\"checkbox\"]'));\n    checkboxes.forEach((checkbox, index) => {\n      checkbox.addEventListener('change', (e) => {\n        this._handleRowSelect(e, index);\n      }, { signal });\n    });\n    this.#rowCheckboxes = checkboxes;\n  }\n\n  /**\n   * Handle column header click for sorting\n   * @private\n   */\n  _handleSort(cell, index) {\n    const columnName = cell.getAttribute('data-column');\n    if (!columnName) return;\n\n    // Cycle through sort states: none -> asc -> desc -> none\n    let newDirection = null;\n    if (this.#sortColumn !== columnName) {\n      newDirection = 'asc';\n    } else if (this.#sortDirection === 'asc') {\n      newDirection = 'desc';\n    } else if (this.#sortDirection === 'desc') {\n      newDirection = null;\n    } else {\n      newDirection = 'asc';\n    }\n\n    this.#sortColumn = newDirection ? columnName : null;\n    this.#sortDirection = newDirection;\n\n    // Update visual indicators\n    this.#headerCells.forEach((hc) => {\n      if (hc.classList.contains('ren-th-sortable')) {\n        hc.removeAttribute('data-sort');\n        hc.setAttribute('aria-sort', 'none');\n      }\n    });\n\n    if (newDirection) {\n      cell.setAttribute('data-sort', newDirection);\n      cell.setAttribute('aria-sort', newDirection === 'asc' ? 'ascending' : 'descending');\n    }\n\n    // Dispatch event\n    this._dispatchEvent('ren-sort', {\n      column: columnName,\n      direction: newDirection,\n    });\n\n    // Re-render\n    this.#currentPage = 1;\n    this._renderTable();\n  }\n\n  /**\n   * Handle select all checkbox\n   * @private\n   */\n  _handleSelectAll() {\n    const isChecked = this.#headerCheckbox.checked;\n\n    this.#visibleRows.forEach((row) => {\n      const rowId = row.getAttribute('data-row-id');\n      const checkbox = row.querySelector('input[type=\"checkbox\"]');\n\n      if (isChecked) {\n        this.#selectedRows.add(rowId);\n        row.setAttribute('aria-selected', 'true');\n        if (checkbox) checkbox.checked = true;\n      } else {\n        this.#selectedRows.delete(rowId);\n        row.removeAttribute('aria-selected');\n        if (checkbox) checkbox.checked = false;\n      }\n    });\n\n    this._dispatchEvent('ren-select', {\n      selected: Array.from(this.#selectedRows),\n    });\n  }\n\n  /**\n   * Handle individual row selection\n   * @private\n   */\n  _handleRowSelect(event, rowIndex) {\n    const row = this.#visibleRows[rowIndex];\n    if (!row) return;\n\n    const rowId = row.getAttribute('data-row-id');\n    const isChecked = event.target.checked;\n    const isShiftClick = event.shiftKey;\n\n    if (isShiftClick && this.#lastSelectedRowIndex !== -1) {\n      // Range selection\n      const start = Math.min(this.#lastSelectedRowIndex, rowIndex);\n      const end = Math.max(this.#lastSelectedRowIndex, rowIndex);\n\n      for (let i = start; i <= end; i++) {\n        const rangeRow = this.#visibleRows[i];\n        const rangeRowId = rangeRow.getAttribute('data-row-id');\n        const rangeCheckbox = rangeRow.querySelector('input[type=\"checkbox\"]');\n\n        if (isChecked) {\n          this.#selectedRows.add(rangeRowId);\n          rangeRow.setAttribute('aria-selected', 'true');\n          if (rangeCheckbox) rangeCheckbox.checked = true;\n        } else {\n          this.#selectedRows.delete(rangeRowId);\n          rangeRow.removeAttribute('aria-selected');\n          if (rangeCheckbox) rangeCheckbox.checked = false;\n        }\n      }\n    } else {\n      // Single selection\n      if (isChecked) {\n        this.#selectedRows.add(rowId);\n        row.setAttribute('aria-selected', 'true');\n      } else {\n        this.#selectedRows.delete(rowId);\n        row.removeAttribute('aria-selected');\n      }\n    }\n\n    this.#lastSelectedRowIndex = rowIndex;\n\n    // Update select-all checkbox state\n    this._updateSelectAllCheckbox();\n\n    // Dispatch event\n    this._dispatchEvent('ren-select', {\n      selected: Array.from(this.#selectedRows),\n    });\n  }\n\n  /**\n   * Update select-all checkbox based on row selections\n   * @private\n   */\n  _updateSelectAllCheckbox() {\n    if (!this.#headerCheckbox) return;\n\n    const visibleRowIds = this.#visibleRows\n      .map((row) => row.getAttribute('data-row-id'))\n      .filter(Boolean);\n    const allVisibleSelected = visibleRowIds.every((id) =>\n      this.#selectedRows.has(id)\n    );\n    const someSelected = visibleRowIds.some((id) =>\n      this.#selectedRows.has(id)\n    );\n\n    this.#headerCheckbox.checked = allVisibleSelected;\n    this.#headerCheckbox.indeterminate = someSelected && !allVisibleSelected;\n  }\n\n  /**\n   * Handle keyboard navigation\n   * @private\n   */\n  _handleKeyboard(event) {\n    const { key, target } = event;\n\n    // Space to toggle row selection\n    if (key === ' ' && target.classList.contains('ren-tr')) {\n      event.preventDefault();\n      const checkbox = target.querySelector('input[type=\"checkbox\"]');\n      if (checkbox) {\n        checkbox.checked = !checkbox.checked;\n        checkbox.dispatchEvent(new Event('change', { bubbles: true }));\n      }\n    }\n\n    // Arrow keys for navigation\n    if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(key)) {\n      event.preventDefault();\n      this._navigateCells(target, key);\n    }\n\n    // Enter to sort (on header)\n    if (key === 'Enter' && target.classList.contains('ren-th-sortable')) {\n      event.preventDefault();\n      target.click();\n    }\n  }\n\n  /**\n   * Navigate between cells with arrow keys\n   * @private\n   */\n  _navigateCells(currentCell, direction) {\n    let nextCell = currentCell;\n\n    if (direction === 'ArrowUp') {\n      const row = currentCell.closest('tr');\n      const prevRow = row?.previousElementSibling;\n      if (prevRow) {\n        const cellIndex = Array.from(row.cells).indexOf(currentCell);\n        nextCell = prevRow.cells[cellIndex];\n      }\n    } else if (direction === 'ArrowDown') {\n      const row = currentCell.closest('tr');\n      const nextRow = row?.nextElementSibling;\n      if (nextRow) {\n        const cellIndex = Array.from(row.cells).indexOf(currentCell);\n        nextCell = nextRow.cells[cellIndex];\n      }\n    } else if (direction === 'ArrowLeft') {\n      nextCell = currentCell.previousElementSibling || currentCell;\n    } else if (direction === 'ArrowRight') {\n      nextCell = currentCell.nextElementSibling || currentCell;\n    }\n\n    if (nextCell && nextCell !== currentCell) {\n      nextCell.focus();\n    }\n  }\n\n  /**\n   * Setup column resizing\n   * @private\n   */\n  _setupColumnResize(signal) {\n    this.#headerCells.forEach((cell) => {\n      let resizeHandle = cell.querySelector('.ren-th-resize');\n\n      if (!resizeHandle) {\n        resizeHandle = document.createElement('div');\n        resizeHandle.className = 'ren-th-resize';\n        cell.appendChild(resizeHandle);\n      }\n\n      resizeHandle.addEventListener('mousedown', (e) => {\n        this._startResize(e, cell);\n      }, { signal });\n    });\n\n    document.addEventListener('mousemove', (e) => this._handleResize(e), { signal });\n    document.addEventListener('mouseup', () => this._endResize(), { signal });\n  }\n\n  /**\n   * Start column resize\n   * @private\n   */\n  _startResize(event, cell) {\n    this.#resizingColumn = cell;\n    this.#resizeStartX = event.clientX;\n    this.#resizeStartWidth = cell.offsetWidth;\n    cell.style.cursor = 'col-resize';\n  }\n\n  /**\n   * Handle column resize drag\n   * @private\n   */\n  _handleResize(event) {\n    if (!this.#resizingColumn) return;\n\n    const delta = event.clientX - this.#resizeStartX;\n    const newWidth = Math.max(80, this.#resizeStartWidth + delta);\n    this.#resizingColumn.style.width = `${newWidth}px`;\n  }\n\n  /**\n   * End column resize\n   * @private\n   */\n  _endResize() {\n    if (this.#resizingColumn) {\n      this.#resizingColumn.style.cursor = '';\n      this.#resizingColumn = null;\n    }\n  }\n\n  /**\n   * Filter rows by search value\n   * @private\n   */\n  _filterRows(searchValue) {\n    const lowerSearch = searchValue.toLowerCase();\n\n    this.#visibleRows = this.#rows.filter((row) => {\n      const cells = Array.from(row.querySelectorAll('.ren-td'));\n      return cells.some((cell) =>\n        cell.textContent.toLowerCase().includes(lowerSearch)\n      );\n    });\n\n    this.#currentPage = 1;\n    this._renderTable();\n\n    this._dispatchEvent('ren-filter', {\n      value: searchValue,\n    });\n  }\n\n  /**\n   * Sort visible rows\n   * @private\n   */\n  _sortVisibleRows() {\n    if (!this.#sortColumn || !this.#sortDirection) return;\n\n    const columnIndex = this.#headerCells.findIndex(\n      (cell) => cell.getAttribute('data-column') === this.#sortColumn\n    );\n\n    if (columnIndex === -1) return;\n\n    this.#visibleRows.sort((a, b) => {\n      const aValue = a.cells[columnIndex]?.textContent || '';\n      const bValue = b.cells[columnIndex]?.textContent || '';\n\n      // Try numeric comparison first\n      const aNum = parseFloat(aValue);\n      const bNum = parseFloat(bValue);\n\n      let comparison = 0;\n      if (!isNaN(aNum) && !isNaN(bNum)) {\n        comparison = aNum - bNum;\n      } else {\n        comparison = aValue.localeCompare(bValue);\n      }\n\n      return this.#sortDirection === 'asc' ? comparison : -comparison;\n    });\n  }\n\n  /**\n   * Setup pagination controls\n   * @private\n   */\n  _setupPagination() {\n    const paginationDiv = this.querySelector('.ren-table-pagination');\n    if (!paginationDiv) return;\n\n    // Only render pagination if there are rows\n    if (this.#visibleRows.length === 0) {\n      paginationDiv.innerHTML = '';\n      return;\n    }\n\n    const totalPages = Math.ceil(this.#visibleRows.length / this.#pageSize);\n    if (totalPages <= 1) {\n      paginationDiv.innerHTML = '';\n      return;\n    }\n\n    const startItem = (this.#currentPage - 1) * this.#pageSize + 1;\n    const endItem = Math.min(this.#currentPage * this.#pageSize, this.#visibleRows.length);\n\n    paginationDiv.innerHTML = `\n      <div class=\"ren-table-pagination-info\">\n        <span>Showing <strong>${startItem}</strong> to <strong>${endItem}</strong> of <strong>${this.#visibleRows.length}</strong></span>\n      </div>\n      <div class=\"ren-table-pagination-controls\">\n        <button class=\"ren-table-pagination-button\" data-page-prev aria-label=\"Previous page\" ${this.#currentPage === 1 ? 'disabled' : ''}>\n          ← Prev\n        </button>\n        <span style=\"padding: 0 var(--space-2); font-size: var(--caption-size); color: var(--color-text-muted);\">\n          Page ${this.#currentPage} of ${totalPages}\n        </span>\n        <button class=\"ren-table-pagination-button\" data-page-next aria-label=\"Next page\" ${this.#currentPage === totalPages ? 'disabled' : ''}>\n          Next →\n        </button>\n      </div>\n    `;\n\n    // Reattach listeners\n    const prevBtn = paginationDiv.querySelector('[data-page-prev]');\n    const nextBtn = paginationDiv.querySelector('[data-page-next]');\n    const listenerOptions = this.#listenerController\n      ? { signal: this.#listenerController.signal }\n      : undefined;\n\n    if (prevBtn) prevBtn.addEventListener('click', () => this._previousPage(), listenerOptions);\n    if (nextBtn) nextBtn.addEventListener('click', () => this._nextPage(), listenerOptions);\n  }\n\n  /**\n   * Go to previous page\n   * @private\n   */\n  _previousPage() {\n    if (this.#currentPage > 1) {\n      this.#currentPage--;\n      this._renderTable();\n    }\n  }\n\n  /**\n   * Go to next page\n   * @private\n   */\n  _nextPage() {\n    const totalPages = Math.ceil(this.#visibleRows.length / this.#pageSize);\n    if (this.#currentPage < totalPages) {\n      this.#currentPage++;\n      this._renderTable();\n    }\n  }\n\n  /**\n   * Render the table with current page, sort, and filter\n   * @private\n   */\n  _renderTable() {\n    // Apply sorting\n    this._sortVisibleRows();\n\n    // Show/hide rows based on current page\n    const startIndex = (this.#currentPage - 1) * this.#pageSize;\n    const endIndex = startIndex + this.#pageSize;\n\n    const visibleIndexByRow = new Map(\n      this.#visibleRows.map((row, visibleIndex) => [row, visibleIndex])\n    );\n\n    this.#rows.forEach((row) => {\n      const visibleIndex = visibleIndexByRow.get(row);\n      const isVisible = visibleIndex !== undefined &&\n        visibleIndex >= startIndex &&\n        visibleIndex < endIndex;\n\n      if (isVisible) {\n        row.style.display = '';\n        row.setAttribute('role', 'row');\n      } else {\n        row.style.display = 'none';\n      }\n    });\n\n    // Update pagination\n    this._setupPagination();\n\n    // Show/hide empty state\n    this._updateEmptyState();\n  }\n\n  /**\n   * Update empty state display\n   * @private\n   */\n  _updateEmptyState() {\n    let emptyState = this.querySelector('.ren-table-empty');\n\n    if (this.#visibleRows.length === 0) {\n      if (!emptyState) {\n        emptyState = document.createElement('div');\n        emptyState.className = 'ren-table-empty';\n        emptyState.innerHTML = `\n          <div class=\"ren-table-empty-icon\">📭</div>\n          <div class=\"ren-table-empty-title\">No results</div>\n          <div class=\"ren-table-empty-description\">No data matches your search or filters</div>\n        `;\n        const wrapper = this.querySelector('.ren-table-wrapper');\n        if (wrapper) {\n          wrapper.parentNode.insertBefore(emptyState, wrapper);\n        }\n      }\n      if (this.#tbody) {\n        this.#tbody.style.display = 'none';\n      }\n    } else {\n      if (emptyState) {\n        emptyState.remove();\n      }\n      if (this.#tbody) {\n        this.#tbody.style.display = '';\n      }\n    }\n  }\n\n  /**\n   * Dispatch custom event\n   * @private\n   */\n  _dispatchEvent(eventName, detail) {\n    this.dispatchEvent(\n      new CustomEvent(eventName, {\n        detail,\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /**\n   * Public API: Filter rows by column\n   * @param {number} columnIndex - Index of column to filter\n   * @param {string} value - Filter value\n   */\n  filter(columnIndex, value) {\n    this.#filterValues.set(columnIndex, value);\n\n    this.#visibleRows = this.#rows.filter((row) => {\n      for (const [colIdx, filterVal] of this.#filterValues) {\n        const cell = row.cells[colIdx];\n        if (!cell || !cell.textContent.toLowerCase().includes(filterVal.toLowerCase())) {\n          return false;\n        }\n      }\n      return true;\n    });\n\n    this.#currentPage = 1;\n    this._renderTable();\n  }\n\n  /**\n   * Public API: Get selected row IDs\n   * @returns {string[]} Array of selected row IDs\n   */\n  getSelectedRows() {\n    return Array.from(this.#selectedRows);\n  }\n\n  /**\n   * Public API: Set loading state\n   * @param {boolean} isLoading - Whether table is loading\n   */\n  setLoading(isLoading) {\n    if (isLoading) {\n      this.setAttribute('data-loading', '');\n      const wrapper = this.querySelector('.ren-table-wrapper');\n      if (wrapper && !wrapper.querySelector('.ren-table-loading-overlay')) {\n        const overlay = document.createElement('div');\n        overlay.className = 'ren-table-loading-overlay';\n        wrapper.appendChild(overlay);\n      }\n    } else {\n      this.removeAttribute('data-loading');\n      const overlay = this.querySelector('.ren-table-loading-overlay');\n      if (overlay) overlay.remove();\n    }\n  }\n\n  /**\n   * Public API: Set density variant\n   * @param {'default' | 'compact' | 'comfortable'} density - Density variant\n   */\n  setDensity(density) {\n    this.#table.classList.remove('ren-table-compact', 'ren-table-comfortable');\n    if (density === 'compact' || density === 'comfortable') {\n      this.#table.classList.add(`ren-table-${density}`);\n    }\n  }\n\n  /**\n   * Public API: Clear all selections\n   */\n  clearSelection() {\n    this.#selectedRows.clear();\n    this.#rowCheckboxes.forEach((checkbox) => {\n      checkbox.checked = false;\n    });\n    if (this.#headerCheckbox) {\n      this.#headerCheckbox.checked = false;\n      this.#headerCheckbox.indeterminate = false;\n    }\n    this.#rows.forEach((row) => {\n      row.removeAttribute('aria-selected');\n    });\n    this._dispatchEvent('ren-select', { selected: [] });\n  }\n\n  /**\n   * Public API: Refresh table data\n   */\n  refresh() {\n    this._refreshRows();\n    this.#currentPage = 1;\n    this._renderTable();\n  }\n}\n\ncustomElements.define('ren-table', RenTable);\n",
      "path": "components/patterns/ren-table",
      "id": "component:pattern:ren-table",
      "type": "component",
      "name": "ren-table"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-avatar",
          ".ren-avatar-2xl",
          ".ren-avatar-group",
          ".ren-avatar-lg",
          ".ren-avatar-sm",
          ".ren-avatar-square",
          ".ren-avatar-status",
          ".ren-avatar-status-away",
          ".ren-avatar-status-busy",
          ".ren-avatar-status-offline",
          ".ren-avatar-wrapper",
          ".ren-avatar-xl",
          ".ren-avatar-xs"
        ],
        "tokens": [
          "--avatar-2xl",
          "--avatar-lg",
          "--avatar-sm",
          "--avatar-xl",
          "--avatar-xs",
          "--color-danger",
          "--color-success",
          "--color-surface",
          "--color-warning",
          "--gray-400",
          "--radius-full",
          "--radius-md",
          "--ren-avatar-bg",
          "--ren-avatar-border",
          "--ren-avatar-color",
          "--ren-avatar-font-size",
          "--ren-avatar-radius",
          "--ren-avatar-size",
          "--text-base",
          "--text-lg",
          "--text-xl",
          "--text-xs",
          "--weight-semibold"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-avatar Component Contract\n\nUser identity primitive for images, initials, and presence indicators.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-avatar` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-avatar` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Avatar primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need to represent a user or entity identity via image, initials, or fallback glyph.\"\n    - \"You need a presence / status dot attached to the identity (online, away, busy, offline).\"\n    - \"You need a stack of overlapping identities (collaborators, attendees) via .ren-avatar-group.\"\n    - \"You need consistent circular (or .ren-avatar-square) framing across sizes xs / sm / md / lg / xl / 2xl.\"\n    - \"The image must crop to a fixed square with object-fit: cover regardless of source aspect ratio.\"\n  avoidWhen:\n    - \"You only need a status label or count without identity — use ren-badge / ren-badge-dot.\"\n    - \"You need a clickable identity menu / profile dropdown — wrap the avatar in ren-menu / ren-popover.\"\n    - \"You need a decorative logo or product icon — use a plain <img> or ren-icon.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-avatar/ren-avatar.css\"\n  notes:\n    - \"CSS-only primitive — no colocated JS exists. Do not import a ren-avatar.js.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Root is <div class=\\\"ren-avatar\\\"> containing either an <img> or text initials (max 2 chars); never both as siblings.\"\n  - \"<img> children must have alt=\\\"\\\" when the avatar is decorative, or alt=\\\"Name\\\" when it conveys identity.\"\n  - \"Status dot must be wrapped together with the avatar in <div class=\\\"ren-avatar-wrapper\\\"> with .ren-avatar-status as a sibling, not a child of .ren-avatar.\"\n  - \"Avatar groups use <div class=\\\"ren-avatar-group\\\"> with .ren-avatar children only (overlap is owned by the group, not by margins on items).\"\n  - \"Size modifiers (.ren-avatar-xs / -sm / -lg / -xl / -2xl) replace the default md size; do not stack two size classes.\"\n\nforbiddenPatterns:\n  - \"Background <img> via CSS background-image — always use a real <img> child for alt-text and lazy-loading.\"\n  - \"Hardcoded width / height in inline styles — use a size modifier or override --ren-avatar-size.\"\n  - \"Communicating presence with .ren-avatar-status alone without a visually-hidden text label (color is not a name).\"\n  - \"Nesting .ren-avatar inside .ren-avatar (e.g., avatar-on-avatar) — group overlap is the documented pattern.\"\n  - \"Custom border-radius via inline style — use .ren-avatar-square or override --ren-avatar-radius.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-avatar-bg, --ren-avatar-border, --ren-avatar-color, --ren-avatar-font-size, --ren-avatar-radius, --ren-avatar-size.\"\n    - \"Semantic tokens: --color-fill, --color-text-secondary, --color-surface, --color-success, --color-warning, --color-danger.\"\n    - \"Size aliases: --avatar-xs / -sm / -md / -lg / -xl / -2xl and --radius-full / --radius-md.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors for background, border, or status dot.\"\n    - \"Bypassing --color-surface for the status ring; the white/dark ring must adapt to theme.\"\n\naccessibility:\n  required:\n    - \"Provide a meaningful alt attribute on the <img> when the avatar identifies a specific person; use alt=\\\"\\\" only when the name is rendered elsewhere.\"\n    - \"Initials avatars must have an accessible name via aria-label on the .ren-avatar or visually-hidden text matching the user's full name.\"\n    - \"Status indicator must be accompanied by a textual label for screen readers (e.g., visually-hidden \\\"Online\\\") — color alone is not sufficient.\"\n    - \"Avatar groups should expose a roll-up count or list label (e.g., aria-label=\\\"3 collaborators\\\") rather than relying on visual stacking.\"\n    - \"Do not attach click handlers to a non-interactive .ren-avatar; wrap in a real <button> or <a> if the identity is actionable.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-avatar/ren-avatar.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div class=\"ren-avatar\"><img src=\"/avatars/jane.jpg\" alt=\"Jane Doe\"></div>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-avatar`\n- `.ren-avatar-2xl`\n- `.ren-avatar-group`\n- `.ren-avatar-lg`\n- `.ren-avatar-sm`\n- `.ren-avatar-square`\n- `.ren-avatar-status`\n- `.ren-avatar-status-away`\n- `.ren-avatar-status-busy`\n- `.ren-avatar-status-offline`\n- `.ren-avatar-wrapper`\n- `.ren-avatar-xl`\n- `.ren-avatar-xs`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-avatar-bg`\n- `--ren-avatar-border`\n- `--ren-avatar-color`\n- `--ren-avatar-font-size`\n- `--ren-avatar-radius`\n- `--ren-avatar-size`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-avatar/ren-avatar.css`\n- `docs/components/ren-avatar.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Avatar\n   ============================================\n   Circular (or square) user image with fallback\n   text, status indicator and group (overlap).\n\n   Usage:\n     <div class=\"ren-avatar\"><img src=\"…\" alt=\"\"></div>\n     <div class=\"ren-avatar ren-avatar-lg\">JD</div>\n\n     <div class=\"ren-avatar-wrapper\">\n       <div class=\"ren-avatar\"><img …></div>\n       <span class=\"ren-avatar-status\"></span>\n     </div>\n\n     <div class=\"ren-avatar-group\">\n       <div class=\"ren-avatar\">…</div>\n       <div class=\"ren-avatar\">…</div>\n     </div>\n   ============================================ */\n\n.ren-avatar {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: var(--ren-avatar-size);     /* 40px default */\n  height: var(--ren-avatar-size);\n  border-radius: var(--ren-avatar-radius);\n  overflow: hidden;\n  background-color: var(--ren-avatar-bg);\n  color: var(--ren-avatar-color);\n  font-weight: var(--weight-semibold);\n  font-size: var(--ren-avatar-font-size);\n  flex-shrink: 0;\n  user-select: none;\n}\n\n.ren-avatar > img {\n  width: 100%;\n  height: 100%;\n  object-fit: cover;\n}\n\n/* ─── Sizes ─── */\n.ren-avatar-xs  { width: var(--avatar-xs);  height: var(--avatar-xs);  font-size: var(--text-xs); }\n.ren-avatar-sm  { width: var(--avatar-sm);  height: var(--avatar-sm);  font-size: var(--text-xs); }\n.ren-avatar-lg  { width: var(--avatar-lg);  height: var(--avatar-lg);  font-size: var(--text-base); }\n.ren-avatar-xl  { width: var(--avatar-xl);  height: var(--avatar-xl);  font-size: var(--text-lg); }\n.ren-avatar-2xl { width: var(--avatar-2xl); height: var(--avatar-2xl); font-size: var(--text-xl); }\n\n/* ─── Square variant ─── */\n.ren-avatar-square {\n  border-radius: var(--radius-md);\n}\n\n/* ─── Status indicator ─── */\n.ren-avatar-wrapper {\n  position: relative;\n  display: inline-block;\n}\n\n.ren-avatar-status {\n  position: absolute;\n  bottom: 0;\n  right: 0;\n  width: 0.75rem;\n  height: 0.75rem;\n  border-radius: var(--radius-full);\n  border: 2px solid var(--color-surface);\n  background-color: var(--color-success);\n}\n\n.ren-avatar-status-offline { background-color: var(--gray-400); }\n.ren-avatar-status-busy    { background-color: var(--color-danger); }\n.ren-avatar-status-away    { background-color: var(--color-warning); }\n\n/* ─── Group (overlapping) ─── */\n.ren-avatar-group {\n  display: flex;\n  flex-direction: row-reverse;\n}\n\n.ren-avatar-group > .ren-avatar {\n  border: var(--ren-avatar-border);\n  margin-inline-start: -0.5rem;\n}\n\n.ren-avatar-group > .ren-avatar:last-child {\n  margin-inline-start: 0;\n}\n",
      "path": "components/primitives/ren-avatar",
      "id": "component:primitive:ren-avatar",
      "type": "component",
      "name": "ren-avatar"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-badge",
          ".ren-badge-danger",
          ".ren-badge-dot",
          ".ren-badge-dot-danger",
          ".ren-badge-dot-success",
          ".ren-badge-dot-warning",
          ".ren-badge-info",
          ".ren-badge-outline",
          ".ren-badge-primary",
          ".ren-badge-secondary",
          ".ren-badge-success",
          ".ren-badge-warning"
        ],
        "tokens": [
          "--color-accent",
          "--color-border",
          "--color-danger",
          "--color-danger-strong",
          "--color-danger-subtle",
          "--color-fill-hover",
          "--color-info-strong",
          "--color-info-subtle",
          "--color-on-accent",
          "--color-success",
          "--color-success-strong",
          "--color-success-subtle",
          "--color-text",
          "--color-text-secondary",
          "--color-warning",
          "--color-warning-strong",
          "--color-warning-subtle",
          "--radius-full",
          "--ren-badge-bg",
          "--ren-badge-color",
          "--ren-badge-font-size",
          "--ren-badge-font-weight",
          "--ren-badge-height",
          "--ren-badge-padding-x",
          "--ren-badge-radius",
          "--space-1",
          "--stroke-1"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-badge Component Contract\n\nSmall status, category, count, or state label.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-badge` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-badge` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Badge primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a small inline label for status (success / warning / danger / info), category, count, or tag.\"\n    - \"The label is decorative or status-only; it does not navigate or trigger a side effect.\"\n    - \"You need a presence dot without text (.ren-badge-dot) — color-coded indicator.\"\n    - \"You need a pill-shaped (radius-full) label sized in em so it scales with surrounding text.\"\n    - \"The label must sit inline inside a heading, list item, button, or table cell.\"\n  avoidWhen:\n    - \"The label is a removable chip with an X button — use ren-tag.\"\n    - \"The label is a persistent inline message with title/icon/dismiss — use ren-banner.\"\n    - \"The label triggers a click action — use ren-btn (or .ren-btn-sm) instead.\"\n    - \"You need an avatar / identity surface — use ren-avatar.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-badge/ren-badge.css\"\n  notes:\n    - \"CSS-only primitive — no colocated JS exists. Do not import a ren-badge.js.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Use <span class=\\\"ren-badge\\\"> (inline) — not <div> — so badges flow with surrounding text.\"\n  - \"Combine .ren-badge with a single variant modifier (.ren-badge-primary | -secondary | -success | -warning | -danger | -info | -outline); do not stack two variants.\"\n  - \"Status badges that convey meaning must include text (or visually-hidden text) — color alone is not sufficient.\"\n  - \"Dot indicators use <span class=\\\"ren-badge-dot ren-badge-dot-success\\\"> (empty content) and require a sibling text label.\"\n  - \"Numeric count badges should set aria-label (e.g., aria-label=\\\"3 unread\\\") when the number is ambiguous without context.\"\n\nforbiddenPatterns:\n  - \"<button class=\\\"ren-badge\\\"> — badges are not interactive; use ren-btn for actions.\"\n  - \"Hardcoded background / color overrides via inline style — override --ren-badge-bg / --ren-badge-color instead.\"\n  - \"Using .ren-badge as a removable chip with a close button — that is ren-tag's contract.\"\n  - \"Color-only status (.ren-badge-danger with no text and no aria-label).\"\n  - \"Setting font-size in px on .ren-badge — the em-based padding will desynchronize from the text scale.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-badge-bg, --ren-badge-color, --ren-badge-font-size, --ren-badge-font-weight, --ren-badge-height, --ren-badge-padding-x, --ren-badge-radius.\"\n    - \"Semantic tokens: --color-fill, --color-fill-hover, --color-accent, --color-on-accent, --color-success-subtle / -strong, --color-warning-subtle / -strong, --color-danger-subtle / -strong, --color-info-subtle / -strong, --color-border.\"\n    - \"Layout tokens: --space-1, --radius-full, --stroke-1, --text-xs, --weight-semibold.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors for badge background, text, or dot color.\"\n    - \"Reaching past the semantic -subtle / -strong pair into raw success / warning / danger primitives.\"\n\naccessibility:\n  required:\n    - \"Badges that communicate status must not rely on color alone — include text or visually-hidden labels.\"\n    - \"Numeric badges need aria-label when the meaning is ambiguous (e.g., \\\"3 unread messages\\\").\"\n    - \"Decorative dot badges (.ren-badge-dot) should be paired with adjacent text content — not used alone.\"\n    - \"Do not place a badge inside a focusable element without ensuring the badge itself is not announced twice; rely on the parent's accessible name.\"\n    - \"Maintain WCAG AA contrast: variants pair -subtle background with -strong text by design; do not invert.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-badge/ren-badge.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<span class=\"ren-badge ren-badge-success\">Published</span>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-badge`\n- `.ren-badge-danger`\n- `.ren-badge-dot`\n- `.ren-badge-dot-danger`\n- `.ren-badge-dot-success`\n- `.ren-badge-dot-warning`\n- `.ren-badge-info`\n- `.ren-badge-outline`\n- `.ren-badge-primary`\n- `.ren-badge-secondary`\n- `.ren-badge-success`\n- `.ren-badge-warning`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-badge-bg`\n- `--ren-badge-color`\n- `--ren-badge-font-size`\n- `--ren-badge-font-weight`\n- `--ren-badge-height`\n- `--ren-badge-padding-x`\n- `--ren-badge-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-badge/ren-badge.css`\n- `docs/components/ren-badge.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Badge\n   ============================================\n   Small inline label for status, counts, or tags.\n   CSS-only, no JS needed.\n\n   Sibling primitives that used to live here are\n   now in their own folders:\n     - ren-separator\n     - ren-avatar\n     - ren-spinner\n     - ren-skeleton\n     - ren-kbd\n\n   Usage:\n     <span class=\"ren-badge\">3</span>\n     <span class=\"ren-badge ren-badge-success\">Active</span>\n     <span class=\"ren-badge-dot ren-badge-dot-danger\"></span>\n   ============================================ */\n\n.ren-badge {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-1);\n  min-height: var(--ren-badge-height);\n  padding: 0.15em var(--ren-badge-padding-x);\n  font-size: var(--ren-badge-font-size);\n  font-weight: var(--ren-badge-font-weight);\n  line-height: 1.5;\n  white-space: nowrap;\n  border-radius: var(--ren-badge-radius);\n  /* Default: neutral */\n  background-color: var(--ren-badge-bg);\n  color: var(--ren-badge-color);\n}\n\n/* ─── Variants ─── */\n.ren-badge-primary {\n  background-color: var(--color-accent);\n  color: var(--color-on-accent);\n}\n\n.ren-badge-secondary {\n  background-color: var(--color-fill-hover);\n  color: var(--color-text);\n}\n\n.ren-badge-success {\n  background-color: var(--color-success-subtle);\n  color: var(--color-success-strong);\n}\n\n.ren-badge-warning {\n  background-color: var(--color-warning-subtle);\n  color: var(--color-warning-strong);\n}\n\n.ren-badge-danger {\n  background-color: var(--color-danger-subtle);\n  color: var(--color-danger-strong);\n}\n\n.ren-badge-info {\n  background-color: var(--color-info-subtle);\n  color: var(--color-info-strong);\n}\n\n/* Outline variant */\n.ren-badge-outline {\n  background-color: transparent;\n  border: var(--stroke-1) solid var(--color-border);\n  color: var(--color-text-secondary);\n}\n\n/* ─── Dot indicator (no text) ─── */\n.ren-badge-dot {\n  width: 0.5rem;\n  height: 0.5rem;\n  padding: 0;\n  border-radius: var(--radius-full);\n  background-color: var(--color-accent);\n}\n\n.ren-badge-dot-success { background-color: var(--color-success); }\n.ren-badge-dot-warning { background-color: var(--color-warning); }\n.ren-badge-dot-danger  { background-color: var(--color-danger); }\n",
      "path": "components/primitives/ren-badge",
      "id": "component:primitive:ren-badge",
      "type": "component",
      "name": "ren-badge"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-banner",
          ".ren-banner-actions",
          ".ren-banner-compact",
          ".ren-banner-content",
          ".ren-banner-danger",
          ".ren-banner-dismiss",
          ".ren-banner-full",
          ".ren-banner-icon",
          ".ren-banner-message",
          ".ren-banner-neutral",
          ".ren-banner-success",
          ".ren-banner-title",
          ".ren-banner-warning"
        ],
        "tokens": [
          "--body-size",
          "--caption-size",
          "--color-border",
          "--color-danger",
          "--color-danger-subtle",
          "--color-focus-ring",
          "--color-success",
          "--color-success-subtle",
          "--color-surface-sunken",
          "--color-text",
          "--color-text-muted",
          "--color-text-secondary",
          "--color-warning",
          "--color-warning-subtle",
          "--duration-state",
          "--duration-tactile",
          "--ease-enter",
          "--label-size",
          "--leading-normal",
          "--radius-sm",
          "--ren-banner-bg",
          "--ren-banner-border-color",
          "--ren-banner-border-width",
          "--ren-banner-color",
          "--ren-banner-gap",
          "--ren-banner-icon-size",
          "--ren-banner-padding",
          "--ren-banner-radius",
          "--ring-width",
          "--space-1",
          "--space-2",
          "--space-3",
          "--text-base",
          "--text-lg",
          "--text-sm",
          "--weight-semibold"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-banner Component Contract\n\nPersistent inline message for status, warning, or alert content.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-banner` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-banner` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Banner primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a persistent inline message that sits in page flow (success / warning / danger / info / neutral).\"\n    - \"The message has structured content: icon + title + message + optional actions or dismiss button.\"\n    - \"You need a page-top full-bleed announcement via .ren-banner-full (no border-radius, no side borders).\"\n    - \"You need a compact single-line variant for dense surfaces (.ren-banner-compact).\"\n    - \"The banner should remain on screen until user dismisses it or the underlying state changes.\"\n  avoidWhen:\n    - \"The message is transient and overlay-style — use ren-toast.\"\n    - \"The message blocks the entire flow and requires confirmation — use ren-dialog (alert).\"\n    - \"You only need a small inline status label — use ren-badge.\"\n    - \"The message is form validation tied to a single field — use ren-field's error slot.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-banner/ren-banner.css\"\n  notes:\n    - \"CSS-only primitive — no colocated JS exists. Dismiss button is plain HTML; if you wire dismissal, set data-dismissing to trigger the documented animation.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Root must carry role=\\\"status\\\" (info / success / neutral) or role=\\\"alert\\\" (danger / urgent warning) so AT announces correctly.\"\n  - \"Use the documented children: <span class=\\\"ren-banner-icon\\\">, <div class=\\\"ren-banner-content\\\"> with <strong class=\\\"ren-banner-title\\\"> + <p class=\\\"ren-banner-message\\\">.\"\n  - \"Dismiss button is <button class=\\\"ren-banner-dismiss\\\" aria-label=\\\"Dismiss\\\"> — never a <div> with click handler.\"\n  - \"Wrap any banner action buttons in <div class=\\\"ren-banner-actions\\\"> placed inside .ren-banner-content.\"\n  - \"To animate dismissal, set [data-dismissing] on the root; do not animate via inline styles.\"\n\nforbiddenPatterns:\n  - \"Using a banner as a transient toast that auto-dismisses without user action — wrong semantics.\"\n  - \"Hardcoded border / background colors per severity — use the .ren-banner-{success | warning | danger | neutral} variant.\"\n  - \"Stacking two severity variants on the same banner (e.g., .ren-banner-success.ren-banner-danger).\"\n  - \"Dismiss button without aria-label or visible text — the ✕ glyph alone is not an accessible name.\"\n  - \"Communicating severity with icon color only; the role attribute and surrounding text must convey severity to AT.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-banner-bg, --ren-banner-border-color, --ren-banner-border-width, --ren-banner-color, --ren-banner-gap, --ren-banner-icon-size, --ren-banner-padding, --ren-banner-radius.\"\n    - \"Semantic tokens: --color-info-subtle, --color-success-subtle, --color-warning-subtle, --color-danger-subtle, --color-surface-sunken, --color-border, --color-text, --color-text-secondary, --color-text-muted, --color-focus-ring.\"\n    - \"Motion tokens: --duration-tactile, --duration-state, --ease-enter.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors or hardcoded rgba border tints — use color-mix on semantic tokens as the CSS already does.\"\n    - \"Inline transition / animation values; use --duration-* / --ease-* tokens.\"\n\naccessibility:\n  required:\n    - \"Set role=\\\"alert\\\" for danger / urgent warning banners and role=\\\"status\\\" for non-urgent info / success / neutral.\"\n    - \"Dismiss button must have aria-label (or visible text) and a visible :focus-visible ring driven by --color-focus-ring.\"\n    - \".ren-banner-title renders as a real heading-like element (<strong>); when stacked among <h*> content, follow document outline.\"\n    - \"Respect prefers-reduced-motion: the [data-dismissing] animation collapses to display:none per the CSS — do not re-introduce motion.\"\n    - \"Banner content must not rely on color alone; the icon + title + message convey severity together.\"\n    - \"Action buttons inside .ren-banner-actions must be real <button> elements with their own keyboard focus order.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-banner/ren-banner.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div class=\"ren-banner ren-banner-info\" role=\"status\">\n  <span class=\"ren-banner-icon\" aria-hidden=\"true\">i</span>\n  <div class=\"ren-banner-content\"><strong class=\"ren-banner-title\">Maintenance</strong><p class=\"ren-banner-message\">Service resumes at 11 PM.</p></div>\n  <button class=\"ren-banner-dismiss\" type=\"button\" aria-label=\"Dismiss\">×</button>\n</div>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-banner`\n- `.ren-banner-actions`\n- `.ren-banner-compact`\n- `.ren-banner-content`\n- `.ren-banner-danger`\n- `.ren-banner-dismiss`\n- `.ren-banner-full`\n- `.ren-banner-icon`\n- `.ren-banner-message`\n- `.ren-banner-neutral`\n- `.ren-banner-success`\n- `.ren-banner-title`\n- `.ren-banner-warning`\n\n## States And Attributes\n\n- `[data-dismissing]`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-banner-bg`\n- `--ren-banner-border-color`\n- `--ren-banner-border-width`\n- `--ren-banner-color`\n- `--ren-banner-gap`\n- `--ren-banner-icon-size`\n- `--ren-banner-padding`\n- `--ren-banner-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-banner/ren-banner.css`\n- `docs/components/ren-banner.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Banner / Alert Component\n   ============================================\n   Inline messages for success, warning, error, info.\n   CSS-only. No JS needed (dismiss is optional JS).\n\n   Different from Toast: Banner is inline, persistent,\n   part of the page flow. Toast is ephemeral overlay.\n\n   Usage:\n     <div class=\"ren-banner\" role=\"status\">\n       <span class=\"ren-banner-icon\">ℹ</span>\n       <div class=\"ren-banner-content\">\n         <strong class=\"ren-banner-title\">Info</strong>\n         <p class=\"ren-banner-message\">Your changes have been saved.</p>\n       </div>\n     </div>\n\n     <div class=\"ren-banner ren-banner-danger\" role=\"alert\">\n       <span class=\"ren-banner-icon\">!</span>\n       <div class=\"ren-banner-content\">\n         <strong class=\"ren-banner-title\">Error</strong>\n         <p class=\"ren-banner-message\">Could not save changes.</p>\n       </div>\n       <button class=\"ren-banner-dismiss\" aria-label=\"Dismiss\">&times;</button>\n     </div>\n\n   Simple (no title):\n     <div class=\"ren-banner ren-banner-success\" role=\"status\">\n       <span class=\"ren-banner-icon\">✓</span>\n       <p class=\"ren-banner-message\">File uploaded successfully.</p>\n     </div>\n   ============================================ */\n\n/* ─── Base Banner ─── */\n.ren-banner {\n  display: flex;\n  align-items: flex-start;\n  gap: var(--ren-banner-gap);\n  padding: var(--ren-banner-padding);\n  border-radius: var(--ren-banner-radius);\n  font-size: var(--body-size);\n  line-height: var(--leading-normal);\n  /* Default: info */\n  background-color: var(--ren-banner-bg);\n  border: var(--ren-banner-border-width) solid var(--ren-banner-border-color);\n  color: var(--ren-banner-color);\n}\n\n/* ─── Icon ─── */\n.ren-banner-icon {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n  width: var(--ren-banner-icon-size);\n  height: var(--ren-banner-icon-size);\n  font-size: var(--text-base);\n  margin-top: 1px;\n}\n\n/* ─── Content ─── */\n.ren-banner-content {\n  flex: 1;\n  min-width: 0;\n}\n\n.ren-banner-title {\n  display: block;\n  font-weight: var(--weight-semibold);\n  font-size: var(--label-size, var(--text-sm));\n  margin: 0 0 var(--space-1);\n}\n\n.ren-banner-message {\n  font-size: var(--caption-size, var(--text-sm));\n  color: var(--color-text-secondary);\n  margin: 0;\n}\n\n/* Title-only (no message) */\n.ren-banner-title:last-child {\n  margin-bottom: 0;\n}\n\n/* ─── Actions (buttons inside banner) ─── */\n.ren-banner-actions {\n  display: flex;\n  gap: var(--space-2);\n  margin-top: var(--space-2);\n}\n\n/* ─── Dismiss button ─── */\n.ren-banner-dismiss {\n  flex-shrink: 0;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 1.5rem;\n  height: 1.5rem;\n  padding: 0;\n  border: none;\n  background: none;\n  color: var(--color-text-muted);\n  cursor: pointer;\n  border-radius: var(--radius-sm);\n  font-size: var(--text-lg);\n  line-height: 1;\n  transition: color var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-banner-dismiss:hover {\n  color: var(--color-text);\n}\n\n.ren-banner-dismiss:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: 2px;\n}\n\n/* ─── Variants ─── */\n\n.ren-banner-success {\n  background-color: var(--color-success-subtle);\n  border-color: color-mix(in oklch, var(--color-success), transparent 70%);\n}\n\n.ren-banner-warning {\n  background-color: var(--color-warning-subtle);\n  border-color: color-mix(in oklch, var(--color-warning), transparent 70%);\n}\n\n.ren-banner-danger {\n  background-color: var(--color-danger-subtle);\n  border-color: color-mix(in oklch, var(--color-danger), transparent 70%);\n}\n\n.ren-banner-neutral {\n  background-color: var(--color-surface-sunken);\n  border-color: var(--color-border);\n}\n\n/* ─── Compact variant (less padding, single line) ─── */\n.ren-banner-compact {\n  align-items: center;\n  padding: var(--space-2) var(--space-3);\n  gap: var(--space-2);\n}\n\n.ren-banner-compact .ren-banner-icon {\n  width: 1.25rem;\n  height: 1.25rem;\n  font-size: var(--text-sm);\n}\n\n/* ─── Full-width variant (no border-radius, for page top) ─── */\n.ren-banner-full {\n  border-radius: 0;\n  border-inline-start: none;\n  border-inline-end: none;\n}\n\n/* ─── Dismiss animation ─── */\n.ren-banner[data-dismissing] {\n  animation: ren-banner-dismiss var(--duration-state) var(--ease-enter) forwards;\n}\n\n@keyframes ren-banner-dismiss {\n  to {\n    opacity: 0;\n    height: 0;\n    padding-block: 0;\n    margin-block: 0;\n    overflow: hidden;\n  }\n}\n\n/* Reduced motion */\n@media (prefers-reduced-motion: reduce) {\n  .ren-banner[data-dismissing] {\n    animation: none;\n    display: none;\n  }\n}\n",
      "path": "components/primitives/ren-banner",
      "id": "component:primitive:ren-banner",
      "type": "component",
      "name": "ren-banner"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-breadcrumb",
          ".ren-breadcrumb-truncated"
        ],
        "tokens": [
          "--color-focus-ring",
          "--color-text",
          "--color-text-faint",
          "--duration-tactile",
          "--ease-enter",
          "--radius-sm",
          "--ren-breadcrumb-active-color",
          "--ren-breadcrumb-color",
          "--ren-breadcrumb-font-size",
          "--ren-breadcrumb-gap",
          "--ren-breadcrumb-separator",
          "--ring-width",
          "--space-1",
          "--weight-medium"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-breadcrumb Component Contract\n\nHierarchical navigation trail built on native navigation/list semantics.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-breadcrumb` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-breadcrumb` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Breadcrumb primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a hierarchical trail that shows the user's location within a site / app tree.\"\n    - \"The trail has 2+ ancestor levels and the final crumb represents the current page.\"\n    - \"You want native <nav> + <ol> semantics with the \\\"Breadcrumb\\\" landmark announcement.\"\n    - \"You need a customizable separator (default ›) via the --breadcrumb-separator custom property.\"\n    - \"Deep paths must collapse middle entries to an ellipsis via .ren-breadcrumb-truncated.\"\n  avoidWhen:\n    - \"The navigation is the primary site nav — use ren-nav.\"\n    - \"The navigation is a tabbed page section — use ren-tabs.\"\n    - \"The navigation is a multi-step wizard — use ren-steps / ren-progress.\"\n    - \"You only need a single \\\"Back\\\" link — use ren-link with an arrow icon.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-breadcrumb/ren-breadcrumb.css\"\n  notes:\n    - \"CSS-only primitive — no colocated JS exists. Do not import a ren-breadcrumb.js.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Root must be <nav aria-label=\\\"Breadcrumb\\\" class=\\\"ren-breadcrumb\\\"> — the aria-label is what AT announces.\"\n  - \"Items are wrapped in an ordered list: <ol> with <li> children; do not use <ul> or plain <div>s.\"\n  - \"Each crumb is an <a href=\\\"...\\\"> except the current page, which uses aria-current=\\\"page\\\" and may stay as <a> (pointer-events disabled by CSS) or render as plain text.\"\n  - \"Separators are CSS-generated via ::before — never insert literal \\\"/\\\" or \\\"›\\\" characters as text nodes.\"\n  - \"Override the separator per-instance with inline style=\\\"--breadcrumb-separator: '/'\\\" — do not edit the CSS.\"\n\nforbiddenPatterns:\n  - \"Using <div class=\\\"ren-breadcrumb\\\"> without <nav> — strips the landmark.\"\n  - \"Marking multiple crumbs with aria-current=\\\"page\\\" — only the final crumb is current.\"\n  - \"Inserting separator glyphs as text content inside <li> — the ::before pseudo handles it.\"\n  - \"Hardcoded font-size or color overrides via inline style — use the --ren-breadcrumb-* tokens.\"\n  - \"Wrapping crumb links in ren-link / ren-btn — breadcrumb owns its own link chrome.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-breadcrumb-active-color, --ren-breadcrumb-color, --ren-breadcrumb-font-size, --ren-breadcrumb-gap, --ren-breadcrumb-separator.\"\n    - \"Semantic tokens: --color-text, --color-text-muted, --color-text-faint, --color-focus-ring.\"\n    - \"Layout / motion tokens: --space-1, --radius-sm, --duration-tactile, --ease-enter, --ring-width, --caption-size, --text-sm.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors for link / current / separator styling.\"\n    - \"Overriding text-decoration with !important — let :hover and aria-current rules drive it.\"\n\naccessibility:\n  required:\n    - \"Root <nav> must carry aria-label=\\\"Breadcrumb\\\" (translated per locale) so AT announces the landmark.\"\n    - \"Current page crumb uses aria-current=\\\"page\\\"; CSS disables pointer-events but the semantic state is what matters for AT.\"\n    - \"Crumb links must have visible :focus-visible outline driven by --color-focus-ring and --ring-width.\"\n    - \"Separators are decorative (CSS ::before) so they are not announced — never put separator text in the DOM.\"\n    - \"When using .ren-breadcrumb-truncated, the hidden middle crumbs are display:none — ensure the ellipsis is purely decorative and provide a way to expand if the hidden context matters.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-breadcrumb/ren-breadcrumb.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\"><ol><li><a href=\"/\">Home</a></li><li aria-current=\"page\">Settings</li></ol></nav>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-breadcrumb`\n- `.ren-breadcrumb-truncated`\n\n## States And Attributes\n\n- `[aria-current]`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-breadcrumb-active-color`\n- `--ren-breadcrumb-color`\n- `--ren-breadcrumb-font-size`\n- `--ren-breadcrumb-gap`\n- `--ren-breadcrumb-separator`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-breadcrumb/ren-breadcrumb.css`\n- `docs/components/ren-breadcrumb.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Breadcrumb Component\n   ============================================\n   Hierarchical navigation trail.\n   CSS-only. No JS needed.\n\n   Uses semantic <nav> + <ol> for accessibility.\n   Screen readers announce \"Breadcrumb\" navigation.\n\n   Usage:\n     <nav aria-label=\"Breadcrumb\" class=\"ren-breadcrumb\">\n       <ol>\n         <li><a href=\"/\">Home</a></li>\n         <li><a href=\"/products\">Products</a></li>\n         <li><a href=\"/products/shoes\" aria-current=\"page\">Shoes</a></li>\n       </ol>\n     </nav>\n\n   With custom separator:\n     <nav aria-label=\"Breadcrumb\" class=\"ren-breadcrumb\"\n          style=\"--breadcrumb-separator: '/'\">\n       ...\n     </nav>\n   ============================================ */\n\n.ren-breadcrumb > ol {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: var(--ren-breadcrumb-gap);\n  list-style: none;\n  padding: 0;\n  margin: 0;\n  font-size: var(--ren-breadcrumb-font-size);\n}\n\n.ren-breadcrumb > ol > li {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--ren-breadcrumb-gap);\n  color: var(--ren-breadcrumb-color);\n}\n\n/* Separator */\n.ren-breadcrumb > ol > li + li::before {\n  content: var(--ren-breadcrumb-separator);\n  color: var(--color-text-faint);\n  font-size: 1.1em;\n  margin-inline-end: var(--space-1);\n}\n\n/* Links */\n.ren-breadcrumb > ol a {\n  color: var(--ren-breadcrumb-color);\n  text-decoration: none;\n  padding: var(--space-1) 0;\n  border-radius: var(--radius-sm);\n  transition: color var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-breadcrumb > ol a:hover {\n  color: var(--color-text);\n  text-decoration: underline;\n  text-underline-offset: 0.15em;\n}\n\n.ren-breadcrumb > ol a:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: 2px;\n}\n\n/* Current page (last item) */\n.ren-breadcrumb [aria-current=\"page\"] {\n  color: var(--ren-breadcrumb-active-color);\n  font-weight: var(--weight-medium);\n  text-decoration: none;\n  pointer-events: none;\n}\n\n/* ─── Truncated (for deep paths) ─── */\n.ren-breadcrumb-truncated > ol > li:not(:first-child):not(:last-child):not(:nth-last-child(2)) {\n  display: none;\n}\n\n/* Show ellipsis in place of hidden items */\n.ren-breadcrumb-truncated > ol > li:nth-child(2)::after {\n  content: '…';\n  color: var(--color-text-faint);\n  padding: 0 var(--space-1);\n}\n",
      "path": "components/primitives/ren-breadcrumb",
      "id": "component:primitive:ren-breadcrumb",
      "type": "component",
      "name": "ren-breadcrumb"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-btn",
          ".ren-btn-accent",
          ".ren-btn-danger",
          ".ren-btn-full",
          ".ren-btn-ghost",
          ".ren-btn-group",
          ".ren-btn-icon",
          ".ren-btn-lg",
          ".ren-btn-link",
          ".ren-btn-outline",
          ".ren-btn-primary",
          ".ren-btn-secondary",
          ".ren-btn-sm"
        ],
        "tokens": [
          "--_active",
          "--_hover",
          "--body-size",
          "--btn-accent",
          "--color-accent",
          "--color-accent-subtle",
          "--color-border",
          "--color-danger",
          "--color-disabled-bg",
          "--color-disabled-text",
          "--color-fill",
          "--color-fill-active",
          "--color-fill-hover",
          "--color-on-accent",
          "--color-on-danger",
          "--color-text",
          "--color-text-link",
          "--color-text-link-hover",
          "--duration-tactile",
          "--ease-enter",
          "--label-size",
          "--radius-full",
          "--radius-lg",
          "--radius-md",
          "--radius-sm",
          "--red-500",
          "--red-600",
          "--ren-btn-bg",
          "--ren-btn-bg-active",
          "--ren-btn-bg-hover",
          "--ren-btn-border-color",
          "--ren-btn-border-width",
          "--ren-btn-color",
          "--ren-btn-duration",
          "--ren-btn-easing",
          "--ren-btn-font-size",
          "--ren-btn-font-weight",
          "--ren-btn-gap",
          "--ren-btn-height",
          "--ren-btn-padding-x",
          "--ren-btn-padding-y",
          "--ren-btn-radius",
          "--ren-btn-ring-color",
          "--ren-btn-ring-offset",
          "--ren-btn-ring-width",
          "--size-lg",
          "--size-sm",
          "--size-xl",
          "--space-1",
          "--space-3",
          "--space-6",
          "--stroke-1"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-button\n\nAction primitive for commands, form submissions, links styled as controls,\nicon-only triggers, and loading states.\n\nLoad this file after `ren-design.md` and before generating, editing, or\nreviewing any `ren-btn` / `<ren-button>` UI.\n\n## Purpose\n\nThe single touch-target primitive in RenDS. Owns visible action affordances:\nsolid CTAs, secondary commands, ghosted toolbar actions, link-styled inline\nactions, and icon-only buttons. Does not own form inputs, navigation, or\ndialog launchers — those compose `ren-button` inside their own contracts.\n\n## Use When\n\n- The element is a clickable command: submit, save, delete, open modal,\n  toggle, fire any imperative action.\n- An anchor needs to *look* like a button (use `.ren-btn` on `<a>` only when\n  the link semantically replaces a button — otherwise use `ren-link`).\n- A trigger is part of a composite (`ren-dialog` opener, `ren-menu` trigger,\n  `ren-popover` anchor); the inner element should be `<button class=\"ren-btn\">`.\n- A loading / busy state must be reflected in markup (`data-loading`).\n\n## Do Not Use When\n\n- The control is a form input (use `ren-field` + native input).\n- The control is plain navigation (use `ren-link` or a real `<a>`).\n- The control is a checkbox / radio / toggle (use `ren-checkbox`,\n  `ren-radio`, `ren-toggle-group`).\n- The element is a styled label or status (use `ren-badge` or `ren-tag`).\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User action triggers an imperative side effect (submit, save, delete, open dialog).\"\n    - \"Need primary / secondary / destructive emphasis tied to the same touch-target shape.\"\n    - \"Need a 44px-minimum touch target with a visible focus ring.\"\n    - \"Need a loading / busy affordance via data-loading + aria-busy.\"\n    - \"Need an icon-only control with an accessible name (.ren-btn-icon + aria-label).\"\n  avoidWhen:\n    - \"The element is purely navigational — use ren-link.\"\n    - \"The element is a form input — use ren-field with a native input.\"\n    - \"The element is a stateful selector (checkbox, radio, switch, toggle).\"\n    - \"You only need text emphasis or a status pill — use ren-badge / ren-tag.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-button/ren-button.css\"\n  js:\n    - \"rends/components/primitives/ren-button/ren-button.js\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"JS is only required when using <ren-button> attributes (loading, variant, size, icon, full); plain <button class=\\\"ren-btn\\\"> works without JS.\"\n\nrequiredMarkup:\n  - \"Always render an actual <button> (or <ren-button>) element. Never style a <div> as a button.\"\n  - \"Icon-only buttons must include an accessible name via aria-label or visually-hidden text.\"\n  - \"Loading buttons must set data-loading and aria-busy=\\\"true\\\" on the same node.\"\n  - \"Use type=\\\"button\\\" for non-submit buttons inside <form> to avoid accidental submission.\"\n\nforbiddenPatterns:\n  - \"div / span with a click handler styled as a button.\"\n  - \"<button style=\\\"background: #...\\\"> — use --ren-btn-bg.\"\n  - \"Removing the focus ring (outline: none) without restoring a visible :focus-visible style.\"\n  - \"Custom danger styling that overrides --color-danger with a primitive --red-* token.\"\n  - \"Disabling via aria-disabled alone without preventing the click handler.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic tokens: --color-accent, --color-on-accent, --color-danger, --color-text-link, --color-focus-ring.\"\n    - \"Component tokens: every --ren-btn-* listed in Public Token API.\"\n    - \"Layout / size tokens: --space-*, --size-*, --radius-*.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, etc.) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Raw transition values; use --ren-btn-duration / --ren-btn-easing.\"\n\naccessibility:\n  required:\n    - \"Real <button> semantics; rely on native click / Enter / Space.\"\n    - \"Visible :focus-visible ring driven by --ren-btn-ring-* tokens.\"\n    - \"Touch target ≥ 44px (default size). Smaller (.ren-btn-sm = 32px) only for non-touch contexts.\"\n    - \"Icon-only buttons have aria-label or visually-hidden label text.\"\n    - \"Loading buttons expose aria-busy=\\\"true\\\" and ignore activation while busy.\"\n    - \"Disabled buttons set both `disabled` (real) or `aria-disabled=\\\"true\\\"` AND prevent activation in the handler.\"\n```\n\n## Required CSS / JS Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-button/ren-button.css\">\n<script type=\"module\" src=\"rends/components/primitives/ren-button/ren-button.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the\nCSS again. JS is only required when consuming `<ren-button>` attributes.\n\n## Canonical Markup\n\nPrimary action button:\n\n```html\n<button class=\"ren-btn\">Save changes</button>\n```\n\nSecondary + small:\n\n```html\n<button class=\"ren-btn ren-btn-secondary ren-btn-sm\" type=\"button\">Cancel</button>\n```\n\nDestructive with loading state:\n\n```html\n<button class=\"ren-btn ren-btn-danger\" data-loading aria-busy=\"true\" disabled>\n  Deleting...\n</button>\n```\n\nIcon-only with accessible name:\n\n```html\n<button class=\"ren-btn ren-btn-icon\" aria-label=\"Close dialog\">\n  <svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\">...</svg>\n</button>\n```\n\nWeb component form (attribute-driven state):\n\n```html\n<ren-button variant=\"secondary\" size=\"sm\">Cancel</ren-button>\n<ren-button variant=\"primary\" loading>Submitting</ren-button>\n```\n\n## Variants\n\n| Class                  | Role                                |\n|------------------------|-------------------------------------|\n| `.ren-btn`             | Default primary CTA.                |\n| `.ren-btn-primary`     | Same as default; explicit.          |\n| `.ren-btn-secondary`   | Tonal / fill-style secondary.       |\n| `.ren-btn-outline`     | Border-only, transparent fill.      |\n| `.ren-btn-ghost`       | No fill, no border; text + hover.   |\n| `.ren-btn-link`        | Inline text-style action.           |\n| `.ren-btn-danger`      | Destructive emphasis.               |\n| `.ren-btn-accent`      | Custom accent via `--btn-accent`.   |\n| `.ren-btn-icon`        | Square icon-only target.            |\n| `.ren-btn-full`        | Stretches to container width.       |\n| `.ren-btn-group`       | Joined sibling buttons.             |\n| `.ren-btn-sm` / `-lg`  | Size override (32px / 52px).        |\n\n## States\n\n| Selector / attr        | Meaning                                                |\n|------------------------|--------------------------------------------------------|\n| `:hover`               | Pointer is over an interactive button.                 |\n| `:active`              | Pressed state.                                         |\n| `:focus-visible`       | Keyboard focus; renders the ring.                      |\n| `:disabled`            | Native disabled; non-interactive, in tab order off.    |\n| `[aria-disabled=\"true\"]` | ARIA-disabled; visually disabled but stays focusable.|\n| `[data-loading]`       | Spinner replaces label; pair with `aria-busy=\"true\"`.  |\n\n## Public Token API\n\nOverride these on `:root`, `[data-theme]`, or a scope wrapping the button.\nDo **not** override the internal `.ren-btn` class.\n\n- `--ren-btn-bg`\n- `--ren-btn-bg-hover`\n- `--ren-btn-bg-active`\n- `--ren-btn-color`\n- `--ren-btn-border-color`\n- `--ren-btn-border-width`\n- `--ren-btn-radius`\n- `--ren-btn-height`\n- `--ren-btn-padding-x`\n- `--ren-btn-padding-y`\n- `--ren-btn-gap`\n- `--ren-btn-font-size`\n- `--ren-btn-font-weight`\n- `--ren-btn-ring-width`\n- `--ren-btn-ring-offset`\n- `--ren-btn-ring-color`\n- `--ren-btn-duration`\n- `--ren-btn-easing`\n\n## Accessibility Contract\n\n- Use a real `<button>` (or `<ren-button>` which renders a button internally).\n- Keep a visible focus ring driven by `--ren-btn-ring-*`.\n- Maintain a 44px minimum touch target (default size). `ren-btn-sm` (32px)\n  is reserved for non-touch contexts (toolbars, dense desktop UI).\n- Icon-only buttons must have an accessible name via `aria-label` or visually\n  hidden text inside the button.\n- Loading buttons expose `aria-busy=\"true\"`. The component sets it\n  automatically when `loading` is the chosen attribute on `<ren-button>`.\n- Do not communicate state through color alone — `ren-btn-danger` carries the\n  variant in the class, not just the hue.\n\n## Anti-Patterns\n\n- ❌ `<div class=\"ren-btn\" onclick=\"...\">` — must be a real `<button>`.\n- ❌ Inline hex colors: `<button style=\"background:#0066ff;\">`.\n- ❌ Overriding `.ren-btn { background: ... !important }` — use\n  `--ren-btn-bg` on a parent scope.\n- ❌ Mixing `disabled` and a click handler that still fires (the handler\n  must early-return or be detached).\n- ❌ Using `ren-btn-link` when the destination is a navigation URL — use\n  `ren-link` instead.\n\n## Related Files\n\n- `components/primitives/ren-button/ren-button.css`\n- `components/primitives/ren-button/ren-button.js`\n- `docs/components/ren-button.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, ARIA, or states change.\n- Run `npm run lint` after token / selector changes.\n- Manually verify light/dark themes when surface, border, or shadow values\n  change — focus rings must remain visible in both modes.\n\n\n/* ============================================\n   RenDS — Button Component\n   ============================================\n   Accessible, multi-variant button.\n   Works as pure CSS classes OR as <ren-button>.\n\n   Variants: primary, secondary, ghost, danger, outline\n   Sizes: sm, md (default), lg\n   States: hover, active, disabled, loading\n\n   Touch target: 44px minimum (Apple HIG)\n   ============================================ */\n\n/* ─── Base Button ─── */\n.ren-btn {\n  /* Layout */\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--ren-btn-gap);\n\n  /* Sizing — default md */\n  min-height: var(--ren-btn-height);        /* 44px touch target */\n  padding: var(--ren-btn-padding-y) var(--ren-btn-padding-x);\n  font-size: var(--ren-btn-font-size);\n  font-weight: var(--ren-btn-font-weight);\n  line-height: 1;\n\n  /* Appearance */\n  border: var(--ren-btn-border-width) solid var(--ren-btn-border-color);\n  border-radius: var(--ren-btn-radius);\n  cursor: pointer;\n  user-select: none;\n  white-space: nowrap;\n  text-decoration: none;\n\n  /* Transition — intent: tactile feedback on hover/press/focus */\n  transition:\n    background-color var(--ren-btn-duration) var(--ren-btn-easing),\n    border-color var(--ren-btn-duration) var(--ren-btn-easing),\n    color var(--ren-btn-duration) var(--ren-btn-easing),\n    box-shadow var(--ren-btn-duration) var(--ren-btn-easing),\n    transform var(--duration-tactile) var(--ease-enter),\n    opacity var(--ren-btn-duration) var(--ren-btn-easing);\n}\n\n/* Active press effect */\n.ren-btn:active:not(:disabled):not([aria-disabled=\"true\"]) {\n  transform: scale(0.97);\n}\n\n/* ─── Variants ─── */\n\n/* Primary (default) */\n.ren-btn:not(.ren-btn-secondary):not(.ren-btn-ghost):not(.ren-btn-outline):not(.ren-btn-danger):not(.ren-btn-link):not(.ren-btn-accent),\n.ren-btn-primary {\n  background-color: var(--ren-btn-bg);\n  color: var(--ren-btn-color);\n  border-color: var(--ren-btn-border-color);\n}\n\n.ren-btn:not(.ren-btn-secondary):not(.ren-btn-ghost):not(.ren-btn-outline):not(.ren-btn-danger):not(.ren-btn-link):not(.ren-btn-accent):hover:not(:disabled):not([aria-disabled=\"true\"]),\n.ren-btn-primary:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--ren-btn-bg-hover);\n  border-color: var(--ren-btn-bg-hover);\n}\n\n.ren-btn:not(.ren-btn-secondary):not(.ren-btn-ghost):not(.ren-btn-outline):not(.ren-btn-danger):not(.ren-btn-link):not(.ren-btn-accent):active:not(:disabled):not([aria-disabled=\"true\"]),\n.ren-btn-primary:active:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--ren-btn-bg-active);\n  border-color: var(--ren-btn-bg-active);\n}\n\n/* Secondary */\n.ren-btn-secondary {\n  background-color: var(--color-fill);\n  color: var(--color-text);\n  border-color: var(--color-border);\n}\n\n.ren-btn-secondary:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--color-fill-hover);\n}\n\n.ren-btn-secondary:active:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--color-fill-active);\n}\n\n/* Ghost (no background) */\n.ren-btn-ghost {\n  background-color: transparent;\n  color: var(--color-text);\n  border-color: transparent;\n}\n\n.ren-btn-ghost:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--color-fill);\n}\n\n.ren-btn-ghost:active:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--color-fill-hover);\n}\n\n/* Outline */\n.ren-btn-outline {\n  background-color: transparent;\n  color: var(--color-accent);\n  border-color: var(--color-accent);\n}\n\n.ren-btn-outline:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--color-accent-subtle);\n}\n\n.ren-btn-outline:active:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--color-accent);\n  color: var(--color-on-accent);\n}\n\n/* Danger */\n.ren-btn-danger {\n  background-color: var(--color-danger);\n  color: var(--color-on-danger);\n  border-color: var(--color-danger);\n}\n\n.ren-btn-danger:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--red-500);\n  border-color: var(--red-500);\n}\n\n.ren-btn-danger:active:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--red-600);\n  border-color: var(--red-600);\n}\n\n/* Link style */\n.ren-btn-link {\n  background-color: transparent;\n  color: var(--color-text-link);\n  border-color: transparent;\n  padding-inline: var(--space-1);\n  min-height: auto;\n  text-decoration: underline;\n  text-underline-offset: 0.15em;\n}\n\n.ren-btn-link:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n  color: var(--color-text-link-hover);\n  text-decoration-thickness: 2px;\n}\n\n/* ─── Sizes ─── */\n\n.ren-btn-sm {\n  min-height: var(--size-sm);       /* 32px */\n  padding: var(--space-1) var(--space-3);\n  font-size: var(--label-size);\n  border-radius: var(--radius-sm);\n  gap: var(--space-1);\n}\n\n.ren-btn-lg {\n  min-height: var(--size-xl);       /* 52px */\n  padding: var(--space-3) var(--space-6);\n  font-size: var(--body-size);\n  border-radius: var(--radius-lg);\n}\n\n/* ─── Icon Button (square) ─── */\n.ren-btn-icon {\n  min-width: var(--size-lg);        /* 44px */\n  padding: 0;\n  aspect-ratio: 1;\n}\n\n.ren-btn-icon.ren-btn-sm {\n  min-width: var(--size-sm);\n  min-height: var(--size-sm);\n}\n\n.ren-btn-icon.ren-btn-lg {\n  min-width: var(--size-xl);\n  min-height: var(--size-xl);\n}\n\n/* ─── Full Width ─── */\n.ren-btn-full {\n  width: 100%;\n}\n\n/* ─── Button Group ─── */\n.ren-btn-group {\n  display: inline-flex;\n  isolation: isolate;  /* contain z-index stacking */\n}\n\n.ren-btn-group > .ren-btn {\n  border-radius: 0;\n  position: relative;\n}\n\n.ren-btn-group > .ren-btn:first-child {\n  border-start-start-radius: var(--radius-md);\n  border-end-start-radius: var(--radius-md);\n}\n\n.ren-btn-group > .ren-btn:last-child {\n  border-start-end-radius: var(--radius-md);\n  border-end-end-radius: var(--radius-md);\n}\n\n.ren-btn-group > .ren-btn + .ren-btn {\n  margin-inline-start: calc(var(--stroke-1) * -1);\n}\n\n/* Hovered/focused button rises above siblings so border isn't hidden */\n.ren-btn-group > .ren-btn:hover,\n.ren-btn-group > .ren-btn:focus-visible {\n  z-index: 1;\n}\n\n/* ─── States ─── */\n\n/* Disabled */\n.ren-btn:disabled,\n.ren-btn[aria-disabled=\"true\"] {\n  background-color: var(--color-disabled-bg);\n  color: var(--color-disabled-text);\n  border-color: var(--color-disabled-bg);\n  cursor: not-allowed;\n  transform: none;\n  box-shadow: none;\n}\n\n/* Loading */\n.ren-btn[data-loading] {\n  position: relative;\n  color: transparent !important;\n  pointer-events: none;\n}\n\n.ren-btn[data-loading]::after {\n  content: '';\n  position: absolute;\n  width: 1.2em;\n  height: 1.2em;\n  border: 2px solid currentColor;\n  border-inline-end-color: transparent;\n  border-radius: var(--radius-full);\n  animation: ren-btn-spin 600ms linear infinite;\n  /* Restore color since parent is transparent */\n  color: var(--color-on-accent);\n}\n\n.ren-btn-secondary[data-loading]::after,\n.ren-btn-ghost[data-loading]::after,\n.ren-btn-outline[data-loading]::after {\n  color: var(--color-text);\n}\n\n@keyframes ren-btn-spin {\n  to { transform: rotate(360deg); }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-btn[data-loading]::after {\n    animation-duration: 1.5s;\n  }\n  .ren-btn:active:not(:disabled):not([aria-disabled=\"true\"]) {\n    transform: none;\n  }\n}\n\n/* ─── Custom Accent (color-mix pattern) ───\n   Use a single --btn-accent color and derive hover/active automatically.\n   Example: <button class=\"ren-btn ren-btn-accent\" style=\"--btn-accent: #8B5CF6\">Purple</button>\n*/\n.ren-btn-accent {\n  --_hover: color-mix(in oklch, var(--btn-accent, var(--color-accent)), black 12%);\n  --_active: color-mix(in oklch, var(--btn-accent, var(--color-accent)), black 22%);\n  --_subtle: color-mix(in oklch, var(--btn-accent, var(--color-accent)), white 90%);\n\n  background-color: var(--btn-accent, var(--color-accent));\n  color: var(--color-on-accent);\n  border-color: var(--btn-accent, var(--color-accent));\n}\n\n.ren-btn-accent:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--_hover);\n  border-color: var(--_hover);\n}\n\n.ren-btn-accent:active:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--_active);\n  border-color: var(--_active);\n}\n\n/* ─── Focus ─── */\n.ren-btn:focus-visible {\n  outline: var(--ren-btn-ring-width) solid var(--ren-btn-ring-color);\n  outline-offset: var(--ren-btn-ring-offset);\n}\n\n\n/**\n * RenDS — <ren-button> Web Component\n * ====================================\n * Enhanced button with loading state, variants, and\n * proper accessibility out of the box.\n *\n * Uses Light DOM — no Shadow DOM needed for a button.\n * The CSS classes work standalone too.\n *\n * Attributes:\n *   variant:  primary | secondary | ghost | outline | danger | link\n *   size:     sm | md | lg\n *   loading:  boolean — shows spinner, disables interaction\n *   disabled: boolean — standard disabled state\n *   icon:     boolean — square icon button\n *   full:     boolean — full width\n *\n * Usage:\n *   <ren-button>Click me</ren-button>\n *   <ren-button variant=\"secondary\" size=\"sm\">Cancel</ren-button>\n *   <ren-button variant=\"danger\" loading>Deleting...</ren-button>\n *   <ren-button icon aria-label=\"Close\">✕</ren-button>\n */\n\nconst VARIANT_MAP = {\n  primary: 'ren-btn',\n  secondary: 'ren-btn ren-btn-secondary',\n  ghost: 'ren-btn ren-btn-ghost',\n  outline: 'ren-btn ren-btn-outline',\n  danger: 'ren-btn ren-btn-danger',\n  link: 'ren-btn ren-btn-link',\n};\n\nconst SIZE_MAP = {\n  sm: 'ren-btn-sm',\n  md: '',\n  lg: 'ren-btn-lg',\n};\n\nexport class RenButton extends HTMLElement {\n  static get observedAttributes() {\n    return ['variant', 'size', 'loading', 'disabled', 'icon', 'full'];\n  }\n\n  constructor() {\n    super();\n    this._button = null;\n  }\n\n  connectedCallback() {\n    // If this element wraps a <button>, enhance it\n    // If not, create one\n    this._button = this.querySelector('button, a, [role=\"button\"]');\n\n    if (!this._button) {\n      // Wrap content in a button\n      const button = document.createElement('button');\n      button.setAttribute('type', 'button');\n\n      // Move children into the button\n      while (this.firstChild) {\n        button.appendChild(this.firstChild);\n      }\n      this.appendChild(button);\n      this._button = button;\n    }\n\n    this._updateClasses();\n    this._updateState();\n  }\n\n  attributeChangedCallback() {\n    if (this._button) {\n      this._updateClasses();\n      this._updateState();\n    }\n  }\n\n  _updateClasses() {\n    if (!this._button) return;\n\n    const variant = this.getAttribute('variant') || 'primary';\n    const size = this.getAttribute('size') || 'md';\n\n    const classes = [\n      VARIANT_MAP[variant] || VARIANT_MAP.primary,\n      SIZE_MAP[size] || '',\n      this.hasAttribute('icon') ? 'ren-btn-icon' : '',\n      this.hasAttribute('full') ? 'ren-btn-full' : '',\n    ]\n      .filter(Boolean)\n      .join(' ');\n\n    this._button.className = classes;\n  }\n\n  _updateState() {\n    if (!this._button) return;\n\n    // Loading\n    if (this.hasAttribute('loading')) {\n      this._button.setAttribute('data-loading', '');\n      this._button.setAttribute('aria-busy', 'true');\n    } else {\n      this._button.removeAttribute('data-loading');\n      this._button.removeAttribute('aria-busy');\n    }\n\n    // Disabled\n    if (this.hasAttribute('disabled')) {\n      this._button.setAttribute('disabled', '');\n      this._button.setAttribute('aria-disabled', 'true');\n    } else {\n      this._button.removeAttribute('disabled');\n      this._button.removeAttribute('aria-disabled');\n    }\n  }\n\n  // ─── Programmatic API ───\n\n  get loading() {\n    return this.hasAttribute('loading');\n  }\n\n  set loading(val) {\n    if (val) {\n      this.setAttribute('loading', '');\n    } else {\n      this.removeAttribute('loading');\n    }\n  }\n\n  get disabled() {\n    return this.hasAttribute('disabled');\n  }\n\n  set disabled(val) {\n    if (val) {\n      this.setAttribute('disabled', '');\n    } else {\n      this.removeAttribute('disabled');\n    }\n  }\n\n  get variant() {\n    return this.getAttribute('variant') || 'primary';\n  }\n\n  set variant(val) {\n    this.setAttribute('variant', val);\n  }\n}\n\n// Register\nif (!customElements.get('ren-button')) {\n  customElements.define('ren-button', RenButton);\n}\n",
      "path": "components/primitives/ren-button",
      "id": "component:primitive:ren-button",
      "type": "component",
      "name": "ren-button"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-card",
          ".ren-card-body",
          ".ren-card-cover",
          ".ren-card-description",
          ".ren-card-elevated",
          ".ren-card-footer",
          ".ren-card-footer-border",
          ".ren-card-ghost",
          ".ren-card-group",
          ".ren-card-header",
          ".ren-card-interactive",
          ".ren-card-outline",
          ".ren-card-selectable",
          ".ren-card-simple",
          ".ren-card-sunken",
          ".ren-card-title"
        ],
        "tokens": [
          "--caption-size",
          "--color-accent",
          "--color-border-muted",
          "--color-border-strong",
          "--color-danger",
          "--color-focus-ring",
          "--color-info",
          "--color-success",
          "--color-surface-sunken",
          "--color-text",
          "--color-text-muted",
          "--color-warning",
          "--duration-state",
          "--duration-tactile",
          "--ease-enter",
          "--leading-normal",
          "--leading-snug",
          "--ren-card-bg",
          "--ren-card-border-color",
          "--ren-card-border-width",
          "--ren-card-gap",
          "--ren-card-header-gap",
          "--ren-card-padding",
          "--ren-card-radius",
          "--ren-card-shadow",
          "--ring-offset-width",
          "--ring-width",
          "--shadow-md",
          "--space-2",
          "--space-3",
          "--space-4",
          "--stroke-1",
          "--text-lg",
          "--text-sm",
          "--title-sm-size",
          "--weight-semibold"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-card\n\nContainer primitive for grouped content with header, body, footer, status,\nand interactive (clickable / selectable) variants.\n\nLoad this file after `ren-design.md` and before generating, editing, or\nreviewing any `.ren-card` UI.\n\n## Purpose\n\nOwns the shape, padding, surface, and border of grouped content. Provides\nslots (`-header`, `-body`, `-footer`) and a small set of variants\n(`-elevated`, `-outline`, `-sunken`, `-ghost`, `-simple`,\n`-interactive`, `-selectable`) plus a status hook (`[data-status]`).\nCard never owns inputs, dialogs, or navigation behavior — it is a passive\ncontainer.\n\n## Use When\n\n- A group of related content needs its own surface separated from the page.\n- A list of repeating items would benefit from a consistent card grid\n  (`ren-grid` + `ren-card`).\n- A tile must be clickable or selectable as a whole (use\n  `.ren-card-interactive` or `.ren-card-selectable`).\n- A surface needs a header / body / footer layout already proven by RenDS.\n\n## Do Not Use When\n\n- The container is just a layout primitive — use `ren-stack`, `ren-cluster`,\n  `ren-grid`, etc.\n- The container is a modal — use `ren-dialog`.\n- The container is a popover / tooltip / menu — use those composites.\n- You need an alert / banner / call-out — use `ren-banner`.\n- You need a form section — use `ren-form` + `ren-field` directly.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You want a visually grouped surface (border / shadow / radius) with predictable spacing.\"\n    - \"You need a card grid (stat tiles, product cards, list items).\"\n    - \"You need a whole-card click target (.ren-card-interactive) or selection (.ren-card-selectable).\"\n    - \"You need a status accent driven by [data-status].\"\n  avoidWhen:\n    - \"You only need vertical spacing — use ren-stack.\"\n    - \"You need overlay behavior — use ren-dialog / ren-popover / ren-sheet.\"\n    - \"You need a banner / alert message — use ren-banner.\"\n    - \"You need a navigation list with hover / active — use ren-sidebar items.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-card/ren-card.css\"\n  notes:\n    - \"Card is CSS-only. There is no <ren-card> custom element.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"A single .ren-card root wrapping any number of .ren-card-header, .ren-card-body, .ren-card-footer.\"\n  - \".ren-card-title is the heading; use a real <h*> element inside it for outline / a11y.\"\n  - \"Selectable cards must wire aria-selected (or [data-selected]) AND a keyboard handler — the class only styles.\"\n  - \"Interactive cards (whole-card click) should wrap content in a real <a> or <button>, not a div with onclick.\"\n\nforbiddenPatterns:\n  - \"Nesting another .ren-card inside .ren-card-body — flatten the structure.\"\n  - \"Replacing internal .ren-card-* selectors with custom classes for theming — use --ren-card-* tokens.\"\n  - \"Hardcoded shadow / radius values overriding card defaults; theme through tokens.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: every --ren-card-* listed in Public Token API.\"\n    - \"Semantic tokens for content inside the card (--color-text, --color-text-muted, etc.).\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, etc.) in consumer code.\"\n    - \"Hardcoded hex / rgb colors in consumer overrides.\"\n\naccessibility:\n  required:\n    - \"Card titles use real heading elements (<h2>, <h3>) — never style a <div> as a heading.\"\n    - \"Whole-card click targets must use <a> or <button> wrappers and respect tab order / focus rings.\"\n    - \"Selectable cards announce state via aria-selected + data-selected; pair with keyboard activation.\"\n    - \"[data-status] is decorative; pair it with meaningful text or icons, not color alone.\"\n```\n\n## Required CSS / JS Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-card/ren-card.css\">\n<!-- No JS — card is CSS-only. -->\n```\n\n## Canonical Markup\n\nStatic card with header + body:\n\n```html\n<article class=\"ren-card\">\n  <div class=\"ren-card-header\">\n    <h3 class=\"ren-card-title\">Project status</h3>\n    <p class=\"ren-card-description\">Updated 2 minutes ago.</p>\n  </div>\n  <div class=\"ren-card-body\">\n    <p>All checks passing.</p>\n  </div>\n</article>\n```\n\nInteractive (whole-card click) with link wrapper:\n\n```html\n<a class=\"ren-card ren-card-interactive\" href=\"/projects/atlas\">\n  <div class=\"ren-card-header\">\n    <h3 class=\"ren-card-title\">Atlas</h3>\n  </div>\n  <div class=\"ren-card-body\">42 open issues</div>\n</a>\n```\n\nSelectable in a grid:\n\n```html\n<div class=\"ren-grid-3\">\n  <button class=\"ren-card ren-card-selectable\" type=\"button\" aria-pressed=\"false\">\n    <span class=\"ren-card-title\">Hobby</span>\n    <span class=\"ren-card-description\">$0/mo</span>\n  </button>\n</div>\n```\n\n## Variants\n\n| Class                   | Role                                      |\n|-------------------------|-------------------------------------------|\n| `.ren-card`             | Default surface.                          |\n| `.ren-card-elevated`    | Adds shadow.                              |\n| `.ren-card-outline`     | Border-only, transparent fill.            |\n| `.ren-card-sunken`      | Inset surface (sunken background).        |\n| `.ren-card-ghost`       | No surface; spacing only.                 |\n| `.ren-card-simple`      | Reduced padding for dense lists.          |\n| `.ren-card-interactive` | Hover / focus styles for clickable cards. |\n| `.ren-card-selectable`  | Toggleable card; pair with `aria-selected`. |\n| `.ren-card-cover`       | Edge-to-edge media slot at the top.       |\n| `.ren-card-group`       | Joined sibling cards with shared borders. |\n| `.ren-card-footer-border` | Footer with top divider.                |\n\n## States\n\n| Selector / attr        | Meaning                                     |\n|------------------------|---------------------------------------------|\n| `:hover` / `:active`   | Pointer interaction (interactive cards).    |\n| `:focus-visible`       | Keyboard focus ring (interactive cards).    |\n| `[aria-selected=\"true\"]` / `[data-selected]` | Selection state.       |\n| `[data-status=\"success\" / \"warning\" / \"danger\" / \"info\"]` | Status accent. |\n\n## Public Token API\n\n- `--ren-card-bg`\n- `--ren-card-border-color`\n- `--ren-card-border-width`\n- `--ren-card-radius`\n- `--ren-card-padding`\n- `--ren-card-gap`\n- `--ren-card-header-gap`\n- `--ren-card-shadow`\n\n## Accessibility Contract\n\n- Card titles are real `<h*>` elements; never style a `<div>` as a heading.\n- Whole-card clickability wraps the entire card in a real `<a>` or `<button>`.\n- Selectable cards must expose `aria-selected` (or `aria-pressed`) plus a\n  visible focus ring via `:focus-visible`.\n- `[data-status]` is decorative; status meaning must be carried by text or\n  icons too.\n\n## Anti-Patterns\n\n- ❌ `<div class=\"ren-card\" onclick=\"...\">` — wrap with a real link / button.\n- ❌ Nesting `.ren-card` inside `.ren-card-body`.\n- ❌ Inline hex colors on `.ren-card { background: #FFFFFF; }` — override\n  `--ren-card-bg` on the parent scope.\n- ❌ Replacing built-in selectors (`.ren-card-title`) with custom classes\n  for theming — theme through component tokens.\n\n## Related Files\n\n- `components/primitives/ren-card/ren-card.css`\n- `docs/components/ren-card.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, ARIA, or states change.\n- Run `npm run lint` after token / selector changes.\n- Manually verify light/dark themes when surface, border, or shadow values\n  change.\n\n\n/* ============================================\n   RenDS — Card Component\n   ============================================\n   Versatile container with header, body, footer.\n   CSS-only. No JS needed.\n\n   Constraint-driven: limited variants on purpose\n   to ensure consistent UI across projects.\n\n   Usage:\n     <div class=\"ren-card\">\n       <div class=\"ren-card-header\">\n         <h3 class=\"ren-card-title\">Title</h3>\n         <p class=\"ren-card-description\">Description here</p>\n       </div>\n       <div class=\"ren-card-body\">\n         Content\n       </div>\n       <div class=\"ren-card-footer\">\n         <button class=\"ren-btn\">Action</button>\n       </div>\n     </div>\n\n   Minimal:\n     <div class=\"ren-card\">\n       <p>Just content, no sections needed.</p>\n     </div>\n   ============================================ */\n\n/* ─── Base Card ─── */\n.ren-card {\n  background-color: var(--ren-card-bg);\n  border: var(--ren-card-border-width) solid var(--ren-card-border-color);\n  border-radius: var(--ren-card-radius);\n  box-shadow: var(--ren-card-shadow);\n  overflow: hidden;\n}\n\n/* ─── Sections ─── */\n\n.ren-card-header {\n  display: flex;\n  flex-direction: column;\n  gap: var(--ren-card-header-gap);\n  padding: var(--ren-card-padding) var(--ren-card-padding) 0;\n}\n\n/* Title and description are typically applied to <h2>/<h3>/<h4> and <p>\n   respectively, which carry browser-default margins that fight the header's\n   `gap`. Reset them here so .ren-card-header's flex gap is the actual visual\n   gap, regardless of the underlying tag. */\n.ren-card-title {\n  font-size: var(--title-sm-size, var(--text-lg));\n  font-weight: var(--weight-semibold);\n  color: var(--color-text);\n  line-height: var(--leading-snug);\n  margin: 0;\n}\n\n.ren-card-description {\n  font-size: var(--caption-size, var(--text-sm));\n  color: var(--color-text-muted);\n  line-height: var(--leading-normal);\n  margin: 0;\n}\n\n.ren-card-body {\n  padding: var(--ren-card-padding);\n}\n\n/* Remove top padding when body follows header */\n.ren-card-header + .ren-card-body {\n  padding-top: var(--space-3);\n}\n\n/* Optional spacing between documented card sections without forcing a new\n   flex/grid layout on the card root. */\n.ren-card > :is(.ren-card-header, .ren-card-body, .ren-card-footer, .ren-card-footer-border)\n  + :is(.ren-card-header, .ren-card-body, .ren-card-footer, .ren-card-footer-border) {\n  margin-block-start: var(--ren-card-gap);\n}\n\n.ren-card-footer {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  padding: 0 var(--ren-card-padding) var(--ren-card-padding);\n}\n\n/* Footer at bottom with border (for action-heavy cards) */\n.ren-card-footer-border {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  padding: var(--space-3) var(--ren-card-padding);\n  border-top: var(--stroke-1) solid var(--color-border-muted);\n}\n\n/* When the bordered footer follows the header directly (no body in between),\n   tighten the top padding so the divider line sits visually close to the\n   description, not floating ~17px below it (which is what you get from the\n   default --space-3 padding plus line-height extra of the description text). */\n.ren-card-header + .ren-card-footer-border {\n  padding-top: var(--space-2);\n}\n\n/* When card has no sections, just padding */\n.ren-card > :first-child:not(.ren-card-header):not(.ren-card-body):not(.ren-card-footer) {\n  padding-top: var(--ren-card-padding);\n}\n\n.ren-card > :last-child:not(.ren-card-header):not(.ren-card-body):not(.ren-card-footer) {\n  padding-bottom: var(--ren-card-padding);\n}\n\n/* Simple card (no sections) */\n.ren-card-simple {\n  padding: var(--ren-card-padding);\n}\n\n/* ─── Variants ─── */\n\n/* Elevated — more shadow, no border */\n.ren-card-elevated {\n  border-color: transparent;\n  box-shadow: var(--shadow-md);\n}\n\n/* Outline — emphasis border, no background difference */\n.ren-card-outline {\n  background-color: transparent;\n}\n\n/* Ghost — no border, no bg (content grouping) */\n.ren-card-ghost {\n  background-color: transparent;\n  border: none;\n}\n\n/* Sunken — recessed appearance */\n.ren-card-sunken {\n  background-color: var(--color-surface-sunken);\n  border-color: transparent;\n}\n\n/* ─── Interactive Card ─── */\n\n.ren-card-interactive {\n  cursor: pointer;\n  transition:\n    box-shadow var(--duration-state) var(--ease-enter),\n    border-color var(--duration-state) var(--ease-enter),\n    transform var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-card-interactive:hover {\n  border-color: var(--color-border-strong);\n  box-shadow: var(--shadow-md);\n}\n\n.ren-card-interactive:active {\n  transform: scale(0.99);\n}\n\n.ren-card-interactive:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: var(--ring-offset-width);\n}\n\n/* ─── Selectable Card ─── */\n\n.ren-card-selectable {\n  cursor: pointer;\n  transition:\n    border-color var(--duration-state) var(--ease-enter),\n    box-shadow var(--duration-state) var(--ease-enter);\n}\n\n.ren-card-selectable:hover {\n  border-color: var(--color-accent);\n}\n\n.ren-card-selectable[aria-selected=\"true\"],\n.ren-card-selectable[data-selected] {\n  border-color: var(--color-accent);\n  box-shadow: 0 0 0 1px var(--color-accent);\n}\n\n/* ─── Status accent (top border) ─── */\n\n.ren-card[data-status=\"success\"] { border-top: 3px solid var(--color-success); }\n.ren-card[data-status=\"warning\"] { border-top: 3px solid var(--color-warning); }\n.ren-card[data-status=\"danger\"]  { border-top: 3px solid var(--color-danger); }\n.ren-card[data-status=\"info\"]    { border-top: 3px solid var(--color-info); }\n\n/* ─── Card with cover image ─── */\n\n.ren-card-cover {\n  width: 100%;\n  height: auto;\n  object-fit: cover;\n  display: block;\n}\n\n/* ─── Card Group (grid of cards) ─── */\n\n.ren-card-group {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));\n  gap: var(--space-4);\n}\n",
      "path": "components/primitives/ren-card",
      "id": "component:primitive:ren-card",
      "type": "component",
      "name": "ren-card"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-checkbox",
          ".ren-checkbox-control"
        ],
        "tokens": [
          "--body-size",
          "--color-accent",
          "--color-accent-hover",
          "--color-focus-ring",
          "--color-text",
          "--duration-tactile",
          "--ease-enter",
          "--ren-checkbox-border",
          "--ren-checkbox-checked-bg",
          "--ren-checkbox-checked-color",
          "--ren-checkbox-duration",
          "--ren-checkbox-easing",
          "--ren-checkbox-gap",
          "--ren-checkbox-radius",
          "--ren-checkbox-size",
          "--ring-offset-width",
          "--ring-width",
          "--touch-min",
          "--weight-regular"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-checkbox Component Contract\n\nNative checkbox styling with checked, indeterminate, disabled, and focus states.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-checkbox` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-checkbox` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Checkbox primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The control toggles a boolean value (checked / unchecked) for a single option.\"\n    - \"The control belongs in a list where each option is independently selectable (multi-select).\"\n    - \"You need an indeterminate / mixed state (e.g., parent of a tree, \\\"select all\\\" with partial children).\"\n    - \"You need native form-submission semantics (name / value / required) tied to a real <input type=\\\"checkbox\\\">.\"\n    - \"The label sits next to a custom-styled box that participates in :checked / :hover / :focus-visible / :indeterminate / :disabled states.\"\n  avoidWhen:\n    - \"The control is an on/off toggle with two visible states — use ren-switch.\"\n    - \"The control is one of several mutually-exclusive options — use ren-radio.\"\n    - \"You need a yes/no choice inside a tabular row without form submission — consider ren-switch.\"\n    - \"You need a tri-state with named states beyond on/off/mixed — design a custom segmented control.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-checkbox/ren-checkbox.css\"\n  notes:\n    - \"CSS-only primitive — no colocated JS exists. Indeterminate must be set in JS via input.indeterminate = true; the CSS reacts to :indeterminate.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Wrap each control in <label class=\\\"ren-checkbox\\\"> containing exactly one <input type=\\\"checkbox\\\"> and one <span class=\\\"ren-checkbox-control\\\"> sibling, in that order.\"\n  - \"Label text goes in a trailing <span> (or text node) after .ren-checkbox-control so :checked + .ren-checkbox-control adjacency works.\"\n  - \"Group related checkboxes inside a <fieldset> with a <legend> to provide a group accessible name.\"\n  - \"For indeterminate state, set input.indeterminate = true via JS; you cannot express it via an HTML attribute alone.\"\n  - \"Disabled inputs must use the real disabled attribute — the .ren-checkbox:has(input:disabled) selector relies on it.\"\n\nforbiddenPatterns:\n  - \"Replacing <input type=\\\"checkbox\\\"> with a <div role=\\\"checkbox\\\"> — breaks form submission and native keyboard.\"\n  - \"Hiding the input with display:none — destroys keyboard focus; use the documented visually-hidden technique already in the CSS.\"\n  - \"Custom checkmark glyphs injected via JS or inline SVG — the ::after pseudo owns the checkmark.\"\n  - \"Mixing two label nodes per input (e.g., wrapping <label> plus a separate <label for>) — choose one.\"\n  - \"Communicating error / validity via color alone; pair with text or an inline error message.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-checkbox-border, --ren-checkbox-checked-bg, --ren-checkbox-checked-color, --ren-checkbox-duration, --ren-checkbox-easing, --ren-checkbox-gap, --ren-checkbox-radius, --ren-checkbox-size.\"\n    - \"Semantic tokens: --color-accent, --color-accent-hover, --color-on-accent, --color-border-strong, --color-text, --color-focus-ring.\"\n    - \"Layout / motion tokens: --space-2, --radius-sm, --touch-min, --ring-width, --ring-offset-width, --duration-state, --duration-tactile, --ease-enter, --ease-playful.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors for the checked fill, border, or checkmark.\"\n    - \"Raw transition values; use --ren-checkbox-duration / --ren-checkbox-easing or the --duration-* / --ease-* tokens.\"\n\naccessibility:\n  required:\n    - \"Always use a real <input type=\\\"checkbox\\\"> so Space toggles, Tab focuses, and form submission carries the value.\"\n    - \"Touch target stays ≥ var(--touch-min) via min-height on .ren-checkbox; do not shrink below that for touch contexts.\"\n    - \"Focus is visible on the .ren-checkbox-control via :focus-visible outline driven by --color-focus-ring and --ring-width.\"\n    - \"Each checkbox has an accessible name via its wrapping <label> text; never rely on placeholder or title alone.\"\n    - \"Group multiple related checkboxes inside <fieldset><legend> for a programmatic group name.\"\n    - \"Disabled state must use the native disabled attribute (not just aria-disabled) so the input is excluded from form submission and keyboard navigation.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-checkbox/ren-checkbox.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<label class=\"ren-checkbox\">\n  <input type=\"checkbox\" name=\"updates\">\n  <span class=\"ren-checkbox-control\"></span>\n  <span>Email updates</span>\n</label>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-checkbox`\n- `.ren-checkbox-control`\n\nFor toggle switches with on/off semantics, see the sibling primitive `ren-switch`.\n\n## States And Attributes\n\n- `:active`\n- `:checked`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n- `:indeterminate`\n\n## Public Token API\n\n- `--ren-checkbox-border`\n- `--ren-checkbox-checked-bg`\n- `--ren-checkbox-checked-color`\n- `--ren-checkbox-duration`\n- `--ren-checkbox-easing`\n- `--ren-checkbox-gap`\n- `--ren-checkbox-radius`\n- `--ren-checkbox-size`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-checkbox/ren-checkbox.css`\n- `docs/components/ren-checkbox.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Checkbox Component\n   ============================================\n   Custom-styled checkbox built on native\n   <input type=\"checkbox\">.\n\n   Preserves native semantics and accessibility.\n   Custom visuals via CSS only.\n\n   Usage:\n     <label class=\"ren-checkbox\">\n       <input type=\"checkbox\">\n       <span class=\"ren-checkbox-control\"></span>\n       <span>Accept terms</span>\n     </label>\n\n   For toggle switches, see ren-switch.\n   ============================================ */\n\n.ren-checkbox {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--ren-checkbox-gap);\n  cursor: pointer;\n  font-size: var(--body-size);\n  font-weight: var(--weight-regular);\n  color: var(--color-text);\n  user-select: none;\n  /* Ensure touch target */\n  min-height: var(--touch-min);\n}\n\n/* Hide native checkbox visually but keep accessible */\n.ren-checkbox > input[type=\"checkbox\"] {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip-path: inset(50%);\n  white-space: nowrap;\n  border-width: 0;\n}\n\n/* Custom checkbox visual */\n.ren-checkbox-control {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: var(--ren-checkbox-size);   /* 20px */\n  height: var(--ren-checkbox-size);\n  flex-shrink: 0;\n  border: 2px solid var(--ren-checkbox-border);\n  border-radius: var(--ren-checkbox-radius);\n  background-color: transparent;\n  transition:\n    background-color var(--ren-checkbox-duration) var(--ease-enter),\n    border-color var(--ren-checkbox-duration) var(--ease-enter),\n    transform var(--duration-tactile) var(--ren-checkbox-easing);\n}\n\n/* Checkmark (hidden by default) */\n.ren-checkbox-control::after {\n  content: '';\n  display: block;\n  width: 0.45rem;\n  height: 0.7rem;\n  border: solid var(--ren-checkbox-checked-color);\n  border-width: 0 2px 2px 0;\n  transform: rotate(45deg) scale(0);\n  transition: transform var(--ren-checkbox-duration) var(--ren-checkbox-easing);\n  margin-top: -2px;\n}\n\n/* ─── States ─── */\n\n/* Checked */\n.ren-checkbox > input:checked + .ren-checkbox-control {\n  background-color: var(--ren-checkbox-checked-bg);\n  border-color: var(--ren-checkbox-checked-bg);\n}\n\n.ren-checkbox > input:checked + .ren-checkbox-control::after {\n  transform: rotate(45deg) scale(1);\n}\n\n/* Hover */\n.ren-checkbox:hover > .ren-checkbox-control {\n  border-color: var(--color-accent);\n}\n\n.ren-checkbox:hover > input:checked + .ren-checkbox-control {\n  background-color: var(--color-accent-hover);\n  border-color: var(--color-accent-hover);\n}\n\n/* Focus */\n.ren-checkbox > input:focus-visible + .ren-checkbox-control {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: var(--ring-offset-width);\n}\n\n/* Active / Press */\n.ren-checkbox:active > .ren-checkbox-control {\n  transform: scale(0.9);\n}\n\n/* Disabled */\n.ren-checkbox:has(input:disabled) {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n\n/* Indeterminate */\n.ren-checkbox > input:indeterminate + .ren-checkbox-control {\n  background-color: var(--ren-checkbox-checked-bg);\n  border-color: var(--ren-checkbox-checked-bg);\n}\n\n.ren-checkbox > input:indeterminate + .ren-checkbox-control::after {\n  border: none;\n  width: 0.6rem;\n  height: 2px;\n  background-color: var(--ren-checkbox-checked-color);\n  transform: none;\n  margin: 0;\n  border-radius: 1px;\n}\n",
      "path": "components/primitives/ren-checkbox",
      "id": "component:primitive:ren-checkbox",
      "type": "component",
      "name": "ren-checkbox"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-field",
          ".ren-field-control",
          ".ren-field-description",
          ".ren-field-error",
          ".ren-field-label",
          ".ren-input",
          ".ren-input-error",
          ".ren-input-icon",
          ".ren-input-icon-end",
          ".ren-input-lg",
          ".ren-input-sm",
          ".ren-input-success",
          ".ren-input-wrapper"
        ],
        "tokens": [
          "--blue-100",
          "--blue-200",
          "--blue-300",
          "--blue-50",
          "--blue-600",
          "--blue-700",
          "--blue-800",
          "--blue-900",
          "--caption-size",
          "--color-accent",
          "--color-border",
          "--color-danger",
          "--color-input-bg-hover",
          "--color-input-border-focus",
          "--color-input-disabled-bg",
          "--color-input-disabled-text",
          "--color-input-focus-ring",
          "--color-surface-overlay",
          "--color-text",
          "--color-text-muted",
          "--duration-state",
          "--duration-tactile",
          "--ease-enter",
          "--gray-400",
          "--gray-50",
          "--gray-500",
          "--gray-800",
          "--icon-lg",
          "--label-size",
          "--label-weight",
          "--leading-normal",
          "--radius-lg",
          "--radius-sm",
          "--ren-field-bg",
          "--ren-field-border-color",
          "--ren-field-border-width",
          "--ren-field-color",
          "--ren-field-error-color",
          "--ren-field-focus-color",
          "--ren-field-font-size",
          "--ren-field-height",
          "--ren-field-padding-x",
          "--ren-field-padding-y",
          "--ren-field-placeholder",
          "--ren-field-radius",
          "--ren-field-success-color",
          "--size-sm",
          "--size-xl",
          "--space-1",
          "--space-10",
          "--space-2",
          "--space-3",
          "--space-4",
          "--text-lg",
          "--text-sm",
          "--touch-min",
          "--weight-bold",
          "--weight-medium"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-field\n\nForm-field wrapper that connects labels, descriptions, errors, and the\nunderlying control through ARIA. Carries the field's visual frame\n(input chrome, error / valid borders, sizing).\n\nLoad this file after `ren-design.md` and before generating, editing, or\nreviewing any `<ren-field>` UI.\n\n## Purpose\n\nOwns the wiring between a `<label>`, helper text (`[data-description]`),\nerror text (`[data-error]`), and the control inside it. The custom element\nauto-generates IDs, links them via `for` / `aria-labelledby` /\n`aria-describedby` / `aria-errormessage`, and toggles `aria-invalid` and\n`data-invalid` from the host's validation state. Visual styling for the\ncontrol itself comes from `.ren-input` / `.ren-input-wrapper`.\n\n## Use When\n\n- The UI is a labeled form control: text, email, password, number, search,\n  url, date, textarea, select.\n- The control needs helper text and / or error text wired through ARIA.\n- You want consistent field heights, border / focus ring treatment, and\n  validation states across the app.\n- The field belongs to a `ren-form` pattern that drives validation.\n\n## Do Not Use When\n\n- The control is a single button — use `ren-button`.\n- The control is a checkbox / radio / switch — use `ren-checkbox` /\n  `ren-radio` / `ren-toggle-group`.\n- The control is a search/combobox with autocomplete — use `ren-combobox`.\n- You only need a static label-value pair for display — use `<dl>` /\n  `ren-card` content.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The control is a labeled form input that benefits from auto-wired ARIA.\"\n    - \"You need helper text + error text + invalid styling tied to the same control.\"\n    - \"You want a consistent input frame (height, padding, focus ring, error border).\"\n    - \"You are inside a <ren-form> that drives validation.\"\n  avoidWhen:\n    - \"The control is a checkbox / radio / switch — use those primitives directly.\"\n    - \"The control is a combobox / select with custom listbox — use ren-combobox / ren-select.\"\n    - \"You need a fully custom number stepper — use ren-number-field.\"\n    - \"You need OTP segmented inputs — use ren-otp.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-field/ren-field.css\"\n  js:\n    - \"rends/components/primitives/ren-field/ren-field.js\"\n  notes:\n    - \"JS is required for ARIA wiring and validation state syncing — do not skip.\"\n\nrequiredMarkup:\n  - \"<ren-field> wraps exactly one <label>, exactly one control, plus optional [data-description] and [data-error].\"\n  - \"Error and description nodes do not need their own IDs — the component generates them.\"\n  - \"Native control attributes drive validity (required, type=email, pattern, min, max). Do not duplicate validation in JS for the same rules.\"\n  - \"Use .ren-input on inputs / textareas / selects; use .ren-input-wrapper for icon adornments.\"\n\nforbiddenPatterns:\n  - \"Manual aria-describedby / aria-errormessage on the input — the component sets them.\"\n  - \"Reaching into the slotted control to override .ren-input internals; theme via --ren-field-* tokens.\"\n  - \"Showing error text without setting [data-invalid] / aria-invalid on the control.\"\n  - \"Hiding required-state info in placeholder text.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: every --ren-field-* listed in Public Token API.\"\n    - \"Semantic tokens for any custom decoration (--color-text-muted for the description, etc.).\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, etc.) in consumer code.\"\n    - \"Hardcoded hex / rgb colors in consumer overrides.\"\n\naccessibility:\n  required:\n    - \"Always provide a visible <label>; do not rely on placeholder text.\"\n    - \"Required inputs must mark required (the attribute) — visual indicator alone is not enough.\"\n    - \"Error messages must be set in [data-error] AND aria-invalid must be true on the control.\"\n    - \"Keep visible focus rings; do not remove the input outline without a replacement.\"\n    - \"Disabled fields use the disabled attribute on the control, not aria-disabled alone.\"\n```\n\n## Required CSS / JS Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-field/ren-field.css\">\n<script type=\"module\" src=\"rends/components/primitives/ren-field/ren-field.js\"></script>\n```\n\n## Canonical Markup\n\nRequired email field with helper + error:\n\n```html\n<ren-field>\n  <label>Email</label>\n  <input class=\"ren-input\" type=\"email\" name=\"email\" autocomplete=\"email\" required>\n  <span data-description>We will never share your email.</span>\n  <span data-error>Enter a valid email address.</span>\n</ren-field>\n```\n\nPassword with visible label and helper:\n\n```html\n<ren-field>\n  <label>Password</label>\n  <input class=\"ren-input\" type=\"password\" name=\"password\" autocomplete=\"new-password\" minlength=\"8\" required>\n  <span data-description>At least 8 characters.</span>\n  <span data-error></span>\n</ren-field>\n```\n\nTextarea:\n\n```html\n<ren-field>\n  <label>Notes</label>\n  <textarea class=\"ren-input\" name=\"notes\" rows=\"4\"></textarea>\n</ren-field>\n```\n\nInput with leading icon:\n\n```html\n<ren-field>\n  <label>Search</label>\n  <span class=\"ren-input-wrapper\">\n    <span class=\"ren-input-icon\" aria-hidden=\"true\">🔍</span>\n    <input class=\"ren-input\" type=\"search\" name=\"q\">\n  </span>\n</ren-field>\n```\n\n## Variants\n\n| Class                       | Role                                |\n|-----------------------------|-------------------------------------|\n| `.ren-input`                | Base input chrome.                  |\n| `.ren-input-sm` / `-lg`     | Size override.                      |\n| `.ren-input-error`          | Manual error styling on the input.  |\n| `.ren-input-success`        | Manual success styling.             |\n| `.ren-input-wrapper`        | Container for adornments.           |\n| `.ren-input-icon`           | Leading icon slot.                  |\n| `.ren-input-icon-end`       | Trailing icon slot.                 |\n| `.ren-field-label`          | Label slot styling.                 |\n| `.ren-field-description`    | Helper text slot styling.           |\n| `.ren-field-error`          | Error slot styling.                 |\n\n## States\n\n| Selector / attr        | Meaning                                              |\n|------------------------|------------------------------------------------------|\n| `:hover` / `:active`   | Pointer interaction.                                 |\n| `:focus-visible`       | Keyboard focus on the control.                       |\n| `:disabled`            | Native disabled state.                               |\n| `:user-invalid`        | Native validity after interaction (CSS Lv4).         |\n| `:user-valid`          | Native validity after interaction (CSS Lv4).         |\n| `[aria-invalid=\"true\"]` / `[data-invalid]` | Component-driven invalid state.  |\n| `[data-valid]`         | Component-driven valid state.                        |\n| `[data-required]`      | Component marks required visually.                   |\n| `[data-description]`   | Slot for helper text.                                |\n| `[data-error]`         | Slot for error text.                                 |\n\n## Public Token API\n\n- `--ren-field-bg`\n- `--ren-field-border-color`\n- `--ren-field-border-width`\n- `--ren-field-radius`\n- `--ren-field-height`\n- `--ren-field-padding-x`\n- `--ren-field-padding-y`\n- `--ren-field-font-size`\n- `--ren-field-color`\n- `--ren-field-placeholder`\n- `--ren-field-focus-color`\n- `--ren-field-error-color`\n- `--ren-field-success-color`\n\n## Accessibility Contract\n\n- Always provide a visible `<label>`; placeholder is not a label.\n- Required inputs use the native `required` attribute; mark it visually too.\n- Error text lives in `[data-error]`. The component sets `aria-invalid` on\n  the control automatically when the host marks the field invalid.\n- Focus rings must remain visible — do not strip outline without restoring.\n- Disabled fields use `disabled` on the control. `aria-disabled` is only\n  acceptable when the disabled state must remain focusable.\n\n## Anti-Patterns\n\n- ❌ Putting label text in `placeholder` instead of `<label>`.\n- ❌ Manual `aria-describedby` / `aria-errormessage` on the input — the\n  component owns these.\n- ❌ Showing error text without setting `aria-invalid` on the control.\n- ❌ `.ren-input { border: 2px solid #ccc; }` — theme through\n  `--ren-field-border-*` tokens.\n\n## Related Files\n\n- `components/primitives/ren-field/ren-field.css`\n- `components/primitives/ren-field/ren-field.js`\n- `components/patterns/ren-form/pattern.md` (validation pattern)\n- `docs/components/ren-field.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n\n## Test Expectations\n\n- Run component / docs a11y coverage when markup, ARIA, or validation\n  behavior changes.\n- Run `npm run lint` after token / selector changes.\n- Manually verify light/dark themes for focus ring and error glow.\n\n\n/* ============================================\n   RenDS — Field Component (Input + Label + Error)\n   ============================================\n   Wraps any form control with label, description,\n   and error message — all properly wired with ARIA.\n\n   Works as pure CSS classes OR as <ren-field>.\n\n   Structure:\n   .ren-field\n     .ren-field-label       (label element)\n     .ren-field-control     (input/select/textarea wrapper)\n     .ren-field-description (helper text)\n     .ren-field-error       (error message)\n   ============================================ */\n\n/* ─── Field Container ─── */\n.ren-field {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-1);\n}\n\n/* ─── Label ─── */\n.ren-field-label {\n  display: flex;\n  align-items: center;\n  gap: var(--space-1);\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  color: var(--color-text);\n}\n\n/* Required indicator */\n.ren-field-label[data-required]::after {\n  content: '*';\n  color: var(--color-danger);\n  font-weight: var(--weight-bold);\n}\n\n/* ─── Input / Control ─── */\n.ren-input {\n  display: block;\n  width: 100%;\n  min-height: var(--ren-field-height);\n  padding: var(--ren-field-padding-y) var(--ren-field-padding-x);\n  font-size: var(--ren-field-font-size);\n  font-family: inherit;\n  color: var(--ren-field-color);\n  background-color: var(--ren-field-bg);\n  border: var(--ren-field-border-width) solid var(--ren-field-border-color);\n  border-radius: var(--ren-field-radius);\n  transition:\n    border-color var(--duration-state) var(--ease-enter),\n    background-color var(--duration-state) var(--ease-enter),\n    box-shadow var(--duration-state) var(--ease-enter);\n}\n\n.ren-input:hover:not(:disabled):not(:focus) {\n  background-color: var(--color-input-bg-hover);\n}\n\n.ren-input:focus {\n  outline: none;\n  border-color: var(--ren-field-focus-color);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n  background-color: var(--color-input-bg-hover);\n}\n\n.ren-input:disabled {\n  background-color: var(--color-input-disabled-bg);\n  color: var(--color-input-disabled-text);\n  cursor: not-allowed;\n  opacity: 0.7;\n}\n\n.ren-input::placeholder {\n  color: var(--ren-field-placeholder);\n  opacity: 1;\n}\n\n/* ─── Textarea ─── */\ntextarea.ren-input {\n  field-sizing: content;  /* Auto-grow with content — no JS needed */\n  min-height: calc(var(--touch-min) * 2);\n  max-height: 20rem;      /* Prevent runaway growth */\n  resize: vertical;\n}\n\n/* ─── Select ─── */\nselect.ren-input {\n  appearance: none;\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%238E8E93' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n  background-repeat: no-repeat;\n  background-position: right var(--space-3) center;\n  padding-inline-end: var(--space-10);\n}\n\n@supports (appearance: base-select) {\n  /* ── Opt-in ── */\n  select.ren-input,\n  select.ren-input::picker(select) {\n    appearance: base-select;\n  }\n\n  /* ── Trigger ── */\n  select.ren-input {\n    display: flex;\n    align-items: center;\n    background-image: none;\n    padding-inline-end: var(--space-3);\n  }\n\n  select.ren-input::picker-icon {\n    color: var(--color-text-muted);\n    opacity: 0.6;\n    margin-inline-start: auto;\n    padding-inline-start: var(--space-3);\n    transition: rotate var(--duration-tactile) var(--ease-enter);\n  }\n\n  select.ren-input:open::picker-icon {\n    rotate: 180deg;\n  }\n\n  select.ren-input option::checkmark {\n    display: none;\n  }\n\n  /* ── Dropdown ── */\n  select.ren-input::picker(select) {\n    background: var(--color-surface-overlay);\n    border: 1px solid var(--color-border);\n    border-radius: var(--radius-lg);\n    box-shadow: 0 4px 20px rgb(0, 0, 0, 0.08),\n                0 1px 3px rgb(0, 0, 0, 0.06);\n    padding: var(--space-1);\n    margin-block-start: var(--space-1);\n    overflow: clip;\n  }\n\n  /* ── Options ── */\n  select.ren-input option {\n    display: flex;\n    align-items: center;\n    padding: var(--space-2) var(--space-3);\n    border-radius: 4px;\n    color: var(--color-text);\n    background: transparent;\n    cursor: pointer;\n  }\n\n  select.ren-input option + option {\n    margin-block-start: 1px;\n  }\n\n  select.ren-input option:hover {\n    background: light-dark(var(--gray-50), var(--gray-800));\n  }\n\n  select.ren-input option:focus-visible,\n  select.ren-input option:active {\n    outline: 2px solid var(--color-accent);\n    outline-offset: -2px;\n    background: light-dark(var(--gray-50), var(--gray-800));\n  }\n\n  select.ren-input option:checked:not(:disabled) {\n    background: light-dark(var(--blue-50), var(--blue-900));\n    color: light-dark(var(--blue-700), var(--blue-200));\n    font-weight: var(--weight-medium);\n  }\n\n  select.ren-input option:checked:not(:disabled)::after {\n    content: \"✓\";\n    margin-inline-start: auto;\n    color: light-dark(var(--blue-600), var(--blue-300));\n    font-weight: var(--weight-bold);\n  }\n\n  select.ren-input option:checked:hover {\n    background: light-dark(var(--blue-100), var(--blue-800));\n  }\n\n  select.ren-input option:disabled {\n    color: light-dark(var(--gray-500), var(--gray-400));\n    cursor: not-allowed;\n  }\n\n  select.ren-input option[hidden] {\n    display: none;\n  }\n\n  /* ── Optgroups ── */\n  select.ren-input optgroup {\n    padding: var(--space-1) 0;\n    padding-inline-start: 0;\n  }\n\n  select.ren-input optgroup:not(:first-of-type) {\n    border-block-start: 1px solid var(--color-border);\n    margin-block-start: var(--space-2);\n    padding-block-start: var(--space-2);\n  }\n\n  select.ren-input optgroup > legend,\n  select.ren-input optgroup > label {\n    padding: var(--space-1) var(--space-3);\n    margin-inline-start: var(--space-3);\n  }\n\n  /* ── Open state ── */\n  select.ren-input:open {\n    border-color: var(--color-input-border-focus);\n    box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n  }\n}\n\n/* ─── Input Sizes ─── */\n.ren-input-sm {\n  min-height: var(--size-sm);\n  padding: var(--space-1) var(--space-2);\n  font-size: var(--text-sm);\n  border-radius: var(--radius-sm);\n}\n\n.ren-input-lg {\n  min-height: var(--size-xl);\n  padding: var(--space-3) var(--space-4);\n  font-size: var(--text-lg);\n}\n\n/* ─── Input with icon ─── */\n.ren-input-wrapper {\n  position: relative;\n  display: flex;\n  align-items: center;\n}\n\n.ren-input-icon {\n  position: absolute;\n  left: var(--space-3);\n  color: var(--color-text-muted);\n  pointer-events: none;\n  display: flex;\n}\n\n.ren-input-icon + .ren-input,\n.ren-input-wrapper > .ren-input:has(+ .ren-input-icon) {\n  padding-inline-start: calc(var(--space-3) + var(--icon-lg) + var(--space-2));\n}\n\n.ren-input-icon-end {\n  position: absolute;\n  right: var(--space-3);\n  color: var(--color-text-muted);\n  pointer-events: none;\n  display: flex;\n}\n\n/* ─── Description ─── */\n.ren-field-description {\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n  line-height: var(--leading-normal);\n  margin: 0;\n}\n\n/* ─── Error Message ─── */\n.ren-field-error {\n  font-size: var(--caption-size);\n  color: var(--ren-field-error-color);\n  line-height: var(--leading-normal);\n  margin: 0;\n  display: flex;\n  align-items: center;\n  gap: var(--space-1);\n}\n\n/* Error state on input */\n.ren-field[data-invalid] .ren-input,\n.ren-input[aria-invalid=\"true\"],\n.ren-input-error {\n  border-color: var(--ren-field-error-color);\n}\n\n.ren-field[data-invalid] .ren-input:focus,\n.ren-input[aria-invalid=\"true\"]:focus,\n.ren-input-error:focus {\n  box-shadow: 0 0 0 3px rgb(255, 59, 48, 0.15);\n  border-color: var(--ren-field-error-color);\n}\n\n/* Error state on label */\n.ren-field[data-invalid] .ren-field-label {\n  color: var(--ren-field-error-color);\n}\n\n/* ─── Success state ─── */\n.ren-field[data-valid] .ren-input,\n.ren-input-success {\n  border-color: var(--ren-field-success-color);\n}\n\n.ren-field[data-valid] .ren-input:focus,\n.ren-input-success:focus {\n  box-shadow: 0 0 0 3px rgb(52, 199, 89, 0.15);\n}\n\n/* ─── Modern Validation (no JS needed) ─── */\n/* Only show validation state AFTER user interaction */\n.ren-input:user-invalid {\n  border-color: var(--ren-field-error-color);\n}\n\n.ren-input:user-invalid:focus {\n  box-shadow: 0 0 0 3px rgb(255, 59, 48, 0.15);\n  border-color: var(--ren-field-error-color);\n}\n\n.ren-input:user-valid {\n  border-color: var(--ren-field-success-color);\n}\n\n.ren-input:user-valid:focus {\n  box-shadow: 0 0 0 3px rgb(52, 199, 89, 0.15);\n}\n\n/* Show/hide error and success messages based on validation */\n.ren-field:has(.ren-input:user-invalid) .ren-field-label {\n  color: var(--ren-field-error-color);\n}\n\n.ren-field:has(.ren-input:user-invalid) .ren-field-error {\n  display: flex;\n}\n\n.ren-field:has(.ren-input:user-valid) .ren-field-error {\n  display: none;\n}\n\n\n/**\n * RenDS — <ren-field> Web Component\n * ===================================\n * Auto-wires ARIA relationships between label, input,\n * description, and error message.\n *\n * Light DOM — no Shadow DOM. Just enhances existing HTML.\n *\n * Usage:\n *   <ren-field>\n *     <label>Email</label>\n *     <input type=\"email\" placeholder=\"name@example.com\">\n *     <span data-description>We'll never share your email.</span>\n *     <span data-error>Please enter a valid email.</span>\n *   </ren-field>\n *\n * The component automatically:\n * - Generates IDs and wires aria-labelledby\n * - Wires aria-describedby to description\n * - Wires aria-errormessage to error\n * - Sets aria-invalid when error is visible\n * - Adds data-required to label when input is required\n * - Adds .ren-field, .ren-field-label, .ren-input, etc. classes\n */\n\nimport { autoId } from '../../../utils/id-generator.js';\n\nexport class RenField extends HTMLElement {\n  connectedCallback() {\n    this._listenerController?.abort();\n    this._listenerController = new AbortController();\n    const { signal } = this._listenerController;\n    // Add base class\n    this.classList.add('ren-field');\n\n    // Find child elements\n    const label = this.querySelector('label');\n    const input = this.querySelector('input, select, textarea');\n    const description = this.querySelector('[data-description]');\n    const error = this.querySelector('[data-error]');\n\n    if (!input) return;\n\n    // ─── Add classes ───\n    label?.classList.add('ren-field-label');\n    input.classList.add('ren-input');\n    description?.classList.add('ren-field-description');\n    error?.classList.add('ren-field-error');\n\n    // ─── Generate IDs ───\n    const inputId = autoId(input, 'input');\n\n    // Wire label → input (using for attribute)\n    if (label && !label.getAttribute('for')) {\n      label.setAttribute('for', inputId);\n    }\n\n    // Wire description → input (aria-describedby)\n    if (description) {\n      const descId = autoId(description, 'desc');\n      const existing = input.getAttribute('aria-describedby');\n      if (!existing?.includes(descId)) {\n        input.setAttribute(\n          'aria-describedby',\n          existing ? `${existing} ${descId}` : descId\n        );\n      }\n    }\n\n    // Wire error → input (aria-errormessage)\n    if (error) {\n      const errorId = autoId(error, 'error');\n      input.setAttribute('aria-errormessage', errorId);\n      error.setAttribute('role', 'alert');\n      error.setAttribute('aria-live', 'polite');\n\n      // Initially hide error if not invalid\n      if (!this.hasAttribute('data-invalid')) {\n        error.hidden = true;\n      }\n    }\n\n    // ─── Required indicator ───\n    if (input.hasAttribute('required') && label) {\n      label.setAttribute('data-required', '');\n    }\n\n    // ─── Observe validity changes ───\n    this._input = input;\n    this._error = error;\n\n    // Listen for custom validation\n    input.addEventListener('invalid', () => this._setInvalid(true), { signal });\n    input.addEventListener('input', () => {\n      if (this.hasAttribute('data-invalid') && input.validity.valid) {\n        this._setInvalid(false);\n      }\n    }, { signal });\n  }\n\n  disconnectedCallback() {\n    this._listenerController?.abort();\n    this._listenerController = null;\n  }\n\n  _setInvalid(invalid) {\n    if (invalid) {\n      this.setAttribute('data-invalid', '');\n      this._input?.setAttribute('aria-invalid', 'true');\n      if (this._error) this._error.hidden = false;\n    } else {\n      this.removeAttribute('data-invalid');\n      this._input?.removeAttribute('aria-invalid');\n      if (this._error) this._error.hidden = true;\n    }\n  }\n\n  // ─── Public API ───\n\n  /** Set field to invalid state with a message */\n  setError(message) {\n    if (this._error && message) {\n      this._error.textContent = message;\n    }\n    this._setInvalid(true);\n  }\n\n  /** Clear error state */\n  clearError() {\n    this._setInvalid(false);\n  }\n\n  /** Check if field is currently invalid */\n  get invalid() {\n    return this.hasAttribute('data-invalid');\n  }\n\n  set invalid(val) {\n    this._setInvalid(val);\n  }\n}\n\nif (!customElements.get('ren-field')) {\n  customElements.define('ren-field', RenField);\n}\n",
      "path": "components/primitives/ren-field",
      "id": "component:primitive:ren-field",
      "type": "component",
      "name": "ren-field"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-icon",
          ".ren-icon-2xl",
          ".ren-icon-danger",
          ".ren-icon-lg",
          ".ren-icon-md",
          ".ren-icon-muted",
          ".ren-icon-primary",
          ".ren-icon-sm",
          ".ren-icon-spin",
          ".ren-icon-success",
          ".ren-icon-warning",
          ".ren-icon-xl",
          ".ren-icon-xs"
        ],
        "tokens": [
          "--color-accent",
          "--color-danger",
          "--color-success",
          "--color-text-secondary",
          "--color-warning",
          "--duration-loop",
          "--ease-loop",
          "--ren-icon-color",
          "--ren-icon-fill",
          "--ren-icon-size"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-icon Component Contract\n\nIcon sizing, color, and motion utility for SVG icons.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-icon` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-icon` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Icon primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a consistent sizing wrapper (xs/sm/md/lg/xl/2xl) around an inline SVG.\"\n    - \"You need an icon that inherits the parent text color via currentColor.\"\n    - \"You need a semantic color tint (primary, success, warning, danger, muted) on a glyph.\"\n    - \"You need a spinning loading glyph that respects prefers-reduced-motion.\"\n    - \"You need an icon slot inside ren-button, ren-link, ren-badge, or other primitives.\"\n  avoidWhen:\n    - \"The graphic is decorative and inline — a raw <svg aria-hidden=\\\"true\\\"> is enough.\"\n    - \"The element is a clickable control — wrap the icon inside ren-button (ren-btn-icon) instead.\"\n    - \"You need a multi-color illustration or logo — use a plain <svg> with its own viewport.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-icon/ren-icon.css\"\n  notes:\n    - \"CSS-only primitive; no JavaScript file exists for ren-icon.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Always wrap a real <svg> (or icon-font glyph) inside <span class=\\\"ren-icon\\\"> — never style the SVG directly with .ren-icon.\"\n  - \"Decorative icons inside text or buttons must set aria-hidden=\\\"true\\\" on the SVG (the wrapper has no role).\"\n  - \"Standalone meaningful icons must have an accessible name on the parent control (aria-label) or visually-hidden text — the .ren-icon wrapper itself does not carry a name.\"\n  - \"Pick exactly one size variant (.ren-icon-xs through .ren-icon-2xl); the base .ren-icon defaults to md (20px).\"\n  - \".ren-icon-spin must be paired with role=\\\"status\\\" or aria-busy on a parent for screen reader users.\"\n\nforbiddenPatterns:\n  - \"Setting width/height inline on <svg> instead of using the .ren-icon-* size variants.\"\n  - \"Hardcoding fill=\\\"#...\\\" on the inner SVG — drive color via currentColor and the .ren-icon-* color variants.\"\n  - \"Using .ren-icon-danger to convey state without an accompanying text label (state by color alone).\"\n  - \"Animating the spin with a custom CSS animation instead of .ren-icon-spin (loses reduced-motion guard).\"\n  - \"Using .ren-icon as a clickable target — wrap with a real <button> or use ren-button instead.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-icon-color, --ren-icon-fill, --ren-icon-size.\"\n    - \"Semantic tokens consumed internally: --color-accent, --color-success, --color-warning, --color-danger, --color-text-secondary.\"\n    - \"Motion tokens: --duration-loop, --ease-loop (for .ren-icon-spin).\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex or named colors on the SVG or wrapper.\"\n    - \"Raw animation durations; rely on --duration-loop / --ease-loop via .ren-icon-spin.\"\n\naccessibility:\n  required:\n    - \"Decorative icons set aria-hidden=\\\"true\\\" on the SVG element.\"\n    - \"Meaningful icons get their accessible name from the surrounding control (button aria-label, link text, etc.) — never from the .ren-icon wrapper alone.\"\n    - \"Color variants (.ren-icon-success, .ren-icon-danger) must be paired with text or aria-label so meaning is not color-only.\"\n    - \".ren-icon-spin animation honors prefers-reduced-motion: reduce automatically.\"\n    - \"Do not turn .ren-icon into an interactive target; it has no focus styles or hit-area guarantees.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-icon/ren-icon.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<span class=\"ren-icon\" aria-hidden=\"true\">\n  <svg viewBox=\"0 0 24 24\" focusable=\"false\"><path d=\"M5 12h14\"></path></svg>\n</span>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-icon`\n- `.ren-icon-2xl`\n- `.ren-icon-danger`\n- `.ren-icon-lg`\n- `.ren-icon-md`\n- `.ren-icon-muted`\n- `.ren-icon-primary`\n- `.ren-icon-sm`\n- `.ren-icon-spin`\n- `.ren-icon-success`\n- `.ren-icon-warning`\n- `.ren-icon-xl`\n- `.ren-icon-xs`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-icon-color`\n- `--ren-icon-fill`\n- `--ren-icon-size`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-icon/ren-icon.css`\n- `docs/components/ren-icon.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Icon Component\n   ============================================\n   Icon sizing, coloring, and animation utilities.\n   Works with any SVG or icon font.\n\n   The base class provides inline layout,\n   color inheritance, and shrink-to-fit.\n\n   Usage:\n     <span class=\"ren-icon ren-icon-md\">\n       <svg>...</svg>\n     </span>\n\n     <button>\n       <span class=\"ren-icon ren-icon-lg ren-icon-success\">\n         <svg>...</svg>\n       </span>\n       Export\n     </button>\n\n     <span class=\"ren-icon ren-icon-spin ren-icon-xl\">\n       <svg>...</svg>\n     </span>\n   ============================================ */\n\n/* ═══════════════════════════════\n   ICON BASE\n   ═══════════════════════════════ */\n\n.ren-icon {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n  width: var(--ren-icon-size);   /* 20px — default md */\n  height: var(--ren-icon-size);\n  color: var(--ren-icon-color);\n  fill: var(--ren-icon-fill);\n}\n\n.ren-icon > svg {\n  width: 100%;\n  height: 100%;\n  display: block;\n}\n\n/* ─── Sizes ─── */\n\n.ren-icon-xs {\n  width: 0.75rem;   /* 12px */\n  height: 0.75rem;\n}\n\n.ren-icon-sm {\n  width: 1rem;      /* 16px */\n  height: 1rem;\n}\n\n.ren-icon-md {\n  width: 1.25rem;   /* 20px */\n  height: 1.25rem;\n}\n\n.ren-icon-lg {\n  width: 1.5rem;    /* 24px */\n  height: 1.5rem;\n}\n\n.ren-icon-xl {\n  width: 2rem;      /* 32px */\n  height: 2rem;\n}\n\n.ren-icon-2xl {\n  width: 3rem;      /* 48px */\n  height: 3rem;\n}\n\n/* ─── Color Variants ─── */\n\n.ren-icon-primary {\n  color: var(--color-accent);\n}\n\n.ren-icon-success {\n  color: var(--color-success);\n}\n\n.ren-icon-warning {\n  color: var(--color-warning);\n}\n\n.ren-icon-danger {\n  color: var(--color-danger);\n}\n\n.ren-icon-muted {\n  color: var(--color-text-secondary);\n}\n\n/* ─── Animation ─── */\n\n.ren-icon-spin {\n  animation: ren-icon-spin var(--duration-loop) var(--ease-loop) infinite;\n}\n\n@keyframes ren-icon-spin {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n/* Respect prefers-reduced-motion */\n@media (prefers-reduced-motion: reduce) {\n  .ren-icon-spin {\n    animation: none;\n  }\n}\n",
      "path": "components/primitives/ren-icon",
      "id": "component:primitive:ren-icon",
      "type": "component",
      "name": "ren-icon"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-kbd"
        ],
        "tokens": [
          "--ren-kbd-bg",
          "--ren-kbd-border",
          "--ren-kbd-color",
          "--ren-kbd-font-family",
          "--ren-kbd-font-size",
          "--ren-kbd-padding",
          "--ren-kbd-radius",
          "--stroke-1",
          "--weight-medium"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-kbd Component Contract\n\nKeyboard key / shortcut primitive for documentation and command hints.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-kbd` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-kbd` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Kbd primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need to display a single keyboard key (⌘, K, Esc, Enter) in documentation or command hints.\"\n    - \"You need to render a keyboard combo (⌘ + K) where each key is a chip with native <kbd> semantics.\"\n    - \"You want monospaced, chip-styled tokens with a sunken background and bottom-shadow border.\"\n    - \"You need an inline label inside menu items, tooltips, or docs showing the shortcut for an action.\"\n  avoidWhen:\n    - \"The element is a status pill or category label — use ren-badge or ren-tag.\"\n    - \"The element is a clickable shortcut trigger — use ren-button and place a <kbd> inside its label.\"\n    - \"You need block-level code samples — use <pre><code> or a code-block component, not <kbd>.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-kbd/ren-kbd.css\"\n  notes:\n    - \"CSS-only primitive; no JavaScript file exists for ren-kbd.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Always use a real <kbd> element with class=\\\"ren-kbd\\\"; never style a <span> or <code> as a kbd chip.\"\n  - \"Each key in a combo is its own <kbd class=\\\"ren-kbd\\\">; the joiner (+) lives between them as plain text.\"\n  - \"Inside running prose, place the <kbd> inline (it is display: inline-flex with no forced line break).\"\n  - \"Use white-space: nowrap behavior built in — do not wrap the key glyph across lines via extra markup.\"\n\nforbiddenPatterns:\n  - \"<span class=\\\"ren-kbd\\\"> or <code class=\\\"ren-kbd\\\"> — must be a real <kbd> for assistive tech.\"\n  - \"Hardcoded background or border colors that bypass --ren-kbd-bg / --ren-kbd-border.\"\n  - \"Adding click handlers on .ren-kbd to fire a shortcut — wrap the kbd inside a ren-button instead.\"\n  - \"Using <kbd class=\\\"ren-kbd\\\"> for a multi-character command name (e.g., \\\"git status\\\") — use <code> for that.\"\n  - \"Stacking multiple keys inside a single <kbd> (e.g., <kbd>⌘K</kbd>) — split them into one <kbd> per key.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-kbd-bg, --ren-kbd-border, --ren-kbd-color, --ren-kbd-font-family, --ren-kbd-font-size, --ren-kbd-padding, --ren-kbd-radius.\"\n    - \"Semantic tokens consumed internally: --color-text, --color-surface-sunken, --color-border-strong.\"\n    - \"Type tokens: --font-mono, --caption-size, --weight-medium.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Custom font-family overrides that bypass --ren-kbd-font-family / --font-mono.\"\n\naccessibility:\n  required:\n    - \"Use the native <kbd> element so screen readers announce \\\"keyboard input\\\" semantics.\"\n    - \"Keep contrast on the chip background ≥ WCAG AA against surrounding text (driven by --color-surface-sunken / --color-text).\"\n    - \"Do not rely on the chip styling alone to convey shortcut meaning; pair with descriptive text (e.g., \\\"Press <kbd>⌘</kbd> + <kbd>K</kbd> to open search\\\").\"\n    - \"Do not make .ren-kbd interactive — it has no focus ring or hit target.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-kbd/ren-kbd.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<kbd class=\"ren-kbd\">⌘</kbd>+<kbd class=\"ren-kbd\">K</kbd>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-kbd`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-kbd-bg`\n- `--ren-kbd-border`\n- `--ren-kbd-color`\n- `--ren-kbd-font-family`\n- `--ren-kbd-font-size`\n- `--ren-kbd-padding`\n- `--ren-kbd-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-kbd/ren-kbd.css`\n- `docs/components/ren-kbd.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Kbd (keyboard shortcut)\n   ============================================\n   Inline chip showing a keyboard key or shortcut\n   combo. Uses native <kbd> semantics.\n\n   Usage:\n     <kbd class=\"ren-kbd\">⌘</kbd>\n     <kbd class=\"ren-kbd\">K</kbd>\n\n   Combo:\n     <kbd class=\"ren-kbd\">⌘</kbd> + <kbd class=\"ren-kbd\">K</kbd>\n   ============================================ */\n\n.ren-kbd {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  min-width: 1.5em;\n  padding: var(--ren-kbd-padding);\n  font-family: var(--ren-kbd-font-family);\n  font-size: var(--ren-kbd-font-size);\n  font-weight: var(--weight-medium);\n  line-height: 1.4;\n  color: var(--ren-kbd-color);\n  background-color: var(--ren-kbd-bg);\n  border: var(--stroke-1) solid var(--ren-kbd-border);\n  border-bottom-width: 2px;\n  border-radius: var(--ren-kbd-radius);\n  white-space: nowrap;\n}\n",
      "path": "components/primitives/ren-kbd",
      "id": "component:primitive:ren-kbd",
      "type": "component",
      "name": "ren-kbd"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-link",
          ".ren-link-external",
          ".ren-link-muted",
          ".ren-link-nav",
          ".ren-link-plain",
          ".ren-link-skip"
        ],
        "tokens": [
          "--color-accent",
          "--color-accent-subtle",
          "--color-fill",
          "--color-focus-ring",
          "--color-on-accent",
          "--color-text",
          "--color-text-link",
          "--color-text-link-hover",
          "--color-text-muted",
          "--duration-tactile",
          "--ease-enter",
          "--radius-md",
          "--radius-sm",
          "--ren-link-color",
          "--ren-link-font-weight",
          "--ren-link-hover-color",
          "--ren-link-underline",
          "--ring-width",
          "--space-1",
          "--space-2",
          "--space-3",
          "--space-4",
          "--touch-min",
          "--weight-medium",
          "--weight-semibold"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-link Component Contract\n\nAccessible link styling for inline, muted, nav, external, and skip links.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-link` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-link` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Link primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The element navigates to a URL, anchor, or routed page (i.e., it has a real href).\"\n    - \"You need inline text links inside paragraphs styled with underline + hover.\"\n    - \"You need a navigation link with a 44px touch target (.ren-link-nav) plus active-route styling via aria-current=\\\"page\\\" / [data-active].\"\n    - \"You need an external-link affordance with a trailing ↗ marker (.ren-link-external).\"\n    - \"You need a skip-to-content link that becomes visible only on focus (.ren-link-skip).\"\n    - \"You need a muted or unstyled (inherits color) anchor (.ren-link-muted, .ren-link-plain).\"\n  avoidWhen:\n    - \"The element triggers an imperative action (submit, open dialog, delete) — use ren-button.\"\n    - \"The element is a tab, menuitem, or pagination control — use the corresponding component.\"\n    - \"The element is a stateful toggle — use a switch, checkbox, or button with aria-pressed.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-link/ren-link.css\"\n  notes:\n    - \"CSS-only primitive; no JavaScript file exists for ren-link.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Always render an actual <a href=\\\"...\\\"> element. Never style a <button> or <span> as a link.\"\n  - \"External links should set rel=\\\"noopener noreferrer\\\" and target=\\\"_blank\\\" alongside .ren-link-external when opening in a new tab.\"\n  - \"Nav links representing the current page set aria-current=\\\"page\\\" (or data-active for non-routing states) on the <a>.\"\n  - \"Skip links (.ren-link-skip) must be the first focusable element in <body> and reference a real target id (e.g., href=\\\"#main\\\").\"\n  - \"The trailing ↗ on .ren-link-external is decorative content — never use it as the only signal that a link opens externally; provide aria-label or visually-hidden text for screen readers if needed.\"\n\nforbiddenPatterns:\n  - \"<button class=\\\"ren-link\\\"> — use a real <a> with an href.\"\n  - \"<a class=\\\"ren-link\\\" onclick=\\\"...\\\"> without an href — non-focusable, breaks keyboard activation.\"\n  - \"Removing the focus ring (outline: none) without restoring a visible :focus-visible style.\"\n  - \"Hardcoded color overrides like style=\\\"color:#0066cc\\\" — use --ren-link-color / --color-text-link.\"\n  - \"Using .ren-link-nav for inline prose links; it expects flex layout and a 44px hit area.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-link-color, --ren-link-font-weight, --ren-link-hover-color, --ren-link-underline.\"\n    - \"Semantic tokens: --color-text-link, --color-text-link-hover, --color-text, --color-text-muted, --color-accent, --color-accent-subtle, --color-on-accent, --color-focus-ring, --color-fill.\"\n    - \"Layout / motion tokens: --space-*, --radius-sm, --radius-md, --touch-min, --ring-width, --duration-tactile, --ease-enter.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Custom underline colors via text-decoration shorthand that bypass --ren-link-underline.\"\n\naccessibility:\n  required:\n    - \"Real <a href> semantics; never simulate a link with a div + onclick.\"\n    - \"Visible :focus-visible ring driven by --color-focus-ring and --ring-width.\"\n    - \".ren-link-nav guarantees a 44px touch target via min-height: var(--touch-min).\"\n    - \"The current navigation item must set aria-current=\\\"page\\\" so screen readers announce route position.\"\n    - \"Skip links must be reachable as the first Tab stop and become visually focused via .ren-link-skip:focus.\"\n    - \"External-link ↗ glyph is a visual hint only; do not rely on it for assistive-tech announcement.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-link/ren-link.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<a class=\"ren-link\" href=\"/docs\">Read the documentation</a>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-link`\n- `.ren-link-external`\n- `.ren-link-muted`\n- `.ren-link-nav`\n- `.ren-link-plain`\n- `.ren-link-skip`\n\n## States And Attributes\n\n- `[aria-current]`\n- `[data-active]`\n- `:active`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-link-color`\n- `--ren-link-font-weight`\n- `--ren-link-hover-color`\n- `--ren-link-underline`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-link/ren-link.css`\n- `docs/components/ren-link.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Link Component\n   ============================================\n   Styled anchor with variants.\n   CSS-only. No JS needed.\n\n   44px touch target on mobile via min-height.\n   Accessible focus ring on keyboard navigation.\n\n   Usage:\n     <a href=\"/page\" class=\"ren-link\">Standard link</a>\n     <a href=\"/page\" class=\"ren-link ren-link-muted\">Subtle link</a>\n     <a href=\"/page\" class=\"ren-link ren-link-external\">External ↗</a>\n   ============================================ */\n\n/* ─── Base Link ─── */\n.ren-link {\n  display: inline;\n  color: var(--ren-link-color);\n  font-weight: var(--ren-link-font-weight, inherit);\n  background: none;\n  text-decoration: underline;\n  text-decoration-color: color-mix(in oklch, var(--color-text-link), transparent 60%);\n  text-underline-offset: 0.15em;\n  text-decoration-thickness: var(--ren-link-underline);\n  cursor: pointer;\n  transition:\n    color var(--duration-tactile) var(--ease-enter),\n    text-decoration-color var(--duration-tactile) var(--ease-enter);\n  /* Inline elements: ensure touch target via padding */\n  -webkit-tap-highlight-color: transparent;\n}\n\n.ren-link:hover {\n  color: var(--ren-link-hover-color);\n  background: none;\n  text-decoration: underline;\n  text-decoration-color: color-mix(in oklch, var(--color-text-link-hover), transparent 30%);\n  text-decoration-thickness: calc(var(--ren-link-underline) * 2);\n}\n\n.ren-link:active {\n  opacity: 0.8;\n}\n\n.ren-link:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: 2px;\n  border-radius: var(--radius-sm);\n}\n\n/* ─── Muted / Subtle ─── */\n.ren-link-muted {\n  color: var(--color-text-muted);\n  text-decoration: none;\n}\n\n.ren-link-muted:hover {\n  color: var(--color-text);\n  text-decoration: underline;\n}\n\n/* ─── Unstyled (inherits parent color) ─── */\n.ren-link-plain {\n  color: inherit;\n  text-decoration: none;\n}\n\n.ren-link-plain:hover {\n  text-decoration: underline;\n}\n\n/* ─── External link indicator ─── */\n.ren-link-external::after {\n  content: ' ↗';\n  font-size: 0.85em;\n  text-decoration: none;\n  display: inline;\n}\n\n/* ─── Nav link (block-level, with padding for touch target) ─── */\n.ren-link-nav {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  min-height: var(--touch-min);\n  padding: var(--space-1) var(--space-2);\n  color: var(--color-text);\n  text-decoration: none;\n  border-radius: var(--radius-md);\n  transition:\n    background-color var(--duration-tactile) var(--ease-enter),\n    color var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-link-nav:hover {\n  background-color: var(--color-fill);\n  color: var(--color-text);\n  text-decoration: none;\n}\n\n.ren-link-nav[aria-current=\"page\"],\n.ren-link-nav[data-active] {\n  background-color: var(--color-accent-subtle);\n  color: var(--color-accent);\n  font-weight: var(--weight-medium);\n}\n\n.ren-link-nav:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: -2px;\n}\n\n/* ─── Skip link (a11y) ─── */\n.ren-link-skip {\n  position: absolute;\n  top: -100%;\n  left: var(--space-3);\n  padding: var(--space-2) var(--space-4);\n  background-color: var(--color-accent);\n  color: var(--color-on-accent);\n  border-radius: var(--radius-md);\n  font-weight: var(--weight-semibold);\n  text-decoration: none;\n  z-index: 9999;\n  transition: top var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-link-skip:focus {\n  top: var(--space-3);\n}\n",
      "path": "components/primitives/ren-link",
      "id": "component:primitive:ren-link",
      "type": "component",
      "name": "ren-link"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-pagination",
          ".ren-pagination-centered",
          ".ren-pagination-compact",
          ".ren-pagination-ellipsis",
          ".ren-pagination-info",
          ".ren-pagination-item",
          ".ren-pagination-next",
          ".ren-pagination-prev",
          ".ren-pagination-simple"
        ],
        "tokens": [
          "--caption-size",
          "--color-disabled-text",
          "--color-fill-hover",
          "--color-focus-ring",
          "--color-text",
          "--color-text-muted",
          "--duration-tactile",
          "--ease-enter",
          "--ren-pagination-active-bg",
          "--ren-pagination-active-color",
          "--ren-pagination-bg",
          "--ren-pagination-font-size",
          "--ren-pagination-gap",
          "--ren-pagination-hover-bg",
          "--ren-pagination-radius",
          "--ren-pagination-size",
          "--ring-offset-width",
          "--ring-width",
          "--size-sm",
          "--space-1",
          "--space-2",
          "--stroke-1",
          "--text-sm",
          "--touch-min",
          "--weight-medium"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-pagination Component Contract\n\nPagination navigation for page ranges and previous/next movement.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-pagination` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-pagination` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Pagination primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"A list, table, or grid is broken into discrete numbered pages with a known total count.\"\n    - \"You need prev / next + numbered items with aria-current=\\\"page\\\" on the active page.\"\n    - \"You need a minimal prev / info / next layout (.ren-pagination-simple) when total pages is unknown.\"\n    - \"You need a compact variant for dense tables (.ren-pagination-compact).\"\n    - \"You need a centered standalone block under content (.ren-pagination-centered).\"\n    - \"You need an ellipsis spacer between page ranges (.ren-pagination-ellipsis).\"\n  avoidWhen:\n    - \"The feed is infinite-scroll or load-more — use a button + scroll observer, not pagination.\"\n    - \"The data is paged by cursor with no concept of page numbers — show only prev/next.\"\n    - \"You need step navigation inside a wizard — use a stepper component.\"\n    - \"The navigation is between sibling sections of a page — use tabs or anchor links.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-pagination/ren-pagination.css\"\n  notes:\n    - \"CSS-only primitive; no JavaScript file exists for ren-pagination.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Wrap the control in a real <nav aria-label=\\\"Pagination\\\"> with class=\\\"ren-pagination\\\".\"\n  - \"Each page link is an <a href=\\\"...\\\"> with class=\\\"ren-pagination-item\\\"; mark the active page with aria-current=\\\"page\\\" (it auto-disables pointer-events).\"\n  - \"Prev / next are <a class=\\\"ren-pagination-prev\\\"> / <a class=\\\"ren-pagination-next\\\"> with aria-label describing direction when the content is a glyph (e.g., aria-label=\\\"Previous page\\\").\"\n  - \"Use <span class=\\\"ren-pagination-ellipsis\\\" aria-hidden=\\\"true\\\">…</span> for visual gaps — it must not be focusable.\"\n  - \"Disabled prev/next use aria-disabled=\\\"true\\\" (preferred for <a>) or disabled (for <button>).\"\n\nforbiddenPatterns:\n  - \"<div role=\\\"navigation\\\"> or <ul> instead of <nav> for the wrapper.\"\n  - \"Plain <span> or <button> styled as a pagination item — use <a class=\\\"ren-pagination-item\\\">.\"\n  - \"Marking the active page with only a CSS class — must include aria-current=\\\"page\\\" for screen readers.\"\n  - \"Hiding disabled prev/next via display: none — keep them in DOM with aria-disabled so layout stays stable.\"\n  - \"Hardcoded color overrides on the active item that bypass --color-accent / --color-on-accent.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-pagination-active-bg, --ren-pagination-active-color, --ren-pagination-bg, --ren-pagination-font-size, --ren-pagination-gap, --ren-pagination-hover-bg, --ren-pagination-radius, --ren-pagination-size.\"\n    - \"Semantic tokens consumed internally: --color-text, --color-text-muted, --color-accent, --color-on-accent, --color-fill, --color-fill-hover, --color-disabled-text, --color-focus-ring.\"\n    - \"Layout tokens: --touch-min, --size-sm, --space-1, --space-2, --radius-md, --stroke-1, --ring-width, --ring-offset-width, --duration-tactile, --ease-enter.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Inline width/height styling that breaks min 44px touch target on default variant.\"\n\naccessibility:\n  required:\n    - \"Wrapper is a real <nav> landmark with aria-label=\\\"Pagination\\\" (or equivalent).\"\n    - \"Active page uses aria-current=\\\"page\\\"; do not duplicate it on a separate <span>.\"\n    - \"Default and simple variants guarantee min 44px touch targets (--touch-min); compact variant is for non-touch contexts only.\"\n    - \"Visible :focus-visible ring driven by --color-focus-ring + --ring-width / --ring-offset-width.\"\n    - \"Glyph-only prev/next provide an aria-label so the direction is announced.\"\n    - \"Disabled prev/next set aria-disabled=\\\"true\\\" AND pointer-events: none (handled by CSS); do not rely on visual dimming alone.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-pagination/ren-pagination.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<nav class=\"ren-pagination\" aria-label=\"Pagination\">\n  <a class=\"ren-pagination-prev\" href=\"?page=1\" aria-label=\"Previous page\">‹</a>\n  <a class=\"ren-pagination-item\" href=\"?page=1\">1</a>\n  <a class=\"ren-pagination-item\" href=\"?page=2\" aria-current=\"page\">2</a>\n  <a class=\"ren-pagination-next\" href=\"?page=3\" aria-label=\"Next page\">›</a>\n</nav>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-pagination`\n- `.ren-pagination-centered`\n- `.ren-pagination-compact`\n- `.ren-pagination-ellipsis`\n- `.ren-pagination-info`\n- `.ren-pagination-item`\n- `.ren-pagination-next`\n- `.ren-pagination-prev`\n- `.ren-pagination-simple`\n\n## States And Attributes\n\n- `[aria-current]`\n- `[aria-disabled]`\n- `:active`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-pagination-active-bg`\n- `--ren-pagination-active-color`\n- `--ren-pagination-bg`\n- `--ren-pagination-font-size`\n- `--ren-pagination-gap`\n- `--ren-pagination-hover-bg`\n- `--ren-pagination-radius`\n- `--ren-pagination-size`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-pagination/ren-pagination.css`\n- `docs/components/ren-pagination.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Pagination Component\n   ============================================\n   Page navigation for lists/tables.\n   CSS-only. No JS needed for styling.\n\n   Semantic <nav> + links for accessibility.\n   44px touch targets on all interactive elements.\n\n   Usage (simple):\n     <nav aria-label=\"Pagination\" class=\"ren-pagination\">\n       <a href=\"?p=1\" class=\"ren-pagination-prev\" aria-label=\"Previous page\">&lsaquo;</a>\n       <a href=\"?p=1\" class=\"ren-pagination-item\">1</a>\n       <a href=\"?p=2\" class=\"ren-pagination-item\" aria-current=\"page\">2</a>\n       <a href=\"?p=3\" class=\"ren-pagination-item\">3</a>\n       <a href=\"?p=3\" class=\"ren-pagination-next\" aria-label=\"Next page\">&rsaquo;</a>\n     </nav>\n\n   Usage (minimal — just prev/next):\n     <nav aria-label=\"Pagination\" class=\"ren-pagination ren-pagination-simple\">\n       <a href=\"?p=1\" class=\"ren-pagination-prev\">Previous</a>\n       <span class=\"ren-pagination-info\">Page 2 of 10</span>\n       <a href=\"?p=3\" class=\"ren-pagination-next\">Next</a>\n     </nav>\n   ============================================ */\n\n.ren-pagination {\n  display: flex;\n  align-items: center;\n  gap: var(--ren-pagination-gap);\n}\n\n/* ─── Page items ─── */\n.ren-pagination-item,\n.ren-pagination-prev,\n.ren-pagination-next {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  min-width: var(--ren-pagination-size);\n  min-height: var(--ren-pagination-size);\n  padding: var(--space-1) var(--space-2);\n  font-size: var(--ren-pagination-font-size);\n  font-weight: var(--weight-medium);\n  color: var(--color-text);\n  text-decoration: none;\n  border-radius: var(--ren-pagination-radius);\n  border: var(--stroke-1) solid transparent;\n  background-color: var(--ren-pagination-bg);\n  transition:\n    background-color var(--duration-tactile) var(--ease-enter),\n    border-color var(--duration-tactile) var(--ease-enter);\n  user-select: none;\n}\n\n.ren-pagination-item:hover,\n.ren-pagination-prev:hover,\n.ren-pagination-next:hover {\n  background-color: var(--ren-pagination-hover-bg);\n}\n\n.ren-pagination-item:active,\n.ren-pagination-prev:active,\n.ren-pagination-next:active {\n  background-color: var(--color-fill-hover);\n}\n\n.ren-pagination-item:focus-visible,\n.ren-pagination-prev:focus-visible,\n.ren-pagination-next:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: var(--ring-offset-width);\n}\n\n/* Current page */\n.ren-pagination-item[aria-current=\"page\"] {\n  background-color: var(--ren-pagination-active-bg);\n  color: var(--ren-pagination-active-color);\n  pointer-events: none;\n}\n\n/* Disabled prev/next */\n.ren-pagination-prev[aria-disabled=\"true\"],\n.ren-pagination-next[aria-disabled=\"true\"],\n.ren-pagination-prev:disabled,\n.ren-pagination-next:disabled {\n  color: var(--color-disabled-text);\n  pointer-events: none;\n  opacity: 0.5;\n}\n\n/* Ellipsis */\n.ren-pagination-ellipsis {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  min-width: var(--touch-min);\n  min-height: var(--touch-min);\n  color: var(--color-text-muted);\n  font-size: var(--text-sm);\n  user-select: none;\n}\n\n/* ─── Simple variant (prev + info + next) ─── */\n.ren-pagination-simple {\n  gap: var(--space-2);\n}\n\n.ren-pagination-info {\n  font-size: var(--caption-size, var(--text-sm));\n  color: var(--color-text-muted);\n  white-space: nowrap;\n  padding: 0 var(--space-2);\n}\n\n/* ─── Compact variant (smaller touch targets) ─── */\n.ren-pagination-compact .ren-pagination-item,\n.ren-pagination-compact .ren-pagination-prev,\n.ren-pagination-compact .ren-pagination-next {\n  min-width: var(--size-sm);\n  min-height: var(--size-sm);\n  padding: var(--space-1);\n  font-size: var(--caption-size, var(--text-sm));\n}\n\n/* ─── Centered (for standalone pagination) ─── */\n.ren-pagination-centered {\n  justify-content: center;\n}\n",
      "path": "components/primitives/ren-pagination",
      "id": "component:primitive:ren-pagination",
      "type": "component",
      "name": "ren-pagination"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-meter",
          ".ren-meter-lg",
          ".ren-meter-sm",
          ".ren-meter-xl",
          ".ren-progress",
          ".ren-progress-bar",
          ".ren-progress-danger",
          ".ren-progress-indeterminate",
          ".ren-progress-info",
          ".ren-progress-label",
          ".ren-progress-lg",
          ".ren-progress-sm",
          ".ren-progress-success",
          ".ren-progress-value",
          ".ren-progress-warning",
          ".ren-progress-xl"
        ],
        "tokens": [
          "--color-danger",
          "--color-fill",
          "--color-info",
          "--color-success",
          "--color-text",
          "--color-text-secondary",
          "--color-warning",
          "--ease-enter",
          "--label-size",
          "--radius-full",
          "--ren-progress-bg",
          "--ren-progress-duration",
          "--ren-progress-easing",
          "--ren-progress-fill",
          "--ren-progress-height",
          "--ren-progress-radius",
          "--space-1",
          "--weight-medium",
          "--weight-regular"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-progress Component Contract\n\nProgress and meter styling for determinate and indeterminate completion states.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-progress` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-progress` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Progress primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a determinate horizontal bar showing percent completion of a known task (.ren-progress + .ren-progress-bar with inline width).\"\n    - \"You need an indeterminate loading bar with sliding animation (.ren-progress-indeterminate).\"\n    - \"You need a semantic-colored progress state (success / warning / danger / info).\"\n    - \"You need a meter (current value within a min/max range, not progress) — use <meter class=\\\"ren-meter\\\">.\"\n    - \"You need optional caption rows above (.ren-progress-label) and below (.ren-progress-value) the bar.\"\n    - \"You need multiple bar heights: sm (4px), md (8px default), lg (12px), xl (16px).\"\n  avoidWhen:\n    - \"You need a circular spinner — use .ren-icon-spin on a loading glyph.\"\n    - \"You need a busy state on a button — use ren-button with data-loading / aria-busy.\"\n    - \"You need a step indicator with discrete milestones — use a stepper component.\"\n    - \"You need a skeleton placeholder while content loads — use ren-skeleton.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-progress/ren-progress.css\"\n  notes:\n    - \"CSS-only primitive; no JavaScript file exists for ren-progress.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Determinate progress: <div class=\\\"ren-progress\\\" role=\\\"progressbar\\\" aria-valuemin=\\\"0\\\" aria-valuemax=\\\"100\\\" aria-valuenow=\\\"<pct>\\\"><div class=\\\"ren-progress-bar\\\" style=\\\"width: <pct>%\\\"></div></div>.\"\n  - \"Indeterminate progress: add class .ren-progress-indeterminate to the outer and omit aria-valuenow (set aria-valuetext or aria-busy=\\\"true\\\" instead).\"\n  - \"Native semantics alternative: <progress class=\\\"ren-progress\\\" max=\\\"100\\\" value=\\\"<pct>\\\"></progress> when no custom inner bar is needed.\"\n  - \"Meter: <meter class=\\\"ren-meter\\\" min=\\\"0\\\" max=\\\"10\\\" value=\\\"<n>\\\"> — for a current value in a range (battery, score), not for progress over time.\"\n  - \"Provide an accessible label via aria-label, aria-labelledby, or a sibling .ren-progress-label.\"\n\nforbiddenPatterns:\n  - \"<div class=\\\"ren-progress\\\"> without role=\\\"progressbar\\\" + aria-value* (when not using native <progress>).\"\n  - \"Animating bar width with a custom transition that bypasses --duration-state / --ease-enter.\"\n  - \"Using .ren-progress-danger to convey state by color alone — pair with a text label.\"\n  - \"Hardcoded background colors on .ren-progress-bar that bypass --color-accent / semantic variants.\"\n  - \"Using <meter> for a progress-over-time scenario or <progress> for a current-value-in-range scenario.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-progress-bg, --ren-progress-duration, --ren-progress-easing, --ren-progress-fill, --ren-progress-height, --ren-progress-radius.\"\n    - \"Semantic tokens consumed internally: --color-fill, --color-accent, --color-success, --color-warning, --color-danger, --color-info, --color-text, --color-text-secondary.\"\n    - \"Layout / type tokens: --radius-full, --label-size, --weight-medium, --weight-regular, --space-1, --duration-state, --ease-enter.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Raw animation durations; rely on --ren-progress-duration / --duration-state.\"\n\naccessibility:\n  required:\n    - \"Determinate bars expose role=\\\"progressbar\\\" + aria-valuemin / aria-valuemax / aria-valuenow (or use native <progress>).\"\n    - \"Indeterminate bars omit aria-valuenow and set aria-busy=\\\"true\\\" or aria-valuetext=\\\"Loading\\\" so AT can announce status.\"\n    - \"Every progress bar has an accessible name (aria-label, aria-labelledby, or a .ren-progress-label paired by id).\"\n    - \"Color-coded variants (success / warning / danger / info) must be paired with a text label or .ren-progress-value — never color-only.\"\n    - \"Indeterminate animation auto-honors prefers-reduced-motion: reduce (slide is suppressed, bar holds at 50%).\"\n    - \"Width transition is suppressed under reduced motion so value updates do not animate.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-progress/ren-progress.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div class=\"ren-progress\" role=\"progressbar\" aria-label=\"Upload progress\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"65\">\n  <div class=\"ren-progress-bar\" style=\"width: 65%\"></div>\n</div>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-meter`\n- `.ren-meter-lg`\n- `.ren-meter-sm`\n- `.ren-meter-xl`\n- `.ren-progress`\n- `.ren-progress-bar`\n- `.ren-progress-danger`\n- `.ren-progress-indeterminate`\n- `.ren-progress-info`\n- `.ren-progress-label`\n- `.ren-progress-lg`\n- `.ren-progress-sm`\n- `.ren-progress-success`\n- `.ren-progress-value`\n- `.ren-progress-warning`\n- `.ren-progress-xl`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-progress-bg`\n- `--ren-progress-duration`\n- `--ren-progress-easing`\n- `--ren-progress-fill`\n- `--ren-progress-height`\n- `--ren-progress-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-progress/ren-progress.css`\n- `docs/components/ren-progress.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Progress Bar & Meter Components\n   ============================================\n   Progress indicators and meter displays.\n   CSS-only, works with <progress> and <meter>.\n\n   Supports multiple sizes, colors, and states.\n   Includes indeterminate animations.\n\n   Usage:\n     <div class=\"ren-progress\">\n       <div class=\"ren-progress-bar\" style=\"width: 65%\"></div>\n     </div>\n\n     <div class=\"ren-progress ren-progress-lg ren-progress-success\">\n       <div class=\"ren-progress-bar\" style=\"width: 85%\"></div>\n     </div>\n\n     <meter class=\"ren-meter\" value=\"6\" min=\"0\" max=\"10\"></meter>\n   ============================================ */\n\n/* ═══════════════════════════════\n   PROGRESS BAR\n   ═══════════════════════════════ */\n\n.ren-progress {\n  display: flex;\n  width: 100%;\n  height: var(--ren-progress-height);   /* default / md */\n  background-color: var(--ren-progress-bg);\n  border-radius: var(--ren-progress-radius);\n  overflow: hidden;\n}\n\n.ren-progress-bar {\n  height: 100%;\n  width: 0%;\n  background-color: var(--ren-progress-fill);\n  border-radius: var(--ren-progress-radius);\n  transition: width var(--ren-progress-duration) var(--ren-progress-easing);\n}\n\n/* ─── Sizes ─── */\n\n.ren-progress-sm {\n  height: 0.25rem;\n}\n\n.ren-progress-lg {\n  height: 0.75rem;\n}\n\n.ren-progress-xl {\n  height: 1rem;\n}\n\n/* ─── Color Variants ─── */\n\n.ren-progress-success .ren-progress-bar {\n  background-color: var(--color-success);\n}\n\n.ren-progress-warning .ren-progress-bar {\n  background-color: var(--color-warning);\n}\n\n.ren-progress-danger .ren-progress-bar {\n  background-color: var(--color-danger);\n}\n\n.ren-progress-info .ren-progress-bar {\n  background-color: var(--color-info);\n}\n\n/* ─── Indeterminate Animation ─── */\n\n.ren-progress-indeterminate .ren-progress-bar {\n  width: 30% !important;\n  animation: ren-progress-slide 1.5s var(--ease-enter) infinite;\n}\n\n@keyframes ren-progress-slide {\n  0% {\n    transform: translateX(-100%);\n  }\n  50% {\n    transform: translateX(500%);\n  }\n  100% {\n    transform: translateX(500%);\n  }\n}\n\n/* Respect prefers-reduced-motion */\n@media (prefers-reduced-motion: reduce) {\n  .ren-progress-indeterminate .ren-progress-bar {\n    animation: none;\n    width: 50% !important;\n  }\n\n  .ren-progress-bar {\n    transition: none;\n  }\n}\n\n/* ═══════════════════════════════\n   PROGRESS LABELS\n   ═══════════════════════════════ */\n\n.ren-progress-label {\n  display: block;\n  font-size: var(--label-size);\n  font-weight: var(--weight-medium);\n  color: var(--color-text);\n  margin: 0 0 var(--space-1);\n}\n\n.ren-progress-value {\n  display: block;\n  font-size: var(--label-size);\n  font-weight: var(--weight-regular);\n  color: var(--color-text-secondary);\n  margin: var(--space-1) 0 0;\n  text-align: end;\n}\n\n/* ═══════════════════════════════\n   METER ELEMENT\n   ═══════════════════════════════ */\n\n.ren-meter {\n  display: block;\n  width: 100%;\n  height: 0.5rem;\n  border: none;\n  border-radius: var(--radius-full);\n  overflow: hidden;\n  background-color: var(--color-fill);\n  appearance: none;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n}\n\n/* Webkit browsers (Chrome, Safari) */\n.ren-meter::-webkit-meter-inner-element {\n  border-radius: var(--radius-full);\n}\n\n.ren-meter::-webkit-meter-bar {\n  background-color: var(--color-fill);\n  border-radius: var(--radius-full);\n  overflow: hidden;\n  border: none;\n}\n\n.ren-meter::-webkit-meter-optimum-value {\n  background-color: var(--color-success);\n  border-radius: var(--radius-full);\n}\n\n.ren-meter::-webkit-meter-suboptimum-value {\n  background-color: var(--color-warning);\n  border-radius: var(--radius-full);\n}\n\n.ren-meter::-webkit-meter-even-less-good-value {\n  background-color: var(--color-danger);\n  border-radius: var(--radius-full);\n}\n\n/* Firefox */\n.ren-meter::-moz-meter-bar {\n  background: var(--color-success);\n  border-radius: var(--radius-full);\n  border: none;\n}\n\n/* ─── Meter Sizes ─── */\n\n.ren-meter-sm {\n  height: 0.25rem;\n}\n\n.ren-meter-lg {\n  height: 0.75rem;\n}\n\n.ren-meter-xl {\n  height: 1rem;\n}\n",
      "path": "components/primitives/ren-progress",
      "id": "component:primitive:ren-progress",
      "type": "component",
      "name": "ren-progress"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-radio",
          ".ren-radio-control",
          ".ren-radio-group",
          ".ren-radio-group-horizontal"
        ],
        "tokens": [
          "--body-size",
          "--color-accent",
          "--color-accent-hover",
          "--color-border-interactive",
          "--color-focus-ring",
          "--color-on-accent",
          "--color-text",
          "--duration-state",
          "--duration-tactile",
          "--ease-enter",
          "--ease-playful",
          "--radius-full",
          "--ring-offset-width",
          "--ring-width",
          "--space-2",
          "--space-3",
          "--space-4",
          "--touch-min",
          "--weight-regular"
        ],
        "hasScript": true,
        "hasDocsPage": true
      },
      "body": "# ren-radio Component Contract\n\nNative radio group styling with optional custom element keyboard enhancement.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-radio` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-radio` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Radio primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The user must pick exactly one option from 2-7 mutually-exclusive choices.\"\n    - \"All choices should be visible at once (no overflow / collapse).\"\n    - \"You need a custom-styled radio dot built on native <input type=\\\"radio\\\"> with preserved semantics.\"\n    - \"You need keyboard arrow navigation + roving tabindex via <ren-radio-group>.\"\n    - \"You need a vertical (.ren-radio-group) or horizontal (.ren-radio-group-horizontal) layout.\"\n  avoidWhen:\n    - \"There are more than ~7 options or the list overflows — use a ren-select / combobox.\"\n    - \"Multiple selections are allowed — use ren-checkbox.\"\n    - \"The choice is binary on/off — use ren-switch or ren-checkbox.\"\n    - \"The selection drives navigation or filters with immediate side effects — consider ren-segmented or tabs.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-radio/ren-radio.css\"\n  js:\n    - \"rends/components/primitives/ren-radio/ren-radio.js\"\n  notes:\n    - \"JS is only required when using <ren-radio-group> for arrow-key roving tabindex; a plain <fieldset role=\\\"radiogroup\\\"> with .ren-radio labels works CSS-only with native browser radio behavior.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Each option is a <label class=\\\"ren-radio\\\"> containing a real <input type=\\\"radio\\\" name=\\\"<group>\\\">, a <span class=\\\"ren-radio-control\\\"></span>, and the visible label text — in that DOM order so the :checked + .ren-radio-control adjacent selector works.\"\n  - \"All radios in one group share the same name attribute so the browser enforces single-selection.\"\n  - \"Wrap the group in <ren-radio-group> (auto-sets role=\\\"radiogroup\\\") or in a <fieldset> with <legend> + role=\\\"radiogroup\\\" when no JS is desired.\"\n  - \"Use <ren-radio-group orientation=\\\"horizontal\\\"> to switch to row layout; the JS swaps the class to .ren-radio-group-horizontal.\"\n  - \"Never set tabindex manually on inputs inside <ren-radio-group> — the roving-tabindex utility manages it.\"\n\nforbiddenPatterns:\n  - \"Replacing <input type=\\\"radio\\\"> with a styled <div role=\\\"radio\\\"> — loses form submission + native a11y.\"\n  - \"Hiding the input with display: none (breaks focus) — use the built-in visually-hidden clip-path pattern.\"\n  - \"Two radios with different name attributes in the same group (allows multi-selection).\"\n  - \"Using .ren-radio for an icon-only toggle without a visible text label or aria-label.\"\n  - \"Animating the dot via custom keyframes that bypass --duration-state / --ease-playful.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic tokens consumed internally: --color-text, --color-border-strong, --color-accent, --color-accent-hover, --color-on-accent, --color-focus-ring.\"\n    - \"Layout / motion tokens: --space-2, --space-3, --space-4, --radius-full, --body-size, --weight-regular, --touch-min, --ring-width, --ring-offset-width, --duration-state, --duration-tactile, --ease-enter, --ease-playful.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Custom radio sizes that fall below the 20px control + 44px label touch target.\"\n\naccessibility:\n  required:\n    - \"Real <input type=\\\"radio\\\"> elements so the browser handles single-selection, form submission, and assistive-tech announcement.\"\n    - \".ren-radio label has min-height: var(--touch-min) so the whole row is a 44px touch target.\"\n    - \"Visible :focus-visible ring on .ren-radio-control driven by --color-focus-ring (the native input is visually hidden but focus is delegated to its sibling).\"\n    - \"<ren-radio-group> sets role=\\\"radiogroup\\\" and supports arrow-key navigation (Up/Down vertical, Left/Right horizontal) with loop; selection follows focus.\"\n    - \"Disabled options use <input disabled> (handled via :has(input:disabled)) — do not rely on opacity alone.\"\n    - \"Provide a group label via <legend> inside <fieldset> or aria-label / aria-labelledby on <ren-radio-group>.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-radio/ren-radio.css\">\n<script type=\"module\" src=\"rends/components/primitives/ren-radio/ren-radio.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-radio-group>\n  <label class=\"ren-radio\"><input type=\"radio\" name=\"plan\" value=\"basic\"><span class=\"ren-radio-control\"></span><span>Basic</span></label>\n  <label class=\"ren-radio\"><input type=\"radio\" name=\"plan\" value=\"pro\"><span class=\"ren-radio-control\"></span><span>Pro</span></label>\n</ren-radio-group>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-radio`\n- `.ren-radio-control`\n- `.ren-radio-group`\n- `.ren-radio-group-horizontal`\n\n## States And Attributes\n\n- `:active`\n- `:checked`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/primitives/ren-radio/ren-radio.css`\n- `components/primitives/ren-radio/ren-radio.js`\n- `docs/components/ren-radio.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Radio Button & Radio Group Components\n   ============================================\n   Custom-styled radio buttons and radio groups\n   built on native <input type=\"radio\">.\n\n   Preserves native semantics and accessibility.\n   Custom visuals via CSS only.\n\n   Usage:\n     <div class=\"ren-radio-group\" role=\"radiogroup\">\n       <label class=\"ren-radio\">\n         <input type=\"radio\" name=\"option\" value=\"a\">\n         <span class=\"ren-radio-control\"></span>\n         <span>Option A</span>\n       </label>\n       <label class=\"ren-radio\">\n         <input type=\"radio\" name=\"option\" value=\"b\">\n         <span class=\"ren-radio-control\"></span>\n         <span>Option B</span>\n       </label>\n     </div>\n   ============================================ */\n\n/* ═══════════════════════════════\n   RADIO BUTTON\n   ═══════════════════════════════ */\n\n.ren-radio {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-2);\n  cursor: pointer;\n  font-size: var(--body-size);\n  font-weight: var(--weight-regular);\n  color: var(--color-text);\n  user-select: none;\n  /* Ensure touch target */\n  min-height: var(--touch-min);\n}\n\n/* Hide native radio visually but keep accessible */\n.ren-radio > input[type=\"radio\"] {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip-path: inset(50%);\n  white-space: nowrap;\n  border-width: 0;\n}\n\n/* Custom radio visual */\n.ren-radio-control {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 1.25rem;   /* 20px */\n  height: 1.25rem;\n  flex-shrink: 0;\n  border: 2px solid var(--color-border-interactive);\n  border-radius: var(--radius-full);\n  background-color: transparent;\n  transition:\n    background-color var(--duration-state) var(--ease-enter),\n    border-color var(--duration-state) var(--ease-enter),\n    transform var(--duration-tactile) var(--ease-playful);\n}\n\n/* Radio dot (hidden by default) */\n.ren-radio-control::after {\n  content: '';\n  display: block;\n  width: 0.5rem;\n  height: 0.5rem;\n  background-color: var(--color-on-accent);\n  border-radius: var(--radius-full);\n  transform: scale(0);\n  transition: transform var(--duration-state) var(--ease-playful);\n}\n\n/* ─── States ─── */\n\n/* Checked */\n.ren-radio > input:checked + .ren-radio-control {\n  background-color: var(--color-accent);\n  border-color: var(--color-accent);\n}\n\n.ren-radio > input:checked + .ren-radio-control::after {\n  transform: scale(1);\n}\n\n/* Hover */\n.ren-radio:hover > .ren-radio-control {\n  border-color: var(--color-accent);\n}\n\n.ren-radio:hover > input:checked + .ren-radio-control {\n  background-color: var(--color-accent-hover);\n  border-color: var(--color-accent-hover);\n}\n\n/* Focus */\n.ren-radio > input:focus-visible + .ren-radio-control {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: var(--ring-offset-width);\n}\n\n/* Active / Press */\n.ren-radio:active > .ren-radio-control {\n  transform: scale(0.9);\n}\n\n/* Disabled */\n.ren-radio:has(input:disabled) {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n\n/* ═══════════════════════════════\n   RADIO GROUP\n   ═══════════════════════════════ */\n\n.ren-radio-group {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-3);\n}\n\n.ren-radio-group-horizontal {\n  display: flex;\n  flex-direction: row;\n  gap: var(--space-4);\n}\n\n\n/**\n * RenDS — <ren-radio-group> Web Component\n * ========================================\n * Accessible radio group with keyboard navigation.\n * Implements arrow key navigation and roving tabindex.\n *\n * Uses Light DOM — no Shadow DOM.\n * Works with native <input type=\"radio\"> elements.\n *\n * Attributes:\n *   orientation: 'vertical' (default) | 'horizontal'\n *   role:        'radiogroup' (auto-set)\n *\n * Usage:\n *   <ren-radio-group>\n *     <label class=\"ren-radio\">\n *       <input type=\"radio\" name=\"option\" value=\"a\">\n *       <span class=\"ren-radio-control\"></span>\n *       <span>Option A</span>\n *     </label>\n *     <label class=\"ren-radio\">\n *       <input type=\"radio\" name=\"option\" value=\"b\">\n *       <span class=\"ren-radio-control\"></span>\n *       <span>Option B</span>\n *     </label>\n *   </ren-radio-group>\n *\n * Arrow keys navigate between radios.\n * Selecting a radio via arrow keys also checks it.\n */\n\nimport { createKeyboardNav } from '../../../utils/keyboard-nav.js';\n\nexport class RenRadioGroup extends HTMLElement {\n  static get observedAttributes() {\n    return ['orientation'];\n  }\n\n  constructor() {\n    super();\n    this._nav = null;\n  }\n\n  connectedCallback() {\n    // Set ARIA role\n    if (!this.hasAttribute('role')) {\n      this.setAttribute('role', 'radiogroup');\n    }\n\n    // Find all radio inputs\n    const radios = this.querySelectorAll('input[type=\"radio\"]');\n    if (radios.length === 0) return;\n\n    // Set up keyboard navigation using roving tabindex\n    const orientation = this.getAttribute('orientation') || 'vertical';\n\n    this._nav = createKeyboardNav(this, {\n      selector: 'input[type=\"radio\"]',\n      orientation,\n      loop: true,\n      typeahead: false,\n      focusOnHover: false,\n      onActivate: (radio, index) => {\n        // When arrow key navigation moves to a radio, check it\n        radio.checked = true;\n        // Dispatch change event to match native behavior\n        radio.dispatchEvent(new Event('change', { bubbles: true }));\n      },\n      onSelect: null,\n    });\n\n    this._nav.attach();\n\n    // Update class based on orientation\n    this._updateOrientationClass();\n  }\n\n  disconnectedCallback() {\n    if (this._nav) {\n      this._nav.detach();\n      this._nav = null;\n    }\n  }\n\n  attributeChangedCallback(name) {\n    if (name === 'orientation') {\n      this._updateOrientationClass();\n      // Detach and reattach nav with new orientation\n      if (this._nav) {\n        this._nav.detach();\n        const orientation = this.getAttribute('orientation') || 'vertical';\n        this._nav = createKeyboardNav(this, {\n          selector: 'input[type=\"radio\"]',\n          orientation,\n          loop: true,\n          typeahead: false,\n          focusOnHover: false,\n          onActivate: (radio, index) => {\n            radio.checked = true;\n            radio.dispatchEvent(new Event('change', { bubbles: true }));\n          },\n          onSelect: null,\n        });\n        this._nav.attach();\n      }\n    }\n  }\n\n  _updateOrientationClass() {\n    const orientation = this.getAttribute('orientation') || 'vertical';\n\n    // Remove existing orientation classes\n    this.classList.remove('ren-radio-group', 'ren-radio-group-horizontal');\n\n    // Add appropriate class\n    if (orientation === 'horizontal') {\n      this.classList.add('ren-radio-group-horizontal');\n    } else {\n      this.classList.add('ren-radio-group');\n    }\n  }\n\n  // ─── Programmatic API ───\n\n  /**\n   * Get the currently checked radio value\n   */\n  get value() {\n    const checked = this.querySelector('input[type=\"radio\"]:checked');\n    return checked ? checked.value : null;\n  }\n\n  /**\n   * Set which radio is checked by value\n   */\n  set value(val) {\n    const radio = this.querySelector(`input[type=\"radio\"][value=\"${val}\"]`);\n    if (radio) {\n      radio.checked = true;\n      radio.dispatchEvent(new Event('change', { bubbles: true }));\n    }\n  }\n\n  /**\n   * Get all radio inputs\n   */\n  get radios() {\n    return Array.from(this.querySelectorAll('input[type=\"radio\"]'));\n  }\n}\n\n// Register\nif (!customElements.get('ren-radio-group')) {\n  customElements.define('ren-radio-group', RenRadioGroup);\n}\n",
      "path": "components/primitives/ren-radio",
      "id": "component:primitive:ren-radio",
      "type": "component",
      "name": "ren-radio"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-separator",
          ".ren-separator-horizontal",
          ".ren-separator-label",
          ".ren-separator-vertical"
        ],
        "tokens": [
          "--caption-size",
          "--color-text-muted",
          "--ren-separator-color",
          "--ren-separator-margin",
          "--ren-separator-width",
          "--space-3"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-separator Component Contract\n\nVisual or semantic divider between content groups.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-separator` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-separator` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Separator primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a horizontal divider between content groups inside a flow (use <hr class=\\\"ren-separator\\\">).\"\n    - \"You need a vertical divider inside a flex/inline row (use .ren-separator-vertical).\"\n    - \"You need a labeled divider (e.g. \\\"or\\\" between auth options) using .ren-separator-label.\"\n    - \"The divider is purely visual and does not need any interactivity or focus.\"\n    - \"You want spacing tokens (--space-3 / --space-4) baked into the divider's margin instead of ad-hoc CSS.\"\n  avoidWhen:\n    - \"You need a sectioning landmark — use semantic <section> / <aside> with headings instead.\"\n    - \"The divider should also collapse the sidebar / scroll region — use ren-sidebar / a layout primitive.\"\n    - \"You want a decorative border around a card — use the card's --color-border, not a separator.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-separator/ren-separator.css\"\n  notes:\n    - \"CSS-only primitive. There is no colocated JS — do not import one.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Horizontal separators must be a real <hr class=\\\"ren-separator\\\"> element so AT exposes the implicit separator role.\"\n  - \"Vertical separators use a non-<hr> element (<div class=\\\"ren-separator-vertical\\\">) inside a flex row so align-self: stretch can take effect.\"\n  - \"Labeled separators use a flex container: <div class=\\\"ren-separator-label\\\">or</div>; the ::before / ::after pseudo-elements draw the lines automatically — do not add extra <span> rules.\"\n  - \"Do not nest interactive content inside a separator; it is decorative chrome only.\"\n\nforbiddenPatterns:\n  - \"<div class=\\\"ren-separator\\\"> for horizontal dividers — use the native <hr> so screen readers announce the separator.\"\n  - \"border-top: 1px solid #... or border-bottom: 1px solid #... in consumer code; use .ren-separator and let --color-separator drive the color.\"\n  - \"Inline margin overrides (style=\\\"margin: 0\\\") to tighten spacing — set --space-* tokens at a parent scope instead.\"\n  - \"Using .ren-separator-vertical outside a flex / grid container that gives it a measurable cross-axis height.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-separator-color, --ren-separator-margin, --ren-separator-width.\"\n    - \"Semantic tokens consumed internally: --color-separator, --color-text-muted, --space-3, --space-4, --caption-size.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named colors for the separator line.\"\n    - \"Raw pixel widths in inline styles; use --ren-separator-width.\"\n\naccessibility:\n  required:\n    - \"Horizontal separators are <hr> so the implicit role=\\\"separator\\\" is exposed to assistive technology.\"\n    - \"Purely decorative vertical separators may use aria-hidden=\\\"true\\\" if they add no information beyond a sibling label.\"\n    - \"Labels on .ren-separator-label must remain real text (not background-image) so they are announced and translatable.\"\n    - \"Color contrast is not required for decorative separators, but the line must remain visible in both light and dark themes via --color-separator.\"\n    - \"Do not add focusable behavior; separators are not interactive.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-separator/ren-separator.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<hr class=\"ren-separator\">\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-separator`\n- `.ren-separator-horizontal`\n- `.ren-separator-label`\n- `.ren-separator-vertical`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-separator-color`\n- `--ren-separator-margin`\n- `--ren-separator-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-separator/ren-separator.css`\n- `docs/components/ren-separator.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Separator\n   ============================================\n   Horizontal or vertical divider with optional\n   label. CSS-only, no JS needed.\n\n   Usage:\n     <hr class=\"ren-separator\">\n     <div class=\"ren-separator-vertical\"></div>\n     <div class=\"ren-separator-label\">or</div>\n   ============================================ */\n\n.ren-separator {\n  border: none;\n  background-color: var(--ren-separator-color);\n}\n\n/* Horizontal (default) */\n.ren-separator,\n.ren-separator-horizontal {\n  width: 100%;\n  height: var(--ren-separator-width);\n  margin-block: var(--ren-separator-margin);\n}\n\n/* Vertical */\n.ren-separator-vertical {\n  width: var(--ren-separator-width);\n  height: auto;\n  align-self: stretch;\n  margin-inline: var(--space-3);\n}\n\n/* With label (e.g. \"or\" between auth options) */\n.ren-separator-label {\n  display: flex;\n  align-items: center;\n  gap: var(--space-3);\n  color: var(--color-text-muted);\n  font-size: var(--caption-size);\n  margin-block: var(--ren-separator-margin);\n}\n\n.ren-separator-label::before,\n.ren-separator-label::after {\n  content: '';\n  flex: 1;\n  height: var(--ren-separator-width);\n  background-color: var(--ren-separator-color);\n}\n",
      "path": "components/primitives/ren-separator",
      "id": "component:primitive:ren-separator",
      "type": "component",
      "name": "ren-separator"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-skeleton",
          ".ren-skeleton-circle",
          ".ren-skeleton-heading",
          ".ren-skeleton-rect",
          ".ren-skeleton-text"
        ],
        "tokens": [
          "--avatar-md",
          "--ease-loop-pulse",
          "--radius-full",
          "--radius-md",
          "--radius-sm",
          "--ren-skeleton-bg",
          "--ren-skeleton-radius",
          "--ren-skeleton-shine",
          "--ren-skeleton-speed"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-skeleton Component Contract\n\nLoading placeholder primitive that respects reduced motion.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-skeleton` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-skeleton` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Skeleton primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"Content is asynchronously loading and you need a placeholder that preserves layout dimensions.\"\n    - \"You want a shimmer that automatically degrades to a static fill under prefers-reduced-motion.\"\n    - \"You need shape-specific placeholders: text line (.ren-skeleton-text), heading (.ren-skeleton-heading), avatar (.ren-skeleton-circle), or block (.ren-skeleton-rect).\"\n    - \"The placeholder is short-lived — it will be replaced by real content within a few seconds.\"\n  avoidWhen:\n    - \"The wait is indeterminate or very long — use ren-spinner with descriptive copy.\"\n    - \"Only a single small inline indicator is needed — use ren-spinner-xs or ren-spinner-sm.\"\n    - \"You want a progress bar with a known percentage — use ren-progress instead.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-skeleton/ren-skeleton.css\"\n  notes:\n    - \"CSS-only primitive. There is no colocated JS — do not import one.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Always combine the base .ren-skeleton with one shape modifier (.ren-skeleton-text, .ren-skeleton-heading, .ren-skeleton-circle, .ren-skeleton-rect) so the size token resolves.\"\n  - \"Wrap a group of skeletons in a container that sets aria-busy=\\\"true\\\" and aria-live=\\\"polite\\\" so assistive tech is told to wait.\"\n  - \"Replace skeleton nodes with real content as soon as the data resolves — do not leave them mounted indefinitely.\"\n  - \"Match the skeleton's dimensions to the final content (avatar size, line count) to avoid layout shift on swap.\"\n\nforbiddenPatterns:\n  - \"Animating with background-image: linear-gradient(...) hex colors directly; use the .ren-skeleton class and let --color-fill / --color-fill-hover drive the shimmer.\"\n  - \"Hardcoded width: 60% or height: 1.5em overrides for headings; use .ren-skeleton-heading and override --avatar-md / --radius-* via tokens if needed.\"\n  - \"Leaving the skeleton in the DOM after content loads — toggle visibility / remove, never repurpose as decoration.\"\n  - \"Using a skeleton for a determinate progress state (e.g. uploading X of Y) — that is a job for ren-progress.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-skeleton-bg, --ren-skeleton-radius, --ren-skeleton-shine, --ren-skeleton-speed.\"\n    - \"Semantic tokens consumed internally: --color-fill, --color-fill-hover, --radius-sm, --radius-md, --radius-full, --avatar-md, --duration-loop-slow, --ease-loop-pulse.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgba shimmer colors.\"\n    - \"Custom animation durations in seconds; theme via --ren-skeleton-speed or --duration-loop-slow.\"\n\naccessibility:\n  required:\n    - \"The container holding skeleton placeholders must set aria-busy=\\\"true\\\" so AT announces the loading state.\"\n    - \"Pair aria-busy with aria-live=\\\"polite\\\" when the content swap should be announced once data resolves.\"\n    - \"Skeleton nodes are presentational; do not put role=\\\"img\\\" or descriptive alt-text on them.\"\n    - \"The shimmer animation must stop under prefers-reduced-motion (the CSS already handles this — do not override animation in consumer code).\"\n    - \"Skeleton color must remain perceivable in both light and dark themes via --color-fill (do not pin to a specific gray).\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-skeleton/ren-skeleton.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div aria-busy=\"true\" aria-live=\"polite\"><div class=\"ren-skeleton ren-skeleton-heading\"></div><div class=\"ren-skeleton ren-skeleton-text\"></div></div>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-skeleton`\n- `.ren-skeleton-circle`\n- `.ren-skeleton-heading`\n- `.ren-skeleton-rect`\n- `.ren-skeleton-text`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-skeleton-bg`\n- `--ren-skeleton-radius`\n- `--ren-skeleton-shine`\n- `--ren-skeleton-speed`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-skeleton/ren-skeleton.css`\n- `docs/components/ren-skeleton.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Skeleton\n   ============================================\n   Loading placeholder with shimmer animation.\n   Respects prefers-reduced-motion (static fill).\n\n   Usage:\n     <div class=\"ren-skeleton ren-skeleton-heading\"></div>\n     <div class=\"ren-skeleton ren-skeleton-text\"></div>\n     <div class=\"ren-skeleton ren-skeleton-circle\"></div>\n     <div class=\"ren-skeleton ren-skeleton-rect\"></div>\n\n   Accessibility:\n     - Wrap multiple skeletons in a container with\n       aria-busy=\"true\" aria-live=\"polite\"\n     - Replace with real content when loaded\n   ============================================ */\n\n.ren-skeleton {\n  display: block;\n  background: linear-gradient(\n    90deg,\n    var(--ren-skeleton-bg) 25%,\n    var(--ren-skeleton-shine) 50%,\n    var(--ren-skeleton-bg) 75%\n  );\n  background-size: 200% 100%;\n  animation: ren-skeleton-pulse var(--ren-skeleton-speed) var(--ease-loop-pulse) infinite;\n  border-radius: var(--ren-skeleton-radius);\n  min-height: 1rem;\n}\n\n/* ─── Variants ─── */\n.ren-skeleton-text {\n  height: 1em;\n  width: 100%;\n  border-radius: var(--radius-sm);\n}\n\n.ren-skeleton-heading {\n  height: 1.5em;\n  width: 60%;\n  border-radius: var(--radius-sm);\n}\n\n.ren-skeleton-circle {\n  width: var(--avatar-md);\n  height: var(--avatar-md);\n  border-radius: var(--radius-full);\n}\n\n.ren-skeleton-rect {\n  width: 100%;\n  height: 8rem;\n  border-radius: var(--radius-md);\n}\n\n@keyframes ren-skeleton-pulse {\n  0%   { background-position: 200% 0; }\n  100% { background-position: -200% 0; }\n}\n\n/* Respect reduced motion */\n@media (prefers-reduced-motion: reduce) {\n  .ren-skeleton {\n    animation: none;\n    background: var(--ren-skeleton-bg);\n  }\n}\n",
      "path": "components/primitives/ren-skeleton",
      "id": "component:primitive:ren-skeleton",
      "type": "component",
      "name": "ren-skeleton"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-spinner",
          ".ren-spinner-lg",
          ".ren-spinner-light",
          ".ren-spinner-sm",
          ".ren-spinner-xl",
          ".ren-spinner-xs"
        ],
        "tokens": [
          "--color-fill-active",
          "--duration-loop-gentle",
          "--ease-loop-pulse",
          "--ease-loop-smooth",
          "--radius-full",
          "--ren-spinner-color",
          "--ren-spinner-size",
          "--ren-spinner-speed",
          "--ren-spinner-width",
          "--white"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-spinner Component Contract\n\nSmall loading indicator for pending or busy states.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-spinner` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-spinner` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Spinner primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a small inline busy / pending indicator with no known duration.\"\n    - \"You need to indicate loading inside a button or near a single control (xs / sm sizes).\"\n    - \"The spinner sits on a dark / colored surface — use .ren-spinner-light.\"\n    - \"The wait is short and content layout is not yet known (otherwise prefer ren-skeleton for layout preservation).\"\n    - \"You want CSS-only animation that automatically degrades to an opacity pulse under prefers-reduced-motion.\"\n  avoidWhen:\n    - \"Content placeholders that preserve layout — use ren-skeleton for cards, lists, avatars.\"\n    - \"Determinate progress (you know the percentage) — use ren-progress.\"\n    - \"Long-running background operations where the user can navigate away — use ren-toast / ren-banner status messaging instead of a persistent spinner.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-spinner/ren-spinner.css\"\n  notes:\n    - \"CSS-only primitive. There is no colocated JS — do not import one.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Render a <span class=\\\"ren-spinner\\\"> (or another inline-block element) with role=\\\"status\\\" and an aria-label like \\\"Loading\\\".\"\n  - \"Add a size modifier when the default 1.5rem is wrong: .ren-spinner-xs (0.875rem), .ren-spinner-sm (1rem), .ren-spinner-lg (2rem), .ren-spinner-xl (3rem).\"\n  - \"Use .ren-spinner-light only on dark / colored surfaces where the default --color-accent ring would lack contrast.\"\n  - \"When a spinner replaces a button's label, also set aria-busy=\\\"true\\\" on the button so AT announces the busy state.\"\n\nforbiddenPatterns:\n  - \"<div class=\\\"ren-spinner\\\" aria-hidden=\\\"true\\\"> with no accessible label when the spinner is the only signal of busy state.\"\n  - \"Hardcoded border-color: #... overrides; theme via --ren-spinner-color or use .ren-spinner-light for inverted surfaces.\"\n  - \"Custom animation-duration values in inline styles; use --ren-spinner-speed (or --duration-loop).\"\n  - \"Combining .ren-spinner-light with --color-accent overrides — pick one approach; light is for inverted surfaces only.\"\n  - \"Spinner left mounted after the operation finishes — remove it or replace with success / error state.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-spinner-color, --ren-spinner-size, --ren-spinner-speed, --ren-spinner-width.\"\n    - \"Semantic tokens consumed internally: --color-fill-active, --color-accent, --white, --radius-full, --duration-loop, --ease-loop-smooth, --ease-loop-pulse.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgba border colors.\"\n    - \"Raw animation: spin 1s linear infinite; use the .ren-spinner class and --ren-spinner-speed.\"\n\naccessibility:\n  required:\n    - \"The spinner must have role=\\\"status\\\" and an aria-label (e.g. \\\"Loading\\\") when it conveys busy state.\"\n    - \"When the spinner replaces a button label, set aria-busy=\\\"true\\\" on the button so the busy state is exposed once.\"\n    - \"Do not stack multiple labeled spinners in the same live region — they will be announced repeatedly.\"\n    - \"Reduced-motion users see a gentle opacity pulse (built-in); do not override animation in consumer code.\"\n    - \"Color is not the only signal: the spinner is also a moving shape. Ensure the rotation / pulse is visible against the background.\"\n    - \"Provide a textual fallback (\\\"Saving…\\\") near the spinner whenever the wait can exceed a couple of seconds.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-spinner/ren-spinner.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<span class=\"ren-spinner\" role=\"status\" aria-label=\"Loading\"></span>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-spinner`\n- `.ren-spinner-lg`\n- `.ren-spinner-light`\n- `.ren-spinner-sm`\n- `.ren-spinner-xl`\n- `.ren-spinner-xs`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-spinner-color`\n- `--ren-spinner-size`\n- `--ren-spinner-speed`\n- `--ren-spinner-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-spinner/ren-spinner.css`\n- `docs/components/ren-spinner.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Spinner\n   ============================================\n   Circular loading indicator. CSS-only animation\n   that respects prefers-reduced-motion (falls\n   back to a gentle opacity pulse instead of\n   rotating).\n\n   Usage:\n     <span class=\"ren-spinner\" role=\"status\"\n           aria-label=\"Loading\"></span>\n     <span class=\"ren-spinner ren-spinner-lg\"></span>\n\n   Accessibility:\n     - Provide role=\"status\" and aria-label for SR\n     - On dark surfaces use .ren-spinner-light\n   ============================================ */\n\n.ren-spinner {\n  display: inline-block;\n  width: var(--ren-spinner-size);\n  height: var(--ren-spinner-size);\n  border: var(--ren-spinner-width) solid var(--color-fill-active);\n  border-top-color: var(--ren-spinner-color);\n  border-radius: var(--radius-full);\n  animation: ren-spin var(--ren-spinner-speed) var(--ease-loop-smooth) infinite;\n}\n\n/* ─── Sizes ─── */\n.ren-spinner-xs { width: 0.875rem; height: 0.875rem; border-width: 2px; }\n.ren-spinner-sm { width: 1rem;     height: 1rem;     border-width: 2px; }\n.ren-spinner-lg { width: 2rem;     height: 2rem;     border-width: 3px; }\n.ren-spinner-xl { width: 3rem;     height: 3rem;     border-width: 3px; }\n\n/* ─── Color variants ─── */\n.ren-spinner-light {\n  border-color: rgb(255, 255, 255, 0.3);\n  border-top-color: var(--white);\n}\n\n@keyframes ren-spin {\n  to { transform: rotate(360deg); }\n}\n\n@keyframes ren-spin-gentle {\n  0%, 100% { opacity: 1; }\n  50%      { opacity: 0.4; }\n}\n\n/* Reduced motion: pulse opacity instead of rotating */\n@media (prefers-reduced-motion: reduce) {\n  .ren-spinner {\n    animation: ren-spin-gentle var(--duration-loop-gentle) var(--ease-loop-pulse) infinite;\n  }\n}\n",
      "path": "components/primitives/ren-spinner",
      "id": "component:primitive:ren-spinner",
      "type": "component",
      "name": "ren-spinner"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-switch",
          ".ren-switch-track"
        ],
        "tokens": [
          "--body-size",
          "--color-fill-hover",
          "--color-focus-ring",
          "--color-success-strong",
          "--color-text",
          "--radius-full",
          "--ren-switch-bg",
          "--ren-switch-checked-bg",
          "--ren-switch-duration",
          "--ren-switch-easing",
          "--ren-switch-height",
          "--ren-switch-thumb-color",
          "--ren-switch-thumb-size",
          "--ren-switch-width",
          "--ring-offset-width",
          "--ring-width",
          "--shadow-sm",
          "--space-2",
          "--touch-min",
          "--weight-regular"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-switch Component Contract\n\nToggle switch primitive built on native `<input type=\"checkbox\" role=\"switch\">` with checked, hover, focus, active, and disabled states.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-switch` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-switch` primitive.\n- Keep generated markup aligned with the colocated CSS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- The UI requires an instant on/off state that takes effect immediately (e.g. enable/disable a setting, toggle dark mode).\n- The control sits in a settings panel or in-line preference area, not in a form that must be submitted before changes apply.\n- Native checkbox semantics with `role=\"switch\"` are appropriate.\n\n## Do Not Use When\n\n- The choice should only take effect on form submit — use `ren-checkbox` instead.\n- More than two states are possible — use `ren-radio` or `ren-toggle-group`.\n- The control must look like a checkbox (square box with checkmark) — use `ren-checkbox`.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The control flips an immediate on/off setting (dark mode, notifications, feature flags).\"\n    - \"The change should take effect the moment the user toggles it, with no submit step.\"\n    - \"Native checkbox semantics extended with role=\\\"switch\\\" are the right a11y model.\"\n    - \"You want the iOS-style 51×31 track plus full label wrapper for a 44px touch target.\"\n    - \"You need the checked color to come from --color-success (positive enablement), not the brand accent.\"\n  avoidWhen:\n    - \"The change must wait for a Save / Submit step — use ren-checkbox so users can review before commit.\"\n    - \"There are 3+ choices or the option is mutually exclusive within a set — use ren-radio or ren-toggle-group.\"\n    - \"The control needs a checkmark glyph to communicate selection — use ren-checkbox.\"\n    - \"The control is a tab / segmented selector across views — use ren-tabs or ren-toggle-group.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-switch/ren-switch.css\"\n  notes:\n    - \"CSS-only primitive. There is no colocated JS — do not import one.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Wrap the control in a <label class=\\\"ren-switch\\\"> so the entire label area is the click / touch target.\"\n  - \"Use a real <input type=\\\"checkbox\\\" role=\\\"switch\\\"> as the first child; the CSS hides it visually but keeps it focusable and announced.\"\n  - \"Follow the input with <span class=\\\"ren-switch-track\\\"></span>; the thumb is the track's ::after pseudo-element — do not add a separate thumb element.\"\n  - \"Include a visible <span>Label</span> sibling for sighted users; if the switch is icon-only, set aria-label on the input.\"\n  - \"Persist on/off changes immediately via the input's change event — never defer to a separate Save action.\"\n\nforbiddenPatterns:\n  - \"<div role=\\\"switch\\\" tabindex=\\\"0\\\"> custom toggles; always use a real <input type=\\\"checkbox\\\" role=\\\"switch\\\">.\"\n  - \"Hiding the input with display: none — it removes it from the accessibility tree. Keep the existing clip-path / visually-hidden pattern.\"\n  - \"Hardcoded track / thumb colors via background-color in inline styles; theme via --ren-switch-bg, --ren-switch-checked-bg, --ren-switch-thumb-color.\"\n  - \"Putting the switch inside a <form> where it only applies after submit — that is checkbox territory.\"\n  - \"Removing the focus outline on .ren-switch-track without restoring an equivalent :focus-visible ring.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-switch-bg, --ren-switch-checked-bg, --ren-switch-duration, --ren-switch-easing, --ren-switch-height, --ren-switch-thumb-color, --ren-switch-thumb-size, --ren-switch-width.\"\n    - \"Semantic tokens consumed internally: --color-fill-active, --color-fill-hover, --color-success, --color-success-strong, --color-text, --color-focus-ring, --white, --radius-full, --ring-width, --ring-offset-width, --touch-min, --shadow-sm.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors for the checked / unchecked track.\"\n    - \"Custom transition values; use --ren-switch-duration and --ren-switch-easing.\"\n\naccessibility:\n  required:\n    - \"The native <input type=\\\"checkbox\\\" role=\\\"switch\\\"> must remain in the DOM and focusable — assistive tech announces it as a switch with on/off states.\"\n    - \"The whole <label class=\\\"ren-switch\\\"> wrapper provides a touch target of at least --touch-min (44px).\"\n    - \"Focus must show the outline ring on .ren-switch-track via :focus-visible (the CSS already wires this up; do not remove it).\"\n    - \"Do not signal state by color alone — the thumb position (left vs right) is the primary visual cue.\"\n    - \"When the switch is disabled, set disabled on the <input> (the parent .ren-switch:has(input:disabled) handles styling) instead of a custom data attribute.\"\n    - \"If the switch's label is not adjacent text, set aria-label or aria-labelledby on the <input>.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-switch/ren-switch.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<label class=\"ren-switch\">\n  <input type=\"checkbox\" role=\"switch\">\n  <span class=\"ren-switch-track\"></span>\n  <span>Dark mode</span>\n</label>\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-switch`\n- `.ren-switch-track`\n\n## States And Attributes\n\n- `:active`\n- `:checked`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n- `role=\"switch\"` on the native input (announces toggle semantics to assistive technology)\n\n## Public Token API\n\n- `--ren-switch-bg`\n- `--ren-switch-checked-bg`\n- `--ren-switch-duration`\n- `--ren-switch-easing`\n- `--ren-switch-height`\n- `--ren-switch-thumb-color`\n- `--ren-switch-thumb-size`\n- `--ren-switch-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; the `role=\"switch\"` on the input announces the on/off toggle pattern.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum (the track is 51×31 but the label wrapper provides the full touch target).\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone — the thumb position is the primary visual signal.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-switch/ren-switch.css`\n- `docs/components/ren-switch.html`\n- `components/primitives/ren-checkbox/component.md` (sibling primitive — submit-time vs immediate semantics)\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Switch / Toggle Component\n   ============================================\n   Custom-styled toggle switch built on native\n   <input type=\"checkbox\" role=\"switch\">.\n\n   Preserves native semantics and accessibility.\n   Custom visuals via CSS only.\n\n   Usage:\n     <label class=\"ren-switch\">\n       <input type=\"checkbox\" role=\"switch\">\n       <span class=\"ren-switch-track\"></span>\n       <span>Dark mode</span>\n     </label>\n   ============================================ */\n\n.ren-switch {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-2);\n  cursor: pointer;\n  font-size: var(--body-size);\n  font-weight: var(--weight-regular);\n  color: var(--color-text);\n  user-select: none;\n  min-height: var(--touch-min);\n}\n\n/* Hide native input */\n.ren-switch > input[type=\"checkbox\"] {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip-path: inset(50%);\n  white-space: nowrap;\n  border-width: 0;\n}\n\n/* Track */\n.ren-switch-track {\n  position: relative;\n  width: var(--ren-switch-width);    /* 51px — Apple HIG */\n  height: var(--ren-switch-height);   /* 31px */\n  flex-shrink: 0;\n  background-color: var(--ren-switch-bg);\n  border-radius: var(--radius-full);\n  transition:\n    background-color var(--ren-switch-duration) var(--ren-switch-easing);\n}\n\n/* Thumb */\n.ren-switch-track::after {\n  content: '';\n  position: absolute;\n  top: 2px;\n  left: 2px;\n  width: var(--ren-switch-thumb-size);   /* 27px */\n  height: var(--ren-switch-thumb-size);\n  background-color: var(--ren-switch-thumb-color);\n  border-radius: var(--radius-full);\n  box-shadow: var(--shadow-sm);\n  transition:\n    transform var(--ren-switch-duration) var(--ren-switch-easing),\n    box-shadow var(--ren-switch-duration) var(--ren-switch-easing);\n}\n\n/* ─── States ─── */\n\n/* Checked (on) */\n.ren-switch > input:checked + .ren-switch-track {\n  background-color: var(--ren-switch-checked-bg);\n}\n\n.ren-switch > input:checked + .ren-switch-track::after {\n  transform: translateX(calc(var(--ren-switch-width) - var(--ren-switch-height)));\n}\n\n/* Hover */\n.ren-switch:hover > .ren-switch-track {\n  background-color: var(--color-fill-hover);\n}\n\n.ren-switch:hover > input:checked + .ren-switch-track {\n  background-color: var(--color-success-strong);\n}\n\n/* Focus */\n.ren-switch > input:focus-visible + .ren-switch-track {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: var(--ring-offset-width);\n}\n\n/* Active */\n.ren-switch:active > .ren-switch-track::after {\n  width: calc(var(--ren-switch-thumb-size) + 6px);\n}\n\n.ren-switch:active > input:checked + .ren-switch-track::after {\n  transform: translateX(calc(var(--ren-switch-width) - var(--ren-switch-height) - 6px));\n}\n\n/* Disabled */\n.ren-switch:has(input:disabled) {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n",
      "path": "components/primitives/ren-switch",
      "id": "component:primitive:ren-switch",
      "type": "component",
      "name": "ren-switch"
    },
    {
      "data": {
        "kind": "primitive",
        "selectors": [
          ".ren-tag",
          ".ren-tag-clickable",
          ".ren-tag-danger",
          ".ren-tag-dismiss",
          ".ren-tag-group",
          ".ren-tag-lg",
          ".ren-tag-primary",
          ".ren-tag-sm",
          ".ren-tag-success",
          ".ren-tag-warning"
        ],
        "tokens": [
          "--body-size",
          "--color-accent",
          "--color-accent-subtle",
          "--color-border-strong",
          "--color-danger",
          "--color-danger-strong",
          "--color-danger-subtle",
          "--color-fill-active",
          "--color-fill-hover",
          "--color-focus-ring",
          "--color-on-accent",
          "--color-success",
          "--color-success-strong",
          "--color-success-subtle",
          "--color-text",
          "--color-text-muted",
          "--color-warning",
          "--color-warning-strong",
          "--color-warning-subtle",
          "--duration-tactile",
          "--ease-enter",
          "--radius-full",
          "--ren-tag-bg",
          "--ren-tag-border-color",
          "--ren-tag-color",
          "--ren-tag-font-size",
          "--ren-tag-font-weight",
          "--ren-tag-gap",
          "--ren-tag-height",
          "--ren-tag-padding-x",
          "--ren-tag-radius",
          "--ring-offset-width",
          "--ring-width",
          "--space-2",
          "--stroke-1",
          "--text-xs"
        ],
        "hasScript": false,
        "hasDocsPage": true
      },
      "body": "# ren-tag Component Contract\n\nCompact keyword, filter, or removable token primitive.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-tag` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-tag` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Tag primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The user picks, adds, or removes a value (filter chip, selected keyword, attached label).\"\n    - \"You need a removable token with an embedded dismiss button (.ren-tag-dismiss).\"\n    - \"You need a toggleable filter pill driven by aria-pressed or data-selected (.ren-tag-clickable).\"\n    - \"You need a status-tinted token (.ren-tag-primary / .ren-tag-success / .ren-tag-warning / .ren-tag-danger) tied to a semantic color.\"\n    - \"Multiple tokens flow together in a wrap — use .ren-tag-group as the parent.\"\n  avoidWhen:\n    - \"The element is a static, non-interactive status pill — use ren-badge.\"\n    - \"The element triggers an imperative action (submit, open dialog) — use ren-button.\"\n    - \"The element is a hyperlink to another page — use ren-link.\"\n    - \"The element is a form-style multi-select dropdown option — use ren-select / ren-combobox option markup.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-tag/ren-tag.css\"\n  notes:\n    - \"CSS-only primitive. There is no colocated JS — wire up dismiss/click handlers in your own script.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Render the tag as an inline element: <span class=\\\"ren-tag\\\"> for static, <span class=\\\"ren-tag ren-tag-clickable\\\" tabindex=\\\"0\\\" role=\\\"button\\\"> for toggleable filters.\"\n  - \"Removable tags must include <button class=\\\"ren-tag-dismiss\\\" type=\\\"button\\\" aria-label=\\\"Remove <name>\\\">; never use a non-button element for dismissal.\"\n  - \"Toggle / filter tags must mirror selection state via aria-pressed=\\\"true|false\\\" (preferred) or data-selected; do not rely on color alone.\"\n  - \"Group multiple tags inside a parent with class=\\\"ren-tag-group\\\" so the gap and wrap behavior come from the design system.\"\n  - \"Combine at most one color variant (.ren-tag-primary | .ren-tag-success | .ren-tag-warning | .ren-tag-danger) with at most one size (.ren-tag-sm | .ren-tag-lg).\"\n\nforbiddenPatterns:\n  - \"<div class=\\\"ren-tag\\\" onclick=...> for clickable tags — use a real <button> or .ren-tag-clickable with role=\\\"button\\\" and tabindex=\\\"0\\\".\"\n  - \"Custom dismiss icons drawn with background-image; place a text \\\"×\\\" or an <svg aria-hidden=\\\"true\\\"> inside .ren-tag-dismiss instead.\"\n  - \"Hardcoded background-color: #... overrides; theme via --ren-tag-bg / --ren-tag-border-color / --ren-tag-color.\"\n  - \"Using ren-tag-danger purely for emphasis on a non-danger value — semantic color variants must match meaning (danger = destructive / error).\"\n  - \"Removing the :focus-visible outline on .ren-tag-clickable or .ren-tag-dismiss without restoring a visible ring.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-tag-bg, --ren-tag-border-color, --ren-tag-color, --ren-tag-font-size, --ren-tag-font-weight, --ren-tag-gap, --ren-tag-height, --ren-tag-padding-x, --ren-tag-radius.\"\n    - \"Semantic tokens consumed internally: --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-fill-active, --color-border, --color-border-strong, --color-accent, --color-accent-subtle, --color-on-accent, --color-success / --color-success-subtle / --color-success-strong, --color-warning / --color-warning-subtle / --color-warning-strong, --color-danger / --color-danger-subtle / --color-danger-strong, --color-focus-ring, --stroke-1, --radius-full, --ring-width, --ring-offset-width.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgba background and border colors.\"\n    - \"Inline padding / font-size overrides; pick the .ren-tag-sm / .ren-tag-lg variant or override --ren-tag-padding-x / --ren-tag-font-size.\"\n\naccessibility:\n  required:\n    - \"Clickable tags must be focusable: either a real <button>, or use .ren-tag-clickable with tabindex=\\\"0\\\" and role=\\\"button\\\" plus Space / Enter handling.\"\n    - \"Toggle state must be exposed via aria-pressed; data-selected is for styling parity only, not a substitute for ARIA.\"\n    - \"Dismiss buttons need an aria-label that names the value being removed (e.g. \\\"Remove React\\\"), not just \\\"Remove\\\".\"\n    - \"Color variants must not be the sole signal of meaning — pair .ren-tag-danger with text or an icon that conveys the same state.\"\n    - \":focus-visible outline must remain visible on both .ren-tag-clickable and .ren-tag-dismiss; the CSS wires this to --color-focus-ring.\"\n    - \"Long tag text truncates with ellipsis (.ren-tag > span overflow rule); when this happens, surface the full value via title or an off-screen label.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-tag/ren-tag.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<span class=\"ren-tag ren-tag-removable\">Accessibility<button class=\"ren-tag-dismiss\" type=\"button\" aria-label=\"Remove Accessibility\">×</button></span>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-tag`\n- `.ren-tag-clickable`\n- `.ren-tag-danger`\n- `.ren-tag-dismiss`\n- `.ren-tag-group`\n- `.ren-tag-lg`\n- `.ren-tag-primary`\n- `.ren-tag-sm`\n- `.ren-tag-success`\n- `.ren-tag-warning`\n\n## States And Attributes\n\n- `[aria-pressed]`\n- `[data-selected]`\n- `:active`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-tag-bg`\n- `--ren-tag-border-color`\n- `--ren-tag-color`\n- `--ren-tag-font-size`\n- `--ren-tag-font-weight`\n- `--ren-tag-gap`\n- `--ren-tag-height`\n- `--ren-tag-padding-x`\n- `--ren-tag-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-tag/ren-tag.css`\n- `docs/components/ren-tag.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n\n\n/* ============================================\n   RenDS — Tag / Chip Component\n   ============================================\n   Interactive badge with optional dismiss button.\n   CSS-only (dismiss needs minimal JS).\n\n   Different from Badge: Tag is interactive,\n   dismissable, and represents user-selected items.\n\n   Usage:\n     <span class=\"ren-tag\">\n       HTML\n     </span>\n\n     <span class=\"ren-tag ren-tag-removable\">\n       React\n       <button class=\"ren-tag-dismiss\" aria-label=\"Remove React\">&times;</button>\n     </span>\n\n     <span class=\"ren-tag ren-tag-clickable\" tabindex=\"0\" role=\"button\">\n       Filter: Active\n     </span>\n   ============================================ */\n\n.ren-tag {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--ren-tag-gap);\n  min-height: var(--ren-tag-height);\n  padding: 0.25em var(--ren-tag-padding-x);\n  font-size: var(--ren-tag-font-size);\n  font-weight: var(--ren-tag-font-weight);\n  line-height: 1.5;\n  color: var(--ren-tag-color);\n  background-color: var(--ren-tag-bg);\n  border: var(--stroke-1) solid var(--ren-tag-border-color);\n  border-radius: var(--ren-tag-radius);\n  white-space: nowrap;\n  user-select: none;\n  max-width: 100%;\n}\n\n/* Tag text truncation */\n.ren-tag > span {\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n/* ─── Dismiss button ─── */\n.ren-tag-dismiss {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 1.1em;\n  height: 1.1em;\n  padding: 0;\n  border: none;\n  background: none;\n  color: var(--color-text-muted);\n  cursor: pointer;\n  border-radius: var(--radius-full);\n  font-size: 1.1em;\n  line-height: 1;\n  flex-shrink: 0;\n  transition:\n    color var(--duration-tactile) var(--ease-enter),\n    background-color var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-tag-dismiss:hover {\n  color: var(--color-text);\n  background-color: var(--color-fill-hover);\n}\n\n.ren-tag-dismiss:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: 1px;\n}\n\n/* ─── Clickable / Selectable ─── */\n.ren-tag-clickable {\n  cursor: pointer;\n  transition:\n    background-color var(--duration-tactile) var(--ease-enter),\n    border-color var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-tag-clickable:hover {\n  background-color: var(--color-fill-hover);\n  border-color: var(--color-border-strong);\n}\n\n.ren-tag-clickable:active {\n  background-color: var(--color-fill-active);\n}\n\n.ren-tag-clickable:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: var(--ring-offset-width);\n}\n\n/* Selected state */\n.ren-tag-clickable[aria-pressed=\"true\"],\n.ren-tag-clickable[data-selected] {\n  background-color: var(--color-accent);\n  color: var(--color-on-accent);\n  border-color: var(--color-accent);\n}\n\n/* ─── Color variants ─── */\n.ren-tag-primary {\n  background-color: var(--color-accent-subtle);\n  border-color: color-mix(in oklch, var(--color-accent), transparent 70%);\n  color: var(--color-accent);\n}\n\n.ren-tag-success {\n  background-color: var(--color-success-subtle);\n  border-color: color-mix(in oklch, var(--color-success), transparent 70%);\n  color: var(--color-success-strong);\n}\n\n.ren-tag-warning {\n  background-color: var(--color-warning-subtle);\n  border-color: color-mix(in oklch, var(--color-warning), transparent 70%);\n  color: var(--color-warning-strong);\n}\n\n.ren-tag-danger {\n  background-color: var(--color-danger-subtle);\n  border-color: color-mix(in oklch, var(--color-danger), transparent 70%);\n  color: var(--color-danger-strong);\n}\n\n/* ─── Size variants ─── */\n.ren-tag-sm {\n  padding: 0.1em 0.45em;\n  font-size: var(--text-xs);\n  gap: 0.15em;\n}\n\n.ren-tag-lg {\n  padding: 0.35em 0.8em;\n  font-size: var(--body-size);\n}\n\n/* ─── Tag group ─── */\n.ren-tag-group {\n  display: flex;\n  flex-wrap: wrap;\n  gap: var(--space-2);\n  align-items: center;\n}\n",
      "path": "components/primitives/ren-tag",
      "id": "component:primitive:ren-tag",
      "type": "component",
      "name": "ren-tag"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-accordion Component Contract\n\nDisclosure group for vertically stacked expandable sections.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-accordion` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-accordion` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Accordion composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a vertically stacked group of disclosure sections sharing chrome.\"\n    - \"Exclusive (single open) behavior is required, optionally with collapsible=all-closed.\"\n    - \"Multiple sections may be open simultaneously (type=\\\"multiple\\\").\"\n    - \"You want native <details>/<summary> semantics with smooth height animation.\"\n    - \"Initial open items must be declarable via default-value=\\\"0,2\\\".\"\n  avoidWhen:\n    - \"Only one disclosure is needed — use ren-collapsible instead.\"\n    - \"Sections should not share chrome / divider lines — use isolated ren-collapsible items.\"\n    - \"The disclosure is a navigation menu — use ren-menu or ren-sidebar.\"\n    - \"Content swaps in place without expanding — use ren-tabs.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-accordion/ren-accordion.css\"\n  js:\n    - \"rends/components/composites/ren-accordion/ren-accordion.js\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"JS adds exclusive mode, default-value parsing, and the ren-accordion-change event; native <details> handles open/close without JS.\"\n\nrequiredMarkup:\n  - \"<ren-accordion> wraps real <details> elements; each <details> holds a <summary class=\\\"ren-accordion-trigger\\\"> and a <div class=\\\"ren-accordion-content\\\">.\"\n  - \"Do not replace <details>/<summary> with <button>+<div> — exclusive mode relies on native [name] grouping.\"\n  - \"Use type=\\\"single\\\" (default) or type=\\\"multiple\\\"; add collapsible only when single mode must allow all closed.\"\n  - \"Set default-value=\\\"0,1\\\" (comma-separated indices) to declare initial open items.\"\n  - \"Use .ren-accordion-bordered or .ren-accordion-flush on the host for variants; do not invent new variant classes.\"\n\nforbiddenPatterns:\n  - \"<div role=\\\"button\\\"> styled as a summary instead of a real <summary> / <button>.\"\n  - \"Manual height animation with hardcoded max-height: 500px in inline styles — rely on the component's ::details-content transition.\"\n  - \"Hardcoded chevron icons inside the summary; the ::after pseudo-element already renders one.\"\n  - \"Toggling open state via display: none on .ren-accordion-content instead of the <details>[open] attribute.\"\n  - \"Overriding focus ring with outline: none on .ren-accordion-trigger without restoring :focus-visible.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-collapse-bg, --ren-collapse-border, --ren-collapse-duration, --ren-collapse-easing, --ren-collapse-padding, --ren-collapse-radius, --ren-collapse-trigger-font, --ren-collapse-trigger-weight.\"\n    - \"Semantic tokens: --color-border, --color-text, --color-text-muted, --color-fill, --color-fill-active, --color-accent.\"\n    - \"Layout / motion tokens: --space-*, --size-*, --radius-*, --duration-enter, --ease-enter, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Raw transition durations; use --duration-enter / --ease-enter or --ren-collapse-duration / --ren-collapse-easing.\"\n\naccessibility:\n  required:\n    - \"Use real <details>/<summary> so the browser exposes the native disclosure pattern to AT.\"\n    - \"Keep the chevron decorative (::after pseudo); never put text inside it that an AT must announce.\"\n    - \"Touch target on .ren-accordion-trigger is min-height: var(--size-lg); do not shrink below 44px on touch surfaces.\"\n    - \"Visible :focus-visible outline (2px solid --color-accent) must be preserved on the summary.\"\n    - \"Disabled triggers must set aria-disabled=\\\"true\\\" and not toggle on activation.\"\n    - \"Animations respect prefers-reduced-motion (transitions are removed under reduce).\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-accordion/ren-accordion.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-accordion/ren-accordion.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-accordion type=\"single\"><details><summary class=\"ren-accordion-trigger\">Billing</summary><div class=\"ren-accordion-content\">Billing details</div></details></ren-accordion>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-accordion`\n- `.ren-accordion-bordered`\n- `.ren-accordion-content`\n- `.ren-accordion-flush`\n- `.ren-accordion-item`\n- `.ren-accordion-trigger`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[aria-expanded]`\n- `:active`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-collapse-bg`\n- `--ren-collapse-border`\n- `--ren-collapse-duration`\n- `--ren-collapse-easing`\n- `--ren-collapse-padding`\n- `--ren-collapse-radius`\n- `--ren-collapse-trigger-font`\n- `--ren-collapse-trigger-weight`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-accordion/ren-accordion.css`\n- `components/composites/ren-accordion/ren-accordion.js`\n- `docs/components/ren-accordion.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-accordion/component.md",
      "id": "contract:composite:ren-accordion",
      "type": "contract",
      "name": "ren-accordion component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-alert-dialog Component Contract\n\nNon-dismissible confirmation dialog for destructive or critical decisions.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-alert-dialog` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-alert-dialog` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Alert Dialog composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"A destructive or irreversible action (delete, sign-out, discard) requires explicit confirmation.\"\n    - \"Accidental dismissal (Escape, backdrop click) must NOT close the dialog.\"\n    - \"The decision is binary and centered around two actions (cancel + confirm).\"\n    - \"Content is short: an icon, a title, a one-paragraph description, and two buttons.\"\n    - \"You want the native <dialog> showModal() flow with returnValue for the outcome.\"\n  avoidWhen:\n    - \"The dialog hosts a form, multi-step flow, or scrollable content — use ren-dialog.\"\n    - \"The disclosure should dismiss on Escape / backdrop — use ren-dialog (no alert).\"\n    - \"The notification does not block the page — use ren-toast or ren-banner.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-alert-dialog/ren-alert-dialog.css\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"No colocated JS — call dialog.showModal() / dialog.close(returnValue) directly on the native <dialog>.\"\n    - \"The component uses the same --ren-dialog-* token namespace as ren-dialog, so token overrides cascade.\"\n\nrequiredMarkup:\n  - \"The root element is a real <dialog class=\\\"ren-alert-dialog\\\">; never substitute a <div role=\\\"alertdialog\\\">.\"\n  - \"Provide a .ren-alert-dialog-title (heading) and a .ren-alert-dialog-description so AT can announce the decision.\"\n  - \"Actions live inside .ren-alert-dialog-actions and use real <button> elements (.ren-btn-secondary + .ren-btn-danger or similar).\"\n  - \"Each button calls dialog.close('cancel' | 'confirm' | …) so the consumer can read returnValue on the close event.\"\n  - \"Use .ren-alert-dialog-icon-danger or .ren-alert-dialog-icon-warning on the icon slot for severity — do not invent new severity classes.\"\n\nforbiddenPatterns:\n  - \"<div class=\\\"ren-alert-dialog\\\"> rendered without the native <dialog> element.\"\n  - \"Calling dialog.show() instead of dialog.showModal() — alert behavior requires modality + ::backdrop.\"\n  - \"Adding Escape-to-close handlers; the alert variant intentionally blocks accidental dismissal.\"\n  - \"Hardcoded rgba backdrops (background: rgba(0,0,0,.5)) — the ::backdrop already reads --color-overlay.\"\n  - \"Replacing .ren-alert-dialog-actions with arbitrary flex containers that bypass the @container narrow-stack layout.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-dialog-backdrop, --ren-dialog-bg, --ren-dialog-border-color, --ren-dialog-duration, --ren-dialog-easing, --ren-dialog-gap, --ren-dialog-padding, --ren-dialog-radius, --ren-dialog-shadow, --ren-dialog-width.\"\n    - \"Semantic tokens: --color-surface, --color-overlay, --color-text, --color-text-muted, --color-danger, --color-danger-subtle, --color-warning, --color-warning-subtle, --color-fill.\"\n    - \"Layout / motion tokens: --space-*, --radius-*, --shadow-xl, --icon-xl, --duration-enter, --ease-enter, --transition-overlay.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Custom backdrop opacity values that bypass --color-overlay or --transition-overlay.\"\n\naccessibility:\n  required:\n    - \"Use a real <dialog> so the browser exposes role=\\\"dialog\\\" + aria-modal automatically; add aria-labelledby pointing to .ren-alert-dialog-title.\"\n    - \"Open with showModal(); the backdrop becomes inert so background content is unreachable to keyboard / AT.\"\n    - \"The destructive button must be visually distinct (.ren-btn-danger) AND announced by its text — never rely on color alone.\"\n    - \"Focus must move into the dialog on open and return to the trigger on close (native <dialog> behavior).\"\n    - \"Touch targets on action buttons stay ≥ 44px (default .ren-btn size).\"\n    - \"Reduced motion: transitions are disabled under prefers-reduced-motion (already handled by the component).\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-alert-dialog/ren-alert-dialog.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<dialog class=\"ren-alert-dialog\" aria-labelledby=\"delete-title\" aria-describedby=\"delete-description\" closedby=\"none\">\n  <h2 id=\"delete-title\" class=\"ren-alert-dialog-title\">Delete project?</h2>\n  <p id=\"delete-description\" class=\"ren-alert-dialog-description\">This cannot be undone.</p>\n  <form method=\"dialog\" class=\"ren-alert-dialog-actions\">\n    <button value=\"cancel\" class=\"ren-btn ren-btn-secondary\">Cancel</button>\n    <button value=\"delete\" class=\"ren-btn ren-btn-danger\">Delete</button>\n  </form>\n</dialog>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-alert-dialog`\n- `.ren-alert-dialog-actions`\n- `.ren-alert-dialog-description`\n- `.ren-alert-dialog-icon`\n- `.ren-alert-dialog-icon-danger`\n- `.ren-alert-dialog-icon-warning`\n- `.ren-alert-dialog-title`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-dialog-backdrop`\n- `--ren-dialog-bg`\n- `--ren-dialog-border-color`\n- `--ren-dialog-duration`\n- `--ren-dialog-easing`\n- `--ren-dialog-gap`\n- `--ren-dialog-padding`\n- `--ren-dialog-radius`\n- `--ren-dialog-shadow`\n- `--ren-dialog-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/composites/ren-alert-dialog/ren-alert-dialog.css`\n- `docs/components/ren-alert-dialog.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-alert-dialog/component.md",
      "id": "contract:composite:ren-alert-dialog",
      "type": "contract",
      "name": "ren-alert-dialog component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-calendar Component Contract\n\nCalendar grid and date selection composite.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-calendar` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-calendar` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Calendar composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The UI needs a full month grid for date browsing or selection.\"\n    - \"You need single, range, or multiple selection modes (mode=\\\"single|range|multiple\\\").\"\n    - \"Locale-aware month names and first-day-of-week must be supported (locale, first-day attrs).\"\n    - \"min / max bounds must constrain selectable dates.\"\n    - \"You need ARIA grid + roving tabindex keyboard navigation across days.\"\n  avoidWhen:\n    - \"You only need a date text input — use ren-date-picker (composite that may embed ren-calendar).\"\n    - \"You need a year/decade picker without a month grid — use a different control.\"\n    - \"The UI is a schedule / agenda view rather than date selection — use a calendar-view pattern.\"\n    - \"Only a static date display is needed — use ren-text with a formatted Date.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-calendar/ren-calendar.css\"\n  js:\n    - \"rends/components/composites/ren-calendar/ren-calendar.js\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"JS is required: <ren-calendar> renders the grid imperatively; there is no static markup fallback.\"\n    - \"Configure via attributes (value, mode, locale, first-day, min, max); avoid manual DOM mutations on .ren-calendar-grid.\"\n\nrequiredMarkup:\n  - \"Render <ren-calendar> as the host; the component populates its own .ren-calendar-header, .ren-calendar-weekdays, and .ren-calendar-grid.\"\n  - \"Each day cell is a real <button class=\\\"ren-calendar-day\\\"> with aria-selected and (when applicable) data-today / data-outside / [disabled].\"\n  - \"Navigation arrows use .ren-calendar-prev / .ren-calendar-next inside .ren-calendar-nav and rely on the component's chevron pseudo-elements.\"\n  - \"Use .ren-calendar-sm or .ren-calendar-lg on the host for size variants; do not invent custom size classes.\"\n  - \"For ranges, the component sets .ren-calendar-day-range-start, .ren-calendar-day-in-range, .ren-calendar-day-range-end — do not author these by hand.\"\n\nforbiddenPatterns:\n  - \"Replacing day <button>s with <div role=\\\"button\\\"> — breaks keyboard + screen reader expectations.\"\n  - \"Hardcoded today highlight (border: 2px solid #...) — rely on [data-today] and --color-accent.\"\n  - \"Toggling a day's selected state by adding a custom class instead of aria-selected=\\\"true\\\".\"\n  - \"Mounting the calendar inside a non-modal popover without focus management — wrap in ren-popover or ren-dialog if floating.\"\n  - \"Hardcoded weekday labels in the markup; the component renders them from locale.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-calendar-bg, --ren-calendar-border, --ren-calendar-day-size, --ren-calendar-radius, --ren-calendar-range-bg, --ren-calendar-selected-bg, --ren-calendar-selected-color, --ren-calendar-today-bg, --ren-calendar-width.\"\n    - \"Semantic tokens: --color-surface-raised, --color-border, --color-text, --color-text-muted, --color-text-faint, --color-fill, --color-fill-active, --color-accent, --color-accent-subtle, --color-on-accent.\"\n    - \"Layout / motion tokens: --space-*, --radius-*, --shadow-md, --touch-min, --duration-enter, --ease-enter, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Hardcoded day-cell sizes; tune --ren-calendar-day-size or pick the .ren-calendar-sm / .ren-calendar-lg variant.\"\n\naccessibility:\n  required:\n    - \"Day cells implement the ARIA grid pattern with a single roving tabindex; never expose more than one tab stop in the grid.\"\n    - \"Keyboard: Arrow keys move focus between days, PageUp/PageDown move months, Home/End jump within a week — preserve these in any customization.\"\n    - \"Selected days set aria-selected=\\\"true\\\"; do not communicate selection through color alone.\"\n    - \"Today indicator uses [data-today] AND an inset ring (box-shadow); the ring is required so color-blind users perceive today.\"\n    - \"Out-of-month days set [data-outside] and pointer-events: none; do not let them receive focus.\"\n    - \"Min/max bounds disable cells via the native [disabled] attribute so the browser blocks activation.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-calendar/ren-calendar.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-calendar/ren-calendar.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-calendar value=\"2026-07-10\" aria-label=\"Choose a date\"></ren-calendar>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-calendar`\n- `.ren-calendar-day`\n- `.ren-calendar-day-in-range`\n- `.ren-calendar-day-range-end`\n- `.ren-calendar-day-range-start`\n- `.ren-calendar-grid`\n- `.ren-calendar-header`\n- `.ren-calendar-lg`\n- `.ren-calendar-nav`\n- `.ren-calendar-next`\n- `.ren-calendar-prev`\n- `.ren-calendar-sm`\n- `.ren-calendar-title`\n- `.ren-calendar-weekday`\n- `.ren-calendar-weekdays`\n\n## States And Attributes\n\n- `[aria-selected]`\n- `[data-outside]`\n- `[data-today]`\n- `:active`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-calendar-bg`\n- `--ren-calendar-border`\n- `--ren-calendar-day-size`\n- `--ren-calendar-radius`\n- `--ren-calendar-range-bg`\n- `--ren-calendar-selected-bg`\n- `--ren-calendar-selected-color`\n- `--ren-calendar-today-bg`\n- `--ren-calendar-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-calendar/ren-calendar.css`\n- `components/composites/ren-calendar/ren-calendar.js`\n- `docs/components/ren-calendar.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-calendar/component.md",
      "id": "contract:composite:ren-calendar",
      "type": "contract",
      "name": "ren-calendar component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-carousel Component Contract\n\nSlide navigation composite for grouped media/content.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-carousel` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-carousel` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Carousel composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"Multiple peer items (images, cards, testimonials) need horizontal cycling with snap behavior.\"\n    - \"Different slides-per-view densities are needed (.ren-carousel-2 / -3 / -4 / -full).\"\n    - \"Optional autoplay with visible progress indicator is desired (data-autoplay + data-autoplay-duration).\"\n    - \"Touch swipe + keyboard arrow navigation are required.\"\n    - \"You want a fade transition variant via .ren-carousel-fade in addition to scroll-snap.\"\n  avoidWhen:\n    - \"Content is editorial reading order — use a vertical stack instead (carousels hide content).\"\n    - \"The interaction is a tab switch with persistent panels — use ren-tabs.\"\n    - \"Items are step-by-step in a process — use ren-stepper / ren-wizard.\"\n    - \"There is only one or two items — render them inline.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-carousel/ren-carousel.css\"\n  js:\n    - \"rends/components/composites/ren-carousel/ren-carousel.js\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"JS handles arrow generation, dot pagination, autoplay timer, and aria-current bookkeeping; CSS-only fallback offers scroll-snap + the native ::scroll-button() pseudos when supported.\"\n    - \"Add data-autoplay to enable autoplay; pair with data-autoplay-duration=\\\"5000\\\" (ms) for the progress bar.\"\n\nrequiredMarkup:\n  - \"<ren-carousel> wraps a single <div class=\\\"ren-carousel-viewport\\\"> which contains <div class=\\\"ren-carousel-slide\\\"> children.\"\n  - \"Use .ren-carousel-2 / -3 / -4 / -full on the host to declare slides-per-view; do not set flex-basis inline.\"\n  - \"Arrow buttons use .ren-carousel-prev / .ren-carousel-next; the JS injects them when needed, do not author chevron glyphs by hand.\"\n  - \"Dot list is <ul class=\\\"ren-carousel-dots\\\"> with <li><button class=\\\"ren-carousel-dot\\\" aria-current=\\\"true|false\\\"></button></li> items.\"\n  - \"Each slide carries aria-current=\\\"true\\\" only when active; the component manages this attribute.\"\n\nforbiddenPatterns:\n  - \"Replacing slides with arbitrary <div role=\\\"group\\\"> without scroll-snap-align: start — breaks snap behavior.\"\n  - \"Hardcoded translateX(-N%) transforms to advance slides; use scroll-snap or the fade variant.\"\n  - \"Adding an autoplay timer in consumer code instead of using data-autoplay + data-autoplay-duration.\"\n  - \"Hiding arrows by setting display: none on .ren-carousel-prev/.ren-carousel-next — use the .ren-carousel-no-arrows variant.\"\n  - \"Inline width: 50% on a slide to override slides-per-view — switch to the correct .ren-carousel-N class.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-carousel-arrow-size, --ren-carousel-dot-active, --ren-carousel-dot-color, --ren-carousel-dot-size, --ren-carousel-duration, --ren-carousel-easing, --ren-carousel-gap, --ren-carousel-radius.\"\n    - \"Semantic tokens: --color-accent, --color-border, --color-text, --color-text-muted.\"\n    - \"Layout / motion tokens: --space-*, --radius-full, --touch-min, --duration-enter, --ease-enter, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides (the default rgba arrow scrim lives inside the component; do not duplicate it).\"\n    - \"Hardcoded dot sizes / colors; tune --ren-carousel-dot-size / --ren-carousel-dot-active.\"\n\naccessibility:\n  required:\n    - \"Wrap the carousel with role=\\\"region\\\" (or role=\\\"group\\\") and an accessible name so screen readers can label the collection.\"\n    - \"Active slide / active dot use aria-current=\\\"true\\\"; never communicate the active state through color alone.\"\n    - \"Keyboard: Left/Right arrows traverse slides; arrow buttons and dots are real <button> elements with :focus-visible rings.\"\n    - \"Touch targets on .ren-carousel-prev/.ren-carousel-next stay ≥ 44px (min-width / min-height enforced in CSS).\"\n    - \"Autoplay must pause on hover / focus / data-autoplay removal; aria-busy=\\\"true\\\" disables interaction during transitions.\"\n    - \"Respect prefers-reduced-motion: scroll-behavior becomes auto and the autoplay progress bar shows the final frame instead of animating.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-carousel/ren-carousel.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-carousel/ren-carousel.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-carousel aria-label=\"Featured articles\"><div class=\"ren-carousel-viewport\"><div class=\"ren-carousel-slide\">First article</div><div class=\"ren-carousel-slide\">Second article</div></div></ren-carousel>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-carousel`\n- `.ren-carousel-2`\n- `.ren-carousel-3`\n- `.ren-carousel-4`\n- `.ren-carousel-counter`\n- `.ren-carousel-counter-current`\n- `.ren-carousel-dot`\n- `.ren-carousel-dots`\n- `.ren-carousel-fade`\n- `.ren-carousel-full`\n- `.ren-carousel-next`\n- `.ren-carousel-no-arrows`\n- `.ren-carousel-no-counter`\n- `.ren-carousel-no-dots`\n- `.ren-carousel-prev`\n- `.ren-carousel-slide`\n- `.ren-carousel-viewport`\n\n## States And Attributes\n\n- `[aria-busy]`\n- `[aria-current]`\n- `[data-at-end]`\n- `[data-at-start]`\n- `[data-autoplay-duration]`\n- `[data-autoplay]`\n- `:active`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-carousel-arrow-size`\n- `--ren-carousel-dot-active`\n- `--ren-carousel-dot-color`\n- `--ren-carousel-dot-size`\n- `--ren-carousel-duration`\n- `--ren-carousel-easing`\n- `--ren-carousel-gap`\n- `--ren-carousel-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-carousel/ren-carousel.css`\n- `components/composites/ren-carousel/ren-carousel.js`\n- `docs/components/ren-carousel.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-carousel/component.md",
      "id": "contract:composite:ren-carousel",
      "type": "contract",
      "name": "ren-carousel component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-collapsible Component Contract\n\nExpandable/collapsible region with explicit state.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-collapsible` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-collapsible` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Collapsible composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"A single, standalone expandable section is needed (no sibling exclusivity).\"\n    - \"You want a zero-JS disclosure based on native <details>/<summary>.\"\n    - \"Initial state must be declarable in HTML via the [open] attribute.\"\n    - \"A subtle ghost variant (no border, flush) is desired.\"\n    - \"The content area animates height via CSS interpolate-size (no JS measurement).\"\n  avoidWhen:\n    - \"Multiple grouped disclosures share chrome — use ren-accordion (handles exclusive mode).\"\n    - \"The disclosure floats above content as a popover — use ren-popover.\"\n    - \"The trigger must control a remote panel — use a button + aria-controls on a custom panel.\"\n    - \"The disclosure is a navigation reveal — use ren-sidebar / ren-menu.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-collapsible/ren-collapsible.css\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"No colocated JS — open / close uses the native <details> toggle event; consumers can listen for 'toggle' if needed.\"\n    - \"Add interpolate-size support polyfill only if you require height animation on legacy browsers.\"\n\nrequiredMarkup:\n  - \"Root element is <details class=\\\"ren-collapsible\\\">; do not wrap a <div> with a custom click handler.\"\n  - \"Trigger is the first child <summary>; content lives in a sibling <div class=\\\"ren-collapsible-content\\\">.\"\n  - \"Use the [open] attribute to render expanded on first paint (do not set hidden / display via style).\"\n  - \"Add .ren-collapsible-ghost to the host for the borderless variant; do not invent additional variant classes.\"\n  - \"Keep the chevron decorative — it is rendered via the summary::after pseudo-element.\"\n\nforbiddenPatterns:\n  - \"<div class=\\\"ren-collapsible\\\"><div role=\\\"button\\\">…</div></div> — must be a real <details>/<summary>.\"\n  - \"Custom triangle / chevron inside the summary text content; rely on summary::after rotation.\"\n  - \"Animating with JavaScript-driven max-height; CSS interpolate-size handles auto-to-zero transitions.\"\n  - \"Removing the focus ring (outline: none) without restoring :focus-visible on summary.\"\n  - \"Calling .open = true on a detached <details> outside the DOM — bind it inside .ren-collapsible first.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-collapse-bg, --ren-collapse-border, --ren-collapse-duration, --ren-collapse-easing, --ren-collapse-padding, --ren-collapse-radius, --ren-collapse-trigger-font, --ren-collapse-trigger-weight.\"\n    - \"Semantic tokens: --color-border, --color-text, --color-text-muted, --color-text-secondary, --color-fill, --color-focus-ring.\"\n    - \"Layout / motion tokens: --space-*, --radius-md, --stroke-1, --touch-min, --ring-width, --duration-enter, --ease-enter, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Raw transition values; use --duration-enter / --ease-enter or --ren-collapse-duration / --ren-collapse-easing.\"\n\naccessibility:\n  required:\n    - \"Use the real <details>/<summary> pair so the browser exposes the native disclosure pattern and supports Enter / Space toggle.\"\n    - \"Touch target on the summary stays ≥ var(--touch-min) (44px) — do not shrink below this on touch surfaces.\"\n    - \"Visible :focus-visible ring on summary uses --color-focus-ring and --ring-width; do not remove it.\"\n    - \"Chevron is decorative (::after pseudo) — never expose its rotation to AT via aria-label.\"\n    - \"Communicate open state through the [open] attribute (native semantics) — never rely on color alone.\"\n    - \"Animations respect prefers-reduced-motion (chevron rotation transition is disabled).\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-collapsible/ren-collapsible.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<details class=\"ren-collapsible\"><summary>Advanced settings</summary><div class=\"ren-collapsible-content\">Additional controls</div></details>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-collapsible`\n- `.ren-collapsible-content`\n- `.ren-collapsible-ghost`\n\n## States And Attributes\n\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-collapse-bg`\n- `--ren-collapse-border`\n- `--ren-collapse-duration`\n- `--ren-collapse-easing`\n- `--ren-collapse-padding`\n- `--ren-collapse-radius`\n- `--ren-collapse-trigger-font`\n- `--ren-collapse-trigger-weight`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/composites/ren-collapsible/ren-collapsible.css`\n- `docs/components/ren-collapsible.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-collapsible/component.md",
      "id": "contract:composite:ren-collapsible",
      "type": "contract",
      "name": "ren-collapsible component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-color-picker Component Contract\n\nColor input composite for selecting and previewing colors.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-color-picker` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-color-picker` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Color Picker composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"Users must pick an arbitrary color via canvas (S/B) + hue + optional alpha.\"\n    - \"Multiple output formats are needed (HEX / RGB / HSL) with a toggle.\"\n    - \"Manual entry through hex + channel inputs and preset swatches is required.\"\n    - \"Eyedropper integration (EyeDropper API) is desired when supported.\"\n    - \"The control opens in a popover anchored to a trigger button showing the current color, with placement=\\\"top|right|bottom|left\\\" mirrored to data-side.\"\n  avoidWhen:\n    - \"Users only need to pick from a small fixed palette — render a list of ren-swatch / radio buttons.\"\n    - \"A native <input type=\\\"color\\\"> is sufficient (no alpha, no format toggle, no presets).\"\n    - \"The UI is a brand-theme picker driven by tokens — use a token-aware variant.\"\n    - \"The control must live inline without a dropdown — wrap the dropdown content directly without the trigger.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-color-picker/ren-color-picker.css\"\n  js:\n    - \"rends/components/composites/ren-color-picker/ren-color-picker.js\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"JS is required: HSV ↔ HEX / RGB / HSL conversion, canvas painting, drag handles, and the popover open/close are all implemented in JS.\"\n    - \"The dropdown uses the native Popover API plus CSS anchor positioning via position-area; older browsers fall back to local absolute positioning.\"\n\nrequiredMarkup:\n  - \"Root <ren-color-picker> is the consumer-authored host; its JS generates the trigger button and .ren-color-picker-dropdown popover.\"\n  - \"Trigger is a real <button class=\\\"ren-color-picker-trigger\\\"> with aria-expanded reflecting popover state.\"\n  - \"Saturation/brightness area renders a <canvas class=\\\"ren-color-picker-saturation-canvas\\\"> inside .ren-color-picker-saturation; do not replace with a gradient div.\"\n  - \"Hue handle and alpha handle are decorative thumbs (no tab stops) — keyboard input lives on the hex / channel inputs.\"\n  - \"Swatch grid items are real <button class=\\\"ren-color-picker-swatch\\\"> with aria-selected on the active swatch.\"\n  - \"Use placement=\\\"bottom\\\" by default; the host and .ren-color-picker-dropdown mirror the preferred side to data-side.\"\n\nforbiddenPatterns:\n  - \"Replacing the canvas with a CSS gradient div — drag math relies on canvas pixel sampling.\"\n  - \"Hardcoded hue rainbow gradients in consumer overrides; the rainbow's six hex stops are intentional and locked to HSL pure hues.\"\n  - \"Setting box-shadow / outline overrides that hide the focus ring on .ren-color-picker-trigger or .ren-color-picker-swatch.\"\n  - \"Using window.alert / window.prompt for color input; the component already exposes hex + channel inputs.\"\n  - \"Calling EyeDropper directly from consumer code; the .ren-color-picker-eyedropper button wraps the API with feature detection.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-cp-bg, --ren-cp-handle-size, --ren-cp-radius, --ren-cp-shadow, --ren-cp-swatch-size, --ren-cp-track-height, --ren-cp-width.\"\n    - \"Semantic tokens: --color-surface, --color-surface-raised, --color-border, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-accent, --color-input-bg, --color-input-border, --color-input-border-focus, --color-input-focus-ring.\"\n    - \"Layout / motion tokens: --space-*, --radius-*, --shadow-lg, --touch-min, --font-mono, --duration-enter, --ease-enter, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides (the hue rainbow stops inside the component are the only sanctioned exception).\"\n    - \"Custom drop shadows that bypass --ren-cp-shadow / --shadow-lg.\"\n\naccessibility:\n  required:\n    - \"Trigger button has aria-haspopup=\\\"dialog\\\" (or appropriate) and aria-expanded reflects the dropdown state.\"\n    - \"All inputs (.ren-color-picker-hex-input, .ren-color-picker-channel-input) have associated <label> via .ren-color-picker-channel-label or aria-label.\"\n    - \"The saturation area is operable via keyboard (Arrow keys move the handle in 1% steps; Shift+Arrow in larger steps).\"\n    - \"Focus rings use --color-input-focus-ring; never set outline: none without restoring box-shadow ring.\"\n    - \"Color is never communicated by hue alone — value text appears alongside the swatch (preview label + value).\"\n    - \"Animations honor prefers-reduced-motion (open animation and transitions are clamped to 0.01ms).\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-color-picker/ren-color-picker.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-color-picker/ren-color-picker.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-color-picker value=\"#007aff\" placement=\"bottom\"></ren-color-picker>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-color-picker`\n- `.ren-color-picker-alpha`\n- `.ren-color-picker-alpha-gradient`\n- `.ren-color-picker-alpha-handle`\n- `.ren-color-picker-channel`\n- `.ren-color-picker-channel-input`\n- `.ren-color-picker-channel-label`\n- `.ren-color-picker-channels`\n- `.ren-color-picker-controls`\n- `.ren-color-picker-controls-left`\n- `.ren-color-picker-controls-right`\n- `.ren-color-picker-dropdown`\n- `.ren-color-picker-eyedropper`\n- `.ren-color-picker-eyedropper-icon`\n- `.ren-color-picker-format-toggle`\n- `.ren-color-picker-hex-input`\n- `.ren-color-picker-hue`\n- `.ren-color-picker-hue-handle`\n- `.ren-color-picker-inputs`\n- `.ren-color-picker-preview`\n- `.ren-color-picker-preview-info`\n- `.ren-color-picker-preview-label`\n- `.ren-color-picker-preview-swatch`\n- `.ren-color-picker-preview-value`\n- `...and 9 more in the source files.`\n\n## States And Attributes\n\n- `[aria-expanded]`\n- `[aria-selected]`\n- `[data-side]`\n- `placement`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-cp-bg`\n- `--ren-cp-handle-size`\n- `--ren-cp-radius`\n- `--ren-cp-shadow`\n- `--ren-cp-swatch-size`\n- `--ren-cp-track-height`\n- `--ren-cp-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-color-picker/ren-color-picker.css`\n- `components/composites/ren-color-picker/ren-color-picker.js`\n- `docs/components/ren-color-picker.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-color-picker/component.md",
      "id": "contract:composite:ren-color-picker",
      "type": "contract",
      "name": "ren-color-picker component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-combobox Component Contract\n\nAutocomplete/select composite with input, listbox, and keyboard behavior.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-combobox` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-combobox` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Combobox composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"Users must filter / autocomplete from a large list using free-text input.\"\n    - \"You need a typeahead with keyboard navigation (Arrow Up/Down, Enter, Escape).\"\n    - \"Grouped options or items with secondary descriptions are needed.\"\n    - \"Async loading states (.ren-combobox-loading) or empty states (.ren-combobox-empty) are required.\"\n    - \"Single-select with a text-input affordance is the right control (input visible at rest).\"\n    - \"The inline list needs a preferred top/bottom opening side via placement without becoming a Popover API overlay.\"\n  avoidWhen:\n    - \"Users pick from a short fixed list without filtering — use ren-select.\"\n    - \"Multi-select with tag chips is required — use a dedicated multi-select / ren-multiselect.\"\n    - \"The input is a free-form search that submits a query — use ren-input + ren-search.\"\n    - \"The list is a navigation menu — use ren-menu / ren-command.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-combobox/ren-combobox.css\"\n  js:\n    - \"rends/components/composites/ren-combobox/ren-combobox.js\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"CSS works standalone on the .ren-combobox structure; <ren-combobox> JS adds ARIA wiring, filtering, highlight bookkeeping, and open/close.\"\n    - \"The component does not define --ren-combobox-* tokens; theme through semantic tokens or scope semantic token overrides.\"\n\nrequiredMarkup:\n  - \"Root <ren-combobox> wraps consumer-authored .ren-combobox-item options; its JS generates the input, listbox, hidden input, and live region.\"\n  - \"Each option is <div class=\\\"ren-combobox-item\\\" role=\\\"option\\\"> with aria-selected toggled by the component and data-highlighted reflecting keyboard focus.\"\n  - \"Use .ren-combobox-item-label and .ren-combobox-item-description inside items for two-line content; do not nest extra layout divs.\"\n  - \"Empty state lives in <div class=\\\"ren-combobox-empty\\\" hidden> and the loading state in <div class=\\\"ren-combobox-loading\\\" hidden>.\"\n  - \"Group items inside <div class=\\\"ren-combobox-group\\\"> with a leading <div class=\\\"ren-combobox-group-label\\\">.\"\n  - \"Use placement=\\\"bottom\\\" by default; the host and .ren-combobox-list mirror placement to data-side. Top changes the absolute list fallback to open above the input.\"\n\nforbiddenPatterns:\n  - \"Replacing <input> with a contentEditable div — breaks IME, autofill, and form submission.\"\n  - \"Using <ul>/<li> for the list when the component's CSS targets .ren-combobox-list / .ren-combobox-item with role=\\\"listbox\\\" / role=\\\"option\\\".\"\n  - \"Using :hover styling alone to indicate keyboard highlight; rely on [data-highlighted] and aria-selected=\\\"true\\\".\"\n  - \"Toggling visibility with display: none on the list; use the [hidden] attribute (CSS handles the close transition).\"\n  - \"Hardcoded dropdown widths; the list uses inset-inline: 0 to match the input width — override --space-* / --radius-* tokens instead.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic tokens: --color-surface, --color-border, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-fill-active, --color-accent, --color-accent-subtle.\"\n    - \"Typography / layout: --text-xs, --text-sm, --leading-5, --space-*, --radius-sm, --radius-md, --radius-lg, --radius-full, --shadow-lg.\"\n    - \"Motion tokens: --duration-micro, --duration-enter, --duration-slow, --ease-enter, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Defining new --ren-combobox-* component tokens — the component intentionally inherits semantic tokens.\"\n\naccessibility:\n  required:\n    - \"Input has role=\\\"combobox\\\", aria-expanded reflecting list visibility, aria-controls pointing to the .ren-combobox-list id, and aria-activedescendant pointing to the highlighted option.\"\n    - \"List has role=\\\"listbox\\\" and each item has role=\\\"option\\\"; do not nest interactive controls inside an option.\"\n    - \"Keyboard contract: Arrow Down opens / moves down, Arrow Up moves up, Enter selects, Escape closes; preserve all four.\"\n    - \"Selected option sets aria-selected=\\\"true\\\"; selection is announced — never communicate via color alone.\"\n    - \"Disabled options set aria-disabled=\\\"true\\\" AND ignore activation in JS (pointer-events styling is not enough).\"\n    - \"Loading state uses an aria-live region (or aria-busy on the input) so AT users know results are pending.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-combobox/ren-combobox.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-combobox/ren-combobox.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-combobox name=\"country\" placeholder=\"Search a country\">\n  <div class=\"ren-combobox-item\" data-value=\"ar\">Argentina</div>\n  <div class=\"ren-combobox-item\" data-value=\"uy\">Uruguay</div>\n</ren-combobox>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-combobox`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[aria-selected]`\n- `[data-side]`\n- `[data-highlighted]`\n- `placement`\n- `:disabled`\n- `:hover`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-combobox/ren-combobox.css`\n- `components/composites/ren-combobox/ren-combobox.js`\n- `docs/components/ren-combobox.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-combobox/component.md",
      "id": "contract:composite:ren-combobox",
      "type": "contract",
      "name": "ren-combobox component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-context-menu Component Contract\n\nContextual action menu opened from a target or pointer interaction.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-context-menu` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-context-menu` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Context Menu composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"A target area needs a right-click (or long-press) action menu that opens at the pointer.\"\n    - \"The menu hosts imperative actions (cut / copy / paste / delete) rather than navigation.\"\n    - \"You want the native Popover API plus the shared ren-menu item styling.\"\n    - \"Positioning should follow the pointer instead of a static anchor.\"\n    - \"A scoped trigger region is needed (declared via data-context=\\\"<menu-id>\\\").\"\n  avoidWhen:\n    - \"The menu opens from a button click — use ren-menu (dropdown) instead.\"\n    - \"The trigger is a hover affordance — use ren-popover / ren-hover-card.\"\n    - \"The menu is the primary navigation — use ren-menubar / ren-sidebar.\"\n    - \"You need cascading submenus — use ren-menu (supports nested patterns).\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-context-menu/ren-context-menu.css\"\n    - \"rends/components/composites/ren-menu/ren-menu.css\"\n  js:\n    - \"rends/components/composites/ren-context-menu/ren-context-menu.js\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"ren-menu.css must be present alongside this CSS — the menu items (.ren-menu-item, .ren-menu-separator) live there and are reused.\"\n    - \"JS handles contextmenu event interception, pointer-based positioning, and popover open/close.\"\n\nrequiredMarkup:\n  - \"Declare the trigger region with data-context=\\\"<menu-id>\\\" on any element; do not use inline oncontextmenu attributes.\"\n  - \"Menu root is <ren-context-menu id=\\\"<menu-id>\\\">; the component applies the .ren-context-menu, role=menu, and manual popover semantics.\"\n  - \"Items are real <button class=\\\"ren-menu-item\\\"> elements; separators are <hr class=\\\"ren-menu-separator\\\">.\"\n  - \"Destructive actions use .ren-menu-item-danger (defined in ren-menu.css); do not invent custom danger classes.\"\n  - \"Position is set by JS — do not write inline top/left on the menu element.\"\n\nforbiddenPatterns:\n  - \"Opening the menu via custom JS that calls .style.display = 'block' — use showPopover() so the API handles light dismiss + the top layer.\"\n  - \"<div role=\\\"menuitem\\\" tabindex=\\\"0\\\"> styled as a button; use a real <button class=\\\"ren-menu-item\\\">.\"\n  - \"Static anchored positioning (top: 0; left: 0) — context menus must follow the contextmenu event coordinates.\"\n  - \"Suppressing the native context menu globally (document.oncontextmenu = e => e.preventDefault()) — scope prevention to data-context regions only.\"\n  - \"Hardcoded box-shadow / hex backgrounds; use --shadow-lg and --color-surface.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic tokens: --color-surface, --color-border, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-fill-active, --color-danger, --color-accent.\"\n    - \"Layout / motion tokens: --space-*, --stroke-1, --radius-lg, --shadow-lg, --duration-enter, --ease-enter.\"\n    - \"Reuses --ren-menu-* tokens defined by ren-menu (item padding, hover bg, etc.) — override at the ren-menu scope.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Defining new --ren-context-menu-* component tokens — the component inherits from ren-menu intentionally.\"\n\naccessibility:\n  required:\n    - \"Menu root has role=\\\"menu\\\" (set by JS) and each .ren-menu-item has role=\\\"menuitem\\\"; do not nest interactive controls inside items.\"\n    - \"Keyboard: Arrow Up/Down moves focus across items, Home/End jump to first/last, Escape closes the menu, Enter / Space activates.\"\n    - \"Trigger must be reachable by keyboard via Shift+F10 (or the dedicated context-menu key) — never gate the menu behind right-click only.\"\n    - \"Disabled items set aria-disabled=\\\"true\\\" AND skip in keyboard navigation order.\"\n    - \"Focus moves into the menu on open and returns to the trigger on close — preserve this when extending behavior.\"\n    - \"Reduced motion: open transition is disabled under prefers-reduced-motion (already handled).\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-context-menu/ren-context-menu.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-context-menu/ren-context-menu.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div data-context=\"editor-actions\" tabindex=\"0\">Right-click me</div>\n<ren-context-menu id=\"editor-actions\">\n  <button class=\"ren-menu-item\" role=\"menuitem\">Copy</button>\n  <button class=\"ren-menu-item\" role=\"menuitem\">Paste</button>\n</ren-context-menu>\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-context-menu`\n- `.ren-menu-item`\n- `.ren-menu-separator`\n- `.ren-open`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[data-context]`\n- `trigger-id`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-context-menu/ren-context-menu.css`\n- `components/composites/ren-context-menu/ren-context-menu.js`\n- `docs/components/ren-context-menu.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-context-menu/component.md",
      "id": "contract:composite:ren-context-menu",
      "type": "contract",
      "name": "ren-context-menu component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-date-picker Component Contract\n\nDate input composite using a calendar popover.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-date-picker` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-date-picker` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Date Picker composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User needs to pick a single date (or a single contiguous range) from a calendar popover.\"\n    - \"Trigger should be a button-like control that opens a positioned popover (CSS anchor positioning with position-area) with an embedded <ren-calendar>.\"\n    - \"The form needs an ISO-string value submitted via a hidden input under a given name.\"\n    - \"Preset shortcuts (Today, Tomorrow, This Week) should appear alongside the calendar grid.\"\n    - \"Mobile must adapt to a bottom sheet automatically; desktop floats below the trigger.\"\n  avoidWhen:\n    - \"You need two independent start/end inputs with apply/cancel — use ren-date-range-picker.\"\n    - \"You only need a raw native <input type=\\\"date\\\"> with no popover styling — fall back to base/primitive-zero.\"\n    - \"You need a non-date generic dropdown — use ren-menu, ren-select, or ren-popover.\"\n    - \"You need a multi-month calendar surface without an input trigger — render <ren-calendar> directly.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-date-picker/ren-date-picker.css\"\n  js:\n    - \"rends/components/composites/ren-date-picker/ren-date-picker.js\"\n  notes:\n    - \"Also requires ren-calendar to be registered (the component instantiates <ren-calendar> inside its dropdown).\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Use <ren-date-picker> as the host element so the upgrade lifecycle wires up the trigger, dropdown, and hidden input.\"\n  - \"Trigger must be a real <button class=\\\"ren-date-picker-trigger\\\"> with type=\\\"button\\\", aria-haspopup=\\\"dialog\\\", and aria-expanded; the component sets these if it renders the trigger itself.\"\n  - \"The dropdown surface is a <div class=\\\"ren-date-picker-dropdown\\\" popover=\\\"manual\\\"> — keep the popover attribute so showPopover()/hidePopover() work.\"\n  - \"Inside the dropdown render a <ren-calendar> child; the host syncs its mode/locale/min/max attributes onto it.\"\n  - \"If you need form submission, set name=\\\"…\\\" on <ren-date-picker> so the auto-injected <input type=\\\"hidden\\\"> carries the ISO value.\"\n  - \"Use placement=\\\"bottom\\\" by default; the host and .ren-date-picker-dropdown mirror the preferred side to data-side.\"\n\nforbiddenPatterns:\n  - \"Styling a <div> or <span> as the trigger instead of a real <button> — keyboard activation and aria-expanded depend on the button.\"\n  - \"Replacing the inner <ren-calendar> with raw markup — the host wires ren-date-select events directly to the calendar element.\"\n  - \"Hardcoding popover positioning offsets via inline styles — rely on anchor-name: --ren-date-picker-anchor, position-anchor, and position-area in the stylesheet.\"\n  - \"Toggling the dropdown by setting display: none — use open()/close()/toggle() on the host so aria-expanded and popover state stay in sync.\"\n  - \"Wrapping a native <input type=\\\"date\\\"> in .ren-date-picker — the component expects to own its trigger and popover.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-calendar-bg, --ren-calendar-border, --ren-calendar-day-size, --ren-calendar-radius, --ren-calendar-range-bg, --ren-calendar-selected-bg, --ren-calendar-selected-color, --ren-calendar-today-bg, --ren-calendar-width.\"\n    - \"Semantic surface tokens used by the trigger / dropdown: --color-surface, --color-surface-raised, --color-border, --color-border-strong, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-fill-active, --color-accent, --color-danger, --color-success.\"\n    - \"Layout/motion tokens: --space-*, --radius-md, --radius-lg, --shadow-lg, --duration-enter, --ease-enter, --transition-tactile, --touch-min, --body-size, --label-size.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / rgb() colors for trigger or dropdown chrome; theme via --color-* and the --ren-calendar-* token API.\"\n    - \"Custom transition/animation durations; reuse --duration-enter and --ease-enter so reduced-motion overrides apply.\"\n\naccessibility:\n  required:\n    - \"Trigger is a real <button type=\\\"button\\\"> with aria-haspopup=\\\"dialog\\\" and aria-expanded that flips true/false as the dropdown opens and closes.\"\n    - \"Trigger min-height is var(--touch-min) so the tap target meets 44px; do not reduce it for touch contexts.\"\n    - \":focus-visible on the trigger and presets must show the --color-accent outline; do not remove it without restoring an equivalent ring.\"\n    - \"Escape closes the dropdown; Enter/Space on the trigger opens it; opening focuses the first non-disabled .ren-calendar-day.\"\n    - \"Error/success states (.ren-date-picker-error, .ren-date-picker-success) must be paired with a text message — do not rely on border color alone.\"\n    - \"Closing returns focus to the trigger; do not break this contract when overriding open()/close().\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-date-picker/ren-date-picker.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-date-picker/ren-date-picker.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-date-picker name=\"start-date\" placement=\"bottom\"><button class=\"ren-date-picker-trigger\" type=\"button\" aria-haspopup=\"dialog\" aria-expanded=\"false\">Choose date</button><div class=\"ren-date-picker-dropdown\" popover=\"manual\"><ren-calendar></ren-calendar></div></ren-date-picker>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-calendar`\n- `.ren-date-picker`\n- `.ren-date-picker-dropdown`\n- `.ren-date-picker-empty`\n- `.ren-date-picker-error`\n- `.ren-date-picker-preset`\n- `.ren-date-picker-presets`\n- `.ren-date-picker-range`\n- `.ren-date-picker-success`\n- `.ren-date-picker-trigger`\n- `.ren-date-picker-value`\n\n## States And Attributes\n\n- `[data-side]`\n- `placement`\n- `:active`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-calendar-bg`\n- `--ren-calendar-border`\n- `--ren-calendar-day-size`\n- `--ren-calendar-radius`\n- `--ren-calendar-range-bg`\n- `--ren-calendar-selected-bg`\n- `--ren-calendar-selected-color`\n- `--ren-calendar-today-bg`\n- `--ren-calendar-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-date-picker/ren-date-picker.css`\n- `components/composites/ren-date-picker/ren-date-picker.js`\n- `docs/components/ren-date-picker.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-date-picker/component.md",
      "id": "contract:composite:ren-date-picker",
      "type": "contract",
      "name": "ren-date-picker component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-date-range-picker Component Contract\n\nTwo-date selection composite for ranges.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-date-range-picker` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-date-range-picker` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Date Range Picker composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User needs to pick a contiguous date range (start + end) confirmed by an explicit Apply action.\"\n    - \"UI needs dual side-by-side calendars (left = start month, right = next month) inside a CSS-anchor-positioned popover.\"\n    - \"Preset shortcuts like Last 7 / Last 30 / This Month / Last Month should be selectable as a sidebar listbox.\"\n    - \"Form submission needs two ISO-string fields named `<name>-start` and `<name>-end` via injected hidden inputs.\"\n    - \"Selection has Apply/Cancel semantics — draft state on outside-click or Cancel must revert to the previously confirmed range.\"\n  avoidWhen:\n    - \"Only a single date is needed — use ren-date-picker (mode=\\\"single\\\").\"\n    - \"User picks two unrelated dates (start-of-trip, end-of-warranty) that are not a contiguous range — use two ren-date-picker instances.\"\n    - \"Range needs commit-on-click without Apply/Cancel — use ren-date-picker with mode=\\\"range\\\".\"\n    - \"No calendar UI is needed; a quick \\\"last N days\\\" preset is enough — render ren-select / ren-menu of presets only.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-date-range-picker/ren-date-range-picker.css\"\n  js:\n    - \"rends/components/composites/ren-date-range-picker/ren-date-range-picker.js\"\n  notes:\n    - \"Also requires ren-calendar to be registered — the component instantiates two <ren-calendar mode=\\\"range\\\"> children inside the dropdown.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Use <ren-date-range-picker> as the host so the upgrade lifecycle builds the trigger, dropdown, dual calendars, presets, and hidden inputs.\"\n  - \"The trigger is a real <button class=\\\"ren-date-range-trigger\\\"> with aria-haspopup=\\\"dialog\\\" and aria-expanded; do not replace it with a div.\"\n  - \"The dropdown root carries role=\\\"dialog\\\", aria-label, and popover=\\\"manual\\\"; presets render inside role=\\\"listbox\\\" with each preset as role=\\\"option\\\" + data-preset=\\\"<key>\\\".\"\n  - \"Footer must contain .ren-date-range-summary (aria-live=\\\"polite\\\"), .ren-date-range-cancel, and .ren-date-range-apply — Apply is disabled until both endpoints are set.\"\n  - \"Use `name=\\\"…\\\"` on the host so the component injects <input type=\\\"hidden\\\" name=\\\"<name>-start\\\"> and `…-end` for form submission.\"\n  - \"Use placement=\\\"bottom\\\" by default; the host and .ren-date-range-dropdown mirror the preferred side to data-side.\"\n\nforbiddenPatterns:\n  - \"Wiring presets to a single ren-calendar instead of using both calendarLeft/calendarRight — the host depends on having two ren-calendar children to call setRange on.\"\n  - \"Skipping the Apply button and writing directly to the trigger — Cancel/outside-click revert depends on a draft vs confirmed split.\"\n  - \"Hardcoding dropdown placement offsets or breakpoints — use placement/data-side for side preference, and keep the container-type: inline-size @container ren-date-range layout under 500px.\"\n  - \"Adding extra inline color tokens to .ren-date-range-preset[aria-selected=\\\"true\\\"] — the highlighted preset already styles via --color-accent-subtle / --color-accent.\"\n  - \"Calling showPopover() directly from outside — use the host's open() / close() / handleApply() / handleCancel() so draft/confirmed state stays consistent.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-calendar-bg, --ren-calendar-border, --ren-calendar-day-size, --ren-calendar-radius, --ren-calendar-range-bg, --ren-calendar-selected-bg, --ren-calendar-selected-color, --ren-calendar-today-bg, --ren-calendar-width.\"\n    - \"Semantic surface / text / accent tokens: --color-surface, --color-surface-raised, --color-border, --color-border-strong, --color-text, --color-text-secondary, --color-text-muted, --color-accent, --color-accent-hover, --color-accent-subtle, --color-on-accent, --color-fill, --color-fill-active, --color-danger, --color-success.\"\n    - \"Layout/motion tokens: --space-*, --radius-md, --radius-lg, --shadow-lg, --duration-enter, --ease-enter, --transition-tactile, --touch-min, --body-size, --label-size, --weight-semibold.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgba colors for any of the trigger, dropdown, presets, or actions — theme via the semantic and --ren-calendar-* tokens.\"\n    - \"Custom transition durations; reuse --duration-enter / --ease-enter so reduced-motion handling continues to apply.\"\n\naccessibility:\n  required:\n    - \"Trigger is a real <button type=\\\"button\\\"> with aria-haspopup=\\\"dialog\\\" and aria-expanded synced to the dropdown state.\"\n    - \"Dropdown is role=\\\"dialog\\\" with aria-label=\\\"Date range selection\\\" and focuses the first preset (or first calendar day) on open.\"\n    - \"Preset listbox uses role=\\\"listbox\\\" with each .ren-date-range-preset as role=\\\"option\\\"; the active preset sets aria-selected=\\\"true\\\".\"\n    - \".ren-date-range-summary uses aria-live=\\\"polite\\\" so changes to the draft range are announced.\"\n    - \"Escape closes via Cancel semantics (reverts draft to confirmed); outside-click does the same — do not silently apply.\"\n    - \"Apply button is disabled (real disabled attribute) until both draftStart and draftEnd are present; do not bypass via aria-disabled alone.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-date-range-picker/ren-date-range-picker.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-date-range-picker/ren-date-range-picker.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-date-range-picker name=\"stay\" placement=\"bottom\"><button class=\"ren-date-range-trigger\" type=\"button\" aria-haspopup=\"dialog\" aria-expanded=\"false\">Choose dates</button><div class=\"ren-date-range-dropdown\" role=\"dialog\" aria-label=\"Choose date range\" popover=\"manual\"><div class=\"ren-date-range-summary\" aria-live=\"polite\">No dates selected</div><button class=\"ren-date-range-cancel\" type=\"button\">Cancel</button><button class=\"ren-date-range-apply\" type=\"button\" disabled>Apply</button></div></ren-date-range-picker>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-calendar`\n- `.ren-date-range-actions`\n- `.ren-date-range-apply`\n- `.ren-date-range-calendars`\n- `.ren-date-range-cancel`\n- `.ren-date-range-divider`\n- `.ren-date-range-dropdown`\n- `.ren-date-range-empty`\n- `.ren-date-range-end`\n- `.ren-date-range-error`\n- `.ren-date-range-footer`\n- `.ren-date-range-inner`\n- `.ren-date-range-picker`\n- `.ren-date-range-preset`\n- `.ren-date-range-presets`\n- `.ren-date-range-separator`\n- `.ren-date-range-start`\n- `.ren-date-range-success`\n- `.ren-date-range-summary`\n- `.ren-date-range-trigger`\n- `.ren-date-range-value`\n\n## States And Attributes\n\n- `[aria-selected]`\n- `[data-side]`\n- `placement`\n- `:active`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-calendar-bg`\n- `--ren-calendar-border`\n- `--ren-calendar-day-size`\n- `--ren-calendar-radius`\n- `--ren-calendar-range-bg`\n- `--ren-calendar-selected-bg`\n- `--ren-calendar-selected-color`\n- `--ren-calendar-today-bg`\n- `--ren-calendar-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-date-range-picker/ren-date-range-picker.css`\n- `components/composites/ren-date-range-picker/ren-date-range-picker.js`\n- `docs/components/ren-date-range-picker.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-date-range-picker/component.md",
      "id": "contract:composite:ren-date-range-picker",
      "type": "contract",
      "name": "ren-date-range-picker component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-dialog\n\nModal dialog composite built on the native `<dialog>` element with focus\ntrapping, scroll locking, animated open/close, and an alert-mode for\ncritical confirmations.\n\nLoad this file after `ren-design.md` and before generating, editing, or\nreviewing any `<ren-dialog>` UI.\n\n## Purpose\n\nOwns the modal-overlay UX of a `<dialog showModal>` plus keyboard / a11y\nbehavior: focus trap inside the dialog, restore focus on close, dismiss\non Escape (unless `no-escape`), close on backdrop click (unless `alert`),\nand animated open/close that respects `prefers-reduced-motion`. Supports\nsize variants and a mobile sheet adaptation.\n\n## Use When\n\n- The UI requires the user's full attention before continuing — confirms,\n  destructive flows, multi-step settings panes.\n- The interaction blocks the page underneath (background goes inert).\n- A critical alert dialog should not be dismissable by accident\n  (use `alert`).\n- A multi-step settings or onboarding form should appear inline as a modal.\n\n## Do Not Use When\n\n- The disclosure is non-modal — use `ren-popover`, `ren-tooltip`,\n  `ren-hover-card`.\n- The disclosure is a side panel — use `ren-sheet`.\n- The disclosure is a transient notification — use `ren-toast` /\n  `ren-banner`.\n- The control is a select / combobox / menu — use those composites.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The flow blocks the underlying page (modal).\"\n    - \"You need focus trap, scroll lock, Escape-to-close, and backdrop dismiss.\"\n    - \"You need an alert variant that disables accidental dismissal.\"\n    - \"You want the native <dialog> semantics (form method=dialog, returnValue, etc.).\"\n  avoidWhen:\n    - \"The disclosure is non-modal (popover, tooltip).\"\n    - \"The disclosure is a side panel — use ren-sheet.\"\n    - \"The notification should not steal focus — use ren-toast.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-dialog/ren-dialog.css\"\n  js:\n    - \"rends/components/composites/ren-dialog/ren-dialog.js\"\n  notes:\n    - \"JS is required (focus trap, animation, scroll lock). The native <dialog> alone does not provide RenDS' UX guarantees.\"\n\nrequiredMarkup:\n  - \"<ren-dialog> wraps a real <dialog> element. Do not replace the inner <dialog> with a <div>.\"\n  - \"Provide a unique id on <ren-dialog> when triggers reference it via data-dialog-trigger.\"\n  - \"Always include .ren-dialog-title (rendered as a real heading) for the accessible name.\"\n  - \"Close affordances use [data-dialog-close]; do not call dialog.close() directly from arbitrary handlers.\"\n\nforbiddenPatterns:\n  - \"<ren-dialog> wrapping a <div role=\\\"dialog\\\"> instead of a real <dialog>.\"\n  - \"Animating the backdrop manually with hardcoded rgba — use --ren-dialog-backdrop.\"\n  - \"Programmatic open via .show() when the user expects modal behavior — use .open() / .show() per native semantics.\"\n  - \"Putting form submit buttons outside <form method=\\\"dialog\\\"> when the dialog is a confirmation.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: every --ren-dialog-* listed in Public Token API.\"\n    - \"Semantic tokens for content inside the dialog (--color-text, --color-text-muted, etc.).\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, etc.) in consumer code.\"\n    - \"Hardcoded backdrop colors; use --ren-dialog-backdrop.\"\n\naccessibility:\n  required:\n    - \"Use a real <dialog> inside <ren-dialog> so the user agent provides modality semantics.\"\n    - \".ren-dialog-title supplies the accessible label when no aria-label is set.\"\n    - \"Modal dialogs trap focus and inert the rest of the page automatically; do not break the trap.\"\n    - \"Escape closes the dialog unless no-escape is set.\"\n    - \"Backdrop click closes the dialog unless alert is set.\"\n    - \"Restore focus to the trigger after close (handled by the component).\"\n```\n\n## Required CSS / JS Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-dialog/ren-dialog.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-dialog/ren-dialog.js\"></script>\n```\n\n## Canonical Markup\n\nConfirmation dialog with trigger:\n\n```html\n<button class=\"ren-btn\" data-dialog-trigger=\"confirm-delete\">Delete project</button>\n\n<ren-dialog id=\"confirm-delete\" alert>\n  <dialog>\n    <div class=\"ren-dialog-header\">\n      <h2 class=\"ren-dialog-title\">Delete project</h2>\n      <button class=\"ren-dialog-close\" data-dialog-close aria-label=\"Close\"></button>\n    </div>\n    <div class=\"ren-dialog-body\">\n      <p>This action cannot be undone.</p>\n    </div>\n    <div class=\"ren-dialog-footer\">\n      <button class=\"ren-btn ren-btn-secondary\" data-dialog-close>Cancel</button>\n      <button class=\"ren-btn ren-btn-danger\" data-dialog-close=\"delete\">Delete</button>\n    </div>\n  </dialog>\n</ren-dialog>\n```\n\nNative command API (when supported):\n\n```html\n<button commandfor=\"settings\" command=\"show-modal\">Open settings</button>\n<ren-dialog id=\"settings\">\n  <dialog>...</dialog>\n</ren-dialog>\n```\n\nProgrammatic API:\n\n```js\nconst dlg = document.querySelector('#settings');\ndlg.show();              // open\ndlg.close('saved');      // close with returnValue\ndlg.addEventListener('ren-close', (e) => console.log(e.detail.returnValue));\n```\n\n## Attributes, Events, and API\n\n- Host attributes: `open`, `alert`, `size=\"sm|md|lg|xl|full\"`, `no-escape`.\n- Trigger attributes: `[data-dialog-trigger=\"<id>\"]`, native\n  `commandfor` + `command=\"show-modal\"` where supported.\n- Close attributes: `[data-dialog-close]`. Optional value becomes the\n  dialog's `returnValue`.\n- Methods: `show()`, `open()`, `close(returnValue)`.\n- Getters: `isOpen`, `dialog`.\n- Events: `ren-open`, `ren-close` (with `detail.returnValue`).\n\n## Variants\n\n| Class                  | Role                                   |\n|------------------------|----------------------------------------|\n| `.ren-dialog`          | Default surface (applied internally).  |\n| `.ren-dialog-sm`       | Small width.                           |\n| `.ren-dialog-md`       | Medium (default size).                 |\n| `.ren-dialog-lg`       | Large width.                           |\n| `.ren-dialog-xl`       | Extra-large.                           |\n| `.ren-dialog-full`     | Full-screen on desktop.                |\n| `.ren-alert-dialog`    | Critical confirmation styling.         |\n| `.ren-dialog-header`   | Header slot.                           |\n| `.ren-dialog-title`    | Heading slot (renders as `<h*>`).      |\n| `.ren-dialog-description` | Subheading / description slot.      |\n| `.ren-dialog-body`     | Scrollable body slot.                  |\n| `.ren-dialog-footer`   | Action footer.                         |\n| `.ren-dialog-close`    | Close button slot.                     |\n\n## States\n\n| Selector / attr             | Meaning                                  |\n|-----------------------------|------------------------------------------|\n| `[open]`                    | Native `<dialog>` open state.            |\n| `[data-mobile-sheet]`       | Mobile adaptation as a bottom sheet.     |\n| `[data-dialog-trigger]`     | Marks an opener for a target dialog.     |\n| `[data-dialog-close]`       | Marks a close affordance.                |\n\n## Public Token API\n\n- `--ren-dialog-bg`\n- `--ren-dialog-border-color`\n- `--ren-dialog-radius`\n- `--ren-dialog-padding`\n- `--ren-dialog-gap`\n- `--ren-dialog-width`\n- `--ren-dialog-shadow`\n- `--ren-dialog-backdrop`\n- `--ren-dialog-duration`\n- `--ren-dialog-easing`\n\n## Accessibility Contract\n\n- A real `<dialog>` element supplies modality semantics; never substitute a\n  `<div role=\"dialog\">`.\n- `.ren-dialog-title` provides the accessible name; if you omit it, set\n  `aria-label` on `<ren-dialog>`.\n- The component traps focus inside the dialog and inerts background content.\n- Escape closes the dialog unless `no-escape` is set.\n- Backdrop click closes the dialog unless `alert` is set.\n- After close, focus returns to the original trigger.\n\n## Anti-Patterns\n\n- ❌ `<ren-dialog><div role=\"dialog\">…</div></ren-dialog>` — must be a real\n  `<dialog>` inside.\n- ❌ Calling `dialog.show()` when modal behavior is needed — use `.open()` /\n  the native `.showModal()` flow the component provides.\n- ❌ Custom backdrops via `position: fixed; background: rgba(0,0,0,.5)` —\n  use `--ren-dialog-backdrop`.\n- ❌ Skipping `.ren-dialog-title` and not providing `aria-label`.\n\n## Related Files\n\n- `components/composites/ren-dialog/ren-dialog.css`\n- `components/composites/ren-dialog/ren-dialog.js`\n- `docs/components/ren-dialog.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n\n## Test Expectations\n\n- Run component / docs a11y coverage on focus trap, Escape, backdrop click.\n- Run `npm run lint` after token / selector changes.\n- Manually verify reduced-motion: open / close should still work but\n  without animation.\n",
      "path": "components/composites/ren-dialog/component.md",
      "id": "contract:composite:ren-dialog",
      "type": "contract",
      "name": "ren-dialog component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-dropzone Component Contract\n\nFile drop and upload affordance with keyboard fallback.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-dropzone` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-dropzone` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Dropzone composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User must upload files via drag-and-drop OR click-to-browse with a single clear target.\"\n    - \"You want a dashed-border surface that highlights on dragover and lists uploaded files inline.\"\n    - \"Drop must also work for keyboard / pointer users by clicking through to a native <input type=\\\"file\\\">.\"\n    - \"You need a custom event (ren-files-added) carrying File[] instead of wiring the change event yourself.\"\n    - \"Showing accepted types / max size hints next to the icon is part of the flow.\"\n  avoidWhen:\n    - \"The flow is a single hidden file picker triggered by a button — use a plain <input type=\\\"file\\\"> + ren-button label.\"\n    - \"Files come from a remote URL / clipboard image only — dropzone is for OS file drops.\"\n    - \"You need a full upload manager with progress bars, chunked upload, or retries — wrap this dropzone with your own upload UI.\"\n    - \"The drop target is a generic editor surface where the file IS the document body (rich-text editor, canvas).\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-dropzone/ren-dropzone.css\"\n  js:\n    - \"rends/components/composites/ren-dropzone/ren-dropzone.js\"\n  notes:\n    - \"JS is exposed as initDropZone(el) — call it explicitly on each .ren-dropzone, there is no auto-upgrade custom element.\"\n    - \"Without the JS, the CSS still renders a styled file input target, but you lose the dragover state, the dispatched event, and the file array shape.\"\n\nrequiredMarkup:\n  - \"Root is <div class=\\\"ren-dropzone\\\"> (or with .ren-dropzone-compact) and contains a single <input type=\\\"file\\\" class=\\\"ren-dropzone-input\\\"> covering the surface.\"\n  - \"The file input MUST be a real <input type=\\\"file\\\"> with absolute positioning; do not replace it with a custom button — keyboard users rely on the native click.\"\n  - \"Content sits in .ren-dropzone-content with .ren-dropzone-icon, .ren-dropzone-title, .ren-dropzone-description as siblings inside it.\"\n  - \"Uploaded files render as <div class=\\\"ren-dropzone-files\\\"> > .ren-dropzone-file rows with .ren-dropzone-file-name / -size / -remove inside each.\"\n  - \"Disabled state must set aria-disabled=\\\"true\\\" on the wrapper AND disabled on the inner <input> — :has(input:disabled) styles depend on both.\"\n\nforbiddenPatterns:\n  - \"Removing <input type=\\\"file\\\"> and relying on dragover events alone — there is no keyboard fallback without the native input.\"\n  - \"Setting display: none on .ren-dropzone-input — the CSS expects it to overlay the dropzone for click-to-browse.\"\n  - \"Toggling drag highlight via inline styles — let the JS set/unset the [data-dragover] attribute so the state and the icon color react together.\"\n  - \"Custom error tints via hardcoded red — use the [data-error] attribute which switches the border to --color-danger.\"\n  - \"Putting the .ren-dropzone-content above the input in stacking order with pointer-events: auto — content uses pointer-events: none so clicks reach the file input.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-dropzone-bg, --ren-dropzone-border-color, --ren-dropzone-border-style, --ren-dropzone-border-width, --ren-dropzone-hover-bg, --ren-dropzone-hover-border, --ren-dropzone-padding, --ren-dropzone-radius.\"\n    - \"Semantic tokens used by the file list and icon: --color-surface-sunken, --color-surface-raised, --color-border, --color-border-strong, --color-fill, --color-text, --color-text-muted, --color-accent, --color-accent-subtle, --color-danger, --color-focus-ring.\"\n    - \"Layout / motion / type tokens: --space-*, --radius-md, --radius-lg, --radius-sm, --radius-full, --stroke-1, --text-xl, --text-base, --text-sm, --body-size, --caption-size, --weight-medium, --ring-width, --ring-offset-width, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer styles.\"\n    - \"Hardcoded hex / rgb() values for border, dragover background, or remove-button color — theme via the --color-* tokens.\"\n    - \"Custom focus rings on the wrapper that bypass --color-focus-ring / --ring-width / --ring-offset-width.\"\n\naccessibility:\n  required:\n    - \"Dropzone exposes :focus-within outline (--color-focus-ring) sourced from the inner native <input type=\\\"file\\\"> — do not remove the outline.\"\n    - \"Provide a visible label inside .ren-dropzone-title plus a description in .ren-dropzone-description so screen readers see the purpose, not just the icon.\"\n    - \"Disabled state requires aria-disabled=\\\"true\\\" AND the native disabled attribute; pointer-events: none alone leaves the input focusable.\"\n    - \"Remove buttons inside .ren-dropzone-file must include accessible names (aria-label=\\\"Remove <file name>\\\") because the visible glyph alone is decorative.\"\n    - \"Drag visual state ([data-dragover]) must not be the only signal — pair it with text feedback when validation fails ([data-error]).\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-dropzone/ren-dropzone.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-dropzone/ren-dropzone.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div class=\"ren-dropzone\"><input class=\"ren-dropzone-input\" type=\"file\" multiple><div class=\"ren-dropzone-content\"><span class=\"ren-dropzone-icon\" aria-hidden=\"true\">↑</span><strong class=\"ren-dropzone-title\">Upload files</strong><span class=\"ren-dropzone-description\">Drag files here or browse</span></div></div>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-dropzone`\n- `.ren-dropzone-compact`\n- `.ren-dropzone-content`\n- `.ren-dropzone-description`\n- `.ren-dropzone-file`\n- `.ren-dropzone-file-name`\n- `.ren-dropzone-file-remove`\n- `.ren-dropzone-file-size`\n- `.ren-dropzone-files`\n- `.ren-dropzone-icon`\n- `.ren-dropzone-input`\n- `.ren-dropzone-title`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[data-dragover]`\n- `[data-error]`\n- `:disabled`\n- `:hover`\n\n## Public Token API\n\n- `--ren-dropzone-bg`\n- `--ren-dropzone-border-color`\n- `--ren-dropzone-border-style`\n- `--ren-dropzone-border-width`\n- `--ren-dropzone-hover-bg`\n- `--ren-dropzone-hover-border`\n- `--ren-dropzone-padding`\n- `--ren-dropzone-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-dropzone/ren-dropzone.css`\n- `components/composites/ren-dropzone/ren-dropzone.js`\n- `docs/components/ren-dropzone.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-dropzone/component.md",
      "id": "contract:composite:ren-dropzone",
      "type": "contract",
      "name": "ren-dropzone component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-hover-card Component Contract\n\nNon-blocking preview surface shown from hover/focus intent.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-hover-card` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-hover-card` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Hover Card composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"Hover or keyboard-focus over a trigger should reveal a richer-than-tooltip preview (header + body + footer).\"\n    - \"The preview is informational and non-essential — user can ignore it without losing access to the action.\"\n    - \"Show/hide should respect intent: 200ms show delay, 300ms hide grace area so the cursor can move into the card.\"\n    - \"Need anchor positioning via position-area + viewport flip via CSS position-try-fallbacks (no JS positioning).\"\n    - \"Trigger keyboard accessibility via focusin/focusout, not just :hover.\"\n  avoidWhen:\n    - \"The trigger is a primary action and the disclosure is required for the task — use ren-popover (click-driven) or ren-dialog.\"\n    - \"You only need a short text annotation — use ren-tooltip (smaller surface, role=\\\"tooltip\\\").\"\n    - \"The disclosure must be operable on touch devices without a hover state — touch has no hover; use ren-popover.\"\n    - \"Content includes form inputs, focusable controls beyond simple links — ren-popover gives proper dialog semantics.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-hover-card/ren-hover-card.css\"\n  js:\n    - \"rends/components/composites/ren-hover-card/ren-hover-card.js\"\n  notes:\n    - \"JS is required: it adds anchor-name/position-anchor inline styles, wires mouseenter/leave + focusin/out, and toggles the native Popover API.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Place the trigger element immediately before <ren-hover-card>, OR set data-hover-trigger=\\\"<selector>\\\" on the host to point at a remote trigger.\"\n  - \"Inside <ren-hover-card> include a real element with class=\\\"ren-hover-card\\\" (the JS will add popover=\\\"manual\\\" and the .ren-hover-card class if missing).\"\n  - \"Use .ren-hover-card-header / -body / -footer as the three layout slots; headers should contain a real <h3> or <h4> for the accessible heading.\"\n  - \"The trigger receives aria-haspopup=\\\"tooltip\\\" and aria-expanded automatically — do not override these in markup.\"\n  - \"Show/hide delays come from show-delay / hide-delay attributes (milliseconds) on the host; do not throttle via inline scripts.\"\n\nforbiddenPatterns:\n  - \"Using ren-hover-card for click-driven disclosure — there is no click handler, only mouseenter/focusin.\"\n  - \"Putting interactive form inputs inside .ren-hover-card-body — the card closes on mouseleave/focusout, so focusing an input would dismiss it.\"\n  - \"Manually setting popover=\\\"auto\\\" on the inner card — the JS uses popover=\\\"manual\\\" so the open/close lifecycle stays in the component's hands.\"\n  - \"Overriding the ::before arrow with custom shapes — keep the inherited background/border so it auto-themes.\"\n  - \"Hardcoding width via inline styles or non-token values — use .ren-hover-card-sm / -lg or override --color-surface / --shadow-xl through semantic tokens.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component-level positioning anchor token: --ren-hover-card-anchor (and the internal --ren-hover-card-anchor anchor-name).\"\n    - \"Semantic tokens that style the card chrome and content: --color-surface, --color-border, --color-text, --color-text-muted, --color-accent, --color-accent-strong, --shadow-xl, --radius-lg, --radius-md.\"\n    - \"Layout / type / motion tokens: --space-* (0-25, 0-5, 1, 2, 3, 4, 5), --font-size-body, --font-size-body-sm, --font-size-xs, --duration-enter, --ease-enter, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded box-shadow / hex colors on the card surface — pipe through --shadow-xl and --color-surface / --color-border.\"\n    - \"Custom transition durations; reuse --duration-enter / --ease-enter to honour reduced-motion.\"\n\naccessibility:\n  required:\n    - \"Trigger must be a focusable element so focusin/focusout reveal the card via keyboard, not only hover.\"\n    - \"Trigger carries aria-haspopup=\\\"tooltip\\\" and aria-expanded that flips true/false in sync with show()/hide().\"\n    - \"The card uses the native Popover API (popover=\\\"manual\\\") so it inerts other content correctly when shown.\"\n    - \"Reduced motion disables the entrance animation and hides the decorative arrow; do not override the @media (prefers-reduced-motion: reduce) rules.\"\n    - \"On viewports under 640px the card stretches to viewport width — do not lock min-width that breaks this responsive behavior.\"\n    - \"Hover cards must never be the only path to an action; provide a click-driven affordance (ren-popover / ren-link) for touch and AT users.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-hover-card/ren-hover-card.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-hover-card/ren-hover-card.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<button id=\"profile-trigger\" type=\"button\">Jane Doe</button><ren-hover-card data-hover-trigger=\"#profile-trigger\"><div class=\"ren-hover-card\"><div class=\"ren-hover-card-header\"><h3>Jane Doe</h3></div><div class=\"ren-hover-card-body\">Product designer</div></div></ren-hover-card>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-hover-card`\n- `.ren-hover-card-body`\n- `.ren-hover-card-disabled`\n- `.ren-hover-card-footer`\n- `.ren-hover-card-header`\n- `.ren-hover-card-lg`\n- `.ren-hover-card-loading`\n- `.ren-hover-card-sm`\n- `.ren-hover-card-trigger`\n\n## States And Attributes\n\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-hover-card-anchor`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-hover-card/ren-hover-card.css`\n- `components/composites/ren-hover-card/ren-hover-card.js`\n- `docs/components/ren-hover-card.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-hover-card/component.md",
      "id": "contract:composite:ren-hover-card",
      "type": "contract",
      "name": "ren-hover-card component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-menu Component Contract\n\nMenu/listbox-style command surface with keyboard navigation.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-menu` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-menu` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Menu composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"A trigger button needs to open a list of imperative commands (Edit, Duplicate, Delete) with role=\\\"menu\\\".\"\n    - \"You need keyboard navigation (Arrow keys, Home/End, typeahead) plus click-outside / Escape dismissal.\"\n    - \"Items must support menuitem, menuitemcheckbox, menuitemradio, separators, group labels, danger styling, and right-aligned shortcut text.\"\n    - \"Right-click context menus reuse the same chrome via the colocated ren-context-menu module at pointer coordinates.\"\n    - \"Positioning needs viewport-aware placement (bottom-start / bottom-end / top-start / top-end, with right/left also accepted) with native Popover API support.\"\n  avoidWhen:\n    - \"User is selecting a single value to commit into a form field — use ren-select.\"\n    - \"User is filtering a combobox of suggestions — use ren-combobox.\"\n    - \"The disclosure is non-list (rich content, header/body/footer) — use ren-popover or ren-hover-card.\"\n    - \"Items are persistent navigation across the app — use ren-sidebar / ren-nav.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-menu/ren-menu.css\"\n  js:\n    - \"rends/components/composites/ren-menu/ren-menu.js\"\n  notes:\n    - \"JS depends on utils/keyboard-nav.js and utils/dismissable.js; its compatibility export delegates ren-context-menu registration to the colocated context-menu module.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Place a trigger element with data-menu-trigger (or referenced via trigger-id=\\\"<id>\\\" on the host) immediately before <ren-menu>.\"\n  - \"Each interactive item is a real <button class=\\\"ren-menu-item\\\"> with role=\\\"menuitem\\\" (the component sets role=\\\"menuitemcheckbox\\\" / \\\"menuitemradio\\\" when those classes are present).\"\n  - \"Visual separators use <div class=\\\"ren-menu-separator\\\"> and group titles use <div class=\\\"ren-menu-label\\\">; do not use <hr> or <h*> for these slots.\"\n  - \"Disabled items set aria-disabled=\\\"true\\\"; the component skips them in keyboard navigation and selection.\"\n  - \"Set data-value=\\\"…\\\" on items so the dispatched ren-menu-select event carries that value in detail.value (falls back to textContent).\"\n  - \"Use placement=\\\"bottom-start\\\" by default; the host mirrors the resolved side/alignment to data-side=\\\"top|right|bottom|left\\\" and data-align=\\\"start|end\\\".\"\n\nforbiddenPatterns:\n  - \"Using <a href> for menu items that perform JS actions — use <button> and listen to ren-menu-select.\"\n  - \"Replacing the trigger with a div that toggles via inline click — the trigger must receive aria-haspopup=\\\"menu\\\" + aria-controls + aria-expanded from the component.\"\n  - \"Manually positioning the menu with custom left/top — let positionMenu() handle viewport collision (or use placement=\\\"bottom-end\\\" etc).\"\n  - \"Toggling visibility with display: none — the menu relies on [popover] / .ren-open and animates via the [data-closing] state.\"\n  - \"Putting non-menu rich content (forms, multi-line text blocks) directly inside <ren-menu> — wrap such content in ren-popover instead.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-menu-bg, --ren-menu-border, --ren-menu-danger-color, --ren-menu-duration, --ren-menu-easing, --ren-menu-item-height, --ren-menu-item-hover-bg, --ren-menu-item-padding, --ren-menu-item-radius, --ren-menu-padding, --ren-menu-radius, --ren-menu-shadow, --ren-menu-width.\"\n    - \"Semantic tokens used inside items: --color-surface, --color-border, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-danger, --color-danger-subtle, --color-accent.\"\n    - \"Layout / size / motion tokens: --space-* (1, 2, 3), --radius-sm, --radius-lg, --shadow-lg, --size-body, --size-caption, --size-icon-md, --z-dropdown, --duration-enter, --ease-enter, --duration-exit, --ease-exit, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / rgb() colors for hover, active, or danger states — pipe through --color-fill / --color-fill-hover / --color-danger / --color-danger-subtle.\"\n    - \"Custom animation timings; reuse --duration-enter / --duration-exit so reduced-motion handling continues to apply.\"\n\naccessibility:\n  required:\n    - \"<ren-menu> sets role=\\\"menu\\\" on itself and forces role=\\\"menuitem\\\" / \\\"menuitemcheckbox\\\" / \\\"menuitemradio\\\" on its children — do not override.\"\n    - \"Trigger receives aria-haspopup=\\\"menu\\\", aria-controls=\\\"<menu-id>\\\", and aria-expanded that flips with open()/close().\"\n    - \"Keyboard contract: ArrowDown/ArrowUp on the trigger opens the menu; Arrow keys navigate vertically with loop; Home/End jump to first/last; typeahead matches printable characters; Enter/Space selects.\"\n    - \"Checkbox / radio items toggle aria-checked; radio items reset siblings within .ren-menu-radio-group (or the menu root) before activating themselves.\"\n    - \"Dismissable layer closes on Escape and outside click via the shared dismissable util; do not block its event listeners.\"\n    - \"Danger items rely on a paired text label (Delete, Remove) — color alone (--color-danger-subtle) is not sufficient.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-menu/ren-menu.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-menu/ren-menu.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<button id=\"actions-trigger\" type=\"button\" data-menu-trigger>Actions</button>\n<ren-menu trigger-id=\"actions-trigger\" placement=\"bottom-start\"><button class=\"ren-menu-item\" role=\"menuitem\" data-value=\"edit\">Edit</button><button class=\"ren-menu-item\" role=\"menuitem\" data-value=\"duplicate\">Duplicate</button></ren-menu>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-menu`\n- `.ren-menu-checkbox-item`\n- `.ren-menu-group`\n- `.ren-menu-item`\n- `.ren-menu-item-danger`\n- `.ren-menu-item-description`\n- `.ren-menu-item-icon`\n- `.ren-menu-item-shortcut`\n- `.ren-menu-label`\n- `.ren-menu-radio-item`\n- `.ren-menu-separator`\n- `.ren-open`\n\n## States And Attributes\n\n- `[aria-checked]`\n- `[aria-disabled]`\n- `[data-closing]`\n- `[data-align]`\n- `[data-highlighted]`\n- `[data-menu-trigger]`\n- `[data-side]`\n- `placement`\n- `:active`\n- `:hover`\n\n## Public Token API\n\n- `--ren-menu-bg`\n- `--ren-menu-border`\n- `--ren-menu-danger-color`\n- `--ren-menu-duration`\n- `--ren-menu-easing`\n- `--ren-menu-item-height`\n- `--ren-menu-item-hover-bg`\n- `--ren-menu-item-padding`\n- `--ren-menu-item-radius`\n- `--ren-menu-padding`\n- `--ren-menu-radius`\n- `--ren-menu-shadow`\n- `--ren-menu-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-menu/ren-menu.css`\n- `components/composites/ren-menu/ren-menu.js`\n- `docs/components/ren-menu.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-menu/component.md",
      "id": "contract:composite:ren-menu",
      "type": "contract",
      "name": "ren-menu component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-number-field Component Contract\n\nNumeric input composite with increment/decrement affordances.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-number-field` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-number-field` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Number Field composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User needs a discrete numeric value with visible − / + stepper buttons (quantities, counts, ratings out of N).\"\n    - \"Need long-press auto-repeat with acceleration on the stepper buttons (mouse and touch).\"\n    - \"Need keyboard contract: ArrowUp/Down for ±step, Home/End for min/max, clamped to min/max/step bounds.\"\n    - \"Need data-invalid / data-valid states wired to the wrapper (single source of truth for the focus ring).\"\n    - \"Need :has(:focus) to color the whole group, not just the inner input.\"\n  avoidWhen:\n    - \"Free-form numeric typing without bounds — use a plain <input type=\\\"number\\\"> styled via ren-input.\"\n    - \"Continuous value selection with visible track — use ren-slider.\"\n    - \"Numeric value paired with a unit selector (10 px / 1 rem) — compose ren-input with a ren-select.\"\n    - \"Currency / locale-formatted amount with thousands separators — that needs a masked input, not a stepper.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-number-field/ren-number-field.css\"\n  js:\n    - \"rends/components/composites/ren-number-field/ren-number-field.js\"\n  notes:\n    - \"Public Token API has no --ren-number-field-* tokens; theme through semantic input/fill tokens listed below.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Use <ren-number-field min=\\\"…\\\" max=\\\"…\\\" step=\\\"…\\\" value=\\\"…\\\"> as the host so attributes wire min/max/step on the inner input.\"\n  - \"Inner input is <input type=\\\"number\\\" class=\\\"ren-number-field-input\\\"> — keep type=\\\"number\\\" so mobile keyboards switch to digits.\"\n  - \"Decrement / increment must be real type=\\\"button\\\" steppers with class=\\\"ren-number-field-decrement\\\" / class=\\\"ren-number-field-increment\\\" and aria-label=\\\"Decrease\\\" / \\\"Increase\\\" (the component fills these if missing).\"\n  - \"Validation state goes on the wrapper as [data-invalid] or [data-valid]; the inner focus ring color reads from --color-danger / --color-success.\"\n  - \"Size variants are .ren-number-field-sm and .ren-number-field-lg on the wrapper; do not size buttons or input independently.\"\n\nforbiddenPatterns:\n  - \"Wrapping a <span role=\\\"button\\\"> instead of a real <button> for the steppers — long-press auto-repeat hooks depend on native button events.\"\n  - \"Showing the native spinner UI — the CSS already hides ::-webkit-inner-spin-button / ::-webkit-outer-spin-button and uses -moz-appearance: textfield.\"\n  - \"Bypassing the host's clamp() by writing this.value directly — call setValue(), increment(), or decrement() so min/max/step clamping runs and ren-change dispatches.\"\n  - \"Removing aria-label from the stepper buttons — the visible ± glyph alone has no accessible name.\"\n  - \"Custom outline on the inner input — focus styling is owned by the wrapper via :has(.ren-number-field-input:focus).\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic input tokens: --color-input-bg, --color-input-bg-hover, --color-input-border, --color-input-border-focus, --color-input-focus-ring, --color-input-placeholder, --color-disabled-bg, --color-disabled-text.\"\n    - \"Semantic neutral / state tokens: --color-text, --color-border, --color-fill-hover, --color-fill-active, --color-danger, --color-success.\"\n    - \"Layout / type / motion tokens: --space-1, --space-2, --space-3, --space-4, --stroke-1, --radius-sm, --radius-md, --radius-lg, --touch-min, --size-sm, --size-lg, --body-size, --text-sm, --text-lg, --font-mono, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / rgb() values; the current CSS still has two rgb() calls for focus-ring tints — do not propagate that pattern, theme via tokens.\"\n    - \"Inventing --ren-number-field-* custom properties not present in the source until they ship in the Public Token API.\"\n\naccessibility:\n  required:\n    - \"Stepper buttons keep min-width / height = var(--touch-min) (44px) — do not shrink below this except inside .ren-number-field-sm for non-touch contexts.\"\n    - \"Each stepper has an aria-label (\\\"Decrease\\\" / \\\"Increase\\\"); the icon is decorative content.\"\n    - \"ArrowUp / ArrowDown on the input dispatch increment()/decrement(); Home / End jump to min / max; values are always clamped before dispatching ren-change.\"\n    - \"Disabled state sets disabled on the inner input AND both buttons so they are removed from the tab order — do not rely on opacity alone.\"\n    - \"data-invalid / data-valid changes border AND focus ring color, but always pair with text feedback near the field — color is not the only signal.\"\n    - \"Long-press auto-repeat must stop on pointerup/touchend AND mouseleave so dragging off the button cancels acceleration.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-number-field/ren-number-field.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-number-field/ren-number-field.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-number-field min=\"0\" max=\"10\" step=\"1\" value=\"2\"><button class=\"ren-number-field-decrement\" type=\"button\" aria-label=\"Decrease\">−</button><input class=\"ren-number-field-input\" type=\"number\" aria-label=\"Quantity\"><button class=\"ren-number-field-increment\" type=\"button\" aria-label=\"Increase\">+</button></ren-number-field>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-number-field`\n- `.ren-number-field-decrement`\n- `.ren-number-field-increment`\n- `.ren-number-field-input`\n- `.ren-number-field-lg`\n- `.ren-number-field-sm`\n\n## States And Attributes\n\n- `[data-invalid]`\n- `[data-valid]`\n- `:active`\n- `:disabled`\n- `:hover`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-number-field/ren-number-field.css`\n- `components/composites/ren-number-field/ren-number-field.js`\n- `docs/components/ren-number-field.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-number-field/component.md",
      "id": "contract:composite:ren-number-field",
      "type": "contract",
      "name": "ren-number-field component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-otp Component Contract\n\nOne-time-passcode segmented input composite.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-otp` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-otp` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Otp composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User must enter a short fixed-length code split across N single-character slots (SMS OTP, 2FA, email PIN).\"\n    - \"Need auto-advance to the next slot, Backspace to previous slot, Arrow keys to navigate, Home/End to jump to first/last.\"\n    - \"Need clipboard paste to distribute the pasted string across consecutive slots and focus the next empty one.\"\n    - \"Need numeric vs alphanumeric filtering (inputmode=\\\"numeric\\\" or \\\"text\\\") and a per-slot data-filled state.\"\n    - \"Need explicit data-valid / data-invalid states on the wrapper plus a ren-complete event when all slots fill.\"\n  avoidWhen:\n    - \"User pastes a long password / token in one go — use a single <input type=\\\"password\\\"> with autocomplete=\\\"one-time-code\\\" if it is a single field.\"\n    - \"Free-form numeric value with unit / decimals — use ren-number-field.\"\n    - \"Code length is variable or unknown — use a single text input.\"\n    - \"Code is alphanumeric AND case-sensitive in a way the slot UI cannot make obvious — fall back to a single masked input.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-otp/ren-otp.css\"\n  js:\n    - \"rends/components/composites/ren-otp/ren-otp.js\"\n  notes:\n    - \"Public Token API has no --ren-otp-* tokens; theme through the semantic input tokens and size scale listed below.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Use <ren-otp length=\\\"6\\\" type=\\\"numeric\\\"> as the host element — slots are generated by JS, do not author them by hand.\"\n  - \"If you must author slots, each must be a real <input class=\\\"ren-otp-slot\\\" type=\\\"text\\\" maxlength=\\\"1\\\" inputmode=\\\"numeric|text\\\" aria-label=\\\"Code digit N of M\\\" data-index=\\\"N\\\">.\"\n  - \"Optional separators are <div class=\\\"ren-otp-separator\\\"> placed between slot groups; they are pointer-events: none and decorative.\"\n  - \"Size variants .ren-otp-sm and .ren-otp-lg go on the wrapper; do not size individual .ren-otp-slot.\"\n  - \"Validation state goes on the wrapper as [data-valid] or [data-invalid]; the per-slot [data-filled] is set automatically by the component.\"\n\nforbiddenPatterns:\n  - \"Using a single <input maxlength=\\\"6\\\"> styled to look segmented — keyboard caret control and paste-distribute won't work.\"\n  - \"Removing maxlength=\\\"1\\\" / inputmode — without them mobile keyboards switch back to letters and the auto-advance breaks.\"\n  - \"Manually wiring focus on slot blur / change — let handleSlotInput / handleSlotKeydown manage focus transitions to keep Backspace and paste flows correct.\"\n  - \"Hardcoding the slot size — use --touch-min for the default, --size-sm via .ren-otp-sm, --size-lg via .ren-otp-lg.\"\n  - \"Mixing data-valid and data-invalid on the wrapper simultaneously — setValid() removes the opposite attribute by design.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic input tokens: --color-input-bg, --color-input-bg-hover, --color-input-border, --color-input-border-focus, --color-input-focus-ring, --color-input-placeholder, --color-disabled-bg, --color-disabled-text.\"\n    - \"Semantic state / text tokens: --color-text, --color-text-muted, --color-border, --color-fill, --color-accent, --color-danger, --color-success.\"\n    - \"Layout / type / motion tokens: --space-0-5, --space-1, --space-2, --space-3, --stroke-1, --radius-sm, --radius-md, --radius-lg, --touch-min, --size-sm, --size-lg, --text-sm, --text-lg, --body-size, --font-mono, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / rgb() colors for focus rings or borders; the current CSS still uses rgb() for the danger / success ring tints — do not replicate, prefer tokens.\"\n    - \"Inventing --ren-otp-* custom properties not present in the source until they ship in the Public Token API.\"\n\naccessibility:\n  required:\n    - \"Slot dimensions default to var(--touch-min) (44px) for thumb accuracy; --size-sm only for non-touch surfaces.\"\n    - \"Each slot exposes aria-label=\\\"Code digit N of M\\\" so screen readers announce position; do not strip these labels.\"\n    - \"Backspace, ArrowLeft/Right, Home, End move focus across slots — keep the keydown handler intact when extending the component.\"\n    - \"Paste handler must run on every slot so users can paste from any slot, not only the first.\"\n    - \"data-valid and data-invalid are paired with text feedback elsewhere on the form (do not communicate validation through color alone).\"\n    - \":focus-visible draws --color-accent at 2px outline; do not remove it without restoring an equivalent ring.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-otp/ren-otp.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-otp/ren-otp.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-otp length=\"6\" type=\"numeric\" aria-label=\"Verification code\"></ren-otp>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-otp`\n- `.ren-otp-lg`\n- `.ren-otp-separator`\n- `.ren-otp-slot`\n- `.ren-otp-sm`\n\n## States And Attributes\n\n- `[data-filled]`\n- `[data-invalid]`\n- `[data-valid]`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-otp/ren-otp.css`\n- `components/composites/ren-otp/ren-otp.js`\n- `docs/components/ren-otp.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-otp/component.md",
      "id": "contract:composite:ren-otp",
      "type": "contract",
      "name": "ren-otp component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-popover Component Contract\n\nNon-modal floating surface for contextual controls or content.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-popover` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-popover` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Popover composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"Click on a trigger should reveal a small contextual surface (filters, mini-form, info card) anchored to that trigger.\"\n    - \"You need anchor-positioned placement (top/right/bottom/left) via position-area with automatic viewport flip via position-try-fallbacks.\"\n    - \"Need click-outside + Escape dismissal and a visible arrow caret that re-rotates per [data-side].\"\n    - \"Need progressive enhancement: anchor positioning in modern browsers, JS computePosition() fallback elsewhere.\"\n    - \"Need header / body / footer slots inside the popover surface, with native role=\\\"dialog\\\" + aria-modal=\\\"false\\\".\"\n  avoidWhen:\n    - \"The disclosure blocks the page and demands the user's attention — use ren-dialog (modal).\"\n    - \"Disclosure is reveal-on-hover-intent for non-essential preview content — use ren-hover-card.\"\n    - \"Disclosure is a short text annotation only — use ren-tooltip.\"\n    - \"Disclosure is a list of commands (menu) — use ren-menu / ren-context-menu.\"\n    - \"Disclosure is a side sheet — use ren-sheet.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-popover/ren-popover.css\"\n  js:\n    - \"rends/components/composites/ren-popover/ren-popover.js\"\n  notes:\n    - \"JS attaches click + Escape + outside-click handlers and computes JS positioning only when CSS anchor positioning is unsupported.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Use <ren-popover placement=\\\"bottom\\\" offset=\\\"8\\\"> as the host so it adds .ren-popover, role=\\\"dialog\\\", and popover=\\\"manual\\\" automatically.\"\n  - \"The trigger is referenced via trigger-id=\\\"<id>\\\", or by a child / previous-sibling with [data-popover-trigger]; the component picks them in that order.\"\n  - \"Inside the popover, group content into .ren-popover-header / .ren-popover-body / .ren-popover-footer; a .ren-popover-arrow div is injected if missing.\"\n  - \"Triggers should be real interactive elements (<button> or <a>) so click + keyboard activation work; the component does not click-style arbitrary nodes.\"\n  - \"When using multiple popovers in the same view, set unique trigger-id values so the click handlers do not cross-wire.\"\n\nforbiddenPatterns:\n  - \"Manually wiring trigger.addEventListener('click', popover.show) — the host already attaches its click + dismissable listeners.\"\n  - \"Calling showPopover() directly while bypassing open() — aria-modal, data-state, and ren-open event won't fire.\"\n  - \"Hardcoding inline left/top values — let CSS position-anchor + position-area + position-try-fallbacks handle placement and flips (or the JS computePosition fallback).\"\n  - \"Removing the .ren-popover-arrow element — the [data-side] rules rotate it per side; without it the visual anchor cue disappears.\"\n  - \"Using ren-popover purely as a click-bound tooltip — accessibility expectations differ; use ren-tooltip for short, non-interactive text.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-popover-bg, --ren-popover-border-color, --ren-popover-duration, --ren-popover-easing, --ren-popover-padding, --ren-popover-radius, --ren-popover-shadow, --ren-popover-width.\"\n    - \"Semantic tokens used inside content: --color-surface, --color-border, --color-text-primary, --color-text-secondary, --shadow-lg, --radius-lg.\"\n    - \"Layout / type / motion tokens: --space-2, --space-3, --space-4, --size-body-sm, --z-popover, --duration-enter, --ease-enter.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded backdrop / surface colors via inline styles or rgba().\"\n    - \"Custom animation timings; reuse --duration-enter / --ease-enter (or --ren-popover-duration / --ren-popover-easing) so reduced-motion overrides apply.\"\n\naccessibility:\n  required:\n    - \"<ren-popover> sets role=\\\"dialog\\\" with aria-modal=\\\"false\\\" (non-modal by design). Do not set aria-modal=\\\"true\\\" or apply focus trapping.\"\n    - \"Trigger must be a focusable, real interactive element; click + Enter/Space activation come from native button semantics.\"\n    - \"Escape closes the popover via the document keydown handler; outside-click closes via the document click handler — do not stopPropagation on container click outside these targets.\"\n    - \"When CSS anchor positioning is unsupported the host applies left/top to the popover element; do not overlay your own positioning style or the flip logic breaks.\"\n    - \"Reduced motion strips the transform animation (opacity-only); keep the @media (prefers-reduced-motion: reduce) block intact.\"\n    - \"Avoid putting form submit buttons that affect the page outside the popover surface — keep the interaction self-contained within the dialog role.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-popover/ren-popover.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-popover/ren-popover.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<button id=\"account-trigger\" type=\"button\" data-popover-trigger>Account</button>\n<ren-popover trigger-id=\"account-trigger\" placement=\"bottom\">\n  <div class=\"ren-popover-header\"><strong>Account</strong></div>\n  <div class=\"ren-popover-body\">Signed in as Jane</div>\n</ren-popover>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-open`\n- `.ren-popover`\n- `.ren-popover-arrow`\n- `.ren-popover-body`\n- `.ren-popover-footer`\n- `.ren-popover-header`\n- `.ren-popover-trigger`\n\n## States And Attributes\n\n- `[data-popover-trigger]`\n- `[data-side]`\n\n## Public Token API\n\n- `--ren-popover-bg`\n- `--ren-popover-border-color`\n- `--ren-popover-duration`\n- `--ren-popover-easing`\n- `--ren-popover-padding`\n- `--ren-popover-radius`\n- `--ren-popover-shadow`\n- `--ren-popover-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-popover/ren-popover.css`\n- `components/composites/ren-popover/ren-popover.js`\n- `docs/components/ren-popover.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-popover/component.md",
      "id": "contract:composite:ren-popover",
      "type": "contract",
      "name": "ren-popover component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-scroll-area Component Contract\n\nScrollable region styling that preserves native scrolling.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-scroll-area` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-scroll-area` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Scroll Area composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"A bounded region needs themed native scrollbars (Firefox scrollbar-color + WebKit ::-webkit-scrollbar).\"\n    - \"You want consistent scrollbar styling across light/dark themes without injecting JS or shadow DOM.\"\n    - \"Need preset max-height utilities (.ren-scroll-area-sm/-md/-lg/-xl/-full) and direction variants (.-x, .-y).\"\n    - \"Need fade-edge masks (.-fade, .-fade-x) to hint at overflow without resorting to JS observers.\"\n    - \"Need an \\\"auto\\\" variant where the scrollbar only appears on hover / focus-within.\"\n  avoidWhen:\n    - \"You need virtualization, restore-scroll-position, or scroll-into-view APIs — those need bespoke JS, not this CSS contract.\"\n    - \"You need a custom-drawn track / thumb (e.g. mini-map, position indicator) — native scrollbar styling cannot do this.\"\n    - \"The container should never scroll (clip overflow) — use overflow: clip / hidden directly.\"\n    - \"The content scrolls horizontally only and needs snap behavior — use a dedicated carousel/scroll-snap pattern.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-scroll-area/ren-scroll-area.css\"\n  notes:\n    - \"CSS-only component: no JavaScript file is colocated. Do not introduce JS unless the source component grows that responsibility.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Apply class=\\\"ren-scroll-area\\\" to a real block element that owns overflow (commonly <div>, <section>, <main>).\"\n  - \"Constrain height/width explicitly — either via .ren-scroll-area-sm/-md/-lg/-xl/-full, an inline --scroll-max custom property, or a parent flex/grid track.\"\n  - \"Pair direction with intent: .-x for horizontal-only, .-y for vertical-only; omit for auto in both axes.\"\n  - \"When using .-fade or .-fade-x the masking ::before/::after read from --color-surface; ensure the scroll area sits on a surface that matches that token.\"\n  - \"Children that should be focusable for keyboard scrolling need tabindex=\\\"0\\\" on the .ren-scroll-area itself when it has no focusable descendants.\"\n\nforbiddenPatterns:\n  - \"Overriding overflow with overflow: hidden — that defeats the entire component (no scrollbar, no fade mask alignment).\"\n  - \"Wrapping a <ren-scroll-area> custom element instead of the .ren-scroll-area class — no custom element exists for this composite.\"\n  - \"Stacking position: absolute children that escape the scroll context — the fade overlays (::before/::after at z-index: 10) assume in-flow content underneath.\"\n  - \"Hardcoding scrollbar-color or ::-webkit-scrollbar-thumb hex values — theme via --color-fill-active / --color-fill-hover so dark mode adapts.\"\n  - \"Setting --scroll-max on the parent expecting cascade — the rule is .ren-scroll-area[--scroll-max] { max-height: var(--scroll-max) } so the variable must be authored on the same element.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic tokens used by the scrollbar and fade masks: --color-fill-active, --color-fill-hover, --color-surface.\"\n    - \"Layout / shape / motion tokens: --radius-full, --duration-enter, --ease-enter, --transition-tactile.\"\n    - \"Authoring custom max-height via the --scroll-max custom property on the .ren-scroll-area element.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / rgb() values for scrollbar thumb or fade-mask gradients.\"\n    - \"Inventing --ren-scroll-area-* tokens — none are part of the Public Token API today.\"\n\naccessibility:\n  required:\n    - \"If the scrollable region contains no focusable child, set tabindex=\\\"0\\\" on the .ren-scroll-area so keyboard users can scroll it with Arrow / PageUp / PageDown.\"\n    - \"Provide an accessible name (aria-label / aria-labelledby) when the region is a discrete content landmark; e.g. <section class=\\\"ren-scroll-area\\\" aria-label=\\\"Logs\\\">.\"\n    - \"Honor prefers-reduced-motion: the component disables scroll-behavior: smooth via the existing @media rule — do not re-enable smooth scrolling under reduced-motion.\"\n    - \"Do not rely on the fade-edge gradients alone to signal more content; pair with a visible affordance (chevron, scroll-hint) when content overflow is decision-critical.\"\n    - \"Scrollbar contrast: --color-fill-active and --color-fill-hover must remain distinguishable from --color-surface in both themes; do not theme so the thumb disappears.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-scroll-area/ren-scroll-area.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<section class=\"ren-scroll-area ren-scroll-area-md\" tabindex=\"0\" aria-label=\"Activity log\"><p>Activity entry</p></section>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-scroll-area`\n- `.ren-scroll-area-full`\n- `.ren-scroll-area-lg`\n- `.ren-scroll-area-md`\n- `.ren-scroll-area-sm`\n- `.ren-scroll-area-xl`\n\n## States And Attributes\n\n- `:hover`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/composites/ren-scroll-area/ren-scroll-area.css`\n- `docs/components/ren-scroll-area.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-scroll-area/component.md",
      "id": "contract:composite:ren-scroll-area",
      "type": "contract",
      "name": "ren-scroll-area component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-select Component Contract\n\nCustom select/listbox composite with trigger, options, and keyboard navigation.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-select` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-select` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Select composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a styled single- or multi-select with a button trigger and a listbox popup.\"\n    - \"You need full keyboard navigation (Arrow keys, Home/End, typeahead, Enter/Space, Escape).\"\n    - \"You need ARIA combobox/listbox semantics with aria-expanded, aria-selected, and data-highlighted.\"\n    - \"You need optional grouping (.ren-select-group + .ren-select-label) or separators between options.\"\n    - \"You need multi-select chips (.ren-select-chips, .ren-select-chip) with removal affordances.\"\n    - \"You need hidden form-input integration for submission alongside custom dropdown UI.\"\n    - \"The dropdown needs a preferred placement via placement=\\\"top|right|bottom|left\\\" while preserving viewport flip fallback.\"\n  avoidWhen:\n    - \"A bare <select> styled via base/primitive-zero would meet the requirements.\"\n    - \"The control is binary state — use ren-checkbox, ren-switch, or ren-toggle.\"\n    - \"The disclosure is a free-form menu of commands, not value selection — use ren-menu / ren-menubar.\"\n    - \"The disclosure is a navigation list — use ren-nav or ren-sidebar.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-select/ren-select.css\"\n  js:\n    - \"rends/components/composites/ren-select/ren-select.js\"\n  notes:\n    - \"JS is required: it owns keyboard nav, dismissable popup, popover positioning, and the hidden form input.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"<ren-select> wraps a real <button data-select-trigger> and a [data-select-content] listbox container.\"\n  - \"Each option is a <div data-select-item data-value=\\\"...\\\"> inside .ren-select-content; rely on data-select-item, not arbitrary children.\"\n  - \"The trigger displays .ren-select-value when something is chosen and .ren-select-placeholder when empty — do not collapse them into one node.\"\n  - \"Set name on <ren-select> when the value must submit with a form; the component injects the hidden input automatically.\"\n  - \"With multiple, value is an ordered array and the component injects one same-name hidden input per selected value so FormData preserves repeated entries. Removable chips render as a sibling of the button trigger so interactive controls are never nested.\"\n  - \"Use .ren-select-group + .ren-select-label for grouped options and .ren-select-separator between groups; do not invent dividers.\"\n  - \"Use placement=\\\"bottom\\\" by default; the host and .ren-select-content mirror the resolved side to data-side and data-align.\"\n\nforbiddenPatterns:\n  - \"Substituting a <div role=\\\"button\\\"> for the [data-select-trigger] <button> — the trigger must be a real button.\"\n  - \"Animating the dropdown manually with display: none / display: block — use the .ren-open class and the popover open state.\"\n  - \"Removing the focus ring on .ren-select-trigger:focus-visible without restoring an equivalent visible ring.\"\n  - \"Hardcoding option backgrounds via inline style — use --color-accent-subtle / --color-fill via the documented selectors.\"\n  - \"Putting interactive children (links, buttons) inside .ren-select-item; items must remain single-action options.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic input tokens: --color-input-bg, --color-input-bg-hover, --color-input-border, --color-input-border-focus, --color-input-focus-ring, --color-input-placeholder, --color-input-disabled-bg, --color-input-disabled-text.\"\n    - \"Surface and content tokens: --color-surface, --color-fill, --color-accent, --color-accent-subtle, --color-text, --color-text-muted, --color-border.\"\n    - \"Shape / motion tokens: --radius-sm, --radius-md, --radius-lg, --space-*, --shadow-lg, --duration-enter, --duration-exit, --duration-micro, --ease-enter, --ease-exit, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named color values in overrides.\"\n    - \"Hardcoded transition durations; use --duration-enter / --duration-exit / --duration-micro with their paired easings.\"\n\naccessibility:\n  required:\n    - \"Trigger exposes aria-expanded on open/close and aria-disabled when inert; native <button> Enter/Space activation must be preserved.\"\n    - \"Listbox container carries role=\\\"listbox\\\"; each option carries role=\\\"option\\\" and aria-selected reflects the selected value.\"\n    - \"Roving highlight on options uses [data-highlighted]; only one option may be highlighted at a time.\"\n    - \"Trigger meets the 44px touch target via min-height: var(--touch-min); .ren-select-sm is only acceptable in non-touch contexts.\"\n    - \"Escape closes the listbox and returns focus to the trigger; outside click dismisses without stealing focus elsewhere.\"\n    - \"Disabled state combines :disabled (or aria-disabled=\\\"true\\\") with pointer-events: none on items so click and keypress both no-op.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-select/ren-select.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-select/ren-select.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-select name=\"country\"><button type=\"button\" data-select-trigger><span class=\"ren-select-value\"></span><span class=\"ren-select-placeholder\">Choose a country</span></button><div class=\"ren-select-content\" data-select-content role=\"listbox\"><div data-select-item data-value=\"ar\">Argentina</div><div data-select-item data-value=\"uy\">Uruguay</div></div></ren-select>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-open`\n- `.ren-select`\n- `.ren-select-chip`\n- `.ren-select-chip-remove`\n- `.ren-select-chips`\n- `.ren-select-content`\n- `.ren-select-empty`\n- `.ren-select-group`\n- `.ren-select-icon`\n- `.ren-select-item`\n- `.ren-select-label`\n- `.ren-select-lg`\n- `.ren-select-placeholder`\n- `.ren-select-separator`\n- `.ren-select-sm`\n- `.ren-select-trigger`\n- `.ren-select-value`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[aria-expanded]`\n- `[aria-selected]`\n- `[data-highlighted]`\n- `[data-align]`\n- `[data-flipped]`\n- `[data-select-content]`\n- `[data-select-item]`\n- `[data-select-trigger]`\n- `[data-side]`\n- `placement`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## JavaScript API\n\n- `value` / `setValue(value)` use a string (or `null`) in single-select mode.\n- `value` / `setValue(values)` use an ordered array in `multiple` mode.\n- Multiple selection accumulates and removes values without closing the\n  listbox, renders `.ren-select-chip` entries, and submits repeated same-name\n  hidden inputs.\n- `aria-disabled=\"false\"` remains operable; only native `disabled` or\n  `aria-disabled=\"true\"` disables an option.\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-select/ren-select.css`\n- `components/composites/ren-select/ren-select.js`\n- `docs/components/ren-select.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-select/component.md",
      "id": "contract:composite:ren-select",
      "type": "contract",
      "name": "ren-select component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-sheet Component Contract\n\nEdge-attached dialog/surface for secondary workflows.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-sheet` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-sheet` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Sheet composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The disclosure is an edge-anchored panel (right / left / top / bottom) over the main app.\"\n    - \"You need the native <dialog> behavior — modal focus trap, ::backdrop, Escape to dismiss — on a side surface.\"\n    - \"You need a slide-in transition controlled by [data-side] with @starting-style fallback.\"\n    - \"You need swipe-to-dismiss on touch and outside-click / Escape dismissal.\"\n    - \"You need a mobile bottom sheet with a drag handle (.ren-sheet-handle).\"\n  avoidWhen:\n    - \"The disclosure should be centered and block the page — use ren-dialog.\"\n    - \"The panel is persistent app-shell navigation — use ren-sidebar.\"\n    - \"The disclosure is a small anchored popup — use ren-popover, ren-tooltip, or ren-menu.\"\n    - \"The notification is transient and must not steal focus — use ren-toast.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-sheet/ren-sheet.css\"\n  js:\n    - \"rends/components/composites/ren-sheet/ren-sheet.js\"\n  notes:\n    - \"JS is required: it wraps the consumer's children in a real <dialog class=\\\"ren-sheet\\\"> and wires triggers, swipe, and lifecycle events.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"<ren-sheet id=\\\"...\\\" side=\\\"right|left|top|bottom\\\"> is the host; do not replace the inner <dialog> the component generates with a <div>.\"\n  - \"Header / body / footer slots use .ren-sheet-header, .ren-sheet-body, .ren-sheet-footer in that order so the body grows and the footer pins to the bottom.\"\n  - \"Provide .ren-sheet-title as a real heading; it supplies the accessible name when no aria-label is set on <ren-sheet>.\"\n  - \"Close affordances use [data-sheet-close]; triggers elsewhere on the page use [data-sheet-trigger=\\\"<id>\\\"].\"\n  - \"Use size variants via the .-sm / .-md / .-lg / .-xl / .-full classes — do not inline width on the host.\"\n\nforbiddenPatterns:\n  - \"<ren-sheet> rendered as a <div role=\\\"dialog\\\"> — the component must use the real <dialog> it generates.\"\n  - \"Calling .show() / .close() on the inner <dialog> directly; always use the <ren-sheet> .show() / .close() methods.\"\n  - \"Hardcoded backdrop colors (rgba(0,0,0,...)); use --color-overlay via the component's ::backdrop rule.\"\n  - \"Animating slide-in with custom @keyframes; use the documented [data-side] + @starting-style + --duration-enter / --ease-enter pipeline.\"\n  - \"Toggling visibility via display: none / display: block — the [open] attribute drives the @starting-style translate.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-sheet-backdrop, --ren-sheet-bg, --ren-sheet-border, --ren-sheet-duration, --ren-sheet-easing, --ren-sheet-padding, --ren-sheet-radius, --ren-sheet-shadow, --ren-sheet-width.\"\n    - \"Semantic surface tokens: --color-surface, --color-text, --color-text-muted, --color-border, --color-overlay, --color-fill, --color-fill-active, --color-fill-hover.\"\n    - \"Spacing / motion tokens: --space-*, --radius-full, --duration-enter, --duration-exit, --ease-enter, --ease-exit, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named color values in overrides — even for the box-shadow on the leading edge (use semantic tokens or override --ren-sheet-shadow).\"\n    - \"Hardcoded transition durations; route through --duration-enter / --duration-exit and their paired easings.\"\n\naccessibility:\n  required:\n    - \"Modality is provided by the inner real <dialog> (focus trap + inert background); do not break the trap with manual focus moves.\"\n    - \".ren-sheet-title supplies the accessible label; if omitted, set aria-label on <ren-sheet>.\"\n    - \"Escape closes the sheet (unless dismissible=\\\"false\\\"); the close button [data-sheet-close] has a visible aria-label.\"\n    - \"Restore focus to the original trigger element after close (handled by the component via the returnFocus reference).\"\n    - \"Swipe-to-dismiss must coexist with keyboard dismissal; do not override touch-action on the dialog.\"\n    - \"Respect prefers-reduced-motion: the slide-in collapses to a plain fade automatically — do not reintroduce custom animations there.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-sheet/ren-sheet.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-sheet/ren-sheet.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<button type=\"button\" data-sheet-trigger=\"filters\">Open filters</button><ren-sheet id=\"filters\" side=\"right\"><header class=\"ren-sheet-header\"><h2 class=\"ren-sheet-title\">Filters</h2><button type=\"button\" data-sheet-close aria-label=\"Close\">×</button></header><div class=\"ren-sheet-body\">Filter controls</div><footer class=\"ren-sheet-footer\"><button type=\"button\" data-sheet-close=\"apply\">Apply</button></footer></ren-sheet>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-sheet`\n- `.ren-sheet-body`\n- `.ren-sheet-close`\n- `.ren-sheet-description`\n- `.ren-sheet-footer`\n- `.ren-sheet-handle`\n- `.ren-sheet-header`\n- `.ren-sheet-title`\n\n## States And Attributes\n\n- `[data-sheet-close]`\n- `[data-sheet-trigger]`\n- `[data-side]`\n- `:active`\n- `:hover`\n\n`close(returnValue)` and native `<form method=\"dialog\">` closure emit exactly\none `ren-close` with `detail.returnValue`, while clearing host `open` and\nrestoring focus to the opener.\n\n## Public Token API\n\n- `--ren-sheet-backdrop`\n- `--ren-sheet-bg`\n- `--ren-sheet-border`\n- `--ren-sheet-duration`\n- `--ren-sheet-easing`\n- `--ren-sheet-padding`\n- `--ren-sheet-radius`\n- `--ren-sheet-shadow`\n- `--ren-sheet-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-sheet/ren-sheet.css`\n- `components/composites/ren-sheet/ren-sheet.js`\n- `docs/components/ren-sheet.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-sheet/component.md",
      "id": "contract:composite:ren-sheet",
      "type": "contract",
      "name": "ren-sheet component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-slider Component Contract\n\nRange input composite for numeric adjustment.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-slider` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-slider` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Slider composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User needs to pick a continuous numeric value within a known min/max range (volume, opacity, brightness, percentage).\"\n    - \"You need a single-thumb slider or a dual-thumb range slider (.ren-slider-range) with two inputs.\"\n    - \"You need a visible value readout (.ren-slider-value via show-value attribute) and/or tick marks (.ren-slider-marks).\"\n    - \"You need semantic color variants for status: .ren-slider-success / .ren-slider-warning / .ren-slider-danger.\"\n    - \"You need a size variant (.ren-slider-sm / .ren-slider-lg) or a vertical orientation (.ren-slider-vertical).\"\n    - \"Native <input type=\\\"range\\\"> ARIA and keyboard support is sufficient (Arrow keys, Home/End, PageUp/PageDown).\"\n  avoidWhen:\n    - \"The choice is from a discrete enumerated list — use ren-select, ren-radio-group, or ren-toggle-group.\"\n    - \"The value is text, number-typed, or formatted (currency, date) — use ren-field with a typed input.\"\n    - \"The control toggles binary state — use ren-switch, ren-checkbox, or ren-toggle.\"\n    - \"You need rating stars — use ren-rating.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-slider/ren-slider.css\"\n  js:\n    - \"rends/components/composites/ren-slider/ren-slider.js\"\n  notes:\n    - \"JS is required to compute the --value percentage CSS variable that paints the track fill; without it the gradient stays at 0%.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"<ren-slider> wraps a real <input type=\\\"range\\\"> with min / max / step / value set as attributes — never style a <div> as a slider.\"\n  - \"Pair the input with a real <label for=\\\"...\\\"> or set aria-label / aria-labelledby on the input for an accessible name.\"\n  - \"Use show-value attribute on <ren-slider> to opt into the .ren-slider-value readout; use label attribute to inject a .ren-slider-label.\"\n  - \"Dual-thumb range mode wraps two <input type=\\\"range\\\"> elements inside .ren-slider-range with a .ren-slider-track-input visual rail.\"\n  - \"In dual-thumb mode the value property and ren-slider-input / ren-slider-change detail.value are a two-number tuple in input order.\"\n  - \"Tick marks use .ren-slider-marks > .ren-slider-mark > (.ren-slider-mark-tick + .ren-slider-mark-label); preserve that structure.\"\n\nforbiddenPatterns:\n  - \"Replacing <input type=\\\"range\\\"> with a <div> + JS drag — accessibility and form submission depend on the native input.\"\n  - \"Setting the track fill via inline background gradients; the JS updates --value and the CSS handles the gradient automatically.\"\n  - \"Hardcoding thumb colors (background: #fff / blue) on ::-webkit-slider-thumb or ::-moz-range-thumb — use --color-accent or the .ren-slider-success/warning/danger variants.\"\n  - \"Removing the focus-visible outline on ::-webkit-slider-thumb / ::-moz-range-thumb without restoring an equivalent visible focus indicator.\"\n  - \"Stripping aria-valuemin / aria-valuemax / aria-valuenow (native input provides these — do not block them via role overrides).\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-slider-duration, --ren-slider-easing, --ren-slider-fill-color, --ren-slider-thumb-bg, --ren-slider-thumb-border, --ren-slider-thumb-shadow, --ren-slider-thumb-size, --ren-slider-track-bg, --ren-slider-track-height, --ren-slider-track-radius.\"\n    - \"Semantic state tokens: --color-accent, --color-success, --color-warning, --color-danger, --color-fill-active, --color-fill-subtle, --color-text, --color-text-muted.\"\n    - \"Shape / motion tokens: --radius-full, --space-*, --shadow-sm, --transition-tactile, font-size tokens (--font-size-label, --font-size-body, --font-size-xs).\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named color values in overrides — including the white thumb border (keep it as a token override if you need to change it).\"\n    - \"Hardcoded transition durations on the thumb hover/active scale; route through --transition-tactile.\"\n\naccessibility:\n  required:\n    - \"Use a real <input type=\\\"range\\\"> so native ARIA (role=slider, aria-valuemin/max/now) and keyboard (Arrow, Home/End, PageUp/Down) work.\"\n    - \"Visible focus ring on the thumb via outline: 2px solid var(--color-accent) with outline-offset; never set outline: none without an equivalent.\"\n    - \"Provide an accessible name via <label for> or aria-label; the .ren-slider-label class alone does not associate the label.\"\n    - \"Color variants must not be the only signal of state — accompany .ren-slider-danger with text or an icon for non-color cues.\"\n    - \"Disabled state uses :disabled on the input plus cursor: not-allowed; opacity changes alone are not sufficient on their own for state.\"\n    - \"Touch / drag interactions must not block keyboard nudges — both must drive the same change/input events the component dispatches.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-slider/ren-slider.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-slider/ren-slider.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<label for=\"volume\">Volume</label><ren-slider show-value><div class=\"ren-slider-track\"><input id=\"volume\" type=\"range\" min=\"0\" max=\"100\" step=\"1\" value=\"50\"></div></ren-slider>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-slider`\n- `.ren-slider-danger`\n- `.ren-slider-label`\n- `.ren-slider-lg`\n- `.ren-slider-mark`\n- `.ren-slider-mark-label`\n- `.ren-slider-mark-tick`\n- `.ren-slider-marks`\n- `.ren-slider-range`\n- `.ren-slider-sm`\n- `.ren-slider-success`\n- `.ren-slider-track`\n- `.ren-slider-track-input`\n- `.ren-slider-value`\n- `.ren-slider-vertical`\n- `.ren-slider-warning`\n\n## States And Attributes\n\n- `:active`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n- `type=\"range\"`\n\n## Public Token API\n\n- `--ren-slider-duration`\n- `--ren-slider-easing`\n- `--ren-slider-fill-color`\n- `--ren-slider-thumb-bg`\n- `--ren-slider-thumb-border`\n- `--ren-slider-thumb-shadow`\n- `--ren-slider-thumb-size`\n- `--ren-slider-track-bg`\n- `--ren-slider-track-height`\n- `--ren-slider-track-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-slider/ren-slider.css`\n- `components/composites/ren-slider/ren-slider.js`\n- `docs/components/ren-slider.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-slider/component.md",
      "id": "contract:composite:ren-slider",
      "type": "contract",
      "name": "ren-slider component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-tabs Component Contract\n\nTabbed interface with trigger list and panels.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-tabs` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-tabs` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Tabs composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a tablist that shows one panel at a time with mutually exclusive triggers.\"\n    - \"You need ARIA tablist semantics (role=tablist, role=tab, role=tabpanel) plus arrow-key keyboard navigation.\"\n    - \"You need a default-value (index or tab id) to control which panel renders on first paint.\"\n    - \"You need manual activation (arrow moves focus, Enter/Space selects) or automatic activation (arrow selects).\"\n    - \"You need a visual variant — .ren-tab-list-underline (default), .ren-tab-list-pills, or .ren-tab-list-enclosed.\"\n    - \"The layout must adapt to narrow widths via container query (horizontal scroll on the .ren-tab-list).\"\n  avoidWhen:\n    - \"Each section should be visible simultaneously — use ren-accordion (multiple) or simple sections.\"\n    - \"The control switches the entire page route, not in-page content — use ren-nav or anchor links.\"\n    - \"Only two states are needed — use ren-toggle-group with a single selection.\"\n    - \"You need a stepper/wizard with sequential progress — use ren-stepper.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-tabs/ren-tabs.css\"\n  js:\n    - \"rends/components/composites/ren-tabs/ren-tabs.js\"\n  notes:\n    - \"JS is required: it sets up roving tabindex, ARIA wiring, and the ren-tab-change event. CSS-only renders triggers but not the selection state.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"<ren-tabs> wraps a <div role=\\\"tablist\\\" class=\\\"ren-tab-list\\\"> followed by sibling <div role=\\\"tabpanel\\\" class=\\\"ren-tab-panel\\\"> elements.\"\n  - \"Each tab trigger is a real <button role=\\\"tab\\\" class=\\\"ren-tab\\\">; never use <a> or <div> for the trigger.\"\n  - \"Tab triggers and tabpanels must match 1:1 in document order; the component pairs them by index.\"\n  - \"Inactive panels carry the hidden attribute (the component toggles it); do not use display: none manually.\"\n  - \"Provide aria-label on <ren-tabs> when there are multiple tablists on the page, so screen-readers can distinguish them.\"\n\nforbiddenPatterns:\n  - \"Wrapping a <a href> as the tab trigger — tabs change the visible panel, they are not navigation links.\"\n  - \"Setting aria-selected manually on a tab; let the component drive it via _selectedIndex.\"\n  - \"Hiding inactive panels with display: none in CSS overrides — use the hidden attribute the JS manages.\"\n  - \"Adding interactive elements (buttons, links) directly inside .ren-tab — keep the trigger a single activation surface.\"\n  - \"Removing the .ren-tab:focus-visible outline without restoring a visible focus indicator inside the inset offset.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-tabs-active-color, --ren-tabs-border-color, --ren-tabs-color, --ren-tabs-duration, --ren-tabs-easing, --ren-tabs-font-size, --ren-tabs-font-weight, --ren-tabs-gap, --ren-tabs-height, --ren-tabs-indicator-color.\"\n    - \"Semantic tokens: --color-text, --color-text-muted, --color-accent, --color-border, --color-fill, --color-fill-hover, --color-surface.\"\n    - \"Shape / motion tokens: --radius-sm, --radius-md, --space-*, --size-lg, --label-md-size, --label-md-weight, --label-lg-weight, --transition-tactile.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named color values in overrides for the active underline or pill background.\"\n    - \"Hardcoded transition durations on the .ren-tab hover; route through --transition-tactile.\"\n\naccessibility:\n  required:\n    - \"Triggers are real <button role=\\\"tab\\\"> with aria-selected reflecting state and tabindex managed by roving (only the selected tab is tabindex=0).\"\n    - \"Tabpanels carry role=\\\"tabpanel\\\" and an aria-labelledby pointing at their trigger id (the component fills this in).\"\n    - \"Arrow Left/Right (horizontal) or Up/Down (vertical) moves focus; Home / End jump to first / last; Enter / Space activate in manual mode.\"\n    - \"The .ren-tab meets touch target via min-height: var(--size-lg); do not shrink it below this in touch contexts.\"\n    - \"Active tab is distinguished by color AND the underline / pill / enclosed visual — never rely on color alone.\"\n    - \"Focus-visible ring is provided by outline + outline-offset on .ren-tab and the panel; preserve both.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-tabs/ren-tabs.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-tabs/ren-tabs.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-tabs aria-label=\"Account settings\">\n  <div class=\"ren-tab-list\" role=\"tablist\"><button class=\"ren-tab\" role=\"tab\">Profile</button><button class=\"ren-tab\" role=\"tab\">Security</button></div>\n  <div class=\"ren-tab-panel\" role=\"tabpanel\">Profile settings</div><div class=\"ren-tab-panel\" role=\"tabpanel\" hidden>Security settings</div>\n</ren-tabs>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-tab`\n- `.ren-tab-list`\n- `.ren-tab-list-enclosed`\n- `.ren-tab-list-pills`\n- `.ren-tab-list-underline`\n- `.ren-tab-panel`\n- `.ren-tabs`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[aria-selected]`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-tabs-active-color`\n- `--ren-tabs-border-color`\n- `--ren-tabs-color`\n- `--ren-tabs-duration`\n- `--ren-tabs-easing`\n- `--ren-tabs-font-size`\n- `--ren-tabs-font-weight`\n- `--ren-tabs-gap`\n- `--ren-tabs-height`\n- `--ren-tabs-indicator-color`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-tabs/ren-tabs.css`\n- `components/composites/ren-tabs/ren-tabs.js`\n- `docs/components/ren-tabs.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-tabs/component.md",
      "id": "contract:composite:ren-tabs",
      "type": "contract",
      "name": "ren-tabs component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-toast Component Contract\n\nTransient notification system with live-region announcements.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-toast` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-toast` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Toast composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a transient notification anchored to a viewport corner that auto-dismisses after a duration.\"\n    - \"The notification must NOT steal focus or block the page (use ren-dialog for that).\"\n    - \"You need a status variant — success / info / warning / danger / loading — with matching icon and subtle status surface.\"\n    - \"You need a polite or assertive live-region announcement for screen-readers (handled via utils/live-region).\"\n    - \"You need the imperative window.toast API (toast.success, toast.danger, toast.promise(...)) instead of declarative markup.\"\n    - \"You need swipe-to-dismiss, hover-to-pause timers, and an optional inline action button.\"\n  avoidWhen:\n    - \"The message blocks the workflow and demands a decision — use ren-dialog (alert variant).\"\n    - \"The message is persistent / inline page-level — use ren-banner or ren-alert.\"\n    - \"The message describes a form field state — use the helper-text / error slot on ren-field.\"\n    - \"The disclosure is a contextual popover from a trigger — use ren-popover or ren-tooltip.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-toast/ren-toast.css\"\n  js:\n    - \"rends/components/composites/ren-toast/ren-toast.js\"\n  notes:\n    - \"JS is required: it owns the viewport queue, timers, live-region announcements, and swipe handling. window.toast is exposed as the imperative API.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Place one <ren-toast-viewport> (or <div class=\\\"ren-toast-viewport\\\" role=\\\"region\\\" aria-label=\\\"Notifications\\\">) per position; data-position defaults to bottom-right.\"\n  - \"Toasts themselves are appended by the JS as .ren-toast nodes with .ren-toast-icon, .ren-toast-body (.ren-toast-title + .ren-toast-description), optional .ren-toast-actions, and .ren-toast-close.\"\n  - \"Status variants are applied as a sibling class on .ren-toast: .ren-toast-success / .ren-toast-info / .ren-toast-warning / .ren-toast-danger / .ren-toast-error / .ren-toast-loading.\"\n  - \"Action buttons inside .ren-toast-actions reuse .ren-btn (typically .ren-btn-ghost .ren-btn-sm); .ren-toast-action is only an override hook.\"\n  - \"Set duration: 0 (or status: 'loading') for persistent toasts; expose a close button so users can dismiss them.\"\n\nforbiddenPatterns:\n  - \"Calling toast.show() with status: 'danger' AND focusing the toast — toasts must not steal focus.\"\n  - \"Rendering toasts inline in document body without a .ren-toast-viewport host (timers and stacking will not work).\"\n  - \"Hardcoded position offsets via inline style — use the [data-position] attribute and rely on var(--space-4) inset.\"\n  - \"Custom slide animations via @keyframes overriding ren-toast-slide-in-*; route motion through --duration-enter / --ease-enter.\"\n  - \"Using colored side borders for status emphasis; variants use a subtle status surface plus icon/text instead.\"\n  - \"Using a toast to ask for confirmation (\\\"Are you sure?\\\") — confirmations belong in ren-dialog.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-toast-anim-duration, --ren-toast-bg, --ren-toast-border, --ren-toast-duration, --ren-toast-easing, --ren-toast-gap, --ren-toast-padding, --ren-toast-radius, --ren-toast-shadow, --ren-toast-width, plus the shared --ren-btn-* family for inline action buttons.\"\n    - \"Semantic state tokens: --color-success, --color-success-subtle, --color-info, --color-info-subtle, --color-warning, --color-warning-subtle, --color-danger, --color-danger-subtle, --color-accent (progress bar), --color-surface, --color-surface-raised, --color-surface-hover, --color-surface-active, --color-text, --color-text-muted, --color-border.\"\n    - \"Shape / motion tokens: --space-*, --radius-sm, --radius-lg, --shadow-lg, --duration-enter, --duration-exit, --duration-micro, --ease-enter, --ease-exit, --transition-tactile, --z-toast.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named color values in overrides for the status surface, border, or icon color.\"\n    - \"Hardcoded animation timings; route through --duration-enter / --duration-exit and the paired easings.\"\n\naccessibility:\n  required:\n    - \"Viewport is a role=\\\"region\\\" with aria-label=\\\"Notifications\\\" so screen-readers can locate it as a landmark.\"\n    - \"Success / info messages announce via aria-live=\\\"polite\\\"; danger / error messages announce via aria-live=\\\"assertive\\\" (handled by announcePolite / announceAssertive).\"\n    - \"Status must be communicated by icon AND text/title — color alone does not satisfy the contrast contract.\"\n    - \"Hover and keyboard focus pause auto-dismiss timers; restoring focus elsewhere resumes them.\"\n    - \"The .ren-toast-close button has an accessible name (aria-label=\\\"Close\\\") and is reachable via keyboard.\"\n    - \"Toasts never trap focus; they receive focus only when the user tabs into the viewport region intentionally.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-toast/ren-toast.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-toast/ren-toast.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-toast-viewport data-position=\"bottom-right\" aria-label=\"Notifications\"></ren-toast-viewport>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-btn`\n- `.ren-toast`\n- `.ren-toast-action`\n- `.ren-toast-actions`\n- `.ren-toast-body`\n- `.ren-toast-close`\n- `.ren-toast-danger`\n- `.ren-toast-description`\n- `.ren-toast-error`\n- `.ren-toast-icon`\n- `.ren-toast-info`\n- `.ren-toast-loading`\n- `.ren-toast-progress`\n- `.ren-toast-success`\n- `.ren-toast-title`\n- `.ren-toast-viewport`\n- `.ren-toast-warning`\n\n## States And Attributes\n\n- `[data-closing]`\n- `[data-position]`\n- `[data-swiping]`\n- `[data-toast-id]`\n- `:active`\n- `:hover`\n\n## Public Token API\n\n- `--ren-btn-bg`\n- `--ren-btn-bg-active`\n- `--ren-btn-bg-hover`\n- `--ren-btn-border-color`\n- `--ren-btn-border-width`\n- `--ren-btn-color`\n- `--ren-btn-duration`\n- `--ren-btn-easing`\n- `--ren-btn-font-size`\n- `--ren-btn-font-weight`\n- `--ren-btn-gap`\n- `--ren-btn-height`\n- `--ren-btn-padding-x`\n- `--ren-btn-padding-y`\n- `--ren-btn-radius`\n- `--ren-btn-ring-color`\n- `--ren-btn-ring-offset`\n- `--ren-btn-ring-width`\n- `--ren-toast-anim-duration`\n- `--ren-toast-bg`\n- `--ren-toast-border`\n- `--ren-toast-duration`\n- `--ren-toast-easing`\n- `--ren-toast-gap`\n- `--ren-toast-padding`\n- `--ren-toast-radius`\n- `--ren-toast-shadow`\n- `--ren-toast-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-toast/ren-toast.css`\n- `components/composites/ren-toast/ren-toast.js`\n- `docs/components/ren-toast.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-toast/component.md",
      "id": "contract:composite:ren-toast",
      "type": "contract",
      "name": "ren-toast component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-toggle-group Component Contract\n\nGrouped toggle controls for mutually exclusive or multi-select modes.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-toggle-group` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-toggle-group` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Toggle Group composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User picks from a small set of mutually exclusive options shown as visually grouped buttons (single mode).\"\n    - \"User toggles multiple independent options that share a control (multiple mode, type=\\\"multiple\\\").\"\n    - \"The choices are short labels or icons (text alignment, view density, list/grid mode) better surfaced than in a select.\"\n    - \"You want a visual variant — default filled pill group or .ren-toggle-group-outline.\"\n    - \"You need size variants (.ren-toggle-group-sm / .ren-toggle-group-lg), full-width fill, or vertical orientation.\"\n  avoidWhen:\n    - \"The choice set is large or sortable — use ren-select.\"\n    - \"The choice is binary on/off for a single concept — use ren-switch or ren-checkbox.\"\n    - \"Items are commands rather than state — use ren-toolbar.\"\n    - \"Items are pages or routes — use ren-tabs or ren-nav.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-toggle-group/ren-toggle-group.css\"\n  js:\n    - \"rends/components/composites/ren-toggle-group/ren-toggle-group.js\"\n  notes:\n    - \"JS is required: it wires role=button, roving tabindex, aria-pressed updates, keyboard navigation, and the ren-toggle-group-change event.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"<ren-toggle-group type=\\\"single|multiple\\\" value=\\\"...\\\"> wraps real <button class=\\\"ren-toggle-group-item\\\" data-value=\\\"...\\\"> elements (the component will also accept [role=button] nodes).\"\n  - \"Each item must carry a data-value attribute or a stable textContent — the component uses it to drive selection and the change event.\"\n  - \"Set type=\\\"single\\\" for mutually-exclusive selection (the component clears other aria-pressed values) or type=\\\"multiple\\\" for independent toggles.\"\n  - \"Variants are applied on the host: .ren-toggle-group-outline, .ren-toggle-group-sm / -lg, .ren-toggle-group-full, .ren-toggle-group-vertical.\"\n  - \"Icons inside items must be <svg> or [role=\\\"img\\\"] children sized to 1em (handled by the CSS); accompany icons with text or aria-label.\"\n\nforbiddenPatterns:\n  - \"<a href> as items — toggle-group is for state, not navigation.\"\n  - \"Setting aria-pressed manually on items; let the component drive it via setValue / click handling.\"\n  - \"Using ren-btn inside the group as items — they bring their own chrome; use plain <button class=\\\"ren-toggle-group-item\\\"> instead.\"\n  - \"Hardcoding the pressed background (background: #fff) — use --color-surface / --color-accent semantic tokens and the outline variant for accent fills.\"\n  - \"Removing the focus-visible outline without restoring an outline-offset visible ring.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-toggle-active-bg, --ren-toggle-active-color, --ren-toggle-bg, --ren-toggle-color, --ren-toggle-font-size, --ren-toggle-font-weight, --ren-toggle-height, --ren-toggle-padding-x, --ren-toggle-radius.\"\n    - \"Semantic tokens: --color-fill, --color-surface, --color-text, --color-text-muted, --color-accent, --color-border, --color-border-strong.\"\n    - \"Shape / motion tokens: --radius-md, --radius-lg, --space-*, --shadow-sm, --touch-min, --transition-tactile, font-size tokens (--font-size-label, --font-size-body, --font-size-xs).\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named color values in overrides for the pressed background or accent border.\"\n    - \"Hardcoded transition durations on item hover; route through --transition-tactile.\"\n\naccessibility:\n  required:\n    - \"Items are real <button> elements with role=\\\"button\\\" and aria-pressed reflecting state; roving tabindex ensures only one item is tabindex=0 at a time.\"\n    - \"Arrow Right/Down moves focus to the next item, Arrow Left/Up to the previous (wrapping); Home / End jump to first / last.\"\n    - \"Touch target meets calc(var(--touch-min) - 4px); .ren-toggle-group-sm should only be used in non-touch contexts.\"\n    - \"Active state communicates via background AND font-weight / box-shadow — never rely on color alone.\"\n    - \"Focus-visible outline is 2px solid var(--color-accent) with outline-offset; preserve it across all variants.\"\n    - \"Disabled items use :disabled with pointer-events: none so they remain in tab order semantics but cannot be activated.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-toggle-group/ren-toggle-group.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-toggle-group/ren-toggle-group.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-toggle-group type=\"single\" value=\"left\"><button class=\"ren-toggle-group-item\" type=\"button\" data-value=\"left\">Left</button><button class=\"ren-toggle-group-item\" type=\"button\" data-value=\"center\">Center</button><button class=\"ren-toggle-group-item\" type=\"button\" data-value=\"right\">Right</button></ren-toggle-group>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-toggle-group`\n- `.ren-toggle-group-full`\n- `.ren-toggle-group-item`\n- `.ren-toggle-group-lg`\n- `.ren-toggle-group-outline`\n- `.ren-toggle-group-sm`\n- `.ren-toggle-group-vertical`\n\n## States And Attributes\n\n- `[aria-pressed]`\n- `[data-state]`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-toggle-active-bg`\n- `--ren-toggle-active-color`\n- `--ren-toggle-bg`\n- `--ren-toggle-color`\n- `--ren-toggle-font-size`\n- `--ren-toggle-font-weight`\n- `--ren-toggle-height`\n- `--ren-toggle-padding-x`\n- `--ren-toggle-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-toggle-group/ren-toggle-group.css`\n- `components/composites/ren-toggle-group/ren-toggle-group.js`\n- `docs/components/ren-toggle-group.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-toggle-group/component.md",
      "id": "contract:composite:ren-toggle-group",
      "type": "contract",
      "name": "ren-toggle-group component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-toolbar Component Contract\n\nGrouped command bar with roving keyboard navigation.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-toolbar` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-toolbar` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Toolbar composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a grouped command bar (formatting actions, edit operations, view controls) co-located near content.\"\n    - \"You need the WAI-ARIA Toolbar pattern: Arrow keys move between items, Tab moves focus in / out of the toolbar.\"\n    - \"You need Home / End to jump to first / last item and skip disabled ones.\"\n    - \"Some items are toggleable (aria-pressed / [data-active] for sticky on-states like Bold).\"\n    - \"You need a visual separator (.ren-toolbar-separator) to group related commands within the bar.\"\n    - \"You need icon-only items via .ren-toolbar-item-icon (aspect-ratio 1) or a ghost variant (.ren-toolbar-ghost).\"\n  avoidWhen:\n    - \"Items represent value selection rather than commands — use ren-toggle-group.\"\n    - \"Items represent navigation to other pages — use ren-nav or ren-sidebar.\"\n    - \"You need to switch between content panels — use ren-tabs.\"\n    - \"There is only one action — use ren-button directly.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-toolbar/ren-toolbar.css\"\n  js:\n    - \"rends/components/composites/ren-toolbar/ren-toolbar.js\"\n  notes:\n    - \"JS is required for the roving tabindex pattern: import initToolbar / initAllToolbars from ren-toolbar.js.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"The host is <div class=\\\"ren-toolbar\\\" role=\\\"toolbar\\\" aria-label=\\\"<purpose>\\\">; the role and an aria-label are required so AT can announce it.\"\n  - \"Items are real <button class=\\\"ren-toolbar-item\\\"> with tabindex=\\\"0\\\" on the first item and tabindex=\\\"-1\\\" on the rest (roving tabindex).\"\n  - \"Separators are <div class=\\\"ren-toolbar-separator\\\" role=\\\"separator\\\"> between logical groups within the toolbar.\"\n  - \"Sticky toggle items reflect state via aria-pressed=\\\"true|false\\\" or [data-active]; do not mix the two on the same item.\"\n  - \"Use .ren-toolbar-vertical on the host to switch arrow keys to Up/Down and turn separators horizontal.\"\n\nforbiddenPatterns:\n  - \"Omitting role=\\\"toolbar\\\" or aria-label on the host — initToolbar bails out and the keyboard pattern will not attach.\"\n  - \"Setting tabindex=\\\"0\\\" on every item — only the focused item should be tabindex=\\\"0\\\"; the JS rewrites the rest to -1.\"\n  - \"Using <a href> as items — toolbar items invoke commands, not navigate; use ren-nav for links.\"\n  - \"Hardcoded gap / padding in inline styles; use --space-1 via the documented selectors.\"\n  - \"Mounting toolbar buttons as .ren-btn — toolbar owns its own item chrome; mixing the two doubles the focus rings and padding.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-toolbar-active-bg, --ren-toolbar-bg, --ren-toolbar-border, --ren-toolbar-height, --ren-toolbar-item-hover, --ren-toolbar-item-radius, --ren-toolbar-item-size, --ren-toolbar-padding, --ren-toolbar-radius.\"\n    - \"Semantic tokens: --color-text, --color-accent, --color-border, --color-fill-hover, --color-fill-active, --color-surface, --color-surface-raised, --color-surface-sunken, --color-disabled-text, --color-focus-ring.\"\n    - \"Shape / motion tokens: --radius-sm, --radius-md, --space-1, --space-2, --size-sm, --stroke-1, --ring-width, --shadow-xs, --transition-tactile, --weight-medium, --caption-size, --text-sm.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named color values in overrides for the pressed-state background or active accent color.\"\n    - \"Hardcoded transition durations on item hover; route through --transition-tactile.\"\n\naccessibility:\n  required:\n    - \"Host carries role=\\\"toolbar\\\" plus an aria-label describing its purpose (e.g. \\\"Formatting\\\").\"\n    - \"Roving tabindex: only one .ren-toolbar-item is tabindex=\\\"0\\\" at any time; Tab enters/exits the toolbar instead of cycling items.\"\n    - \"Arrow Left/Right (horizontal) or Up/Down (.ren-toolbar-vertical) moves focus; Home / End jump to first / last; disabled items are skipped.\"\n    - \"Toggle items expose aria-pressed and a visible non-color cue (box-shadow + accent color) in addition to background fill.\"\n    - \"Focus-visible ring is var(--ring-width) solid var(--color-focus-ring) with outline-offset: -1px; preserve it across variants.\"\n    - \"Disabled items use :disabled or aria-disabled=\\\"true\\\"; both block keyboard activation and are filtered out of the focus rotation.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-toolbar/ren-toolbar.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-toolbar/ren-toolbar.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div class=\"ren-toolbar\" role=\"toolbar\" aria-label=\"Formatting\"><button class=\"ren-toolbar-item\" type=\"button\" tabindex=\"0\">Bold</button><div class=\"ren-toolbar-separator\" role=\"separator\"></div><button class=\"ren-toolbar-item\" type=\"button\" tabindex=\"-1\">Italic</button></div>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-toolbar`\n- `.ren-toolbar-ghost`\n- `.ren-toolbar-item`\n- `.ren-toolbar-item-icon`\n- `.ren-toolbar-separator`\n- `.ren-toolbar-vertical`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[aria-pressed]`\n- `[data-active]`\n- `:active`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-toolbar-active-bg`\n- `--ren-toolbar-bg`\n- `--ren-toolbar-border`\n- `--ren-toolbar-height`\n- `--ren-toolbar-item-hover`\n- `--ren-toolbar-item-radius`\n- `--ren-toolbar-item-size`\n- `--ren-toolbar-padding`\n- `--ren-toolbar-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-toolbar/ren-toolbar.css`\n- `components/composites/ren-toolbar/ren-toolbar.js`\n- `docs/components/ren-toolbar.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-toolbar/component.md",
      "id": "contract:composite:ren-toolbar",
      "type": "contract",
      "name": "ren-toolbar component.md"
    },
    {
      "data": {
        "kind": "composite"
      },
      "body": "# ren-tooltip Component Contract\n\nShort accessible description attached to a trigger.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-tooltip` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-tooltip` composite.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Tooltip composite behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this composite.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a short, non-interactive description attached to a hoverable / focusable trigger (icon button label, truncated text expansion).\"\n    - \"The content is plain text under ~15rem and provides supplementary info, not essential interaction.\"\n    - \"Disclosure is driven by hover, focus, and the popover API with anchor positioning + flip-block fallback.\"\n    - \"You need the dark high-contrast pill with an optional arrow caret (.ren-tooltip-arrow).\"\n    - \"You can place [data-side=\\\"top|right|bottom|left\\\"] on the .ren-tooltip to indicate preferred side; CSS handles flipping.\"\n  avoidWhen:\n    - \"The content is interactive (links, buttons, form fields) — use ren-popover or ren-hover-card.\"\n    - \"The content is critical for completing the task — use inline helper text or a visible label.\"\n    - \"The disclosure is value selection — use ren-select or ren-menu.\"\n    - \"The trigger has no accessible name and the tooltip is the only label — give the trigger a real aria-label or visible text instead.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/composites/ren-tooltip/ren-tooltip.css\"\n  js:\n    - \"rends/components/composites/ren-tooltip/ren-tooltip.js\"\n  notes:\n    - \"JS is required: it owns the popover toggle, hover/focus timing (--ren-tooltip-delay), and the fallback positioning for browsers without CSS anchor-name support.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Pair a .ren-tooltip-trigger (real <button> or focusable element) with a .ren-tooltip element referenced via the popover API (popovertarget / id linkage).\"\n  - \"The tooltip carries popover attribute so it lives in the top layer; do not implement it as a plain absolutely-positioned <div>.\"\n  - \"Include .ren-tooltip-arrow as a child of .ren-tooltip when an arrow is desired; position is driven by [data-side].\"\n  - \"Set the desired side on the tooltip via data-side=\\\"top|right|bottom|left\\\"; CSS uses position-area + position-try-fallbacks: flip-block for safe placement.\"\n  - \"Tooltip content must be plain text — no buttons, links, or form fields (those break the contract; use ren-popover instead).\"\n\nforbiddenPatterns:\n  - \"Putting interactive elements inside .ren-tooltip — pointer-events: none on the tooltip means clicks would not work and screen readers would not focus them anyway.\"\n  - \"Showing the tooltip only on hover without keyboard focus support — every trigger must reveal the tooltip on :focus-visible.\"\n  - \"Using a tooltip as the sole accessible name for an icon button — provide aria-label on the trigger and let the tooltip echo it.\"\n  - \"Animating the tooltip via custom @keyframes; rely on the documented opacity + transform transition with @starting-style.\"\n  - \"Hardcoding background / color on .ren-tooltip — override --ren-tooltip-bg / --ren-tooltip-color tokens instead.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-tooltip-bg, --ren-tooltip-color, --ren-tooltip-delay, --ren-tooltip-duration, --ren-tooltip-easing, --ren-tooltip-font-size, --ren-tooltip-padding-x, --ren-tooltip-padding-y, --ren-tooltip-radius, --ren-tooltip-shadow.\"\n    - \"Shape / motion tokens: --radius-md, --space-1, --space-2, --size-caption, --duration-enter, --ease-enter, --z-tooltip.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code. Note: the current default uses --color-gray-900 as the surface; consumers should override --ren-tooltip-bg with a semantic token (e.g. --color-text) rather than reaching for another --gray-* directly.\"\n    - \"Hardcoded hex / rgb / named color values (e.g. background: #111, color: white) in overrides — use --ren-tooltip-bg and --ren-tooltip-color.\"\n    - \"Hardcoded transition durations; route through --duration-enter / --ren-tooltip-duration and the paired easings.\"\n\naccessibility:\n  required:\n    - \"Trigger has its own accessible name (visible text or aria-label); the tooltip supplements but does not replace it.\"\n    - \"Tooltip opens on both :hover and keyboard :focus-visible; closing on blur / mouseleave / Escape.\"\n    - \"Hover delay respects --ren-tooltip-delay; do not show tooltips instantly on mouse jitter or hide them on the next mousemove.\"\n    - \"Tooltip text must meet contrast for the chosen --ren-tooltip-bg / --ren-tooltip-color pair (default dark bg / white text is WCAG AA against typical surfaces).\"\n    - \"Touch users: tooltips should not be the only way to reveal essential info — provide a visible label or alternate disclosure for touch.\"\n    - \"Tooltip is pointer-events: none and never receives focus — if the content needs interaction, switch to ren-popover.\"\n    - \"The component appends its ID to any existing aria-describedby token list and removes only that owned token on disconnect.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/composites/ren-tooltip/ren-tooltip.css\">\n<script type=\"module\" src=\"rends/components/composites/ren-tooltip/ren-tooltip.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<button class=\"ren-tooltip-trigger\" type=\"button\" aria-label=\"More information\">Info<ren-tooltip id=\"info-tip\" placement=\"top\">More information</ren-tooltip></button>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-open`\n- `.ren-tooltip`\n- `.ren-tooltip-arrow`\n- `.ren-tooltip-trigger`\n\n## States And Attributes\n\n- `[data-side]`\n\n## Public Token API\n\n- `--ren-tooltip-bg`\n- `--ren-tooltip-color`\n- `--ren-tooltip-delay`\n- `--ren-tooltip-duration`\n- `--ren-tooltip-easing`\n- `--ren-tooltip-font-size`\n- `--ren-tooltip-padding-x`\n- `--ren-tooltip-padding-y`\n- `--ren-tooltip-radius`\n- `--ren-tooltip-shadow`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/composites/ren-tooltip/ren-tooltip.css`\n- `components/composites/ren-tooltip/ren-tooltip.js`\n- `docs/components/ren-tooltip.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/composites/ren-tooltip/component.md",
      "id": "contract:composite:ren-tooltip",
      "type": "contract",
      "name": "ren-tooltip component.md"
    },
    {
      "data": {
        "kind": "pattern"
      },
      "body": "# ren-ai Pattern Contract\n\nAI interaction pattern for prompts, responses, suggestions, and assistant surfaces.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-ai` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-ai` pattern.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Ai pattern behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this pattern.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The UI surfaces AI-generated content (chat reply, completion, summary, suggestion).\"\n    - \"You need an AI message bubble with streaming caret (.ren-ai-streaming) or typing indicator.\"\n    - \"You need to render citations / source references attached to AI output (.ren-ai-citation, .ren-ai-sources).\"\n    - \"You need a confidence indicator with high/medium/low levels (data-level=\\\"high|medium|low\\\").\"\n    - \"You need a chat-style prompt input (.ren-ai-prompt + .ren-ai-prompt-input + .ren-ai-prompt-send).\"\n    - \"You need to mark non-AI content as AI-augmented via an inline slug (.ren-ai-slug).\"\n  avoidWhen:\n    - \"The content is a generic human message — use a plain card / message bubble.\"\n    - \"The control is a generic submit button — use ren-button, not .ren-ai-prompt-send.\"\n    - \"The loading state is for non-AI fetch/page — use ren-skeleton, not .ren-ai-skeleton.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-ai/ren-ai.css\"\n  notes:\n    - \"CSS-only pattern. No colocated JS — streaming text and feedback handlers live in consumer code.\"\n    - \"Animations respect prefers-reduced-motion via fallback rules; do not override.\"\n\nrequiredMarkup:\n  - \"Wrap AI replies in <div class=\\\"ren-ai-message\\\"> with a .ren-ai-message-header (slug + meta) and .ren-ai-message-content.\"\n  - \"Streaming responses add the .ren-ai-streaming class on .ren-ai-message; the blinking caret is the ::after on .ren-ai-message-content.\"\n  - \"Citations are inline <a class=\\\"ren-ai-citation\\\"> with vertical-align: super; source lists use <a class=\\\"ren-ai-source\\\"> rows inside .ren-ai-sources.\"\n  - \".ren-ai-confidence MUST carry data-level=\\\"high|medium|low\\\"; the fill width and color are driven by that attribute.\"\n  - \"The prompt composer must be .ren-ai-prompt wrapping a <textarea class=\\\"ren-ai-prompt-input\\\"> and a <button class=\\\"ren-ai-prompt-send\\\"> with an accessible name.\"\n\nforbiddenPatterns:\n  - \"Using .ren-ai-message for non-AI chat content (use a neutral message component instead).\"\n  - \"Faking confidence levels by inline-styling .ren-ai-confidence-fill width — set data-level.\"\n  - \"Replacing .ren-ai-prompt-send with a bare <button> styled as a circle — use the documented selector.\"\n  - \"Hardcoding the AI accent (e.g. #8B5CF6, --purple-*) instead of using --color-ai / --color-ai-subtle.\"\n  - \"Adding colored side borders to mark AI content; use the AI surface, icon/slug, and semantic tokens instead.\"\n  - \"Animating the typing dots or skeleton manually; the pattern ships keyframes + reduced-motion fallbacks.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic tokens: --color-ai, --color-ai-hover, --color-ai-subtle, --color-on-ai, --color-text, --color-text-muted, --color-surface-raised, --color-surface-sunken, --color-fill, --color-border-muted, --color-success, --color-warning, --color-danger.\"\n    - \"Layout / type tokens: --space-*, --radius-*, --stroke-1, --body-size, --caption-size, --caption-sm-size, --label-size, --leading-relaxed, --weight-medium, --weight-semibold, --weight-bold.\"\n    - \"Motion tokens: --duration-tactile, --duration-state, --ease-enter.\"\n    - \"Size tokens for the send button and action chrome: --size-sm, --size-md, --text-lg.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code or inside the pattern CSS.\"\n    - \"Hardcoded hex / named colors (e.g. #8B5CF6, purple, rgb(...)).\"\n    - \"Raw transition durations or easings; use --duration-* / --ease-*.\"\n\naccessibility:\n  required:\n    - \"AI messages must include a visible AI slug or .ren-ai-message-header so screen-reader users know the source is AI-generated, not human.\"\n    - \"Streaming caret and typing dots respect prefers-reduced-motion; never strip those fallbacks.\"\n    - \".ren-ai-action-thumbs feedback buttons need accessible names (aria-label=\\\"Helpful\\\" / \\\"Not helpful\\\"); icon-only is not enough.\"\n    - \".ren-ai-prompt-input is a real <textarea> with an associated <label> (or aria-label); the send button must reflect disabled state via the disabled attribute, not opacity alone.\"\n    - \"Confidence must not rely on color alone — keep the numeric / textual level visible next to .ren-ai-confidence-bar.\"\n    - \".ren-ai-citation must be a real <a> with href and an accessible target; do not implement as a styled <span>.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-ai/ren-ai.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div class=\"ren-ai-message\"><div class=\"ren-ai-message-header\"><span class=\"ren-ai-slug\">AI</span></div><div class=\"ren-ai-message-content\">Here is the summary.</div></div><form class=\"ren-ai-prompt\"><textarea class=\"ren-ai-prompt-input\" aria-label=\"Message\"></textarea><button class=\"ren-ai-prompt-send\" type=\"submit\" aria-label=\"Send message\">Send</button></form>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-ai-action`\n- `.ren-ai-action-edit`\n- `.ren-ai-action-regenerate`\n- `.ren-ai-action-thumbs`\n- `.ren-ai-actions`\n- `.ren-ai-citation`\n- `.ren-ai-confidence`\n- `.ren-ai-confidence-bar`\n- `.ren-ai-confidence-fill`\n- `.ren-ai-error`\n- `.ren-ai-error-message`\n- `.ren-ai-file-chip`\n- `.ren-ai-file-chip-icon`\n- `.ren-ai-file-chip-name`\n- `.ren-ai-file-chip-remove`\n- `.ren-ai-file-chip-size`\n- `.ren-ai-message`\n- `.ren-ai-message-content`\n- `.ren-ai-message-footer`\n- `.ren-ai-message-header`\n- `.ren-ai-prompt`\n- `.ren-ai-prompt-input`\n- `.ren-ai-prompt-send`\n- `.ren-ai-skeleton`\n- `.ren-ai-skeleton-line`\n- `.ren-ai-slug`\n- `.ren-ai-source`\n- `.ren-ai-source-number`\n- `.ren-ai-source-title`\n- `.ren-ai-source-url`\n- `.ren-ai-sources`\n- `.ren-ai-streaming`\n- `.ren-ai-tool-call`\n- `.ren-ai-tool-call-args`\n- `.ren-ai-tool-call-header`\n- `.ren-ai-tool-call-name`\n- `.ren-ai-tool-call-result`\n- `.ren-ai-tool-call-status`\n- `.ren-ai-typing`\n- `.ren-ai-typing-dots`\n\nAll selectors are CSS-only — the consumer wires retry handlers, file\nupload state, tool execution, and edit-and-resend logic. The pattern\ngives you the visuals (and a sane `<details>` baseline for tool calls\nso collapse works without JS); state belongs to your app.\n\n## States And Attributes\n\n- `[data-active]`\n- `[data-level]` — on `.ren-ai-confidence` (high|medium|low)\n- `[data-status]` — on `.ren-ai-tool-call` (pending|running|success|error)\n- `[open]` — native on `.ren-ai-tool-call` when expanded\n- `:active`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/patterns/ren-ai/ren-ai.css`\n- `docs/components/ren-ai.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/patterns/ren-ai/pattern.md",
      "id": "contract:pattern:ren-ai",
      "type": "contract",
      "name": "ren-ai pattern.md"
    },
    {
      "data": {
        "kind": "pattern"
      },
      "body": "# ren-command Pattern Contract\n\nCommand palette pattern for searchable application actions.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-command` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-command` pattern.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Command pattern behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this pattern.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The product needs a Ctrl/Cmd+K command palette / Spotlight-style action launcher.\"\n    - \"Actions are searchable by title, description, or data-keywords, with keyboard up/down navigation and Enter to activate.\"\n    - \"Items are grouped (e.g. \\\"Navigation\\\", \\\"Settings\\\") via .ren-command-group with .ren-command-group-heading.\"\n    - \"You want a global keyboard shortcut (data-shortcut=\\\"ctrl+k\\\") that opens the palette from anywhere on the page.\"\n    - \"You need a modal overlay with backdrop + Escape-to-close dismissal driven by a real <dialog>.\"\n  avoidWhen:\n    - \"The list is a navigation menu — use ren-nav or ren-menubar.\"\n    - \"The list is a static dropdown of options — use ren-menu / ren-select.\"\n    - \"The list is a filter for a data table — use ren-table-toolbar-search inside ren-table.\"\n    - \"There is no search input (just a static action list) — use ren-menu.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-command/ren-command.css\"\n  js:\n    - \"rends/components/patterns/ren-command/ren-command.js\"\n  notes:\n    - \"JS registers customElements.define('ren-command', ...) and owns the global shortcut listener, filtering, roving highlight, and ren-command-select event.\"\n    - \"The pattern is dialog-based; the underlying <dialog> must be present so showModal() / close() work.\"\n\nrequiredMarkup:\n  - \"Use a <ren-command> custom element wrapping a real <dialog class=\\\"ren-command\\\"> (the JS calls dialog.showModal()).\"\n  - \"Inside, include a .ren-command-input-wrapper with a <input class=\\\"ren-command-input\\\" type=\\\"text\\\"> and an optional .ren-command-kbd hint.\"\n  - \"Wrap action rows in <ul class=\\\"ren-command-list\\\"> (or container) of <button class=\\\"ren-command-item\\\"> rows; items may carry data-keywords, data-value, data-action.\"\n  - \"Group rows under <div class=\\\"ren-command-group\\\"><div class=\\\"ren-command-group-heading\\\">…</div>…</div>; the JS toggles [data-empty] on empty groups.\"\n  - \"Provide a .ren-command-empty fallback element; the JS shows it when the filter produces zero matches.\"\n  - \"Each .ren-command-item that has a shortcut puts the keys inside .ren-command-item-shortcut with <kbd> elements.\"\n\nforbiddenPatterns:\n  - \"Implementing as a <div> with click handlers — must be <ren-command> + <dialog> so Escape, focus, and a11y come from the platform.\"\n  - \"Filtering items via display: none from external code — the component owns visibility via _filterItems().\"\n  - \"Building roving focus / highlight manually — the [data-highlighted] attribute is set by the component on the active item.\"\n  - \"Binding a second Ctrl+K listener at the document level — use the data-shortcut attribute or registerAction() API.\"\n  - \"Submitting selection from a custom click handler that bypasses the ren-command-select event.\"\n\ntokenPolicy:\n  allowed:\n    - \"Public component tokens listed in Public Token API: --ren-command-bg, --ren-command-border, --ren-command-item-height, --ren-command-item-radius, --ren-command-max-height, --ren-command-radius, --ren-command-shadow, --ren-command-width.\"\n    - \"Semantic surface / text tokens consumed by selectors: --color-surface, --color-border, --color-text, --color-text-muted, --color-text-faint, --color-fill, --color-fill-hover, --color-separator.\"\n    - \"Spacing / motion tokens: --ren-space-*, --ren-radius-*, --duration-enter, --duration-overlay, --duration-state, --ease-enter, --ease-state-change.\"\n    - \"Z-index token: --ren-z-modal.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / named colors for surface, border, fill, or text.\"\n    - \"Custom z-index values that bypass --ren-z-modal (the palette must layer above content but below toasts).\"\n\naccessibility:\n  required:\n    - \"Render as a real <dialog> so focus trap, Escape, and inert background come from the browser.\"\n    - \"The input is a real <input type=\\\"text\\\"> with an accessible name (label or aria-label like \\\"Search commands\\\").\"\n    - \"The component injects a [role=\\\"status\\\"][aria-live=\\\"polite\\\"] region announcing result counts on every filter — do not remove it.\"\n    - \"Items must be real <button> elements (or role=\\\"option\\\" rows) so Enter / Space / click all work uniformly.\"\n    - \"Disabled items use aria-disabled=\\\"true\\\" AND pointer-events: none (the CSS handles the latter).\"\n    - \"The shortcut hint inside .ren-command-kbd is decorative; do not rely on it as the only affordance — Escape and click-outside must both close.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-command/ren-command.css\">\n<script type=\"module\" src=\"rends/components/patterns/ren-command/ren-command.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-command><dialog class=\"ren-command\"><div class=\"ren-command-input-wrapper\"><input class=\"ren-command-input\" type=\"text\" aria-label=\"Search commands\"></div><ul class=\"ren-command-list\"><li><button class=\"ren-command-item\" type=\"button\" data-value=\"save\">Save</button></li></ul><div class=\"ren-command-empty\" hidden>No commands found</div></dialog></ren-command>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-command`\n- `.ren-command-empty`\n- `.ren-command-footer`\n- `.ren-command-footer-hint`\n- `.ren-command-group`\n- `.ren-command-group-heading`\n- `.ren-command-input`\n- `.ren-command-input-wrapper`\n- `.ren-command-item`\n- `.ren-command-item-content`\n- `.ren-command-item-description`\n- `.ren-command-item-icon`\n- `.ren-command-item-shortcut`\n- `.ren-command-item-title`\n- `.ren-command-kbd`\n- `.ren-command-list`\n- `.ren-command-separator`\n\n## States And Attributes\n\n- `[aria-disabled]`\n- `[aria-live]`\n- `[data-empty]`\n- `[data-highlighted]`\n- `:active`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-command-bg`\n- `--ren-command-border`\n- `--ren-command-item-height`\n- `--ren-command-item-radius`\n- `--ren-command-max-height`\n- `--ren-command-radius`\n- `--ren-command-shadow`\n- `--ren-command-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/patterns/ren-command/ren-command.css`\n- `components/patterns/ren-command/ren-command.js`\n- `docs/components/ren-command.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/patterns/ren-command/pattern.md",
      "id": "contract:pattern:ren-command",
      "type": "contract",
      "name": "ren-command pattern.md"
    },
    {
      "data": {
        "kind": "pattern"
      },
      "body": "# ren-empty-state Pattern Contract\n\nEmpty state pattern for no-data, first-run, and recovery surfaces.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-empty-state` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-empty-state` pattern.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Empty State pattern behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this pattern.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"A data container (ren-table, list, ren-card, search result) renders zero items and needs a no-data surface.\"\n    - \"A first-run experience needs an icon + title + description + primary action (e.g. \\\"Add your first product\\\").\"\n    - \"A search / filter result is empty and the surface should explain how to recover (.ren-empty-state-compact).\"\n    - \"An error / recovery state needs the same centered layout as zero-data (e.g. \\\"Nothing here yet\\\").\"\n    - \"You want a dashed-bordered placeholder inside a card or table body (.ren-empty-state-bordered).\"\n  avoidWhen:\n    - \"The UI is loading data (still pending) — use ren-skeleton, not an empty state.\"\n    - \"The page is a real 404 / 500 error route — use the error-page template, not this inline component.\"\n    - \"The container is full but a row is unselected — that is a selection-prompt, not an empty state.\"\n    - \"You need a toast / inline alert about a failed action — use ren-alert / ren-toast.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-empty-state/ren-empty-state.css\"\n  notes:\n    - \"CSS-only pattern. No JS — actions inside .ren-empty-state-actions are real <button> / ren-button elements wired by the consumer.\"\n    - \"Drop directly into <tbody>, <ul>, or any container — it centers itself with max-width: 28rem and auto inline margins.\"\n\nrequiredMarkup:\n  - \"Root is <div class=\\\"ren-empty-state\\\"> (or a <section> for a landmark).\"\n  - \"Use an .ren-empty-state-icon block for the glyph/illustration; mark decorative icons aria-hidden=\\\"true\\\".\"\n  - \"Heading is an <h2> / <h3> with class .ren-empty-state-title — never put plain text without a heading element.\"\n  - \"Description text is a <p class=\\\"ren-empty-state-description\\\">; keep within the 28rem prose width by not overriding the max-width.\"\n  - \"Actions live in <div class=\\\"ren-empty-state-actions\\\"> and should be real <button class=\\\"ren-btn\\\"> or <a class=\\\"ren-btn\\\"> — not links styled as buttons via <span>.\"\n\nforbiddenPatterns:\n  - \"Using .ren-empty-state as a generic centered hero — it is reserved for no-data / zero-state surfaces.\"\n  - \"Combining .ren-empty-state-compact with .ren-empty-state-bordered when the container is already a bordered card (double border).\"\n  - \"Decorative icon as the only message — always include .ren-empty-state-title so screen readers understand the state.\"\n  - \"Hardcoding padding / max-width via inline style — override --ren-empty-padding / --ren-empty-max-width instead.\"\n  - \"Embedding form fields directly inside .ren-empty-state — pair with ren-form / ren-button actions only.\"\n\ntokenPolicy:\n  allowed:\n    - \"Public component tokens listed in Public Token API: --ren-empty-padding, --ren-empty-gap, --ren-empty-icon-size, --ren-empty-title-size, --ren-empty-desc-color, --ren-empty-max-width.\"\n    - \"Semantic tokens consumed by selectors: --color-text, --color-text-muted, --color-fill, --color-border.\"\n    - \"Layout / type tokens: --space-*, --radius-full, --radius-lg, --width-prose, --text-xl, --text-3xl, --body-size, --caption-size, --weight-semibold, --leading-normal.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) for the icon background or borders.\"\n    - \"Hardcoded hex / named colors for text, fill, or border.\"\n    - \"Custom widths / margins that bypass --ren-empty-max-width; the component is meant to self-center.\"\n\naccessibility:\n  required:\n    - \"Title is a real heading element (<h2> / <h3>) so the empty state appears in the document outline.\"\n    - \"Decorative icons / emoji use aria-hidden=\\\"true\\\"; meaningful illustrations need alt text or aria-label.\"\n    - \"Primary action is a real <button> or <a> with keyboard activation and a 44px touch target (inherited from .ren-btn).\"\n    - \"Do not communicate the empty state through icon color alone — the title text must remain the source of truth.\"\n    - \"If the empty state is announced after an async load, wrap it in role=\\\"status\\\" or aria-live=\\\"polite\\\" so screen readers learn the new state.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-empty-state/ren-empty-state.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<section class=\"ren-empty-state\"><div class=\"ren-empty-state-icon\" aria-hidden=\"true\">□</div><h2 class=\"ren-empty-state-title\">No projects</h2><p class=\"ren-empty-state-description\">Create a project to get started.</p><div class=\"ren-empty-state-actions\"><button class=\"ren-btn\" type=\"button\">Create project</button></div></section>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-empty-state`\n- `.ren-empty-state-actions`\n- `.ren-empty-state-bordered`\n- `.ren-empty-state-compact`\n- `.ren-empty-state-description`\n- `.ren-empty-state-icon`\n- `.ren-empty-state-title`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-empty-desc-color`\n- `--ren-empty-gap`\n- `--ren-empty-icon-size`\n- `--ren-empty-max-width`\n- `--ren-empty-padding`\n- `--ren-empty-title-size`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/patterns/ren-empty-state/ren-empty-state.css`\n- `docs/components/ren-empty-state.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/patterns/ren-empty-state/pattern.md",
      "id": "contract:pattern:ren-empty-state",
      "type": "contract",
      "name": "ren-empty-state pattern.md"
    },
    {
      "data": {
        "kind": "pattern"
      },
      "body": "# ren-form\n\nForm validation + layout pattern. Wraps a native `<form>` with declarative\nfield rules, error summaries, multi-step support, and emit-on-submit\nevents. Pairs with `ren-field` for control-level wiring.\n\nLoad this file after `ren-design.md` and before generating, editing, or\nreviewing any `<ren-form>` UI.\n\n## Purpose\n\nThe form-level pattern in RenDS. Owns:\n- Declarative validation (`data-rules`) with built-in validators\n  (`required`, `email`, `min`, `max`, `pattern`, `match`, etc.).\n- Validation timing modes (`onSubmit`, `onBlur`, `onChange`, `onTouched`).\n- Error summary management at the top of the form.\n- Multi-step (wizard) progression via `data-steps`.\n- Submitting / disabled state propagation across the form.\n\nField-level ARIA wiring is delegated to `ren-field`. Layout uses\n`ren-stack` / `ren-form-row` / `ren-form-section`.\n\n## Use When\n\n- The page is a real form (login, signup, settings, checkout, multi-step\n  wizard) that needs declarative validation rules.\n- You want a single error summary at the top of the form, focusable on\n  invalid submit.\n- You want consistent submitting state and event-driven submission.\n- You are using `ren-field` to wrap the controls.\n\n## Do Not Use When\n\n- The \"form\" is a single search input — use `<form>` + `ren-field` directly.\n- The \"form\" is a chat composer or freeform editor — use a custom flow.\n- You need fully-custom field validation that does not fit the\n  `data-rules` DSL — use a plain `<form>` with your own JS.\n- The interaction is a step-by-step UI without form fields — use\n  `ren-tabs` or `ren-empty-state` flows.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The UI is a real form with multiple fields and validation.\"\n    - \"You want declarative rules (data-rules=\\\"required|email|min:8\\\").\"\n    - \"You want a single error summary that focuses on invalid submit.\"\n    - \"You want submitting state to disable the form during async submission.\"\n    - \"You want a multi-step wizard with progress indicator.\"\n  avoidWhen:\n    - \"It is a single-input search/filter — use ren-field alone.\"\n    - \"Validation logic is irreducibly custom and the data-rules DSL would not express it.\"\n    - \"The flow is not actually a form (chat composer, command palette, etc.).\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-form/ren-form.css\"\n  js:\n    - \"rends/components/patterns/ren-form/ren-form.js\"\n  notes:\n    - \"Always pair with rends/components/primitives/ren-field/ren-field.css + ren-field.js for field wiring.\"\n\nrequiredMarkup:\n  - \"<ren-form> wraps a real <form>. Do not omit the <form> element.\"\n  - \"Each input lives inside <ren-field>; do not use bare <input> at the top level of the form.\"\n  - \"Add data-validate=\\\"onSubmit|onBlur|onChange|onTouched\\\" on <ren-form>; default is onSubmit.\"\n  - \"If using rules, attach data-rules to each <ren-field> (or to the inner control), not to the <form>.\"\n  - \"Submit button must be type=\\\"submit\\\" inside the <form>.\"\n  - \"Provide an empty .ren-form-error-summary with role=\\\"alert\\\" tabindex=\\\"-1\\\" so the component can populate AND focus it on invalid submit. The component sets tabindex=\\\"-1\\\" automatically if you forget.\"\n\nforbiddenPatterns:\n  - \"<ren-form> wrapping a <div> instead of a <form>.\"\n  - \"Manual aria-invalid / aria-describedby — ren-field owns those.\"\n  - \"Submitting via fetch from a custom click handler that bypasses the ren-submit event.\"\n  - \"Using a placeholder as the only label.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-field-* for input chrome, --ren-btn-* for actions.\"\n    - \"Layout tokens (--space-*) and semantic colors for content.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, etc.).\"\n    - \"Hardcoded colors / sizes that bypass tokens.\"\n\naccessibility:\n  required:\n    - \"Native <form> semantics; preserve the submit event.\"\n    - \"Each control has a <label> via ren-field.\"\n    - \"Error summary has role=\\\"alert\\\" AND tabindex=\\\"-1\\\" so .focus() actually shifts focus on invalid submit. The component sets tabindex=\\\"-1\\\" if missing.\"\n    - \"Required-state must be explicit (required attribute), not placeholder-only.\"\n    - \"Disabled / submitting state cannot strip focus rings.\"\n```\n\n## Required CSS / JS Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-form/ren-form.css\">\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-field/ren-field.css\">\n<script type=\"module\" src=\"rends/components/patterns/ren-form/ren-form.js\"></script>\n<script type=\"module\" src=\"rends/components/primitives/ren-field/ren-field.js\"></script>\n```\n\n## Canonical Markup\n\nSingle-step login form with onTouched validation:\n\n```html\n<ren-form data-validate=\"onTouched\">\n  <form class=\"ren-form ren-stack\" novalidate>\n    <div class=\"ren-form-error-summary\" role=\"alert\" tabindex=\"-1\" hidden>\n      <strong>Please fix the following errors:</strong>\n      <ul></ul>\n    </div>\n\n    <ren-field data-rules=\"required|email\">\n      <label>Email</label>\n      <input class=\"ren-input\" type=\"email\" name=\"email\" autocomplete=\"email\" required>\n      <span data-error></span>\n    </ren-field>\n\n    <ren-field data-rules=\"required|min:8\">\n      <label>Password</label>\n      <input class=\"ren-input\" type=\"password\" name=\"password\" autocomplete=\"current-password\" required minlength=\"8\">\n      <span data-error></span>\n    </ren-field>\n\n    <div class=\"ren-form-actions\">\n      <button class=\"ren-btn ren-btn-full\" type=\"submit\">Sign in</button>\n    </div>\n  </form>\n</ren-form>\n\n<script type=\"module\">\n  document.querySelector('ren-form').addEventListener('ren-submit', (e) => {\n    e.preventDefault();\n    fetch('/api/login', { method: 'POST', body: JSON.stringify(e.detail.values) });\n  });\n</script>\n```\n\nMulti-step form with progress:\n\n```html\n<ren-form data-validate=\"onSubmit\" data-steps=\"3\">\n  <form class=\"ren-form ren-stack\">\n    <ol class=\"ren-form-progress\" aria-label=\"Progress\">\n      <li class=\"ren-form-step\" data-active>\n        <span class=\"ren-form-step-label\">Account</span>\n      </li>\n      <li class=\"ren-form-step\">\n        <span class=\"ren-form-step-label\">Profile</span>\n      </li>\n      <li class=\"ren-form-step\">\n        <span class=\"ren-form-step-label\">Review</span>\n      </li>\n    </ol>\n    <!-- step sections … -->\n  </form>\n</ren-form>\n```\n\n## Attributes, Events, and API\n\n- Host attributes:\n  - `data-validate=\"onSubmit|onBlur|onChange|onTouched\"` (default: `onSubmit`).\n  - `data-steps=\"N\"` enables multi-step mode.\n- Field rules: `data-rules=\"required|email|min:8|max:64|pattern:^[A-Z]+$|match:password\"`.\n- Submitting state: component sets `[data-submitting]` on `<ren-form>` and\n  disables submit buttons while the `ren-submit` consumer is async.\n- Events:\n  - `ren-submit` — `detail.values` (validated form data). Repeated names are arrays.\n    Register async work with `event.detail.waitUntil(promise)`; the\n    `[data-submitting]` state remains until every registered promise settles.\n    The event is cancelable so consumers may call `preventDefault()` when they\n    own submission. A rejected promise emits `ren-submit-error` with\n    `detail.error`.\n  - `ren-invalid` — `detail.errors` (rule failures).\n- Static API: `RenForm.registerValidator(name, fn)` for custom validators.\n  Validators may return a Promise; `validateAsync()` and submit await it.\n  Use `RenForm.registerMessages(locale, messages)` to localize `required`,\n  `email`, `pattern`, `min`, `max`, and `invalid` messages. Set `lang` on\n  `<ren-form>` (or `<html>`) for locale selection. `data-persist=\"key\"`\n  stores/restores values in `localStorage`; call `reset()` to clear it.\n\n## Variants and Public Selectors\n\n| Class                          | Role                                   |\n|--------------------------------|----------------------------------------|\n| `.ren-form`                    | Form root chrome.                      |\n| `.ren-form-section`            | Visually grouped section.              |\n| `.ren-form-section-title`      | Section heading.                       |\n| `.ren-form-row`                | Horizontal row of fields.              |\n| `.ren-form-actions`            | Footer of action buttons.              |\n| `.ren-form-error-summary`      | Top-of-form error summary.             |\n| `.ren-form-success`            | Post-submit confirmation slot.         |\n| `.ren-form-progress`           | Multi-step progress bar.               |\n| `.ren-form-step`               | Single step.                           |\n| `.ren-form-step-label`         | Step label text.                       |\n\n## States\n\n| Selector / attr        | Meaning                                                |\n|------------------------|--------------------------------------------------------|\n| `[data-submitting]`    | Form is processing; submit buttons disabled.           |\n| `[data-has-errors]`    | One or more fields are currently invalid.              |\n| `[data-invalid]`       | A specific field is invalid.                           |\n| `[data-active]`        | Active step in a multi-step form.                      |\n| `[data-completed]`     | Completed step.                                        |\n| `[data-disabled]`      | A step is locked.                                      |\n\n## Public Token API\n\nForm layout uses `ren-stack` / `--space-*`. Field chrome inherits from\n`ren-field`:\n\n- `--ren-field-bg`\n- `--ren-field-border-color`\n- `--ren-field-border-width`\n- `--ren-field-radius`\n- `--ren-field-height`\n- `--ren-field-padding-x`\n- `--ren-field-padding-y`\n- `--ren-field-color`\n- `--ren-field-placeholder`\n- `--ren-field-focus-color`\n- `--ren-field-error-color`\n- `--ren-field-success-color`\n\n## Accessibility Contract\n\n- The form is a real `<form>` element. `ren-form` is an enhancement.\n- Each control lives in `ren-field` (auto-wires label / description / error\n  IDs).\n- The error summary has `role=\"alert\"` and `tabindex=\"-1\"` (the component\n  sets `tabindex=\"-1\"` automatically if it is missing). It receives focus\n  on invalid submit so screen readers and keyboard users land on the\n  error list.\n- `required` is the source of truth for required state; visual indicators\n  pair with it.\n- Submit buttons are real `<button type=\"submit\">`.\n- During submitting state, focus stays on the form (do not yank focus).\n\n## Anti-Patterns\n\n- ❌ `<ren-form><div>…</div></ren-form>` — wrap a real `<form>`.\n- ❌ Bare `<input>` at the top of the form without a `ren-field`.\n- ❌ Bypassing `ren-submit` and POSTing from the click handler.\n- ❌ Hand-writing `aria-invalid` / `aria-describedby` — `ren-field` owns\n  these.\n- ❌ Using placeholder as a label.\n\n## Related Files\n\n- `components/patterns/ren-form/ren-form.css`\n- `components/patterns/ren-form/ren-form.js`\n- `components/primitives/ren-field/component.md`\n- `docs/components/ren-form.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n\n## Test Expectations\n\n- Run component / docs a11y coverage on submit / invalid flows.\n- Run `npm run lint` after token / selector changes.\n- Manually verify keyboard-only submit and error summary focus.\n",
      "path": "components/patterns/ren-form/pattern.md",
      "id": "contract:pattern:ren-form",
      "type": "contract",
      "name": "ren-form pattern.md"
    },
    {
      "data": {
        "kind": "pattern"
      },
      "body": "# ren-menubar Pattern Contract\n\nApplication menubar pattern for top-level menu navigation.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-menubar` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-menubar` pattern.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Menubar pattern behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this pattern.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The UI is a desktop-style application menubar with persistent top-level menus (File / Edit / View / Help).\"\n    - \"You need WAI-ARIA Menubar semantics: role=\\\"menubar\\\", role=\\\"menu\\\", role=\\\"menuitem|menuitemcheckbox|menuitemradio\\\".\"\n    - \"You need keyboard navigation (Arrow keys, Enter, Space, Escape, Home, End) with roving focus and \\\"menubar glide\\\" between triggers.\"\n    - \"You need typeahead character matching for fast item selection.\"\n    - \"You need checkbox / radio menu items (.ren-menubar-checkbox, .ren-menubar-radio) with aria-checked state.\"\n    - \"You need nested submenus with chevron and lateral keyboard navigation.\"\n  avoidWhen:\n    - \"The nav is a horizontal site bar (Home / About / Pricing) — use ren-nav.\"\n    - \"It is a single dropdown / context menu — use ren-menu or ren-popover.\"\n    - \"It is a side rail with sections — use ren-sidebar.\"\n    - \"It is a Ctrl+K searchable launcher — use ren-command.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-menubar/ren-menubar.css\"\n  js:\n    - \"rends/components/patterns/ren-menubar/ren-menubar.js\"\n  notes:\n    - \"JS owns the keyboard model, roving focus, submenu opening, and ren-menubar-select event. CSS-only fallback only renders static chrome.\"\n    - \"Custom element registers as <ren-menubar>; menus are toggled via the [hidden] attribute on .ren-menubar-menu.\"\n\nrequiredMarkup:\n  - \"Root is <ren-menubar> wrapping <div class=\\\"ren-menubar\\\" role=\\\"menubar\\\">.\"\n  - \"Each top-level menu uses <button class=\\\"ren-menubar-trigger\\\" aria-haspopup=\\\"menu\\\" aria-expanded=\\\"false\\\"> + a sibling <div class=\\\"ren-menubar-menu\\\" role=\\\"menu\\\" hidden>.\"\n  - \"Each item is a <button class=\\\"ren-menubar-item\\\" role=\\\"menuitem\\\"> (or role=\\\"menuitemcheckbox|menuitemradio\\\" for stateful variants).\"\n  - \"Checkbox / radio items carry aria-checked=\\\"true|false\\\"; the ::before pseudo renders the check / dot — do not hand-draw it.\"\n  - \"Separators are <div class=\\\"ren-menubar-separator\\\" role=\\\"separator\\\">; submenu chevrons are added automatically by .ren-menubar-submenu::after.\"\n  - \"Keyboard shortcuts shown via <span class=\\\"ren-menubar-shortcut\\\">⌘N</span>; mirror with a real document-level keydown handler.\"\n\nforbiddenPatterns:\n  - \"Building from <a> tags — items must be <button> so Space activates them.\"\n  - \"Hiding menus with display: none from external code — toggle the [hidden] attribute; the component owns animation states.\"\n  - \"Setting aria-expanded manually from consumer code — the menubar JS manages it on the trigger.\"\n  - \"Wrapping items in <li> without role=\\\"none\\\" parent — keep the menu role tree flat or wrap with role=\\\"none\\\".\"\n  - \"Reimplementing keyboard navigation; the component already handles arrows, typeahead, Home/End, and Escape.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic surface / text tokens: --color-surface, --color-surface-raised, --color-border, --color-separator, --color-text, --color-text-muted, --color-fill-hover, --color-fill-active, --color-accent, --color-on-accent.\"\n    - \"Spacing / radius / shadow tokens: --space-1, --space-2, --space-3, --radius-sm, --radius-md, --shadow-lg.\"\n    - \"Type tokens: --body-size, --label-size, --label-weight, --caption-size.\"\n    - \"Motion tokens: --duration-tactile, --duration-exit, --ease-enter.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / named colors for hover, active, or accent.\"\n    - \"Replacing the focus outline color with anything other than --color-accent.\"\n\naccessibility:\n  required:\n    - \"Container has role=\\\"menubar\\\" and each <button> is a real menuitem with role=\\\"menuitem|menuitemcheckbox|menuitemradio\\\".\"\n    - \"Triggers expose aria-haspopup=\\\"menu\\\" and aria-expanded reflects the open/closed state of the associated menu.\"\n    - \"Roving tabindex: only one focusable item at a time; arrow keys move focus. The component manages this — do not set tabindex manually on every item.\"\n    - \"Checkbox / radio items expose aria-checked; the visual check (::before) must not be the only state cue.\"\n    - \"Disabled items use [data-disabled] AND pointer-events: none (CSS handles the latter).\"\n    - \"Focus-visible outline uses --color-accent — never strip without restoring a visible alternative.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-menubar/ren-menubar.css\">\n<script type=\"module\" src=\"rends/components/patterns/ren-menubar/ren-menubar.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-menubar><div class=\"ren-menubar\" role=\"menubar\"><button class=\"ren-menubar-trigger\" type=\"button\" aria-haspopup=\"menu\" aria-expanded=\"false\">File</button><div class=\"ren-menubar-menu\" role=\"menu\" hidden><button class=\"ren-menubar-item\" type=\"button\" role=\"menuitem\">New</button></div></div></ren-menubar>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-menubar`\n- `.ren-menubar-checkbox`\n- `.ren-menubar-item`\n- `.ren-menubar-label`\n- `.ren-menubar-menu`\n- `.ren-menubar-radio`\n- `.ren-menubar-separator`\n- `.ren-menubar-shortcut`\n- `.ren-menubar-submenu`\n- `.ren-menubar-trigger`\n\n## States And Attributes\n\n- `[aria-checked]`\n- `[aria-expanded]`\n- `[data-disabled]`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/patterns/ren-menubar/ren-menubar.css`\n- `components/patterns/ren-menubar/ren-menubar.js`\n- `docs/components/ren-menubar.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/patterns/ren-menubar/pattern.md",
      "id": "contract:pattern:ren-menubar",
      "type": "contract",
      "name": "ren-menubar pattern.md"
    },
    {
      "data": {
        "kind": "pattern"
      },
      "body": "# ren-nav Pattern Contract\n\nNavigation pattern for responsive site/app nav and active states.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-nav` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-nav` pattern.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Nav pattern behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this pattern.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The product needs a horizontal site / app top navigation bar (brand + links + actions).\"\n    - \"The nav must collapse to a hamburger menu below 48rem (768px) with an animated <span>-stripe toggle.\"\n    - \"Active route should be reflected via aria-current=\\\"page\\\" (or the .active class) on a .ren-nav-link.\"\n    - \"You need optional sticky-on-scroll behavior with a translucent backdrop blur (.ren-nav-sticky).\"\n    - \"You need a transparent variant for hero sections (.ren-nav-transparent).\"\n    - \"You need top-level dropdown menus inside the nav using the native [popover] API (.ren-nav-dropdown).\"\n  avoidWhen:\n    - \"The nav is a persistent left/right rail — use ren-sidebar.\"\n    - \"The UI is a desktop-style application menubar (File / Edit / View) — use ren-menubar.\"\n    - \"The disclosure is a one-off dropdown unrelated to site nav — use ren-menu or ren-popover.\"\n    - \"The nav lives inside a card or pattern as secondary tabs — use ren-tabs.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-nav/ren-nav.css\"\n  js:\n    - \"rends/components/patterns/ren-nav/ren-nav.js\"\n  notes:\n    - \"JS registers <ren-nav> and owns the mobile toggle, Escape-to-close, resize handler, and popover-based dropdowns.\"\n    - \"CSS-only fallback renders the desktop layout; mobile menu requires JS to toggle [aria-expanded] on .ren-nav-toggle.\"\n\nrequiredMarkup:\n  - \"Wrap the bar in <ren-nav> containing a <nav class=\\\"ren-nav\\\" aria-label=\\\"…\\\">.\"\n  - \"Brand block uses <a class=\\\"ren-nav-brand\\\" href=\\\"/\\\"> with a logo + product name; it sits at the inline-start.\"\n  - \"Links live in <ul class=\\\"ren-nav-links\\\"> of <li><a class=\\\"ren-nav-link\\\" href=\\\"…\\\"></a></li>; active item adds aria-current=\\\"page\\\".\"\n  - \"Mobile hamburger is <button class=\\\"ren-nav-toggle\\\" aria-expanded=\\\"false\\\" aria-controls=\\\"…\\\" aria-label=\\\"Toggle menu\\\"> with three <span> stripes inside.\"\n  - \"Action area (CTA buttons, theme toggle, sign-in) goes in <div class=\\\"ren-nav-actions\\\"> which floats to the inline-end via margin-inline-start: auto.\"\n  - \"Dropdowns use .ren-nav-dropdown wrapping a trigger <a class=\\\"ren-nav-link\\\" aria-expanded=\\\"false\\\" popovertarget=\\\"…\\\"> and a [popover] panel.\"\n\nforbiddenPatterns:\n  - \"Using <div> stacks instead of <nav> + <ul>/<li> — landmarks and list semantics matter.\"\n  - \"Manually toggling display: none on .ren-nav-links for mobile — set [data-open] on .ren-nav or aria-expanded on .ren-nav-toggle.\"\n  - \"Styling links with ren-btn — sidebar / nav items own their own chrome via .ren-nav-link.\"\n  - \"Replacing the hamburger with a custom toggle that lacks aria-expanded / aria-controls.\"\n  - \"Hardcoding the sticky backdrop color — the .ren-nav-sticky variant already uses color-mix on --color-surface.\"\n\ntokenPolicy:\n  allowed:\n    - \"Public component tokens listed in Public Token API: --ren-nav-bg, --ren-nav-border, --ren-nav-gap, --ren-nav-height, --ren-nav-link-active, --ren-nav-link-color, --ren-nav-padding-x.\"\n    - \"Semantic tokens consumed by selectors: --color-surface, --color-border, --color-text, --color-text-muted, --color-accent, --color-accent-subtle, --color-fill (for sticky color-mix).\"\n    - \"Spacing / radius tokens: --ren-space-1, --ren-space-2, --ren-space-3, --ren-space-4, --ren-title-sm-size, --ren-label-size.\"\n    - \"Motion tokens: --duration-overlay, --duration-state, --duration-route, --duration-enter, --ease-state-change, --ease-enter.\"\n    - \"Z-index token: --ren-z-sticky.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides.\"\n    - \"Hardcoded hex / named colors for bg, border, active, or hover.\"\n    - \"Custom z-index values that bypass --ren-z-sticky for sticky mode.\"\n\naccessibility:\n  required:\n    - \"Root is a real <nav> with aria-label (e.g. \\\"Primary\\\") so screen readers can list landmarks.\"\n    - \"Active route uses aria-current=\\\"page\\\" (the .active class is a styling alias, not the source of truth).\"\n    - \".ren-nav-toggle has aria-expanded reflecting menu state, aria-controls pointing to the links list, and an aria-label (icon-only is not a name).\"\n    - \"Mobile menu closes on Escape and on link activation (the component owns both behaviors).\"\n    - \"Dropdown triggers expose aria-expanded; popover panels are real [popover] elements so the platform manages focus return.\"\n    - \"Focus-visible outlines use --color-accent at 2px offset — do not remove without a visible alternative.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-nav/ren-nav.css\">\n<script type=\"module\" src=\"rends/components/patterns/ren-nav/ren-nav.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-nav><nav class=\"ren-nav\" aria-label=\"Primary\"><a class=\"ren-nav-brand\" href=\"/\">RenDS</a><ul id=\"primary-links\" class=\"ren-nav-links\"><li><a class=\"ren-nav-link\" href=\"/docs\" aria-current=\"page\">Docs</a></li></ul><button class=\"ren-nav-toggle\" type=\"button\" aria-expanded=\"false\" aria-controls=\"primary-links\" aria-label=\"Toggle menu\"><span></span><span></span><span></span></button></nav></ren-nav>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-nav`\n- `.ren-nav-actions`\n- `.ren-nav-brand`\n- `.ren-nav-dropdown`\n- `.ren-nav-link`\n- `.ren-nav-links`\n- `.ren-nav-sticky`\n- `.ren-nav-toggle`\n- `.ren-nav-transparent`\n\n## States And Attributes\n\n- `[aria-current]`\n- `[aria-expanded]`\n- `[data-dropdown]`\n- `[data-open]`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-nav-bg`\n- `--ren-nav-border`\n- `--ren-nav-gap`\n- `--ren-nav-height`\n- `--ren-nav-link-active`\n- `--ren-nav-link-color`\n- `--ren-nav-padding-x`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/patterns/ren-nav/ren-nav.css`\n- `components/patterns/ren-nav/ren-nav.js`\n- `docs/components/ren-nav.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/patterns/ren-nav/pattern.md",
      "id": "contract:pattern:ren-nav",
      "type": "contract",
      "name": "ren-nav pattern.md"
    },
    {
      "data": {
        "kind": "pattern"
      },
      "body": "# ren-sidebar\n\nApplication sidebar pattern: persistent left/right navigation with desktop\ncollapse, mobile overlay, active-item state, and keyboard accessibility.\nPairs with a main content slot to form an app shell layout.\n\nLoad this file after `ren-design.md` and before generating, editing, or\nreviewing any `<ren-sidebar>` UI.\n\n## Purpose\n\nThe dashboard / app-shell sidebar pattern in RenDS. Owns:\n- Persistent layout with `.ren-sidebar-layout` (sidebar + main content).\n- Desktop collapsed / expanded states (`[data-collapsed]`).\n- Mobile overlay state (`[data-open]`) with backdrop and Escape / outside\n  click dismissal.\n- Active item state via `aria-current=\"page\"` plus `.active`.\n- Persistence of collapse state in `localStorage`.\n- Toggle button + keyboard support.\n\nSidebar items are real `<a>` (for routes) or `<button>` (for actions); they\nare *not* `ren-link` / `ren-btn` — sidebar styling owns their chrome.\n\n## Use When\n\n- The product has persistent left/right navigation across multiple pages.\n- The page is an app shell with sidebar + main layout (`.ren-sidebar-layout`).\n- The sidebar must collapse on desktop and overlay on mobile.\n- Persistence of collapse state across sessions matters.\n\n## Do Not Use When\n\n- The navigation is a top horizontal bar — use `ren-nav`.\n- The navigation is a one-off menu inside another component — use\n  `ren-menu` / `ren-menubar`.\n- The sidebar is a transient panel — use `ren-sheet`.\n- The sidebar is a single floating button toolbar — use `ren-toolbar`.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The product needs persistent navigation across many pages.\"\n    - \"Desktop should support a collapsed (icon-only) and expanded state.\"\n    - \"Mobile should overlay with backdrop dismissal.\"\n    - \"Active route should be reflected via aria-current=\\\"page\\\".\"\n    - \"Collapse state should persist in localStorage.\"\n  avoidWhen:\n    - \"The navigation is a horizontal top bar — use ren-nav.\"\n    - \"The disclosure is transient or context-specific — use ren-sheet / ren-menu.\"\n    - \"The page does not have a multi-section nav.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-sidebar/ren-sidebar.css\"\n  js:\n    - \"rends/components/patterns/ren-sidebar/ren-sidebar.js\"\n  notes:\n    - \"JS handles toggle, persistence, and overlay dismissal. CSS-only fallback only renders the expanded desktop state.\"\n\nrequiredMarkup:\n  - \"Wrap the app shell in <div class=\\\"ren-sidebar-layout\\\">.\"\n  - \"<ren-sidebar> is the sidebar root; main content goes in a sibling <main class=\\\"ren-sidebar-main\\\">.\"\n  - \"Wrap items in <nav class=\\\"ren-sidebar-nav\\\"> with <a class=\\\"ren-sidebar-item\\\"> children.\"\n  - \"Use aria-current=\\\"page\\\" on the active item; the .active class is for styling parity.\"\n  - \"Provide an aria-label on <ren-sidebar> when multiple navigation landmarks exist on the page.\"\n  - \"Toggle button uses .ren-sidebar-toggle and aria-label (the icon alone is not a name).\"\n\nforbiddenPatterns:\n  - \"Wrapping non-nav content in <nav class=\\\"ren-sidebar-nav\\\">.\"\n  - \"Using ren-btn / ren-link as sidebar items — use .ren-sidebar-item directly.\"\n  - \"Hardcoded sidebar widths in inline styles; use --ren-sidebar-width.\"\n  - \"Manually toggling display: none for the mobile overlay; use the [data-open] state.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: every --ren-sidebar-* listed in Public Token API.\"\n    - \"Semantic tokens for content (--color-text, --color-text-muted, etc.).\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, etc.) in consumer code.\"\n    - \"Hardcoded shadow / overlay colors that bypass tokens.\"\n\naccessibility:\n  required:\n    - \"Wrap items in a real <nav> landmark.\"\n    - \"Active item uses aria-current=\\\"page\\\".\"\n    - \"Toggle button has aria-label and toggles aria-expanded if it controls disclosure.\"\n    - \"Collapsed icon-only items must keep accessible names from their link text (do not hide it from AT).\"\n    - \"Mobile overlay closes on Escape and outside click.\"\n```\n\n## Required CSS / JS Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-sidebar/ren-sidebar.css\">\n<script type=\"module\" src=\"rends/components/patterns/ren-sidebar/ren-sidebar.js\"></script>\n```\n\n## Canonical Markup\n\nApp shell with sidebar + main:\n\n```html\n<div class=\"ren-sidebar-layout\">\n  <ren-sidebar class=\"ren-sidebar\" aria-label=\"Primary\">\n    <div class=\"ren-sidebar-header\">\n      <a class=\"ren-sidebar-item\" href=\"/\" aria-label=\"Home\">\n        <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">⌂</span>\n        <span class=\"ren-sidebar-item-text\">Acme</span>\n      </a>\n    </div>\n\n    <nav class=\"ren-sidebar-nav\" aria-label=\"Main\">\n      <a class=\"ren-sidebar-item active\" href=\"/dashboard\" aria-current=\"page\">\n        <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">📊</span>\n        <span class=\"ren-sidebar-item-text\">Dashboard</span>\n      </a>\n      <a class=\"ren-sidebar-item\" href=\"/projects\">\n        <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">📁</span>\n        <span class=\"ren-sidebar-item-text\">Projects</span>\n      </a>\n    </nav>\n\n    <div class=\"ren-sidebar-section\">\n      <div class=\"ren-sidebar-section-label\">Account</div>\n      <a class=\"ren-sidebar-item\" href=\"/settings\">\n        <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">⚙</span>\n        <span class=\"ren-sidebar-item-text\">Settings</span>\n      </a>\n    </div>\n\n    <button class=\"ren-sidebar-toggle\" type=\"button\" aria-label=\"Toggle sidebar\"></button>\n  </ren-sidebar>\n\n  <main class=\"ren-sidebar-main\">\n    <!-- page content -->\n  </main>\n</div>\n```\n\nProgrammatic API:\n\n```js\nconst sidebar = document.querySelector('ren-sidebar');\nsidebar.toggleMenu();\nsidebar.setActiveItem('/projects');\nsidebar.addEventListener('ren-sidebar-toggle', (e) => console.log(e.detail.collapsed));\n```\n\n## Attributes, Events, and API\n\n- Host states:\n  - `[data-collapsed]` — desktop collapsed (icon-only).\n  - `[data-open]` — mobile overlay open.\n- Active item: `.active` class plus `aria-current=\"page\"` on the\n  current `.ren-sidebar-item`.\n- Methods: `toggleMenu()`, `openMobileMenu()`, `closeMobileMenu()`,\n  `setActiveItem(href)`.\n- Getters: `isCollapsed`, `isMobileOpen`.\n- Event: `ren-sidebar-toggle` (`detail.collapsed`).\n- Persistence: collapse state stored in `localStorage` under\n  `ren-sidebar-collapsed`.\n\n## Variants and Public Selectors\n\n| Class                            | Role                                |\n|----------------------------------|-------------------------------------|\n| `.ren-sidebar`                   | Sidebar root chrome.                |\n| `.ren-sidebar-layout`            | Layout wrapper (sidebar + main).    |\n| `.ren-sidebar-main`              | Main-content slot.                  |\n| `.ren-sidebar-header`            | Header slot.                        |\n| `.ren-sidebar-content`           | Scrollable middle slot.             |\n| `.ren-sidebar-footer`            | Footer slot.                        |\n| `.ren-sidebar-nav`               | `<nav>` wrapper.                    |\n| `.ren-sidebar-section`           | Grouped section.                    |\n| `.ren-sidebar-section-label`     | Section heading text.               |\n| `.ren-sidebar-item`              | Single nav item.                    |\n| `.ren-sidebar-item-icon`         | Icon slot inside an item.           |\n| `.ren-sidebar-item-text`         | Text slot inside an item.           |\n| `.ren-sidebar-divider`           | Visual divider between sections.    |\n| `.ren-sidebar-toggle`            | Collapse button.                    |\n| `.ren-sidebar-overlay`           | Mobile backdrop.                    |\n\n## States\n\n| Selector / attr        | Meaning                                |\n|------------------------|----------------------------------------|\n| `[aria-current=\"page\"]`| Active route on the current item.      |\n| `[aria-disabled]`      | Disabled item (still focusable).       |\n| `[data-collapsed]`     | Desktop collapsed (icon-only) state.   |\n| `[data-open]`          | Mobile overlay open.                   |\n| `:focus-visible`       | Keyboard focus on an item or toggle.   |\n\n## Public Token API\n\n- `--ren-sidebar-bg`\n- `--ren-sidebar-border`\n- `--ren-sidebar-width`\n- `--ren-sidebar-collapsed-width`\n- `--ren-sidebar-item-height`\n- `--ren-sidebar-item-padding`\n- `--ren-sidebar-item-radius`\n- `--ren-sidebar-active-bg`\n- `--ren-sidebar-active-color`\n- `--ren-sidebar-duration`\n- `--ren-sidebar-easing`\n\n## Accessibility Contract\n\n- Wrap items in a real `<nav>` landmark and label the sidebar with\n  `aria-label` when multiple nav landmarks exist on the page.\n- The current route uses `aria-current=\"page\"`.\n- Collapsed icon-only items must keep accessible names — do not hide the\n  text label from screen readers.\n- The toggle button has an `aria-label` (`\"Toggle sidebar\"`).\n- Mobile overlay closes on Escape and outside click.\n- Persistence respects user choice; do not auto-collapse on every visit.\n\n## Anti-Patterns\n\n- ❌ Wrapping a `<nav>` of non-RenDS items inside `.ren-sidebar-nav`.\n- ❌ Replacing items with `ren-btn` / `ren-link` and re-styling.\n- ❌ `<ren-sidebar style=\"width:240px\">` — set `--ren-sidebar-width` on a\n  parent scope.\n- ❌ Using `display: none` to hide the sidebar on mobile — toggle the\n  `[data-open]` state instead.\n- ❌ Decorative-icon-only items without text labels.\n\n## Related Files\n\n- `components/patterns/ren-sidebar/ren-sidebar.css`\n- `components/patterns/ren-sidebar/ren-sidebar.js`\n- `docs/components/ren-sidebar.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n\n## Test Expectations\n\n- Run component / docs a11y coverage on collapse / mobile overlay flows.\n- Run `npm run lint` after token / selector changes.\n- Manually verify keyboard navigation: Tab through items, Escape closes\n  the mobile overlay.\n",
      "path": "components/patterns/ren-sidebar/pattern.md",
      "id": "contract:pattern:ren-sidebar",
      "type": "contract",
      "name": "ren-sidebar pattern.md"
    },
    {
      "data": {
        "kind": "pattern"
      },
      "body": "# ren-table Pattern Contract\n\nData table pattern with sorting, row state, selection, and dense scanning.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-table` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-table` pattern.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Table pattern behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this pattern.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The UI is a tabular dataset with rows + columns that needs sortable headers (.ren-th-sortable + data-sort).\"\n    - \"You need row selection (multi-select with shift-click range) using a leading <input type=\\\"checkbox\\\"> in .ren-table-select cells.\"\n    - \"You need client-side pagination (.ren-table-pagination + data-page-prev/data-page-next/data-page-size-select buttons).\"\n    - \"You need search / filter via a [data-table-search] input inside .ren-table-toolbar.\"\n    - \"You need density variants (.ren-table-compact, .ren-table-comfortable) or sticky / pinned headers and columns.\"\n    - \"You need integrated empty state (.ren-table-empty) and loading shimmer ([data-loading] + .ren-table-loading-overlay).\"\n  avoidWhen:\n    - \"The data is a list of cards / tiles — use ren-card grid, not a table.\"\n    - \"There are only 2-3 key/value pairs per item — use ren-description-list.\"\n    - \"It is a tree / hierarchical view — use ren-tree.\"\n    - \"It is a single read-only stat — use ren-stat / ren-metric.\"\n    - \"The dataset is bigger than what fits in memory — pair with server-side pagination, not the built-in client pager.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/patterns/ren-table/ren-table.css\"\n  js:\n    - \"rends/components/patterns/ren-table/ren-table.js\"\n  notes:\n    - \"JS registers <ren-table> and owns sorting, selection, pagination, keyboard nav, column resize, and search filter.\"\n    - \"Use real <table>, <thead>, <tbody>, <tr>, <th>, <td> — the JS hooks the standard elements; do not replace with divs.\"\n\nrequiredMarkup:\n  - \"Root is <ren-table data-page-size=\\\"…\\\"> wrapping a <div class=\\\"ren-table-wrapper\\\"> > <table class=\\\"ren-table\\\">.\"\n  - \"Header row uses <thead class=\\\"ren-table-header\\\"><tr><th class=\\\"ren-th\\\">…</th></tr></thead>; sortable headers add .ren-th-sortable + data-column=\\\"<key>\\\".\"\n  - \"Body uses <tbody class=\\\"ren-table-body\\\">; its rows are <tr class=\\\"ren-tr\\\" data-row-id=\\\"…\\\"> with <td class=\\\"ren-td\\\"> cells. Selection uses class .ren-table-select on both th/td and a real <input type=\\\"checkbox\\\" aria-label=\\\"Select row\\\">.\"\n  - \"Pagination chrome goes in <div class=\\\"ren-table-pagination\\\"> with .ren-table-pagination-info + .ren-table-pagination-controls containing buttons carrying [data-page-prev] / [data-page-next] / [data-page-size-select].\"\n  - \"Toolbar lives in <div class=\\\"ren-table-toolbar\\\"> and the search box must include the [data-table-search] attribute so the JS wires filtering.\"\n  - \"Empty state is <div class=\\\"ren-table-empty\\\"> with .ren-table-empty-icon / .ren-table-empty-title / .ren-table-empty-description (or reuse the ren-empty-state pattern).\"\n\nforbiddenPatterns:\n  - \"Replacing <table> with <div role=\\\"table\\\"> — kills native keyboard / screen-reader support.\"\n  - \"Implementing sort by clicking a non-th element — sort affordance must live on <th class=\\\"ren-th-sortable\\\">.\"\n  - \"Setting [data-sort] manually from consumer code — the component owns asc/desc cycling.\"\n  - \"Custom checkbox UI inside .ren-table-select — keep the native <input type=\\\"checkbox\\\"> (accent-color is themed via --color-accent).\"\n  - \"Toggling rows with display: none for filtering — let the JS manage row visibility through its filter pipeline.\"\n  - \"Hardcoding pinned column offsets — use .ren-th-pinned / .ren-td-pinned which already set sticky positioning.\"\n\ntokenPolicy:\n  allowed:\n    - \"Public component tokens listed in Public Token API: --ren-table-border-color, --ren-table-cell-padding, --ren-table-header-bg, --ren-table-header-color, --ren-table-header-size, --ren-table-header-weight, --ren-table-hover-bg, --ren-table-row-height, --ren-table-selected-bg, --ren-table-stripe-bg.\"\n    - \"Semantic tokens consumed by selectors: --color-surface, --color-surface-raised, --color-border, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-accent, --color-on-accent, --color-accent-subtle.\"\n    - \"Spacing / radius / type tokens: --space-2, --space-3, --space-4, --space-10, --radius-sm, --radius-md, --stroke-1, --body-size, --label-size, --label-weight, --caption-size, --touch-min.\"\n    - \"Motion tokens: --duration-tactile, --duration-state, --ease-enter.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) for stripes, borders, or selection.\"\n    - \"Hardcoded hex / named colors for hover, selected, or accent.\"\n    - \"Inline transition values — use --duration-* / --ease-* tokens.\"\n\naccessibility:\n  required:\n    - \"Use a real <table> with <thead> / <tbody>; sortable <th> elements set aria-sort=\\\"ascending|descending|none\\\" mirroring [data-sort].\"\n    - \"Selection checkboxes are real <input type=\\\"checkbox\\\"> with aria-label (\\\"Select all\\\" / \\\"Select row N\\\"); rows reflect state via aria-selected=\\\"true\\\" on <tr>.\"\n    - \"Pagination buttons are real <button> with min-width/height of 44px (--touch-min); disabled state uses the disabled attribute, not opacity alone.\"\n    - \"Loading state sets [data-loading] on <ren-table> and exposes pointer-events: none on rows; pair with an aria-live announcement so SR users hear \\\"Loading…\\\" / \\\"Loaded N rows\\\".\"\n    - \"Empty state must include a visible .ren-table-empty-title text; do not rely on icon alone.\"\n    - \"Focus rings on <th>, <td>, .ren-tr (:focus-within), and pagination buttons all use --color-accent — never strip without restoring a visible alternative.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/patterns/ren-table/ren-table.css\">\n<script type=\"module\" src=\"rends/components/patterns/ren-table/ren-table.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-table data-page-size=\"10\"><div class=\"ren-table-wrapper\"><table class=\"ren-table\"><thead class=\"ren-table-header\"><tr><th class=\"ren-th ren-th-sortable\" data-column=\"name\">Name</th></tr></thead><tbody class=\"ren-table-body\"><tr class=\"ren-tr\" data-row-id=\"1\"><td class=\"ren-td\">Jane Doe</td></tr></tbody></table></div></ren-table>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-table`\n- `.ren-table-body`\n- `.ren-table-comfortable`\n- `.ren-table-compact`\n- `.ren-table-empty`\n- `.ren-table-empty-description`\n- `.ren-table-empty-icon`\n- `.ren-table-empty-title`\n- `.ren-table-header`\n- `.ren-table-loading-overlay`\n- `.ren-table-pagination`\n- `.ren-table-pagination-button`\n- `.ren-table-pagination-controls`\n- `.ren-table-pagination-info`\n- `.ren-table-select`\n- `.ren-table-toolbar`\n- `.ren-table-toolbar-search`\n- `.ren-table-wrapper`\n- `.ren-td`\n- `.ren-td-pinned`\n- `.ren-th`\n- `.ren-th-pinned`\n- `.ren-th-resize`\n- `.ren-th-sortable`\n- `...and 1 more in the source files.`\n\n## States And Attributes\n\n- `[aria-selected]`\n- `[data-loading]`\n- `[data-page-next]`\n- `[data-page-prev]`\n- `[data-page-size-select]`\n- `[data-sort]`\n- `[data-table-search]`\n- `:active`\n- `:checked`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-table-border-color`\n- `--ren-table-cell-padding`\n- `--ren-table-header-bg`\n- `--ren-table-header-color`\n- `--ren-table-header-size`\n- `--ren-table-header-weight`\n- `--ren-table-hover-bg`\n- `--ren-table-row-height`\n- `--ren-table-selected-bg`\n- `--ren-table-stripe-bg`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Events\n\n- `ren-sort` — `{ column, direction }` after the sort direction changes.\n- `ren-select` — `{ selected }` after row selection changes or is cleared.\n- `ren-filter` — `{ value }` after the toolbar search filter changes.\n\nAll three events bubble and are composed; they are not cancelable.\n\n## Related Files\n\n- `components/patterns/ren-table/ren-table.css`\n- `components/patterns/ren-table/ren-table.js`\n- `docs/components/ren-table.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/patterns/ren-table/pattern.md",
      "id": "contract:pattern:ren-table",
      "type": "contract",
      "name": "ren-table pattern.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-avatar Component Contract\n\nUser identity primitive for images, initials, and presence indicators.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-avatar` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-avatar` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Avatar primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need to represent a user or entity identity via image, initials, or fallback glyph.\"\n    - \"You need a presence / status dot attached to the identity (online, away, busy, offline).\"\n    - \"You need a stack of overlapping identities (collaborators, attendees) via .ren-avatar-group.\"\n    - \"You need consistent circular (or .ren-avatar-square) framing across sizes xs / sm / md / lg / xl / 2xl.\"\n    - \"The image must crop to a fixed square with object-fit: cover regardless of source aspect ratio.\"\n  avoidWhen:\n    - \"You only need a status label or count without identity — use ren-badge / ren-badge-dot.\"\n    - \"You need a clickable identity menu / profile dropdown — wrap the avatar in ren-menu / ren-popover.\"\n    - \"You need a decorative logo or product icon — use a plain <img> or ren-icon.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-avatar/ren-avatar.css\"\n  notes:\n    - \"CSS-only primitive — no colocated JS exists. Do not import a ren-avatar.js.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Root is <div class=\\\"ren-avatar\\\"> containing either an <img> or text initials (max 2 chars); never both as siblings.\"\n  - \"<img> children must have alt=\\\"\\\" when the avatar is decorative, or alt=\\\"Name\\\" when it conveys identity.\"\n  - \"Status dot must be wrapped together with the avatar in <div class=\\\"ren-avatar-wrapper\\\"> with .ren-avatar-status as a sibling, not a child of .ren-avatar.\"\n  - \"Avatar groups use <div class=\\\"ren-avatar-group\\\"> with .ren-avatar children only (overlap is owned by the group, not by margins on items).\"\n  - \"Size modifiers (.ren-avatar-xs / -sm / -lg / -xl / -2xl) replace the default md size; do not stack two size classes.\"\n\nforbiddenPatterns:\n  - \"Background <img> via CSS background-image — always use a real <img> child for alt-text and lazy-loading.\"\n  - \"Hardcoded width / height in inline styles — use a size modifier or override --ren-avatar-size.\"\n  - \"Communicating presence with .ren-avatar-status alone without a visually-hidden text label (color is not a name).\"\n  - \"Nesting .ren-avatar inside .ren-avatar (e.g., avatar-on-avatar) — group overlap is the documented pattern.\"\n  - \"Custom border-radius via inline style — use .ren-avatar-square or override --ren-avatar-radius.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-avatar-bg, --ren-avatar-border, --ren-avatar-color, --ren-avatar-font-size, --ren-avatar-radius, --ren-avatar-size.\"\n    - \"Semantic tokens: --color-fill, --color-text-secondary, --color-surface, --color-success, --color-warning, --color-danger.\"\n    - \"Size aliases: --avatar-xs / -sm / -md / -lg / -xl / -2xl and --radius-full / --radius-md.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors for background, border, or status dot.\"\n    - \"Bypassing --color-surface for the status ring; the white/dark ring must adapt to theme.\"\n\naccessibility:\n  required:\n    - \"Provide a meaningful alt attribute on the <img> when the avatar identifies a specific person; use alt=\\\"\\\" only when the name is rendered elsewhere.\"\n    - \"Initials avatars must have an accessible name via aria-label on the .ren-avatar or visually-hidden text matching the user's full name.\"\n    - \"Status indicator must be accompanied by a textual label for screen readers (e.g., visually-hidden \\\"Online\\\") — color alone is not sufficient.\"\n    - \"Avatar groups should expose a roll-up count or list label (e.g., aria-label=\\\"3 collaborators\\\") rather than relying on visual stacking.\"\n    - \"Do not attach click handlers to a non-interactive .ren-avatar; wrap in a real <button> or <a> if the identity is actionable.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-avatar/ren-avatar.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div class=\"ren-avatar\"><img src=\"/avatars/jane.jpg\" alt=\"Jane Doe\"></div>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-avatar`\n- `.ren-avatar-2xl`\n- `.ren-avatar-group`\n- `.ren-avatar-lg`\n- `.ren-avatar-sm`\n- `.ren-avatar-square`\n- `.ren-avatar-status`\n- `.ren-avatar-status-away`\n- `.ren-avatar-status-busy`\n- `.ren-avatar-status-offline`\n- `.ren-avatar-wrapper`\n- `.ren-avatar-xl`\n- `.ren-avatar-xs`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-avatar-bg`\n- `--ren-avatar-border`\n- `--ren-avatar-color`\n- `--ren-avatar-font-size`\n- `--ren-avatar-radius`\n- `--ren-avatar-size`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-avatar/ren-avatar.css`\n- `docs/components/ren-avatar.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-avatar/component.md",
      "id": "contract:primitive:ren-avatar",
      "type": "contract",
      "name": "ren-avatar component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-badge Component Contract\n\nSmall status, category, count, or state label.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-badge` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-badge` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Badge primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a small inline label for status (success / warning / danger / info), category, count, or tag.\"\n    - \"The label is decorative or status-only; it does not navigate or trigger a side effect.\"\n    - \"You need a presence dot without text (.ren-badge-dot) — color-coded indicator.\"\n    - \"You need a pill-shaped (radius-full) label sized in em so it scales with surrounding text.\"\n    - \"The label must sit inline inside a heading, list item, button, or table cell.\"\n  avoidWhen:\n    - \"The label is a removable chip with an X button — use ren-tag.\"\n    - \"The label is a persistent inline message with title/icon/dismiss — use ren-banner.\"\n    - \"The label triggers a click action — use ren-btn (or .ren-btn-sm) instead.\"\n    - \"You need an avatar / identity surface — use ren-avatar.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-badge/ren-badge.css\"\n  notes:\n    - \"CSS-only primitive — no colocated JS exists. Do not import a ren-badge.js.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Use <span class=\\\"ren-badge\\\"> (inline) — not <div> — so badges flow with surrounding text.\"\n  - \"Combine .ren-badge with a single variant modifier (.ren-badge-primary | -secondary | -success | -warning | -danger | -info | -outline); do not stack two variants.\"\n  - \"Status badges that convey meaning must include text (or visually-hidden text) — color alone is not sufficient.\"\n  - \"Dot indicators use <span class=\\\"ren-badge-dot ren-badge-dot-success\\\"> (empty content) and require a sibling text label.\"\n  - \"Numeric count badges should set aria-label (e.g., aria-label=\\\"3 unread\\\") when the number is ambiguous without context.\"\n\nforbiddenPatterns:\n  - \"<button class=\\\"ren-badge\\\"> — badges are not interactive; use ren-btn for actions.\"\n  - \"Hardcoded background / color overrides via inline style — override --ren-badge-bg / --ren-badge-color instead.\"\n  - \"Using .ren-badge as a removable chip with a close button — that is ren-tag's contract.\"\n  - \"Color-only status (.ren-badge-danger with no text and no aria-label).\"\n  - \"Setting font-size in px on .ren-badge — the em-based padding will desynchronize from the text scale.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-badge-bg, --ren-badge-color, --ren-badge-font-size, --ren-badge-font-weight, --ren-badge-height, --ren-badge-padding-x, --ren-badge-radius.\"\n    - \"Semantic tokens: --color-fill, --color-fill-hover, --color-accent, --color-on-accent, --color-success-subtle / -strong, --color-warning-subtle / -strong, --color-danger-subtle / -strong, --color-info-subtle / -strong, --color-border.\"\n    - \"Layout tokens: --space-1, --radius-full, --stroke-1, --text-xs, --weight-semibold.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors for badge background, text, or dot color.\"\n    - \"Reaching past the semantic -subtle / -strong pair into raw success / warning / danger primitives.\"\n\naccessibility:\n  required:\n    - \"Badges that communicate status must not rely on color alone — include text or visually-hidden labels.\"\n    - \"Numeric badges need aria-label when the meaning is ambiguous (e.g., \\\"3 unread messages\\\").\"\n    - \"Decorative dot badges (.ren-badge-dot) should be paired with adjacent text content — not used alone.\"\n    - \"Do not place a badge inside a focusable element without ensuring the badge itself is not announced twice; rely on the parent's accessible name.\"\n    - \"Maintain WCAG AA contrast: variants pair -subtle background with -strong text by design; do not invert.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-badge/ren-badge.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<span class=\"ren-badge ren-badge-success\">Published</span>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-badge`\n- `.ren-badge-danger`\n- `.ren-badge-dot`\n- `.ren-badge-dot-danger`\n- `.ren-badge-dot-success`\n- `.ren-badge-dot-warning`\n- `.ren-badge-info`\n- `.ren-badge-outline`\n- `.ren-badge-primary`\n- `.ren-badge-secondary`\n- `.ren-badge-success`\n- `.ren-badge-warning`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-badge-bg`\n- `--ren-badge-color`\n- `--ren-badge-font-size`\n- `--ren-badge-font-weight`\n- `--ren-badge-height`\n- `--ren-badge-padding-x`\n- `--ren-badge-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-badge/ren-badge.css`\n- `docs/components/ren-badge.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-badge/component.md",
      "id": "contract:primitive:ren-badge",
      "type": "contract",
      "name": "ren-badge component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-banner Component Contract\n\nPersistent inline message for status, warning, or alert content.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-banner` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-banner` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Banner primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a persistent inline message that sits in page flow (success / warning / danger / info / neutral).\"\n    - \"The message has structured content: icon + title + message + optional actions or dismiss button.\"\n    - \"You need a page-top full-bleed announcement via .ren-banner-full (no border-radius, no side borders).\"\n    - \"You need a compact single-line variant for dense surfaces (.ren-banner-compact).\"\n    - \"The banner should remain on screen until user dismisses it or the underlying state changes.\"\n  avoidWhen:\n    - \"The message is transient and overlay-style — use ren-toast.\"\n    - \"The message blocks the entire flow and requires confirmation — use ren-dialog (alert).\"\n    - \"You only need a small inline status label — use ren-badge.\"\n    - \"The message is form validation tied to a single field — use ren-field's error slot.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-banner/ren-banner.css\"\n  notes:\n    - \"CSS-only primitive — no colocated JS exists. Dismiss button is plain HTML; if you wire dismissal, set data-dismissing to trigger the documented animation.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Root must carry role=\\\"status\\\" (info / success / neutral) or role=\\\"alert\\\" (danger / urgent warning) so AT announces correctly.\"\n  - \"Use the documented children: <span class=\\\"ren-banner-icon\\\">, <div class=\\\"ren-banner-content\\\"> with <strong class=\\\"ren-banner-title\\\"> + <p class=\\\"ren-banner-message\\\">.\"\n  - \"Dismiss button is <button class=\\\"ren-banner-dismiss\\\" aria-label=\\\"Dismiss\\\"> — never a <div> with click handler.\"\n  - \"Wrap any banner action buttons in <div class=\\\"ren-banner-actions\\\"> placed inside .ren-banner-content.\"\n  - \"To animate dismissal, set [data-dismissing] on the root; do not animate via inline styles.\"\n\nforbiddenPatterns:\n  - \"Using a banner as a transient toast that auto-dismisses without user action — wrong semantics.\"\n  - \"Hardcoded border / background colors per severity — use the .ren-banner-{success | warning | danger | neutral} variant.\"\n  - \"Stacking two severity variants on the same banner (e.g., .ren-banner-success.ren-banner-danger).\"\n  - \"Dismiss button without aria-label or visible text — the ✕ glyph alone is not an accessible name.\"\n  - \"Communicating severity with icon color only; the role attribute and surrounding text must convey severity to AT.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-banner-bg, --ren-banner-border-color, --ren-banner-border-width, --ren-banner-color, --ren-banner-gap, --ren-banner-icon-size, --ren-banner-padding, --ren-banner-radius.\"\n    - \"Semantic tokens: --color-info-subtle, --color-success-subtle, --color-warning-subtle, --color-danger-subtle, --color-surface-sunken, --color-border, --color-text, --color-text-secondary, --color-text-muted, --color-focus-ring.\"\n    - \"Motion tokens: --duration-tactile, --duration-state, --ease-enter.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors or hardcoded rgba border tints — use color-mix on semantic tokens as the CSS already does.\"\n    - \"Inline transition / animation values; use --duration-* / --ease-* tokens.\"\n\naccessibility:\n  required:\n    - \"Set role=\\\"alert\\\" for danger / urgent warning banners and role=\\\"status\\\" for non-urgent info / success / neutral.\"\n    - \"Dismiss button must have aria-label (or visible text) and a visible :focus-visible ring driven by --color-focus-ring.\"\n    - \".ren-banner-title renders as a real heading-like element (<strong>); when stacked among <h*> content, follow document outline.\"\n    - \"Respect prefers-reduced-motion: the [data-dismissing] animation collapses to display:none per the CSS — do not re-introduce motion.\"\n    - \"Banner content must not rely on color alone; the icon + title + message convey severity together.\"\n    - \"Action buttons inside .ren-banner-actions must be real <button> elements with their own keyboard focus order.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-banner/ren-banner.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div class=\"ren-banner ren-banner-info\" role=\"status\">\n  <span class=\"ren-banner-icon\" aria-hidden=\"true\">i</span>\n  <div class=\"ren-banner-content\"><strong class=\"ren-banner-title\">Maintenance</strong><p class=\"ren-banner-message\">Service resumes at 11 PM.</p></div>\n  <button class=\"ren-banner-dismiss\" type=\"button\" aria-label=\"Dismiss\">×</button>\n</div>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-banner`\n- `.ren-banner-actions`\n- `.ren-banner-compact`\n- `.ren-banner-content`\n- `.ren-banner-danger`\n- `.ren-banner-dismiss`\n- `.ren-banner-full`\n- `.ren-banner-icon`\n- `.ren-banner-message`\n- `.ren-banner-neutral`\n- `.ren-banner-success`\n- `.ren-banner-title`\n- `.ren-banner-warning`\n\n## States And Attributes\n\n- `[data-dismissing]`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-banner-bg`\n- `--ren-banner-border-color`\n- `--ren-banner-border-width`\n- `--ren-banner-color`\n- `--ren-banner-gap`\n- `--ren-banner-icon-size`\n- `--ren-banner-padding`\n- `--ren-banner-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-banner/ren-banner.css`\n- `docs/components/ren-banner.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-banner/component.md",
      "id": "contract:primitive:ren-banner",
      "type": "contract",
      "name": "ren-banner component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-breadcrumb Component Contract\n\nHierarchical navigation trail built on native navigation/list semantics.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-breadcrumb` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-breadcrumb` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Breadcrumb primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a hierarchical trail that shows the user's location within a site / app tree.\"\n    - \"The trail has 2+ ancestor levels and the final crumb represents the current page.\"\n    - \"You want native <nav> + <ol> semantics with the \\\"Breadcrumb\\\" landmark announcement.\"\n    - \"You need a customizable separator (default ›) via the --breadcrumb-separator custom property.\"\n    - \"Deep paths must collapse middle entries to an ellipsis via .ren-breadcrumb-truncated.\"\n  avoidWhen:\n    - \"The navigation is the primary site nav — use ren-nav.\"\n    - \"The navigation is a tabbed page section — use ren-tabs.\"\n    - \"The navigation is a multi-step wizard — use ren-steps / ren-progress.\"\n    - \"You only need a single \\\"Back\\\" link — use ren-link with an arrow icon.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-breadcrumb/ren-breadcrumb.css\"\n  notes:\n    - \"CSS-only primitive — no colocated JS exists. Do not import a ren-breadcrumb.js.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Root must be <nav aria-label=\\\"Breadcrumb\\\" class=\\\"ren-breadcrumb\\\"> — the aria-label is what AT announces.\"\n  - \"Items are wrapped in an ordered list: <ol> with <li> children; do not use <ul> or plain <div>s.\"\n  - \"Each crumb is an <a href=\\\"...\\\"> except the current page, which uses aria-current=\\\"page\\\" and may stay as <a> (pointer-events disabled by CSS) or render as plain text.\"\n  - \"Separators are CSS-generated via ::before — never insert literal \\\"/\\\" or \\\"›\\\" characters as text nodes.\"\n  - \"Override the separator per-instance with inline style=\\\"--breadcrumb-separator: '/'\\\" — do not edit the CSS.\"\n\nforbiddenPatterns:\n  - \"Using <div class=\\\"ren-breadcrumb\\\"> without <nav> — strips the landmark.\"\n  - \"Marking multiple crumbs with aria-current=\\\"page\\\" — only the final crumb is current.\"\n  - \"Inserting separator glyphs as text content inside <li> — the ::before pseudo handles it.\"\n  - \"Hardcoded font-size or color overrides via inline style — use the --ren-breadcrumb-* tokens.\"\n  - \"Wrapping crumb links in ren-link / ren-btn — breadcrumb owns its own link chrome.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-breadcrumb-active-color, --ren-breadcrumb-color, --ren-breadcrumb-font-size, --ren-breadcrumb-gap, --ren-breadcrumb-separator.\"\n    - \"Semantic tokens: --color-text, --color-text-muted, --color-text-faint, --color-focus-ring.\"\n    - \"Layout / motion tokens: --space-1, --radius-sm, --duration-tactile, --ease-enter, --ring-width, --caption-size, --text-sm.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors for link / current / separator styling.\"\n    - \"Overriding text-decoration with !important — let :hover and aria-current rules drive it.\"\n\naccessibility:\n  required:\n    - \"Root <nav> must carry aria-label=\\\"Breadcrumb\\\" (translated per locale) so AT announces the landmark.\"\n    - \"Current page crumb uses aria-current=\\\"page\\\"; CSS disables pointer-events but the semantic state is what matters for AT.\"\n    - \"Crumb links must have visible :focus-visible outline driven by --color-focus-ring and --ring-width.\"\n    - \"Separators are decorative (CSS ::before) so they are not announced — never put separator text in the DOM.\"\n    - \"When using .ren-breadcrumb-truncated, the hidden middle crumbs are display:none — ensure the ellipsis is purely decorative and provide a way to expand if the hidden context matters.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-breadcrumb/ren-breadcrumb.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\"><ol><li><a href=\"/\">Home</a></li><li aria-current=\"page\">Settings</li></ol></nav>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-breadcrumb`\n- `.ren-breadcrumb-truncated`\n\n## States And Attributes\n\n- `[aria-current]`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-breadcrumb-active-color`\n- `--ren-breadcrumb-color`\n- `--ren-breadcrumb-font-size`\n- `--ren-breadcrumb-gap`\n- `--ren-breadcrumb-separator`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-breadcrumb/ren-breadcrumb.css`\n- `docs/components/ren-breadcrumb.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-breadcrumb/component.md",
      "id": "contract:primitive:ren-breadcrumb",
      "type": "contract",
      "name": "ren-breadcrumb component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-button\n\nAction primitive for commands, form submissions, links styled as controls,\nicon-only triggers, and loading states.\n\nLoad this file after `ren-design.md` and before generating, editing, or\nreviewing any `ren-btn` / `<ren-button>` UI.\n\n## Purpose\n\nThe single touch-target primitive in RenDS. Owns visible action affordances:\nsolid CTAs, secondary commands, ghosted toolbar actions, link-styled inline\nactions, and icon-only buttons. Does not own form inputs, navigation, or\ndialog launchers — those compose `ren-button` inside their own contracts.\n\n## Use When\n\n- The element is a clickable command: submit, save, delete, open modal,\n  toggle, fire any imperative action.\n- An anchor needs to *look* like a button (use `.ren-btn` on `<a>` only when\n  the link semantically replaces a button — otherwise use `ren-link`).\n- A trigger is part of a composite (`ren-dialog` opener, `ren-menu` trigger,\n  `ren-popover` anchor); the inner element should be `<button class=\"ren-btn\">`.\n- A loading / busy state must be reflected in markup (`data-loading`).\n\n## Do Not Use When\n\n- The control is a form input (use `ren-field` + native input).\n- The control is plain navigation (use `ren-link` or a real `<a>`).\n- The control is a checkbox / radio / toggle (use `ren-checkbox`,\n  `ren-radio`, `ren-toggle-group`).\n- The element is a styled label or status (use `ren-badge` or `ren-tag`).\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"User action triggers an imperative side effect (submit, save, delete, open dialog).\"\n    - \"Need primary / secondary / destructive emphasis tied to the same touch-target shape.\"\n    - \"Need a 44px-minimum touch target with a visible focus ring.\"\n    - \"Need a loading / busy affordance via data-loading + aria-busy.\"\n    - \"Need an icon-only control with an accessible name (.ren-btn-icon + aria-label).\"\n  avoidWhen:\n    - \"The element is purely navigational — use ren-link.\"\n    - \"The element is a form input — use ren-field with a native input.\"\n    - \"The element is a stateful selector (checkbox, radio, switch, toggle).\"\n    - \"You only need text emphasis or a status pill — use ren-badge / ren-tag.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-button/ren-button.css\"\n  js:\n    - \"rends/components/primitives/ren-button/ren-button.js\"\n  notes:\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n    - \"JS is only required when using <ren-button> attributes (loading, variant, size, icon, full); plain <button class=\\\"ren-btn\\\"> works without JS.\"\n\nrequiredMarkup:\n  - \"Always render an actual <button> (or <ren-button>) element. Never style a <div> as a button.\"\n  - \"Icon-only buttons must include an accessible name via aria-label or visually-hidden text.\"\n  - \"Loading buttons must set data-loading and aria-busy=\\\"true\\\" on the same node.\"\n  - \"Use type=\\\"button\\\" for non-submit buttons inside <form> to avoid accidental submission.\"\n\nforbiddenPatterns:\n  - \"div / span with a click handler styled as a button.\"\n  - \"<button style=\\\"background: #...\\\"> — use --ren-btn-bg.\"\n  - \"Removing the focus ring (outline: none) without restoring a visible :focus-visible style.\"\n  - \"Custom danger styling that overrides --color-danger with a primitive --red-* token.\"\n  - \"Disabling via aria-disabled alone without preventing the click handler.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic tokens: --color-accent, --color-on-accent, --color-danger, --color-text-link, --color-focus-ring.\"\n    - \"Component tokens: every --ren-btn-* listed in Public Token API.\"\n    - \"Layout / size tokens: --space-*, --size-*, --radius-*.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, etc.) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Raw transition values; use --ren-btn-duration / --ren-btn-easing.\"\n\naccessibility:\n  required:\n    - \"Real <button> semantics; rely on native click / Enter / Space.\"\n    - \"Visible :focus-visible ring driven by --ren-btn-ring-* tokens.\"\n    - \"Touch target ≥ 44px (default size). Smaller (.ren-btn-sm = 32px) only for non-touch contexts.\"\n    - \"Icon-only buttons have aria-label or visually-hidden label text.\"\n    - \"Loading buttons expose aria-busy=\\\"true\\\" and ignore activation while busy.\"\n    - \"Disabled buttons set both `disabled` (real) or `aria-disabled=\\\"true\\\"` AND prevent activation in the handler.\"\n```\n\n## Required CSS / JS Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-button/ren-button.css\">\n<script type=\"module\" src=\"rends/components/primitives/ren-button/ren-button.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the\nCSS again. JS is only required when consuming `<ren-button>` attributes.\n\n## Canonical Markup\n\nPrimary action button:\n\n```html\n<button class=\"ren-btn\">Save changes</button>\n```\n\nSecondary + small:\n\n```html\n<button class=\"ren-btn ren-btn-secondary ren-btn-sm\" type=\"button\">Cancel</button>\n```\n\nDestructive with loading state:\n\n```html\n<button class=\"ren-btn ren-btn-danger\" data-loading aria-busy=\"true\" disabled>\n  Deleting...\n</button>\n```\n\nIcon-only with accessible name:\n\n```html\n<button class=\"ren-btn ren-btn-icon\" aria-label=\"Close dialog\">\n  <svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\">...</svg>\n</button>\n```\n\nWeb component form (attribute-driven state):\n\n```html\n<ren-button variant=\"secondary\" size=\"sm\">Cancel</ren-button>\n<ren-button variant=\"primary\" loading>Submitting</ren-button>\n```\n\n## Variants\n\n| Class                  | Role                                |\n|------------------------|-------------------------------------|\n| `.ren-btn`             | Default primary CTA.                |\n| `.ren-btn-primary`     | Same as default; explicit.          |\n| `.ren-btn-secondary`   | Tonal / fill-style secondary.       |\n| `.ren-btn-outline`     | Border-only, transparent fill.      |\n| `.ren-btn-ghost`       | No fill, no border; text + hover.   |\n| `.ren-btn-link`        | Inline text-style action.           |\n| `.ren-btn-danger`      | Destructive emphasis.               |\n| `.ren-btn-accent`      | Custom accent via `--btn-accent`.   |\n| `.ren-btn-icon`        | Square icon-only target.            |\n| `.ren-btn-full`        | Stretches to container width.       |\n| `.ren-btn-group`       | Joined sibling buttons.             |\n| `.ren-btn-sm` / `-lg`  | Size override (32px / 52px).        |\n\n## States\n\n| Selector / attr        | Meaning                                                |\n|------------------------|--------------------------------------------------------|\n| `:hover`               | Pointer is over an interactive button.                 |\n| `:active`              | Pressed state.                                         |\n| `:focus-visible`       | Keyboard focus; renders the ring.                      |\n| `:disabled`            | Native disabled; non-interactive, in tab order off.    |\n| `[aria-disabled=\"true\"]` | ARIA-disabled; visually disabled but stays focusable.|\n| `[data-loading]`       | Spinner replaces label; pair with `aria-busy=\"true\"`.  |\n\n## Public Token API\n\nOverride these on `:root`, `[data-theme]`, or a scope wrapping the button.\nDo **not** override the internal `.ren-btn` class.\n\n- `--ren-btn-bg`\n- `--ren-btn-bg-hover`\n- `--ren-btn-bg-active`\n- `--ren-btn-color`\n- `--ren-btn-border-color`\n- `--ren-btn-border-width`\n- `--ren-btn-radius`\n- `--ren-btn-height`\n- `--ren-btn-padding-x`\n- `--ren-btn-padding-y`\n- `--ren-btn-gap`\n- `--ren-btn-font-size`\n- `--ren-btn-font-weight`\n- `--ren-btn-ring-width`\n- `--ren-btn-ring-offset`\n- `--ren-btn-ring-color`\n- `--ren-btn-duration`\n- `--ren-btn-easing`\n\n## Accessibility Contract\n\n- Use a real `<button>` (or `<ren-button>` which renders a button internally).\n- Keep a visible focus ring driven by `--ren-btn-ring-*`.\n- Maintain a 44px minimum touch target (default size). `ren-btn-sm` (32px)\n  is reserved for non-touch contexts (toolbars, dense desktop UI).\n- Icon-only buttons must have an accessible name via `aria-label` or visually\n  hidden text inside the button.\n- Loading buttons expose `aria-busy=\"true\"`. The component sets it\n  automatically when `loading` is the chosen attribute on `<ren-button>`.\n- Do not communicate state through color alone — `ren-btn-danger` carries the\n  variant in the class, not just the hue.\n\n## Anti-Patterns\n\n- ❌ `<div class=\"ren-btn\" onclick=\"...\">` — must be a real `<button>`.\n- ❌ Inline hex colors: `<button style=\"background:#0066ff;\">`.\n- ❌ Overriding `.ren-btn { background: ... !important }` — use\n  `--ren-btn-bg` on a parent scope.\n- ❌ Mixing `disabled` and a click handler that still fires (the handler\n  must early-return or be detached).\n- ❌ Using `ren-btn-link` when the destination is a navigation URL — use\n  `ren-link` instead.\n\n## Related Files\n\n- `components/primitives/ren-button/ren-button.css`\n- `components/primitives/ren-button/ren-button.js`\n- `docs/components/ren-button.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, ARIA, or states change.\n- Run `npm run lint` after token / selector changes.\n- Manually verify light/dark themes when surface, border, or shadow values\n  change — focus rings must remain visible in both modes.\n",
      "path": "components/primitives/ren-button/component.md",
      "id": "contract:primitive:ren-button",
      "type": "contract",
      "name": "ren-button component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-card\n\nContainer primitive for grouped content with header, body, footer, status,\nand interactive (clickable / selectable) variants.\n\nLoad this file after `ren-design.md` and before generating, editing, or\nreviewing any `.ren-card` UI.\n\n## Purpose\n\nOwns the shape, padding, surface, and border of grouped content. Provides\nslots (`-header`, `-body`, `-footer`) and a small set of variants\n(`-elevated`, `-outline`, `-sunken`, `-ghost`, `-simple`,\n`-interactive`, `-selectable`) plus a status hook (`[data-status]`).\nCard never owns inputs, dialogs, or navigation behavior — it is a passive\ncontainer.\n\n## Use When\n\n- A group of related content needs its own surface separated from the page.\n- A list of repeating items would benefit from a consistent card grid\n  (`ren-grid` + `ren-card`).\n- A tile must be clickable or selectable as a whole (use\n  `.ren-card-interactive` or `.ren-card-selectable`).\n- A surface needs a header / body / footer layout already proven by RenDS.\n\n## Do Not Use When\n\n- The container is just a layout primitive — use `ren-stack`, `ren-cluster`,\n  `ren-grid`, etc.\n- The container is a modal — use `ren-dialog`.\n- The container is a popover / tooltip / menu — use those composites.\n- You need an alert / banner / call-out — use `ren-banner`.\n- You need a form section — use `ren-form` + `ren-field` directly.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You want a visually grouped surface (border / shadow / radius) with predictable spacing.\"\n    - \"You need a card grid (stat tiles, product cards, list items).\"\n    - \"You need a whole-card click target (.ren-card-interactive) or selection (.ren-card-selectable).\"\n    - \"You need a status accent driven by [data-status].\"\n  avoidWhen:\n    - \"You only need vertical spacing — use ren-stack.\"\n    - \"You need overlay behavior — use ren-dialog / ren-popover / ren-sheet.\"\n    - \"You need a banner / alert message — use ren-banner.\"\n    - \"You need a navigation list with hover / active — use ren-sidebar items.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-card/ren-card.css\"\n  notes:\n    - \"Card is CSS-only. There is no <ren-card> custom element.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"A single .ren-card root wrapping any number of .ren-card-header, .ren-card-body, .ren-card-footer.\"\n  - \".ren-card-title is the heading; use a real <h*> element inside it for outline / a11y.\"\n  - \"Selectable cards must wire aria-selected (or [data-selected]) AND a keyboard handler — the class only styles.\"\n  - \"Interactive cards (whole-card click) should wrap content in a real <a> or <button>, not a div with onclick.\"\n\nforbiddenPatterns:\n  - \"Nesting another .ren-card inside .ren-card-body — flatten the structure.\"\n  - \"Replacing internal .ren-card-* selectors with custom classes for theming — use --ren-card-* tokens.\"\n  - \"Hardcoded shadow / radius values overriding card defaults; theme through tokens.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: every --ren-card-* listed in Public Token API.\"\n    - \"Semantic tokens for content inside the card (--color-text, --color-text-muted, etc.).\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, etc.) in consumer code.\"\n    - \"Hardcoded hex / rgb colors in consumer overrides.\"\n\naccessibility:\n  required:\n    - \"Card titles use real heading elements (<h2>, <h3>) — never style a <div> as a heading.\"\n    - \"Whole-card click targets must use <a> or <button> wrappers and respect tab order / focus rings.\"\n    - \"Selectable cards announce state via aria-selected + data-selected; pair with keyboard activation.\"\n    - \"[data-status] is decorative; pair it with meaningful text or icons, not color alone.\"\n```\n\n## Required CSS / JS Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-card/ren-card.css\">\n<!-- No JS — card is CSS-only. -->\n```\n\n## Canonical Markup\n\nStatic card with header + body:\n\n```html\n<article class=\"ren-card\">\n  <div class=\"ren-card-header\">\n    <h3 class=\"ren-card-title\">Project status</h3>\n    <p class=\"ren-card-description\">Updated 2 minutes ago.</p>\n  </div>\n  <div class=\"ren-card-body\">\n    <p>All checks passing.</p>\n  </div>\n</article>\n```\n\nInteractive (whole-card click) with link wrapper:\n\n```html\n<a class=\"ren-card ren-card-interactive\" href=\"/projects/atlas\">\n  <div class=\"ren-card-header\">\n    <h3 class=\"ren-card-title\">Atlas</h3>\n  </div>\n  <div class=\"ren-card-body\">42 open issues</div>\n</a>\n```\n\nSelectable in a grid:\n\n```html\n<div class=\"ren-grid-3\">\n  <button class=\"ren-card ren-card-selectable\" type=\"button\" aria-pressed=\"false\">\n    <span class=\"ren-card-title\">Hobby</span>\n    <span class=\"ren-card-description\">$0/mo</span>\n  </button>\n</div>\n```\n\n## Variants\n\n| Class                   | Role                                      |\n|-------------------------|-------------------------------------------|\n| `.ren-card`             | Default surface.                          |\n| `.ren-card-elevated`    | Adds shadow.                              |\n| `.ren-card-outline`     | Border-only, transparent fill.            |\n| `.ren-card-sunken`      | Inset surface (sunken background).        |\n| `.ren-card-ghost`       | No surface; spacing only.                 |\n| `.ren-card-simple`      | Reduced padding for dense lists.          |\n| `.ren-card-interactive` | Hover / focus styles for clickable cards. |\n| `.ren-card-selectable`  | Toggleable card; pair with `aria-selected`. |\n| `.ren-card-cover`       | Edge-to-edge media slot at the top.       |\n| `.ren-card-group`       | Joined sibling cards with shared borders. |\n| `.ren-card-footer-border` | Footer with top divider.                |\n\n## States\n\n| Selector / attr        | Meaning                                     |\n|------------------------|---------------------------------------------|\n| `:hover` / `:active`   | Pointer interaction (interactive cards).    |\n| `:focus-visible`       | Keyboard focus ring (interactive cards).    |\n| `[aria-selected=\"true\"]` / `[data-selected]` | Selection state.       |\n| `[data-status=\"success\" / \"warning\" / \"danger\" / \"info\"]` | Status accent. |\n\n## Public Token API\n\n- `--ren-card-bg`\n- `--ren-card-border-color`\n- `--ren-card-border-width`\n- `--ren-card-radius`\n- `--ren-card-padding`\n- `--ren-card-gap`\n- `--ren-card-header-gap`\n- `--ren-card-shadow`\n\n## Accessibility Contract\n\n- Card titles are real `<h*>` elements; never style a `<div>` as a heading.\n- Whole-card clickability wraps the entire card in a real `<a>` or `<button>`.\n- Selectable cards must expose `aria-selected` (or `aria-pressed`) plus a\n  visible focus ring via `:focus-visible`.\n- `[data-status]` is decorative; status meaning must be carried by text or\n  icons too.\n\n## Anti-Patterns\n\n- ❌ `<div class=\"ren-card\" onclick=\"...\">` — wrap with a real link / button.\n- ❌ Nesting `.ren-card` inside `.ren-card-body`.\n- ❌ Inline hex colors on `.ren-card { background: #FFFFFF; }` — override\n  `--ren-card-bg` on the parent scope.\n- ❌ Replacing built-in selectors (`.ren-card-title`) with custom classes\n  for theming — theme through component tokens.\n\n## Related Files\n\n- `components/primitives/ren-card/ren-card.css`\n- `docs/components/ren-card.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, ARIA, or states change.\n- Run `npm run lint` after token / selector changes.\n- Manually verify light/dark themes when surface, border, or shadow values\n  change.\n",
      "path": "components/primitives/ren-card/component.md",
      "id": "contract:primitive:ren-card",
      "type": "contract",
      "name": "ren-card component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-checkbox Component Contract\n\nNative checkbox styling with checked, indeterminate, disabled, and focus states.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-checkbox` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-checkbox` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Checkbox primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The control toggles a boolean value (checked / unchecked) for a single option.\"\n    - \"The control belongs in a list where each option is independently selectable (multi-select).\"\n    - \"You need an indeterminate / mixed state (e.g., parent of a tree, \\\"select all\\\" with partial children).\"\n    - \"You need native form-submission semantics (name / value / required) tied to a real <input type=\\\"checkbox\\\">.\"\n    - \"The label sits next to a custom-styled box that participates in :checked / :hover / :focus-visible / :indeterminate / :disabled states.\"\n  avoidWhen:\n    - \"The control is an on/off toggle with two visible states — use ren-switch.\"\n    - \"The control is one of several mutually-exclusive options — use ren-radio.\"\n    - \"You need a yes/no choice inside a tabular row without form submission — consider ren-switch.\"\n    - \"You need a tri-state with named states beyond on/off/mixed — design a custom segmented control.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-checkbox/ren-checkbox.css\"\n  notes:\n    - \"CSS-only primitive — no colocated JS exists. Indeterminate must be set in JS via input.indeterminate = true; the CSS reacts to :indeterminate.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Wrap each control in <label class=\\\"ren-checkbox\\\"> containing exactly one <input type=\\\"checkbox\\\"> and one <span class=\\\"ren-checkbox-control\\\"> sibling, in that order.\"\n  - \"Label text goes in a trailing <span> (or text node) after .ren-checkbox-control so :checked + .ren-checkbox-control adjacency works.\"\n  - \"Group related checkboxes inside a <fieldset> with a <legend> to provide a group accessible name.\"\n  - \"For indeterminate state, set input.indeterminate = true via JS; you cannot express it via an HTML attribute alone.\"\n  - \"Disabled inputs must use the real disabled attribute — the .ren-checkbox:has(input:disabled) selector relies on it.\"\n\nforbiddenPatterns:\n  - \"Replacing <input type=\\\"checkbox\\\"> with a <div role=\\\"checkbox\\\"> — breaks form submission and native keyboard.\"\n  - \"Hiding the input with display:none — destroys keyboard focus; use the documented visually-hidden technique already in the CSS.\"\n  - \"Custom checkmark glyphs injected via JS or inline SVG — the ::after pseudo owns the checkmark.\"\n  - \"Mixing two label nodes per input (e.g., wrapping <label> plus a separate <label for>) — choose one.\"\n  - \"Communicating error / validity via color alone; pair with text or an inline error message.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-checkbox-border, --ren-checkbox-checked-bg, --ren-checkbox-checked-color, --ren-checkbox-duration, --ren-checkbox-easing, --ren-checkbox-gap, --ren-checkbox-radius, --ren-checkbox-size.\"\n    - \"Semantic tokens: --color-accent, --color-accent-hover, --color-on-accent, --color-border-strong, --color-text, --color-focus-ring.\"\n    - \"Layout / motion tokens: --space-2, --radius-sm, --touch-min, --ring-width, --ring-offset-width, --duration-state, --duration-tactile, --ease-enter, --ease-playful.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors for the checked fill, border, or checkmark.\"\n    - \"Raw transition values; use --ren-checkbox-duration / --ren-checkbox-easing or the --duration-* / --ease-* tokens.\"\n\naccessibility:\n  required:\n    - \"Always use a real <input type=\\\"checkbox\\\"> so Space toggles, Tab focuses, and form submission carries the value.\"\n    - \"Touch target stays ≥ var(--touch-min) via min-height on .ren-checkbox; do not shrink below that for touch contexts.\"\n    - \"Focus is visible on the .ren-checkbox-control via :focus-visible outline driven by --color-focus-ring and --ring-width.\"\n    - \"Each checkbox has an accessible name via its wrapping <label> text; never rely on placeholder or title alone.\"\n    - \"Group multiple related checkboxes inside <fieldset><legend> for a programmatic group name.\"\n    - \"Disabled state must use the native disabled attribute (not just aria-disabled) so the input is excluded from form submission and keyboard navigation.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-checkbox/ren-checkbox.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<label class=\"ren-checkbox\">\n  <input type=\"checkbox\" name=\"updates\">\n  <span class=\"ren-checkbox-control\"></span>\n  <span>Email updates</span>\n</label>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-checkbox`\n- `.ren-checkbox-control`\n\nFor toggle switches with on/off semantics, see the sibling primitive `ren-switch`.\n\n## States And Attributes\n\n- `:active`\n- `:checked`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n- `:indeterminate`\n\n## Public Token API\n\n- `--ren-checkbox-border`\n- `--ren-checkbox-checked-bg`\n- `--ren-checkbox-checked-color`\n- `--ren-checkbox-duration`\n- `--ren-checkbox-easing`\n- `--ren-checkbox-gap`\n- `--ren-checkbox-radius`\n- `--ren-checkbox-size`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-checkbox/ren-checkbox.css`\n- `docs/components/ren-checkbox.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-checkbox/component.md",
      "id": "contract:primitive:ren-checkbox",
      "type": "contract",
      "name": "ren-checkbox component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-field\n\nForm-field wrapper that connects labels, descriptions, errors, and the\nunderlying control through ARIA. Carries the field's visual frame\n(input chrome, error / valid borders, sizing).\n\nLoad this file after `ren-design.md` and before generating, editing, or\nreviewing any `<ren-field>` UI.\n\n## Purpose\n\nOwns the wiring between a `<label>`, helper text (`[data-description]`),\nerror text (`[data-error]`), and the control inside it. The custom element\nauto-generates IDs, links them via `for` / `aria-labelledby` /\n`aria-describedby` / `aria-errormessage`, and toggles `aria-invalid` and\n`data-invalid` from the host's validation state. Visual styling for the\ncontrol itself comes from `.ren-input` / `.ren-input-wrapper`.\n\n## Use When\n\n- The UI is a labeled form control: text, email, password, number, search,\n  url, date, textarea, select.\n- The control needs helper text and / or error text wired through ARIA.\n- You want consistent field heights, border / focus ring treatment, and\n  validation states across the app.\n- The field belongs to a `ren-form` pattern that drives validation.\n\n## Do Not Use When\n\n- The control is a single button — use `ren-button`.\n- The control is a checkbox / radio / switch — use `ren-checkbox` /\n  `ren-radio` / `ren-toggle-group`.\n- The control is a search/combobox with autocomplete — use `ren-combobox`.\n- You only need a static label-value pair for display — use `<dl>` /\n  `ren-card` content.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The control is a labeled form input that benefits from auto-wired ARIA.\"\n    - \"You need helper text + error text + invalid styling tied to the same control.\"\n    - \"You want a consistent input frame (height, padding, focus ring, error border).\"\n    - \"You are inside a <ren-form> that drives validation.\"\n  avoidWhen:\n    - \"The control is a checkbox / radio / switch — use those primitives directly.\"\n    - \"The control is a combobox / select with custom listbox — use ren-combobox / ren-select.\"\n    - \"You need a fully custom number stepper — use ren-number-field.\"\n    - \"You need OTP segmented inputs — use ren-otp.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-field/ren-field.css\"\n  js:\n    - \"rends/components/primitives/ren-field/ren-field.js\"\n  notes:\n    - \"JS is required for ARIA wiring and validation state syncing — do not skip.\"\n\nrequiredMarkup:\n  - \"<ren-field> wraps exactly one <label>, exactly one control, plus optional [data-description] and [data-error].\"\n  - \"Error and description nodes do not need their own IDs — the component generates them.\"\n  - \"Native control attributes drive validity (required, type=email, pattern, min, max). Do not duplicate validation in JS for the same rules.\"\n  - \"Use .ren-input on inputs / textareas / selects; use .ren-input-wrapper for icon adornments.\"\n\nforbiddenPatterns:\n  - \"Manual aria-describedby / aria-errormessage on the input — the component sets them.\"\n  - \"Reaching into the slotted control to override .ren-input internals; theme via --ren-field-* tokens.\"\n  - \"Showing error text without setting [data-invalid] / aria-invalid on the control.\"\n  - \"Hiding required-state info in placeholder text.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: every --ren-field-* listed in Public Token API.\"\n    - \"Semantic tokens for any custom decoration (--color-text-muted for the description, etc.).\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, etc.) in consumer code.\"\n    - \"Hardcoded hex / rgb colors in consumer overrides.\"\n\naccessibility:\n  required:\n    - \"Always provide a visible <label>; do not rely on placeholder text.\"\n    - \"Required inputs must mark required (the attribute) — visual indicator alone is not enough.\"\n    - \"Error messages must be set in [data-error] AND aria-invalid must be true on the control.\"\n    - \"Keep visible focus rings; do not remove the input outline without a replacement.\"\n    - \"Disabled fields use the disabled attribute on the control, not aria-disabled alone.\"\n```\n\n## Required CSS / JS Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-field/ren-field.css\">\n<script type=\"module\" src=\"rends/components/primitives/ren-field/ren-field.js\"></script>\n```\n\n## Canonical Markup\n\nRequired email field with helper + error:\n\n```html\n<ren-field>\n  <label>Email</label>\n  <input class=\"ren-input\" type=\"email\" name=\"email\" autocomplete=\"email\" required>\n  <span data-description>We will never share your email.</span>\n  <span data-error>Enter a valid email address.</span>\n</ren-field>\n```\n\nPassword with visible label and helper:\n\n```html\n<ren-field>\n  <label>Password</label>\n  <input class=\"ren-input\" type=\"password\" name=\"password\" autocomplete=\"new-password\" minlength=\"8\" required>\n  <span data-description>At least 8 characters.</span>\n  <span data-error></span>\n</ren-field>\n```\n\nTextarea:\n\n```html\n<ren-field>\n  <label>Notes</label>\n  <textarea class=\"ren-input\" name=\"notes\" rows=\"4\"></textarea>\n</ren-field>\n```\n\nInput with leading icon:\n\n```html\n<ren-field>\n  <label>Search</label>\n  <span class=\"ren-input-wrapper\">\n    <span class=\"ren-input-icon\" aria-hidden=\"true\">🔍</span>\n    <input class=\"ren-input\" type=\"search\" name=\"q\">\n  </span>\n</ren-field>\n```\n\n## Variants\n\n| Class                       | Role                                |\n|-----------------------------|-------------------------------------|\n| `.ren-input`                | Base input chrome.                  |\n| `.ren-input-sm` / `-lg`     | Size override.                      |\n| `.ren-input-error`          | Manual error styling on the input.  |\n| `.ren-input-success`        | Manual success styling.             |\n| `.ren-input-wrapper`        | Container for adornments.           |\n| `.ren-input-icon`           | Leading icon slot.                  |\n| `.ren-input-icon-end`       | Trailing icon slot.                 |\n| `.ren-field-label`          | Label slot styling.                 |\n| `.ren-field-description`    | Helper text slot styling.           |\n| `.ren-field-error`          | Error slot styling.                 |\n\n## States\n\n| Selector / attr        | Meaning                                              |\n|------------------------|------------------------------------------------------|\n| `:hover` / `:active`   | Pointer interaction.                                 |\n| `:focus-visible`       | Keyboard focus on the control.                       |\n| `:disabled`            | Native disabled state.                               |\n| `:user-invalid`        | Native validity after interaction (CSS Lv4).         |\n| `:user-valid`          | Native validity after interaction (CSS Lv4).         |\n| `[aria-invalid=\"true\"]` / `[data-invalid]` | Component-driven invalid state.  |\n| `[data-valid]`         | Component-driven valid state.                        |\n| `[data-required]`      | Component marks required visually.                   |\n| `[data-description]`   | Slot for helper text.                                |\n| `[data-error]`         | Slot for error text.                                 |\n\n## Public Token API\n\n- `--ren-field-bg`\n- `--ren-field-border-color`\n- `--ren-field-border-width`\n- `--ren-field-radius`\n- `--ren-field-height`\n- `--ren-field-padding-x`\n- `--ren-field-padding-y`\n- `--ren-field-font-size`\n- `--ren-field-color`\n- `--ren-field-placeholder`\n- `--ren-field-focus-color`\n- `--ren-field-error-color`\n- `--ren-field-success-color`\n\n## Accessibility Contract\n\n- Always provide a visible `<label>`; placeholder is not a label.\n- Required inputs use the native `required` attribute; mark it visually too.\n- Error text lives in `[data-error]`. The component sets `aria-invalid` on\n  the control automatically when the host marks the field invalid.\n- Focus rings must remain visible — do not strip outline without restoring.\n- Disabled fields use `disabled` on the control. `aria-disabled` is only\n  acceptable when the disabled state must remain focusable.\n\n## Anti-Patterns\n\n- ❌ Putting label text in `placeholder` instead of `<label>`.\n- ❌ Manual `aria-describedby` / `aria-errormessage` on the input — the\n  component owns these.\n- ❌ Showing error text without setting `aria-invalid` on the control.\n- ❌ `.ren-input { border: 2px solid #ccc; }` — theme through\n  `--ren-field-border-*` tokens.\n\n## Related Files\n\n- `components/primitives/ren-field/ren-field.css`\n- `components/primitives/ren-field/ren-field.js`\n- `components/patterns/ren-form/pattern.md` (validation pattern)\n- `docs/components/ren-field.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n\n## Test Expectations\n\n- Run component / docs a11y coverage when markup, ARIA, or validation\n  behavior changes.\n- Run `npm run lint` after token / selector changes.\n- Manually verify light/dark themes for focus ring and error glow.\n",
      "path": "components/primitives/ren-field/component.md",
      "id": "contract:primitive:ren-field",
      "type": "contract",
      "name": "ren-field component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-icon Component Contract\n\nIcon sizing, color, and motion utility for SVG icons.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-icon` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-icon` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Icon primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a consistent sizing wrapper (xs/sm/md/lg/xl/2xl) around an inline SVG.\"\n    - \"You need an icon that inherits the parent text color via currentColor.\"\n    - \"You need a semantic color tint (primary, success, warning, danger, muted) on a glyph.\"\n    - \"You need a spinning loading glyph that respects prefers-reduced-motion.\"\n    - \"You need an icon slot inside ren-button, ren-link, ren-badge, or other primitives.\"\n  avoidWhen:\n    - \"The graphic is decorative and inline — a raw <svg aria-hidden=\\\"true\\\"> is enough.\"\n    - \"The element is a clickable control — wrap the icon inside ren-button (ren-btn-icon) instead.\"\n    - \"You need a multi-color illustration or logo — use a plain <svg> with its own viewport.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-icon/ren-icon.css\"\n  notes:\n    - \"CSS-only primitive; no JavaScript file exists for ren-icon.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Always wrap a real <svg> (or icon-font glyph) inside <span class=\\\"ren-icon\\\"> — never style the SVG directly with .ren-icon.\"\n  - \"Decorative icons inside text or buttons must set aria-hidden=\\\"true\\\" on the SVG (the wrapper has no role).\"\n  - \"Standalone meaningful icons must have an accessible name on the parent control (aria-label) or visually-hidden text — the .ren-icon wrapper itself does not carry a name.\"\n  - \"Pick exactly one size variant (.ren-icon-xs through .ren-icon-2xl); the base .ren-icon defaults to md (20px).\"\n  - \".ren-icon-spin must be paired with role=\\\"status\\\" or aria-busy on a parent for screen reader users.\"\n\nforbiddenPatterns:\n  - \"Setting width/height inline on <svg> instead of using the .ren-icon-* size variants.\"\n  - \"Hardcoding fill=\\\"#...\\\" on the inner SVG — drive color via currentColor and the .ren-icon-* color variants.\"\n  - \"Using .ren-icon-danger to convey state without an accompanying text label (state by color alone).\"\n  - \"Animating the spin with a custom CSS animation instead of .ren-icon-spin (loses reduced-motion guard).\"\n  - \"Using .ren-icon as a clickable target — wrap with a real <button> or use ren-button instead.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-icon-color, --ren-icon-fill, --ren-icon-size.\"\n    - \"Semantic tokens consumed internally: --color-accent, --color-success, --color-warning, --color-danger, --color-text-secondary.\"\n    - \"Motion tokens: --duration-loop, --ease-loop (for .ren-icon-spin).\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex or named colors on the SVG or wrapper.\"\n    - \"Raw animation durations; rely on --duration-loop / --ease-loop via .ren-icon-spin.\"\n\naccessibility:\n  required:\n    - \"Decorative icons set aria-hidden=\\\"true\\\" on the SVG element.\"\n    - \"Meaningful icons get their accessible name from the surrounding control (button aria-label, link text, etc.) — never from the .ren-icon wrapper alone.\"\n    - \"Color variants (.ren-icon-success, .ren-icon-danger) must be paired with text or aria-label so meaning is not color-only.\"\n    - \".ren-icon-spin animation honors prefers-reduced-motion: reduce automatically.\"\n    - \"Do not turn .ren-icon into an interactive target; it has no focus styles or hit-area guarantees.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-icon/ren-icon.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<span class=\"ren-icon\" aria-hidden=\"true\">\n  <svg viewBox=\"0 0 24 24\" focusable=\"false\"><path d=\"M5 12h14\"></path></svg>\n</span>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-icon`\n- `.ren-icon-2xl`\n- `.ren-icon-danger`\n- `.ren-icon-lg`\n- `.ren-icon-md`\n- `.ren-icon-muted`\n- `.ren-icon-primary`\n- `.ren-icon-sm`\n- `.ren-icon-spin`\n- `.ren-icon-success`\n- `.ren-icon-warning`\n- `.ren-icon-xl`\n- `.ren-icon-xs`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-icon-color`\n- `--ren-icon-fill`\n- `--ren-icon-size`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-icon/ren-icon.css`\n- `docs/components/ren-icon.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-icon/component.md",
      "id": "contract:primitive:ren-icon",
      "type": "contract",
      "name": "ren-icon component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-kbd Component Contract\n\nKeyboard key / shortcut primitive for documentation and command hints.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-kbd` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-kbd` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Kbd primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need to display a single keyboard key (⌘, K, Esc, Enter) in documentation or command hints.\"\n    - \"You need to render a keyboard combo (⌘ + K) where each key is a chip with native <kbd> semantics.\"\n    - \"You want monospaced, chip-styled tokens with a sunken background and bottom-shadow border.\"\n    - \"You need an inline label inside menu items, tooltips, or docs showing the shortcut for an action.\"\n  avoidWhen:\n    - \"The element is a status pill or category label — use ren-badge or ren-tag.\"\n    - \"The element is a clickable shortcut trigger — use ren-button and place a <kbd> inside its label.\"\n    - \"You need block-level code samples — use <pre><code> or a code-block component, not <kbd>.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-kbd/ren-kbd.css\"\n  notes:\n    - \"CSS-only primitive; no JavaScript file exists for ren-kbd.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Always use a real <kbd> element with class=\\\"ren-kbd\\\"; never style a <span> or <code> as a kbd chip.\"\n  - \"Each key in a combo is its own <kbd class=\\\"ren-kbd\\\">; the joiner (+) lives between them as plain text.\"\n  - \"Inside running prose, place the <kbd> inline (it is display: inline-flex with no forced line break).\"\n  - \"Use white-space: nowrap behavior built in — do not wrap the key glyph across lines via extra markup.\"\n\nforbiddenPatterns:\n  - \"<span class=\\\"ren-kbd\\\"> or <code class=\\\"ren-kbd\\\"> — must be a real <kbd> for assistive tech.\"\n  - \"Hardcoded background or border colors that bypass --ren-kbd-bg / --ren-kbd-border.\"\n  - \"Adding click handlers on .ren-kbd to fire a shortcut — wrap the kbd inside a ren-button instead.\"\n  - \"Using <kbd class=\\\"ren-kbd\\\"> for a multi-character command name (e.g., \\\"git status\\\") — use <code> for that.\"\n  - \"Stacking multiple keys inside a single <kbd> (e.g., <kbd>⌘K</kbd>) — split them into one <kbd> per key.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-kbd-bg, --ren-kbd-border, --ren-kbd-color, --ren-kbd-font-family, --ren-kbd-font-size, --ren-kbd-padding, --ren-kbd-radius.\"\n    - \"Semantic tokens consumed internally: --color-text, --color-surface-sunken, --color-border-strong.\"\n    - \"Type tokens: --font-mono, --caption-size, --weight-medium.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Custom font-family overrides that bypass --ren-kbd-font-family / --font-mono.\"\n\naccessibility:\n  required:\n    - \"Use the native <kbd> element so screen readers announce \\\"keyboard input\\\" semantics.\"\n    - \"Keep contrast on the chip background ≥ WCAG AA against surrounding text (driven by --color-surface-sunken / --color-text).\"\n    - \"Do not rely on the chip styling alone to convey shortcut meaning; pair with descriptive text (e.g., \\\"Press <kbd>⌘</kbd> + <kbd>K</kbd> to open search\\\").\"\n    - \"Do not make .ren-kbd interactive — it has no focus ring or hit target.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-kbd/ren-kbd.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<kbd class=\"ren-kbd\">⌘</kbd>+<kbd class=\"ren-kbd\">K</kbd>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-kbd`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-kbd-bg`\n- `--ren-kbd-border`\n- `--ren-kbd-color`\n- `--ren-kbd-font-family`\n- `--ren-kbd-font-size`\n- `--ren-kbd-padding`\n- `--ren-kbd-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-kbd/ren-kbd.css`\n- `docs/components/ren-kbd.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-kbd/component.md",
      "id": "contract:primitive:ren-kbd",
      "type": "contract",
      "name": "ren-kbd component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-link Component Contract\n\nAccessible link styling for inline, muted, nav, external, and skip links.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-link` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-link` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Link primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The element navigates to a URL, anchor, or routed page (i.e., it has a real href).\"\n    - \"You need inline text links inside paragraphs styled with underline + hover.\"\n    - \"You need a navigation link with a 44px touch target (.ren-link-nav) plus active-route styling via aria-current=\\\"page\\\" / [data-active].\"\n    - \"You need an external-link affordance with a trailing ↗ marker (.ren-link-external).\"\n    - \"You need a skip-to-content link that becomes visible only on focus (.ren-link-skip).\"\n    - \"You need a muted or unstyled (inherits color) anchor (.ren-link-muted, .ren-link-plain).\"\n  avoidWhen:\n    - \"The element triggers an imperative action (submit, open dialog, delete) — use ren-button.\"\n    - \"The element is a tab, menuitem, or pagination control — use the corresponding component.\"\n    - \"The element is a stateful toggle — use a switch, checkbox, or button with aria-pressed.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-link/ren-link.css\"\n  notes:\n    - \"CSS-only primitive; no JavaScript file exists for ren-link.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Always render an actual <a href=\\\"...\\\"> element. Never style a <button> or <span> as a link.\"\n  - \"External links should set rel=\\\"noopener noreferrer\\\" and target=\\\"_blank\\\" alongside .ren-link-external when opening in a new tab.\"\n  - \"Nav links representing the current page set aria-current=\\\"page\\\" (or data-active for non-routing states) on the <a>.\"\n  - \"Skip links (.ren-link-skip) must be the first focusable element in <body> and reference a real target id (e.g., href=\\\"#main\\\").\"\n  - \"The trailing ↗ on .ren-link-external is decorative content — never use it as the only signal that a link opens externally; provide aria-label or visually-hidden text for screen readers if needed.\"\n\nforbiddenPatterns:\n  - \"<button class=\\\"ren-link\\\"> — use a real <a> with an href.\"\n  - \"<a class=\\\"ren-link\\\" onclick=\\\"...\\\"> without an href — non-focusable, breaks keyboard activation.\"\n  - \"Removing the focus ring (outline: none) without restoring a visible :focus-visible style.\"\n  - \"Hardcoded color overrides like style=\\\"color:#0066cc\\\" — use --ren-link-color / --color-text-link.\"\n  - \"Using .ren-link-nav for inline prose links; it expects flex layout and a 44px hit area.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-link-color, --ren-link-font-weight, --ren-link-hover-color, --ren-link-underline.\"\n    - \"Semantic tokens: --color-text-link, --color-text-link-hover, --color-text, --color-text-muted, --color-accent, --color-accent-subtle, --color-on-accent, --color-focus-ring, --color-fill.\"\n    - \"Layout / motion tokens: --space-*, --radius-sm, --radius-md, --touch-min, --ring-width, --duration-tactile, --ease-enter.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Custom underline colors via text-decoration shorthand that bypass --ren-link-underline.\"\n\naccessibility:\n  required:\n    - \"Real <a href> semantics; never simulate a link with a div + onclick.\"\n    - \"Visible :focus-visible ring driven by --color-focus-ring and --ring-width.\"\n    - \".ren-link-nav guarantees a 44px touch target via min-height: var(--touch-min).\"\n    - \"The current navigation item must set aria-current=\\\"page\\\" so screen readers announce route position.\"\n    - \"Skip links must be reachable as the first Tab stop and become visually focused via .ren-link-skip:focus.\"\n    - \"External-link ↗ glyph is a visual hint only; do not rely on it for assistive-tech announcement.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-link/ren-link.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<a class=\"ren-link\" href=\"/docs\">Read the documentation</a>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-link`\n- `.ren-link-external`\n- `.ren-link-muted`\n- `.ren-link-nav`\n- `.ren-link-plain`\n- `.ren-link-skip`\n\n## States And Attributes\n\n- `[aria-current]`\n- `[data-active]`\n- `:active`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-link-color`\n- `--ren-link-font-weight`\n- `--ren-link-hover-color`\n- `--ren-link-underline`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-link/ren-link.css`\n- `docs/components/ren-link.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-link/component.md",
      "id": "contract:primitive:ren-link",
      "type": "contract",
      "name": "ren-link component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-pagination Component Contract\n\nPagination navigation for page ranges and previous/next movement.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-pagination` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-pagination` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Pagination primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"A list, table, or grid is broken into discrete numbered pages with a known total count.\"\n    - \"You need prev / next + numbered items with aria-current=\\\"page\\\" on the active page.\"\n    - \"You need a minimal prev / info / next layout (.ren-pagination-simple) when total pages is unknown.\"\n    - \"You need a compact variant for dense tables (.ren-pagination-compact).\"\n    - \"You need a centered standalone block under content (.ren-pagination-centered).\"\n    - \"You need an ellipsis spacer between page ranges (.ren-pagination-ellipsis).\"\n  avoidWhen:\n    - \"The feed is infinite-scroll or load-more — use a button + scroll observer, not pagination.\"\n    - \"The data is paged by cursor with no concept of page numbers — show only prev/next.\"\n    - \"You need step navigation inside a wizard — use a stepper component.\"\n    - \"The navigation is between sibling sections of a page — use tabs or anchor links.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-pagination/ren-pagination.css\"\n  notes:\n    - \"CSS-only primitive; no JavaScript file exists for ren-pagination.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Wrap the control in a real <nav aria-label=\\\"Pagination\\\"> with class=\\\"ren-pagination\\\".\"\n  - \"Each page link is an <a href=\\\"...\\\"> with class=\\\"ren-pagination-item\\\"; mark the active page with aria-current=\\\"page\\\" (it auto-disables pointer-events).\"\n  - \"Prev / next are <a class=\\\"ren-pagination-prev\\\"> / <a class=\\\"ren-pagination-next\\\"> with aria-label describing direction when the content is a glyph (e.g., aria-label=\\\"Previous page\\\").\"\n  - \"Use <span class=\\\"ren-pagination-ellipsis\\\" aria-hidden=\\\"true\\\">…</span> for visual gaps — it must not be focusable.\"\n  - \"Disabled prev/next use aria-disabled=\\\"true\\\" (preferred for <a>) or disabled (for <button>).\"\n\nforbiddenPatterns:\n  - \"<div role=\\\"navigation\\\"> or <ul> instead of <nav> for the wrapper.\"\n  - \"Plain <span> or <button> styled as a pagination item — use <a class=\\\"ren-pagination-item\\\">.\"\n  - \"Marking the active page with only a CSS class — must include aria-current=\\\"page\\\" for screen readers.\"\n  - \"Hiding disabled prev/next via display: none — keep them in DOM with aria-disabled so layout stays stable.\"\n  - \"Hardcoded color overrides on the active item that bypass --color-accent / --color-on-accent.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-pagination-active-bg, --ren-pagination-active-color, --ren-pagination-bg, --ren-pagination-font-size, --ren-pagination-gap, --ren-pagination-hover-bg, --ren-pagination-radius, --ren-pagination-size.\"\n    - \"Semantic tokens consumed internally: --color-text, --color-text-muted, --color-accent, --color-on-accent, --color-fill, --color-fill-hover, --color-disabled-text, --color-focus-ring.\"\n    - \"Layout tokens: --touch-min, --size-sm, --space-1, --space-2, --radius-md, --stroke-1, --ring-width, --ring-offset-width, --duration-tactile, --ease-enter.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Inline width/height styling that breaks min 44px touch target on default variant.\"\n\naccessibility:\n  required:\n    - \"Wrapper is a real <nav> landmark with aria-label=\\\"Pagination\\\" (or equivalent).\"\n    - \"Active page uses aria-current=\\\"page\\\"; do not duplicate it on a separate <span>.\"\n    - \"Default and simple variants guarantee min 44px touch targets (--touch-min); compact variant is for non-touch contexts only.\"\n    - \"Visible :focus-visible ring driven by --color-focus-ring + --ring-width / --ring-offset-width.\"\n    - \"Glyph-only prev/next provide an aria-label so the direction is announced.\"\n    - \"Disabled prev/next set aria-disabled=\\\"true\\\" AND pointer-events: none (handled by CSS); do not rely on visual dimming alone.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-pagination/ren-pagination.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<nav class=\"ren-pagination\" aria-label=\"Pagination\">\n  <a class=\"ren-pagination-prev\" href=\"?page=1\" aria-label=\"Previous page\">‹</a>\n  <a class=\"ren-pagination-item\" href=\"?page=1\">1</a>\n  <a class=\"ren-pagination-item\" href=\"?page=2\" aria-current=\"page\">2</a>\n  <a class=\"ren-pagination-next\" href=\"?page=3\" aria-label=\"Next page\">›</a>\n</nav>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-pagination`\n- `.ren-pagination-centered`\n- `.ren-pagination-compact`\n- `.ren-pagination-ellipsis`\n- `.ren-pagination-info`\n- `.ren-pagination-item`\n- `.ren-pagination-next`\n- `.ren-pagination-prev`\n- `.ren-pagination-simple`\n\n## States And Attributes\n\n- `[aria-current]`\n- `[aria-disabled]`\n- `:active`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-pagination-active-bg`\n- `--ren-pagination-active-color`\n- `--ren-pagination-bg`\n- `--ren-pagination-font-size`\n- `--ren-pagination-gap`\n- `--ren-pagination-hover-bg`\n- `--ren-pagination-radius`\n- `--ren-pagination-size`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-pagination/ren-pagination.css`\n- `docs/components/ren-pagination.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-pagination/component.md",
      "id": "contract:primitive:ren-pagination",
      "type": "contract",
      "name": "ren-pagination component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-progress Component Contract\n\nProgress and meter styling for determinate and indeterminate completion states.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-progress` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-progress` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Progress primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a determinate horizontal bar showing percent completion of a known task (.ren-progress + .ren-progress-bar with inline width).\"\n    - \"You need an indeterminate loading bar with sliding animation (.ren-progress-indeterminate).\"\n    - \"You need a semantic-colored progress state (success / warning / danger / info).\"\n    - \"You need a meter (current value within a min/max range, not progress) — use <meter class=\\\"ren-meter\\\">.\"\n    - \"You need optional caption rows above (.ren-progress-label) and below (.ren-progress-value) the bar.\"\n    - \"You need multiple bar heights: sm (4px), md (8px default), lg (12px), xl (16px).\"\n  avoidWhen:\n    - \"You need a circular spinner — use .ren-icon-spin on a loading glyph.\"\n    - \"You need a busy state on a button — use ren-button with data-loading / aria-busy.\"\n    - \"You need a step indicator with discrete milestones — use a stepper component.\"\n    - \"You need a skeleton placeholder while content loads — use ren-skeleton.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-progress/ren-progress.css\"\n  notes:\n    - \"CSS-only primitive; no JavaScript file exists for ren-progress.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Determinate progress: <div class=\\\"ren-progress\\\" role=\\\"progressbar\\\" aria-valuemin=\\\"0\\\" aria-valuemax=\\\"100\\\" aria-valuenow=\\\"<pct>\\\"><div class=\\\"ren-progress-bar\\\" style=\\\"width: <pct>%\\\"></div></div>.\"\n  - \"Indeterminate progress: add class .ren-progress-indeterminate to the outer and omit aria-valuenow (set aria-valuetext or aria-busy=\\\"true\\\" instead).\"\n  - \"Native semantics alternative: <progress class=\\\"ren-progress\\\" max=\\\"100\\\" value=\\\"<pct>\\\"></progress> when no custom inner bar is needed.\"\n  - \"Meter: <meter class=\\\"ren-meter\\\" min=\\\"0\\\" max=\\\"10\\\" value=\\\"<n>\\\"> — for a current value in a range (battery, score), not for progress over time.\"\n  - \"Provide an accessible label via aria-label, aria-labelledby, or a sibling .ren-progress-label.\"\n\nforbiddenPatterns:\n  - \"<div class=\\\"ren-progress\\\"> without role=\\\"progressbar\\\" + aria-value* (when not using native <progress>).\"\n  - \"Animating bar width with a custom transition that bypasses --duration-state / --ease-enter.\"\n  - \"Using .ren-progress-danger to convey state by color alone — pair with a text label.\"\n  - \"Hardcoded background colors on .ren-progress-bar that bypass --color-accent / semantic variants.\"\n  - \"Using <meter> for a progress-over-time scenario or <progress> for a current-value-in-range scenario.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-progress-bg, --ren-progress-duration, --ren-progress-easing, --ren-progress-fill, --ren-progress-height, --ren-progress-radius.\"\n    - \"Semantic tokens consumed internally: --color-fill, --color-accent, --color-success, --color-warning, --color-danger, --color-info, --color-text, --color-text-secondary.\"\n    - \"Layout / type tokens: --radius-full, --label-size, --weight-medium, --weight-regular, --space-1, --duration-state, --ease-enter.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Raw animation durations; rely on --ren-progress-duration / --duration-state.\"\n\naccessibility:\n  required:\n    - \"Determinate bars expose role=\\\"progressbar\\\" + aria-valuemin / aria-valuemax / aria-valuenow (or use native <progress>).\"\n    - \"Indeterminate bars omit aria-valuenow and set aria-busy=\\\"true\\\" or aria-valuetext=\\\"Loading\\\" so AT can announce status.\"\n    - \"Every progress bar has an accessible name (aria-label, aria-labelledby, or a .ren-progress-label paired by id).\"\n    - \"Color-coded variants (success / warning / danger / info) must be paired with a text label or .ren-progress-value — never color-only.\"\n    - \"Indeterminate animation auto-honors prefers-reduced-motion: reduce (slide is suppressed, bar holds at 50%).\"\n    - \"Width transition is suppressed under reduced motion so value updates do not animate.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-progress/ren-progress.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div class=\"ren-progress\" role=\"progressbar\" aria-label=\"Upload progress\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"65\">\n  <div class=\"ren-progress-bar\" style=\"width: 65%\"></div>\n</div>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-meter`\n- `.ren-meter-lg`\n- `.ren-meter-sm`\n- `.ren-meter-xl`\n- `.ren-progress`\n- `.ren-progress-bar`\n- `.ren-progress-danger`\n- `.ren-progress-indeterminate`\n- `.ren-progress-info`\n- `.ren-progress-label`\n- `.ren-progress-lg`\n- `.ren-progress-sm`\n- `.ren-progress-success`\n- `.ren-progress-value`\n- `.ren-progress-warning`\n- `.ren-progress-xl`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-progress-bg`\n- `--ren-progress-duration`\n- `--ren-progress-easing`\n- `--ren-progress-fill`\n- `--ren-progress-height`\n- `--ren-progress-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-progress/ren-progress.css`\n- `docs/components/ren-progress.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-progress/component.md",
      "id": "contract:primitive:ren-progress",
      "type": "contract",
      "name": "ren-progress component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-radio Component Contract\n\nNative radio group styling with optional custom element keyboard enhancement.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-radio` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-radio` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Radio primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The user must pick exactly one option from 2-7 mutually-exclusive choices.\"\n    - \"All choices should be visible at once (no overflow / collapse).\"\n    - \"You need a custom-styled radio dot built on native <input type=\\\"radio\\\"> with preserved semantics.\"\n    - \"You need keyboard arrow navigation + roving tabindex via <ren-radio-group>.\"\n    - \"You need a vertical (.ren-radio-group) or horizontal (.ren-radio-group-horizontal) layout.\"\n  avoidWhen:\n    - \"There are more than ~7 options or the list overflows — use a ren-select / combobox.\"\n    - \"Multiple selections are allowed — use ren-checkbox.\"\n    - \"The choice is binary on/off — use ren-switch or ren-checkbox.\"\n    - \"The selection drives navigation or filters with immediate side effects — consider ren-segmented or tabs.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-radio/ren-radio.css\"\n  js:\n    - \"rends/components/primitives/ren-radio/ren-radio.js\"\n  notes:\n    - \"JS is only required when using <ren-radio-group> for arrow-key roving tabindex; a plain <fieldset role=\\\"radiogroup\\\"> with .ren-radio labels works CSS-only with native browser radio behavior.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Each option is a <label class=\\\"ren-radio\\\"> containing a real <input type=\\\"radio\\\" name=\\\"<group>\\\">, a <span class=\\\"ren-radio-control\\\"></span>, and the visible label text — in that DOM order so the :checked + .ren-radio-control adjacent selector works.\"\n  - \"All radios in one group share the same name attribute so the browser enforces single-selection.\"\n  - \"Wrap the group in <ren-radio-group> (auto-sets role=\\\"radiogroup\\\") or in a <fieldset> with <legend> + role=\\\"radiogroup\\\" when no JS is desired.\"\n  - \"Use <ren-radio-group orientation=\\\"horizontal\\\"> to switch to row layout; the JS swaps the class to .ren-radio-group-horizontal.\"\n  - \"Never set tabindex manually on inputs inside <ren-radio-group> — the roving-tabindex utility manages it.\"\n\nforbiddenPatterns:\n  - \"Replacing <input type=\\\"radio\\\"> with a styled <div role=\\\"radio\\\"> — loses form submission + native a11y.\"\n  - \"Hiding the input with display: none (breaks focus) — use the built-in visually-hidden clip-path pattern.\"\n  - \"Two radios with different name attributes in the same group (allows multi-selection).\"\n  - \"Using .ren-radio for an icon-only toggle without a visible text label or aria-label.\"\n  - \"Animating the dot via custom keyframes that bypass --duration-state / --ease-playful.\"\n\ntokenPolicy:\n  allowed:\n    - \"Semantic tokens consumed internally: --color-text, --color-border-strong, --color-accent, --color-accent-hover, --color-on-accent, --color-focus-ring.\"\n    - \"Layout / motion tokens: --space-2, --space-3, --space-4, --radius-full, --body-size, --weight-regular, --touch-min, --ring-width, --ring-offset-width, --duration-state, --duration-tactile, --ease-enter, --ease-playful.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors in consumer overrides.\"\n    - \"Custom radio sizes that fall below the 20px control + 44px label touch target.\"\n\naccessibility:\n  required:\n    - \"Real <input type=\\\"radio\\\"> elements so the browser handles single-selection, form submission, and assistive-tech announcement.\"\n    - \".ren-radio label has min-height: var(--touch-min) so the whole row is a 44px touch target.\"\n    - \"Visible :focus-visible ring on .ren-radio-control driven by --color-focus-ring (the native input is visually hidden but focus is delegated to its sibling).\"\n    - \"<ren-radio-group> sets role=\\\"radiogroup\\\" and supports arrow-key navigation (Up/Down vertical, Left/Right horizontal) with loop; selection follows focus.\"\n    - \"Disabled options use <input disabled> (handled via :has(input:disabled)) — do not rely on opacity alone.\"\n    - \"Provide a group label via <legend> inside <fieldset> or aria-label / aria-labelledby on <ren-radio-group>.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-radio/ren-radio.css\">\n<script type=\"module\" src=\"rends/components/primitives/ren-radio/ren-radio.js\"></script>\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<ren-radio-group>\n  <label class=\"ren-radio\"><input type=\"radio\" name=\"plan\" value=\"basic\"><span class=\"ren-radio-control\"></span><span>Basic</span></label>\n  <label class=\"ren-radio\"><input type=\"radio\" name=\"plan\" value=\"pro\"><span class=\"ren-radio-control\"></span><span>Pro</span></label>\n</ren-radio-group>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-radio`\n- `.ren-radio-control`\n- `.ren-radio-group`\n- `.ren-radio-group-horizontal`\n\n## States And Attributes\n\n- `:active`\n- `:checked`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\nIf no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.\n\n## Related Files\n\n- `components/primitives/ren-radio/ren-radio.css`\n- `components/primitives/ren-radio/ren-radio.js`\n- `docs/components/ren-radio.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-radio/component.md",
      "id": "contract:primitive:ren-radio",
      "type": "contract",
      "name": "ren-radio component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-separator Component Contract\n\nVisual or semantic divider between content groups.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-separator` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-separator` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Separator primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a horizontal divider between content groups inside a flow (use <hr class=\\\"ren-separator\\\">).\"\n    - \"You need a vertical divider inside a flex/inline row (use .ren-separator-vertical).\"\n    - \"You need a labeled divider (e.g. \\\"or\\\" between auth options) using .ren-separator-label.\"\n    - \"The divider is purely visual and does not need any interactivity or focus.\"\n    - \"You want spacing tokens (--space-3 / --space-4) baked into the divider's margin instead of ad-hoc CSS.\"\n  avoidWhen:\n    - \"You need a sectioning landmark — use semantic <section> / <aside> with headings instead.\"\n    - \"The divider should also collapse the sidebar / scroll region — use ren-sidebar / a layout primitive.\"\n    - \"You want a decorative border around a card — use the card's --color-border, not a separator.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-separator/ren-separator.css\"\n  notes:\n    - \"CSS-only primitive. There is no colocated JS — do not import one.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Horizontal separators must be a real <hr class=\\\"ren-separator\\\"> element so AT exposes the implicit separator role.\"\n  - \"Vertical separators use a non-<hr> element (<div class=\\\"ren-separator-vertical\\\">) inside a flex row so align-self: stretch can take effect.\"\n  - \"Labeled separators use a flex container: <div class=\\\"ren-separator-label\\\">or</div>; the ::before / ::after pseudo-elements draw the lines automatically — do not add extra <span> rules.\"\n  - \"Do not nest interactive content inside a separator; it is decorative chrome only.\"\n\nforbiddenPatterns:\n  - \"<div class=\\\"ren-separator\\\"> for horizontal dividers — use the native <hr> so screen readers announce the separator.\"\n  - \"border-top: 1px solid #... or border-bottom: 1px solid #... in consumer code; use .ren-separator and let --color-separator drive the color.\"\n  - \"Inline margin overrides (style=\\\"margin: 0\\\") to tighten spacing — set --space-* tokens at a parent scope instead.\"\n  - \"Using .ren-separator-vertical outside a flex / grid container that gives it a measurable cross-axis height.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-separator-color, --ren-separator-margin, --ren-separator-width.\"\n    - \"Semantic tokens consumed internally: --color-separator, --color-text-muted, --space-3, --space-4, --caption-size.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgb / named colors for the separator line.\"\n    - \"Raw pixel widths in inline styles; use --ren-separator-width.\"\n\naccessibility:\n  required:\n    - \"Horizontal separators are <hr> so the implicit role=\\\"separator\\\" is exposed to assistive technology.\"\n    - \"Purely decorative vertical separators may use aria-hidden=\\\"true\\\" if they add no information beyond a sibling label.\"\n    - \"Labels on .ren-separator-label must remain real text (not background-image) so they are announced and translatable.\"\n    - \"Color contrast is not required for decorative separators, but the line must remain visible in both light and dark themes via --color-separator.\"\n    - \"Do not add focusable behavior; separators are not interactive.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-separator/ren-separator.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<hr class=\"ren-separator\">\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-separator`\n- `.ren-separator-horizontal`\n- `.ren-separator-label`\n- `.ren-separator-vertical`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-separator-color`\n- `--ren-separator-margin`\n- `--ren-separator-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-separator/ren-separator.css`\n- `docs/components/ren-separator.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-separator/component.md",
      "id": "contract:primitive:ren-separator",
      "type": "contract",
      "name": "ren-separator component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-skeleton Component Contract\n\nLoading placeholder primitive that respects reduced motion.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-skeleton` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-skeleton` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Skeleton primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"Content is asynchronously loading and you need a placeholder that preserves layout dimensions.\"\n    - \"You want a shimmer that automatically degrades to a static fill under prefers-reduced-motion.\"\n    - \"You need shape-specific placeholders: text line (.ren-skeleton-text), heading (.ren-skeleton-heading), avatar (.ren-skeleton-circle), or block (.ren-skeleton-rect).\"\n    - \"The placeholder is short-lived — it will be replaced by real content within a few seconds.\"\n  avoidWhen:\n    - \"The wait is indeterminate or very long — use ren-spinner with descriptive copy.\"\n    - \"Only a single small inline indicator is needed — use ren-spinner-xs or ren-spinner-sm.\"\n    - \"You want a progress bar with a known percentage — use ren-progress instead.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-skeleton/ren-skeleton.css\"\n  notes:\n    - \"CSS-only primitive. There is no colocated JS — do not import one.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Always combine the base .ren-skeleton with one shape modifier (.ren-skeleton-text, .ren-skeleton-heading, .ren-skeleton-circle, .ren-skeleton-rect) so the size token resolves.\"\n  - \"Wrap a group of skeletons in a container that sets aria-busy=\\\"true\\\" and aria-live=\\\"polite\\\" so assistive tech is told to wait.\"\n  - \"Replace skeleton nodes with real content as soon as the data resolves — do not leave them mounted indefinitely.\"\n  - \"Match the skeleton's dimensions to the final content (avatar size, line count) to avoid layout shift on swap.\"\n\nforbiddenPatterns:\n  - \"Animating with background-image: linear-gradient(...) hex colors directly; use the .ren-skeleton class and let --color-fill / --color-fill-hover drive the shimmer.\"\n  - \"Hardcoded width: 60% or height: 1.5em overrides for headings; use .ren-skeleton-heading and override --avatar-md / --radius-* via tokens if needed.\"\n  - \"Leaving the skeleton in the DOM after content loads — toggle visibility / remove, never repurpose as decoration.\"\n  - \"Using a skeleton for a determinate progress state (e.g. uploading X of Y) — that is a job for ren-progress.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-skeleton-bg, --ren-skeleton-radius, --ren-skeleton-shine, --ren-skeleton-speed.\"\n    - \"Semantic tokens consumed internally: --color-fill, --color-fill-hover, --radius-sm, --radius-md, --radius-full, --avatar-md, --duration-loop-slow, --ease-loop-pulse.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgba shimmer colors.\"\n    - \"Custom animation durations in seconds; theme via --ren-skeleton-speed or --duration-loop-slow.\"\n\naccessibility:\n  required:\n    - \"The container holding skeleton placeholders must set aria-busy=\\\"true\\\" so AT announces the loading state.\"\n    - \"Pair aria-busy with aria-live=\\\"polite\\\" when the content swap should be announced once data resolves.\"\n    - \"Skeleton nodes are presentational; do not put role=\\\"img\\\" or descriptive alt-text on them.\"\n    - \"The shimmer animation must stop under prefers-reduced-motion (the CSS already handles this — do not override animation in consumer code).\"\n    - \"Skeleton color must remain perceivable in both light and dark themes via --color-fill (do not pin to a specific gray).\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-skeleton/ren-skeleton.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<div aria-busy=\"true\" aria-live=\"polite\"><div class=\"ren-skeleton ren-skeleton-heading\"></div><div class=\"ren-skeleton ren-skeleton-text\"></div></div>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-skeleton`\n- `.ren-skeleton-circle`\n- `.ren-skeleton-heading`\n- `.ren-skeleton-rect`\n- `.ren-skeleton-text`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-skeleton-bg`\n- `--ren-skeleton-radius`\n- `--ren-skeleton-shine`\n- `--ren-skeleton-speed`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-skeleton/ren-skeleton.css`\n- `docs/components/ren-skeleton.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-skeleton/component.md",
      "id": "contract:primitive:ren-skeleton",
      "type": "contract",
      "name": "ren-skeleton component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-spinner Component Contract\n\nSmall loading indicator for pending or busy states.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-spinner` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-spinner` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Spinner primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"You need a small inline busy / pending indicator with no known duration.\"\n    - \"You need to indicate loading inside a button or near a single control (xs / sm sizes).\"\n    - \"The spinner sits on a dark / colored surface — use .ren-spinner-light.\"\n    - \"The wait is short and content layout is not yet known (otherwise prefer ren-skeleton for layout preservation).\"\n    - \"You want CSS-only animation that automatically degrades to an opacity pulse under prefers-reduced-motion.\"\n  avoidWhen:\n    - \"Content placeholders that preserve layout — use ren-skeleton for cards, lists, avatars.\"\n    - \"Determinate progress (you know the percentage) — use ren-progress.\"\n    - \"Long-running background operations where the user can navigate away — use ren-toast / ren-banner status messaging instead of a persistent spinner.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-spinner/ren-spinner.css\"\n  notes:\n    - \"CSS-only primitive. There is no colocated JS — do not import one.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Render a <span class=\\\"ren-spinner\\\"> (or another inline-block element) with role=\\\"status\\\" and an aria-label like \\\"Loading\\\".\"\n  - \"Add a size modifier when the default 1.5rem is wrong: .ren-spinner-xs (0.875rem), .ren-spinner-sm (1rem), .ren-spinner-lg (2rem), .ren-spinner-xl (3rem).\"\n  - \"Use .ren-spinner-light only on dark / colored surfaces where the default --color-accent ring would lack contrast.\"\n  - \"When a spinner replaces a button's label, also set aria-busy=\\\"true\\\" on the button so AT announces the busy state.\"\n\nforbiddenPatterns:\n  - \"<div class=\\\"ren-spinner\\\" aria-hidden=\\\"true\\\"> with no accessible label when the spinner is the only signal of busy state.\"\n  - \"Hardcoded border-color: #... overrides; theme via --ren-spinner-color or use .ren-spinner-light for inverted surfaces.\"\n  - \"Custom animation-duration values in inline styles; use --ren-spinner-speed (or --duration-loop).\"\n  - \"Combining .ren-spinner-light with --color-accent overrides — pick one approach; light is for inverted surfaces only.\"\n  - \"Spinner left mounted after the operation finishes — remove it or replace with success / error state.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-spinner-color, --ren-spinner-size, --ren-spinner-speed, --ren-spinner-width.\"\n    - \"Semantic tokens consumed internally: --color-fill-active, --color-accent, --white, --radius-full, --duration-loop, --ease-loop-smooth, --ease-loop-pulse.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgba border colors.\"\n    - \"Raw animation: spin 1s linear infinite; use the .ren-spinner class and --ren-spinner-speed.\"\n\naccessibility:\n  required:\n    - \"The spinner must have role=\\\"status\\\" and an aria-label (e.g. \\\"Loading\\\") when it conveys busy state.\"\n    - \"When the spinner replaces a button label, set aria-busy=\\\"true\\\" on the button so the busy state is exposed once.\"\n    - \"Do not stack multiple labeled spinners in the same live region — they will be announced repeatedly.\"\n    - \"Reduced-motion users see a gentle opacity pulse (built-in); do not override animation in consumer code.\"\n    - \"Color is not the only signal: the spinner is also a moving shape. Ensure the rotation / pulse is visible against the background.\"\n    - \"Provide a textual fallback (\\\"Saving…\\\") near the spinner whenever the wait can exceed a couple of seconds.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-spinner/ren-spinner.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<span class=\"ren-spinner\" role=\"status\" aria-label=\"Loading\"></span>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-spinner`\n- `.ren-spinner-lg`\n- `.ren-spinner-light`\n- `.ren-spinner-sm`\n- `.ren-spinner-xl`\n- `.ren-spinner-xs`\n\n## States And Attributes\n\n- `None detected in the colocated CSS/JS. Check related files before inventing one.`\n\n## Public Token API\n\n- `--ren-spinner-color`\n- `--ren-spinner-size`\n- `--ren-spinner-speed`\n- `--ren-spinner-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-spinner/ren-spinner.css`\n- `docs/components/ren-spinner.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-spinner/component.md",
      "id": "contract:primitive:ren-spinner",
      "type": "contract",
      "name": "ren-spinner component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-switch Component Contract\n\nToggle switch primitive built on native `<input type=\"checkbox\" role=\"switch\">` with checked, hover, focus, active, and disabled states.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-switch` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-switch` primitive.\n- Keep generated markup aligned with the colocated CSS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- The UI requires an instant on/off state that takes effect immediately (e.g. enable/disable a setting, toggle dark mode).\n- The control sits in a settings panel or in-line preference area, not in a form that must be submitted before changes apply.\n- Native checkbox semantics with `role=\"switch\"` are appropriate.\n\n## Do Not Use When\n\n- The choice should only take effect on form submit — use `ren-checkbox` instead.\n- More than two states are possible — use `ren-radio` or `ren-toggle-group`.\n- The control must look like a checkbox (square box with checkmark) — use `ren-checkbox`.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The control flips an immediate on/off setting (dark mode, notifications, feature flags).\"\n    - \"The change should take effect the moment the user toggles it, with no submit step.\"\n    - \"Native checkbox semantics extended with role=\\\"switch\\\" are the right a11y model.\"\n    - \"You want the iOS-style 51×31 track plus full label wrapper for a 44px touch target.\"\n    - \"You need the checked color to come from --color-success (positive enablement), not the brand accent.\"\n  avoidWhen:\n    - \"The change must wait for a Save / Submit step — use ren-checkbox so users can review before commit.\"\n    - \"There are 3+ choices or the option is mutually exclusive within a set — use ren-radio or ren-toggle-group.\"\n    - \"The control needs a checkmark glyph to communicate selection — use ren-checkbox.\"\n    - \"The control is a tab / segmented selector across views — use ren-tabs or ren-toggle-group.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-switch/ren-switch.css\"\n  notes:\n    - \"CSS-only primitive. There is no colocated JS — do not import one.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Wrap the control in a <label class=\\\"ren-switch\\\"> so the entire label area is the click / touch target.\"\n  - \"Use a real <input type=\\\"checkbox\\\" role=\\\"switch\\\"> as the first child; the CSS hides it visually but keeps it focusable and announced.\"\n  - \"Follow the input with <span class=\\\"ren-switch-track\\\"></span>; the thumb is the track's ::after pseudo-element — do not add a separate thumb element.\"\n  - \"Include a visible <span>Label</span> sibling for sighted users; if the switch is icon-only, set aria-label on the input.\"\n  - \"Persist on/off changes immediately via the input's change event — never defer to a separate Save action.\"\n\nforbiddenPatterns:\n  - \"<div role=\\\"switch\\\" tabindex=\\\"0\\\"> custom toggles; always use a real <input type=\\\"checkbox\\\" role=\\\"switch\\\">.\"\n  - \"Hiding the input with display: none — it removes it from the accessibility tree. Keep the existing clip-path / visually-hidden pattern.\"\n  - \"Hardcoded track / thumb colors via background-color in inline styles; theme via --ren-switch-bg, --ren-switch-checked-bg, --ren-switch-thumb-color.\"\n  - \"Putting the switch inside a <form> where it only applies after submit — that is checkbox territory.\"\n  - \"Removing the focus outline on .ren-switch-track without restoring an equivalent :focus-visible ring.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-switch-bg, --ren-switch-checked-bg, --ren-switch-duration, --ren-switch-easing, --ren-switch-height, --ren-switch-thumb-color, --ren-switch-thumb-size, --ren-switch-width.\"\n    - \"Semantic tokens consumed internally: --color-fill-active, --color-fill-hover, --color-success, --color-success-strong, --color-text, --color-focus-ring, --white, --radius-full, --ring-width, --ring-offset-width, --touch-min, --shadow-sm.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / named colors for the checked / unchecked track.\"\n    - \"Custom transition values; use --ren-switch-duration and --ren-switch-easing.\"\n\naccessibility:\n  required:\n    - \"The native <input type=\\\"checkbox\\\" role=\\\"switch\\\"> must remain in the DOM and focusable — assistive tech announces it as a switch with on/off states.\"\n    - \"The whole <label class=\\\"ren-switch\\\"> wrapper provides a touch target of at least --touch-min (44px).\"\n    - \"Focus must show the outline ring on .ren-switch-track via :focus-visible (the CSS already wires this up; do not remove it).\"\n    - \"Do not signal state by color alone — the thumb position (left vs right) is the primary visual cue.\"\n    - \"When the switch is disabled, set disabled on the <input> (the parent .ren-switch:has(input:disabled) handles styling) instead of a custom data attribute.\"\n    - \"If the switch's label is not adjacent text, set aria-label or aria-labelledby on the <input>.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-switch/ren-switch.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<label class=\"ren-switch\">\n  <input type=\"checkbox\" role=\"switch\">\n  <span class=\"ren-switch-track\"></span>\n  <span>Dark mode</span>\n</label>\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-switch`\n- `.ren-switch-track`\n\n## States And Attributes\n\n- `:active`\n- `:checked`\n- `:disabled`\n- `:focus-visible`\n- `:hover`\n- `role=\"switch\"` on the native input (announces toggle semantics to assistive technology)\n\n## Public Token API\n\n- `--ren-switch-bg`\n- `--ren-switch-checked-bg`\n- `--ren-switch-duration`\n- `--ren-switch-easing`\n- `--ren-switch-height`\n- `--ren-switch-thumb-color`\n- `--ren-switch-thumb-size`\n- `--ren-switch-width`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; the `role=\"switch\"` on the input announces the on/off toggle pattern.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum (the track is 51×31 but the label wrapper provides the full touch target).\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone — the thumb position is the primary visual signal.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-switch/ren-switch.css`\n- `docs/components/ren-switch.html`\n- `components/primitives/ren-checkbox/component.md` (sibling primitive — submit-time vs immediate semantics)\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-switch/component.md",
      "id": "contract:primitive:ren-switch",
      "type": "contract",
      "name": "ren-switch component.md"
    },
    {
      "data": {
        "kind": "primitive"
      },
      "body": "# ren-tag Component Contract\n\nCompact keyword, filter, or removable token primitive.\n\nLoad this file after `ren-design.md` and before generating, editing, or reviewing `ren-tag` UI.\n\n## Purpose\n\n- Provide the agent-facing public contract for the `ren-tag` primitive.\n- Keep generated markup aligned with the colocated CSS/JS source.\n- Route theming through semantic tokens and the component token API instead of ad hoc styles.\n\n## Use When\n\n- You need the Tag primitive behavior or visual role.\n- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.\n- The implementation can use the public selectors, states, and imports listed here.\n\n## Do Not Use When\n\n- Native HTML plus `base/primitive-zero.md` is enough.\n- A simpler primitive can express the UI without this primitive.\n- You would need to invent undocumented selectors, states, or JavaScript APIs.\n\n## aiHints\n\n```yaml\nselectionCriteria:\n  useWhen:\n    - \"The user picks, adds, or removes a value (filter chip, selected keyword, attached label).\"\n    - \"You need a removable token with an embedded dismiss button (.ren-tag-dismiss).\"\n    - \"You need a toggleable filter pill driven by aria-pressed or data-selected (.ren-tag-clickable).\"\n    - \"You need a status-tinted token (.ren-tag-primary / .ren-tag-success / .ren-tag-warning / .ren-tag-danger) tied to a semantic color.\"\n    - \"Multiple tokens flow together in a wrap — use .ren-tag-group as the parent.\"\n  avoidWhen:\n    - \"The element is a static, non-interactive status pill — use ren-badge.\"\n    - \"The element triggers an imperative action (submit, open dialog) — use ren-button.\"\n    - \"The element is a hyperlink to another page — use ren-link.\"\n    - \"The element is a form-style multi-select dropdown option — use ren-select / ren-combobox option markup.\"\n\ncanonicalImports:\n  css:\n    - \"rends/components/primitives/ren-tag/ren-tag.css\"\n  notes:\n    - \"CSS-only primitive. There is no colocated JS — wire up dismiss/click handlers in your own script.\"\n    - \"If the page already imports rends/components/index.css, do not import the CSS again.\"\n\nrequiredMarkup:\n  - \"Render the tag as an inline element: <span class=\\\"ren-tag\\\"> for static, <span class=\\\"ren-tag ren-tag-clickable\\\" tabindex=\\\"0\\\" role=\\\"button\\\"> for toggleable filters.\"\n  - \"Removable tags must include <button class=\\\"ren-tag-dismiss\\\" type=\\\"button\\\" aria-label=\\\"Remove <name>\\\">; never use a non-button element for dismissal.\"\n  - \"Toggle / filter tags must mirror selection state via aria-pressed=\\\"true|false\\\" (preferred) or data-selected; do not rely on color alone.\"\n  - \"Group multiple tags inside a parent with class=\\\"ren-tag-group\\\" so the gap and wrap behavior come from the design system.\"\n  - \"Combine at most one color variant (.ren-tag-primary | .ren-tag-success | .ren-tag-warning | .ren-tag-danger) with at most one size (.ren-tag-sm | .ren-tag-lg).\"\n\nforbiddenPatterns:\n  - \"<div class=\\\"ren-tag\\\" onclick=...> for clickable tags — use a real <button> or .ren-tag-clickable with role=\\\"button\\\" and tabindex=\\\"0\\\".\"\n  - \"Custom dismiss icons drawn with background-image; place a text \\\"×\\\" or an <svg aria-hidden=\\\"true\\\"> inside .ren-tag-dismiss instead.\"\n  - \"Hardcoded background-color: #... overrides; theme via --ren-tag-bg / --ren-tag-border-color / --ren-tag-color.\"\n  - \"Using ren-tag-danger purely for emphasis on a non-danger value — semantic color variants must match meaning (danger = destructive / error).\"\n  - \"Removing the :focus-visible outline on .ren-tag-clickable or .ren-tag-dismiss without restoring a visible ring.\"\n\ntokenPolicy:\n  allowed:\n    - \"Component tokens: --ren-tag-bg, --ren-tag-border-color, --ren-tag-color, --ren-tag-font-size, --ren-tag-font-weight, --ren-tag-gap, --ren-tag-height, --ren-tag-padding-x, --ren-tag-radius.\"\n    - \"Semantic tokens consumed internally: --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-fill-active, --color-border, --color-border-strong, --color-accent, --color-accent-subtle, --color-on-accent, --color-success / --color-success-subtle / --color-success-strong, --color-warning / --color-warning-subtle / --color-warning-strong, --color-danger / --color-danger-subtle / --color-danger-strong, --color-focus-ring, --stroke-1, --radius-full, --ring-width, --ring-offset-width.\"\n  forbidden:\n    - \"Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code.\"\n    - \"Hardcoded hex / rgba background and border colors.\"\n    - \"Inline padding / font-size overrides; pick the .ren-tag-sm / .ren-tag-lg variant or override --ren-tag-padding-x / --ren-tag-font-size.\"\n\naccessibility:\n  required:\n    - \"Clickable tags must be focusable: either a real <button>, or use .ren-tag-clickable with tabindex=\\\"0\\\" and role=\\\"button\\\" plus Space / Enter handling.\"\n    - \"Toggle state must be exposed via aria-pressed; data-selected is for styling parity only, not a substitute for ARIA.\"\n    - \"Dismiss buttons need an aria-label that names the value being removed (e.g. \\\"Remove React\\\"), not just \\\"Remove\\\".\"\n    - \"Color variants must not be the sole signal of meaning — pair .ren-tag-danger with text or an icon that conveys the same state.\"\n    - \":focus-visible outline must remain visible on both .ren-tag-clickable and .ren-tag-dismiss; the CSS wires this to --color-focus-ring.\"\n    - \"Long tag text truncates with ellipsis (.ren-tag > span overflow rule); when this happens, surface the full value via title or an off-screen label.\"\n```\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/components/primitives/ren-tag/ren-tag.css\">\n<!-- No colocated JavaScript file detected. -->\n```\n\nIf the page already imports `rends/components/index.css`, do not import the CSS twice.\n\n## Canonical Markup\n\n```html\n<span class=\"ren-tag ren-tag-removable\">Accessibility<button class=\"ren-tag-dismiss\" type=\"button\" aria-label=\"Remove Accessibility\">×</button></span>\n\n```\n\nUse the docs page and source files listed below for full examples before adding production markup.\n\n## Variants And Public Selectors\n\n- `.ren-tag`\n- `.ren-tag-clickable`\n- `.ren-tag-danger`\n- `.ren-tag-dismiss`\n- `.ren-tag-group`\n- `.ren-tag-lg`\n- `.ren-tag-primary`\n- `.ren-tag-sm`\n- `.ren-tag-success`\n- `.ren-tag-warning`\n\n## States And Attributes\n\n- `[aria-pressed]`\n- `[data-selected]`\n- `:active`\n- `:focus-visible`\n- `:hover`\n\n## Public Token API\n\n- `--ren-tag-bg`\n- `--ren-tag-border-color`\n- `--ren-tag-color`\n- `--ren-tag-font-size`\n- `--ren-tag-font-weight`\n- `--ren-tag-gap`\n- `--ren-tag-height`\n- `--ren-tag-padding-x`\n- `--ren-tag-radius`\n\nTheme through these public custom properties before reaching for selectors.\n\n## Accessibility Contract\n\n- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.\n- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.\n- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.\n- Respect reduced motion by using RenDS duration/easing tokens only.\n- Do not communicate state through color alone.\n- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.\n\n## Related Files\n\n- `components/primitives/ren-tag/ren-tag.css`\n- `docs/components/ren-tag.html`\n- `ren-design.md`\n- `tokens/tokens.md`\n- `base/layouts.md`\n\n## Test Expectations\n\n- Run component or docs a11y coverage when markup, states, or ARIA change.\n- Run CSS lint when selectors, tokens, or visual states change.\n- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.\n",
      "path": "components/primitives/ren-tag/component.md",
      "id": "contract:primitive:ren-tag",
      "type": "contract",
      "name": "ren-tag component.md"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Accordion — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .demo-acc { background: var(--color-surface); border-radius: var(--radius-md); overflow: hidden; }\n    .demo-acc details { border-bottom: 1px solid var(--color-border); }\n    .demo-acc details:last-child { border-bottom: 0; }\n    .demo-acc summary { list-style: none; padding: var(--space-3) var(--space-4); cursor: pointer; font-weight: var(--weight-medium); display: flex; justify-content: space-between; align-items: center; }\n    .demo-acc summary::-webkit-details-marker { display: none; }\n    .demo-acc summary::marker { display: none; content: ''; }\n    .demo-acc summary::after {\n      content: '';\n      width: 16px; height: 16px;\n      background-image: url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>\");\n      background-repeat: no-repeat;\n      background-position: center;\n      background-size: contain;\n      border: none;             /* override classless.css base chevron drawn with borders */\n      transform: rotate(0deg);  /* override classless.css base rotate(-45deg) */\n      transition: transform 150ms;\n      flex-shrink: 0;\n    }\n    .demo-acc details[open] summary::after { transform: rotate(180deg); }\n    .demo-acc summary:hover { background: var(--color-fill); }\n    .demo-acc-body { padding: 0 var(--space-4) var(--space-4); color: var(--color-text-secondary); font-size: var(--text-sm); line-height: 1.6; }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\" aria-current=\"page\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Accordion</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Accordion <span class=\"dx-api-badge dx-api-badge-hybrid\" title=\"Renders without JS; JS adds enhancements\">Hybrid</span></h1>\n        <p class=\"lede\">Stack of collapsible panels. Built on the native <code>&lt;details&gt;</code> + <code>&lt;summary&gt;</code> pair so accessibility comes for free. Single-open or multi-open mode, animated expand / collapse.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Accordions hide secondary content behind disclosure triggers. Use them when the page has more sections than fits on a screen and most users only need one or two at a time — FAQs, settings groups, app feature tours. RenDS just styles the native <code>&lt;details&gt;</code>; the platform does the open/close logic, keyboard handling, and the <code>open</code> attribute.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Don't hide critical content in an accordion.</strong> If users need to read it to make a decision, show it. Accordions hide content one level removed from the primary task.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <div class=\"demo-acc\" style=\"max-width: 520px;\">\n              <details open><summary>How does pricing work?</summary><div class=\"demo-acc-body\">We charge per user, per month. The Basic plan is $9; Pro is $29 and removes seat caps. Annual billing saves 20%.</div></details>\n              <details><summary>Can I cancel anytime?</summary><div class=\"demo-acc-body\">Yes — cancel from your billing settings. Your plan stays active until the end of the current period; we don't pro-rate refunds.</div></details>\n              <details><summary>Do you offer a free trial?</summary><div class=\"demo-acc-body\">14 days, no credit card. After the trial you'll be downgraded to the free tier unless you pick a plan.</div></details>\n            </div>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-accordion&gt;\n  &lt;details open&gt;\n    &lt;summary&gt;How does pricing work?&lt;/summary&gt;\n    &lt;p&gt;We charge per user, per month…&lt;/p&gt;\n  &lt;/details&gt;\n  &lt;details&gt;\n    &lt;summary&gt;Can I cancel anytime?&lt;/summary&gt;\n    &lt;p&gt;Yes — cancel from your billing settings…&lt;/p&gt;\n  &lt;/details&gt;\n&lt;/ren-accordion&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-accordion</code></td><td>Stack container. Borders the group; visually links the items.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-accordion-item</code></td><td>Optional class on each <code>&lt;details&gt;</code>. Useful when accordions live without the wrapper.</td></tr>\n          </tbody>\n        </table>\n        <h3>Web Component attributes</h3>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">single</code></td><td><span class=\"dx-api-type\">boolean</span></td><td><span class=\"dx-api-default\">false</span></td><td>If present, opening one item closes the others (mutually exclusive mode).</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <p>Native <code>&lt;details&gt;</code> handles all the accessibility — keyboard, screen reader announcement of expanded state, the <code>aria-expanded</code> wiring. You don't need to add anything.</p>\n        <ul>\n          <li><kbd>Tab</kbd> moves focus to a closed <code>&lt;summary&gt;</code>; <kbd>Enter</kbd> or <kbd>Space</kbd> toggles open / closed.</li>\n          <li>Screen readers announce \"expanded\" or \"collapsed\" automatically.</li>\n          <li>The summary is the trigger — keep it short and start with a verb or noun the user is looking for.</li>\n        </ul>\n      </section>\n\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-accordion.html",
      "id": "docs:docs/components/ren-accordion.html",
      "type": "docs_page",
      "name": "ren-accordion docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>AI Patterns — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); }\n    .dx-demo-code   { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow  { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label {\n      font-size: var(--text-xs);\n      font-weight: var(--weight-semibold);\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--color-text-muted);\n      padding-top: var(--space-3);\n    }\n    .dx-vrow-items {\n      padding: var(--space-3);\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n      display: grid;\n      gap: var(--space-3);\n    }\n    @media (max-width: 720px) {\n      .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); }\n      .dx-vrow-label { padding-top: 0; }\n    }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\">\n    <div class=\"dx-nav-inner\">\n      <a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a>\n      <nav class=\"dx-nav-menu\" aria-label=\"Primary\">\n        <a href=\"../index.html\">Docs</a>\n        <a href=\"../components.html\" aria-current=\"page\">Components</a>\n        <a href=\"../../templates/index.html\">Templates</a>\n        <a href=\"../../create/index.html\">Theme Builder</a>\n      </nav>\n      <div class=\"dx-nav-actions\">\n        <a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a>\n        <a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\" aria-current=\"page\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n      </ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">AI Patterns</li></ol></nav>\n        <p class=\"dx-kicker\">Pattern</p>\n        <h1>AI Patterns <span class=\"dx-api-badge dx-api-badge-css\" title=\"Works without any JavaScript\">CSS-only</span></h1>\n        <p class=\"lede\"><strong>CSS-only pattern. No Web Component, no colocated JS — bring your own inference layer.</strong> Visual primitives for chat-style AI surfaces: assistant message bubbles, streaming + typing indicators, inline citations, source lists, confidence bars, prompt input, and action affordances (copy, regenerate, thumbs).</p>\n      </header>\n\n      <!-- 1. OVERVIEW -->\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>AI chat surfaces share a small visual vocabulary: a thread of assistant messages, a streaming response indicator while the model is thinking, inline citations linking to source documents, an action row under the latest message (copy, regenerate, thumbs up/down), and a prompt input at the bottom. RenDS provides every one of those as a stand-alone CSS primitive you can compose freely.</p>\n        <p><strong>Important: this is a CSS-only pattern.</strong> There is no <code>&lt;ren-ai&gt;</code> custom element, no JavaScript file that ships with the package, and no streaming or state machine baked in. You bring:</p>\n        <ul>\n          <li>The inference call (OpenAI, Anthropic, your own backend, etc.).</li>\n          <li>The streaming text update logic (append tokens to <code>.ren-ai-message-content</code>).</li>\n          <li>The action handlers (copy → clipboard API, thumbs → your feedback endpoint, regenerate → re-call the model).</li>\n          <li>The thread state (which message is streaming, message history, scroll-to-bottom).</li>\n        </ul>\n        <p>RenDS does the visual + accessibility plumbing — tokens, layout, focus rings, live regions, reduced-motion fallbacks — so your application code focuses on the model logic, not the chrome.</p>\n\n        <h3>When to use</h3>\n        <ul>\n          <li>Building a chat-style assistant UI (in-app help, support agent, document Q&A).</li>\n          <li>Adding an \"Ask AI\" panel next to existing content.</li>\n          <li>Rendering AI-generated text with provenance (citations + sources).</li>\n        </ul>\n\n        <h3>When not to use</h3>\n        <ul>\n          <li><strong>Static text labeled \"AI-generated\".</strong> Use a <a href=\"ren-badge.html\" class=\"ren-link\">Badge</a> or the <code>.ren-ai-slug</code> primitive inline instead.</li>\n          <li><strong>Form-style \"AI assist\".</strong> If the AI just fills a field, use <a href=\"ren-field.html\" class=\"ren-link\">Field</a> with a custom button — no chat shell needed.</li>\n          <li><strong>Real-time co-edit / multiplayer.</strong> Out of scope — this pattern assumes turn-based request → response.</li>\n        </ul>\n\n        <div class=\"dx-callout\">\n          <p><strong>Visual primitives, not a chat engine.</strong> If you need a higher-level component that manages thread state, streaming, and message history, build it on top using the classes here. The intent is to give you the look without imposing a state shape.</p>\n        </div>\n      </section>\n\n      <!-- 2. ANATOMY -->\n      <section class=\"dx-section\" id=\"anatomy\">\n        <p class=\"dx-kicker\">Structure</p>\n        <h2>Anatomy</h2>\n        <p>A complete AI surface has six possible regions. Pick what you need.</p>\n        <ol>\n          <li><strong>AI slug</strong> (<code>.ren-ai-slug</code>) — inline label marking content as AI-generated. Place wherever you'd write \"AI\" in text.</li>\n          <li><strong>Message</strong> (<code>.ren-ai-message</code>) — assistant bubble. Header (avatar / title), content (the text), footer (actions). User messages style themselves — keep them as plain bubbles or use your own.</li>\n          <li><strong>Indicators</strong> (<code>.ren-ai-typing</code>, <code>.ren-ai-streaming</code>) — shown while the model is generating. The typing dots show \"thinking\"; streaming pulses the latest token.</li>\n          <li><strong>Citations + sources</strong> (<code>.ren-ai-citation</code>, <code>.ren-ai-sources</code>) — superscript references in the body that link to a list of sources rendered below the message.</li>\n          <li><strong>Confidence</strong> (<code>.ren-ai-confidence</code>) — optional bar showing model confidence (data-level=\"high|medium|low\").</li>\n          <li><strong>Prompt</strong> (<code>.ren-ai-prompt</code>) — the input at the bottom of the thread with a send button.</li>\n        </ol>\n      </section>\n\n      <!-- 3. DEMO -->\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <div role=\"log\" aria-live=\"polite\" aria-label=\"Conversation with assistant\" style=\"display: grid; gap: var(--space-3); max-width: 580px; margin-inline: auto;\">\n\n              <!-- user turn -->\n              <div style=\"justify-self: end; background: var(--color-accent); color: var(--color-on-accent); padding: var(--space-3) var(--space-4); border-radius: var(--radius-lg) var(--radius-lg) var(--radius-sm) var(--radius-lg); max-width: 80%; font-size: var(--text-sm);\">\n                What's the difference between a Tooltip and a Popover?\n              </div>\n\n              <!-- assistant turn with full chrome -->\n              <div class=\"ren-ai-message\">\n                <header class=\"ren-ai-message-header\">\n                  <span class=\"ren-ai-slug\">AI</span>\n                  <span style=\"color: var(--color-text-muted); font-size: var(--text-xs);\">Assistant · just now</span>\n                </header>\n                <div class=\"ren-ai-message-content\">\n                  Tooltip is a passive hint shown on hover or focus — typically just text<sup class=\"ren-ai-citation\">1</sup>.\n                  Popover is interactive: it can hold forms, multiple actions, anything keyboard-navigable<sup class=\"ren-ai-citation\">2</sup>.\n                  Tooltip never traps focus; Popover does.\n                </div>\n\n                <div class=\"ren-ai-confidence\" data-level=\"high\">\n                  <span style=\"font-size: var(--text-xs); color: var(--color-text-muted);\">Confidence: High</span>\n                  <div class=\"ren-ai-confidence-bar\"><div class=\"ren-ai-confidence-fill\"></div></div>\n                </div>\n\n                <ol class=\"ren-ai-sources\">\n                  <li class=\"ren-ai-source\">\n                    <span class=\"ren-ai-source-number\">1</span>\n                    <a class=\"ren-ai-source-title\" href=\"#\">Tooltip — RenDS docs</a>\n                    <span class=\"ren-ai-source-url\">rends.dev/docs/components/ren-tooltip.html</span>\n                  </li>\n                  <li class=\"ren-ai-source\">\n                    <span class=\"ren-ai-source-number\">2</span>\n                    <a class=\"ren-ai-source-title\" href=\"#\">Popover — RenDS docs</a>\n                    <span class=\"ren-ai-source-url\">rends.dev/docs/components/ren-popover.html</span>\n                  </li>\n                </ol>\n\n                <footer class=\"ren-ai-message-footer\">\n                  <div class=\"ren-ai-actions\">\n                    <button type=\"button\" class=\"ren-ai-action\" aria-label=\"Copy response\">📋 Copy</button>\n                    <button type=\"button\" class=\"ren-ai-action\" aria-label=\"Regenerate response\">↻ Regenerate</button>\n                    <button type=\"button\" class=\"ren-ai-action ren-ai-action-thumbs\" aria-label=\"Helpful\">👍</button>\n                    <button type=\"button\" class=\"ren-ai-action ren-ai-action-thumbs\" aria-label=\"Not helpful\">👎</button>\n                  </div>\n                </footer>\n              </div>\n\n              <!-- typing indicator -->\n              <div class=\"ren-ai-typing\" aria-live=\"polite\" aria-label=\"Assistant is typing\">\n                <span class=\"ren-ai-typing-dots\"><span></span><span></span><span></span></span>\n                <span style=\"font-size: var(--text-xs); color: var(--color-text-muted);\">Thinking…</span>\n              </div>\n            </div>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div role=\"log\" aria-live=\"polite\" aria-label=\"Conversation with assistant\"&gt;\n\n  &lt;!-- assistant message --&gt;\n  &lt;div class=\"ren-ai-message\"&gt;\n    &lt;header class=\"ren-ai-message-header\"&gt;\n      &lt;span class=\"ren-ai-slug\"&gt;AI&lt;/span&gt;\n      &lt;span&gt;Assistant · just now&lt;/span&gt;\n    &lt;/header&gt;\n\n    &lt;div class=\"ren-ai-message-content\"&gt;\n      Tooltip is passive&lt;sup class=\"ren-ai-citation\"&gt;1&lt;/sup&gt;.\n      Popover is interactive&lt;sup class=\"ren-ai-citation\"&gt;2&lt;/sup&gt;.\n    &lt;/div&gt;\n\n    &lt;div class=\"ren-ai-confidence\" data-level=\"high\"&gt;\n      &lt;span&gt;Confidence: High&lt;/span&gt;\n      &lt;div class=\"ren-ai-confidence-bar\"&gt;\n        &lt;div class=\"ren-ai-confidence-fill\"&gt;&lt;/div&gt;\n      &lt;/div&gt;\n    &lt;/div&gt;\n\n    &lt;ol class=\"ren-ai-sources\"&gt;\n      &lt;li class=\"ren-ai-source\"&gt;\n        &lt;span class=\"ren-ai-source-number\"&gt;1&lt;/span&gt;\n        &lt;a class=\"ren-ai-source-title\" href=\"…\"&gt;Tooltip — RenDS docs&lt;/a&gt;\n        &lt;span class=\"ren-ai-source-url\"&gt;rends.dev/…&lt;/span&gt;\n      &lt;/li&gt;\n    &lt;/ol&gt;\n\n    &lt;footer class=\"ren-ai-message-footer\"&gt;\n      &lt;div class=\"ren-ai-actions\"&gt;\n        &lt;button class=\"ren-ai-action\"&gt;📋 Copy&lt;/button&gt;\n        &lt;button class=\"ren-ai-action\"&gt;↻ Regenerate&lt;/button&gt;\n        &lt;button class=\"ren-ai-action ren-ai-action-thumbs\"&gt;👍&lt;/button&gt;\n      &lt;/div&gt;\n    &lt;/footer&gt;\n  &lt;/div&gt;\n\n  &lt;!-- typing indicator (while model is generating) --&gt;\n  &lt;div class=\"ren-ai-typing\" aria-live=\"polite\"&gt;\n    &lt;span class=\"ren-ai-typing-dots\"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;\n    &lt;span&gt;Thinking…&lt;/span&gt;\n  &lt;/div&gt;\n&lt;/div&gt;</code></pre>\n        </div>\n      </section>\n\n      <!-- 4. VARIANTS -->\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Visual</p>\n        <h2>Variants</h2>\n\n        <div class=\"dx-vgrid\">\n\n          <div class=\"dx-vrow\">\n            <div class=\"dx-vrow-label\">AI slug</div>\n            <div class=\"dx-vrow-items\">\n              <p>Inline marker that flags AI-generated content. Wear it on a button, in copy, or on a card.</p>\n              <p style=\"margin: 0;\">This text was <span class=\"ren-ai-slug\">AI</span> generated.</p>\n              <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;span class=\"ren-ai-slug\"&gt;AI&lt;/span&gt;</code></pre>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <div class=\"dx-vrow-label\">Streaming</div>\n            <div class=\"dx-vrow-items\">\n              <p>Add <code>.ren-ai-streaming</code> on the message bubble while tokens are being appended to <code>.ren-ai-message-content</code>. It animates the trailing edge as a \"still talking\" cue.</p>\n              <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;div class=\"ren-ai-message ren-ai-streaming\"&gt;\n  &lt;div class=\"ren-ai-message-content\"&gt;The capital of France is Pa&lt;/div&gt;\n&lt;/div&gt;</code></pre>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <div class=\"dx-vrow-label\">Typing</div>\n            <div class=\"dx-vrow-items\">\n              <p>Pre-streaming \"thinking\" state. Three pulsing dots inside <code>.ren-ai-typing-dots</code>.</p>\n              <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;div class=\"ren-ai-typing\" aria-live=\"polite\"&gt;\n  &lt;span class=\"ren-ai-typing-dots\"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;\n  &lt;span&gt;Thinking…&lt;/span&gt;\n&lt;/div&gt;</code></pre>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <div class=\"dx-vrow-label\">Skeleton</div>\n            <div class=\"dx-vrow-items\">\n              <p>Placeholder for a message that hasn't started streaming yet (e.g. initial load of a saved thread).</p>\n              <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;div class=\"ren-ai-message\"&gt;\n  &lt;div class=\"ren-ai-skeleton\"&gt;\n    &lt;div class=\"ren-ai-skeleton-line\"&gt;&lt;/div&gt;\n    &lt;div class=\"ren-ai-skeleton-line\"&gt;&lt;/div&gt;\n  &lt;/div&gt;\n&lt;/div&gt;</code></pre>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <div class=\"dx-vrow-label\">Confidence</div>\n            <div class=\"dx-vrow-items\">\n              <p>Three levels via <code>data-level</code>. The bar fills proportionally and color-codes (high → success, medium → warning, low → muted).</p>\n              <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;div class=\"ren-ai-confidence\" data-level=\"high|medium|low\"&gt;\n  &lt;span&gt;Confidence: High&lt;/span&gt;\n  &lt;div class=\"ren-ai-confidence-bar\"&gt;\n    &lt;div class=\"ren-ai-confidence-fill\"&gt;&lt;/div&gt;\n  &lt;/div&gt;\n&lt;/div&gt;</code></pre>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <div class=\"dx-vrow-label\">Citations</div>\n            <div class=\"dx-vrow-items\">\n              <p>Inline <code>&lt;sup class=\"ren-ai-citation\"&gt;</code> references that link to the sources list under the message.</p>\n              <pre class=\"dx-pre\" tabindex=\"0\"><code>The capital is Paris&lt;sup class=\"ren-ai-citation\"&gt;1&lt;/sup&gt;.\n\n&lt;ol class=\"ren-ai-sources\"&gt;\n  &lt;li class=\"ren-ai-source\"&gt;\n    &lt;span class=\"ren-ai-source-number\"&gt;1&lt;/span&gt;\n    &lt;a class=\"ren-ai-source-title\" href=\"…\"&gt;World Atlas, 2024&lt;/a&gt;\n    &lt;span class=\"ren-ai-source-url\"&gt;example.com/atlas&lt;/span&gt;\n  &lt;/li&gt;\n&lt;/ol&gt;</code></pre>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <div class=\"dx-vrow-label\">Prompt input</div>\n            <div class=\"dx-vrow-items\">\n              <p>Sticky composer at the bottom of the thread. Plain <code>&lt;textarea&gt;</code> + send <code>&lt;button&gt;</code> wrapped in <code>.ren-ai-prompt</code>.</p>\n              <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;form class=\"ren-ai-prompt\" action=\"/api/chat\" method=\"post\"&gt;\n  &lt;textarea class=\"ren-ai-prompt-input\" name=\"prompt\"\n            placeholder=\"Ask anything…\"\n            rows=\"1\"&gt;&lt;/textarea&gt;\n  &lt;button type=\"submit\" class=\"ren-ai-prompt-send\" aria-label=\"Send\"&gt;→&lt;/button&gt;\n&lt;/form&gt;</code></pre>\n            </div>\n          </div>\n\n        </div>\n      </section>\n\n      <!-- 5. API -->\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n\n        <h3>CSS classes</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-slug</code></td><td>Inline \"AI\" pill. Use anywhere AI-generated content needs an explicit marker.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-message</code></td><td>Assistant message bubble. Wraps header / content / footer.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-message-header</code></td><td>Top row inside the bubble: slug, author name, timestamp.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-message-content</code></td><td>The body text. Append streaming tokens here.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-message-footer</code></td><td>Bottom row: confidence, sources, actions.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-streaming</code></td><td>Add to <code>.ren-ai-message</code> while tokens are streaming. Animates the trailing edge.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-typing</code></td><td>Pre-streaming \"thinking\" container. Sibling, not nested, of the streaming message.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-typing-dots</code></td><td>Three pulsing dots inside <code>.ren-ai-typing</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-skeleton</code> / <code class=\"dx-api-name\">.ren-ai-skeleton-line</code></td><td>Placeholder for a not-yet-loaded message.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-citation</code></td><td>On <code>&lt;sup&gt;</code> inline in content. Renders as a clickable reference number.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-sources</code></td><td>Ordered list of source entries below the message body.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-source</code></td><td>One source list item. Number + title + url.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-source-number</code> / <code class=\"dx-api-name\">-title</code> / <code class=\"dx-api-name\">-url</code></td><td>Composable parts of a source entry.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-confidence</code></td><td>Confidence indicator. Set <code>data-level=\"high\" | \"medium\" | \"low\"</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-confidence-bar</code> / <code class=\"dx-api-name\">-fill</code></td><td>Track + filled portion of the bar.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-actions</code></td><td>Trailing row of action buttons under a message.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-action</code></td><td>One action button (copy, regenerate, etc.). Real <code>&lt;button&gt;</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-action-thumbs</code></td><td>Variant for thumbs up / down feedback buttons.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-prompt</code></td><td>Wrapper for the composer at the bottom of the thread.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-prompt-input</code></td><td>The <code>&lt;textarea&gt;</code> styled to grow with content.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-ai-prompt-send</code></td><td>The send button. Icon-only by convention.</td></tr>\n          </tbody>\n        </table>\n\n        <h3>Data attributes</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Attribute</th><th>Where</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">data-level</code></td><td><code>.ren-ai-confidence</code></td><td><code>high</code> / <code>medium</code> / <code>low</code>. Tints the bar.</td></tr>\n          </tbody>\n        </table>\n\n        <h3>JavaScript</h3>\n        <p>None ships with this pattern. The customer code:</p>\n        <ul>\n          <li>Appends tokens to <code>.ren-ai-message-content</code> during streaming.</li>\n          <li>Adds / removes <code>.ren-ai-streaming</code> to flip the trailing-edge animation.</li>\n          <li>Wires <code>.ren-ai-action</code> click handlers (clipboard for copy, callback for regenerate / thumbs).</li>\n          <li>Manages the thread state (which message is active, scroll-to-bottom, save history).</li>\n          <li>Handles the <code>.ren-ai-prompt</code> form submission (call the model, render the response).</li>\n        </ul>\n\n        <div class=\"dx-callout\">\n          <p><strong>Coming in 0.9.0:</strong> selectors for error messages (<code>.ren-ai-error</code>), file upload chips (<code>.ren-ai-file-chip</code>), tool-call display (<code>.ren-ai-tool-call</code>), and edit-and-resend (<code>.ren-ai-action-edit</code>). Still CSS-only — same philosophy.</p>\n        </div>\n      </section>\n\n      <!-- 6. ACCESSIBILITY -->\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n\n        <h3>Live regions</h3>\n        <ul>\n          <li>Wrap the thread in <code>role=\"log\"</code> + <code>aria-live=\"polite\"</code>. New messages are announced as they appear.</li>\n          <li>For long streaming responses, <strong>announce the final text once when complete</strong>, not character by character. Strategy: render the streaming text inside an off-live container, then move it into a live region when streaming finishes.</li>\n          <li>The typing indicator carries its own <code>aria-live=\"polite\"</code> + <code>aria-label=\"Assistant is typing\"</code> so it's announced once.</li>\n        </ul>\n\n        <h3>Focus management</h3>\n        <ul>\n          <li><strong>Don't auto-scroll while the user is reading.</strong> Pause auto-scroll when the user scrolls up from the bottom; resume when they reach the bottom again.</li>\n          <li><strong>Don't move focus on each new message</strong> — focus stays where the user put it.</li>\n          <li><strong>Do</strong> move focus to the prompt input after submitting (the user is likely typing the next message).</li>\n        </ul>\n\n        <h3>Action buttons</h3>\n        <ul>\n          <li>Always include an <strong>accessible name</strong> on icon-only actions (<code>aria-label=\"Copy response\"</code>, <code>aria-label=\"Regenerate\"</code>).</li>\n          <li>Toggle state on thumbs: <code>aria-pressed=\"true\"</code> when the user clicked it.</li>\n          <li>\"Copy\" is the most-used affordance — make sure it works with no extra modal.</li>\n        </ul>\n\n        <h3>Citations + sources</h3>\n        <ul>\n          <li>The citation <code>&lt;sup&gt;</code> should be a real <code>&lt;a href=\"#source-N\"&gt;</code> so keyboard / screen-reader users can jump to the source list.</li>\n          <li>Source URLs should be readable to screen readers — don't replace the entire URL with an icon.</li>\n        </ul>\n\n        <h3>Reduced motion</h3>\n        <p>All animations (streaming pulse, typing dots, skeleton shimmer) respect <code>prefers-reduced-motion: reduce</code> and fall back to static / opacity-only states. You don't have to wire this — the CSS handles it.</p>\n\n        <div class=\"dx-callout\">\n          <p><strong>Always provide a way out.</strong> An AI thread without a \"stop generating\" button traps the user when the model goes off the rails. Include a cancel affordance — RenDS doesn't enforce it but it's the most important AI-UX rule.</p>\n        </div>\n      </section>\n\n      <!-- 7. EXAMPLES -->\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n\n        <h3>Streaming a response</h3>\n        <p>Render an empty <code>.ren-ai-message</code> with <code>.ren-ai-streaming</code> while you append tokens to <code>.ren-ai-message-content</code>.</p>\n        <pre class=\"dx-pre\" tabindex=\"0\"><code>const msg = document.createElement('div');\nmsg.className = 'ren-ai-message ren-ai-streaming';\nmsg.innerHTML = `\n  &lt;header class=\"ren-ai-message-header\"&gt;\n    &lt;span class=\"ren-ai-slug\"&gt;AI&lt;/span&gt;\n  &lt;/header&gt;\n  &lt;div class=\"ren-ai-message-content\"&gt;&lt;/div&gt;\n`;\nthread.append(msg);\nconst target = msg.querySelector('.ren-ai-message-content');\n\nfor await (const chunk of streamFromModel(prompt)) {\n  target.append(chunk);\n}\n\n// done streaming\nmsg.classList.remove('ren-ai-streaming');</code></pre>\n\n        <h3>Copy action with clipboard API</h3>\n        <pre class=\"dx-pre\" tabindex=\"0\"><code>thread.addEventListener('click', async (e) =&gt; {\n  const btn = e.target.closest('.ren-ai-action');\n  if (!btn || btn.dataset.action !== 'copy') return;\n\n  const msg = btn.closest('.ren-ai-message');\n  const text = msg.querySelector('.ren-ai-message-content').innerText;\n  await navigator.clipboard.writeText(text);\n  toast.success('Copied');\n});</code></pre>\n\n        <h3>Thumbs feedback with aria-pressed</h3>\n        <pre class=\"dx-pre\" tabindex=\"0\"><code>// Markup\n&lt;button type=\"button\"\n        class=\"ren-ai-action ren-ai-action-thumbs\"\n        aria-pressed=\"false\"\n        aria-label=\"Helpful\"&gt;👍&lt;/button&gt;\n\n// JS\nbtn.addEventListener('click', () =&gt; {\n  const pressed = btn.getAttribute('aria-pressed') === 'true';\n  btn.setAttribute('aria-pressed', String(!pressed));\n  fetch('/api/feedback', {\n    method: 'POST',\n    body: JSON.stringify({ messageId, helpful: !pressed }),\n  });\n});</code></pre>\n\n        <h3>Suggestion chips</h3>\n        <p>Use <a href=\"ren-tag.html\" class=\"ren-link\">Tag</a> with the <code>clickable</code> variant — there's no AI-specific suggestion class, suggestions are just tags.</p>\n        <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;div style=\"display: flex; flex-wrap: wrap; gap: var(--space-1);\"&gt;\n  &lt;button class=\"ren-tag ren-tag-clickable\"&gt;Show me an example&lt;/button&gt;\n  &lt;button class=\"ren-tag ren-tag-clickable\"&gt;What about Dialog?&lt;/button&gt;\n&lt;/div&gt;</code></pre>\n      </section>\n\n    </main>\n  </div>\n\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-ai.html",
      "id": "docs:docs/components/ren-ai.html",
      "type": "docs_page",
      "name": "ren-ai docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Alert Dialog — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-8) var(--space-5); display: flex; gap: var(--space-3); align-items: center; justify-content: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\" aria-current=\"page\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Alert Dialog</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Alert Dialog <span class=\"dx-api-badge dx-api-badge-css\" title=\"Styled with CSS; opened with the native dialog API\">CSS-only</span></h1>\n        <p class=\"lede\">A confirmation dialog for destructive or irreversible actions. Same shell as <a href=\"ren-dialog.html\" class=\"ren-link\">Dialog</a>, but the user must press a button to close — backdrop click and Escape are suppressed.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Use Alert Dialog when accidental dismissal would be costly — deleting an account, force-quitting a long upload, sending an email that can't be unsent. The dialog stays open until the user explicitly chooses Cancel or the destructive action; there's no \"click outside to ignore.\"</p>\n        <div class=\"dx-callout\">\n          <p><strong>Reserve alert dialogs for genuine destructive moments.</strong> If every confirm uses an alert dialog, users learn to dismiss them on autopilot.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <button class=\"ren-btn ren-btn-danger\" type=\"button\" data-trigger=\"alert-demo\" aria-haspopup=\"dialog\" aria-controls=\"alert-demo\">Delete account</button>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;button type=\"button\" onclick=\"confirmDelete.showModal()\"&gt;Delete account&lt;/button&gt;\n\n&lt;dialog id=\"confirmDelete\" class=\"ren-alert-dialog\"\n  aria-labelledby=\"cd-title\" aria-describedby=\"cd-description\" closedby=\"none\"&gt;\n  &lt;h2 id=\"cd-title\" class=\"ren-alert-dialog-title\"&gt;Delete this account?&lt;/h2&gt;\n  &lt;p id=\"cd-description\" class=\"ren-alert-dialog-description\"&gt;\n    All projects, files, and history will be permanently removed.\n  &lt;/p&gt;\n  &lt;form method=\"dialog\" class=\"ren-alert-dialog-actions\"&gt;\n    &lt;button value=\"cancel\" class=\"ren-btn ren-btn-secondary\"&gt;Keep account&lt;/button&gt;\n    &lt;button value=\"delete\" class=\"ren-btn ren-btn-danger\"&gt;Delete account&lt;/button&gt;\n  &lt;/form&gt;\n&lt;/dialog&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <p>Alert Dialog uses the native <code>&lt;dialog&gt;</code> API directly. The component supplies the decision-focused visual treatment; the browser owns modality, focus, and form return values.</p>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class / element</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-alert-dialog</code></td><td>Class applied directly to a native <code>&lt;dialog&gt;</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">closedby=\"none\"</code></td><td>Requires an explicit action instead of Escape or backdrop dismissal.</td></tr>\n            <tr><td><code class=\"dx-api-name\">showModal()</code></td><td>Opens the alert in the top layer and makes background content inert.</td></tr>\n          </tbody>\n        </table>\n        <p>Read the selected action from the native dialog's <code>returnValue</code> after its <code>close</code> event.</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li>A real <code>&lt;dialog&gt;</code> exposes modal semantics through the platform and is labelled by its decision heading.</li>\n          <li>Focus is trapped inside until a button is clicked.</li>\n          <li><kbd>Esc</kbd> and light dismissal are suppressed with <code>closedby=\"none\"</code>.</li>\n          <li>Backdrop click does nothing — the user must commit to a choice.</li>\n        </ul>\n        <div class=\"dx-callout\">\n          <p><strong>Pair the destructive button with the destructive label.</strong> \"Delete account\" — not \"Confirm\". Users who scan only the buttons should still understand what they're confirming.</p>\n        </div>\n      </section>\n\n    </main>\n  </div>\n  <dialog id=\"alert-demo\" class=\"ren-alert-dialog\" aria-labelledby=\"alert-demo-title\" aria-describedby=\"alert-demo-description\" closedby=\"none\">\n    <div class=\"ren-alert-dialog-icon ren-alert-dialog-icon-danger\" aria-hidden=\"true\">!</div>\n    <h2 id=\"alert-demo-title\" class=\"ren-alert-dialog-title\">Delete this account?</h2>\n    <p id=\"alert-demo-description\" class=\"ren-alert-dialog-description\">All projects, files, and history will be permanently removed. This cannot be undone.</p>\n    <form method=\"dialog\" class=\"ren-alert-dialog-actions\">\n      <button value=\"cancel\" class=\"ren-btn ren-btn-secondary\">Keep account</button>\n      <button value=\"delete\" class=\"ren-btn ren-btn-danger\">Delete account</button>\n    </form>\n  </dialog>\n  <script>\n    const alertDemo = document.getElementById('alert-demo');\n    const alertDemoTrigger = document.querySelector('[data-trigger=\"alert-demo\"]');\n    alertDemoTrigger.addEventListener('click', () => alertDemo.showModal());\n    // `closedby=\"none\"` is not implemented consistently by every engine yet.\n    // Keep the destructive confirmation explicit in WebKit as well.\n    alertDemo.addEventListener('cancel', (event) => event.preventDefault());\n    alertDemo.addEventListener('close', () => alertDemoTrigger.focus());\n  </script>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-alert-dialog.html",
      "id": "docs:docs/components/ren-alert-dialog.html",
      "type": "docs_page",
      "name": "ren-alert-dialog docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Avatar — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-3); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\" aria-current=\"page\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Avatar</li></ol></nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Avatar <span class=\"dx-api-badge dx-api-badge-css\" title=\"Works without any JavaScript\">CSS-only</span></h1>\n        <p class=\"lede\">User photos, initials, and presence indicators. Six sizes, square / round, with optional online / offline / busy status dot.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>Avatars represent people or entities. They handle the messy details: image fallback when the photo fails to load, initials when there's no photo, status dots for presence.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\"><div style=\"width: 32px; height: 32px; border-radius: 50%; background: var(--color-accent); color: var(--color-on-accent); display: grid; place-items: center; font-size: var(--text-xs); font-weight: var(--weight-semibold);\">JD</div><div style=\"width: 40px; height: 40px; border-radius: 50%; background: var(--color-success); color: var(--color-on-success); display: grid; place-items: center; font-size: var(--text-sm); font-weight: var(--weight-semibold);\">AB</div><div style=\"width: 56px; height: 56px; border-radius: 50%; background: var(--color-warning); color: var(--color-on-warning); display: grid; place-items: center; font-size: var(--text-base); font-weight: var(--weight-semibold);\">CD</div><div style=\"position: relative; width: 40px; height: 40px;\"><div style=\"width: 40px; height: 40px; border-radius: 50%; background: var(--color-accent); color: var(--color-on-accent); display: grid; place-items: center; font-size: var(--text-sm); font-weight: var(--weight-semibold);\">EF</div><span style=\"position: absolute; bottom: 0; right: 0; width: 12px; height: 12px; background: var(--color-success); border: 2px solid var(--color-surface); border-radius: 50%;\"></span></div></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;img class=\"ren-avatar ren-avatar-md\" src=\"/u/ren.jpg\" alt=\"Ren\"&gt;\n&lt;span class=\"ren-avatar ren-avatar-md\"&gt;JD&lt;/span&gt; &lt;!-- initials fallback --&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><table class=\"dx-api\"><thead><tr><th>Class</th><th>Effect</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">.ren-avatar</code></td><td>Base. Works on <code>&lt;img&gt;</code> or any element holding initials.</td></tr><tr><td><code class=\"dx-api-name\">.ren-avatar-xs</code> / <code class=\"dx-api-name\">-sm</code> / <code class=\"dx-api-name\">-md</code> / <code class=\"dx-api-name\">-lg</code> / <code class=\"dx-api-name\">-xl</code> / <code class=\"dx-api-name\">-2xl</code></td><td>Sizes from 24 px to 64 px.</td></tr><tr><td><code class=\"dx-api-name\">.ren-avatar-square</code></td><td>Rounded-square instead of circle.</td></tr><tr><td><code class=\"dx-api-name\">.ren-avatar-status</code></td><td>Wrapper that positions a presence dot at the bottom-right.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li>Image avatars need <code>alt=\"{Person name}\"</code>. The avatar is functionally a label.</li><li>Initials avatars use <code>aria-label</code> with the full name (initials alone don't identify a person to a screen reader).</li><li>Status dots are decorative — the status word should appear elsewhere in the row.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-avatar.html",
      "id": "docs:docs/components/ren-avatar.html",
      "type": "docs_page",
      "name": "ren-avatar docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Badge — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: var(--space-3); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); align-items: center; }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\">\n    <div class=\"dx-nav-inner\">\n      <a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a>\n      <nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav>\n      <div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div>\n    </div>\n  </header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\" aria-current=\"page\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Badge</li></ol></nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Badge <span class=\"dx-api-badge dx-api-badge-css\" title=\"Works without any JavaScript\">CSS-only</span></h1>\n        <p class=\"lede\">Tiny inline status labels: counts, statuses, tags. Eight variants, dot indicator, outline option. Use them next to titles, inside list rows, or on top of icons to convey unread / pending / new state.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>A badge is the smallest possible visual indicator: a colored chip with a one-or-two-word label. Use it to show counts (\"3 new\"), states (\"Beta\", \"Pending\"), or category tags. Don't use badges for clickable filters — that's <a href=\"ren-tag.html\" class=\"ren-link\">Tag</a>'s job.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Badges aren't interactive.</strong> They have no hover, no focus, no click target. If a user can act on it, it's a Tag (or a button styled like a chip).</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <span class=\"ren-badge\">Default</span>\n            <span class=\"ren-badge ren-badge-primary\">Primary</span>\n            <span class=\"ren-badge ren-badge-secondary\">Secondary</span>\n            <span class=\"ren-badge ren-badge-success\">Active</span>\n            <span class=\"ren-badge ren-badge-warning\">Pending</span>\n            <span class=\"ren-badge ren-badge-danger\">Failed</span>\n            <span class=\"ren-badge ren-badge-info\">Info</span>\n            <span class=\"ren-badge ren-badge-outline\">Outline</span>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;span class=\"ren-badge\"&gt;Default&lt;/span&gt;\n&lt;span class=\"ren-badge ren-badge-success\"&gt;Active&lt;/span&gt;\n&lt;span class=\"ren-badge ren-badge-danger\"&gt;Failed&lt;/span&gt;\n&lt;span class=\"ren-badge ren-badge-outline\"&gt;Outline&lt;/span&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Shapes</p>\n        <h2>Variants</h2>\n        <div class=\"dx-vgrid\">\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Status</span><div class=\"dx-vrow-items\">\n            <span class=\"ren-badge ren-badge-success\">Active</span>\n            <span class=\"ren-badge ren-badge-warning\">Pending</span>\n            <span class=\"ren-badge ren-badge-danger\">Inactive</span>\n            <span class=\"ren-badge ren-badge-info\">Beta</span>\n          </div></div>\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">With dot</span><div class=\"dx-vrow-items\">\n            <span class=\"ren-badge ren-badge-success\"><span class=\"ren-badge-dot ren-badge-dot-success\"></span>Online</span>\n            <span class=\"ren-badge ren-badge-warning\"><span class=\"ren-badge-dot ren-badge-dot-warning\"></span>Idle</span>\n            <span class=\"ren-badge ren-badge-danger\"><span class=\"ren-badge-dot ren-badge-dot-danger\"></span>Offline</span>\n          </div></div>\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Counter</span><div class=\"dx-vrow-items\">\n            <span class=\"ren-badge ren-badge-primary\">3</span>\n            <span class=\"ren-badge ren-badge-primary\">12</span>\n            <span class=\"ren-badge ren-badge-primary\">99+</span>\n          </div></div>\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Outline</span><div class=\"dx-vrow-items\">\n            <span class=\"ren-badge ren-badge-outline\">Draft</span>\n            <span class=\"ren-badge ren-badge-outline\">v2.0</span>\n          </div></div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <h3>CSS classes</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-badge</code></td><td>Base. Required. Default neutral fill, subtle border.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-badge-primary</code></td><td>Accent fill, on-accent text.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-badge-secondary</code></td><td>Secondary fill — muted gray, for less prominent labels.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-badge-success</code> / <code class=\"dx-api-name\">-warning</code> / <code class=\"dx-api-name\">-danger</code> / <code class=\"dx-api-name\">-info</code></td><td>Status variants in the matching semantic color.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-badge-outline</code></td><td>Border only, transparent background. For secondary contexts.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-badge-dot</code></td><td>The 6 px circle prefix inside a status badge. Combine with <code>.ren-badge-dot-{status}</code>.</td></tr>\n          </tbody>\n        </table>\n        <p>No JavaScript. Pure CSS — render any element (<code>span</code>, <code>div</code>, <code>a</code>) with these classes.</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <p>Badges are decorative by default. If a badge conveys information not present elsewhere, give it semantic context.</p>\n        <ul>\n          <li><strong>Counters next to labels</strong>: pair the count with the noun it counts. <code>&lt;a&gt;Inbox &lt;span class=\"ren-badge\"&gt;3 unread&lt;/span&gt;&lt;/a&gt;</code> reads correctly. <code>&lt;a&gt;Inbox &lt;span class=\"ren-badge\"&gt;3&lt;/span&gt;&lt;/a&gt;</code> doesn't.</li>\n          <li><strong>Status with color only</strong>: don't rely on the color alone — include the status word. \"Online\" / \"Offline\" beats just a green / red dot.</li>\n          <li><strong>Live counters</strong>: if the count updates dynamically, wrap the number in <code>aria-live=\"polite\"</code> on the parent so screen readers re-announce.</li>\n        </ul>\n        <div class=\"dx-callout\">\n          <p><strong>Don't use a badge as a label for an icon-only button.</strong> A badge is decoration. The button needs <code>aria-label</code> to be discoverable.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n        <h3>Status next to a title</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;h2&gt;\n  Project Atlas\n  &lt;span class=\"ren-badge ren-badge-warning\"&gt;Pending review&lt;/span&gt;\n&lt;/h2&gt;</code></div>\n\n        <h3>Unread count on a nav link</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;a href=\"/inbox\" class=\"nav-link\"&gt;\n  Inbox\n  &lt;span class=\"ren-badge ren-badge-primary\" aria-label=\"3 unread\"&gt;3&lt;/span&gt;\n&lt;/a&gt;</code></div>\n\n        <h3>Online indicator</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;span class=\"ren-badge ren-badge-success\"&gt;\n  &lt;span class=\"ren-badge-dot ren-badge-dot-success\"&gt;&lt;/span&gt;\n  Online\n&lt;/span&gt;</code></div>\n      </section>\n\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-badge.html",
      "id": "docs:docs/components/ren-badge.html",
      "type": "docs_page",
      "name": "ren-badge docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Banner — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-direction: column; gap: var(--space-3); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-3); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\" aria-current=\"page\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Banner</li></ol></nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Banner <span class=\"dx-api-badge dx-api-badge-css\" title=\"Works without any JavaScript\">CSS-only</span></h1>\n        <p class=\"lede\">Page-level callouts for system messages: maintenance windows, success confirmations, errors, neutral announcements. Five status variants, optional icon, action slot, dismissible.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>A Banner sits above (or inside) a content region and announces something the user should know before continuing. Use them for app-wide notices (\"Scheduled maintenance Saturday\"), success feedback after a critical action, or contextual warnings inside a form.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Banner vs Toast vs Dialog.</strong> Banner is persistent and contextual. Toast is transient. Dialog blocks. Pick based on how important the message is and whether the user has to act on it.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <div class=\"ren-banner ren-banner-info\" role=\"status\">\n              <span class=\"ren-banner-icon\" aria-hidden=\"true\">ℹ</span>\n              <div class=\"ren-banner-content\">\n                <p class=\"ren-banner-title\">Scheduled maintenance</p>\n                <p class=\"ren-banner-message\">The service will be briefly unavailable Saturday 2 AM – 3 AM ART.</p>\n              </div>\n              <button class=\"ren-banner-dismiss\" type=\"button\" aria-label=\"Dismiss\">×</button>\n            </div>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div class=\"ren-banner ren-banner-info\" role=\"status\"&gt;\n  &lt;span class=\"ren-banner-icon\" aria-hidden=\"true\"&gt;ℹ&lt;/span&gt;\n  &lt;div class=\"ren-banner-content\"&gt;\n    &lt;p class=\"ren-banner-title\"&gt;Scheduled maintenance&lt;/p&gt;\n    &lt;p class=\"ren-banner-message\"&gt;The service will be briefly unavailable…&lt;/p&gt;\n  &lt;/div&gt;\n  &lt;button class=\"ren-banner-dismiss\" aria-label=\"Dismiss\"&gt;×&lt;/button&gt;\n&lt;/div&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Status</p>\n        <h2>Variants</h2>\n        <div class=\"dx-vgrid\">\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Status</span><div class=\"dx-vrow-items\">\n            <div class=\"ren-banner ren-banner-success\" role=\"status\"><span class=\"ren-banner-icon\"><svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"20 6 9 17 4 12\"/></svg></span><div class=\"ren-banner-content\"><p class=\"ren-banner-title\">Saved</p></div></div>\n            <div class=\"ren-banner ren-banner-warning\" role=\"status\"><span class=\"ren-banner-icon\"><svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z\"/><line x1=\"12\" x2=\"12\" y1=\"9\" y2=\"13\"/><line x1=\"12\" x2=\"12.01\" y1=\"17\" y2=\"17\"/></svg></span><div class=\"ren-banner-content\"><p class=\"ren-banner-title\">Approaching plan limit</p></div></div>\n            <div class=\"ren-banner ren-banner-danger\" role=\"alert\"><span class=\"ren-banner-icon\"><svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg></span><div class=\"ren-banner-content\"><p class=\"ren-banner-title\">Sync failed</p><p class=\"ren-banner-message\">Check your connection and retry.</p></div></div>\n            <div class=\"ren-banner ren-banner-info\" role=\"status\"><span class=\"ren-banner-icon\">ℹ</span><div class=\"ren-banner-content\"><p class=\"ren-banner-title\">New version available</p></div></div>\n            <div class=\"ren-banner ren-banner-neutral\" role=\"status\"><span class=\"ren-banner-icon\">·</span><div class=\"ren-banner-content\"><p class=\"ren-banner-title\">Read-only mode</p></div></div>\n          </div></div>\n\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">With action</span><div class=\"dx-vrow-items\">\n            <div class=\"ren-banner ren-banner-warning\" role=\"status\">\n              <span class=\"ren-banner-icon\"><svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z\"/><line x1=\"12\" x2=\"12\" y1=\"9\" y2=\"13\"/><line x1=\"12\" x2=\"12.01\" y1=\"17\" y2=\"17\"/></svg></span>\n              <div class=\"ren-banner-content\">\n                <p class=\"ren-banner-title\">Verify your email</p>\n                <p class=\"ren-banner-message\">Some features are limited until you confirm.</p>\n              </div>\n              <div class=\"ren-banner-actions\">\n                <button class=\"ren-btn ren-btn-secondary ren-btn-sm\">Resend</button>\n              </div>\n            </div>\n          </div></div>\n\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Compact</span><div class=\"dx-vrow-items\">\n            <div class=\"ren-banner ren-banner-info ren-banner-compact\" role=\"status\">\n              <span class=\"ren-banner-icon\">ℹ</span>\n              <div class=\"ren-banner-content\"><p class=\"ren-banner-title\">Cookies are required for sign-in.</p></div>\n            </div>\n          </div></div>\n\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Full-bleed</span><div class=\"dx-vrow-items\">\n            <div class=\"ren-banner ren-banner-danger ren-banner-full\" role=\"alert\" style=\"border-radius: 0; margin-inline: calc(var(--space-3) * -1);\">\n              <span class=\"ren-banner-icon\"><svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg></span>\n              <div class=\"ren-banner-content\"><p class=\"ren-banner-title\">Service degraded — see status page</p></div>\n            </div>\n          </div></div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <h3>CSS classes</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-banner</code></td><td>Base. Flex row with icon · content · actions · dismiss.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-banner-success</code> / <code class=\"dx-api-name\">-warning</code> / <code class=\"dx-api-name\">-danger</code> / <code class=\"dx-api-name\">-info</code> / <code class=\"dx-api-name\">-neutral</code></td><td>Status variants. Tints icon, accent border, and subtle background.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-banner-icon</code></td><td>Leading icon slot. Use a real SVG or a unicode glyph; mark <code>aria-hidden</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-banner-content</code></td><td>Title + optional message. The message reflows under the title when present.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-banner-title</code></td><td>Bold one-liner.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-banner-message</code></td><td>Secondary line. Optional.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-banner-actions</code></td><td>Trailing slot for one or two buttons. Right-aligned.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-banner-dismiss</code></td><td>The × close button. Always pair with <code>aria-label</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-banner-compact</code></td><td>Tighter padding, smaller icon. For inline contexts.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-banner-full</code></td><td>Full-bleed, no border-radius. For app-wide bars at the top of the layout.</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li><strong>Use <code>role=\"status\"</code></strong> for non-critical banners (info / success / neutral / warning) — screen readers announce when idle.</li>\n          <li><strong>Use <code>role=\"alert\"</code></strong> for danger banners — they interrupt the current announcement.</li>\n          <li><strong>Don't rely on color alone</strong> — the icon and the title text should make the status clear without color.</li>\n          <li><strong>Dismiss button</strong>: always include <code>aria-label=\"Dismiss\"</code> (or more specific). Don't make dismissal the only way to act on a critical message.</li>\n        </ul>\n        <div class=\"dx-callout\">\n          <p><strong>Don't auto-dismiss banners.</strong> Unlike Toast, banners are persistent — the user reads them in their own time. Auto-hiding defeats the purpose.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n        <h3>App-wide notice at top of page</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;div class=\"ren-banner ren-banner-warning ren-banner-full\" role=\"status\"&gt;\n  &lt;span class=\"ren-banner-icon\"&gt;&lt;!-- alert SVG --&gt;&lt;/span&gt;\n  &lt;div class=\"ren-banner-content\"&gt;\n    &lt;p class=\"ren-banner-title\"&gt;Read-only mode — database migration in progress&lt;/p&gt;\n  &lt;/div&gt;\n&lt;/div&gt;</code></div>\n\n        <h3>Empty-state hint inside a card</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;div class=\"ren-banner ren-banner-info ren-banner-compact\" role=\"status\"&gt;\n  &lt;span class=\"ren-banner-icon\"&gt;ℹ&lt;/span&gt;\n  &lt;div class=\"ren-banner-content\"&gt;\n    &lt;p class=\"ren-banner-title\"&gt;No invoices yet&lt;/p&gt;\n    &lt;p class=\"ren-banner-message\"&gt;Your first invoice will appear here once you onboard a client.&lt;/p&gt;\n  &lt;/div&gt;\n  &lt;div class=\"ren-banner-actions\"&gt;\n    &lt;button class=\"ren-btn ren-btn-primary ren-btn-sm\"&gt;Add client&lt;/button&gt;\n  &lt;/div&gt;\n&lt;/div&gt;</code></div>\n      </section>\n\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-banner.html",
      "id": "docs:docs/components/ren-banner.html",
      "type": "docs_page",
      "name": "ren-banner docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Breadcrumb — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-direction: column; gap: var(--space-3); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-3); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-direction: column; gap: var(--space-2); }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\" aria-current=\"page\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Breadcrumb</li></ol></nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Breadcrumb <span class=\"dx-api-badge dx-api-badge-css\" title=\"Works without any JavaScript\">CSS-only</span></h1>\n        <p class=\"lede\">Trail of links showing where the user is in a hierarchy. Always rendered as <code>&lt;nav&gt; &lt;ol&gt; &lt;li&gt;</code> with <code>aria-label</code> and <code>aria-current=\"page\"</code> on the leaf — semantics first, decoration second.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Breadcrumbs help orient the user when pages live deep in a hierarchy: Docs › Components › Breadcrumb. Each step links upward; the leaf (current page) is text, not a link. Use them for hierarchical content (docs, file systems, e-commerce categories) — not for chronological flows like multi-step forms (use a stepper for that).</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\">\n              <ol>\n                <li><a href=\"#\">Docs</a></li>\n                <li><a href=\"#\">Components</a></li>\n                <li><a href=\"#\">Primitives</a></li>\n                <li aria-current=\"page\">Breadcrumb</li>\n              </ol>\n            </nav>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\"&gt;\n  &lt;ol&gt;\n    &lt;li&gt;&lt;a href=\"/docs\"&gt;Docs&lt;/a&gt;&lt;/li&gt;\n    &lt;li&gt;&lt;a href=\"/docs/components\"&gt;Components&lt;/a&gt;&lt;/li&gt;\n    &lt;li&gt;&lt;a href=\"/docs/components/primitives\"&gt;Primitives&lt;/a&gt;&lt;/li&gt;\n    &lt;li aria-current=\"page\"&gt;Breadcrumb&lt;/li&gt;\n  &lt;/ol&gt;\n&lt;/nav&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Shapes</p>\n        <h2>Variants</h2>\n        <div class=\"dx-vgrid\">\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Default separator</span><div class=\"dx-vrow-items\">\n            <nav class=\"ren-breadcrumb\" aria-label=\"Default\"><ol>\n              <li><a href=\"#\">Docs</a></li><li><a href=\"#\">Components</a></li><li aria-current=\"page\">Breadcrumb</li>\n            </ol></nav>\n          </div></div>\n\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Slash separator</span><div class=\"dx-vrow-items\">\n            <nav class=\"ren-breadcrumb\" style=\"--breadcrumb-separator: '/';\" aria-label=\"Slash variant\"><ol>\n              <li><a href=\"#\">Home</a></li><li><a href=\"#\">Projects</a></li><li><a href=\"#\">Atlas</a></li><li aria-current=\"page\">README</li>\n            </ol></nav>\n          </div></div>\n\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Truncated</span><div class=\"dx-vrow-items\">\n            <nav class=\"ren-breadcrumb ren-breadcrumb-truncated\" aria-label=\"Truncated\"><ol>\n              <li><a href=\"#\">Docs</a></li>\n              <li><a href=\"#\">Components</a></li>\n              <li><a href=\"#\">Primitives</a></li>\n              <li><a href=\"#\">Form controls</a></li>\n              <li><a href=\"#\">Inputs</a></li>\n              <li aria-current=\"page\">Email</li>\n            </ol></nav>\n            <p style=\"font-size: var(--text-xs); color: var(--color-text-muted); margin: 0;\">Middle items collapse to … under <code>.ren-breadcrumb-truncated</code> — first and last two stay visible.</p>\n          </div></div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <h3>CSS classes & tokens</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class / token</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-breadcrumb</code></td><td>Wraps the <code>&lt;nav&gt;</code>. Sets the typography and color.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-breadcrumb &gt; ol</code></td><td>The list itself. Inline-flex with gap; resets list styling.</td></tr>\n            <tr><td><code class=\"dx-api-name\">--breadcrumb-separator</code></td><td>CSS custom property for the separator glyph. Default <code>'›'</code>. Use <code>'/'</code> or <code>'›'</code> for variants.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-breadcrumb-truncated</code></td><td>Collapses middle items to <code>…</code>; keeps first and last two.</td></tr>\n            <tr><td><code class=\"dx-api-name\">[aria-current=\"page\"]</code></td><td>Marks the leaf. Renders as plain text, no underline. Required.</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li><strong>Wrap in <code>&lt;nav aria-label=\"Breadcrumb\"&gt;</code></strong>. Without the label, screen readers announce a generic \"navigation\".</li>\n          <li><strong>Use a real <code>&lt;ol&gt;</code></strong>, not a sequence of spans. Order matters in a breadcrumb; the list reflects that.</li>\n          <li><strong>Mark the current page</strong> with <code>aria-current=\"page\"</code>. It's a plain <code>&lt;li&gt;</code> with no link — assistive tech announces it as the active step.</li>\n          <li><strong>Don't link the current page to itself.</strong> Confusing for everyone.</li>\n        </ul>\n      </section>\n\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n        <h3>App documentation</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\"&gt;\n  &lt;ol&gt;\n    &lt;li&gt;&lt;a href=\"/docs\"&gt;Docs&lt;/a&gt;&lt;/li&gt;\n    &lt;li&gt;&lt;a href=\"/docs/getting-started\"&gt;Getting started&lt;/a&gt;&lt;/li&gt;\n    &lt;li aria-current=\"page\"&gt;Install&lt;/li&gt;\n  &lt;/ol&gt;\n&lt;/nav&gt;</code></div>\n\n        <h3>File path</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;nav class=\"ren-breadcrumb\" style=\"--breadcrumb-separator: '/';\" aria-label=\"File path\"&gt;\n  &lt;ol&gt;\n    &lt;li&gt;&lt;a href=\"/files\"&gt;files&lt;/a&gt;&lt;/li&gt;\n    &lt;li&gt;&lt;a href=\"/files/projects\"&gt;projects&lt;/a&gt;&lt;/li&gt;\n    &lt;li aria-current=\"page\"&gt;atlas.md&lt;/li&gt;\n  &lt;/ol&gt;\n&lt;/nav&gt;</code></div>\n      </section>\n\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-breadcrumb.html",
      "id": "docs:docs/components/ren-breadcrumb.html",
      "type": "docs_page",
      "name": "ren-breadcrumb docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Button — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    /* Page-specific styles — chrome lives in shell.css */\n\n    /* ═══════════════════════════════════════════════════════════\n       Component-page specific patterns (new)\n       ═══════════════════════════════════════════════════════════ */\n\n    /* Demo block: preview frame + code block, stacked */\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview {\n      background: var(--color-surface-raised);\n      padding: var(--space-8) var(--space-5);\n      display: flex;\n      flex-wrap: wrap;\n      gap: var(--space-3);\n      align-items: center;\n      justify-content: center;\n      min-height: 96px;\n    }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n\n    /* Variant grid: structured rows for Variants section */\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label {\n      font-size: var(--text-xs);\n      font-weight: var(--weight-semibold);\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--color-text-muted);\n      padding-top: var(--space-3);\n    }\n    .dx-vrow-items {\n      display: flex;\n      flex-wrap: wrap;\n      gap: var(--space-3);\n      align-items: center;\n      padding: var(--space-3);\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n    }\n    @media (max-width: 720px) {\n      .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); }\n      .dx-vrow-label { padding-top: 0; }\n    }\n\n    /* Anatomy: assembled button on top, 2x2 grid of part specimens below */\n    .dx-anatomy {\n      margin: var(--space-4) 0;\n      display: grid;\n      gap: var(--space-4);\n    }\n\n    /* Number badge — guaranteed AA: text on surface (black on white / white on black) */\n    .dx-anatomy-num {\n      display: inline-grid;\n      place-items: center;\n      width: 22px;\n      height: 22px;\n      border-radius: 50%;\n      background: var(--color-text);\n      color: var(--color-surface);\n      font-size: var(--text-xs);\n      font-weight: var(--weight-bold);\n      flex-shrink: 0;\n    }\n\n    /* Top: assembled reference */\n    .dx-anatomy-stage {\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n      padding: var(--space-8) var(--space-5);\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      gap: var(--space-3);\n    }\n    .dx-anatomy-stage-label {\n      font-size: var(--text-xs);\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--color-text-muted);\n      font-weight: var(--weight-semibold);\n      margin: 0;\n    }\n    .dx-anatomy-stage .ren-btn { pointer-events: none; }\n\n    /* Bottom: 2x2 grid of parts */\n    .dx-anatomy-parts {\n      display: grid;\n      grid-template-columns: repeat(2, 1fr);\n      gap: var(--space-3);\n    }\n    @media (max-width: 720px) { .dx-anatomy-parts { grid-template-columns: 1fr; } }\n\n    .dx-anatomy-part {\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n      padding: var(--space-4);\n      display: grid;\n      gap: var(--space-3);\n    }\n    .dx-anatomy-part-header {\n      display: flex;\n      align-items: center;\n      gap: var(--space-2);\n    }\n    .dx-anatomy-part-name {\n      font-size: var(--text-sm);\n      font-weight: var(--weight-semibold);\n      color: var(--color-text);\n    }\n    .dx-anatomy-part-spec {\n      background: var(--color-surface-sunken);\n      border: 1px dashed var(--color-border);\n      border-radius: var(--radius-sm);\n      min-height: 64px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      color: var(--color-text);\n    }\n    .dx-anatomy-part-spec svg { display: block; }\n    .dx-anatomy-part-spec .container-frame {\n      width: 110px;\n      height: 32px;\n      border: 2px solid var(--color-accent);\n      border-radius: var(--radius-md);\n      background: transparent;\n    }\n    .dx-anatomy-part-desc {\n      font-size: var(--text-sm);\n      line-height: 1.5;\n      color: var(--color-text-secondary);\n      margin: 0;\n    }\n    .dx-anatomy-part-desc code {\n      font-size: 0.85em;\n    }\n\n    /* .dx-api and .dx-keys live in shell.css so every reference table\n       on every component page shares the same column boundary. */\n  </style>\n</head>\n<body>\n\n  <!-- Top nav -->\n  <header class=\"dx-nav\">\n    <div class=\"dx-nav-inner\">\n      <a href=\"../index.html\" class=\"dx-brand\">\n        <span class=\"dx-brand-mark\">R</span>\n        <span>RenDS</span>\n        <span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span>\n      </a>\n      <nav class=\"dx-nav-menu\" aria-label=\"Primary\">\n        <a href=\"../index.html\">Docs</a>\n        <a href=\"../components.html\" aria-current=\"page\">Components</a>\n        <a href=\"../../templates/index.html\">Templates</a>\n        <a href=\"../../create/index.html\">Theme Builder</a>\n      </nav>\n      <div class=\"dx-nav-actions\">\n        <a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a>\n        <a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n\n    <!-- Persistent sidebar — same nav as the catalog so context is never lost -->\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\" aria-current=\"page\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <!-- Content -->\n    <main class=\"dx-content\">\n\n      <!-- Page header -->\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\">\n          <ol>\n            <li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li>\n            <li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li>\n            <li aria-current=\"page\">Button</li>\n          </ol>\n        </nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Button <span class=\"dx-api-badge dx-api-badge-hybrid\" title=\"Renders without JS; JS adds enhancements\">Hybrid</span></h1>\n        <p class=\"lede\">A multi-variant action trigger with built-in loading and disabled states. Works as a CSS-only class set <em>or</em> as the <code>&lt;ren-button&gt;</code> custom element. Meets a 44 px touch target and AA contrast in every variant out of the box.</p>\n      </header>\n\n    <!-- ═══════════════════════════════════════════════════════════\n         1. OVERVIEW\n         ═══════════════════════════════════════════════════════════ -->\n    <section class=\"dx-section\" id=\"overview\">\n      <p class=\"dx-kicker\">About</p>\n      <h2>Overview</h2>\n      <p>A button triggers an action — submitting a form, opening a dialog, deleting a record. RenDS gives you one component with seven visual variants (primary, secondary, ghost, outline, danger, link, accent), three sizes, and a built-in loading state that flips the button to <code>aria-busy=\"true\"</code> and disables interaction without you wiring anything.</p>\n\n      <p>Every variant ships with a 44 px minimum touch target (Apple HIG), a visible focus ring, hover/active feedback that respects <code>prefers-reduced-motion</code>, and AA contrast in both light and dark mode.</p>\n\n      <h3>When to use</h3>\n      <ul>\n        <li>To trigger an action on the current page (submit, save, delete, copy, run).</li>\n        <li>To open or close an overlay (dialog, popover, menu).</li>\n        <li>For a primary CTA, group it with a secondary or ghost button — never two primaries side by side.</li>\n      </ul>\n\n      <h3>When not to use</h3>\n      <ul>\n        <li>For navigation to a different page or a different URL — that's <code>&lt;a&gt;</code> or <code>ren-link</code>. A button that navigates breaks browser conventions (no middle-click open, no copy-link).</li>\n        <li>For an inline call-to-read-more inside body text — use the <code>link</code> variant only when a button is genuinely needed; otherwise reach for <code>ren-link</code>.</li>\n        <li>For a binary state choice — that's <code>ren-toggle</code> or a checkbox.</li>\n      </ul>\n\n      <div class=\"dx-callout\">\n        <p><strong>Native button, semantically.</strong> The <code>&lt;ren-button&gt;</code> custom element wraps a native <code>&lt;button&gt;</code> in light DOM. Forms still submit, focus still works, screen readers still announce \"button\". You're not opting out of the platform — you're styling it.</p>\n      </div>\n    </section>\n\n    <!-- ═══════════════════════════════════════════════════════════\n         2. ANATOMY\n         ═══════════════════════════════════════════════════════════ -->\n    <section class=\"dx-section\" id=\"anatomy\">\n      <p class=\"dx-kicker\">Structure</p>\n      <h2>Anatomy</h2>\n      <p>A button has up to four visible parts. Only the label is mandatory — the rest are optional and combine freely.</p>\n\n      <div class=\"dx-anatomy\">\n\n        <!-- Top: assembled button as reference -->\n        <div class=\"dx-anatomy-stage\">\n          <p class=\"dx-anatomy-stage-label\">Assembled</p>\n          <button class=\"ren-btn ren-btn-primary ren-btn-lg\" type=\"button\" tabindex=\"-1\" aria-hidden=\"true\">\n            <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n              <path d=\"M12 5v14M5 12h14\"/>\n            </svg>\n            Add item\n            <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n              <polyline points=\"9 18 15 12 9 6\"/>\n            </svg>\n          </button>\n        </div>\n\n        <!-- Bottom: 4 parts shown in isolation -->\n        <div class=\"dx-anatomy-parts\">\n\n          <div class=\"dx-anatomy-part\">\n            <div class=\"dx-anatomy-part-header\">\n              <span class=\"dx-anatomy-num\" aria-hidden=\"true\">1</span>\n              <span class=\"dx-anatomy-part-name\">Container</span>\n            </div>\n            <div class=\"dx-anatomy-part-spec\" aria-hidden=\"true\">\n              <span class=\"container-frame\"></span>\n            </div>\n            <p class=\"dx-anatomy-part-desc\">The whole clickable surface. Sets the variant color, padding, border-radius, and the 44&nbsp;px touch target. Receives focus.</p>\n          </div>\n\n          <div class=\"dx-anatomy-part\">\n            <div class=\"dx-anatomy-part-header\">\n              <span class=\"dx-anatomy-num\" aria-hidden=\"true\">2</span>\n              <span class=\"dx-anatomy-part-name\">Leading icon</span>\n            </div>\n            <div class=\"dx-anatomy-part-spec\" aria-hidden=\"true\">\n              <svg width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 5v14M5 12h14\"/></svg>\n            </div>\n            <p class=\"dx-anatomy-part-desc\">Optional. Placed before the label, gap-spaced with <code>--space-2</code>. Decorative icons should carry <code>aria-hidden=\"true\"</code>.</p>\n          </div>\n\n          <div class=\"dx-anatomy-part\">\n            <div class=\"dx-anatomy-part-header\">\n              <span class=\"dx-anatomy-num\" aria-hidden=\"true\">3</span>\n              <span class=\"dx-anatomy-part-name\">Label</span>\n            </div>\n            <div class=\"dx-anatomy-part-spec\" aria-hidden=\"true\">\n              <span style=\"font-size: var(--text-base); font-weight: var(--weight-medium);\">Add item</span>\n            </div>\n            <p class=\"dx-anatomy-part-desc\">The visible text. Required for non-icon buttons. For icon-only buttons, omit it and add <code>aria-label</code> on the button itself.</p>\n          </div>\n\n          <div class=\"dx-anatomy-part\">\n            <div class=\"dx-anatomy-part-header\">\n              <span class=\"dx-anatomy-num\" aria-hidden=\"true\">4</span>\n              <span class=\"dx-anatomy-part-name\">Trailing icon</span>\n            </div>\n            <div class=\"dx-anatomy-part-spec\" aria-hidden=\"true\">\n              <svg width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"9 18 15 12 9 6\"/></svg>\n            </div>\n            <p class=\"dx-anatomy-part-desc\">Optional. Same rules as the leading icon. Common cases: a chevron for \"more options\", an external-link arrow.</p>\n          </div>\n\n        </div>\n      </div>\n    </section>\n\n    <!-- ═══════════════════════════════════════════════════════════\n         3. DEMO\n         ═══════════════════════════════════════════════════════════ -->\n    <section class=\"dx-section\" id=\"demo\">\n      <p class=\"dx-kicker\">Live</p>\n      <h2>Demo</h2>\n      <p>The default button — primary variant, medium size. Tab to it, press Space or Enter to fire the click handler.</p>\n\n      <div class=\"dx-demo\">\n        <div class=\"dx-demo-preview\">\n          <button class=\"ren-btn ren-btn-primary\" type=\"button\">Primary action</button>\n        </div>\n        <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;!-- CSS-only --&gt;\n&lt;button class=\"ren-btn ren-btn-primary\" type=\"button\"&gt;Primary action&lt;/button&gt;\n\n&lt;!-- Web Component (same output, attribute-driven) --&gt;\n&lt;ren-button variant=\"primary\"&gt;Primary action&lt;/ren-button&gt;</code></pre>\n      </div>\n\n      <div class=\"dx-callout\">\n        <p><strong>Two ways to use it, one component.</strong> The CSS classes work standalone — you don't need the JS at all. The <code>&lt;ren-button&gt;</code> element exists for two reasons: declarative attributes (<code>variant</code>, <code>loading</code>) you can flip from JS, and built-in ARIA wiring for the loading state.</p>\n      </div>\n    </section>\n\n    <!-- ═══════════════════════════════════════════════════════════\n         4. VARIANTS\n         ═══════════════════════════════════════════════════════════ -->\n    <section class=\"dx-section\" id=\"variants\">\n      <p class=\"dx-kicker\">Catalog</p>\n      <h2>Variants</h2>\n      <p>Pick the variant that matches the action's <em>weight</em>, not its category. Primary for the one most-likely action on the screen, secondary for everything else, danger for destructive, ghost when the button has to disappear into the chrome.</p>\n\n      <div class=\"dx-vgrid\">\n\n        <div class=\"dx-vrow\">\n          <span class=\"dx-vrow-label\">Variant</span>\n          <div class=\"dx-vrow-items\">\n            <button class=\"ren-btn ren-btn-primary\" type=\"button\">Primary</button>\n            <button class=\"ren-btn ren-btn-secondary\" type=\"button\">Secondary</button>\n            <button class=\"ren-btn ren-btn-ghost\" type=\"button\">Ghost</button>\n            <button class=\"ren-btn ren-btn-outline\" type=\"button\">Outline</button>\n            <button class=\"ren-btn ren-btn-danger\" type=\"button\">Danger</button>\n            <button class=\"ren-btn ren-btn-link\" type=\"button\">Link</button>\n          </div>\n        </div>\n\n        <div class=\"dx-vrow\">\n          <span class=\"dx-vrow-label\">Size</span>\n          <div class=\"dx-vrow-items\">\n            <button class=\"ren-btn ren-btn-primary ren-btn-sm\" type=\"button\">Small</button>\n            <button class=\"ren-btn ren-btn-primary\" type=\"button\">Medium</button>\n            <button class=\"ren-btn ren-btn-primary ren-btn-lg\" type=\"button\">Large</button>\n          </div>\n        </div>\n\n        <div class=\"dx-vrow\">\n          <span class=\"dx-vrow-label\">State</span>\n          <div class=\"dx-vrow-items\">\n            <button class=\"ren-btn ren-btn-primary\" type=\"button\">Default</button>\n            <button class=\"ren-btn ren-btn-primary\" type=\"button\" disabled>Disabled</button>\n            <button class=\"ren-btn ren-btn-primary\" type=\"button\" data-loading aria-busy=\"true\">Loading</button>\n          </div>\n        </div>\n\n        <div class=\"dx-vrow\">\n          <span class=\"dx-vrow-label\">Icon-only</span>\n          <div class=\"dx-vrow-items\">\n            <button class=\"ren-btn ren-btn-primary ren-btn-icon\" type=\"button\" aria-label=\"Add\">\n              <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><path d=\"M12 5v14M5 12h14\"/></svg>\n            </button>\n            <button class=\"ren-btn ren-btn-secondary ren-btn-icon\" type=\"button\" aria-label=\"Search\">\n              <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/></svg>\n            </button>\n            <button class=\"ren-btn ren-btn-ghost ren-btn-icon\" type=\"button\" aria-label=\"More options\">\n              <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"12\" cy=\"5\" r=\"1\"/><circle cx=\"12\" cy=\"19\" r=\"1\"/></svg>\n            </button>\n          </div>\n        </div>\n\n        <div class=\"dx-vrow\">\n          <span class=\"dx-vrow-label\">Full width</span>\n          <div class=\"dx-vrow-items\" style=\"display: block;\">\n            <button class=\"ren-btn ren-btn-primary ren-btn-full\" type=\"button\">Continue</button>\n          </div>\n        </div>\n\n        <div class=\"dx-vrow\">\n          <span class=\"dx-vrow-label\">Group</span>\n          <div class=\"dx-vrow-items\">\n            <div class=\"ren-btn-group\">\n              <button class=\"ren-btn ren-btn-secondary\" type=\"button\">Day</button>\n              <button class=\"ren-btn ren-btn-secondary\" type=\"button\" aria-pressed=\"true\">Week</button>\n              <button class=\"ren-btn ren-btn-secondary\" type=\"button\">Month</button>\n            </div>\n          </div>\n        </div>\n\n      </div>\n    </section>\n\n    <!-- ═══════════════════════════════════════════════════════════\n         5. API\n         ═══════════════════════════════════════════════════════════ -->\n    <section class=\"dx-section\" id=\"api\">\n      <p class=\"dx-kicker\">Reference</p>\n      <h2>API</h2>\n      <p>Two parallel surfaces. Pick the one that matches your codebase: CSS classes for static markup, the custom element for declarative attributes you can flip at runtime.</p>\n\n      <h3>CSS classes</h3>\n      <table class=\"dx-api\">\n        <thead>\n          <tr>\n            <th>Class</th>\n            <th>Effect</th>\n          </tr>\n        </thead>\n        <tbody>\n          <tr>\n            <td><code class=\"dx-api-name\">.ren-btn</code></td>\n            <td>Base. Required on every button. Resolves to <em>primary</em> if no variant modifier is present.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">.ren-btn-primary</code></td>\n            <td>Filled accent. The default — also implicit when only <code>.ren-btn</code> is set.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">.ren-btn-secondary</code></td>\n            <td>Filled neutral. The most common companion to a primary.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">.ren-btn-ghost</code></td>\n            <td>No background until hover. For toolbars and dense chrome.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">.ren-btn-outline</code></td>\n            <td>Border only, transparent fill. A lighter secondary.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">.ren-btn-danger</code></td>\n            <td>Destructive actions. Filled red, text-on-red passes AA in both modes.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">.ren-btn-link</code></td>\n            <td>Looks like an inline link but behaves as a button. Use sparingly.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">.ren-btn-sm</code> / <code class=\"dx-api-name\">.ren-btn-lg</code></td>\n            <td>Size modifiers. Default (no modifier) is medium with a 44 px touch target.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">.ren-btn-icon</code></td>\n            <td>Square aspect ratio for icon-only buttons. Pair with <code>aria-label</code>.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">.ren-btn-full</code></td>\n            <td>Stretches to <code>width: 100%</code>. For mobile CTAs and stepped forms.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">.ren-btn-group</code></td>\n            <td>Wrapper class. Joins adjacent buttons into a segmented control with shared borders.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">[data-loading]</code></td>\n            <td>Attribute, not a class. Shows a spinner, hides the label, sets <code>aria-busy</code>.</td>\n          </tr>\n        </tbody>\n      </table>\n\n      <h3>Web Component attributes</h3>\n      <p>Set on the <code>&lt;ren-button&gt;</code> element. All are reactive — flipping them in JS re-renders the underlying button.</p>\n      <table class=\"dx-api dx-api-cols-4\">\n        <thead>\n          <tr>\n            <th>Attribute</th>\n            <th>Type</th>\n            <th>Default</th>\n            <th>Notes</th>\n          </tr>\n        </thead>\n        <tbody>\n          <tr>\n            <td><code class=\"dx-api-name\">variant</code></td>\n            <td><span class=\"dx-api-type\">\"primary\" | \"secondary\" | \"ghost\" | \"outline\" | \"danger\" | \"link\"</span></td>\n            <td><span class=\"dx-api-default\">\"primary\"</span></td>\n            <td>Visual variant.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">size</code></td>\n            <td><span class=\"dx-api-type\">\"sm\" | \"md\" | \"lg\"</span></td>\n            <td><span class=\"dx-api-default\">\"md\"</span></td>\n            <td>Padding and font scale.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">loading</code></td>\n            <td><span class=\"dx-api-type\">boolean</span></td>\n            <td><span class=\"dx-api-default\">false</span></td>\n            <td>Adds <code>data-loading</code> + <code>aria-busy=\"true\"</code>.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">disabled</code></td>\n            <td><span class=\"dx-api-type\">boolean</span></td>\n            <td><span class=\"dx-api-default\">false</span></td>\n            <td>Mirrors the native <code>disabled</code> + sets <code>aria-disabled</code>.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">icon</code></td>\n            <td><span class=\"dx-api-type\">boolean</span></td>\n            <td><span class=\"dx-api-default\">false</span></td>\n            <td>Square button for icon-only usage.</td>\n          </tr>\n          <tr>\n            <td><code class=\"dx-api-name\">full</code></td>\n            <td><span class=\"dx-api-type\">boolean</span></td>\n            <td><span class=\"dx-api-default\">false</span></td>\n            <td>Stretches to 100% width.</td>\n          </tr>\n        </tbody>\n      </table>\n\n      <h3>JS properties</h3>\n      <p>The <code>RenButton</code> class exposes getters/setters that mirror the boolean attributes. Useful when you'd rather flip a property than toggle an attribute.</p>\n      <div class=\"dx-pre\" tabindex=\"0\"><code>const btn = document.querySelector('ren-button');\nbtn.loading = true;          // shows spinner\nbtn.disabled = true;\nbtn.variant  = 'danger';     // re-renders</code></div>\n    </section>\n\n    <!-- ═══════════════════════════════════════════════════════════\n         6. ACCESSIBILITY\n         ═══════════════════════════════════════════════════════════ -->\n    <section class=\"dx-section\" id=\"a11y\">\n      <p class=\"dx-kicker\">Inclusive by default</p>\n      <h2>Accessibility</h2>\n      <p>Every button passes WCAG 2.1 AA in both modes and every variant. The component does most of the work for you, but a few things are still your call.</p>\n\n      <h3>Keyboard</h3>\n      <div class=\"dx-keys\">\n        <div class=\"dx-keyrow\">\n          <span class=\"keys\"><kbd class=\"ren-kbd\">Tab</kbd></span>\n          <span>Moves focus to the button (skips disabled buttons).</span>\n        </div>\n        <div class=\"dx-keyrow\">\n          <span class=\"keys\"><kbd class=\"ren-kbd\">Space</kbd></span>\n          <span>Fires the click handler. Standard native behavior.</span>\n        </div>\n        <div class=\"dx-keyrow\">\n          <span class=\"keys\"><kbd class=\"ren-kbd\">Enter</kbd></span>\n          <span>Fires the click handler. Inside a form, also submits the form unless <code>type=\"button\"</code> is set.</span>\n        </div>\n      </div>\n\n      <div class=\"dx-callout\">\n        <p><strong>Always set <code>type=\"button\"</code></strong> on buttons that aren't form submitters. The browser default is <code>type=\"submit\"</code>, which will submit the nearest enclosing form on Enter — usually not what you want for \"Cancel\", \"Open menu\", or any non-submit action.</p>\n      </div>\n\n      <h3>ARIA wiring</h3>\n      <ul>\n        <li>The button has <code>role=\"button\"</code> implicitly via the native <code>&lt;button&gt;</code>. No need to set it.</li>\n        <li>For <strong>icon-only</strong> buttons, set <code>aria-label</code> on the button. Decorative icons inside should carry <code>aria-hidden=\"true\"</code>.</li>\n        <li>For <strong>loading</strong> state, the component sets <code>aria-busy=\"true\"</code> automatically. Screen readers will announce the state change.</li>\n        <li>For <strong>disabled</strong> state, both <code>disabled</code> and <code>aria-disabled=\"true\"</code> are set. Disabled buttons are skipped in tab order; if you need a focusable-but-inert button (rare), use <code>aria-disabled</code> alone and handle the click guard yourself.</li>\n        <li>For <strong>toggle</strong> buttons (a button that flips between two states), add <code>aria-pressed=\"true\"</code> or <code>\"false\"</code>. The <em>group</em> example above uses this.</li>\n      </ul>\n\n      <h3>Screen reader announcements</h3>\n      <ul>\n        <li>\"Add to cart, button\" — for a regular labelled button.</li>\n        <li>\"Search, button, busy\" — when <code>loading</code> is set.</li>\n        <li>\"Save, button, dimmed\" (or similar) — when <code>disabled</code> is set.</li>\n        <li>\"Week, button, pressed\" — for a toggled button in a group.</li>\n      </ul>\n\n      <h3>Touch and pointer</h3>\n      <ul>\n        <li>Default and large sizes meet the <strong>44 × 44 px</strong> minimum touch target. The small size is below that and should only be used in dense desktop UI (toolbars, table cells).</li>\n        <li>The press-down feedback (a <code>scale(0.97)</code> on <code>:active</code>) respects <code>prefers-reduced-motion</code> via the semantic motion tokens — it stays subtle for users who request reduced motion.</li>\n        <li>Hover styles never replace focus styles. A keyboard user always sees a focus ring on the focused button, even when a different button is hovered.</li>\n      </ul>\n    </section>\n\n    <!-- ═══════════════════════════════════════════════════════════\n         7. EXAMPLES\n         ═══════════════════════════════════════════════════════════ -->\n    <section class=\"dx-section\" id=\"examples\">\n      <p class=\"dx-kicker\">Recipes</p>\n      <h2>Examples</h2>\n      <p>Common patterns that combine the variants above with other RenDS primitives. Copy the markup, swap the labels.</p>\n\n      <h3>Primary + secondary pair</h3>\n      <p>The classic \"confirm and cancel\" pairing. Primary on the right (or last), secondary on the left.</p>\n      <div class=\"dx-demo\">\n        <div class=\"dx-demo-preview\">\n          <div class=\"ren-cluster\" style=\"--cluster-gap: var(--space-2);\">\n            <button class=\"ren-btn ren-btn-secondary\" type=\"button\">Cancel</button>\n            <button class=\"ren-btn ren-btn-primary\" type=\"button\">Save changes</button>\n          </div>\n        </div>\n        <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div class=\"ren-cluster\"&gt;\n  &lt;button class=\"ren-btn ren-btn-secondary\" type=\"button\"&gt;Cancel&lt;/button&gt;\n  &lt;button class=\"ren-btn ren-btn-primary\"   type=\"button\"&gt;Save changes&lt;/button&gt;\n&lt;/div&gt;</code></pre>\n      </div>\n\n      <h3>Destructive confirmation</h3>\n      <p>For irreversible actions. The danger variant signals stakes; pair with secondary, never with another colored variant.</p>\n      <div class=\"dx-demo\">\n        <div class=\"dx-demo-preview\">\n          <div class=\"ren-cluster\" style=\"--cluster-gap: var(--space-2);\">\n            <button class=\"ren-btn ren-btn-secondary\" type=\"button\">Keep account</button>\n            <button class=\"ren-btn ren-btn-danger\" type=\"button\">Delete account</button>\n          </div>\n        </div>\n        <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div class=\"ren-cluster\"&gt;\n  &lt;button class=\"ren-btn ren-btn-secondary\" type=\"button\"&gt;Keep account&lt;/button&gt;\n  &lt;button class=\"ren-btn ren-btn-danger\"    type=\"button\"&gt;Delete account&lt;/button&gt;\n&lt;/div&gt;</code></pre>\n      </div>\n\n      <h3>Loading from JS</h3>\n      <p>Flip <code>loading</code> on submit, flip it back when the request resolves.</p>\n      <div class=\"dx-demo\">\n        <div class=\"dx-demo-preview\">\n          <ren-button id=\"demo-load\" variant=\"primary\">Save</ren-button>\n        </div>\n        <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>const btn = document.querySelector('ren-button#demo-load');\nbtn.addEventListener('click', async () =&gt; {\n  btn.loading = true;\n  try {\n    await fetch('/api/save', { method: 'POST' });\n  } finally {\n    btn.loading = false;\n  }\n});</code></pre>\n      </div>\n\n      <h3>Icon-only in a toolbar</h3>\n      <p>Ghost icon buttons disappear into chrome until hovered. Always set <code>aria-label</code>.</p>\n      <div class=\"dx-demo\">\n        <div class=\"dx-demo-preview\">\n          <div class=\"ren-cluster\" style=\"--cluster-gap: var(--space-1);\">\n            <button class=\"ren-btn ren-btn-ghost ren-btn-icon\" type=\"button\" aria-label=\"Bold\">\n              <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" aria-hidden=\"true\"><path d=\"M14 12a4 4 0 0 0 0-8H6v8\"/><path d=\"M15 20a4 4 0 0 0 0-8H6v8z\"/></svg>\n            </button>\n            <button class=\"ren-btn ren-btn-ghost ren-btn-icon\" type=\"button\" aria-label=\"Italic\">\n              <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><line x1=\"19\" y1=\"4\" x2=\"10\" y2=\"4\"/><line x1=\"14\" y1=\"20\" x2=\"5\" y2=\"20\"/><line x1=\"15\" y1=\"4\" x2=\"9\" y2=\"20\"/></svg>\n            </button>\n            <button class=\"ren-btn ren-btn-ghost ren-btn-icon\" type=\"button\" aria-label=\"Underline\">\n              <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><path d=\"M6 4v6a6 6 0 0 0 12 0V4\"/><line x1=\"4\" y1=\"20\" x2=\"20\" y2=\"20\"/></svg>\n            </button>\n          </div>\n        </div>\n        <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div class=\"ren-cluster\"&gt;\n  &lt;button class=\"ren-btn ren-btn-ghost ren-btn-icon\" type=\"button\" aria-label=\"Bold\"&gt;\n    &lt;svg ... aria-hidden=\"true\"&gt;...&lt;/svg&gt;\n  &lt;/button&gt;\n  &lt;!-- repeat for italic, underline --&gt;\n&lt;/div&gt;</code></pre>\n      </div>\n\n      <h3>Segmented control</h3>\n      <p>Three buttons in a group, one pressed. Manage <code>aria-pressed</code> from JS as the user clicks.</p>\n      <div class=\"dx-demo\">\n        <div class=\"dx-demo-preview\">\n          <div class=\"ren-btn-group\" role=\"group\" aria-label=\"View by\">\n            <button class=\"ren-btn ren-btn-secondary\" type=\"button\">Day</button>\n            <button class=\"ren-btn ren-btn-secondary\" type=\"button\" aria-pressed=\"true\">Week</button>\n            <button class=\"ren-btn ren-btn-secondary\" type=\"button\">Month</button>\n          </div>\n        </div>\n        <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div class=\"ren-btn-group\" role=\"group\" aria-label=\"View by\"&gt;\n  &lt;button class=\"ren-btn ren-btn-secondary\" type=\"button\"&gt;Day&lt;/button&gt;\n  &lt;button class=\"ren-btn ren-btn-secondary\" type=\"button\" aria-pressed=\"true\"&gt;Week&lt;/button&gt;\n  &lt;button class=\"ren-btn ren-btn-secondary\" type=\"button\"&gt;Month&lt;/button&gt;\n&lt;/div&gt;</code></pre>\n      </div>\n\n      <h3>Full-width on mobile</h3>\n      <p>Stretch the primary CTA on small screens for comfortable thumb reach.</p>\n      <div class=\"dx-demo\">\n        <div class=\"dx-demo-preview\" style=\"display: block;\">\n          <button class=\"ren-btn ren-btn-primary ren-btn-lg ren-btn-full\" type=\"button\">Continue</button>\n        </div>\n        <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;button class=\"ren-btn ren-btn-primary ren-btn-lg ren-btn-full\" type=\"button\"&gt;\n  Continue\n&lt;/button&gt;</code></pre>\n      </div>\n    </section>\n\n    </main>\n\n  </div>\n\n  <!-- Web Component registration so the live demos render -->\n  <script type=\"module\" src=\"../../components/primitives/ren-button/ren-button.js\"></script>\n\n  <!-- Demo: simulate loading toggle -->\n  <script>\n    const demoLoad = document.getElementById('demo-load');\n    if (demoLoad) {\n      demoLoad.addEventListener('click', () => {\n        if (demoLoad.loading) return;\n        demoLoad.loading = true;\n        setTimeout(() => { demoLoad.loading = false; }, 1600);\n      });\n    }\n  </script>\n\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-button.html",
      "id": "docs:docs/components/ren-button.html",
      "type": "docs_page",
      "name": "ren-button docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Calendar — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; justify-content: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n\n    /* Demo calendar */\n    .demo-cal {\n      padding: var(--space-3);\n      background: var(--color-surface);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n      width: 280px;\n    }\n    .demo-cal-head {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      margin-bottom: var(--space-2);\n    }\n    .demo-cal-grid {\n      display: grid;\n      grid-template-columns: repeat(7, 1fr);\n      gap: 2px;\n      text-align: center;\n      font-size: var(--text-xs);\n    }\n    .demo-cal-grid .wd {\n      color: var(--color-text-muted);\n      padding: var(--space-1);\n      font-weight: var(--weight-semibold);\n    }\n    .demo-cal-grid .day {\n      padding: var(--space-1);\n      cursor: pointer;\n      border-radius: 4px;\n    }\n    .demo-cal-grid .day:hover { background: var(--color-fill); }\n    .demo-cal-grid .day.outside { color: var(--color-text-faint); }\n    .demo-cal-grid .day.today { font-weight: var(--weight-semibold); color: var(--color-accent); }\n    .demo-cal-grid .day.selected {\n      background: var(--color-accent);\n      color: var(--color-on-accent);\n      font-weight: var(--weight-semibold);\n    }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\" aria-current=\"page\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\">\n          <ol>\n            <li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li>\n            <li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li>\n            <li aria-current=\"page\">Calendar</li>\n          </ol>\n        </nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Calendar <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">Monthly grid for picking a date. Locale-aware first day of week, full keyboard navigation, plus optional range and multi-select modes.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>The standalone calendar grid. Use it inline in a page (event scheduling, availability picker, booking flow), or wire it up to a trigger via <a href=\"ren-date-picker.html\" class=\"ren-link\">Date Picker</a> when the calendar should appear in a popover. For start + end pairs, see <a href=\"ren-date-range-picker.html\" class=\"ren-link\">Date Range Picker</a>.</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <div class=\"demo-cal\" role=\"application\" aria-label=\"Calendar, May 2026\">\n              <div class=\"demo-cal-head\">\n                <button class=\"ren-btn ren-btn-ghost ren-btn-sm\" type=\"button\" aria-label=\"Previous month\">‹</button>\n                <strong style=\"font-size: var(--text-sm);\">May 2026</strong>\n                <button class=\"ren-btn ren-btn-ghost ren-btn-sm\" type=\"button\" aria-label=\"Next month\">›</button>\n              </div>\n              <div class=\"demo-cal-grid\">\n                <span class=\"wd\">M</span><span class=\"wd\">T</span><span class=\"wd\">W</span><span class=\"wd\">T</span><span class=\"wd\">F</span><span class=\"wd\">S</span><span class=\"wd\">S</span>\n                <span class=\"day outside\">27</span><span class=\"day outside\">28</span><span class=\"day outside\">29</span><span class=\"day outside\">30</span>\n                <span class=\"day\">1</span><span class=\"day\">2</span><span class=\"day\">3</span>\n                <span class=\"day\">4</span><span class=\"day\">5</span><span class=\"day\">6</span>\n                <span class=\"day selected\" aria-selected=\"true\">7</span>\n                <span class=\"day\">8</span><span class=\"day\">9</span><span class=\"day\">10</span>\n                <span class=\"day\">11</span><span class=\"day\">12</span><span class=\"day\">13</span><span class=\"day\">14</span>\n                <span class=\"day\">15</span><span class=\"day\">16</span><span class=\"day\">17</span>\n                <span class=\"day\">18</span><span class=\"day\">19</span><span class=\"day\">20</span><span class=\"day\">21</span>\n                <span class=\"day\">22</span><span class=\"day\">23</span><span class=\"day\">24</span>\n                <span class=\"day\">25</span><span class=\"day\">26</span><span class=\"day\">27</span><span class=\"day\">28</span>\n                <span class=\"day\">29</span><span class=\"day\">30</span><span class=\"day\">31</span>\n                <span class=\"day outside\">1</span><span class=\"day outside\">2</span><span class=\"day outside\">3</span><span class=\"day outside\">4</span>\n              </div>\n            </div>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-calendar value=\"2026-05-07\" locale=\"en-US\"&gt;&lt;/ren-calendar&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead>\n            <tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td><code class=\"dx-api-name\">value</code></td>\n              <td><span class=\"dx-api-type\">\"YYYY-MM-DD\"</span></td>\n              <td><span class=\"dx-api-default\">today</span></td>\n              <td>Currently selected date.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">min</code> / <code class=\"dx-api-name\">max</code></td>\n              <td><span class=\"dx-api-type\">\"YYYY-MM-DD\"</span></td>\n              <td><span class=\"dx-api-default\">—</span></td>\n              <td>Restrict the selectable range.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">locale</code></td>\n              <td><span class=\"dx-api-type\">BCP-47 string</span></td>\n              <td><span class=\"dx-api-default\">\"en\"</span></td>\n              <td>Affects month / weekday names and the first day of the week.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">mode</code></td>\n              <td><span class=\"dx-api-type\">\"single\" | \"range\" | \"multi\"</span></td>\n              <td><span class=\"dx-api-default\">\"single\"</span></td>\n              <td>Selection mode.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">disabled-dates</code></td>\n              <td><span class=\"dx-api-type\">string[]</span></td>\n              <td><span class=\"dx-api-default\">[]</span></td>\n              <td>Array of <code>YYYY-MM-DD</code> strings to disable individually.</td>\n            </tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li>Implements the WAI-ARIA Date Picker dialog pattern. The grid is a <code>role=\"grid\"</code>; each day is a <code>role=\"gridcell\"</code>.</li>\n          <li>Arrow keys move day-by-day, <kbd>Page Up</kbd> / <kbd>Page Down</kbd> move by month, <kbd>Shift</kbd>+<kbd>Page Up</kbd>/<kbd>Page Down</kbd> by year, <kbd>Home</kbd> / <kbd>End</kbd> jump to the start / end of the week.</li>\n          <li>Each day cell carries an <code>aria-label</code> with the full date so screen readers announce <em>\"Thursday, May 7, 2026\"</em> rather than just <em>\"7\"</em>.</li>\n          <li>The currently selected day uses <code>aria-selected=\"true\"</code>; disabled days use <code>aria-disabled=\"true\"</code>.</li>\n        </ul>\n      </section>\n\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-calendar.html",
      "id": "docs:docs/components/ren-calendar.html",
      "type": "docs_page",
      "name": "ren-calendar docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Card — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    /* Page-specific styles — chrome lives in shell.css */\n\n    /* ═══════════════════════════════════════════════════════════\n       Component-page specific patterns\n       ═══════════════════════════════════════════════════════════ */\n\n    /* Demo block */\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview {\n      background: var(--color-surface-raised);\n      padding: var(--space-6) var(--space-5);\n      display: flex;\n      flex-wrap: wrap;\n      gap: var(--space-3);\n      align-items: center;\n      justify-content: center;\n      min-height: 96px;\n    }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n\n    /* Variant grid */\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-3); background: var(--color-surface); border: 1px solid var(--color-border-muted); border-radius: var(--radius-md); }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n\n    /* Anatomy */\n    .dx-anatomy { margin: var(--space-4) 0; display: grid; gap: var(--space-4); }\n    .dx-anatomy-num { display: inline-grid; place-items: center; width: 22px; height: 22px; border-radius: 50%; background: var(--color-text); color: var(--color-surface); font-size: var(--text-xs); font-weight: var(--weight-bold); flex-shrink: 0; }\n    .dx-anatomy-stage { background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-6); display: flex; flex-direction: column; align-items: center; gap: var(--space-3); }\n    .dx-anatomy-stage-label { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); font-weight: var(--weight-semibold); margin: 0; }\n    .dx-anatomy-parts { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-3); }\n    @media (max-width: 720px) { .dx-anatomy-parts { grid-template-columns: 1fr; } }\n    .dx-anatomy-part { background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-4); display: grid; gap: var(--space-3); }\n    .dx-anatomy-part-header { display: flex; align-items: center; gap: var(--space-2); }\n    .dx-anatomy-part-name { font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--color-text); }\n    .dx-anatomy-part-spec {\n      background: var(--color-surface-sunken);\n      border: 1px dashed var(--color-border);\n      border-radius: var(--radius-sm);\n      min-height: 72px;\n      padding: var(--space-3);\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      color: var(--color-text);\n    }\n    .dx-anatomy-part-desc { font-size: var(--text-sm); line-height: 1.5; color: var(--color-text-secondary); margin: 0; }\n    .dx-anatomy-part-desc code { font-size: 0.85em; }\n\n    /* Body p reset for demos that use raw <p> inside .ren-card-body (the title\n       and description resets now live in ren-card.css itself, post-fix). */\n    .dx-demo-preview .ren-card-body p,\n    .dx-anatomy-stage .ren-card-body p { margin: 0; }\n\n    /* .dx-api and .dx-keys live in shell.css so every reference table\n       on every component page shares the same column boundary. */\n  </style>\n</head>\n<body>\n\n  <header class=\"dx-nav\">\n    <div class=\"dx-nav-inner\">\n      <a href=\"../index.html\" class=\"dx-brand\">\n        <span class=\"dx-brand-mark\">R</span>\n        <span>RenDS</span>\n        <span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span>\n      </a>\n      <nav class=\"dx-nav-menu\" aria-label=\"Primary\">\n        <a href=\"../index.html\">Docs</a>\n        <a href=\"../components.html\" aria-current=\"page\">Components</a>\n        <a href=\"../../templates/index.html\">Templates</a>\n        <a href=\"../../create/index.html\">Theme Builder</a>\n      </nav>\n      <div class=\"dx-nav-actions\">\n        <a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a>\n        <a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n\n    <!-- Persistent sidebar — same nav as the catalog so context is never lost -->\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\" aria-current=\"page\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <!-- Content -->\n    <main class=\"dx-content\">\n\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\">\n          <ol>\n            <li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li>\n            <li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li>\n            <li aria-current=\"page\">Card</li>\n          </ol>\n        </nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Card <span class=\"dx-api-badge dx-api-badge-css\" title=\"Works without any JavaScript\">CSS-only</span></h1>\n        <p class=\"lede\">A flexible content container with optional header, body, and footer sections. Five visual variants, status accents, and built-in interactive / selectable patterns. Pure CSS — no JavaScript required.</p>\n      </header>\n\n    <!-- 1. OVERVIEW -->\n    <section class=\"dx-section\" id=\"overview\">\n      <p class=\"dx-kicker\">About</p>\n      <h2>Overview</h2>\n      <p>A card groups related content into a single visual unit — a profile, a stat, a settings panel, a list item, an article preview. RenDS gives you one base component with optional header / body / footer slots, five visual variants, and three interaction states (static, interactive, selectable).</p>\n\n      <p>Cards are entirely CSS-driven. There's no <code>&lt;ren-card&gt;</code> Web Component because there's nothing for JS to do — the structure is plain HTML, the variants are class modifiers, and the interactivity (hover, focus, selection) is handled by CSS pseudo-classes plus standard ARIA attributes.</p>\n\n      <h3>When to use</h3>\n      <ul>\n        <li>To group a chunk of content (text + actions + metadata) that the user will visually scan as a single unit.</li>\n        <li>To present a list of similar items (a grid of products, posts, profiles, settings groups).</li>\n        <li>For status surfaces — use the <code>data-status</code> attribute to signal success, warning, danger, or info at a glance.</li>\n      </ul>\n\n      <h3>When not to use</h3>\n      <ul>\n        <li>For a single line of content — a card adds visual weight that body text doesn't need. Use a paragraph or a banner.</li>\n        <li>For a modal dialog — that's <code>ren-dialog</code>, which traps focus and dims the rest of the page.</li>\n        <li>For navigation between pages — wrap an anchor or use <code>ren-link</code>; cards make poor primary navigation.</li>\n      </ul>\n\n      <div class=\"dx-callout\">\n        <p><strong>Cards are containers, not components.</strong> They don't dictate what goes inside. Mix and match — drop in a button group, an avatar, a chart, a form. The card supplies the surface; you supply the content.</p>\n      </div>\n    </section>\n\n    <!-- 2. ANATOMY -->\n    <section class=\"dx-section\" id=\"anatomy\">\n      <p class=\"dx-kicker\">Structure</p>\n      <h2>Anatomy</h2>\n      <p>A card has a container plus three optional section slots. Each slot has its own padding rules so they nest cleanly without you fighting margins.</p>\n\n      <div class=\"dx-anatomy\">\n        <div class=\"dx-anatomy-stage\">\n          <p class=\"dx-anatomy-stage-label\">Assembled</p>\n          <div class=\"ren-card\" style=\"max-width: 320px; width: 100%;\">\n            <div class=\"ren-card-header\">\n              <h4 class=\"ren-card-title\" style=\"margin: 0;\">Project update</h4>\n              <p class=\"ren-card-description\" style=\"margin: 0;\">Last edited 2 hours ago</p>\n            </div>\n            <div class=\"ren-card-body\">\n              <p style=\"margin: 0;\">Dashboard ready for review. All components passed a11y audit.</p>\n            </div>\n            <div class=\"ren-card-footer\">\n              <button type=\"button\" class=\"ren-btn ren-btn-secondary ren-btn-sm\">Dismiss</button>\n              <button type=\"button\" class=\"ren-btn ren-btn-primary ren-btn-sm\">View</button>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"dx-anatomy-parts\">\n\n          <div class=\"dx-anatomy-part\">\n            <div class=\"dx-anatomy-part-header\">\n              <span class=\"dx-anatomy-num\" aria-hidden=\"true\">1</span>\n              <span class=\"dx-anatomy-part-name\">Container (.ren-card)</span>\n            </div>\n            <div class=\"dx-anatomy-part-spec\" aria-hidden=\"true\">\n              <div style=\"width: 80%; height: 56px; background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-lg);\"></div>\n            </div>\n            <p class=\"dx-anatomy-part-desc\">The outer surface. Sets background (<code>--color-surface-raised</code>), border, radius, and clips children with <code>overflow: hidden</code>.</p>\n          </div>\n\n          <div class=\"dx-anatomy-part\">\n            <div class=\"dx-anatomy-part-header\">\n              <span class=\"dx-anatomy-num\" aria-hidden=\"true\">2</span>\n              <span class=\"dx-anatomy-part-name\">Header (.ren-card-header)</span>\n            </div>\n            <div class=\"dx-anatomy-part-spec\" aria-hidden=\"true\">\n              <div class=\"ren-card\" style=\"width: 100%;\">\n                <div class=\"ren-card-header\">\n                  <h4 class=\"ren-card-title\">Title here</h4>\n                  <p class=\"ren-card-description\">Description below</p>\n                </div>\n                <div class=\"ren-card-body\">\n                  <p style=\"margin: 0; opacity: 0.35; font-size: var(--text-sm);\">…body…</p>\n                </div>\n              </div>\n            </div>\n            <p class=\"dx-anatomy-part-desc\">Optional. Top section for title + description. Stack of <code>.ren-card-title</code> + <code>.ren-card-description</code>.</p>\n          </div>\n\n          <div class=\"dx-anatomy-part\">\n            <div class=\"dx-anatomy-part-header\">\n              <span class=\"dx-anatomy-num\" aria-hidden=\"true\">3</span>\n              <span class=\"dx-anatomy-part-name\">Body (.ren-card-body)</span>\n            </div>\n            <div class=\"dx-anatomy-part-spec\" aria-hidden=\"true\">\n              <div class=\"ren-card\" style=\"width: 100%;\">\n                <div class=\"ren-card-header\">\n                  <p class=\"ren-card-description\" style=\"opacity: 0.35;\">…header…</p>\n                </div>\n                <div class=\"ren-card-body\">\n                  <p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-secondary);\">Main content goes here.</p>\n                </div>\n              </div>\n            </div>\n            <p class=\"dx-anatomy-part-desc\">The main content area. Padded uniformly. When following a header, top padding tightens automatically.</p>\n          </div>\n\n          <div class=\"dx-anatomy-part\">\n            <div class=\"dx-anatomy-part-header\">\n              <span class=\"dx-anatomy-num\" aria-hidden=\"true\">4</span>\n              <span class=\"dx-anatomy-part-name\">Footer (.ren-card-footer)</span>\n            </div>\n            <div class=\"dx-anatomy-part-spec\" aria-hidden=\"true\">\n              <div class=\"ren-card\" style=\"width: 100%;\">\n                <div class=\"ren-card-body\">\n                  <p style=\"margin: 0; opacity: 0.35; font-size: var(--text-sm);\">…body…</p>\n                </div>\n                <div class=\"ren-card-footer\">\n                  <button type=\"button\" class=\"ren-btn ren-btn-secondary ren-btn-sm\" tabindex=\"-1\">Cancel</button>\n                  <button type=\"button\" class=\"ren-btn ren-btn-primary ren-btn-sm\" tabindex=\"-1\">Save</button>\n                </div>\n              </div>\n            </div>\n            <p class=\"dx-anatomy-part-desc\">Optional. Bottom row for actions. Use <code>.ren-card-footer-border</code> for a separating line above the footer.</p>\n          </div>\n\n        </div>\n      </div>\n    </section>\n\n    <!-- 3. DEMO -->\n    <section class=\"dx-section\" id=\"demo\">\n      <p class=\"dx-kicker\">Live</p>\n      <h2>Demo</h2>\n      <p>The default card with all three sections. Drop in your own content — RenDS won't fight you.</p>\n\n      <div class=\"dx-demo\">\n        <div class=\"dx-demo-preview\">\n          <div class=\"ren-card\" style=\"max-width: 360px; width: 100%;\">\n            <div class=\"ren-card-header\">\n              <h4 class=\"ren-card-title\" style=\"margin: 0;\">Welcome back</h4>\n              <p class=\"ren-card-description\" style=\"margin: 0;\">Pick up where you left off</p>\n            </div>\n            <div class=\"ren-card-body\">\n              <p style=\"margin: 0;\">Three drafts saved, two pull requests waiting for review, and a deployment scheduled for tomorrow.</p>\n            </div>\n            <div class=\"ren-card-footer\">\n              <button type=\"button\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">Later</button>\n              <button type=\"button\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Open dashboard</button>\n            </div>\n          </div>\n        </div>\n        <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div class=\"ren-card\"&gt;\n  &lt;div class=\"ren-card-header\"&gt;\n    &lt;h4 class=\"ren-card-title\"&gt;Welcome back&lt;/h4&gt;\n    &lt;p class=\"ren-card-description\"&gt;Pick up where you left off&lt;/p&gt;\n  &lt;/div&gt;\n  &lt;div class=\"ren-card-body\"&gt;\n    &lt;p&gt;Three drafts saved, two pull requests waiting for review…&lt;/p&gt;\n  &lt;/div&gt;\n  &lt;div class=\"ren-card-footer\"&gt;\n    &lt;button class=\"ren-btn ren-btn-ghost ren-btn-sm\"&gt;Later&lt;/button&gt;\n    &lt;button class=\"ren-btn ren-btn-primary ren-btn-sm\"&gt;Open dashboard&lt;/button&gt;\n  &lt;/div&gt;\n&lt;/div&gt;</code></pre>\n      </div>\n\n      <p>Sections are all optional. A card with only <code>.ren-card-body</code> works. A card with no sections at all (just children) auto-pads its first and last child via <code>:first-child</code> / <code>:last-child</code> selectors.</p>\n    </section>\n\n    <!-- 4. VARIANTS -->\n    <section class=\"dx-section\" id=\"variants\">\n      <p class=\"dx-kicker\">Catalog</p>\n      <h2>Variants</h2>\n      <p>Five visual variants — pick the one that matches the card's role. Plus three interaction states layered on top, and four status accents for at-a-glance signals.</p>\n\n      <div class=\"dx-vgrid\">\n\n        <div class=\"dx-vrow\">\n          <span class=\"dx-vrow-label\">Visual</span>\n          <div class=\"dx-vrow-items\" style=\"display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\">\n            <div class=\"ren-card\">\n              <div class=\"ren-card-body\"><strong>Default</strong><p style=\"margin: var(--space-1) 0 0; font-size: var(--text-xs); color: var(--color-text-muted);\">Surface + border</p></div>\n            </div>\n            <div class=\"ren-card ren-card-elevated\">\n              <div class=\"ren-card-body\"><strong>Elevated</strong><p style=\"margin: var(--space-1) 0 0; font-size: var(--text-xs); color: var(--color-text-muted);\">Shadow, no border</p></div>\n            </div>\n            <div class=\"ren-card ren-card-outline\">\n              <div class=\"ren-card-body\"><strong>Outline</strong><p style=\"margin: var(--space-1) 0 0; font-size: var(--text-xs); color: var(--color-text-muted);\">Border, transparent bg</p></div>\n            </div>\n            <div class=\"ren-card ren-card-ghost\">\n              <div class=\"ren-card-body\"><strong>Ghost</strong><p style=\"margin: var(--space-1) 0 0; font-size: var(--text-xs); color: var(--color-text-muted);\">No surface, grouping only</p></div>\n            </div>\n            <div class=\"ren-card ren-card-sunken\">\n              <div class=\"ren-card-body\"><strong>Sunken</strong><p style=\"margin: var(--space-1) 0 0; font-size: var(--text-xs); color: var(--color-text-muted);\">Recessed surface</p></div>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"dx-vrow\">\n          <span class=\"dx-vrow-label\">Interaction</span>\n          <div class=\"dx-vrow-items\" style=\"display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\">\n            <div class=\"ren-card ren-card-interactive\" tabindex=\"0\">\n              <div class=\"ren-card-body\"><strong>Interactive</strong><p style=\"margin: var(--space-1) 0 0; font-size: var(--text-xs); color: var(--color-text-muted);\">Hover lifts, click feedback</p></div>\n            </div>\n            <div class=\"ren-card ren-card-selectable\" tabindex=\"0\">\n              <div class=\"ren-card-body\"><strong>Selectable</strong><p style=\"margin: var(--space-1) 0 0; font-size: var(--text-xs); color: var(--color-text-muted);\">Click to set <code>aria-selected</code></p></div>\n            </div>\n            <div class=\"ren-card ren-card-selectable\" tabindex=\"0\" aria-selected=\"true\">\n              <div class=\"ren-card-body\"><strong>Selectable (selected)</strong><p style=\"margin: var(--space-1) 0 0; font-size: var(--text-xs); color: var(--color-text-muted);\">Accent border + ring</p></div>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"dx-vrow\">\n          <span class=\"dx-vrow-label\">Status</span>\n          <div class=\"dx-vrow-items\" style=\"display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\">\n            <div class=\"ren-card\" data-status=\"success\">\n              <div class=\"ren-card-body\"><strong>Success</strong></div>\n            </div>\n            <div class=\"ren-card\" data-status=\"warning\">\n              <div class=\"ren-card-body\"><strong>Warning</strong></div>\n            </div>\n            <div class=\"ren-card\" data-status=\"danger\">\n              <div class=\"ren-card-body\"><strong>Danger</strong></div>\n            </div>\n            <div class=\"ren-card\" data-status=\"info\">\n              <div class=\"ren-card-body\"><strong>Info</strong></div>\n            </div>\n          </div>\n        </div>\n\n      </div>\n    </section>\n\n    <!-- 5. API -->\n    <section class=\"dx-section\" id=\"api\">\n      <p class=\"dx-kicker\">Reference</p>\n      <h2>API</h2>\n      <p>CSS classes only. There is no Web Component for card.</p>\n\n      <h3>Container</h3>\n      <table class=\"dx-api\">\n        <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n        <tbody>\n          <tr><td><code class=\"dx-api-name\">.ren-card</code></td><td>Base container. Required.</td></tr>\n          <tr><td><code class=\"dx-api-name\">.ren-card-elevated</code></td><td>Removes border, adds <code>--shadow-md</code>. For floating UI.</td></tr>\n          <tr><td><code class=\"dx-api-name\">.ren-card-outline</code></td><td>Border only, transparent background. Lighter weight.</td></tr>\n          <tr><td><code class=\"dx-api-name\">.ren-card-ghost</code></td><td>Removes both border and background. Pure structural grouping.</td></tr>\n          <tr><td><code class=\"dx-api-name\">.ren-card-sunken</code></td><td>Inverted depth — sits below the surface. Use sparingly.</td></tr>\n        </tbody>\n      </table>\n\n      <h3>Sections</h3>\n      <table class=\"dx-api\">\n        <thead><tr><th>Class</th><th>Use</th></tr></thead>\n        <tbody>\n          <tr><td><code class=\"dx-api-name\">.ren-card-header</code></td><td>Top section. Stack of title + description with auto-spacing.</td></tr>\n          <tr><td><code class=\"dx-api-name\">.ren-card-title</code></td><td>Heading inside the header. Set on the actual heading tag (<code>&lt;h3&gt;</code>, <code>&lt;h4&gt;</code>, etc).</td></tr>\n          <tr><td><code class=\"dx-api-name\">.ren-card-description</code></td><td>Subtitle / metadata under the title. Muted color.</td></tr>\n          <tr><td><code class=\"dx-api-name\">.ren-card-body</code></td><td>Main content. Self-padded.</td></tr>\n          <tr><td><code class=\"dx-api-name\">.ren-card-footer</code></td><td>Bottom row, no separator. For action clusters.</td></tr>\n          <tr><td><code class=\"dx-api-name\">.ren-card-footer-border</code></td><td>Footer with a top border separator. Heavier visual weight.</td></tr>\n          <tr><td><code class=\"dx-api-name\">.ren-card-simple</code></td><td>Quick padding shorthand for cards without explicit sections.</td></tr>\n        </tbody>\n      </table>\n\n      <h3>Interaction</h3>\n      <table class=\"dx-api\">\n        <thead><tr><th>Class / attribute</th><th>Effect</th></tr></thead>\n        <tbody>\n          <tr><td><code class=\"dx-api-name\">.ren-card-interactive</code></td><td>Hover lift + shadow + border emphasis. Adds <code>cursor: pointer</code>, focus ring on <code>:focus-visible</code>. Wrap in or apply to a focusable element (<code>&lt;a&gt;</code>, <code>&lt;button&gt;</code>, or <code>tabindex=\"0\"</code>).</td></tr>\n          <tr><td><code class=\"dx-api-name\">.ren-card-selectable</code></td><td>Hover swaps border to accent. Combine with <code>aria-selected=\"true\"</code> or <code>data-selected</code> for the chosen state.</td></tr>\n          <tr><td><code class=\"dx-api-name\">[data-status=\"success|warning|danger|info\"]</code></td><td>Adds a 3px accent border on top in the matching status color. Stacks with any visual variant.</td></tr>\n        </tbody>\n      </table>\n\n      <h3>Layout helper</h3>\n      <table class=\"dx-api\">\n        <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n        <tbody>\n          <tr><td><code class=\"dx-api-name\">.ren-card-group</code></td><td>Auto-fill grid of cards at <code>min(100%, 280px)</code> per column. Drop on the parent of the cards.</td></tr>\n          <tr><td><code class=\"dx-api-name\">.ren-card-cover</code></td><td>For a full-bleed image at the top of a card. <code>width: 100%; object-fit: cover</code>.</td></tr>\n        </tbody>\n      </table>\n    </section>\n\n    <!-- 6. ACCESSIBILITY -->\n    <section class=\"dx-section\" id=\"a11y\">\n      <p class=\"dx-kicker\">Inclusive by default</p>\n      <h2>Accessibility</h2>\n      <p>Static cards are non-interactive — they're just visual containers and don't need any ARIA. Interactive and selectable cards inherit semantics from the wrapping element you choose.</p>\n\n      <h3>Static cards</h3>\n      <ul>\n        <li>Use semantic content inside — proper heading levels (<code>&lt;h2&gt;</code>–<code>&lt;h4&gt;</code>) for titles, real <code>&lt;p&gt;</code> tags for prose, real <code>&lt;button&gt;</code>s for actions.</li>\n        <li>Don't add ARIA roles to the card itself — it's a generic <code>&lt;div&gt;</code> by intent.</li>\n        <li>For status accents, the visual <code>data-status=\"danger\"</code> top border isn't enough on its own. Pair with a text label, an icon, or an <code>aria-describedby</code> for screen reader users.</li>\n      </ul>\n\n      <h3>Interactive cards</h3>\n      <p>If the entire card is clickable, wrap the card content in an <code>&lt;a&gt;</code> or <code>&lt;button&gt;</code>, or set <code>tabindex=\"0\"</code> + <code>role=\"button\"</code> if neither makes sense.</p>\n      <div class=\"dx-keys\">\n        <div class=\"dx-keyrow\"><span class=\"keys\"><kbd class=\"ren-kbd\">Tab</kbd></span><span>Moves focus into the interactive card.</span></div>\n        <div class=\"dx-keyrow\"><span class=\"keys\"><kbd class=\"ren-kbd\">Enter</kbd></span><span>Activates the card (follows the wrapping <code>&lt;a&gt;</code> or fires <code>&lt;button&gt;</code>'s click).</span></div>\n        <div class=\"dx-keyrow\"><span class=\"keys\"><kbd class=\"ren-kbd\">Space</kbd></span><span>Activates if the wrapper is a <code>&lt;button&gt;</code>. Anchors don't respond to Space — use Enter.</span></div>\n      </div>\n\n      <div class=\"dx-callout\">\n        <p><strong>Don't nest interactive elements.</strong> An interactive card that contains a button creates a confusing focus order — Tab, Tab, Tab to land somewhere predictable. Pick one: either the whole card is clickable, or the card has internal buttons. Not both.</p>\n      </div>\n\n      <h3>Selectable cards (radio / checkbox patterns)</h3>\n      <ul>\n        <li>For exclusive choice (one of N), wrap each card in a <code>&lt;label&gt;</code> with a hidden <code>&lt;input type=\"radio\"&gt;</code>, or use <code>role=\"radio\"</code> + <code>aria-checked</code>.</li>\n        <li>For multi-select, use <code>role=\"checkbox\"</code> + <code>aria-checked</code>, or a hidden <code>&lt;input type=\"checkbox\"&gt;</code> in a label.</li>\n        <li>Set <code>aria-selected=\"true\"</code> on the chosen card so screen readers announce \"selected\". The CSS targets both <code>aria-selected</code> and <code>data-selected</code> for the accent ring.</li>\n      </ul>\n\n      <h3>Reduced motion</h3>\n      <p>The hover-lift on interactive cards uses <code>--duration-tactile</code> and respects <code>prefers-reduced-motion</code> via the semantic motion tokens. Users who request reduced motion get a quieter transition (color change without scale).</p>\n    </section>\n\n    <!-- 7. EXAMPLES -->\n    <section class=\"dx-section\" id=\"examples\">\n      <p class=\"dx-kicker\">Recipes</p>\n      <h2>Examples</h2>\n      <p>Common compositions. Copy the markup, swap the content.</p>\n\n      <h3>KPI / stat card</h3>\n      <p>For dashboards. One number, one label, optional trend.</p>\n      <div class=\"dx-demo\">\n        <div class=\"dx-demo-preview\">\n          <div class=\"ren-card\" style=\"max-width: 240px; width: 100%;\">\n            <div class=\"ren-card-body\">\n              <p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Active users</p>\n              <p style=\"margin: var(--space-1) 0 0; font-size: var(--text-3xl); font-weight: var(--weight-bold); line-height: 1.1;\">12,430</p>\n              <p style=\"margin: var(--space-2) 0 0; font-size: var(--text-sm); color: var(--color-success); font-weight: var(--weight-medium);\">↑ 12.5% vs last month</p>\n            </div>\n          </div>\n        </div>\n        <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div class=\"ren-card\"&gt;\n  &lt;div class=\"ren-card-body\"&gt;\n    &lt;p class=\"stat-label\"&gt;Active users&lt;/p&gt;\n    &lt;p class=\"stat-value\"&gt;12,430&lt;/p&gt;\n    &lt;p class=\"stat-trend\"&gt;↑ 12.5% vs last month&lt;/p&gt;\n  &lt;/div&gt;\n&lt;/div&gt;</code></pre>\n      </div>\n\n      <h3>Selectable plan card (radio pattern)</h3>\n      <p>For pricing tiers, plan switchers, theme pickers.</p>\n      <div class=\"dx-demo\">\n        <div class=\"dx-demo-preview\" style=\"display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--space-3); width: 100%;\">\n          <label class=\"ren-card ren-card-selectable\" style=\"cursor: pointer; display: block;\">\n            <input type=\"radio\" name=\"plan-demo\" style=\"position: absolute; opacity: 0; pointer-events: none;\">\n            <div class=\"ren-card-body\">\n              <strong>Free</strong>\n              <p style=\"margin: var(--space-1) 0 0; font-size: var(--text-sm); color: var(--color-text-muted);\">$0/mo · 3 projects</p>\n            </div>\n          </label>\n          <label class=\"ren-card ren-card-selectable\" style=\"cursor: pointer; display: block;\" aria-selected=\"true\">\n            <input type=\"radio\" name=\"plan-demo\" style=\"position: absolute; opacity: 0; pointer-events: none;\" checked>\n            <div class=\"ren-card-body\">\n              <strong>Pro</strong>\n              <p style=\"margin: var(--space-1) 0 0; font-size: var(--text-sm); color: var(--color-text-muted);\">$12/mo · unlimited</p>\n            </div>\n          </label>\n          <label class=\"ren-card ren-card-selectable\" style=\"cursor: pointer; display: block;\">\n            <input type=\"radio\" name=\"plan-demo\" style=\"position: absolute; opacity: 0; pointer-events: none;\">\n            <div class=\"ren-card-body\">\n              <strong>Team</strong>\n              <p style=\"margin: var(--space-1) 0 0; font-size: var(--text-sm); color: var(--color-text-muted);\">$32/mo · 10 seats</p>\n            </div>\n          </label>\n        </div>\n        <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;label class=\"ren-card ren-card-selectable\"&gt;\n  &lt;input type=\"radio\" name=\"plan\" /&gt;\n  &lt;div class=\"ren-card-body\"&gt;\n    &lt;strong&gt;Pro&lt;/strong&gt;\n    &lt;p&gt;$12/mo · unlimited&lt;/p&gt;\n  &lt;/div&gt;\n&lt;/label&gt;</code></pre>\n      </div>\n\n      <h3>Article preview (interactive card with cover)</h3>\n      <p>Wrap the whole card in <code>&lt;a&gt;</code> so the entire surface is one click target.</p>\n      <div class=\"dx-demo\">\n        <div class=\"dx-demo-preview\">\n          <a href=\"#\" class=\"ren-card ren-card-interactive\" style=\"display: block; max-width: 320px; width: 100%; text-decoration: none; color: inherit;\">\n            <div style=\"aspect-ratio: 16/9; background: var(--color-accent-subtle); border-bottom: 1px solid var(--color-border);\"></div>\n            <div class=\"ren-card-header\">\n              <h4 class=\"ren-card-title\" style=\"margin: 0;\">From Tailwind to vanilla in a weekend</h4>\n              <p class=\"ren-card-description\" style=\"margin: 0;\">5 min read · Engineering</p>\n            </div>\n            <div class=\"ren-card-body\">\n              <p style=\"margin: 0;\">How we replaced a 180&nbsp;KB toolchain with a CSS-first design system and shipped faster.</p>\n            </div>\n          </a>\n        </div>\n        <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;a href=\"/post/123\" class=\"ren-card ren-card-interactive\"&gt;\n  &lt;div class=\"cover\"&gt;&lt;/div&gt;\n  &lt;div class=\"ren-card-header\"&gt;\n    &lt;h4 class=\"ren-card-title\"&gt;From Tailwind to vanilla…&lt;/h4&gt;\n    &lt;p class=\"ren-card-description\"&gt;5 min read · Engineering&lt;/p&gt;\n  &lt;/div&gt;\n  &lt;div class=\"ren-card-body\"&gt;\n    &lt;p&gt;How we replaced a 180 KB toolchain…&lt;/p&gt;\n  &lt;/div&gt;\n&lt;/a&gt;</code></pre>\n      </div>\n\n      <h3>Danger-zone card</h3>\n      <p>For destructive actions in settings panels. Status border + danger button.</p>\n      <div class=\"dx-demo\">\n        <div class=\"dx-demo-preview\">\n          <div class=\"ren-card\" data-status=\"danger\" style=\"max-width: 480px; width: 100%;\">\n            <div class=\"ren-card-header\">\n              <h4 class=\"ren-card-title\" style=\"margin: 0;\">Delete account</h4>\n              <p class=\"ren-card-description\" style=\"margin: 0;\">Permanently remove your account and all associated data.</p>\n            </div>\n            <div class=\"ren-card-footer-border\" style=\"justify-content: flex-end;\">\n              <button type=\"button\" class=\"ren-btn ren-btn-danger ren-btn-sm\">Delete account</button>\n            </div>\n          </div>\n        </div>\n        <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div class=\"ren-card\" data-status=\"danger\"&gt;\n  &lt;div class=\"ren-card-header\"&gt;\n    &lt;h4 class=\"ren-card-title\"&gt;Delete account&lt;/h4&gt;\n    &lt;p class=\"ren-card-description\"&gt;Permanently remove…&lt;/p&gt;\n  &lt;/div&gt;\n  &lt;div class=\"ren-card-footer-border\"&gt;\n    &lt;button class=\"ren-btn ren-btn-danger ren-btn-sm\"&gt;Delete account&lt;/button&gt;\n  &lt;/div&gt;\n&lt;/div&gt;</code></pre>\n      </div>\n\n      <h3>Card group (auto-grid)</h3>\n      <p>One class on the parent gives you a responsive grid of cards.</p>\n      <div class=\"dx-demo\">\n        <div class=\"dx-demo-preview\">\n          <div class=\"ren-card-group\" style=\"width: 100%;\">\n            <div class=\"ren-card\"><div class=\"ren-card-body\"><strong>Item one</strong><p style=\"margin: var(--space-1) 0 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Auto-sized</p></div></div>\n            <div class=\"ren-card\"><div class=\"ren-card-body\"><strong>Item two</strong><p style=\"margin: var(--space-1) 0 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Wraps responsively</p></div></div>\n            <div class=\"ren-card\"><div class=\"ren-card-body\"><strong>Item three</strong><p style=\"margin: var(--space-1) 0 0; font-size: var(--text-sm); color: var(--color-text-muted);\">No JS needed</p></div></div>\n          </div>\n        </div>\n        <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div class=\"ren-card-group\"&gt;\n  &lt;div class=\"ren-card\"&gt;…&lt;/div&gt;\n  &lt;div class=\"ren-card\"&gt;…&lt;/div&gt;\n  &lt;div class=\"ren-card\"&gt;…&lt;/div&gt;\n&lt;/div&gt;</code></pre>\n      </div>\n    </section>\n\n    </main>\n\n  </div>\n\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-card.html",
      "id": "docs:docs/components/ren-card.html",
      "type": "docs_page",
      "name": "ren-card docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Carousel — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\" aria-current=\"page\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Carousel</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Carousel <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">Scroll-snap gallery with prev / next buttons and dot indicators. Touch- and keyboard-friendly. Built on CSS scroll snap, no janky JS animation.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>Use Carousel for image galleries, hero slideshows, \"more like this\" rows. The scroll-snap underpinning means it feels native on touch and keyboard.</p><div class=\"dx-callout\"><p><strong>Don't auto-rotate carousels.</strong> Auto-rotation is one of the most-criticized patterns on the web. If a slide must auto-advance, give the user a clear pause / play and pause on hover and focus.</p></div></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\" style=\"display: block;\"><div style=\"display: flex; gap: var(--space-3); overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: var(--space-2);\"><div style=\"flex: 0 0 200px; scroll-snap-align: start; aspect-ratio: 4/3; background: var(--color-accent-subtle); border: 1px solid var(--color-accent); border-radius: var(--radius-md); display: grid; place-items: center; color: var(--color-accent-active); font-weight: var(--weight-semibold);\">1</div><div style=\"flex: 0 0 200px; scroll-snap-align: start; aspect-ratio: 4/3; background: var(--color-surface-sunken); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: grid; place-items: center; color: var(--color-text-muted); font-weight: var(--weight-semibold);\">2</div><div style=\"flex: 0 0 200px; scroll-snap-align: start; aspect-ratio: 4/3; background: var(--color-surface-sunken); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: grid; place-items: center; color: var(--color-text-muted); font-weight: var(--weight-semibold);\">3</div><div style=\"flex: 0 0 200px; scroll-snap-align: start; aspect-ratio: 4/3; background: var(--color-surface-sunken); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: grid; place-items: center; color: var(--color-text-muted); font-weight: var(--weight-semibold);\">4</div><div style=\"flex: 0 0 200px; scroll-snap-align: start; aspect-ratio: 4/3; background: var(--color-surface-sunken); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: grid; place-items: center; color: var(--color-text-muted); font-weight: var(--weight-semibold);\">5</div></div></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-carousel aria-label=\"Featured posts\"&gt;\n  &lt;article slot=\"slide\"&gt;Slide 1&lt;/article&gt;\n  &lt;article slot=\"slide\"&gt;Slide 2&lt;/article&gt;\n&lt;/ren-carousel&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><table class=\"dx-api dx-api-cols-4\"><thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">orientation</code></td><td><span class=\"dx-api-type\">\"horizontal\" | \"vertical\"</span></td><td><span class=\"dx-api-default\">\"horizontal\"</span></td><td>Scroll direction.</td></tr><tr><td><code class=\"dx-api-name\">loop</code></td><td><span class=\"dx-api-type\">boolean</span></td><td><span class=\"dx-api-default\">false</span></td><td>Wrap from last slide back to first.</td></tr><tr><td><code class=\"dx-api-name\">snap</code></td><td><span class=\"dx-api-type\">\"start\" | \"center\" | \"none\"</span></td><td><span class=\"dx-api-default\">\"start\"</span></td><td>CSS scroll-snap alignment.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li>Wrap in <code>role=\"region\"</code> with <code>aria-label=\"…\"</code> describing the gallery.</li><li>Arrow keys move between slides; Tab moves out of the carousel entirely.</li><li>Slides with images need real alt text — they're content, not decoration.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-carousel.html",
      "id": "docs:docs/components/ren-carousel.html",
      "type": "docs_page",
      "name": "ren-carousel docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Checkbox — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-direction: column; gap: var(--space-3); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-4); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-direction: column; gap: var(--space-3); }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n    .dx-anatomy-stage { background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-6) var(--space-5); margin: var(--space-4) 0; }\n  </style>\n</head>\n<body>\n\n  <header class=\"dx-nav\">\n    <div class=\"dx-nav-inner\">\n      <a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a>\n      <nav class=\"dx-nav-menu\" aria-label=\"Primary\">\n        <a href=\"../index.html\">Docs</a>\n        <a href=\"../components.html\" aria-current=\"page\">Components</a>\n        <a href=\"../../templates/index.html\">Templates</a>\n        <a href=\"../../create/index.html\">Theme Builder</a>\n      </nav>\n      <div class=\"dx-nav-actions\">\n        <a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a>\n        <a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\" aria-current=\"page\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\">\n          <ol>\n            <li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li>\n            <li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li>\n            <li aria-current=\"page\">Checkbox</li>\n          </ol>\n        </nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Checkbox <span class=\"dx-api-badge dx-api-badge-hybrid\" title=\"Renders without JS; JS adds enhancements\">Hybrid</span></h1>\n        <p class=\"lede\">A boolean toggle wrapped around a native <code>&lt;input type=\"checkbox\"&gt;</code>. Custom-styled control, native semantics, full keyboard and screen-reader support out of the box. Indeterminate state included.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Checkbox is a CSS-only enhancement over the native input. The actual checkbox is the hidden <code>&lt;input&gt;</code> — keyboard, screen readers, and form submission all use the platform's standard behavior. RenDS only paints the visual control on top via <code>.ren-checkbox-control</code>.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Use a Switch instead</strong> when the checkbox represents an immediate setting that takes effect right away (notifications on/off, dark mode). Use a Checkbox for things that get committed on form submit.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <label class=\"ren-checkbox\">\n              <input type=\"checkbox\">\n              <span class=\"ren-checkbox-control\"></span>\n              <span>Subscribe to the weekly digest</span>\n            </label>\n            <label class=\"ren-checkbox\">\n              <input type=\"checkbox\" checked>\n              <span class=\"ren-checkbox-control\"></span>\n              <span>Email me about product updates</span>\n            </label>\n            <label class=\"ren-checkbox\">\n              <input type=\"checkbox\" disabled>\n              <span class=\"ren-checkbox-control\"></span>\n              <span>Marketing emails (disabled — change in account settings)</span>\n            </label>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;label class=\"ren-checkbox\"&gt;\n  &lt;input type=\"checkbox\"&gt;\n  &lt;span class=\"ren-checkbox-control\"&gt;&lt;/span&gt;\n  &lt;span&gt;Subscribe to the weekly digest&lt;/span&gt;\n&lt;/label&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">States</p>\n        <h2>Variants</h2>\n        <div class=\"dx-vgrid\">\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">States</span>\n            <div class=\"dx-vrow-items\">\n              <label class=\"ren-checkbox\"><input type=\"checkbox\"><span class=\"ren-checkbox-control\"></span><span>Unchecked</span></label>\n              <label class=\"ren-checkbox\"><input type=\"checkbox\" checked><span class=\"ren-checkbox-control\"></span><span>Checked</span></label>\n              <label class=\"ren-checkbox\"><input type=\"checkbox\" id=\"indet-demo\"><span class=\"ren-checkbox-control\"></span><span>Indeterminate</span></label>\n              <label class=\"ren-checkbox\"><input type=\"checkbox\" disabled><span class=\"ren-checkbox-control\"></span><span>Disabled (unchecked)</span></label>\n              <label class=\"ren-checkbox\"><input type=\"checkbox\" checked disabled><span class=\"ren-checkbox-control\"></span><span>Disabled (checked)</span></label>\n              <script>document.getElementById('indet-demo').indeterminate = true;</script>\n            </div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Group</span>\n            <div class=\"dx-vrow-items\">\n              <fieldset style=\"border: 0; padding: 0; margin: 0;\">\n                <legend style=\"font-size: var(--label-size); font-weight: var(--label-weight); margin-bottom: var(--space-2);\">Notify me about</legend>\n                <div style=\"display: flex; flex-direction: column; gap: var(--space-2);\">\n                  <label class=\"ren-checkbox\"><input type=\"checkbox\" name=\"notify\" value=\"comments\" checked><span class=\"ren-checkbox-control\"></span><span>Comments on my posts</span></label>\n                  <label class=\"ren-checkbox\"><input type=\"checkbox\" name=\"notify\" value=\"mentions\" checked><span class=\"ren-checkbox-control\"></span><span>Mentions</span></label>\n                  <label class=\"ren-checkbox\"><input type=\"checkbox\" name=\"notify\" value=\"follows\"><span class=\"ren-checkbox-control\"></span><span>New followers</span></label>\n                </div>\n              </fieldset>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <h3>CSS classes</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-checkbox</code></td><td>The label wrapper. Inline-flex layout with the control + text. Clicking anywhere in the label toggles the input.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-checkbox-control</code></td><td>The painted box. Sits next to the hidden input and listens to <code>:checked</code> via the adjacent sibling selector.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-checkbox &gt; input[type=\"checkbox\"]</code></td><td>The native input. Visually hidden but keyboard- and screen-reader-accessible. Submits with the form like any other checkbox.</td></tr>\n          </tbody>\n        </table>\n        <h3>States handled by CSS</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>State</th><th>Selector</th></tr></thead>\n          <tbody>\n            <tr><td>Checked</td><td><code>input:checked + .ren-checkbox-control</code> (filled with accent color, white check mark)</td></tr>\n            <tr><td>Indeterminate</td><td><code>input:indeterminate + .ren-checkbox-control</code> (filled with a horizontal bar)</td></tr>\n            <tr><td>Hover</td><td><code>.ren-checkbox:hover &gt; .ren-checkbox-control</code> (border darkens)</td></tr>\n            <tr><td>Focus visible</td><td><code>input:focus-visible + .ren-checkbox-control</code> (focus ring)</td></tr>\n            <tr><td>Disabled</td><td><code>.ren-checkbox:has(input:disabled)</code> (opacity reduced, cursor not-allowed)</td></tr>\n          </tbody>\n        </table>\n        <p>No JavaScript or Web Component — every state is pure CSS.</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <p>Because the actual control is a real <code>&lt;input type=\"checkbox\"&gt;</code>, every accessibility behavior comes from the platform: Tab focus, Space to toggle, screen reader announcement of state, native form participation.</p>\n        <h3>Keyboard</h3>\n        <div class=\"dx-keys\">\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Tab</kbd></span><span>Moves focus to the input.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Space</kbd></span><span>Toggles the checked state.</span></div>\n        </div>\n        <div class=\"dx-callout\">\n          <p><strong>Always wrap the input in the label.</strong> The pattern <code>&lt;label class=\"ren-checkbox\"&gt;&lt;input&gt;…&lt;/label&gt;</code> means the click target is the entire label — not just the small box. That's a 44 px touch target without any extra work.</p>\n        </div>\n        <h3>Indeterminate state</h3>\n        <p>Set programmatically: <code>checkbox.indeterminate = true</code>. Use it for parent checkboxes that summarize a mixed group of children. Any user interaction (click or Space) clears the indeterminate flag.</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n        <h3>Select-all with indeterminate parent</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>const all = document.querySelector('#all');\nconst items = document.querySelectorAll('input[name=\"items\"]');\n\nconst sync = () =&gt; {\n  const checked = Array.from(items).filter(i =&gt; i.checked);\n  all.checked = checked.length === items.length;\n  all.indeterminate = checked.length &gt; 0 &amp;&amp; checked.length &lt; items.length;\n};\n\nall.addEventListener('change', () =&gt; {\n  items.forEach(i =&gt; i.checked = all.checked);\n});\nitems.forEach(i =&gt; i.addEventListener('change', sync));\nsync();</code></pre>\n      </section>\n\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-checkbox.html",
      "id": "docs:docs/components/ren-checkbox.html",
      "type": "docs_page",
      "name": "ren-checkbox docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Collapsible — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\" aria-current=\"page\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Collapsible</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Collapsible <span class=\"dx-api-badge dx-api-badge-hybrid\" title=\"Renders without JS; JS adds enhancements\">Hybrid</span></h1>\n        <p class=\"lede\">Single show/hide region — the simplest disclosure pattern. Built on native <code>&lt;details&gt;</code>.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>For a single section that toggles open / closed, <code>&lt;details&gt;</code> is the right primitive. RenDS just styles it. For groups of collapsibles use <a href=\"ren-accordion.html\" class=\"ren-link\">Accordion</a>.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\" style=\"flex-direction: column; align-items: stretch; width: 100%;\"><details class=\"ren-collapsible\"><summary>Show advanced settings</summary><p style=\"margin-top: var(--space-3); font-size: var(--text-sm); color: var(--color-text-secondary);\">Tweaks for power users — log level, request timeout, retry count.</p></details></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;details class=\"ren-collapsible\"&gt;\n  &lt;summary&gt;Show advanced settings&lt;/summary&gt;\n  &lt;p&gt;Hidden content here…&lt;/p&gt;\n&lt;/details&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><table class=\"dx-api\"><thead><tr><th>Class</th><th>Effect</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">.ren-collapsible</code></td><td>On <code>&lt;details&gt;</code>. Adds chevron rotation, padding, hover background.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><p>Native <code>&lt;details&gt;</code> handles all keyboard / screen reader behavior. Tab to summary, Enter / Space to toggle.</p></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-collapsible.html",
      "id": "docs:docs/components/ren-collapsible.html",
      "type": "docs_page",
      "name": "ren-collapsible docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Color Picker — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\" aria-current=\"page\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Color Picker</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Color Picker <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">Color selection with hue-saturation gradient, alpha slider, hex / RGB / HSL inputs and a swatch palette of recent colors.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>Use Color Picker in design tools, theme builders and visual editors. For form-style \"pick from a palette\" use a Toggle Group of swatches instead — full pickers are heavy.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\"><label style=\"display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm);\"><span>Brand color</span><input type=\"color\" value=\"#0063D1\" style=\"width: 44px; height: 32px; border: 1px solid var(--color-border); border-radius: var(--radius-sm); padding: 0; cursor: pointer;\"><code>#0063D1</code></label></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-color-picker name=\"brand\" value=\"#0063D1\"&gt;&lt;/ren-color-picker&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><table class=\"dx-api dx-api-cols-4\"><thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">value</code></td><td><span class=\"dx-api-type\">string (hex)</span></td><td><span class=\"dx-api-default\">\"#000000\"</span></td><td>Initial color.</td></tr><tr><td><code class=\"dx-api-name\">format</code></td><td><span class=\"dx-api-type\">\"hex\" | \"rgb\" | \"hsl\"</span></td><td><span class=\"dx-api-default\">\"hex\"</span></td><td>Output format on the <code>ren-change</code> event.</td></tr><tr><td><code class=\"dx-api-name\">alpha</code></td><td><span class=\"dx-api-type\">boolean</span></td><td><span class=\"dx-api-default\">false</span></td><td>Show alpha (transparency) slider.</td></tr><tr><td><code class=\"dx-api-name\">placement</code></td><td><span class=\"dx-api-type\">\"top\" | \"right\" | \"bottom\" | \"left\"</span></td><td><span class=\"dx-api-default\">\"bottom\"</span></td><td>Preferred side for the popover. Reflected to <code>data-side</code>.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li>Always provide a hex / RGB text input alongside the visual picker — it's the only path for users who can't see colors.</li><li>The 2D HSV gradient is keyboard-accessible: arrow keys move the cursor, Page Up / Down jumps in larger steps.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-color-picker.html",
      "id": "docs:docs/components/ren-color-picker.html",
      "type": "docs_page",
      "name": "ren-color-picker docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Combobox — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: visible; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-4); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-direction: column; gap: var(--space-3); align-items: flex-start; }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n    .dx-anatomy { margin: var(--space-4) 0; display: grid; gap: var(--space-4); }\n    .dx-anatomy-num { display: inline-grid; place-items: center; width: 22px; height: 22px; border-radius: 50%; background: var(--color-text); color: var(--color-surface); font-size: var(--text-xs); font-weight: var(--weight-bold); flex-shrink: 0; }\n    .dx-anatomy-stage { background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-6) var(--space-5); }\n    .dx-anatomy-stage-label { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); font-weight: var(--weight-semibold); margin: 0 0 var(--space-3); }\n    .dx-anatomy-parts { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-3); }\n    @media (max-width: 720px) { .dx-anatomy-parts { grid-template-columns: 1fr; } }\n    .dx-anatomy-part { background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-4); display: grid; gap: var(--space-2); }\n    .dx-anatomy-part-header { display: flex; align-items: center; gap: var(--space-2); }\n    .dx-anatomy-part-name { font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--color-text); }\n    .dx-anatomy-part-desc { font-size: var(--text-sm); line-height: 1.5; color: var(--color-text-secondary); margin: 0; }\n    .dx-anatomy-part-desc code { font-size: 0.85em; }\n\n    /* Constrain demo width */\n    .dx-demo-preview ren-combobox { max-width: 280px; }\n  </style>\n</head>\n<body>\n\n  <header class=\"dx-nav\">\n    <div class=\"dx-nav-inner\">\n      <a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a>\n      <nav class=\"dx-nav-menu\" aria-label=\"Primary\">\n        <a href=\"../index.html\">Docs</a>\n        <a href=\"../components.html\" aria-current=\"page\">Components</a>\n        <a href=\"../../templates/index.html\">Templates</a>\n        <a href=\"../../create/index.html\">Theme Builder</a>\n      </nav>\n      <div class=\"dx-nav-actions\">\n        <a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a>\n        <a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\" aria-current=\"page\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\">\n          <ol>\n            <li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li>\n            <li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li>\n            <li aria-current=\"page\">Combobox</li>\n          </ol>\n        </nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Combobox <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">A typeahead-searchable picker. Combines a text input with a filtered listbox so users can either type to filter or arrow-key through options. Use it when you have more than ~15 options and need search, but don't need full free-text input (use <code>&lt;input type=\"text\"&gt;</code> with a datalist for that).</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Combobox is a Select with a search box on top. The user can either click the trigger to see all options, or start typing to narrow them down. Each keystroke filters the visible items, arrow keys jump between them, Enter picks one. The chosen value is submitted via a hidden input named after the <code>name</code> attribute.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Combobox vs Select vs native select.</strong> Use <strong>native</strong> for short lists where the platform dropdown is fine. Use <a href=\"ren-select.html\" class=\"ren-link\">Select</a> for richer item rendering but no search. Use <strong>Combobox</strong> when there are too many items to scroll comfortably and the user knows roughly what they want.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"anatomy\">\n        <p class=\"dx-kicker\">Parts</p>\n        <h2>Anatomy</h2>\n        <div class=\"dx-anatomy\">\n          <div class=\"dx-anatomy-stage\">\n            <p class=\"dx-anatomy-stage-label\">Assembled</p>\n            <div style=\"display: flex; justify-content: center;\">\n              <div class=\"demo-combo\" data-anatomy>\n                <input class=\"demo-combo-input\" type=\"text\" placeholder=\"Type to search…\" value=\"Argentina\" readonly>\n              </div>\n            </div>\n          </div>\n          <div class=\"dx-anatomy-parts\">\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\"><span class=\"dx-anatomy-num\">1</span><span class=\"dx-anatomy-part-name\">Container</span></div>\n              <p class=\"dx-anatomy-part-desc\"><code>&lt;ren-combobox&gt;</code> wrapper. Manages selection, filtering, and the hidden form value.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\"><span class=\"dx-anatomy-num\">2</span><span class=\"dx-anatomy-part-name\">Input</span></div>\n              <p class=\"dx-anatomy-part-desc\"><code>&lt;input role=\"combobox\"&gt;</code>. Holds the search query when open, the chosen label when closed.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\"><span class=\"dx-anatomy-num\">3</span><span class=\"dx-anatomy-part-name\">Listbox</span></div>\n              <p class=\"dx-anatomy-part-desc\"><code>role=\"listbox\"</code> popover with the visible (filtered) items. Hides items that don't match the query.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\"><span class=\"dx-anatomy-num\">4</span><span class=\"dx-anatomy-part-name\">Item</span></div>\n              <p class=\"dx-anatomy-part-desc\"><code>.ren-combobox-item</code> with <code>role=\"option\"</code> and a <code>data-value</code>. Filtering matches against the visible text.</p>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <ren-combobox name=\"country\" placeholder=\"Search a country…\">\n              <div class=\"ren-combobox-item\" data-value=\"ar\">Argentina</div>\n              <div class=\"ren-combobox-item\" data-value=\"br\">Brazil</div>\n              <div class=\"ren-combobox-item\" data-value=\"cl\">Chile</div>\n              <div class=\"ren-combobox-item\" data-value=\"co\">Colombia</div>\n              <div class=\"ren-combobox-item\" data-value=\"ec\">Ecuador</div>\n              <div class=\"ren-combobox-item\" data-value=\"mx\">Mexico</div>\n              <div class=\"ren-combobox-item\" data-value=\"pe\">Peru</div>\n              <div class=\"ren-combobox-item\" data-value=\"us\">United States</div>\n              <div class=\"ren-combobox-item\" data-value=\"uy\">Uruguay</div>\n            </ren-combobox>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-combobox name=\"country\" placeholder=\"Search a country…\"&gt;\n  &lt;div class=\"ren-combobox-item\" data-value=\"ar\"&gt;Argentina&lt;/div&gt;\n  &lt;div class=\"ren-combobox-item\" data-value=\"br\"&gt;Brazil&lt;/div&gt;\n  &lt;div class=\"ren-combobox-item\" data-value=\"cl\"&gt;Chile&lt;/div&gt;\n  …\n&lt;/ren-combobox&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Shapes</p>\n        <h2>Variants</h2>\n        <div class=\"dx-vgrid\">\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">With descriptions</span>\n            <div class=\"dx-vrow-items\"><p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Items can hold structured content — a primary line and a muted secondary line. Useful for \"User name + email\" or \"Repo name + organization\" patterns.</p></div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Grouped</span>\n            <div class=\"dx-vrow-items\"><p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Wrap related items in <code>.ren-combobox-group</code> with an optional <code>.ren-combobox-group-label</code>. Filtering keeps the group label visible while any of its items match.</p></div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Async loading</span>\n            <div class=\"dx-vrow-items\"><p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Listen for <code>ren-search</code> and replace the items as the user types. Show <code>.ren-combobox-loading</code> while a request is in flight.</p></div>\n          </div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n\n        <h3>CSS classes</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-combobox</code></td><td>Wrapper. Sets up positioning context for the floating list.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-combobox-input</code></td><td>The text input. <code>role=\"combobox\"</code>, <code>aria-autocomplete=\"list\"</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-combobox-list</code></td><td>The listbox popover. Shows below the input on open.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-combobox-item</code></td><td>One option. Use with <code>role=\"option\"</code> and <code>data-value</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-combobox-group</code> / <code class=\"dx-api-name\">-group-label</code></td><td>Optional grouping for items.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-combobox-empty</code></td><td>\"No results\" message shown when the filter matches nothing.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-combobox-loading</code></td><td>Loading row for async data.</td></tr>\n          </tbody>\n        </table>\n\n        <h3>Web Component attributes</h3>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">name</code></td><td><span class=\"dx-api-type\">string</span></td><td><span class=\"dx-api-default\">—</span></td><td>Form field name. Submitted via a hidden input.</td></tr>\n            <tr><td><code class=\"dx-api-name\">value</code></td><td><span class=\"dx-api-type\">string</span></td><td><span class=\"dx-api-default\">—</span></td><td>Initial selected <code>data-value</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">placeholder</code></td><td><span class=\"dx-api-type\">string</span></td><td><span class=\"dx-api-default\">\"Search...\"</span></td><td>Empty-state text in the input.</td></tr>\n            <tr><td><code class=\"dx-api-name\">placement</code></td><td><span class=\"dx-api-type\">\"top\" | \"bottom\"</span></td><td><span class=\"dx-api-default\">\"bottom\"</span></td><td>Preferred side for the local list. Reflected to <code>data-side</code>; <code>top</code> opens above the input.</td></tr>\n            <tr><td><code class=\"dx-api-name\">disabled</code></td><td><span class=\"dx-api-type\">boolean</span></td><td><span class=\"dx-api-default\">false</span></td><td>Locks the input.</td></tr>\n            <tr><td><code class=\"dx-api-name\">async</code></td><td><span class=\"dx-api-type\">boolean</span></td><td><span class=\"dx-api-default\">false</span></td><td>If present, suppresses local filtering — the parent app provides items via <code>ren-search</code>.</td></tr>\n          </tbody>\n        </table>\n\n        <h3>Events</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Event</th><th>Detail</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">ren-change</code></td><td>Selection changed. <code>{ value, item }</code>. Bubbles, composed.</td></tr>\n            <tr><td><code class=\"dx-api-name\">ren-search</code></td><td>User typed. <code>{ query }</code>. Use this for async loading. Bubbles.</td></tr>\n            <tr><td><code class=\"dx-api-name\">ren-open</code> / <code class=\"dx-api-name\">ren-close</code></td><td>Listbox opened / closed. Bubbles, composed.</td></tr>\n          </tbody>\n        </table>\n\n        <h3>JavaScript methods</h3>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead><tr><th>Method</th><th>Returns</th><th>Args</th><th>Notes</th></tr></thead>\n          <tbody>\n            <tr>\n              <td><code class=\"dx-api-name\">.value</code></td>\n              <td><span class=\"dx-api-type\">string</span></td>\n              <td><span class=\"dx-api-default\">—</span></td>\n              <td>Getter / setter for the selected <code>data-value</code>. Setting it updates the input and dispatches <code>ren-change</code>.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.open()</code></td>\n              <td><span class=\"dx-api-type\">void</span></td>\n              <td><span class=\"dx-api-default\">—</span></td>\n              <td>Programmatically opens the listbox.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.close()</code></td>\n              <td><span class=\"dx-api-type\">void</span></td>\n              <td><span class=\"dx-api-default\">—</span></td>\n              <td>Programmatically closes the listbox.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.setLoading(state)</code></td>\n              <td><span class=\"dx-api-type\">void</span></td>\n              <td><span class=\"dx-api-type\">boolean</span></td>\n              <td>Toggles the <code>.ren-combobox-loading</code> row. Pair with <code>ren-search</code> to indicate an async fetch is in flight.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.setItems(items)</code></td>\n              <td><span class=\"dx-api-type\">void</span></td>\n              <td><span class=\"dx-api-type\">Array&lt;{value, label, description?}&gt;</span></td>\n              <td>Replaces the visible options. Use with the <code>async</code> attribute when the parent app owns filtering.</td>\n            </tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <h3>Keyboard</h3>\n        <div class=\"dx-keys\">\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>↓</kbd></span><span>Opens the list, highlights the first match.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>↑</kbd> / <kbd>↓</kbd></span><span>Navigate the visible items.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Enter</kbd></span><span>Select the highlighted item.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Esc</kbd></span><span>Closes the list. Press again to clear the input.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\">Type</span><span>Filters the items. Filtering is case-insensitive substring match by default.</span></div>\n        </div>\n        <h3>Labelling</h3>\n        <p>Wrap inside <a href=\"ren-field.html\" class=\"ren-link\">Form Field</a>, or set <code>aria-label</code> on the input. Don't use the placeholder as a label.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Always handle the empty state.</strong> When filtering yields zero items, show <code>.ren-combobox-empty</code>. Silence is worse than \"No results — try a different word\".</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n        <h3>Async loading</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>const combo = document.querySelector('ren-combobox[async]');\n\ncombo.addEventListener('ren-search', async (e) =&gt; {\n  combo.setLoading(true);\n  const results = await fetch(`/api/users?q=${encodeURIComponent(e.detail.query)}`).then(r =&gt; r.json());\n  combo.setItems(results.map(u =&gt; ({ value: u.id, label: u.name, description: u.email })));\n  combo.setLoading(false);\n});</code></pre>\n\n        <h3>Pre-selecting</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;ren-combobox name=\"country\" value=\"ar\"&gt;\n  &lt;div class=\"ren-combobox-item\" data-value=\"ar\"&gt;Argentina&lt;/div&gt;\n  &lt;div class=\"ren-combobox-item\" data-value=\"br\"&gt;Brazil&lt;/div&gt;\n&lt;/ren-combobox&gt;</code></pre>\n      </section>\n\n    </main>\n  </div>\n\n  <script type=\"module\" src=\"../../components/composites/ren-combobox/ren-combobox.js\"></script>\n\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-combobox.html",
      "id": "docs:docs/components/ren-combobox.html",
      "type": "docs_page",
      "name": "ren-combobox docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Command Palette — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\" aria-current=\"page\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Command Palette</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Command Palette <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">Searchable command launcher (⌘K). Fuzzy matching, keyboard-first, grouped by category. The fastest way for power users to do anything in the app.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>Command palettes turn your app into a keyboard-first surface. Bind <kbd>⌘</kbd>+<kbd>K</kbd> globally and let users type to find any action — navigation, settings, recently-used items.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\"><button class=\"ren-btn ren-btn-secondary\" type=\"button\">Press <kbd>⌘</kbd>+<kbd>K</kbd> to open</button></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-command id=\"cmd\"&gt;\n  &lt;input slot=\"input\" placeholder=\"Type a command…\"&gt;\n  &lt;ren-command-group label=\"Recent\"&gt;\n    &lt;ren-command-item value=\"open-project\"&gt;Open project…&lt;/ren-command-item&gt;\n  &lt;/ren-command-group&gt;\n  &lt;ren-command-group label=\"Settings\"&gt;\n    &lt;ren-command-item value=\"theme\"&gt;Toggle theme&lt;/ren-command-item&gt;\n  &lt;/ren-command-group&gt;\n&lt;/ren-command&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><table class=\"dx-api\"><thead><tr><th>Class</th><th>Effect</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">.ren-command</code></td><td>The dialog wrapper. Opens centered, dimmed backdrop.</td></tr><tr><td><code class=\"dx-api-name\">.ren-command-input</code></td><td>The search input.</td></tr><tr><td><code class=\"dx-api-name\">.ren-command-list</code></td><td>The filtered results.</td></tr><tr><td><code class=\"dx-api-name\">.ren-command-group</code> / <code class=\"dx-api-name\">-group-label</code></td><td>Optional grouping of commands.</td></tr><tr><td><code class=\"dx-api-name\">.ren-command-item</code></td><td>One command. <code>data-value</code> identifies it; <code>data-keywords</code> adds extra matching terms.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li>Implements the WAI-ARIA Combobox + Listbox pattern.</li><li>Up / Down navigate, Enter runs the command, Esc closes.</li><li>Always include keyboard fallbacks for users who can't reach the ⌘K shortcut — every command should also be reachable from a menu.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-command.html",
      "id": "docs:docs/components/ren-command.html",
      "type": "docs_page",
      "name": "ren-command docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Context Menu — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-8) var(--space-5); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .demo-target { padding: var(--space-8) var(--space-5); background: var(--color-surface); border: 2px dashed var(--color-border); border-radius: var(--radius-md); text-align: center; color: var(--color-text-muted); font-size: var(--text-sm); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\" aria-current=\"page\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Context Menu</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Context Menu <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">A right-click menu anchored to the cursor position. Same engine as <a href=\"ren-menu.html\" class=\"ren-link\">Menu</a> — actions list with icons, shortcuts, separators, danger styling — but triggered by <code>contextmenu</code> events instead of a button.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Context menus give power users a faster path to common actions on an element they've already located. Use them in editors, file lists, design canvases — anywhere the user thinks \"I want to do something to <em>this thing</em>.\"</p>\n        <div class=\"dx-callout\">\n          <p><strong>Always provide a non-context-menu path.</strong> Touch users have no right-click. Every action in a context menu must also be reachable via a button, keyboard shortcut, or regular menu.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <p>Right-click the area below to open the context menu (touch: long-press).</p>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\" style=\"position: relative;\">\n            <div class=\"demo-target\" id=\"demo-cm-target\" data-context=\"demo-cm-menu\" tabindex=\"0\">Right-click anywhere in this box</div>\n            <div class=\"ren-context-menu\" id=\"demo-cm-menu\" popover=\"manual\">\n              <button role=\"menuitem\" class=\"ren-menu-item\">Cut <span class=\"ren-menu-item-shortcut\">⌘X</span></button>\n              <button role=\"menuitem\" class=\"ren-menu-item\">Copy <span class=\"ren-menu-item-shortcut\">⌘C</span></button>\n              <button role=\"menuitem\" class=\"ren-menu-item\">Paste <span class=\"ren-menu-item-shortcut\">⌘V</span></button>\n              <hr class=\"ren-menu-separator\">\n              <button role=\"menuitem\" class=\"ren-menu-item ren-menu-item-danger\">Delete</button>\n            </div>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div id=\"editor-canvas\" data-context=\"editor-menu\" tabindex=\"0\"&gt;\n  Right-click this area\n&lt;/div&gt;\n\n&lt;div id=\"editor-menu\" class=\"ren-context-menu\" popover=\"manual\"&gt;\n  &lt;button class=\"ren-menu-item\"&gt;Cut&lt;span class=\"ren-menu-item-shortcut\"&gt;⌘X&lt;/span&gt;&lt;/button&gt;\n  &lt;button class=\"ren-menu-item\"&gt;Copy&lt;span class=\"ren-menu-item-shortcut\"&gt;⌘C&lt;/span&gt;&lt;/button&gt;\n  &lt;button class=\"ren-menu-item\"&gt;Paste&lt;span class=\"ren-menu-item-shortcut\"&gt;⌘V&lt;/span&gt;&lt;/button&gt;\n  &lt;hr class=\"ren-menu-separator\"&gt;\n  &lt;button class=\"ren-menu-item ren-menu-item-danger\"&gt;Delete&lt;/button&gt;\n&lt;/div&gt;\n\n&lt;script type=\"module\"&gt;\n  import { initContextMenu } from './ren-context-menu.js';\n  initContextMenu('editor-menu');\n&lt;/script&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <p>Visual classes are inherited from <a href=\"ren-menu.html\" class=\"ren-link\">Menu</a> — <code>.ren-menu</code>, <code>.ren-menu-item</code>, <code>.ren-menu-separator</code>, <code>.ren-menu-item-icon</code>, <code>.ren-menu-item-shortcut</code>, <code>.ren-menu-item-danger</code>.</p>\n        <h3>Initializer contract</h3>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">data-context</code></td><td><span class=\"dx-api-type\">string (menu id)</span></td><td><span class=\"dx-api-default\">required</span></td><td>Add to each element that should trigger the context menu.</td></tr>\n            <tr><td><code class=\"dx-api-name\">id</code></td><td><span class=\"dx-api-type\">string</span></td><td><span class=\"dx-api-default\">required</span></td><td>The popover element id passed to <code>initContextMenu(id)</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">popover</code></td><td><span class=\"dx-api-type\">manual</span></td><td><span class=\"dx-api-default\">manual</span></td><td>Uses manual popover mode so the menu stays open after the right-click gesture completes.</td></tr>\n          </tbody>\n        </table>\n        <p>For full event and item documentation see the <a href=\"ren-menu.html#api\" class=\"ren-link\">Menu API</a>.</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li>Trigger via <code>contextmenu</code> event — works for right-click, two-finger tap on Mac trackpads, and the Menu key on keyboards.</li>\n          <li>Keyboard fallback: <kbd>Shift</kbd>+<kbd>F10</kbd> opens a context menu on the focused element.</li>\n          <li>The actions inside use the same Menu component, so arrow-key navigation, Enter to activate, and Escape to close all work.</li>\n          <li>Don't put exclusive functionality in a context menu — touch users won't find it.</li>\n        </ul>\n      </section>\n\n    </main>\n  </div>\n  <script type=\"module\">\n    import { initContextMenu } from '../../components/composites/ren-context-menu/ren-context-menu.js';\n\n    initContextMenu('demo-cm-menu');\n  </script>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-context-menu.html",
      "id": "docs:docs/components/ren-context-menu.html",
      "type": "docs_page",
      "name": "ren-context-menu docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Date Picker — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\" aria-current=\"page\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Date Picker</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Date Picker <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">Input + calendar popover. Type the date manually or pick from the calendar — both stay in sync. Format-aware, with min / max constraints and disabled-date support.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>The bread-and-butter date control: a text input the user can type into, plus a calendar that opens on focus or click. Combines <a href=\"ren-field.html\" class=\"ren-link\">Form Field</a> with <a href=\"ren-calendar.html\" class=\"ren-link\">Calendar</a>.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\"><label style=\"display: flex; flex-direction: column; gap: 4px; font-size: var(--text-sm);\"><span>Birthday</span><input type=\"date\" class=\"ren-input\" value=\"1990-04-12\" style=\"width: 200px;\"></label></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-date-picker name=\"birthday\" value=\"1990-04-12\"&gt;\n  &lt;label slot=\"label\"&gt;Birthday&lt;/label&gt;\n&lt;/ren-date-picker&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><p>Inherits all <a href=\"ren-calendar.html#api\" class=\"ren-link\">Calendar attributes</a>. Plus:</p><table class=\"dx-api dx-api-cols-4\"><thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">format</code></td><td><span class=\"dx-api-type\">string</span></td><td><span class=\"dx-api-default\">locale-default</span></td><td>Display format (\"MM/DD/YYYY\", \"DD-MM-YYYY\"…). Submitted value is always ISO 8601.</td></tr><tr><td><code class=\"dx-api-name\">placeholder</code></td><td><span class=\"dx-api-type\">string</span></td><td><span class=\"dx-api-default\">\"YYYY-MM-DD\"</span></td><td>Input placeholder.</td></tr><tr><td><code class=\"dx-api-name\">placement</code></td><td><span class=\"dx-api-type\">\"top\" | \"right\" | \"bottom\" | \"left\"</span></td><td><span class=\"dx-api-default\">\"bottom\"</span></td><td>Preferred side for the calendar popover. Reflected to <code>data-side</code>.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li>The text input accepts free typing — assistive tech users don't need to navigate the calendar grid.</li><li>The calendar trigger button has <code>aria-label=\"Open calendar\"</code> and <code>aria-haspopup=\"dialog\"</code>.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-date-picker.html",
      "id": "docs:docs/components/ren-date-picker.html",
      "type": "docs_page",
      "name": "ren-date-picker docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Date Range Picker — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\" aria-current=\"page\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Date Range Picker</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Date Range Picker <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">Two-handle date range with presets (last 7 days, this month, last quarter…). For analytics filters, booking flows, report scopes.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>For \"from–to\" date ranges. Renders two inputs (start / end) plus an optional preset list (\"Last 7 days\", \"Last 30 days\", \"This year\"). The calendar lets users pick both ends visually.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\"><div style=\"display: flex; gap: var(--space-2); align-items: center;\"><input type=\"date\" class=\"ren-input\" value=\"2026-04-01\" style=\"width: 160px;\"><span>→</span><input type=\"date\" class=\"ren-input\" value=\"2026-04-30\" style=\"width: 160px;\"></div></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-date-range-picker name=\"period\"\n                       start=\"2026-04-01\" end=\"2026-04-30\"\n                       presets=\"7d,30d,90d,year\"&gt;\n&lt;/ren-date-range-picker&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><table class=\"dx-api dx-api-cols-4\"><thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">start</code> / <code class=\"dx-api-name\">end</code></td><td><span class=\"dx-api-type\">\"YYYY-MM-DD\"</span></td><td><span class=\"dx-api-default\">—</span></td><td>Range bounds.</td></tr><tr><td><code class=\"dx-api-name\">max-range</code></td><td><span class=\"dx-api-type\">number (days)</span></td><td><span class=\"dx-api-default\">—</span></td><td>Limit the maximum span the user can pick.</td></tr><tr><td><code class=\"dx-api-name\">presets</code></td><td><span class=\"dx-api-type\">string list</span></td><td><span class=\"dx-api-default\">—</span></td><td>Comma-separated preset keys: <code>7d, 30d, 90d, year, ytd, last-quarter</code>.</td></tr><tr><td><code class=\"dx-api-name\">placement</code></td><td><span class=\"dx-api-type\">\"top\" | \"right\" | \"bottom\" | \"left\"</span></td><td><span class=\"dx-api-default\">\"bottom\"</span></td><td>Preferred side for the range popover. Reflected to <code>data-side</code>.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li>Inside the calendar, the two ends of the range have distinct ARIA labels (\"Start of range\", \"End of range\").</li><li>Days that would fall outside the allowed range get <code>aria-disabled=\"true\"</code>.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-date-range-picker.html",
      "id": "docs:docs/components/ren-date-range-picker.html",
      "type": "docs_page",
      "name": "ren-date-range-picker docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Dialog — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    /* Page-specific styles — chrome and tables live in shell.css */\n\n    /* Demo block: preview frame + code block, stacked */\n    .dx-demo {\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n      overflow: hidden;\n      margin: var(--space-4) 0;\n    }\n    .dx-demo-preview {\n      background: var(--color-surface-raised);\n      padding: var(--space-8) var(--space-5);\n      display: flex;\n      flex-wrap: wrap;\n      gap: var(--space-3);\n      align-items: center;\n      justify-content: center;\n      min-height: 96px;\n    }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n\n    /* Variant rows — labeled groups of demos */\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label {\n      font-size: var(--text-xs);\n      font-weight: var(--weight-semibold);\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--color-text-muted);\n      padding-top: var(--space-3);\n    }\n    .dx-vrow-items {\n      display: flex;\n      flex-wrap: wrap;\n      gap: var(--space-3);\n      align-items: center;\n      padding: var(--space-3);\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n    }\n    @media (max-width: 720px) {\n      .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); }\n      .dx-vrow-label { padding-top: 0; }\n    }\n\n    /* Anatomy: assembled dialog on top, parts breakdown below */\n    .dx-anatomy {\n      margin: var(--space-4) 0;\n      display: grid;\n      gap: var(--space-4);\n    }\n    .dx-anatomy-num {\n      display: inline-grid;\n      place-items: center;\n      width: 22px;\n      height: 22px;\n      border-radius: 50%;\n      background: var(--color-text);\n      color: var(--color-surface);\n      font-size: var(--text-xs);\n      font-weight: var(--weight-bold);\n      flex-shrink: 0;\n    }\n    .dx-anatomy-stage {\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n      padding: var(--space-6) var(--space-5);\n    }\n    .dx-anatomy-stage-label {\n      font-size: var(--text-xs);\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--color-text-muted);\n      font-weight: var(--weight-semibold);\n      margin: 0 0 var(--space-3);\n    }\n    .dx-anatomy-parts {\n      display: grid;\n      grid-template-columns: repeat(2, 1fr);\n      gap: var(--space-3);\n    }\n    @media (max-width: 720px) { .dx-anatomy-parts { grid-template-columns: 1fr; } }\n    .dx-anatomy-part {\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n      padding: var(--space-4);\n      display: grid;\n      gap: var(--space-2);\n    }\n    .dx-anatomy-part-header {\n      display: flex;\n      align-items: center;\n      gap: var(--space-2);\n    }\n    .dx-anatomy-part-name {\n      font-size: var(--text-sm);\n      font-weight: var(--weight-semibold);\n      color: var(--color-text);\n    }\n    .dx-anatomy-part-desc {\n      font-size: var(--text-sm);\n      line-height: 1.5;\n      color: var(--color-text-secondary);\n      margin: 0;\n    }\n    .dx-anatomy-part-desc code { font-size: 0.85em; }\n\n    /* Mocked dialog illustration — a static, non-interactive preview of the\n       dialog frame so users can visualize the parts without opening the modal.\n       Intentionally light: no internal dividers, transparent close button,\n       header/body/footer separated only by spacing. */\n    .dx-dialog-mock {\n      width: 100%;\n      max-width: 380px;\n      margin-inline: auto;\n      background: var(--color-surface);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-xl);\n      box-shadow: var(--shadow-xl, 0 12px 48px rgba(0,0,0,0.18));\n      padding: var(--space-5) var(--space-5) var(--space-4);\n    }\n    .dx-dialog-mock-header {\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      gap: var(--space-3);\n    }\n    .dx-dialog-mock-title {\n      font-size: var(--text-base);\n      font-weight: var(--weight-semibold);\n      margin: 0;\n      line-height: 1.2;\n    }\n    .dx-dialog-mock-close {\n      width: 24px;\n      height: 24px;\n      display: grid;\n      place-items: center;\n      background: transparent;\n      border: 0;\n      padding: 0;\n      color: var(--color-text-muted);\n      cursor: pointer;\n      line-height: 1;\n      font-size: 18px;\n    }\n    .dx-dialog-mock-close:hover { color: var(--color-text); }\n    .dx-dialog-mock-body {\n      margin-block: var(--space-3) var(--space-4);\n      font-size: var(--text-sm);\n      color: var(--color-text-secondary);\n      line-height: 1.6;\n    }\n    .dx-dialog-mock-footer {\n      display: flex;\n      justify-content: flex-end;\n      gap: var(--space-2);\n    }\n  </style>\n</head>\n<body>\n\n  <!-- Top bar (shell.css) -->\n  <header class=\"dx-nav\">\n    <div class=\"dx-nav-inner\">\n      <a href=\"../index.html\" class=\"dx-brand\">\n        <span class=\"dx-brand-mark\">R</span>\n        <span>RenDS</span>\n        <span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span>\n      </a>\n      <nav class=\"dx-nav-menu\" aria-label=\"Primary\">\n        <a href=\"../index.html\">Docs</a>\n        <a href=\"../components.html\" aria-current=\"page\">Components</a>\n        <a href=\"../../templates/index.html\">Templates</a>\n        <a href=\"../../create/index.html\">Theme Builder</a>\n      </nav>\n      <div class=\"dx-nav-actions\">\n        <a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a>\n        <a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a>\n      </div>\n    </div>\n  </header>\n\n  <!-- Sidebar + content shell -->\n  <div class=\"dx-shell dx-shell-grid\">\n\n    <!-- Persistent sidebar — same nav as the catalog so context is never lost -->\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\" aria-current=\"page\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <!-- Content -->\n    <main class=\"dx-content\">\n\n      <!-- Page header -->\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\">\n          <ol>\n            <li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li>\n            <li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li>\n            <li aria-current=\"page\">Dialog</li>\n          </ol>\n        </nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Dialog <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">A modal overlay built on the native <code>&lt;dialog&gt;</code> element. Focus trap, keyboard handling, click-outside dismiss, and smooth entry / exit animations come for free. Available as a CSS class set <em>or</em> as the <code>&lt;ren-dialog&gt;</code> Web Component with auto-wired triggers.</p>\n      </header>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           1. OVERVIEW\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>A dialog interrupts the user's current flow to confirm an action, gather a small piece of input, or surface a focused decision. RenDS uses the browser's native <code>&lt;dialog&gt;</code> element under the hood, so a lot of the hard parts — modal scroll lock, top-layer rendering, focus trapping, escape-to-close — come from the platform. The component layers smooth animations, mobile sheet behavior, and declarative auto-wiring on top.</p>\n\n        <p>Use a dialog when the user needs to make a deliberate choice before the rest of the UI continues. If the action is destructive or irreversible, use the <strong>alert</strong> variant so the dialog can't be dismissed by clicking the backdrop.</p>\n\n        <div class=\"dx-callout\">\n          <p><strong>Don't reach for a dialog by default.</strong> If the decision can live on the page (an inline form, a popover, an expandable panel), prefer those. A modal blocks everything else and shouldn't be the answer to \"where do I put this content?\".</p>\n        </div>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           2. ANATOMY\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"anatomy\">\n        <p class=\"dx-kicker\">Parts</p>\n        <h2>Anatomy</h2>\n        <p>A dialog is composed of five regions. Three are required (container, body, footer with at least one action), two are conventional (header with title and close button, backdrop).</p>\n\n        <div class=\"dx-anatomy\">\n          <!-- Assembled reference -->\n          <div class=\"dx-anatomy-stage\">\n            <p class=\"dx-anatomy-stage-label\">Assembled</p>\n            <div class=\"dx-dialog-mock\" aria-hidden=\"true\">\n              <div class=\"dx-dialog-mock-header\">\n                <h4 class=\"dx-dialog-mock-title\">Confirm changes</h4>\n                <button class=\"dx-dialog-mock-close\" type=\"button\" tabindex=\"-1\">×</button>\n              </div>\n              <div class=\"dx-dialog-mock-body\">Save the edits to your profile? You can revert any time from settings.</div>\n              <div class=\"dx-dialog-mock-footer\">\n                <button type=\"button\" class=\"ren-btn ren-btn-secondary ren-btn-sm\" tabindex=\"-1\">Cancel</button>\n                <button type=\"button\" class=\"ren-btn ren-btn-primary ren-btn-sm\" tabindex=\"-1\">Save</button>\n              </div>\n            </div>\n          </div>\n\n          <!-- Parts -->\n          <div class=\"dx-anatomy-parts\">\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\">\n                <span class=\"dx-anatomy-num\">1</span>\n                <span class=\"dx-anatomy-part-name\">Container</span>\n              </div>\n              <p class=\"dx-anatomy-part-desc\">The dialog box itself — <code>&lt;dialog class=\"ren-dialog\"&gt;</code>. Sized by a width variant token (<code>--dialog-max-width</code>). Renders in the browser's top layer.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\">\n                <span class=\"dx-anatomy-num\">2</span>\n                <span class=\"dx-anatomy-part-name\">Backdrop</span>\n              </div>\n              <p class=\"dx-anatomy-part-desc\">The dimmed scrim. Styled via <code>::backdrop</code>. Click to dismiss (unless <code>alert</code> is set). Fades in with the dialog.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\">\n                <span class=\"dx-anatomy-num\">3</span>\n                <span class=\"dx-anatomy-part-name\">Header</span>\n              </div>\n              <p class=\"dx-anatomy-part-desc\"><code>.ren-dialog-header</code> with <code>.ren-dialog-title</code> and an optional close button (<code>data-dialog-close</code>). Bottom-bordered, padded.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\">\n                <span class=\"dx-anatomy-num\">4</span>\n                <span class=\"dx-anatomy-part-name\">Body</span>\n              </div>\n              <p class=\"dx-anatomy-part-desc\"><code>.ren-dialog-body</code>. Scrolls when content exceeds the dialog's max-height. Independent scrollbar so the page beneath stays locked.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\">\n                <span class=\"dx-anatomy-num\">5</span>\n                <span class=\"dx-anatomy-part-name\">Footer</span>\n              </div>\n              <p class=\"dx-anatomy-part-desc\"><code>.ren-dialog-footer</code> with action buttons. Conventionally right-aligned, primary action last. Top-bordered.</p>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           3. DEMO\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <p>Click any trigger to open the dialog. <kbd>Esc</kbd> closes it; clicking outside the dialog closes it (except for alert dialogs). Tab cycles through the focusable elements without escaping.</p>\n\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <button type=\"button\" class=\"ren-btn ren-btn-primary\" data-dialog-trigger=\"demo-confirm\">Open dialog</button>\n            <button type=\"button\" class=\"ren-btn ren-btn-danger\" data-dialog-trigger=\"demo-alert\">Open alert dialog</button>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;button data-dialog-trigger=\"dlg-confirm\"&gt;Open dialog&lt;/button&gt;\n\n&lt;ren-dialog id=\"dlg-confirm\"&gt;\n  &lt;dialog class=\"ren-dialog\"&gt;\n    &lt;div class=\"ren-dialog-header\"&gt;\n      &lt;h2 class=\"ren-dialog-title\"&gt;Confirm changes&lt;/h2&gt;\n      &lt;button class=\"ren-dialog-close\" data-dialog-close aria-label=\"Close\"&gt;×&lt;/button&gt;\n    &lt;/div&gt;\n    &lt;div class=\"ren-dialog-body\"&gt;\n      Save the edits to your profile?\n    &lt;/div&gt;\n    &lt;div class=\"ren-dialog-footer\"&gt;\n      &lt;button class=\"ren-btn ren-btn-secondary\" data-dialog-close&gt;Cancel&lt;/button&gt;\n      &lt;button class=\"ren-btn ren-btn-primary\"&gt;Save&lt;/button&gt;\n    &lt;/div&gt;\n  &lt;/dialog&gt;\n&lt;/ren-dialog&gt;</code></pre>\n        </div>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           4. VARIANTS\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Shapes</p>\n        <h2>Variants</h2>\n        <p>Five sizes plus an alert mode. Default is <code>md</code>. Pick the smallest size that fits the content — bigger isn't friendlier.</p>\n\n        <div class=\"dx-vgrid\">\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Sizes</span>\n            <div class=\"dx-vrow-items\">\n              <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-dialog-trigger=\"demo-size-sm\">sm</button>\n              <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-dialog-trigger=\"demo-size-md\">md (default)</button>\n              <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-dialog-trigger=\"demo-size-lg\">lg</button>\n              <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-dialog-trigger=\"demo-size-xl\">xl</button>\n              <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-dialog-trigger=\"demo-size-full\">full</button>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Alert</span>\n            <div class=\"dx-vrow-items\">\n              <button type=\"button\" class=\"ren-btn ren-btn-danger\" data-dialog-trigger=\"demo-alert\">Delete account</button>\n              <span style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">No backdrop dismiss · forces a button choice</span>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">No Escape</span>\n            <div class=\"dx-vrow-items\">\n              <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-dialog-trigger=\"demo-no-escape\">Open critical flow</button>\n              <span style=\"font-size: var(--text-sm); color: var(--color-text-muted);\"><kbd>Esc</kbd> is suppressed · use the in-dialog buttons</span>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           5. API\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n\n        <h3>CSS classes</h3>\n        <p>Apply directly to the <code>&lt;dialog&gt;</code> element. They work even if you don't use the Web Component (you'd just script open / close yourself).</p>\n        <table class=\"dx-api\">\n          <thead>\n            <tr>\n              <th>Class</th>\n              <th>Effect</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-dialog</code></td>\n              <td>Base. Required on the <code>&lt;dialog&gt;</code> element. Provides background, border-radius, shadow, animations, and the backdrop styling.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-dialog-header</code></td>\n              <td>Top region. Flex row with a bottom border. Holds the title and close button.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-dialog-title</code></td>\n              <td>Heading inside the header. Set on the actual heading tag (<code>&lt;h2&gt;</code>, <code>&lt;h3&gt;</code>) so screen readers announce it correctly.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-dialog-description</code></td>\n              <td>Optional sub-title under the title. Muted color.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-dialog-body</code></td>\n              <td>Main content region. Scrolls independently when its content exceeds <code>max-height: 85dvh</code>.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-dialog-footer</code></td>\n              <td>Bottom region for action buttons. Top-bordered, right-aligned by default.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-dialog-close</code></td>\n              <td>The icon close button placed in the header. Square, ghost-styled, focus ring matches the system token.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-dialog-sm</code> / <code class=\"dx-api-name\">-md</code> / <code class=\"dx-api-name\">-lg</code> / <code class=\"dx-api-name\">-xl</code> / <code class=\"dx-api-name\">-full</code></td>\n              <td>Size modifiers. Set <code>--dialog-max-width</code>: 24rem · 32rem · 42rem · 56rem · 95dvw.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-alert-dialog</code></td>\n              <td>Alert flavor. Suppresses click-outside dismissal. Used by <code>&lt;ren-alert-dialog&gt;</code> internally.</td>\n            </tr>\n          </tbody>\n        </table>\n\n        <h3>Web Component attributes</h3>\n        <p>Set on the <code>&lt;ren-dialog&gt;</code> wrapper. All are reactive — flipping them in JS opens, closes, or resizes the dialog without rebuilding the markup.</p>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead>\n            <tr>\n              <th>Attribute</th>\n              <th>Type</th>\n              <th>Default</th>\n              <th>Notes</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td><code class=\"dx-api-name\">open</code></td>\n              <td><span class=\"dx-api-type\">boolean</span></td>\n              <td><span class=\"dx-api-default\">false</span></td>\n              <td>Opens the dialog when present. Mirrors <code>&lt;dialog open&gt;</code> but routes through the component lifecycle (events, focus trap).</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">size</code></td>\n              <td><span class=\"dx-api-type\">\"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\"</span></td>\n              <td><span class=\"dx-api-default\">\"md\"</span></td>\n              <td>Width variant. Adds the matching <code>.ren-dialog-*</code> class to the inner dialog.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">alert</code></td>\n              <td><span class=\"dx-api-type\">boolean</span></td>\n              <td><span class=\"dx-api-default\">false</span></td>\n              <td>Disables backdrop click dismissal. The user must press a button to close.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">no-escape</code></td>\n              <td><span class=\"dx-api-type\">boolean</span></td>\n              <td><span class=\"dx-api-default\">false</span></td>\n              <td>Suppresses <kbd>Esc</kbd> closing the dialog. Use sparingly — it traps the user.</td>\n            </tr>\n          </tbody>\n        </table>\n\n        <h3>JavaScript methods</h3>\n        <p>Available on every <code>&lt;ren-dialog&gt;</code> instance. All return Promises so you can <code>await</code> open / close transitions before continuing.</p>\n        <table class=\"dx-api\">\n          <thead>\n            <tr>\n              <th>Method</th>\n              <th>Description</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td><code class=\"dx-api-name\">show()</code> / <code class=\"dx-api-name\">open()</code></td>\n              <td>Opens the dialog. Returns a Promise that resolves once the entry animation finishes. Aliases.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">close(returnValue?)</code></td>\n              <td>Closes the dialog. Optional <code>returnValue</code> is forwarded to the <code>ren-close</code> event detail. Promise resolves after the exit animation.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">isOpen</code></td>\n              <td>Boolean getter. <code>true</code> while the dialog is visible.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">dialog</code></td>\n              <td>Getter that returns the underlying native <code>&lt;dialog&gt;</code> element if you need to call its DOM API directly.</td>\n            </tr>\n          </tbody>\n        </table>\n\n        <div class=\"dx-pre\" tabindex=\"0\"><code>const dialog = document.querySelector('ren-dialog');\n\nawait dialog.show();              // wait for it to be visible\nconst result = await dialog.close('confirmed');  // returnValue surfaces in ren-close\n\nif (dialog.isOpen) { /* still open */ }</code></div>\n\n        <h3>Events</h3>\n        <p>Both events bubble and are composed. Listen on the wrapper or any ancestor.</p>\n        <table class=\"dx-api\">\n          <thead>\n            <tr>\n              <th>Event</th>\n              <th>Detail</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td><code class=\"dx-api-name\">ren-open</code></td>\n              <td>Fired after the entry animation completes. <code>event.detail</code> is empty.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">ren-close</code></td>\n              <td>Fired after the exit animation completes. <code>event.detail.returnValue</code> contains the value passed to <code>close()</code>, if any.</td>\n            </tr>\n          </tbody>\n        </table>\n\n        <h3>Auto-wiring</h3>\n        <p>You don't need to write JavaScript for the common case. Two declarative attributes wire opening and closing:</p>\n        <table class=\"dx-api\">\n          <thead>\n            <tr>\n              <th>Attribute</th>\n              <th>Effect</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td><code class=\"dx-api-name\">data-dialog-trigger=\"ID\"</code></td>\n              <td>On any element. Click opens the <code>&lt;ren-dialog&gt;</code> with the matching <code>id</code>.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">data-dialog-close</code></td>\n              <td>On any element inside the dialog. Click closes the parent dialog.</td>\n            </tr>\n          </tbody>\n        </table>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           6. ACCESSIBILITY\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <p>Because the component renders an actual <code>&lt;dialog&gt;</code> element, the platform handles most of the work. RenDS adds the focus management, motion preferences, and labelling conventions on top.</p>\n\n        <h3>Keyboard</h3>\n        <div class=\"dx-keys\">\n          <div class=\"dx-keyrow\">\n            <span class=\"keys\"><kbd>Tab</kbd></span>\n            <span>Moves focus to the next focusable element inside the dialog. Cycles back to the first when it reaches the end.</span>\n          </div>\n          <div class=\"dx-keyrow\">\n            <span class=\"keys\"><kbd>Shift</kbd> + <kbd>Tab</kbd></span>\n            <span>Moves focus backward, cycling at the start of the dialog.</span>\n          </div>\n          <div class=\"dx-keyrow\">\n            <span class=\"keys\"><kbd>Esc</kbd></span>\n            <span>Closes the dialog. Suppressed when <code>no-escape</code> is set.</span>\n          </div>\n          <div class=\"dx-keyrow\">\n            <span class=\"keys\"><kbd>Enter</kbd></span>\n            <span>Activates the focused button or submits the focused form, as the platform default. Not intercepted.</span>\n          </div>\n        </div>\n\n        <h3>Focus management</h3>\n        <ul>\n          <li>On open, focus moves to the first focusable element inside the dialog. To override, mark a specific element with <code>autofocus</code>.</li>\n          <li>While open, focus is trapped inside the dialog — Tab and Shift+Tab cycle without leaving.</li>\n          <li>On close, focus returns to the element that opened the dialog (the trigger button). The user never has to find their place again.</li>\n        </ul>\n\n        <h3>Labelling</h3>\n        <p>Always pair the dialog with an accessible name. The simplest path is to give the title an <code>id</code> and reference it via <code>aria-labelledby</code>:</p>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;ren-dialog id=\"dlg-labelling\" aria-labelledby=\"dlg-labelling-title\"&gt;\n  &lt;dialog class=\"ren-dialog\"&gt;\n    &lt;div class=\"ren-dialog-header\"&gt;\n      &lt;h2 class=\"ren-dialog-title\" id=\"dlg-labelling-title\"&gt;Delete file&lt;/h2&gt;\n    &lt;/div&gt;\n    ...\n  &lt;/dialog&gt;\n&lt;/ren-dialog&gt;</code></div>\n        <p>If you also have descriptive prose under the title, link it with <code>aria-describedby</code>.</p>\n\n        <h3>Motion</h3>\n        <p>Entry and exit animations use the <code>--duration-enter</code> motion token. Under <code>prefers-reduced-motion: reduce</code> the transitions are disabled — the dialog appears and disappears instantly.</p>\n\n        <div class=\"dx-callout\">\n          <p><strong>Use the alert variant for destructive confirmations.</strong> A backdrop click on a regular dialog dismisses it silently — fine for most cases, dangerous when the user is about to delete something. The alert variant forces an explicit button choice.</p>\n        </div>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           7. EXAMPLES\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n\n        <h3>Confirmation</h3>\n        <p>The classic: a dialog asks the user to confirm a non-trivial action.</p>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;button data-dialog-trigger=\"dlg-save\"&gt;Save&lt;/button&gt;\n\n&lt;ren-dialog id=\"dlg-save\" aria-labelledby=\"save-title\"&gt;\n  &lt;dialog class=\"ren-dialog\"&gt;\n    &lt;div class=\"ren-dialog-header\"&gt;\n      &lt;h2 class=\"ren-dialog-title\" id=\"save-title\"&gt;Save changes?&lt;/h2&gt;\n    &lt;/div&gt;\n    &lt;div class=\"ren-dialog-body\"&gt;\n      Your edits will be visible to the team immediately.\n    &lt;/div&gt;\n    &lt;div class=\"ren-dialog-footer\"&gt;\n      &lt;button class=\"ren-btn ren-btn-secondary\" data-dialog-close&gt;Cancel&lt;/button&gt;\n      &lt;button class=\"ren-btn ren-btn-primary\"&gt;Save&lt;/button&gt;\n    &lt;/div&gt;\n  &lt;/dialog&gt;\n&lt;/ren-dialog&gt;</code></div>\n\n        <h3>Destructive (alert)</h3>\n        <p>Backdrop click is suppressed; the user must press one of the two buttons.</p>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;button class=\"ren-btn ren-btn-danger\" data-dialog-trigger=\"dlg-delete\"&gt;Delete account&lt;/button&gt;\n\n&lt;ren-alert-dialog id=\"dlg-delete\" aria-labelledby=\"delete-title\"&gt;\n  &lt;dialog class=\"ren-dialog ren-alert-dialog\"&gt;\n    &lt;div class=\"ren-dialog-header\"&gt;\n      &lt;h2 class=\"ren-dialog-title\" id=\"delete-title\"&gt;Delete this account?&lt;/h2&gt;\n    &lt;/div&gt;\n    &lt;div class=\"ren-dialog-body\"&gt;\n      All projects, files, and history will be permanently removed. This cannot be undone.\n    &lt;/div&gt;\n    &lt;div class=\"ren-dialog-footer\"&gt;\n      &lt;button class=\"ren-btn ren-btn-secondary\" data-dialog-close&gt;Keep account&lt;/button&gt;\n      &lt;button class=\"ren-btn ren-btn-danger\"&gt;Delete account&lt;/button&gt;\n    &lt;/div&gt;\n  &lt;/dialog&gt;\n&lt;/ren-alert-dialog&gt;</code></div>\n\n        <h3>Form dialog</h3>\n        <p>Using a dialog to collect a small piece of input. Submit the form to close.</p>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;button data-dialog-trigger=\"dlg-rename\"&gt;Rename project&lt;/button&gt;\n\n&lt;ren-dialog id=\"dlg-rename\" aria-labelledby=\"dlg-rename-title\"&gt;\n  &lt;dialog class=\"ren-dialog ren-dialog-sm\"&gt;\n    &lt;form method=\"dialog\"&gt;\n      &lt;div class=\"ren-dialog-header\"&gt;\n        &lt;h2 class=\"ren-dialog-title\" id=\"dlg-rename-title\"&gt;Rename project&lt;/h2&gt;\n      &lt;/div&gt;\n      &lt;div class=\"ren-dialog-body\"&gt;\n        &lt;label for=\"project-name\" class=\"ren-field-label\"&gt;New name&lt;/label&gt;\n        &lt;input id=\"project-name\" name=\"project-name\" class=\"ren-input\" autofocus required&gt;\n      &lt;/div&gt;\n      &lt;div class=\"ren-dialog-footer\"&gt;\n        &lt;button class=\"ren-btn ren-btn-secondary\" type=\"button\" data-dialog-close&gt;Cancel&lt;/button&gt;\n        &lt;button class=\"ren-btn ren-btn-primary\" type=\"submit\"&gt;Rename&lt;/button&gt;\n      &lt;/div&gt;\n    &lt;/form&gt;\n  &lt;/dialog&gt;\n&lt;/ren-dialog&gt;</code></div>\n\n        <h3>Reading the result</h3>\n        <p>When you need to know <em>which</em> button closed the dialog, listen to <code>ren-close</code> and pass a <code>returnValue</code>.</p>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>const dialog = document.querySelector('#dlg-save');\n\ndialog.querySelector('.ren-btn-primary')\n  .addEventListener('click', () =&gt; dialog.close('save'));\n\ndialog.querySelector('.ren-btn-secondary')\n  .addEventListener('click', () =&gt; dialog.close('cancel'));\n\ndialog.addEventListener('ren-close', (e) =&gt; {\n  if (e.detail.returnValue === 'save') {\n    persist();\n  }\n});</code></div>\n      </section>\n\n    </main>\n\n  </div>\n\n  <!-- ═══════════════════════════════════════════════════════════\n       Live demo dialogs — kept at the end of the body so they sit\n       outside the dx-shell-grid (top-layer rendering still works\n       no matter where in the DOM the dialog lives).\n       ═══════════════════════════════════════════════════════════ -->\n\n  <ren-dialog id=\"demo-confirm\" aria-labelledby=\"demo-confirm-title\">\n    <dialog class=\"ren-dialog\">\n      <div class=\"ren-dialog-header\">\n        <h2 class=\"ren-dialog-title\" id=\"demo-confirm-title\">Confirm changes</h2>\n        <button class=\"ren-dialog-close\" data-dialog-close aria-label=\"Close\">×</button>\n      </div>\n      <div class=\"ren-dialog-body\">\n        Save the edits to your profile? You can revert any time from settings.\n      </div>\n      <div class=\"ren-dialog-footer\">\n        <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-dialog-close>Cancel</button>\n        <button type=\"button\" class=\"ren-btn ren-btn-primary\" data-dialog-close>Save</button>\n      </div>\n    </dialog>\n  </ren-dialog>\n\n  <ren-alert-dialog id=\"demo-alert\" aria-labelledby=\"demo-alert-title\">\n    <dialog class=\"ren-dialog ren-alert-dialog\">\n      <div class=\"ren-dialog-header\">\n        <h2 class=\"ren-dialog-title\" id=\"demo-alert-title\">Delete this account?</h2>\n      </div>\n      <div class=\"ren-dialog-body\">\n        All projects, files, and history will be permanently removed. This cannot be undone.\n      </div>\n      <div class=\"ren-dialog-footer\">\n        <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-dialog-close>Keep account</button>\n        <button type=\"button\" class=\"ren-btn ren-btn-danger\" data-dialog-close>Delete account</button>\n      </div>\n    </dialog>\n  </ren-alert-dialog>\n\n  <ren-dialog id=\"demo-size-sm\" size=\"sm\" aria-labelledby=\"demo-size-sm-title\">\n    <dialog class=\"ren-dialog ren-dialog-sm\">\n      <div class=\"ren-dialog-header\">\n        <h2 class=\"ren-dialog-title\" id=\"demo-size-sm-title\">Small dialog</h2>\n        <button class=\"ren-dialog-close\" data-dialog-close aria-label=\"Close\">×</button>\n      </div>\n      <div class=\"ren-dialog-body\">24 rem (384 px) max-width. For tight, single-decision dialogs.</div>\n      <div class=\"ren-dialog-footer\">\n        <button type=\"button\" class=\"ren-btn ren-btn-primary\" data-dialog-close>OK</button>\n      </div>\n    </dialog>\n  </ren-dialog>\n\n  <ren-dialog id=\"demo-size-md\" size=\"md\" aria-labelledby=\"demo-size-md-title\">\n    <dialog class=\"ren-dialog ren-dialog-md\">\n      <div class=\"ren-dialog-header\">\n        <h2 class=\"ren-dialog-title\" id=\"demo-size-md-title\">Medium dialog (default)</h2>\n        <button class=\"ren-dialog-close\" data-dialog-close aria-label=\"Close\">×</button>\n      </div>\n      <div class=\"ren-dialog-body\">32 rem (512 px) max-width. The everyday default — fits a paragraph of context plus a couple of buttons.</div>\n      <div class=\"ren-dialog-footer\">\n        <button type=\"button\" class=\"ren-btn ren-btn-primary\" data-dialog-close>OK</button>\n      </div>\n    </dialog>\n  </ren-dialog>\n\n  <ren-dialog id=\"demo-size-lg\" size=\"lg\" aria-labelledby=\"demo-size-lg-title\">\n    <dialog class=\"ren-dialog ren-dialog-lg\">\n      <div class=\"ren-dialog-header\">\n        <h2 class=\"ren-dialog-title\" id=\"demo-size-lg-title\">Large dialog</h2>\n        <button class=\"ren-dialog-close\" data-dialog-close aria-label=\"Close\">×</button>\n      </div>\n      <div class=\"ren-dialog-body\">42 rem (672 px) max-width. Use for forms with multiple fields or list-pickers.</div>\n      <div class=\"ren-dialog-footer\">\n        <button type=\"button\" class=\"ren-btn ren-btn-primary\" data-dialog-close>OK</button>\n      </div>\n    </dialog>\n  </ren-dialog>\n\n  <ren-dialog id=\"demo-size-xl\" size=\"xl\" aria-labelledby=\"demo-size-xl-title\">\n    <dialog class=\"ren-dialog ren-dialog-xl\">\n      <div class=\"ren-dialog-header\">\n        <h2 class=\"ren-dialog-title\" id=\"demo-size-xl-title\">Extra-large dialog</h2>\n        <button class=\"ren-dialog-close\" data-dialog-close aria-label=\"Close\">×</button>\n      </div>\n      <div class=\"ren-dialog-body\">56 rem (896 px) max-width. For media browsers, multi-step wizards, or rich previews.</div>\n      <div class=\"ren-dialog-footer\">\n        <button type=\"button\" class=\"ren-btn ren-btn-primary\" data-dialog-close>OK</button>\n      </div>\n    </dialog>\n  </ren-dialog>\n\n  <ren-dialog id=\"demo-size-full\" size=\"full\" aria-labelledby=\"demo-size-full-title\">\n    <dialog class=\"ren-dialog ren-dialog-full\">\n      <div class=\"ren-dialog-header\">\n        <h2 class=\"ren-dialog-title\" id=\"demo-size-full-title\">Full-bleed dialog</h2>\n        <button class=\"ren-dialog-close\" data-dialog-close aria-label=\"Close\">×</button>\n      </div>\n      <div class=\"ren-dialog-body\">95 dvw — almost edge-to-edge. Reserve for full-screen takeovers (image lightbox, focus modes).</div>\n      <div class=\"ren-dialog-footer\">\n        <button type=\"button\" class=\"ren-btn ren-btn-primary\" data-dialog-close>OK</button>\n      </div>\n    </dialog>\n  </ren-dialog>\n\n  <ren-dialog id=\"demo-no-escape\" no-escape aria-labelledby=\"demo-no-escape-title\">\n    <dialog class=\"ren-dialog\">\n      <div class=\"ren-dialog-header\">\n        <h2 class=\"ren-dialog-title\" id=\"demo-no-escape-title\">Critical step</h2>\n      </div>\n      <div class=\"ren-dialog-body\">\n        <kbd>Esc</kbd> won't close this. Use one of the buttons below to leave. (You'd reserve this for moments where dismissing accidentally would lose work — e.g. mid-payment-flow.)\n      </div>\n      <div class=\"ren-dialog-footer\">\n        <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-dialog-close>Cancel</button>\n        <button type=\"button\" class=\"ren-btn ren-btn-primary\" data-dialog-close>Continue</button>\n      </div>\n    </dialog>\n  </ren-dialog>\n\n  <!-- Web Component registration (focus trap, animations, auto-wiring) -->\n  <script type=\"module\" src=\"../../components/composites/ren-dialog/ren-dialog.js\"></script>\n\n  <!-- Demo wiring — vanilla, uses the native <dialog> API so triggers fire\n       even if the Web Component never registers. -->\n  <script>\n    // Open: any [data-dialog-trigger=\"ID\"] opens the matching dialog\n    document.querySelectorAll('[data-dialog-trigger]').forEach(trigger => {\n      const targetId = trigger.getAttribute('data-dialog-trigger');\n      trigger.addEventListener('click', (e) => {\n        e.preventDefault();\n        const wrapper = document.getElementById(targetId);\n        if (!wrapper) return;\n        const dialog = wrapper.tagName === 'DIALOG' ? wrapper : wrapper.querySelector('dialog');\n        if (dialog && typeof dialog.showModal === 'function' && !dialog.open) {\n          dialog.showModal();\n        }\n      });\n    });\n\n    // Close: any [data-dialog-close] inside a <dialog> closes it\n    document.querySelectorAll('[data-dialog-close]').forEach(btn => {\n      btn.addEventListener('click', (e) => {\n        e.preventDefault();\n        const dialog = btn.closest('dialog');\n        if (dialog && typeof dialog.close === 'function') {\n          dialog.close();\n        }\n      });\n    });\n\n    // Backdrop click closes non-alert dialogs (skip <ren-alert-dialog> wrappers)\n    document.querySelectorAll('dialog').forEach(dialog => {\n      const isAlert = dialog.closest('ren-alert-dialog') || dialog.classList.contains('ren-alert-dialog');\n      if (isAlert) return;\n      dialog.addEventListener('click', (e) => {\n        // Click on the backdrop registers as a click on the <dialog> element itself\n        if (e.target === dialog) dialog.close();\n      });\n    });\n  </script>\n\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-dialog.html",
      "id": "docs:docs/components/ren-dialog.html",
      "type": "docs_page",
      "name": "ren-dialog docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Dropzone — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .demo-dz { border: 2px dashed var(--color-border); border-radius: var(--radius-md); padding: var(--space-8) var(--space-5); text-align: center; background: var(--color-surface); cursor: pointer; transition: var(--transition-state); }\n    .demo-dz:hover, .demo-dz[data-drag] { border-color: var(--color-accent); background: var(--color-accent-subtle); }\n    .demo-dz-icon { font-size: var(--text-3xl); color: var(--color-text-muted); margin-bottom: var(--space-2); }\n    .demo-dz-title { font-size: var(--text-base); font-weight: var(--weight-semibold); margin: 0 0 var(--space-1); }\n    .demo-dz-hint { font-size: var(--text-sm); color: var(--color-text-muted); margin: 0; }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\" aria-current=\"page\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Dropzone</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Dropzone <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">A file-drop area with drag-over feedback, click-to-browse fallback, and multi-file support. Wraps a hidden <code>&lt;input type=\"file\"&gt;</code> so all the platform niceties — file picker, accept filtering, multiple — come along.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Dropzone gives users two paths to upload: drag a file in, or click and browse. Both end at the same hidden <code>&lt;input type=\"file\"&gt;</code>. The component adds visual drag-over state, file type / size validation hooks, and a built-in error display. Use it for avatars, document uploads, image galleries — anywhere \"drop a file\" feels right.</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <label class=\"demo-dz\" for=\"demo-dz-input\">\n              <input type=\"file\" id=\"demo-dz-input\" hidden>\n              <div class=\"demo-dz-icon\">↑</div>\n              <p class=\"demo-dz-title\">Drop a file or click to browse</p>\n              <p class=\"demo-dz-hint\">PDF, PNG, JPG up to 10 MB</p>\n            </label>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-dropzone accept=\"image/*\" max-size=\"10485760\"&gt;\n  &lt;input type=\"file\" hidden&gt;\n  &lt;div class=\"ren-dropzone-icon\"&gt;↑&lt;/div&gt;\n  &lt;p class=\"ren-dropzone-title\"&gt;Drop a file or click to browse&lt;/p&gt;\n  &lt;p class=\"ren-dropzone-hint\"&gt;PDF, PNG, JPG up to 10 MB&lt;/p&gt;\n&lt;/ren-dropzone&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-dropzone</code></td><td>The drop region. Dashed border, hover / drag-over / accepted / rejected states all via CSS.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-dropzone-icon</code></td><td>Optional leading icon (or up-arrow glyph).</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-dropzone-title</code></td><td>Primary message — \"Drop a file or click to browse\".</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-dropzone-hint</code></td><td>Secondary hint — accepted formats, size limit.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-dropzone-list</code></td><td>Accepted files render here once dropped / picked.</td></tr>\n          </tbody>\n        </table>\n        <h3>Web Component attributes</h3>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">accept</code></td><td><span class=\"dx-api-type\">string</span></td><td><span class=\"dx-api-default\">\"*\"</span></td><td>MIME pattern, mirrors <code>&lt;input accept&gt;</code>. e.g. <code>image/*</code>, <code>.pdf,.docx</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">multiple</code></td><td><span class=\"dx-api-type\">boolean</span></td><td><span class=\"dx-api-default\">false</span></td><td>Allow multiple files per drop.</td></tr>\n            <tr><td><code class=\"dx-api-name\">max-size</code></td><td><span class=\"dx-api-type\">number (bytes)</span></td><td><span class=\"dx-api-default\">—</span></td><td>Reject files larger than this. Component shows an error message.</td></tr>\n          </tbody>\n        </table>\n        <h3>Events</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Event</th><th>Detail</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">ren-files</code></td><td>Files were accepted. <code>{ files: File[] }</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">ren-reject</code></td><td>Files rejected (wrong type, too big). <code>{ files: File[], reason: string }</code>.</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li>The dropzone is a real <code>&lt;label&gt;</code> wrapping the hidden file input — clicking anywhere opens the file picker, no JS needed.</li>\n          <li>Keyboard users can <kbd>Tab</kbd> to the input and press <kbd>Enter</kbd> / <kbd>Space</kbd> to open the file picker.</li>\n          <li>Drag-over is visual only; without it, the component is still fully usable.</li>\n          <li>Show clear feedback on rejection — wrong type, too big — and announce it with <code>role=\"alert\"</code>.</li>\n        </ul>\n      </section>\n\n    </main>\n  </div>\n  <script type=\"module\" src=\"../../components/composites/ren-dropzone/ren-dropzone.js\"></script>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-dropzone.html",
      "id": "docs:docs/components/ren-dropzone.html",
      "type": "docs_page",
      "name": "ren-dropzone docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Empty State — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .demo-empty { text-align: center; padding: var(--space-8) var(--space-5); }\n    .demo-empty-icon { color: var(--color-text-muted); margin-bottom: var(--space-3); display: inline-flex; }\n    .demo-empty-icon svg { width: 40px; height: 40px; }\n    .demo-empty-title { font-size: var(--text-lg); font-weight: var(--weight-semibold); margin: 0 0 var(--space-1); }\n    .demo-empty-desc { font-size: var(--text-sm); color: var(--color-text-muted); margin: 0 0 var(--space-4); max-width: 38ch; margin-inline: auto; line-height: 1.55; }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\" aria-current=\"page\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Empty State</li></ol></nav>\n        <p class=\"dx-kicker\">Pattern</p>\n        <h1>Empty State <span class=\"dx-api-badge dx-api-badge-css\" title=\"Works without any JavaScript\">CSS-only</span></h1>\n        <p class=\"lede\">\"Nothing here yet\" panels — the first impression a user has of a feature before they've created data. Icon, headline, supporting copy, and one clear next action. Get them right and the empty page becomes onboarding.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Use an empty state when a list, table, or section has no content yet. The structure is rigid for a reason — it makes empty pages feel intentional instead of broken: a small illustration, a one-line headline (\"No invoices yet\"), one short paragraph of context, and a single primary action to get started.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Empty states are onboarding in disguise.</strong> First-time users land here. Phrase the copy as encouragement, not as an apology — \"Create your first project\" beats \"Sorry, you have no projects\".</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <div class=\"ren-empty-state demo-empty\">\n              <div class=\"demo-empty-icon\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M16.5 9.4 7.55 4.24\"/><path d=\"M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z\"/><path d=\"m3.3 7 8.7 5 8.7-5\"/><path d=\"M12 22V12\"/></svg></div>\n              <h2 class=\"demo-empty-title\">No projects yet</h2>\n              <p class=\"demo-empty-desc\">Projects are how you organize your work. Create one to start inviting collaborators and tracking progress.</p>\n              <button class=\"ren-btn ren-btn-primary\">Create your first project</button>\n            </div>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div class=\"ren-empty-state\"&gt;\n  &lt;div class=\"ren-empty-state-icon\"&gt;\n    &lt;!-- SVG illustration --&gt;\n  &lt;/div&gt;\n  &lt;h2 class=\"ren-empty-state-title\"&gt;No projects yet&lt;/h2&gt;\n  &lt;p class=\"ren-empty-state-desc\"&gt;\n    Projects are how you organize your work. Create one to start\n    inviting collaborators and tracking progress.\n  &lt;/p&gt;\n  &lt;button class=\"ren-btn ren-btn-primary\"&gt;Create your first project&lt;/button&gt;\n&lt;/div&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-empty-state</code></td><td>The container. Center-aligned, generous vertical padding.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-empty-state-icon</code></td><td>The illustration slot. Sized to ~64–80 px square.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-empty-state-title</code></td><td>Headline. Use a real heading tag (<code>h2</code> or <code>h3</code>) at the right document level.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-empty-state-desc</code></td><td>Supporting copy — one or two lines max. Capped to ~38ch for readability.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-empty-state-actions</code></td><td>Optional cluster for one or two action buttons. Right-align primary action when there are two.</td></tr>\n          </tbody>\n        </table>\n        <h3>Variants</h3>\n        <ul>\n          <li><strong>First run</strong> — the default. \"Get started\" tone.</li>\n          <li><strong>Search returned nothing</strong> — different copy. \"No results for 'foo'. Try a different keyword.\"</li>\n          <li><strong>Filtered out</strong> — \"All your items are in another tab. Clear filters to see them.\"</li>\n          <li><strong>Permission</strong> — \"You don't have access. Ask an admin to invite you.\"</li>\n        </ul>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li>The illustration is decorative — wrap it in <code>aria-hidden=\"true\"</code> so screen readers skip it.</li>\n          <li>The headline is a real <code>&lt;h2&gt;</code> or <code>&lt;h3&gt;</code> at the appropriate document level. Don't use a styled <code>&lt;p&gt;</code>.</li>\n          <li>The action button is reachable by Tab. If there's just one action, make it the page's primary CTA.</li>\n          <li>Don't replace the empty state with a permanent loading spinner — show a skeleton during load, then the empty state when the response arrives.</li>\n        </ul>\n        <div class=\"dx-callout\">\n          <p><strong>Empty states deserve copy review.</strong> The wording is the entire pattern — bad copy makes a beautifully designed empty state feel hollow. Have someone read it cold and confirm they know what to do next.</p>\n        </div>\n      </section>\n\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-empty-state.html",
      "id": "docs:docs/components/ren-empty-state.html",
      "type": "docs_page",
      "name": "ren-empty-state docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Field — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    /* Page-specific styles — chrome and tables live in shell.css */\n\n    /* Demo block: preview frame + code block, stacked */\n    .dx-demo {\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n      overflow: hidden;\n      margin: var(--space-4) 0;\n    }\n    .dx-demo-preview {\n      background: var(--color-surface-raised);\n      padding: var(--space-6) var(--space-5);\n    }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n\n    /* Variant rows */\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label {\n      font-size: var(--text-xs);\n      font-weight: var(--weight-semibold);\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--color-text-muted);\n      padding-top: var(--space-3);\n    }\n    .dx-vrow-items {\n      padding: var(--space-4);\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n      display: flex;\n      flex-direction: column;\n      gap: var(--space-3);\n    }\n    @media (max-width: 720px) {\n      .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); }\n      .dx-vrow-label { padding-top: 0; }\n    }\n\n    /* Anatomy */\n    .dx-anatomy { margin: var(--space-4) 0; display: grid; gap: var(--space-4); }\n    .dx-anatomy-num {\n      display: inline-grid; place-items: center;\n      width: 22px; height: 22px;\n      border-radius: 50%;\n      background: var(--color-text); color: var(--color-surface);\n      font-size: var(--text-xs); font-weight: var(--weight-bold);\n      flex-shrink: 0;\n    }\n    .dx-anatomy-stage {\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n      padding: var(--space-6) var(--space-5);\n    }\n    .dx-anatomy-stage-label {\n      font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.06em;\n      color: var(--color-text-muted); font-weight: var(--weight-semibold);\n      margin: 0 0 var(--space-3);\n    }\n    .dx-anatomy-parts {\n      display: grid;\n      grid-template-columns: repeat(2, 1fr);\n      gap: var(--space-3);\n    }\n    @media (max-width: 720px) { .dx-anatomy-parts { grid-template-columns: 1fr; } }\n    .dx-anatomy-part {\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n      padding: var(--space-4);\n      display: grid;\n      gap: var(--space-2);\n    }\n    .dx-anatomy-part-header { display: flex; align-items: center; gap: var(--space-2); }\n    .dx-anatomy-part-name { font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--color-text); }\n    .dx-anatomy-part-desc { font-size: var(--text-sm); line-height: 1.5; color: var(--color-text-secondary); margin: 0; }\n    .dx-anatomy-part-desc code { font-size: 0.85em; }\n\n    /* Custom element fallback display */\n    ren-field { display: block; }\n\n    /* Form row helper for the examples section */\n    .demo-form-row {\n      display: grid;\n      grid-template-columns: 1fr 1fr;\n      gap: var(--space-3);\n    }\n    @media (max-width: 540px) { .demo-form-row { grid-template-columns: 1fr; } }\n  </style>\n</head>\n<body>\n\n  <!-- Top bar (shell.css) -->\n  <header class=\"dx-nav\">\n    <div class=\"dx-nav-inner\">\n      <a href=\"../index.html\" class=\"dx-brand\">\n        <span class=\"dx-brand-mark\">R</span>\n        <span>RenDS</span>\n        <span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span>\n      </a>\n      <nav class=\"dx-nav-menu\" aria-label=\"Primary\">\n        <a href=\"../index.html\">Docs</a>\n        <a href=\"../components.html\" aria-current=\"page\">Components</a>\n        <a href=\"../../templates/index.html\">Templates</a>\n        <a href=\"../../create/index.html\">Theme Builder</a>\n      </nav>\n      <div class=\"dx-nav-actions\">\n        <a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a>\n        <a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a>\n      </div>\n    </div>\n  </header>\n\n  <!-- Sidebar + content shell -->\n  <div class=\"dx-shell dx-shell-grid\">\n\n    <!-- Persistent sidebar -->\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\" aria-current=\"page\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <!-- Content -->\n    <main class=\"dx-content\">\n\n      <!-- Page header -->\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\">\n          <ol>\n            <li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li>\n            <li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li>\n            <li aria-current=\"page\">Field</li>\n          </ol>\n        </nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Field <span class=\"dx-api-badge dx-api-badge-hybrid\" title=\"Renders without JS; JS adds enhancements\">Hybrid</span></h1>\n        <p class=\"lede\">The smallest accessible unit of a form: label, input, helper text, error message — wired together. Use the CSS classes directly, or wrap your markup in <code>&lt;ren-field&gt;</code> and let the component generate IDs and ARIA attributes for you.</p>\n      </header>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           1. OVERVIEW\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>A form field is the building block of every form in your app. RenDS gives you two ways to compose it: write the classes manually if you like full control, or wrap semantic HTML in <code>&lt;ren-field&gt;</code> and the component will fill in the parts that humans regularly forget — generated <code>id</code>s, the <code>for</code> attribute on the label, <code>aria-describedby</code> for the helper text, <code>aria-errormessage</code> for validation messages, and <code>data-required</code> on the label when the input is required.</p>\n\n        <p>The visual styling lives entirely in CSS. The Web Component is a thin layer of accessibility wiring on top — your form still works without it, just with fewer ARIA hooks.</p>\n\n        <div class=\"dx-callout\">\n          <p><strong>Use one field per question.</strong> A field bundles a label and one input. Don't reuse the wrapper to group several inputs — for that, use a <code>fieldset</code> with a <code>legend</code> instead.</p>\n        </div>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           2. ANATOMY\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"anatomy\">\n        <p class=\"dx-kicker\">Parts</p>\n        <h2>Anatomy</h2>\n        <p>A complete field has four content parts plus the container. Description and error are optional.</p>\n\n        <div class=\"dx-anatomy\">\n          <div class=\"dx-anatomy-stage\">\n            <p class=\"dx-anatomy-stage-label\">Assembled</p>\n            <div class=\"ren-field\" style=\"max-width: 360px; margin-inline: auto;\">\n              <label class=\"ren-field-label\" data-required for=\"anatomy-email\">Work email</label>\n              <input class=\"ren-input\" type=\"email\" id=\"anatomy-email\" placeholder=\"name@example.com\" aria-describedby=\"anatomy-email-desc\">\n              <span class=\"ren-field-description\" id=\"anatomy-email-desc\">We'll send the verification link here.</span>\n            </div>\n          </div>\n\n          <div class=\"dx-anatomy-parts\">\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\">\n                <span class=\"dx-anatomy-num\">1</span>\n                <span class=\"dx-anatomy-part-name\">Container</span>\n              </div>\n              <p class=\"dx-anatomy-part-desc\"><code>.ren-field</code> wrapper. Stacks the parts vertically with a small gap. Use <code>&lt;ren-field&gt;</code> if you want auto-wiring.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\">\n                <span class=\"dx-anatomy-num\">2</span>\n                <span class=\"dx-anatomy-part-name\">Label</span>\n              </div>\n              <p class=\"dx-anatomy-part-desc\"><code>&lt;label class=\"ren-field-label\"&gt;</code>. Always paired to its input via <code>for</code>. Add <code>data-required</code> for the asterisk.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\">\n                <span class=\"dx-anatomy-num\">3</span>\n                <span class=\"dx-anatomy-part-name\">Control</span>\n              </div>\n              <p class=\"dx-anatomy-part-desc\"><code>&lt;input class=\"ren-input\"&gt;</code> — or <code>&lt;textarea&gt;</code> / <code>&lt;select&gt;</code> with the same class. The control owns its native validation.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\">\n                <span class=\"dx-anatomy-num\">4</span>\n                <span class=\"dx-anatomy-part-name\">Description</span>\n              </div>\n              <p class=\"dx-anatomy-part-desc\"><code>.ren-field-description</code>. Helper text under the input. Linked to the input via <code>aria-describedby</code>.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\">\n                <span class=\"dx-anatomy-num\">5</span>\n                <span class=\"dx-anatomy-part-name\">Error</span>\n              </div>\n              <p class=\"dx-anatomy-part-desc\"><code>.ren-field-error</code> with <code>role=\"alert\"</code>. Hidden until the field is invalid. Replaces / supplements the description on error.</p>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           3. DEMO\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <p>Type something invalid and the error appears. Click the field to focus and the focus ring follows the accent color.</p>\n\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <div class=\"ren-field\" style=\"max-width: 380px; margin-inline: auto;\">\n              <label class=\"ren-field-label\" for=\"demo-email\">Email address</label>\n              <input class=\"ren-input\" type=\"email\" id=\"demo-email\" placeholder=\"name@example.com\" aria-describedby=\"demo-email-desc\">\n              <span class=\"ren-field-description\" id=\"demo-email-desc\">We'll never share your email with anyone.</span>\n            </div>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-field&gt;\n  &lt;label&gt;Email address&lt;/label&gt;\n  &lt;input type=\"email\" placeholder=\"name@example.com\"&gt;\n  &lt;span data-description&gt;We'll never share your email with anyone.&lt;/span&gt;\n&lt;/ren-field&gt;</code></pre>\n        </div>\n\n        <p>Same field written without the Web Component — explicit classes, manual <code>for</code> and <code>aria-describedby</code> wiring:</p>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;div class=\"ren-field\"&gt;\n  &lt;label class=\"ren-field-label\" for=\"email\"&gt;Email address&lt;/label&gt;\n  &lt;input class=\"ren-input\" type=\"email\" id=\"email\"\n         placeholder=\"name@example.com\"\n         aria-describedby=\"email-desc\"&gt;\n  &lt;span class=\"ren-field-description\" id=\"email-desc\"&gt;\n    We'll never share your email with anyone.\n  &lt;/span&gt;\n&lt;/div&gt;</code></pre>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           4. VARIANTS\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Shapes</p>\n        <h2>Variants</h2>\n        <p>The same field structure adapts to different control types and visual densities.</p>\n\n        <div class=\"dx-vgrid\">\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Sizes</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-field\">\n                <label class=\"ren-field-label\" for=\"size-sm\">Small</label>\n                <input class=\"ren-input ren-input-sm\" id=\"size-sm\" placeholder=\"ren-input-sm\">\n              </div>\n              <div class=\"ren-field\">\n                <label class=\"ren-field-label\" for=\"size-md\">Medium (default)</label>\n                <input class=\"ren-input\" id=\"size-md\" placeholder=\"ren-input\">\n              </div>\n              <div class=\"ren-field\">\n                <label class=\"ren-field-label\" for=\"size-lg\">Large</label>\n                <input class=\"ren-input ren-input-lg\" id=\"size-lg\" placeholder=\"ren-input-lg\">\n              </div>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">States</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-field\">\n                <label class=\"ren-field-label\" for=\"state-default\">Default</label>\n                <input class=\"ren-input\" id=\"state-default\" placeholder=\"Type here\">\n              </div>\n              <div class=\"ren-field\">\n                <label class=\"ren-field-label\" for=\"state-disabled\">Disabled</label>\n                <input class=\"ren-input\" id=\"state-disabled\" placeholder=\"Disabled\" disabled>\n              </div>\n              <div class=\"ren-field\" data-invalid>\n                <label class=\"ren-field-label\" for=\"state-invalid\">Invalid</label>\n                <input class=\"ren-input\" id=\"state-invalid\" value=\"not-an-email\" aria-invalid=\"true\" aria-errormessage=\"state-invalid-err\">\n                <span class=\"ren-field-error\" role=\"alert\" id=\"state-invalid-err\">Please enter a valid email address.</span>\n              </div>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Required</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-field\">\n                <label class=\"ren-field-label\" data-required for=\"req-name\">Full name</label>\n                <input class=\"ren-input\" id=\"req-name\" required placeholder=\"Required field\">\n                <span class=\"ren-field-description\">Marked with an asterisk via <code>data-required</code> on the label.</span>\n              </div>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">With icon</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-field\">\n                <label class=\"ren-field-label\" for=\"icon-search\">Search</label>\n                <div class=\"ren-input-wrapper\">\n                  <span class=\"ren-input-icon\" aria-hidden=\"true\">\n                    <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"8\"></circle><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line></svg>\n                  </span>\n                  <input class=\"ren-input\" type=\"search\" id=\"icon-search\" placeholder=\"Search projects…\">\n                </div>\n              </div>\n              <div class=\"ren-field\">\n                <label class=\"ren-field-label\" for=\"icon-money\">Amount</label>\n                <div class=\"ren-input-wrapper\">\n                  <input class=\"ren-input\" type=\"number\" id=\"icon-money\" placeholder=\"0.00\">\n                  <span class=\"ren-input-icon ren-input-icon-end\" aria-hidden=\"true\" style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">USD</span>\n                </div>\n              </div>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Textarea</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-field\">\n                <label class=\"ren-field-label\" for=\"multi-msg\">Message</label>\n                <textarea class=\"ren-input\" id=\"multi-msg\" rows=\"4\" placeholder=\"Tell us what's on your mind…\"></textarea>\n                <span class=\"ren-field-description\">Same <code>.ren-input</code> class works on <code>&lt;textarea&gt;</code>.</span>\n              </div>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Select</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-field\">\n                <label class=\"ren-field-label\" for=\"multi-country\">Country</label>\n                <select class=\"ren-input\" id=\"multi-country\">\n                  <option>Argentina</option>\n                  <option>Chile</option>\n                  <option>Mexico</option>\n                  <option>United States</option>\n                </select>\n                <span class=\"ren-field-description\">Native <code>&lt;select&gt;</code> with the same class. For a custom-styled dropdown, use <code>ren-select</code>.</span>\n              </div>\n            </div>\n          </div>\n\n        </div>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           5. API\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n\n        <h3>CSS classes</h3>\n        <p>Apply directly if you don't use the Web Component. The visual styling is identical either way.</p>\n        <table class=\"dx-api\">\n          <thead>\n            <tr>\n              <th>Class</th>\n              <th>Effect</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-field</code></td>\n              <td>Container. Vertical flex stack with a small gap between label, control, and description / error.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-field-label</code></td>\n              <td>Label styling. Use on the actual <code>&lt;label&gt;</code> tag so the click-to-focus behavior works natively.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-field-label[data-required]</code></td>\n              <td>Adds a red asterisk after the label text via <code>::after</code>. Set automatically when the input has <code>required</code>.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-input</code></td>\n              <td>Base input style. Works on <code>&lt;input&gt;</code>, <code>&lt;textarea&gt;</code>, and <code>&lt;select&gt;</code>. 44 px touch target, focus ring, hover, disabled.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-input-sm</code> / <code class=\"dx-api-name\">.ren-input-lg</code></td>\n              <td>Size modifiers. Default (no modifier) is medium (44 px). Small is denser; large is more prominent.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-input-wrapper</code></td>\n              <td>Use to nest an icon (or any decoration) alongside the input. Acts as a relative container.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-input-icon</code> / <code class=\"dx-api-name\">.ren-input-icon-end</code></td>\n              <td>Place an icon before (default) or after (<code>-end</code>) the input. The input is automatically padded so the icon doesn't overlap text.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-field-description</code></td>\n              <td>Helper text. Muted color, smaller font. Should be linked to the input via <code>aria-describedby</code>.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-field-error</code></td>\n              <td>Error message. Danger color. Hidden by default; shown when the field has <code>data-invalid</code>.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-field[data-invalid]</code></td>\n              <td>Invalid container state. Recolors the input border and shows the error region.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-input-error</code></td>\n              <td>Apply on the input directly to force the invalid look without using <code>data-invalid</code> on the wrapper.</td>\n            </tr>\n          </tbody>\n        </table>\n\n        <h3>Web Component attributes</h3>\n        <p>Set on the <code>&lt;ren-field&gt;</code> wrapper. The component watches them and re-syncs ARIA state when changed.</p>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead>\n            <tr>\n              <th>Attribute</th>\n              <th>Type</th>\n              <th>Default</th>\n              <th>Notes</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td><code class=\"dx-api-name\">data-invalid</code></td>\n              <td><span class=\"dx-api-type\">boolean</span></td>\n              <td><span class=\"dx-api-default\">false</span></td>\n              <td>Presence shows the error region and sets <code>aria-invalid=\"true\"</code> on the input. Removed automatically when the input becomes valid again on <code>input</code>.</td>\n            </tr>\n          </tbody>\n        </table>\n\n        <h3>Auto-wiring (the value prop)</h3>\n        <p>What <code>&lt;ren-field&gt;</code> sets automatically when it mounts. You can write the simplest possible HTML — semantic tags, no IDs — and the component fills in the rest.</p>\n        <table class=\"dx-api\">\n          <thead>\n            <tr>\n              <th>Wired up</th>\n              <th>How</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td>Label ↔ Input</td>\n              <td>Input gets a generated <code>id</code>; label gets a matching <code>for</code> attribute.</td>\n            </tr>\n            <tr>\n              <td>Description ↔ Input</td>\n              <td>Description gets a generated <code>id</code>; input's <code>aria-describedby</code> is appended (existing values preserved).</td>\n            </tr>\n            <tr>\n              <td>Error ↔ Input</td>\n              <td>Error gets a generated <code>id</code>; input's <code>aria-errormessage</code> is set. Error gets <code>role=\"alert\"</code> + <code>aria-live=\"polite\"</code>.</td>\n            </tr>\n            <tr>\n              <td>Required indicator</td>\n              <td>If the input has <code>required</code>, the label gets <code>data-required</code> (renders the asterisk).</td>\n            </tr>\n            <tr>\n              <td>Validation listener</td>\n              <td>Listens for native <code>invalid</code> events; sets <code>data-invalid</code> on the field and reveals the error message. Clears on next valid <code>input</code>.</td>\n            </tr>\n          </tbody>\n        </table>\n\n        <h3>JavaScript methods</h3>\n        <p>Available on every <code>&lt;ren-field&gt;</code> instance.</p>\n        <table class=\"dx-api\">\n          <thead>\n            <tr>\n              <th>Method</th>\n              <th>Description</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td><code class=\"dx-api-name\">setError(message)</code></td>\n              <td>Marks the field invalid and writes the error message into the error region. Use for server-side validation results.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">clearError()</code></td>\n              <td>Removes the invalid state and hides the error region.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">invalid</code></td>\n              <td>Boolean getter / setter. Reads the current invalid state, or assigns one (same as <code>setError</code> / <code>clearError</code>).</td>\n            </tr>\n          </tbody>\n        </table>\n\n        <div class=\"dx-pre\" tabindex=\"0\"><code>const field = document.querySelector('ren-field');\n\n// After a server response\nif (response.error) {\n  field.setError('That email is already taken.');\n} else {\n  field.clearError();\n}</code></div>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           6. ACCESSIBILITY\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <p>Form accessibility lives in three relationships: label-to-input, description-to-input, and error-to-input. The Web Component wires all three. If you're using the CSS classes alone, you set them by hand — this section is your checklist.</p>\n\n        <h3>Keyboard</h3>\n        <div class=\"dx-keys\">\n          <div class=\"dx-keyrow\">\n            <span class=\"keys\"><kbd>Tab</kbd></span>\n            <span>Moves focus into the input. Disabled inputs are skipped.</span>\n          </div>\n          <div class=\"dx-keyrow\">\n            <span class=\"keys\"><kbd>Shift</kbd> + <kbd>Tab</kbd></span>\n            <span>Moves focus backward.</span>\n          </div>\n          <div class=\"dx-keyrow\">\n            <span class=\"keys\">Click on the label</span>\n            <span>Focuses the paired input — only works when the label has a <code>for</code> attribute matching the input's <code>id</code> (or wraps the input directly).</span>\n          </div>\n        </div>\n\n        <h3>What the component sets</h3>\n        <p>When you wrap your markup in <code>&lt;ren-field&gt;</code>, the following ARIA wiring is automatic:</p>\n        <ul>\n          <li><code>label[for]</code> ↔ <code>input[id]</code></li>\n          <li><code>aria-describedby</code> on the input pointing at the description</li>\n          <li><code>aria-errormessage</code> on the input pointing at the error</li>\n          <li><code>aria-invalid=\"true\"</code> when the field is in an invalid state</li>\n          <li><code>role=\"alert\"</code> and <code>aria-live=\"polite\"</code> on the error so screen readers announce it</li>\n        </ul>\n\n        <h3>Validation messaging</h3>\n        <p>Two paths, both supported:</p>\n        <ul>\n          <li><strong>Native HTML5</strong>: add <code>required</code>, <code>type=\"email\"</code>, <code>pattern</code>, etc. to the input. The browser's <code>invalid</code> event flips the field into the invalid state automatically.</li>\n          <li><strong>Programmatic</strong>: call <code>field.setError(\"custom message\")</code> after a server response. The same DOM hooks render the same way.</li>\n        </ul>\n\n        <div class=\"dx-callout\">\n          <p><strong>Always pair the label.</strong> A bare input with a placeholder is not a labelled input. Screen readers announce nothing meaningful. Even a visually hidden label (<code>.ren-sr-only</code>) is better than no label.</p>\n        </div>\n\n        <h3>Don't trap focus inside the field</h3>\n        <p>The field is just a wrapper. Tab and Shift+Tab move out of it normally. Don't add <code>tabindex</code> to the wrapper — focus belongs on the input itself.</p>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           7. EXAMPLES\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n\n        <h3>Login form</h3>\n        <p>Two required fields, primary submit. Native HTML5 validation handles the empty state.</p>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <form style=\"max-width: 360px; margin-inline: auto; display: flex; flex-direction: column; gap: var(--space-4);\">\n              <div class=\"ren-field\">\n                <label class=\"ren-field-label\" data-required for=\"login-email\">Email</label>\n                <input class=\"ren-input\" type=\"email\" id=\"login-email\" required autocomplete=\"email\">\n              </div>\n              <div class=\"ren-field\">\n                <label class=\"ren-field-label\" data-required for=\"login-password\">Password</label>\n                <input class=\"ren-input\" type=\"password\" id=\"login-password\" required autocomplete=\"current-password\">\n              </div>\n              <button type=\"submit\" class=\"ren-btn ren-btn-primary\">Sign in</button>\n            </form>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;form&gt;\n  &lt;ren-field&gt;\n    &lt;label&gt;Email&lt;/label&gt;\n    &lt;input type=\"email\" required autocomplete=\"email\"&gt;\n  &lt;/ren-field&gt;\n\n  &lt;ren-field&gt;\n    &lt;label&gt;Password&lt;/label&gt;\n    &lt;input type=\"password\" required autocomplete=\"current-password\"&gt;\n  &lt;/ren-field&gt;\n\n  &lt;button type=\"submit\" class=\"ren-btn ren-btn-primary\"&gt;Sign in&lt;/button&gt;\n&lt;/form&gt;</code></pre>\n        </div>\n\n        <h3>Server-side error</h3>\n        <p>Submit, get an error from the API, attach it to the right field.</p>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>const form = document.querySelector('form');\nconst emailField = form.querySelector('ren-field:has(input[type=\"email\"])');\n\nform.addEventListener('submit', async (e) =&gt; {\n  e.preventDefault();\n  emailField.clearError();\n\n  const res = await fetch('/api/signup', { method: 'POST', body: new FormData(form) });\n  const data = await res.json();\n\n  if (data.error?.field === 'email') {\n    emailField.setError(data.error.message);\n    emailField.querySelector('input').focus();\n    return;\n  }\n\n  location.assign('/dashboard');\n});</code></pre>\n\n        <h3>Search box with icon</h3>\n        <p>Wrap the input in <code>.ren-input-wrapper</code> with an icon for a leading affordance.</p>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;ren-field&gt;\n  &lt;label class=\"ren-sr-only\"&gt;Search&lt;/label&gt;\n  &lt;div class=\"ren-input-wrapper\"&gt;\n    &lt;span class=\"ren-input-icon\" aria-hidden=\"true\"&gt;\n      &lt;!-- search SVG --&gt;\n    &lt;/span&gt;\n    &lt;input type=\"search\" placeholder=\"Search projects…\"&gt;\n  &lt;/div&gt;\n&lt;/ren-field&gt;</code></pre>\n\n        <h3>Two fields side by side</h3>\n        <p>The field is a normal block. Drop them into any layout primitive — here, a 2-column grid.</p>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <div class=\"demo-form-row\">\n              <div class=\"ren-field\">\n                <label class=\"ren-field-label\" for=\"row-first\">First name</label>\n                <input class=\"ren-input\" id=\"row-first\" autocomplete=\"given-name\">\n              </div>\n              <div class=\"ren-field\">\n                <label class=\"ren-field-label\" for=\"row-last\">Last name</label>\n                <input class=\"ren-input\" id=\"row-last\" autocomplete=\"family-name\">\n              </div>\n            </div>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div class=\"ren-grid-2\"&gt;\n  &lt;ren-field&gt;\n    &lt;label&gt;First name&lt;/label&gt;\n    &lt;input autocomplete=\"given-name\"&gt;\n  &lt;/ren-field&gt;\n\n  &lt;ren-field&gt;\n    &lt;label&gt;Last name&lt;/label&gt;\n    &lt;input autocomplete=\"family-name\"&gt;\n  &lt;/ren-field&gt;\n&lt;/div&gt;</code></pre>\n        </div>\n      </section>\n\n    </main>\n\n  </div>\n\n  <!-- Web Component registration — adds auto-wiring on top of the CSS-only baseline -->\n  <script type=\"module\" src=\"../../components/primitives/ren-field/ren-field.js\"></script>\n\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-field.html",
      "id": "docs:docs/components/ren-field.html",
      "type": "docs_page",
      "name": "ren-field docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Form Validation — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\" aria-current=\"page\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Form Validation</li></ol></nav>\n        <p class=\"dx-kicker\">Pattern</p>\n        <h1>Form Validation <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">Conventions for inline error messages, summary banners, and submit-button state. Browser-native HTML5 validation does most of the work; this pattern documents how to wire the rest.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>Form validation is a pattern, not a component — it's how Field, Banner, Button and Toast cooperate when a form is wrong or submitted. Use HTML5 attributes (<code>required</code>, <code>type</code>, <code>pattern</code>, <code>min</code>, <code>max</code>) for client-side; show server-side errors via <code>setError()</code> on each Field.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\" style=\"flex-direction: column; align-items: stretch; max-width: 420px; margin-inline: auto; gap: var(--space-3);\"><div class=\"ren-banner ren-banner-danger\" role=\"alert\"><span class=\"ren-banner-icon\"><svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg></span><div class=\"ren-banner-content\"><p class=\"ren-banner-title\">2 fields need your attention</p></div></div><div class=\"ren-field\" data-invalid><label class=\"ren-field-label\" for=\"vd-email\">Email</label><input class=\"ren-input\" id=\"vd-email\" value=\"not-an-email\" aria-invalid=\"true\"><span class=\"ren-field-error\" role=\"alert\">Please enter a valid email address.</span></div><div class=\"ren-field\"><label class=\"ren-field-label\" data-required for=\"vd-pw\">Password</label><input class=\"ren-input\" id=\"vd-pw\" type=\"password\" required></div></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;form novalidate&gt;\n  &lt;ren-field&gt;\n    &lt;label&gt;Email&lt;/label&gt;\n    &lt;input type=\"email\" required&gt;\n  &lt;/ren-field&gt;\n\n  &lt;button type=\"submit\"&gt;Save&lt;/button&gt;\n&lt;/form&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Patterns</p><h2>Pieces</h2><ul><li><strong>HTML5 attributes</strong>: <code>required</code>, <code>type=\"email\"</code>, <code>pattern</code>, <code>min</code>, <code>max</code>, <code>minlength</code>, <code>maxlength</code>.</li><li><strong>Per-field errors</strong>: <a href=\"ren-field.html\" class=\"ren-link\">Form Field</a> shows <code>aria-invalid</code> + the error message.</li><li><strong>Form-level summary</strong>: a <a href=\"ren-banner.html\" class=\"ren-link\">Banner</a> at the top with the count of errors and links to each one (skip-to-error pattern).</li><li><strong>Server-side errors</strong>: after a failed submit, call <code>field.setError(message)</code> for each invalid field.</li><li><strong>Submit button state</strong>: <code>data-loading</code> while submitting; disabled until the form has any input on touch (don't disable proactively before the user types — it's confusing).</li></ul></section>\n      <section class=\"dx-section\" id=\"multi-step\"><p class=\"dx-kicker\">Variant</p><h2>Multi-step forms</h2><p>Use a multi-step form when a single page would feel overwhelming or when steps depend on previous answers. <code>ren-form</code> supports multi-step via the <code>data-steps</code> attribute on the host and a <code>.ren-stepper</code> progress indicator.</p><div class=\"dx-demo\"><div class=\"dx-demo-preview\" style=\"flex-direction: column; align-items: stretch;\"><ol class=\"ren-stepper\" style=\"display: flex; gap: var(--space-3); list-style: none; padding: 0; margin: 0 0 var(--space-4); justify-content: space-between;\"><li style=\"display: flex; align-items: center; gap: var(--space-2);\"><span style=\"width: 24px; height: 24px; border-radius: 50%; background: var(--color-success); color: var(--color-on-success); display: grid; place-items: center; font-size: var(--text-xs); font-weight: var(--weight-bold);\"><svg viewBox=\"0 0 24 24\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"20 6 9 17 4 12\"/></svg></span><span style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Account</span></li><li style=\"display: flex; align-items: center; gap: var(--space-2);\"><span style=\"width: 24px; height: 24px; border-radius: 50%; background: var(--color-accent); color: var(--color-on-accent); display: grid; place-items: center; font-size: var(--text-xs); font-weight: var(--weight-bold);\">2</span><span style=\"font-size: var(--text-sm); font-weight: var(--weight-semibold);\">Profile</span></li><li style=\"display: flex; align-items: center; gap: var(--space-2);\"><span style=\"width: 24px; height: 24px; border-radius: 50%; background: var(--color-fill); color: var(--color-text-muted); display: grid; place-items: center; font-size: var(--text-xs); font-weight: var(--weight-bold);\">3</span><span style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Plan</span></li></ol><p style=\"font-size: var(--text-sm); color: var(--color-text-muted); margin: 0;\">Step 2 of 3 — current step content goes here.</p></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-form data-steps=\"3\"&gt;\n  &lt;ol class=\"ren-stepper\" aria-label=\"Setup progress\"&gt;\n    &lt;li data-completed&gt;Account&lt;/li&gt;\n    &lt;li aria-current=\"step\"&gt;Profile&lt;/li&gt;\n    &lt;li&gt;Plan&lt;/li&gt;\n  &lt;/ol&gt;\n  &lt;fieldset data-step=\"1\"&gt;…&lt;/fieldset&gt;\n  &lt;fieldset data-step=\"2\" data-active&gt;…&lt;/fieldset&gt;\n  &lt;fieldset data-step=\"3\"&gt;…&lt;/fieldset&gt;\n&lt;/ren-form&gt;</code></pre></div><h3>Stepper API</h3><table class=\"dx-api\"><thead><tr><th>Class / attribute</th><th>Effect</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">.ren-stepper</code></td><td>The progress indicator. Numbered ol with completed / current / pending states.</td></tr><tr><td><code class=\"dx-api-name\">[aria-current=\"step\"]</code></td><td>Marks the active step.</td></tr><tr><td><code class=\"dx-api-name\">[data-completed]</code></td><td>Marks a past step (checkmark fill).</td></tr><tr><td><code class=\"dx-api-name\">[data-steps=\"N\"]</code> on host</td><td>Declares the total number of steps.</td></tr><tr><td><code class=\"dx-api-name\">[data-active]</code> on fieldset</td><td>Marks the currently visible step; others are hidden.</td></tr></tbody></table><h3>Pattern checklist</h3><ul><li>Validate the current step before <kbd>Next</kbd> advances.</li><li>Allow free <kbd>Back</kbd> navigation — don't lose prior data.</li><li>Save progress to <code>localStorage</code> or the server so the user can leave and resume.</li><li>Last step is \"Review\" — show all entered data before final submit.</li><li>Autofocus the first input on each step change.</li><li>Announce progress via a polite live region: \"Step 2 of 3.\"</li></ul><div class=\"dx-callout\"><p><strong>Status of <code>.ren-stepper</code>:</strong> the stepper is an internal CSS pattern of <code>ren-form</code>, not a standalone component. There is no <code>ren-stepper</code> primitive, Web Component, or separate JS — it's a styled <code>&lt;ol&gt;</code> that consumer code drives. If you need a reusable Stepper outside a multi-step form (e.g. an onboarding tour, or a wizard not tied to a single form), open an issue — it's on the backlog.</p></div></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li>Use <code>novalidate</code> on the form so you control validation timing instead of letting the browser show its own popup before submission.</li><li>Validate on <strong>blur</strong> (not on every keystroke) for first-time entry; revalidate on input after the field has been marked invalid once.</li><li>Move focus to the first invalid field after a failed submit.</li><li>Summary banner uses <code>role=\"alert\"</code> so screen readers announce the error count immediately.</li><li>For multi-step flows: each step is its own <code>&lt;fieldset&gt;</code> with its own focus management; the stepper uses <code>aria-current=\"step\"</code> on the active item.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-form.html",
      "id": "docs:docs/components/ren-form.html",
      "type": "docs_page",
      "name": "ren-form docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Hover Card — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: visible; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-8) var(--space-5); display: flex; gap: var(--space-3); align-items: center; justify-content: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\" aria-current=\"page\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Hover Card</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Hover Card <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">A rich preview that appears when the user hovers over a link or avatar. Think Twitter / GitHub profile cards on hover. Larger and more leisurely than a Tooltip; doesn't trap focus like a Popover.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Hover cards show a peek of related content without forcing the user to navigate. Use them on user mentions, tags, internal links — anywhere a quick preview helps. They're voluntary previews, not modals; they shouldn't carry actions the user has to take.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Tooltip vs Hover Card.</strong> Tooltip is text-only and small (a label, a shortcut). Hover Card is rich content with images and structure, sized like a small popover.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <p>The component activates after a 700 ms hover delay (longer than Tooltip's 300 ms — gives the user a beat to keep moving past the trigger if they don't actually want the preview).</p>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <p style=\"font-size: var(--text-sm); margin: 0;\">Hover the link to see the user preview: <a href=\"#\" class=\"ren-link\" data-hover=\"@ren\">@ren</a> shipped this design system.</p>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-hover-card&gt;\n  &lt;a slot=\"trigger\" href=\"/users/ren\"&gt;@ren&lt;/a&gt;\n  &lt;div slot=\"content\"&gt;\n    &lt;img src=\"/avatars/ren.jpg\" class=\"ren-avatar\"&gt;\n    &lt;h3&gt;Ren Soconese&lt;/h3&gt;\n    &lt;p&gt;Designer · Buenos Aires&lt;/p&gt;\n    &lt;p class=\"muted\"&gt;Joined March 2024 · 142 projects&lt;/p&gt;\n  &lt;/div&gt;\n&lt;/ren-hover-card&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-hover-card</code></td><td>The floating panel. Card-like surface with shadow and padding.</td></tr>\n          </tbody>\n        </table>\n        <h3>Web Component attributes</h3>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">delay</code></td><td><span class=\"dx-api-type\">number (ms)</span></td><td><span class=\"dx-api-default\">700</span></td><td>Hover dwell before showing.</td></tr>\n            <tr><td><code class=\"dx-api-name\">close-delay</code></td><td><span class=\"dx-api-type\">number (ms)</span></td><td><span class=\"dx-api-default\">300</span></td><td>Time after the cursor leaves both trigger and card before closing — gives the user a moment to move into the card.</td></tr>\n            <tr><td><code class=\"dx-api-name\">placement</code></td><td><span class=\"dx-api-type\">\"top\" | \"right\" | \"bottom\" | \"left\"</span></td><td><span class=\"dx-api-default\">\"bottom\"</span></td><td>Side relative to the trigger.</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li>Trigger must be a real link or button. Focusing it via Tab also opens the card (not just hover).</li>\n          <li>Card content is supplementary — never put critical information here. Touch users won't see it.</li>\n          <li>If the card contains links or actions, those should ALSO live somewhere reachable — on the linked profile page, in a menu.</li>\n          <li><kbd>Esc</kbd> closes the card while focus stays on the trigger.</li>\n        </ul>\n      </section>\n\n    </main>\n  </div>\n  <script type=\"module\" src=\"../../components/composites/ren-hover-card/ren-hover-card.js\"></script>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-hover-card.html",
      "id": "docs:docs/components/ren-hover-card.html",
      "type": "docs_page",
      "name": "ren-hover-card docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Icons — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-3); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\" aria-current=\"page\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Icons</li></ol></nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Icons <span class=\"dx-api-badge dx-api-badge-css\" title=\"Works without any JavaScript\">CSS-only</span></h1>\n        <p class=\"lede\">Conventions for SVG icons in RenDS. 16 / 20 / 24 px sizing, currentColor stroke, semantic alt or <code>aria-hidden</code> based on whether the icon carries meaning on its own.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>RenDS doesn't ship an icon set. Bring your own (Lucide, Heroicons, Iconoir, custom) and follow the conventions below so they look consistent and stay accessible.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 8v4M12 16h.01\"/></svg><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M5 12h14M12 5l7 7-7 7\"/></svg><svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M20 6L9 17l-5-5\"/></svg></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\"\n     stroke=\"currentColor\" stroke-width=\"2\"\n     aria-hidden=\"true\"&gt;\n  &lt;path d=\"...\"/&gt;\n&lt;/svg&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><h3>Sizing convention</h3><table class=\"dx-api\"><thead><tr><th>Use case</th><th>Size</th></tr></thead><tbody><tr><td>Inline with text (next to a label)</td><td>16 px</td></tr><tr><td>Standard (buttons, menus, tabs)</td><td>20 px</td></tr><tr><td>Large (empty states, headers)</td><td>24 px</td></tr></tbody></table><h3>Color</h3><p>Set <code>stroke=\"currentColor\"</code> (or <code>fill=\"currentColor\"</code> for filled icons) so the icon inherits the surrounding text color. Don't hardcode colors on icons.</p></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li><strong>Decorative icon next to text</strong>: add <code>aria-hidden=\"true\"</code>. The text already labels the action.</li><li><strong>Icon-only button</strong>: the button needs <code>aria-label=\"Action name\"</code>. The icon stays <code>aria-hidden</code>.</li><li><strong>Icon as the entire link/button identity</strong> (rare): the SVG can have <code>role=\"img\" aria-label=\"…\"</code>.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-icon.html",
      "id": "docs:docs/components/ren-icon.html",
      "type": "docs_page",
      "name": "ren-icon docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Keyboard Key — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-3); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\" aria-current=\"page\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Keyboard Key</li></ol></nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Keyboard Key <span class=\"dx-api-badge dx-api-badge-css\" title=\"Works without any JavaScript\">CSS-only</span></h1>\n        <p class=\"lede\">Keyboard shortcut keys. Renders <code>&lt;kbd&gt;</code> with mono type and a subtle bottom-bordered chip look.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>Use <code>&lt;kbd&gt;</code> for keyboard shortcuts in tooltips, menus, command palettes, and documentation. Combine with <code>+</code> for chords like <code>&lt;kbd&gt;Cmd&lt;/kbd&gt;+&lt;kbd&gt;K&lt;/kbd&gt;</code>.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\"><kbd class=\"ren-kbd\">Tab</kbd><kbd class=\"ren-kbd\">⌘</kbd>+<kbd class=\"ren-kbd\">K</kbd><kbd class=\"ren-kbd\">Shift</kbd>+<kbd class=\"ren-kbd\">Enter</kbd><kbd class=\"ren-kbd\">Esc</kbd></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>Press &lt;kbd class=\"ren-kbd\"&gt;⌘&lt;/kbd&gt;+&lt;kbd class=\"ren-kbd\"&gt;K&lt;/kbd&gt; to open the command palette.</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><table class=\"dx-api\"><thead><tr><th>Class</th><th>Effect</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">.ren-kbd</code></td><td>Apply on <code>&lt;kbd&gt;</code>. Mono type, subtle border, slightly bolder bottom border for the \"key cap\" feel.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><p>Always use the semantic <code>&lt;kbd&gt;</code> tag. Screen readers announce it appropriately. Inside menus or tooltips, the parent already provides context.</p></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-kbd.html",
      "id": "docs:docs/components/ren-kbd.html",
      "type": "docs_page",
      "name": "ren-kbd docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Link — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-3); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\" aria-current=\"page\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Link</li></ol></nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Link <span class=\"dx-api-badge dx-api-badge-css\" title=\"Works without any JavaScript\">CSS-only</span></h1>\n        <p class=\"lede\">Variants of <code>&lt;a&gt;</code> beyond the default — subtle, plain (no underline), destructive, with leading/trailing icon, standalone block. The base <code>&lt;a&gt;</code> uses the classless default; reach for <code>.ren-link</code> when you need one of the variants.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>Most links should just be <code>&lt;a href&gt;</code> — the base classless styling already handles color, hover and focus. Use <code>.ren-link</code> when you specifically need one of the variants below.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\"><a href=\"#\" class=\"ren-link\">Primary link</a><a href=\"#\" class=\"ren-link-subtle\">Subtle</a><a href=\"#\" class=\"ren-link-plain\">Plain (no underline)</a><a href=\"#\" class=\"ren-link-destructive\">Destructive</a></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;a href=\"/contact\" class=\"ren-link\"&gt;Get in touch&lt;/a&gt;\n&lt;a href=\"/policy\" class=\"ren-link-subtle\"&gt;Privacy policy&lt;/a&gt;\n&lt;a href=\"/delete\" class=\"ren-link-destructive\"&gt;Delete account&lt;/a&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><table class=\"dx-api\"><thead><tr><th>Class</th><th>Effect</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">.ren-link</code></td><td>Standard link styling. Use when the inherited classless style needs explicit control.</td></tr><tr><td><code class=\"dx-api-name\">.ren-link-subtle</code></td><td>Quieter — secondary text color, underline only on hover.</td></tr><tr><td><code class=\"dx-api-name\">.ren-link-plain</code></td><td>No underline. Use inside cards or list rows where the whole row is clickable.</td></tr><tr><td><code class=\"dx-api-name\">.ren-link-destructive</code></td><td>Danger color. For \"Delete\", \"Cancel subscription\", etc.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li>Always use a real <code>&lt;a href&gt;</code>. A clickable <code>&lt;span&gt;</code> is not a link.</li><li>Don't remove the underline from prose links — it's the primary affordance for color-blind users. Reserve <code>.ren-link-plain</code> for self-evident click targets like card surfaces.</li><li>Use descriptive link text. \"Click here\" tells a screen reader user nothing.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-link.html",
      "id": "docs:docs/components/ren-link.html",
      "type": "docs_page",
      "name": "ren-link docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Menu — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: visible; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-4); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-direction: column; gap: var(--space-3); align-items: flex-start; }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n\n    /* Static menu mock */\n    .demo-menu {\n      list-style: none; margin: 0; padding: var(--space-1);\n      width: 240px;\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border); border-radius: var(--radius-md);\n      box-shadow: var(--shadow-lg);\n    }\n    .demo-menu-item {\n      display: flex; align-items: center; gap: var(--space-3);\n      padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);\n      cursor: pointer; font-size: var(--text-sm);\n    }\n    .demo-menu-item:hover, .demo-menu-item[data-highlighted] { background: var(--color-fill); }\n    .demo-menu-item-icon { width: 16px; height: 16px; color: var(--color-text-muted); display: inline-flex; align-items: center; justify-content: center; }\n    .demo-menu-item-label { flex: 1; }\n    .demo-menu-item-shortcut { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text-muted); }\n    .demo-menu-item-danger { color: var(--color-danger); }\n    .demo-menu-item-danger:hover, .demo-menu-item-danger[data-highlighted] { background: color-mix(in srgb, var(--color-danger) 8%, var(--color-surface)); }\n    .demo-menu-separator { height: 1px; margin: var(--space-1) 0; background: var(--color-border); }\n    .demo-menu-label { padding: var(--space-2) var(--space-3) var(--space-1); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); font-weight: var(--weight-semibold); }\n  </style>\n</head>\n<body>\n\n  <header class=\"dx-nav\">\n    <div class=\"dx-nav-inner\">\n      <a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a>\n      <nav class=\"dx-nav-menu\" aria-label=\"Primary\">\n        <a href=\"../index.html\">Docs</a>\n        <a href=\"../components.html\" aria-current=\"page\">Components</a>\n        <a href=\"../../templates/index.html\">Templates</a>\n        <a href=\"../../create/index.html\">Theme Builder</a>\n      </nav>\n      <div class=\"dx-nav-actions\">\n        <a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a>\n        <a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\" aria-current=\"page\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\">\n          <ol>\n            <li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li>\n            <li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li>\n            <li aria-current=\"page\">Menu</li>\n          </ol>\n        </nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Menu <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">A dropdown list of <em>actions</em>, anchored to a trigger. Use it for \"More options\" buttons, account menus, and contextual action lists. Each item runs a command — unlike Select, which holds state.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>A Menu is a transient floating list opened by a trigger button (or a right-click for the context-menu variant). Each item is an action: clicking runs it and closes the menu. Items can be grouped with separators, labelled, or marked as destructive.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Menu vs Select.</strong> Menu = \"do something\". Select = \"choose something\". A menu item disappears the moment you click it; a select option stays as the chosen state.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"anatomy\">\n        <p class=\"dx-kicker\">Parts</p>\n        <h2>Anatomy</h2>\n        <p>Three layers: the trigger that opens the menu, the menu itself, and the items inside (with optional icons, shortcuts, descriptions, and dangerous-action styling).</p>\n        <div style=\"background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-6) var(--space-5); margin: var(--space-4) 0;\">\n          <p style=\"font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); font-weight: var(--weight-semibold); margin: 0 0 var(--space-3);\">Assembled</p>\n          <div style=\"display: flex; justify-content: center;\">\n            <ul class=\"demo-menu\" role=\"menu\" aria-label=\"Project actions\" data-anatomy>\n              <li class=\"demo-menu-label\" role=\"presentation\">Project</li>\n              <li class=\"demo-menu-item\" role=\"menuitem\"><span class=\"demo-menu-item-icon\"><svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z\"/><path d=\"m15 5 4 4\"/></svg></span><span class=\"demo-menu-item-label\">Rename</span><span class=\"demo-menu-item-shortcut\">⌘R</span></li>\n              <li class=\"demo-menu-item\" role=\"menuitem\"><span class=\"demo-menu-item-icon\">↗</span><span class=\"demo-menu-item-label\">Move to…</span></li>\n              <li class=\"demo-menu-item\" role=\"menuitem\"><span class=\"demo-menu-item-icon\">⎘</span><span class=\"demo-menu-item-label\">Duplicate</span><span class=\"demo-menu-item-shortcut\">⌘D</span></li>\n              <li class=\"demo-menu-separator\" role=\"separator\"></li>\n              <li class=\"demo-menu-item demo-menu-item-danger\" role=\"menuitem\"><span class=\"demo-menu-item-icon\">⌫</span><span class=\"demo-menu-item-label\">Delete</span><span class=\"demo-menu-item-shortcut\">⌘⌫</span></li>\n            </ul>\n          </div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <ul class=\"demo-menu\" role=\"menu\" aria-label=\"Demo project actions\" id=\"demo-menu\" style=\"margin-inline: auto;\">\n              <li class=\"demo-menu-label\" role=\"presentation\">Project</li>\n              <li class=\"demo-menu-item\" role=\"menuitem\" tabindex=\"-1\"><span class=\"demo-menu-item-icon\"><svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z\"/><path d=\"m15 5 4 4\"/></svg></span><span class=\"demo-menu-item-label\">Rename</span><span class=\"demo-menu-item-shortcut\">⌘R</span></li>\n              <li class=\"demo-menu-item\" role=\"menuitem\" tabindex=\"-1\"><span class=\"demo-menu-item-icon\">↗</span><span class=\"demo-menu-item-label\">Move to…</span></li>\n              <li class=\"demo-menu-item\" role=\"menuitem\" tabindex=\"-1\"><span class=\"demo-menu-item-icon\">⎘</span><span class=\"demo-menu-item-label\">Duplicate</span><span class=\"demo-menu-item-shortcut\">⌘D</span></li>\n              <li class=\"demo-menu-separator\" role=\"separator\"></li>\n              <li class=\"demo-menu-item demo-menu-item-danger\" role=\"menuitem\" tabindex=\"-1\"><span class=\"demo-menu-item-icon\">⌫</span><span class=\"demo-menu-item-label\">Delete project</span><span class=\"demo-menu-item-shortcut\">⌘⌫</span></li>\n            </ul>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;button data-menu-trigger=\"actions\"&gt;More&lt;/button&gt;\n\n&lt;ren-menu id=\"actions\"&gt;\n  &lt;div class=\"ren-menu-label\"&gt;Project&lt;/div&gt;\n\n  &lt;button class=\"ren-menu-item\" role=\"menuitem\"&gt;\n    &lt;span class=\"ren-menu-item-icon\"&gt;…&lt;/span&gt;\n    &lt;span&gt;Rename&lt;/span&gt;\n    &lt;span class=\"ren-menu-item-shortcut\"&gt;⌘R&lt;/span&gt;\n  &lt;/button&gt;\n\n  &lt;hr class=\"ren-menu-separator\"&gt;\n\n  &lt;button class=\"ren-menu-item ren-menu-item-danger\" role=\"menuitem\"&gt;\n    Delete project\n  &lt;/button&gt;\n&lt;/ren-menu&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Item types</p>\n        <h2>Variants</h2>\n        <div class=\"dx-vgrid\">\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Plain item</span>\n            <div class=\"dx-vrow-items\"><p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\"><code>.ren-menu-item</code> — basic action. Click runs the action and closes the menu.</p></div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">With icon + shortcut</span>\n            <div class=\"dx-vrow-items\"><p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Add <code>.ren-menu-item-icon</code> at the start and <code>.ren-menu-item-shortcut</code> at the end. The icon renders muted; the shortcut uses mono type.</p></div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Destructive</span>\n            <div class=\"dx-vrow-items\"><p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\"><code>.ren-menu-item-danger</code> tints the item red on hover/highlight. Pair with a confirmation Dialog for irreversible actions.</p></div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Group label</span>\n            <div class=\"dx-vrow-items\"><p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\"><code>.ren-menu-label</code> — non-interactive section header (uppercase, muted). Use to group related items.</p></div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Separator</span>\n            <div class=\"dx-vrow-items\"><p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\"><code>.ren-menu-separator</code> or a plain <code>&lt;hr&gt;</code> with that class — divides groups visually.</p></div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Disabled</span>\n            <div class=\"dx-vrow-items\"><p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Add <code>aria-disabled=\"true\"</code> on a menuitem to gray it out and skip it during keyboard navigation.</p></div>\n          </div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <h3>CSS classes</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-menu</code></td><td>The floating menu container. Use with <code>role=\"menu\"</code>. Designed to live inside a popover (or be positioned by the component).</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-menu-item</code></td><td>One action. <code>role=\"menuitem\"</code>. Best as a <code>&lt;button type=\"button\"&gt;</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-menu-item-icon</code></td><td>Leading icon inside an item. 16 px square, muted color.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-menu-item-shortcut</code></td><td>Trailing keyboard shortcut text. Mono font, muted.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-menu-item-description</code></td><td>Optional second line under the item label. Smaller, muted.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-menu-item-danger</code></td><td>Destructive styling. Red text + red hover background.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-menu-label</code></td><td>Section heading inside the menu. Uppercase, muted, non-interactive.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-menu-separator</code></td><td>Thin divider between groups.</td></tr>\n          </tbody>\n        </table>\n\n        <h3>Web Component attributes</h3>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">data-menu-trigger</code></td><td><span class=\"dx-api-type\">string (id)</span></td><td><span class=\"dx-api-default\">—</span></td><td>On any element. Click opens the matching <code>&lt;ren-menu&gt;</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">placement</code></td><td><span class=\"dx-api-type\">\"bottom-start\" | \"bottom-end\" | \"top-start\" | \"top-end\" | \"right-start\" | \"right-end\" | \"left-start\" | \"left-end\"</span></td><td><span class=\"dx-api-default\">\"bottom-start\"</span></td><td>Where the menu floats relative to the trigger. Reflected to <code>data-side</code> and <code>data-align</code>.</td></tr>\n          </tbody>\n        </table>\n\n        <h3>Events</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Event</th><th>Detail</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">ren-menu-select</code></td><td>An item was activated. <code>{ item, value }</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">ren-menu-open</code> / <code class=\"dx-api-name\">ren-menu-close</code></td><td>Menu opened / closed.</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <p>Implements the WAI-ARIA Menu Button pattern.</p>\n        <h3>Keyboard</h3>\n        <div class=\"dx-keys\">\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Space</kbd> / <kbd>Enter</kbd> / <kbd>↓</kbd></span><span>On the trigger: opens the menu, focuses the first item.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>↑</kbd> / <kbd>↓</kbd></span><span>Move highlight through items. Skips disabled and separators.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Home</kbd> / <kbd>End</kbd></span><span>Jump to the first / last item.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Enter</kbd></span><span>Activate the highlighted item.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\">Type</span><span>Typeahead — jumps to the first item starting with the typed letter(s).</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Esc</kbd></span><span>Close the menu, return focus to the trigger.</span></div>\n        </div>\n        <div class=\"dx-callout\">\n          <p><strong>Pair destructive items with a confirmation.</strong> A menu item that deletes data should open a Dialog with explicit confirm/cancel — never delete on first click.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n        <h3>Account menu</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;button class=\"ren-btn ren-btn-ghost\" data-menu-trigger=\"account\"&gt;\n  &lt;img src=\"/avatar.jpg\" class=\"ren-avatar\"&gt;\n&lt;/button&gt;\n\n&lt;ren-menu id=\"account\" placement=\"bottom-end\"&gt;\n  &lt;div class=\"ren-menu-label\"&gt;Signed in as ren@example.com&lt;/div&gt;\n\n  &lt;button class=\"ren-menu-item\"&gt;Profile&lt;/button&gt;\n  &lt;button class=\"ren-menu-item\"&gt;Settings&lt;/button&gt;\n  &lt;button class=\"ren-menu-item\"&gt;Help &amp; feedback&lt;/button&gt;\n\n  &lt;hr class=\"ren-menu-separator\"&gt;\n\n  &lt;button class=\"ren-menu-item\"&gt;Sign out&lt;/button&gt;\n&lt;/ren-menu&gt;</code></pre>\n\n        <h3>Run an action</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>document.querySelector('ren-menu').addEventListener('ren-menu-select', (e) =&gt; {\n  const action = e.detail.item.dataset.action;\n  switch (action) {\n    case 'rename':    openRenameDialog(); break;\n    case 'duplicate': duplicateProject(); break;\n    case 'delete':    confirmDelete(); break;\n  }\n});</code></pre>\n      </section>\n\n    </main>\n  </div>\n\n  <script type=\"module\" src=\"../../components/composites/ren-menu/ren-menu.js\"></script>\n  <script>\n    // Keyboard navigation for the static demo menu\n    const menu = document.getElementById('demo-menu');\n    if (menu) {\n      const items = menu.querySelectorAll('.demo-menu-item');\n      let idx = 0;\n      const highlight = (i) => {\n        items.forEach((it, j) => it.toggleAttribute('data-highlighted', i === j));\n        if (items[i]) items[i].focus();\n      };\n      items.forEach((it, i) => {\n        it.addEventListener('mouseenter', () => { idx = i; highlight(i); });\n        it.addEventListener('keydown', (e) => {\n          if (e.key === 'ArrowDown') { e.preventDefault(); idx = (idx + 1) % items.length; highlight(idx); }\n          else if (e.key === 'ArrowUp') { e.preventDefault(); idx = (idx - 1 + items.length) % items.length; highlight(idx); }\n          else if (e.key === 'Home') { e.preventDefault(); idx = 0; highlight(0); }\n          else if (e.key === 'End') { e.preventDefault(); idx = items.length - 1; highlight(idx); }\n        });\n      });\n      // Make the first item focusable\n      if (items[0]) items[0].setAttribute('tabindex', '0');\n    }\n  </script>\n\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-menu.html",
      "id": "docs:docs/components/ren-menu.html",
      "type": "docs_page",
      "name": "ren-menu docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Menubar — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\" aria-current=\"page\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Menubar</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Menubar <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">Horizontal menu bar with sub-menus. For desktop-style apps with a top-level File / Edit / View structure.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>Use a Menubar when your app has more global commands than fit in a button row, and the desktop-app metaphor (File menu, Edit menu) makes sense — IDEs, design tools, document editors.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\" style=\"display: block;\"><div role=\"menubar\" class=\"ren-menubar\" style=\"display: flex; gap: var(--space-1); padding: var(--space-1); border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-surface);\"><button role=\"menuitem\" type=\"button\" style=\"padding: var(--space-1) var(--space-3); background: transparent; border: 0; border-radius: var(--radius-sm); cursor: pointer; font-size: var(--text-sm);\">File</button><button role=\"menuitem\" type=\"button\" style=\"padding: var(--space-1) var(--space-3); background: transparent; border: 0; border-radius: var(--radius-sm); cursor: pointer; font-size: var(--text-sm);\">Edit</button><button role=\"menuitem\" type=\"button\" style=\"padding: var(--space-1) var(--space-3); background: transparent; border: 0; border-radius: var(--radius-sm); cursor: pointer; font-size: var(--text-sm);\">View</button><button role=\"menuitem\" type=\"button\" style=\"padding: var(--space-1) var(--space-3); background: transparent; border: 0; border-radius: var(--radius-sm); cursor: pointer; font-size: var(--text-sm);\">Help</button></div></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-menubar aria-label=\"Main menu\"&gt;\n  &lt;button role=\"menuitem\"&gt;File&lt;/button&gt;\n  &lt;button role=\"menuitem\"&gt;Edit&lt;/button&gt;\n  &lt;button role=\"menuitem\"&gt;View&lt;/button&gt;\n&lt;/ren-menubar&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><p>Each top-level item is a <code>&lt;button role=\"menuitem\"&gt;</code>. Submenus use <a href=\"ren-menu.html\" class=\"ren-link\">Menu</a> popovers triggered by each item.</p></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li>Wrapper has <code>role=\"menubar\"</code> + descriptive <code>aria-label</code>.</li><li>Arrow keys navigate horizontally between top-level items, vertically inside an open submenu.</li><li><kbd>Esc</kbd> closes any open submenu.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-menubar.html",
      "id": "docs:docs/components/ren-menubar.html",
      "type": "docs_page",
      "name": "ren-menubar docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Nav — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\" aria-current=\"page\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Nav</li></ol></nav>\n        <p class=\"dx-kicker\">Pattern</p>\n        <h1>Nav <span class=\"dx-api-badge dx-api-badge-hybrid\" title=\"Renders without JS; JS adds enhancements\">Hybrid</span></h1>\n        <p class=\"lede\">Top navigation bar with brand, primary links, search, account menu. The pattern most apps put at the top of every page.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>Nav is the global header. It tells the user where they are, what's available, and how to switch context. Make it sticky so it's reachable without scrolling, and keep it simple — primary destinations only, secondary stuff goes in account menus or settings.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\" style=\"display: block; padding: 0;\"><nav class=\"ren-nav\" style=\"display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); padding: var(--space-3) var(--space-5); border-bottom: 1px solid var(--color-border); background: var(--color-surface);\"><div style=\"display: flex; align-items: center; gap: var(--space-4);\"><strong>Acme</strong><a href=\"#\" class=\"ren-link-plain\" style=\"font-size: var(--text-sm);\">Dashboard</a><a href=\"#\" class=\"ren-link-plain\" style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Reports</a><a href=\"#\" class=\"ren-link-plain\" style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Settings</a></div><div style=\"display: flex; align-items: center; gap: var(--space-2);\"><button class=\"ren-btn ren-btn-ghost ren-btn-sm\" type=\"button\">Search</button><button class=\"ren-btn ren-btn-secondary ren-btn-sm\" type=\"button\">Account</button></div></nav></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;nav class=\"ren-nav\" aria-label=\"Primary\"&gt;\n  &lt;a href=\"/\" class=\"ren-nav-brand\"&gt;Acme&lt;/a&gt;\n  &lt;ul class=\"ren-nav-links\"&gt;\n    &lt;li&gt;&lt;a href=\"/dashboard\" aria-current=\"page\"&gt;Dashboard&lt;/a&gt;&lt;/li&gt;\n    &lt;li&gt;&lt;a href=\"/reports\"&gt;Reports&lt;/a&gt;&lt;/li&gt;\n  &lt;/ul&gt;\n  &lt;div class=\"ren-nav-actions\"&gt;…&lt;/div&gt;\n&lt;/nav&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><table class=\"dx-api\"><thead><tr><th>Class</th><th>Effect</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">.ren-nav</code></td><td>The wrapper. Sticky-top, blurred backdrop, bottom border.</td></tr><tr><td><code class=\"dx-api-name\">.ren-nav-brand</code></td><td>The logo / app name link. Always returns to home.</td></tr><tr><td><code class=\"dx-api-name\">.ren-nav-links</code></td><td>The horizontal list of primary links.</td></tr><tr><td><code class=\"dx-api-name\">.ren-nav-actions</code></td><td>Trailing slot for buttons / account menu.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li>Wrap in <code>&lt;nav aria-label=\"Primary\"&gt;</code>.</li><li>Mark the active link with <code>aria-current=\"page\"</code>.</li><li>On mobile, collapse the links into a button-triggered <a href=\"ren-sheet.html\" class=\"ren-link\">Sheet</a> rather than overflow off-screen.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-nav.html",
      "id": "docs:docs/components/ren-nav.html",
      "type": "docs_page",
      "name": "ren-nav docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Number Field — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\" aria-current=\"page\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Number Field</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Number Field <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">Numeric input with stepper buttons, min / max / step, and clamping. Native <code>&lt;input type=&quot;number&quot;&gt;</code> on steroids.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>Use Number Field for quantities, prices, ages, durations — anywhere a precise number matters. Renders bigger increment / decrement buttons than the native browser controls and clamps to your min / max.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\" style=\"flex-direction: column; align-items: stretch;\"><label class=\"ren-field\"><span class=\"ren-field-label\">Quantity</span><div style=\"display: flex; gap: var(--space-1); align-items: center;\"><button type=\"button\" class=\"ren-btn ren-btn-secondary ren-btn-sm\" style=\"width: 32px; height: 32px;\">−</button><input class=\"ren-input\" type=\"number\" value=\"3\" min=\"0\" max=\"99\" style=\"text-align: center; width: 80px;\"><button type=\"button\" class=\"ren-btn ren-btn-secondary ren-btn-sm\" style=\"width: 32px; height: 32px;\">+</button></div></label></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-number-field name=\"qty\" value=\"3\" min=\"0\" max=\"99\" step=\"1\"&gt;\n  &lt;label slot=\"label\"&gt;Quantity&lt;/label&gt;\n&lt;/ren-number-field&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><table class=\"dx-api dx-api-cols-4\"><thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">value</code></td><td><span class=\"dx-api-type\">number</span></td><td><span class=\"dx-api-default\">—</span></td><td>Current value. Submitted with the form.</td></tr><tr><td><code class=\"dx-api-name\">min</code> / <code class=\"dx-api-name\">max</code></td><td><span class=\"dx-api-type\">number</span></td><td><span class=\"dx-api-default\">—</span></td><td>Clamped on input and on stepper press.</td></tr><tr><td><code class=\"dx-api-name\">step</code></td><td><span class=\"dx-api-type\">number</span></td><td><span class=\"dx-api-default\">1</span></td><td>Stepper increment / decrement size.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li>The actual control is a real <code>&lt;input type=\"number\"&gt;</code> with native validation and form participation.</li><li>Stepper buttons have <code>aria-label=\"Increment\"</code> / <code>\"Decrement\"</code> and stay keyboard-reachable.</li><li>Arrow Up / Down on the input also steps by <code>step</code>.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-number-field.html",
      "id": "docs:docs/components/ren-number-field.html",
      "type": "docs_page",
      "name": "ren-number-field docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Input OTP — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\" aria-current=\"page\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Input OTP</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Input OTP <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">One-time-code input. Auto-advances on each digit, accepts paste, supports a custom mask (4 / 6 / 8 cells).</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>For 2FA / verification codes. Each digit gets its own visible cell so users can see exactly how many digits are expected. Paste the whole code at once and it distributes across cells.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\" style=\"gap: var(--space-2);\"><input type=\"text\" inputmode=\"numeric\" maxlength=\"1\" style=\"width: 40px; height: 48px; text-align: center; font-size: var(--text-xl); border: 1px solid var(--color-border); border-radius: var(--radius-sm);\"><input type=\"text\" inputmode=\"numeric\" maxlength=\"1\" style=\"width: 40px; height: 48px; text-align: center; font-size: var(--text-xl); border: 1px solid var(--color-border); border-radius: var(--radius-sm);\"><input type=\"text\" inputmode=\"numeric\" maxlength=\"1\" style=\"width: 40px; height: 48px; text-align: center; font-size: var(--text-xl); border: 1px solid var(--color-border); border-radius: var(--radius-sm);\"><span style=\"color: var(--color-text-muted);\">—</span><input type=\"text\" inputmode=\"numeric\" maxlength=\"1\" style=\"width: 40px; height: 48px; text-align: center; font-size: var(--text-xl); border: 1px solid var(--color-border); border-radius: var(--radius-sm);\"><input type=\"text\" inputmode=\"numeric\" maxlength=\"1\" style=\"width: 40px; height: 48px; text-align: center; font-size: var(--text-xl); border: 1px solid var(--color-border); border-radius: var(--radius-sm);\"><input type=\"text\" inputmode=\"numeric\" maxlength=\"1\" style=\"width: 40px; height: 48px; text-align: center; font-size: var(--text-xl); border: 1px solid var(--color-border); border-radius: var(--radius-sm);\"></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-otp length=\"6\" pattern=\"3-3\"&gt;&lt;/ren-otp&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><table class=\"dx-api dx-api-cols-4\"><thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">length</code></td><td><span class=\"dx-api-type\">number</span></td><td><span class=\"dx-api-default\">6</span></td><td>How many digits the code has.</td></tr><tr><td><code class=\"dx-api-name\">pattern</code></td><td><span class=\"dx-api-type\">string (e.g. \"3-3\")</span></td><td><span class=\"dx-api-default\">—</span></td><td>Visual grouping with a separator. <code>\"3-3\"</code> = three cells, dash, three cells.</td></tr><tr><td><code class=\"dx-api-name\">inputmode</code></td><td><span class=\"dx-api-type\">\"numeric\" | \"text\"</span></td><td><span class=\"dx-api-default\">\"numeric\"</span></td><td>Mobile keyboard hint.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li>Each cell is a real input — Tab moves between, paste splits, autocomplete <code>autocomplete=\"one-time-code\"</code> is set so iOS / Android suggest codes from SMS.</li><li>Wrap in a <code>&lt;label&gt;</code> for the whole group.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-otp.html",
      "id": "docs:docs/components/ren-otp.html",
      "type": "docs_page",
      "name": "ren-otp docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Pagination — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-direction: column; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-3); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-direction: column; gap: var(--space-2); align-items: center; }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\" aria-current=\"page\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Pagination</li></ol></nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Pagination <span class=\"dx-api-badge dx-api-badge-hybrid\" title=\"Renders without JS; JS adds enhancements\">Hybrid</span></h1>\n        <p class=\"lede\">Page navigation for paginated lists, search results, and tables. Numbered links with prev/next arrows; an optional simple variant collapses to \"Page N of M\".</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Pagination splits a long list into pages so users don't scroll forever. Use it for: search results, data tables, blog post archives, anywhere users navigate by page rather than by infinite scroll. The current page is marked with <code>aria-current=\"page\"</code>; prev/next are disabled at the boundaries.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Pagination vs infinite scroll.</strong> Use Pagination when users need to jump back (\"page 3\"), bookmark a position, or share a URL pointing at a specific page. Use infinite scroll for feed-like content where order rarely matters and users browse forward.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <nav class=\"ren-pagination\" aria-label=\"Search results pagination\">\n              <a class=\"ren-pagination-prev\" href=\"#\" aria-label=\"Previous page\">‹ Prev</a>\n              <a class=\"ren-pagination-item\" href=\"#\">1</a>\n              <a class=\"ren-pagination-item\" href=\"#\">2</a>\n              <a class=\"ren-pagination-item\" href=\"#\" aria-current=\"page\">3</a>\n              <a class=\"ren-pagination-item\" href=\"#\">4</a>\n              <a class=\"ren-pagination-item\" href=\"#\">5</a>\n              <span class=\"ren-pagination-ellipsis\" aria-hidden=\"true\">…</span>\n              <a class=\"ren-pagination-item\" href=\"#\">42</a>\n              <a class=\"ren-pagination-next\" href=\"#\" aria-label=\"Next page\">Next ›</a>\n            </nav>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;nav class=\"ren-pagination\" aria-label=\"Search results pagination\"&gt;\n  &lt;a class=\"ren-pagination-prev\" href=\"?page=2\" aria-label=\"Previous page\"&gt;‹ Prev&lt;/a&gt;\n  &lt;a class=\"ren-pagination-item\" href=\"?page=1\"&gt;1&lt;/a&gt;\n  &lt;a class=\"ren-pagination-item\" href=\"?page=2\"&gt;2&lt;/a&gt;\n  &lt;a class=\"ren-pagination-item\" href=\"?page=3\" aria-current=\"page\"&gt;3&lt;/a&gt;\n  &lt;a class=\"ren-pagination-item\" href=\"?page=4\"&gt;4&lt;/a&gt;\n  &lt;span class=\"ren-pagination-ellipsis\" aria-hidden=\"true\"&gt;…&lt;/span&gt;\n  &lt;a class=\"ren-pagination-item\" href=\"?page=42\"&gt;42&lt;/a&gt;\n  &lt;a class=\"ren-pagination-next\" href=\"?page=4\" aria-label=\"Next page\"&gt;Next ›&lt;/a&gt;\n&lt;/nav&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Shapes</p>\n        <h2>Variants</h2>\n        <div class=\"dx-vgrid\">\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Numbered</span><div class=\"dx-vrow-items\">\n            <nav class=\"ren-pagination\" aria-label=\"Numbered\"><a class=\"ren-pagination-prev\" href=\"#\">‹</a><a class=\"ren-pagination-item\" href=\"#\">1</a><a class=\"ren-pagination-item\" href=\"#\" aria-current=\"page\">2</a><a class=\"ren-pagination-item\" href=\"#\">3</a><a class=\"ren-pagination-next\" href=\"#\">›</a></nav>\n          </div></div>\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Simple</span><div class=\"dx-vrow-items\">\n            <nav class=\"ren-pagination ren-pagination-simple\" aria-label=\"Simple\"><a class=\"ren-pagination-prev\" href=\"#\">‹ Prev</a><span>Page 3 of 42</span><a class=\"ren-pagination-next\" href=\"#\">Next ›</a></nav>\n          </div></div>\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Boundary disabled</span><div class=\"dx-vrow-items\">\n            <nav class=\"ren-pagination\" aria-label=\"At first page\"><a class=\"ren-pagination-prev\" aria-disabled=\"true\">‹</a><a class=\"ren-pagination-item\" href=\"#\" aria-current=\"page\">1</a><a class=\"ren-pagination-item\" href=\"#\">2</a><a class=\"ren-pagination-item\" href=\"#\">3</a><a class=\"ren-pagination-next\" href=\"#\">›</a></nav>\n          </div></div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-pagination</code></td><td>Wrapping <code>&lt;nav&gt;</code>. Inline-flex row of links.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-pagination-item</code></td><td>One numbered page link. Square, 44 px touch target.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-pagination-prev</code> / <code class=\"dx-api-name\">-next</code></td><td>The previous / next arrow links.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-pagination-ellipsis</code></td><td>The \"…\" between distant page numbers. Decorative — mark <code>aria-hidden</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-pagination-simple</code></td><td>Compact variant — just prev / current label / next, for contexts with little room.</td></tr>\n            <tr><td><code class=\"dx-api-name\">[aria-current=\"page\"]</code></td><td>Marks the current page. Solid accent fill, not a link visually.</td></tr>\n            <tr><td><code class=\"dx-api-name\">[aria-disabled=\"true\"]</code></td><td>Marks prev/next at the boundaries. Lower opacity, not interactive.</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li><strong>Wrap in <code>&lt;nav aria-label=\"…\"&gt;</code></strong> with a label that describes what's being paginated (\"Search results pagination\").</li>\n          <li><strong>Mark the current page</strong> with <code>aria-current=\"page\"</code>. Don't link it.</li>\n          <li><strong>Boundary disable</strong>: at page 1, <code>prev</code> gets <code>aria-disabled=\"true\"</code> and no <code>href</code>. Same for <code>next</code> on the last page.</li>\n          <li><strong>Touch targets</strong>: each link is at least 44×44 px. Don't shrink them on mobile — small targets fail Fitts.</li>\n        </ul>\n      </section>\n\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n        <h3>Server-rendered pagination</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;nav class=\"ren-pagination\" aria-label=\"Posts pagination\"&gt;\n  &lt;a class=\"ren-pagination-prev\" href=\"/posts?page=&lt;%= currentPage - 1 %&gt;\"\n     &lt;%= currentPage === 1 ? 'aria-disabled=\"true\"' : '' %&gt;&gt;‹ Prev&lt;/a&gt;\n\n  &lt;% for (const p of visiblePages) { %&gt;\n    &lt;a class=\"ren-pagination-item\" href=\"/posts?page=&lt;%= p %&gt;\"\n       &lt;%= p === currentPage ? 'aria-current=\"page\"' : '' %&gt;&gt;&lt;%= p %&gt;&lt;/a&gt;\n  &lt;% } %&gt;\n\n  &lt;a class=\"ren-pagination-next\" href=\"/posts?page=&lt;%= currentPage + 1 %&gt;\"\n     &lt;%= currentPage === totalPages ? 'aria-disabled=\"true\"' : '' %&gt;&gt;Next ›&lt;/a&gt;\n&lt;/nav&gt;</code></div>\n      </section>\n\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-pagination.html",
      "id": "docs:docs/components/ren-pagination.html",
      "type": "docs_page",
      "name": "ren-pagination docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Popover — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: visible; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-8) var(--space-5); display: flex; gap: var(--space-3); align-items: center; justify-content: center; min-height: 160px; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .demo-pop { position: relative; }\n    .demo-pop-content { position: absolute; top: calc(100% + 8px); left: 0; min-width: 240px; padding: var(--space-4); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); box-shadow: var(--shadow-lg); z-index: 5; }\n    .demo-pop-content[hidden] { display: none; }\n    .demo-filter-group { min-inline-size: 0; margin: 0; padding: 0; border: 0; }\n    .demo-filter-legend { margin-bottom: var(--space-3); font-size: var(--text-sm); font-weight: var(--weight-semibold); }\n    .demo-filter-options { --stack-gap: var(--space-3); }\n    .demo-pop-actions { justify-content: flex-end; }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\" aria-current=\"page\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Popover</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Popover <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">A floating panel anchored to a trigger. Holds richer content than a Tooltip — small forms, action menus, settings panels — with full keyboard navigation. Built on the native Popover API where supported.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Popovers open on click (not hover) and stay open until the user dismisses them. Use them for short forms, mini settings panels, or contextual choices that don't warrant a full Dialog. The trigger is always a button; the content can be anything keyboard-friendly.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Popover vs Dialog vs Tooltip.</strong> Tooltip = passive hint on hover. Popover = small interactive panel anchored to a trigger. Dialog = modal that blocks the rest of the page.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <div class=\"demo-pop\" id=\"popover-demo\">\n              <button class=\"ren-btn ren-btn-secondary\" type=\"button\" aria-haspopup=\"dialog\" aria-expanded=\"false\" aria-controls=\"popover-demo-content\">Filters</button>\n              <div class=\"demo-pop-content ren-stack\" id=\"popover-demo-content\" role=\"dialog\" aria-label=\"Filters\" hidden>\n                <fieldset class=\"demo-filter-group\">\n                  <legend class=\"demo-filter-legend\">Filter posts</legend>\n                  <div class=\"ren-stack demo-filter-options\">\n                    <label class=\"ren-checkbox\"><input type=\"checkbox\" checked><span class=\"ren-checkbox-control\"></span><span>Drafts</span></label>\n                    <label class=\"ren-checkbox\"><input type=\"checkbox\"><span class=\"ren-checkbox-control\"></span><span>Archived</span></label>\n                  </div>\n                </fieldset>\n                <div class=\"ren-row demo-pop-actions\">\n                  <button class=\"ren-btn ren-btn-ghost ren-btn-sm\" type=\"button\" data-close>Cancel</button>\n                  <button class=\"ren-btn ren-btn-primary ren-btn-sm\" type=\"button\" data-close>Apply</button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-popover&gt;\n  &lt;button slot=\"trigger\"&gt;Filters&lt;/button&gt;\n  &lt;div slot=\"content\" role=\"dialog\" aria-label=\"Filters\"&gt;\n    &lt;!-- form, checkboxes, etc. --&gt;\n  &lt;/div&gt;\n&lt;/ren-popover&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-popover</code></td><td>The floating panel. Card-like surface, shadow, padding.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-popover-arrow</code></td><td>Optional arrow pointing at the trigger.</td></tr>\n          </tbody>\n        </table>\n        <h3>Web Component attributes</h3>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">placement</code></td><td><span class=\"dx-api-type\">\"top\" | \"right\" | \"bottom\" | \"left\"</span></td><td><span class=\"dx-api-default\">\"bottom\"</span></td><td>Side relative to the trigger; auto-flips when there isn't room.</td></tr>\n            <tr><td><code class=\"dx-api-name\">open</code></td><td><span class=\"dx-api-type\">boolean</span></td><td><span class=\"dx-api-default\">false</span></td><td>Programmatically toggle open state.</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li>Trigger has <code>aria-haspopup=\"dialog\"</code> and <code>aria-expanded</code> kept in sync.</li>\n          <li>Open: focus moves to the first focusable element inside the popover.</li>\n          <li>Close: focus returns to the trigger.</li>\n          <li><kbd>Esc</kbd> closes the popover. Click outside also closes.</li>\n        </ul>\n      </section>\n\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n        <h3>Quick filters panel</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;ren-popover&gt;\n  &lt;button slot=\"trigger\" class=\"ren-btn ren-btn-secondary\"&gt;Filters&lt;/button&gt;\n  &lt;form slot=\"content\"&gt;…&lt;/form&gt;\n&lt;/ren-popover&gt;</code></div>\n      </section>\n\n    </main>\n  </div>\n  <script>\n    // Vanilla popover toggle for the demo\n    document.querySelectorAll('.demo-pop').forEach(root => {\n      const trigger = root.querySelector('button');\n      const content = root.querySelector('.demo-pop-content');\n      const open = () => { content.hidden = false; trigger.setAttribute('aria-expanded', 'true'); content.querySelector('input,button')?.focus(); };\n      const close = () => { content.hidden = true; trigger.setAttribute('aria-expanded', 'false'); trigger.focus(); };\n      trigger.addEventListener('click', () => content.hidden ? open() : close());\n      content.querySelectorAll('[data-close]').forEach(b => b.addEventListener('click', close));\n      document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !content.hidden) close(); });\n      document.addEventListener('click', (e) => { if (!root.contains(e.target) && !content.hidden) close(); });\n    });\n  </script>\n  <script type=\"module\" src=\"../../components/composites/ren-popover/ren-popover.js\"></script>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-popover.html",
      "id": "docs:docs/components/ren-popover.html",
      "type": "docs_page",
      "name": "ren-popover docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Progress — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-3); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\" aria-current=\"page\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Progress</li></ol></nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Progress <span class=\"dx-api-badge dx-api-badge-css\" title=\"Works without any JavaScript\">CSS-only</span></h1>\n        <p class=\"lede\">Bar that fills from 0 to a max value. Determinate (you know the percentage) or indeterminate (continuous animation when the duration is unknown).</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>Use Progress when a long task has measurable progress: file upload, multi-step wizard, video buffering. For short waits where you don't know the duration, use a <a href=\"ren-spinner.html\" class=\"ren-link\">Spinner</a> instead.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\" style=\"flex-direction: column; align-items: stretch; gap: var(--space-3); width: 100%;\"><div><label style=\"font-size: var(--text-sm);\">Uploading… 60%</label><div class=\"ren-progress\" role=\"progressbar\" aria-valuenow=\"60\" aria-valuemin=\"0\" aria-valuemax=\"100\"><div class=\"ren-progress-bar\" style=\"width: 60%;\"></div></div></div><div><label style=\"font-size: var(--text-sm);\">Saving…</label><div class=\"ren-progress ren-progress-indeterminate\" role=\"progressbar\" aria-label=\"Loading\"></div></div></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div class=\"ren-progress\" role=\"progressbar\"\n     aria-valuenow=\"60\" aria-valuemin=\"0\" aria-valuemax=\"100\"&gt;\n  &lt;div class=\"ren-progress-bar\" style=\"width: 60%\"&gt;&lt;/div&gt;\n&lt;/div&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><table class=\"dx-api\"><thead><tr><th>Class</th><th>Effect</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">.ren-progress</code></td><td>The track. Use with <code>role=\"progressbar\"</code>.</td></tr><tr><td><code class=\"dx-api-name\">.ren-progress-bar</code></td><td>The filled portion. Set <code>width: NN%</code> or use the data attribute on the parent.</td></tr><tr><td><code class=\"dx-api-name\">.ren-progress-indeterminate</code></td><td>Continuous-loop animation when progress is unknown.</td></tr><tr><td><code class=\"dx-api-name\">.ren-progress-success</code> / <code class=\"dx-api-name\">-warning</code> / <code class=\"dx-api-name\">-danger</code></td><td>Status colors for the bar.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li>Always set <code>role=\"progressbar\"</code> on the track.</li><li>For determinate: <code>aria-valuenow</code>, <code>aria-valuemin</code>, <code>aria-valuemax</code>.</li><li>For indeterminate: omit <code>aria-valuenow</code>; add <code>aria-label</code>.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-progress.html",
      "id": "docs:docs/components/ren-progress.html",
      "type": "docs_page",
      "name": "ren-progress docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Radio — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-direction: column; gap: var(--space-3); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-4); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-direction: column; gap: var(--space-3); }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n  </style>\n</head>\n<body>\n\n  <header class=\"dx-nav\">\n    <div class=\"dx-nav-inner\">\n      <a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a>\n      <nav class=\"dx-nav-menu\" aria-label=\"Primary\">\n        <a href=\"../index.html\">Docs</a>\n        <a href=\"../components.html\" aria-current=\"page\">Components</a>\n        <a href=\"../../templates/index.html\">Templates</a>\n        <a href=\"../../create/index.html\">Theme Builder</a>\n      </nav>\n      <div class=\"dx-nav-actions\">\n        <a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a>\n        <a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\" aria-current=\"page\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\">\n          <ol>\n            <li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li>\n            <li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li>\n            <li aria-current=\"page\">Radio</li>\n          </ol>\n        </nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Radio <span class=\"dx-api-badge dx-api-badge-hybrid\" title=\"Renders without JS; JS adds enhancements\">Hybrid</span></h1>\n        <p class=\"lede\">Mutually-exclusive choice from a small set of options. Always grouped under a shared <code>name</code>; selecting one deselects the others. Same custom-styled-control / native-input pattern as Checkbox.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>A radio group lets the user pick exactly one option from 2–5 alternatives. Past 5, switch to a <a href=\"ren-select.html\" class=\"ren-link\">Select</a> dropdown — radios start to feel cramped and hard to scan. Each radio is a real <code>&lt;input type=\"radio\"&gt;</code>, so arrow-key navigation between them works out of the box.</p>\n        <div class=\"dx-callout\">\n          <p><strong>All radios in a group share the same <code>name</code>.</strong> Without it, each radio behaves as its own group — selecting one doesn't deselect the others, and only the last one submits. Wrap them in a <code>fieldset</code> with a <code>legend</code> for the group's label.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <fieldset style=\"border: 0; padding: 0; margin: 0;\">\n              <legend style=\"font-size: var(--label-size); font-weight: var(--label-weight); margin-bottom: var(--space-3);\">How would you like to receive notifications?</legend>\n              <div class=\"ren-radio-group\">\n                <label class=\"ren-radio\"><input type=\"radio\" name=\"demo-notif\" value=\"all\" checked><span class=\"ren-radio-control\"></span><span>All updates</span></label>\n                <label class=\"ren-radio\"><input type=\"radio\" name=\"demo-notif\" value=\"mentions\"><span class=\"ren-radio-control\"></span><span>Mentions only</span></label>\n                <label class=\"ren-radio\"><input type=\"radio\" name=\"demo-notif\" value=\"none\"><span class=\"ren-radio-control\"></span><span>Nothing</span></label>\n              </div>\n            </fieldset>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;fieldset&gt;\n  &lt;legend&gt;How would you like to receive notifications?&lt;/legend&gt;\n  &lt;div class=\"ren-radio-group\"&gt;\n    &lt;label class=\"ren-radio\"&gt;\n      &lt;input type=\"radio\" name=\"notif\" value=\"all\" checked&gt;\n      &lt;span class=\"ren-radio-control\"&gt;&lt;/span&gt;\n      &lt;span&gt;All updates&lt;/span&gt;\n    &lt;/label&gt;\n    &lt;label class=\"ren-radio\"&gt;\n      &lt;input type=\"radio\" name=\"notif\" value=\"mentions\"&gt;\n      &lt;span class=\"ren-radio-control\"&gt;&lt;/span&gt;\n      &lt;span&gt;Mentions only&lt;/span&gt;\n    &lt;/label&gt;\n    &lt;label class=\"ren-radio\"&gt;\n      &lt;input type=\"radio\" name=\"notif\" value=\"none\"&gt;\n      &lt;span class=\"ren-radio-control\"&gt;&lt;/span&gt;\n      &lt;span&gt;Nothing&lt;/span&gt;\n    &lt;/label&gt;\n  &lt;/div&gt;\n&lt;/fieldset&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Layouts</p>\n        <h2>Variants</h2>\n        <div class=\"dx-vgrid\">\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Vertical</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-radio-group\">\n                <label class=\"ren-radio\"><input type=\"radio\" name=\"v-demo\" value=\"a\" checked><span class=\"ren-radio-control\"></span><span>Option A</span></label>\n                <label class=\"ren-radio\"><input type=\"radio\" name=\"v-demo\" value=\"b\"><span class=\"ren-radio-control\"></span><span>Option B</span></label>\n                <label class=\"ren-radio\"><input type=\"radio\" name=\"v-demo\" value=\"c\"><span class=\"ren-radio-control\"></span><span>Option C</span></label>\n              </div>\n            </div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Horizontal</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-radio-group ren-radio-group-horizontal\" style=\"display: flex; gap: var(--space-4); flex-wrap: wrap;\">\n                <label class=\"ren-radio\"><input type=\"radio\" name=\"h-demo\" value=\"s\" checked><span class=\"ren-radio-control\"></span><span>Small</span></label>\n                <label class=\"ren-radio\"><input type=\"radio\" name=\"h-demo\" value=\"m\"><span class=\"ren-radio-control\"></span><span>Medium</span></label>\n                <label class=\"ren-radio\"><input type=\"radio\" name=\"h-demo\" value=\"l\"><span class=\"ren-radio-control\"></span><span>Large</span></label>\n              </div>\n            </div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Disabled</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-radio-group\">\n                <label class=\"ren-radio\"><input type=\"radio\" name=\"d-demo\" value=\"a\" checked disabled><span class=\"ren-radio-control\"></span><span>Selected & disabled</span></label>\n                <label class=\"ren-radio\"><input type=\"radio\" name=\"d-demo\" value=\"b\" disabled><span class=\"ren-radio-control\"></span><span>Unselected & disabled</span></label>\n              </div>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <h3>CSS classes</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-radio</code></td><td>Single radio. The label wrapper around <code>&lt;input type=\"radio\"&gt;</code> + <code>.ren-radio-control</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-radio-control</code></td><td>The painted circle. Fills with the accent dot when its sibling input is checked.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-radio-group</code></td><td>Container for multiple radios. Default is vertical stack with consistent spacing.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-radio-group-horizontal</code></td><td>Lays the group out as a wrap-friendly horizontal row.</td></tr>\n          </tbody>\n        </table>\n        <p>No JavaScript or Web Component. The browser handles the radio-group selection logic via the shared <code>name</code> attribute.</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <p>Radios are one of the platform's most accessible form controls — when grouped correctly, the browser handles the rest.</p>\n        <h3>Keyboard</h3>\n        <div class=\"dx-keys\">\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Tab</kbd></span><span>Moves focus to the currently selected radio in the group (or the first radio if none is selected).</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>↑</kbd> / <kbd>↓</kbd> / <kbd>←</kbd> / <kbd>→</kbd></span><span>Move focus AND selection to the next radio in the group. Skips disabled radios.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Space</kbd></span><span>Selects the focused radio (rarely needed — arrow keys already select).</span></div>\n        </div>\n        <h3>Group labelling</h3>\n        <p>Always wrap a radio group in a <code>&lt;fieldset&gt;</code> with a <code>&lt;legend&gt;</code>. Screen readers announce the legend before each option, so users hear the question and the answer in context.</p>\n        <div class=\"dx-callout\">\n          <p><strong>One radio is not enough.</strong> A single radio with no siblings is meaningless — it can't be turned off. Use a Checkbox or Switch instead.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n        <h3>Choosing a plan</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;fieldset&gt;\n  &lt;legend&gt;Choose a plan&lt;/legend&gt;\n  &lt;div class=\"ren-radio-group\"&gt;\n    &lt;label class=\"ren-radio\"&gt;\n      &lt;input type=\"radio\" name=\"plan\" value=\"basic\" checked&gt;\n      &lt;span class=\"ren-radio-control\"&gt;&lt;/span&gt;\n      &lt;span&gt;\n        &lt;strong&gt;Basic&lt;/strong&gt; — $9/mo, 1 project\n      &lt;/span&gt;\n    &lt;/label&gt;\n    &lt;label class=\"ren-radio\"&gt;\n      &lt;input type=\"radio\" name=\"plan\" value=\"pro\"&gt;\n      &lt;span class=\"ren-radio-control\"&gt;&lt;/span&gt;\n      &lt;span&gt;\n        &lt;strong&gt;Pro&lt;/strong&gt; — $29/mo, unlimited\n      &lt;/span&gt;\n    &lt;/label&gt;\n  &lt;/div&gt;\n&lt;/fieldset&gt;</code></pre>\n      </section>\n\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-radio.html",
      "id": "docs:docs/components/ren-radio.html",
      "type": "docs_page",
      "name": "ren-radio docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Scroll Area — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .demo-scroll { width: 280px; height: 200px; overflow-y: auto; border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-3); background: var(--color-surface); margin-inline: auto; scrollbar-width: thin; scrollbar-color: var(--color-border) transparent; }\n    .demo-scroll::-webkit-scrollbar { width: 8px; }\n    .demo-scroll::-webkit-scrollbar-thumb { background: var(--color-border); border-radius: 4px; }\n    .demo-scroll::-webkit-scrollbar-thumb:hover { background: var(--color-border-strong); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\" aria-current=\"page\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Scroll Area</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Scroll Area <span class=\"dx-api-badge dx-api-badge-hybrid\" title=\"Renders without JS; JS adds enhancements\">Hybrid</span></h1>\n        <p class=\"lede\">A scrollable region with branded thin scrollbars on platforms that support custom styling. For lists inside menus, sidebars inside dialogs, anywhere the scrollbar would otherwise be ugly or oversized.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Native scrollbars look fine on macOS (where they're auto-hidden) but stand out on Windows and Linux. ScrollArea provides a thin, branded scrollbar that matches the design system on every platform. Use it inside containers — never on the document root, where the platform default belongs.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Don't replace the page-level scrollbar.</strong> The browser's main scrollbar is part of the platform. Custom scrollbars belong inside scrollable containers, not around the whole document.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <div class=\"demo-scroll ren-scroll-area\">\n              <p style=\"margin: 0; font-size: var(--text-sm); line-height: 1.6;\">Item 1 — Lorem ipsum dolor sit amet.</p>\n              <p style=\"font-size: var(--text-sm); line-height: 1.6;\">Item 2 — Consectetur adipiscing elit.</p>\n              <p style=\"font-size: var(--text-sm); line-height: 1.6;\">Item 3 — Sed do eiusmod tempor incididunt ut labore.</p>\n              <p style=\"font-size: var(--text-sm); line-height: 1.6;\">Item 4 — Ut enim ad minim veniam.</p>\n              <p style=\"font-size: var(--text-sm); line-height: 1.6;\">Item 5 — Quis nostrud exercitation ullamco laboris.</p>\n              <p style=\"font-size: var(--text-sm); line-height: 1.6;\">Item 6 — Nisi ut aliquip ex ea commodo consequat.</p>\n              <p style=\"font-size: var(--text-sm); line-height: 1.6;\">Item 7 — Duis aute irure dolor in reprehenderit.</p>\n              <p style=\"font-size: var(--text-sm); line-height: 1.6;\">Item 8 — In voluptate velit esse cillum dolore.</p>\n              <p style=\"font-size: var(--text-sm); line-height: 1.6;\">Item 9 — Eu fugiat nulla pariatur.</p>\n              <p style=\"font-size: var(--text-sm); line-height: 1.6;\">Item 10 — Excepteur sint occaecat cupidatat non proident.</p>\n            </div>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div class=\"ren-scroll-area\" style=\"height: 200px;\"&gt;\n  &lt;!-- Tall content --&gt;\n&lt;/div&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-scroll-area</code></td><td>Apply on any container with <code>overflow-y: auto</code> (or <code>overflow-x</code>). Sets a thin scrollbar styled with system colors.</td></tr>\n          </tbody>\n        </table>\n        <p>The class adjusts <code>scrollbar-width</code> and <code>::-webkit-scrollbar</code> consistently. No JavaScript needed.</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li>Native scrolling — keyboard, mouse wheel, touch, and screen-reader gestures all work without extra wiring.</li>\n          <li>Mark scrollable regions with <code>tabindex=\"0\"</code> when they don't already have a focusable child, so keyboard users can scroll with arrow keys.</li>\n          <li>Don't hide the scrollbar entirely — it's the only visual hint that more content exists.</li>\n        </ul>\n      </section>\n\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-scroll-area.html",
      "id": "docs:docs/components/ren-scroll-area.html",
      "type": "docs_page",
      "name": "ren-scroll-area docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Select — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    /* Demo block */\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: visible; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-4); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-direction: column; gap: var(--space-3); align-items: flex-start; }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n    .dx-anatomy { margin: var(--space-4) 0; display: grid; gap: var(--space-4); }\n    .dx-anatomy-num { display: inline-grid; place-items: center; width: 22px; height: 22px; border-radius: 50%; background: var(--color-text); color: var(--color-surface); font-size: var(--text-xs); font-weight: var(--weight-bold); flex-shrink: 0; }\n    .dx-anatomy-stage { background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-6) var(--space-5); }\n    .dx-anatomy-stage-label { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); font-weight: var(--weight-semibold); margin: 0 0 var(--space-3); }\n    .dx-anatomy-parts { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-3); }\n    @media (max-width: 720px) { .dx-anatomy-parts { grid-template-columns: 1fr; } }\n    .dx-anatomy-part { background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-4); display: grid; gap: var(--space-2); }\n    .dx-anatomy-part-header { display: flex; align-items: center; gap: var(--space-2); }\n    .dx-anatomy-part-name { font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--color-text); }\n    .dx-anatomy-part-desc { font-size: var(--text-sm); line-height: 1.5; color: var(--color-text-secondary); margin: 0; }\n    .dx-anatomy-part-desc code { font-size: 0.85em; }\n\n    /* Mock select preview for anatomy */\n    .demo-select { position: relative; width: 240px; }\n    .demo-select-trigger {\n      display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);\n      width: 100%; min-height: 40px;\n      padding: var(--space-2) var(--space-3);\n      background: var(--color-input-bg); color: var(--color-text);\n      border: 1px solid var(--color-input-border); border-radius: var(--radius-md);\n      cursor: pointer; font: inherit; font-size: var(--body-size);\n      transition: border-color 150ms;\n    }\n    .demo-select-trigger:hover { border-color: var(--color-border-strong); }\n    .demo-select-trigger[aria-expanded=\"true\"] {\n      border-color: var(--color-accent);\n      box-shadow: 0 0 0 3px var(--color-accent-subtle);\n    }\n    .demo-select-list {\n      list-style: none; margin: 0; padding: var(--space-1);\n      position: absolute; top: calc(100% + var(--space-1)); inset-inline: 0;\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border); border-radius: var(--radius-md);\n      box-shadow: var(--shadow-lg);\n      z-index: 5;\n    }\n    .demo-select-list[hidden] { display: none; }\n    .demo-select-item {\n      display: flex; align-items: center; gap: var(--space-2);\n      padding: var(--space-2) var(--space-3);\n      border-radius: var(--radius-sm);\n      cursor: pointer; font-size: var(--text-sm);\n    }\n    .demo-select-item:hover, .demo-select-item[data-highlighted] { background: var(--color-fill); }\n    .demo-select-item[aria-selected=\"true\"] { color: var(--color-accent); font-weight: var(--weight-medium); }\n  </style>\n</head>\n<body>\n\n  <header class=\"dx-nav\">\n    <div class=\"dx-nav-inner\">\n      <a href=\"../index.html\" class=\"dx-brand\">\n        <span class=\"dx-brand-mark\">R</span><span>RenDS</span>\n        <span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span>\n      </a>\n      <nav class=\"dx-nav-menu\" aria-label=\"Primary\">\n        <a href=\"../index.html\">Docs</a>\n        <a href=\"../components.html\" aria-current=\"page\">Components</a>\n        <a href=\"../../templates/index.html\">Templates</a>\n        <a href=\"../../create/index.html\">Theme Builder</a>\n      </nav>\n      <div class=\"dx-nav-actions\">\n        <a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a>\n        <a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\" aria-current=\"page\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\">\n          <ol>\n            <li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li>\n            <li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li>\n            <li aria-current=\"page\">Select</li>\n          </ol>\n        </nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Select <span class=\"dx-api-badge dx-api-badge-hybrid\" title=\"Renders without JS; JS adds enhancements\">Hybrid</span></h1>\n        <p class=\"lede\">A custom-styled dropdown for choosing one option from a list. Renders a trigger button and a popover list using the native Popover API where supported. Keyboard-accessible by default — typeahead, arrow keys, Home/End, Escape — and fully WAI-ARIA listbox compliant.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Use a Select when the options need richer rendering than a native <code>&lt;select&gt;</code> can offer — icons, descriptions, group separators, custom layouts. For plain text options under ~10 items in a form, prefer the native element via <a href=\"ren-field.html\" class=\"ren-link\">Form Field</a>; the platform's behavior is hard to beat for accessibility and mobile pickers.</p>\n        <div class=\"dx-callout\">\n          <p><strong>The trigger is a button, not an input.</strong> Selects don't accept typed input; they show a list and let the user pick. If you need free-text + suggestions, use <a href=\"ren-combobox.html\" class=\"ren-link\">Combobox</a> instead.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"anatomy\">\n        <p class=\"dx-kicker\">Parts</p>\n        <h2>Anatomy</h2>\n        <p>A Select has three parts in the DOM: the trigger that's always visible, the listbox content that floats on open, and the items inside.</p>\n        <div class=\"dx-anatomy\">\n          <div class=\"dx-anatomy-stage\">\n            <p class=\"dx-anatomy-stage-label\">Assembled</p>\n            <div style=\"display: flex; justify-content: center;\">\n              <div class=\"demo-select\" data-anatomy>\n                <button type=\"button\" class=\"demo-select-trigger\" aria-haspopup=\"listbox\" aria-expanded=\"false\">\n                  <span>Argentina</span>\n                  <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"6 9 12 15 18 9\"/></svg>\n                </button>\n              </div>\n            </div>\n          </div>\n          <div class=\"dx-anatomy-parts\">\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\"><span class=\"dx-anatomy-num\">1</span><span class=\"dx-anatomy-part-name\">Container</span></div>\n              <p class=\"dx-anatomy-part-desc\"><code>&lt;ren-select&gt;</code> wrapper. Holds trigger + content. Owns selection state and exposes a <code>name</code>/<code>value</code> API for forms.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\"><span class=\"dx-anatomy-num\">2</span><span class=\"dx-anatomy-part-name\">Trigger</span></div>\n              <p class=\"dx-anatomy-part-desc\"><code>.ren-select-trigger</code> button with <code>aria-haspopup=\"listbox\"</code>. Shows the selected value or a placeholder. Click or Enter opens the list.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\"><span class=\"dx-anatomy-num\">3</span><span class=\"dx-anatomy-part-name\">Content</span></div>\n              <p class=\"dx-anatomy-part-desc\"><code>.ren-select-content</code> with <code>role=\"listbox\"</code>. Floats on open via the Popover API. Closes on Escape, click outside, or selection.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\"><span class=\"dx-anatomy-num\">4</span><span class=\"dx-anatomy-part-name\">Item</span></div>\n              <p class=\"dx-anatomy-part-desc\"><code>.ren-select-item</code> with <code>role=\"option\"</code> and <code>data-value</code>. Optional <code>data-highlighted</code> for keyboard focus, <code>aria-selected</code> for the active choice.</p>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <p>Click the trigger to open. Use Up/Down to navigate, Enter to select, Escape to close.</p>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <div class=\"demo-select\" id=\"select-demo\">\n              <button type=\"button\" class=\"demo-select-trigger\" aria-haspopup=\"listbox\" aria-expanded=\"false\" aria-controls=\"select-demo-list\">\n                <span data-value-display>Select a country</span>\n                <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"6 9 12 15 18 9\"/></svg>\n              </button>\n              <ul class=\"demo-select-list\" id=\"select-demo-list\" role=\"listbox\" hidden>\n                <li class=\"demo-select-item\" role=\"option\" data-value=\"ar\">Argentina</li>\n                <li class=\"demo-select-item\" role=\"option\" data-value=\"br\">Brazil</li>\n                <li class=\"demo-select-item\" role=\"option\" data-value=\"cl\">Chile</li>\n                <li class=\"demo-select-item\" role=\"option\" data-value=\"mx\">Mexico</li>\n                <li class=\"demo-select-item\" role=\"option\" data-value=\"us\">United States</li>\n              </ul>\n            </div>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-select name=\"country\" placeholder=\"Select a country\"&gt;\n  &lt;button class=\"ren-select-trigger\"&gt;\n    &lt;span class=\"ren-select-value\"&gt;&lt;/span&gt;\n    &lt;span class=\"ren-select-icon\"&gt;…&lt;/span&gt;\n  &lt;/button&gt;\n  &lt;div class=\"ren-select-content\" popover&gt;\n    &lt;div class=\"ren-select-item\" data-value=\"ar\"&gt;Argentina&lt;/div&gt;\n    &lt;div class=\"ren-select-item\" data-value=\"br\"&gt;Brazil&lt;/div&gt;\n    &lt;div class=\"ren-select-item\" data-value=\"cl\"&gt;Chile&lt;/div&gt;\n    &lt;div class=\"ren-select-item\" data-value=\"mx\"&gt;Mexico&lt;/div&gt;\n    &lt;div class=\"ren-select-item\" data-value=\"us\"&gt;United States&lt;/div&gt;\n  &lt;/div&gt;\n&lt;/ren-select&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Shapes</p>\n        <h2>Variants</h2>\n        <div class=\"dx-vgrid\">\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Sizes</span>\n            <div class=\"dx-vrow-items\">\n              <p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Three sizes via <code>size=\"sm|md|lg\"</code> on <code>&lt;ren-select&gt;</code> — generates <code>.ren-select-sm</code>, <code>-md</code> (default), or <code>-lg</code>. The trigger height matches the matching <code>.ren-input</code> size so selects line up with text fields in the same form.</p>\n            </div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">With icon</span>\n            <div class=\"dx-vrow-items\">\n              <p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Each <code>.ren-select-item</code> can hold any markup — an icon, a description, a badge. The component just routes selection by <code>data-value</code>.</p>\n            </div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">With description</span>\n            <div class=\"dx-vrow-items\">\n              <p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Add a secondary line per item with <code>.ren-select-item-description</code>. Useful for Plan pickers (name + price) or Locale pickers (name + native script).</p>\n            </div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Disabled</span>\n            <div class=\"dx-vrow-items\">\n              <p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Add <code>disabled</code> on the trigger to lock the whole field. <code>aria-disabled</code> on individual <code>.ren-select-item</code> elements skips them in keyboard navigation.</p>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n\n        <h3>CSS classes</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-select</code></td><td>Wrapper. Establishes the relative positioning context for the popover.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-select-trigger</code></td><td>The button users see. Should be a real <code>&lt;button type=\"button\"&gt;</code>. Holds <code>aria-haspopup=\"listbox\"</code> and <code>aria-expanded</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-select-value</code></td><td>The slot where the selected option's display text appears.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-select-placeholder</code></td><td>Replaces <code>.ren-select-value</code> styling when nothing is selected — muted color.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-select-icon</code></td><td>The chevron at the trigger's end. Rotates 180° while the listbox is open.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-select-content</code></td><td>The floating list. Use with <code>popover</code> attribute and <code>role=\"listbox\"</code>. Auto-positioned by the component.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-select-item</code></td><td>One option. Use with <code>role=\"option\"</code> and a <code>data-value</code>. <code>aria-selected=\"true\"</code> marks the chosen one.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-select-sm</code> / <code class=\"dx-api-name\">-lg</code></td><td>Size modifiers. Default trigger is medium (44 px touch target).</td></tr>\n          </tbody>\n        </table>\n\n        <h3>Web Component attributes</h3>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">name</code></td><td><span class=\"dx-api-type\">string</span></td><td><span class=\"dx-api-default\">—</span></td><td>Form field name. Submitted via a hidden input so the value travels with the form.</td></tr>\n            <tr><td><code class=\"dx-api-name\">value</code></td><td><span class=\"dx-api-type\">string</span></td><td><span class=\"dx-api-default\">—</span></td><td>Initial selected value. Must match a <code>data-value</code> on one of the items.</td></tr>\n            <tr><td><code class=\"dx-api-name\">placeholder</code></td><td><span class=\"dx-api-type\">string</span></td><td><span class=\"dx-api-default\">\"Select an option\"</span></td><td>Trigger text when nothing is selected.</td></tr>\n            <tr><td><code class=\"dx-api-name\">placement</code></td><td><span class=\"dx-api-type\">\"top\" | \"right\" | \"bottom\" | \"left\"</span></td><td><span class=\"dx-api-default\">\"bottom\"</span></td><td>Preferred side for the listbox. Reflected to <code>data-side</code>; may flip when viewport space requires it.</td></tr>\n            <tr><td><code class=\"dx-api-name\">size</code></td><td><span class=\"dx-api-type\">\"sm\" | \"md\" | \"lg\"</span></td><td><span class=\"dx-api-default\">\"md\"</span></td><td>Visual size variant.</td></tr>\n            <tr><td><code class=\"dx-api-name\">disabled</code></td><td><span class=\"dx-api-type\">boolean</span></td><td><span class=\"dx-api-default\">false</span></td><td>Prevents opening. Adds <code>aria-disabled</code>.</td></tr>\n          </tbody>\n        </table>\n\n        <h3>JavaScript API</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Member</th><th>Description</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">value</code></td><td>Getter / setter. Reads or assigns the selected value. Setting triggers <code>ren-change</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">selectedItem</code></td><td>Getter. The currently selected <code>.ren-select-item</code> element (or null).</td></tr>\n            <tr><td><code class=\"dx-api-name\">open()</code> / <code class=\"dx-api-name\">close()</code></td><td>Programmatically toggle the listbox.</td></tr>\n            <tr><td><code class=\"dx-api-name\">isOpen</code></td><td>Boolean getter. True while the listbox is visible.</td></tr>\n          </tbody>\n        </table>\n\n        <h3>Events</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Event</th><th>Detail</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">ren-change</code></td><td>Fires when the selection changes. <code>event.detail</code>: <code>{ value, item }</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">ren-open</code> / <code class=\"dx-api-name\">ren-close</code></td><td>Fire when the listbox opens or closes. No detail.</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <p>Implements the WAI-ARIA Listbox pattern via the trigger + popover combination.</p>\n        <h3>Keyboard</h3>\n        <div class=\"dx-keys\">\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Space</kbd> / <kbd>Enter</kbd></span><span>On the trigger: opens the listbox. On a list item: selects it and closes.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>↑</kbd> / <kbd>↓</kbd></span><span>Move highlight through the items. Wraps at the ends.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Home</kbd> / <kbd>End</kbd></span><span>Jump to the first or last item.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\">Type to filter</span><span>Typeahead — types build a buffer that highlights the first matching item. Buffer clears after 500 ms idle.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Esc</kbd></span><span>Closes the listbox without changing the selection. Focus returns to the trigger.</span></div>\n        </div>\n        <h3>Labelling</h3>\n        <p>Wrap the Select inside a <a href=\"ren-field.html\" class=\"ren-link\">Form Field</a> so the label, description and error get auto-wired. Or set <code>aria-label</code> directly on the trigger if there's no visible label.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Avoid putting too many items in one Select.</strong> Past ~15, switch to a <a href=\"ren-combobox.html\" class=\"ren-link\">Combobox</a> with search. Typeahead helps but doesn't scale to hundreds.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n\n        <h3>Inside a Form Field</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;ren-field&gt;\n  &lt;label&gt;Country&lt;/label&gt;\n  &lt;ren-select name=\"country\" placeholder=\"Select your country\"&gt;\n    &lt;button class=\"ren-select-trigger\"&gt;\n      &lt;span class=\"ren-select-value\"&gt;&lt;/span&gt;\n      &lt;span class=\"ren-select-icon\"&gt;…&lt;/span&gt;\n    &lt;/button&gt;\n    &lt;div class=\"ren-select-content\" popover&gt;\n      &lt;div class=\"ren-select-item\" data-value=\"ar\"&gt;Argentina&lt;/div&gt;\n      &lt;div class=\"ren-select-item\" data-value=\"br\"&gt;Brazil&lt;/div&gt;\n    &lt;/div&gt;\n  &lt;/ren-select&gt;\n&lt;/ren-field&gt;</code></pre>\n\n        <h3>React to selection</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>document.querySelector('ren-select')\n  .addEventListener('ren-change', (e) =&gt; {\n    console.log('Selected', e.detail.value);\n    updatePreview(e.detail.value);\n  });</code></pre>\n\n        <h3>Items with icons + descriptions</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;div class=\"ren-select-item\" data-value=\"basic\"&gt;\n  &lt;span class=\"ren-select-item-icon\"&gt;&lt;!-- icon SVG --&gt;&lt;/span&gt;\n  &lt;span&gt;\n    &lt;strong&gt;Basic&lt;/strong&gt;\n    &lt;span class=\"ren-select-item-description\"&gt;$9 per month&lt;/span&gt;\n  &lt;/span&gt;\n&lt;/div&gt;</code></pre>\n      </section>\n\n    </main>\n  </div>\n\n  <script type=\"module\" src=\"../../components/composites/ren-select/ren-select.js\"></script>\n  <script>\n    // Demo wiring — vanilla, works without the Web Component.\n    document.querySelectorAll('.demo-select').forEach(root => {\n      const trigger = root.querySelector('.demo-select-trigger');\n      const list = root.querySelector('.demo-select-list');\n      const display = root.querySelector('[data-value-display]');\n      if (!trigger || !list) return;\n\n      const items = list.querySelectorAll('.demo-select-item');\n      let highlightIdx = -1;\n\n      const open = () => {\n        list.hidden = false;\n        trigger.setAttribute('aria-expanded', 'true');\n        const selected = list.querySelector('[aria-selected=\"true\"]');\n        highlightIdx = selected ? Array.from(items).indexOf(selected) : 0;\n        highlight(highlightIdx);\n      };\n      const close = () => {\n        list.hidden = true;\n        trigger.setAttribute('aria-expanded', 'false');\n        items.forEach(i => i.removeAttribute('data-highlighted'));\n      };\n      const highlight = (i) => {\n        items.forEach((it, idx) => it.toggleAttribute('data-highlighted', idx === i));\n      };\n      const select = (item) => {\n        items.forEach(i => i.removeAttribute('aria-selected'));\n        item.setAttribute('aria-selected', 'true');\n        if (display) display.textContent = item.textContent.trim();\n        close();\n        trigger.focus();\n      };\n\n      trigger.addEventListener('click', () => list.hidden ? open() : close());\n      items.forEach(item => item.addEventListener('click', () => select(item)));\n\n      trigger.addEventListener('keydown', (e) => {\n        if ((e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') && list.hidden) {\n          e.preventDefault();\n          open();\n        }\n      });\n      list.addEventListener('keydown', (e) => {\n        if (e.key === 'Escape') { e.preventDefault(); close(); trigger.focus(); }\n        else if (e.key === 'ArrowDown') { e.preventDefault(); highlightIdx = (highlightIdx + 1) % items.length; highlight(highlightIdx); }\n        else if (e.key === 'ArrowUp')   { e.preventDefault(); highlightIdx = (highlightIdx - 1 + items.length) % items.length; highlight(highlightIdx); }\n        else if (e.key === 'Home')      { e.preventDefault(); highlightIdx = 0; highlight(highlightIdx); }\n        else if (e.key === 'End')       { e.preventDefault(); highlightIdx = items.length - 1; highlight(highlightIdx); }\n        else if (e.key === 'Enter')     { e.preventDefault(); if (highlightIdx >= 0) select(items[highlightIdx]); }\n      });\n      list.tabIndex = -1;\n\n      // Click outside closes\n      document.addEventListener('click', (e) => {\n        if (!root.contains(e.target) && !list.hidden) close();\n      });\n\n      // Make sure the list is keyboard reachable when open\n      const observer = new MutationObserver(() => { if (!list.hidden) list.focus(); });\n      observer.observe(list, { attributes: true, attributeFilter: ['hidden'] });\n    });\n  </script>\n\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-select.html",
      "id": "docs:docs/components/ren-select.html",
      "type": "docs_page",
      "name": "ren-select docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Separator — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-3); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\" aria-current=\"page\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Separator</li></ol></nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Separator <span class=\"dx-api-badge dx-api-badge-css\" title=\"Works without any JavaScript\">CSS-only</span></h1>\n        <p class=\"lede\">Visual divider between sections. Plain horizontal rule, optional center label, vertical orientation for inline groups.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>Use a separator when whitespace alone isn't enough to break up content — for example between unrelated form sections, or to mark \"or\" between two distinct paths.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\" style=\"flex-direction: column; align-items: stretch; gap: var(--space-4);\"><div><p style=\"margin: 0 0 var(--space-2); font-size: var(--text-sm);\">Above</p><hr class=\"ren-divider\"><p style=\"margin: var(--space-2) 0 0; font-size: var(--text-sm);\">Below</p></div><div class=\"ren-divider\">Or continue with</div></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;hr class=\"ren-divider\"&gt;\n\n&lt;div class=\"ren-divider\"&gt;Or continue with&lt;/div&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><table class=\"dx-api\"><thead><tr><th>Class</th><th>Effect</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">.ren-divider</code></td><td>On <code>&lt;hr&gt;</code> for a plain horizontal divider, or on a <code>&lt;div&gt;</code> with text for a labelled divider.</td></tr><tr><td><code class=\"dx-api-name\">.ren-divider-vertical</code></td><td>Vertical 1 px line. Use inside flex rows to separate inline items.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><p>Use the semantic <code>&lt;hr&gt;</code> tag. Screen readers announce it as \"separator\". For a div-based labelled divider, add <code>role=\"separator\"</code>.</p></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-separator.html",
      "id": "docs:docs/components/ren-separator.html",
      "type": "docs_page",
      "name": "ren-separator docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Sheet — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-8) var(--space-5); display: flex; gap: var(--space-3); align-items: center; justify-content: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\" aria-current=\"page\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Sheet</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Sheet <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">An edge-anchored panel that slides in from a side of the screen. The mobile-first equivalent of a Drawer — bottom sheet on phones, side sheet on desktops, your call.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Sheets carry secondary surfaces — filter panels, navigation menus, comment threads — without leaving the page. They're like Dialog but visually attached to an edge instead of floating in the center, which feels more grounded on touch.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Sheet vs Dialog vs Sidebar.</strong> Sidebar is permanent on wide screens. Dialog is centered modal. Sheet is dismissible from an edge — best when you don't want the sidebar to take permanent room but the user opens it often.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <button class=\"ren-btn ren-btn-secondary\" type=\"button\" data-sheet-trigger=\"demo-right-sheet\">Open right sheet</button>\n            <button class=\"ren-btn ren-btn-secondary\" type=\"button\" data-sheet-trigger=\"demo-bottom-sheet\">Open bottom sheet</button>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-sheet side=\"right\" id=\"filters\"&gt;\n  &lt;header class=\"ren-sheet-header\"&gt;\n    &lt;h2 class=\"ren-sheet-title\"&gt;Filters&lt;/h2&gt;\n    &lt;button class=\"ren-sheet-close\" data-sheet-close aria-label=\"Close\"&gt;×&lt;/button&gt;\n  &lt;/header&gt;\n  &lt;div class=\"ren-sheet-body\"&gt;…&lt;/div&gt;\n&lt;/ren-sheet&gt;\n\n&lt;button data-sheet-trigger=\"filters\"&gt;Open&lt;/button&gt;</code></pre>\n        </div>\n\n        <!-- Live demo sheets -->\n        <ren-sheet side=\"right\" size=\"md\" id=\"demo-right-sheet\">\n          <header class=\"ren-sheet-header\">\n            <h2 class=\"ren-sheet-title\">Filters</h2>\n            <button type=\"button\" class=\"ren-sheet-close\" data-sheet-close aria-label=\"Close\">×</button>\n          </header>\n          <div class=\"ren-sheet-body\">\n            <p>Adjust filters to refine the results.</p>\n            <p style=\"margin-top: var(--space-3);\"><label><input type=\"checkbox\"> Only verified</label></p>\n            <p><label><input type=\"checkbox\"> In stock</label></p>\n            <p><label><input type=\"checkbox\"> Free shipping</label></p>\n          </div>\n          <footer class=\"ren-sheet-footer\">\n            <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-sheet-close>Cancel</button>\n            <button type=\"button\" class=\"ren-btn ren-btn-primary\" data-sheet-close>Apply</button>\n          </footer>\n        </ren-sheet>\n\n        <ren-sheet side=\"bottom\" size=\"md\" id=\"demo-bottom-sheet\">\n          <div class=\"ren-sheet-handle\"></div>\n          <header class=\"ren-sheet-header\">\n            <h2 class=\"ren-sheet-title\">Share</h2>\n            <button type=\"button\" class=\"ren-sheet-close\" data-sheet-close aria-label=\"Close\">×</button>\n          </header>\n          <div class=\"ren-sheet-body\">\n            <p>Pick where to share this link.</p>\n          </div>\n        </ren-sheet>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-sheet</code></td><td>The panel itself. Surface, padding, scrolls if content overflows.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-sheet-overlay</code></td><td>The dimmed backdrop behind the sheet.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-sheet-handle</code></td><td>Optional drag handle indicator (used on bottom sheets).</td></tr>\n          </tbody>\n        </table>\n        <h3>Web Component attributes</h3>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">side</code></td><td><span class=\"dx-api-type\">\"right\" | \"left\" | \"top\" | \"bottom\"</span></td><td><span class=\"dx-api-default\">\"right\"</span></td><td>Edge the sheet slides from.</td></tr>\n            <tr><td><code class=\"dx-api-name\">size</code></td><td><span class=\"dx-api-type\">\"sm\" | \"md\" | \"lg\" | \"full\"</span></td><td><span class=\"dx-api-default\">\"md\"</span></td><td>Width (for left/right) or height (for top/bottom).</td></tr>\n            <tr><td><code class=\"dx-api-name\">open</code></td><td><span class=\"dx-api-type\">boolean</span></td><td><span class=\"dx-api-default\">false</span></td><td>Programmatic open / close.</td></tr>\n            <tr><td><code class=\"dx-api-name\">dismissible</code></td><td><span class=\"dx-api-type\">boolean</span></td><td><span class=\"dx-api-default\">true</span></td><td>If false, only the close button can dismiss (no backdrop click, no Esc).</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li>Sheet is a modal: focus trapped inside while open; <kbd>Esc</kbd> closes; backdrop click closes.</li>\n          <li>Use <code>role=\"dialog\"</code> with <code>aria-labelledby</code> pointing at the sheet's heading.</li>\n          <li>On open, focus moves to the first focusable element inside (or the close button).</li>\n          <li>On close, focus returns to the trigger.</li>\n        </ul>\n      </section>\n\n    </main>\n  </div>\n  <script type=\"module\" src=\"../../components/composites/ren-sheet/ren-sheet.js\"></script>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-sheet.html",
      "id": "docs:docs/components/ren-sheet.html",
      "type": "docs_page",
      "name": "ren-sheet docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Sidebar — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\" aria-current=\"page\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Sidebar</li></ol></nav>\n        <p class=\"dx-kicker\">Pattern</p>\n        <h1>Sidebar <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">Left navigation column for app shells. Collapsible sections, active-route indicator, footer for account / sign-out. The post-login surface that runs every workspace app.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>Sidebars present persistent navigation in admin / dashboard apps. They give users a stable mental map of \"what's in this app\". Stay narrow (240–280 px), collapsible to icons-only on tight screens, scroll independently from the main content.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\" style=\"display: block; padding: 0; height: 280px;\"><div style=\"display: grid; grid-template-columns: 220px 1fr; height: 100%; background: var(--color-surface);\"><aside class=\"ren-sidebar\" style=\"border-right: 1px solid var(--color-border); padding: var(--space-3); display: flex; flex-direction: column; gap: var(--space-1);\"><strong style=\"font-size: var(--text-sm); padding: var(--space-1) var(--space-2);\">Acme</strong><div style=\"margin-top: var(--space-2); display: flex; flex-direction: column; gap: 2px;\"><a href=\"#\" style=\"padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); background: var(--color-fill); font-size: var(--text-sm); color: var(--color-text); text-decoration: none; font-weight: var(--weight-medium);\">Dashboard</a><a href=\"#\" style=\"padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); font-size: var(--text-sm); color: var(--color-text-muted); text-decoration: none;\">Projects</a><a href=\"#\" style=\"padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); font-size: var(--text-sm); color: var(--color-text-muted); text-decoration: none;\">Reports</a><a href=\"#\" style=\"padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); font-size: var(--text-sm); color: var(--color-text-muted); text-decoration: none;\">Settings</a></div></aside><main style=\"padding: var(--space-3); color: var(--color-text-muted); font-size: var(--text-sm);\">Main content</main></div></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-sidebar aria-label=\"Workspace\"&gt;\n  &lt;a class=\"ren-sidebar-brand\" href=\"/\"&gt;Acme&lt;/a&gt;\n  &lt;ul class=\"ren-sidebar-section\"&gt;\n    &lt;li&gt;&lt;a aria-current=\"page\"&gt;Dashboard&lt;/a&gt;&lt;/li&gt;\n    &lt;li&gt;&lt;a&gt;Projects&lt;/a&gt;&lt;/li&gt;\n  &lt;/ul&gt;\n&lt;/ren-sidebar&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><table class=\"dx-api\"><thead><tr><th>Class</th><th>Effect</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">.ren-sidebar</code></td><td>The aside container. Use with <code>aria-label</code>.</td></tr><tr><td><code class=\"dx-api-name\">.ren-sidebar-brand</code></td><td>Top brand / logo link.</td></tr><tr><td><code class=\"dx-api-name\">.ren-sidebar-section</code></td><td>One group of links. Optional <code>.ren-sidebar-section-title</code> as a heading.</td></tr><tr><td><code class=\"dx-api-name\">.ren-sidebar-footer</code></td><td>Bottom-pinned slot for account menu, version info.</td></tr><tr><td><code class=\"dx-api-name\">.ren-sidebar-collapsed</code></td><td>State class for icon-only mode.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li>Always <code>&lt;aside&gt;</code> with descriptive <code>aria-label</code>.</li><li>Mark active route with <code>aria-current=\"page\"</code>.</li><li>On mobile (sub-720 px), the sidebar becomes a Sheet triggered from the Nav.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-sidebar.html",
      "id": "docs:docs/components/ren-sidebar.html",
      "type": "docs_page",
      "name": "ren-sidebar docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Skeleton — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-4); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n\n    /* Demo skeleton primitives */\n    .demo-sk { background: var(--color-fill); border-radius: var(--radius-sm); animation: skeleton-pulse 1.5s ease-in-out infinite; }\n    .demo-sk-line { height: 12px; }\n    .demo-sk-line-sm { height: 10px; }\n    .demo-sk-circle { border-radius: 50%; width: 40px; height: 40px; flex-shrink: 0; }\n    .demo-sk-rect { aspect-ratio: 16 / 9; }\n    @keyframes skeleton-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }\n    @media (prefers-reduced-motion: reduce) { .demo-sk { animation: none; } }\n\n    .demo-card-mock { display: flex; gap: var(--space-3); align-items: flex-start; padding: var(--space-4); border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-surface); max-width: 420px; }\n    .demo-card-mock .lines { flex: 1; display: flex; flex-direction: column; gap: var(--space-2); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\" aria-current=\"page\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\">\n          <ol>\n            <li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li>\n            <li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li>\n            <li aria-current=\"page\">Skeleton</li>\n          </ol>\n        </nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Skeleton <span class=\"dx-api-badge dx-api-badge-css\" title=\"Works without any JavaScript\">CSS-only</span></h1>\n        <p class=\"lede\">Placeholder shapes that mimic the layout of content while it loads. They pulse subtly so the page feels alive instead of broken.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Use a skeleton when content is on its way and showing real shapes — line widths, avatar circles, image rectangles — helps the user predict what's coming. For shorter waits inside a button or a small panel, prefer <a href=\"ren-spinner.html\" class=\"ren-link\">Spinner</a>.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Match the real layout, not the loading state.</strong> A skeleton that doesn't look like the final UI introduces a second, jarring layout shift when the data arrives. Mirror the spacing, the line count, and the avatar size of the real component.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <div class=\"demo-card-mock\">\n              <div class=\"ren-skeleton demo-sk demo-sk-circle\" aria-hidden=\"true\"></div>\n              <div class=\"lines\">\n                <div class=\"ren-skeleton demo-sk demo-sk-line\" style=\"width: 70%;\" aria-hidden=\"true\"></div>\n                <div class=\"ren-skeleton demo-sk demo-sk-line-sm\" style=\"width: 90%;\" aria-hidden=\"true\"></div>\n                <div class=\"ren-skeleton demo-sk demo-sk-line-sm\" style=\"width: 60%;\" aria-hidden=\"true\"></div>\n              </div>\n            </div>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div class=\"card-mock\"&gt;\n  &lt;div class=\"ren-skeleton ren-skeleton-circle\"&gt;&lt;/div&gt;\n  &lt;div&gt;\n    &lt;div class=\"ren-skeleton ren-skeleton-line\" style=\"width: 70%\"&gt;&lt;/div&gt;\n    &lt;div class=\"ren-skeleton ren-skeleton-line-sm\" style=\"width: 90%\"&gt;&lt;/div&gt;\n    &lt;div class=\"ren-skeleton ren-skeleton-line-sm\" style=\"width: 60%\"&gt;&lt;/div&gt;\n  &lt;/div&gt;\n&lt;/div&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Shapes</p>\n        <h2>Variants</h2>\n        <div class=\"dx-vgrid\">\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Line</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-skeleton demo-sk demo-sk-line\" style=\"width: 100%; margin-bottom: var(--space-2);\" aria-hidden=\"true\"></div>\n              <div class=\"ren-skeleton demo-sk demo-sk-line\" style=\"width: 80%; margin-bottom: var(--space-2);\" aria-hidden=\"true\"></div>\n              <div class=\"ren-skeleton demo-sk demo-sk-line\" style=\"width: 60%;\" aria-hidden=\"true\"></div>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Circle</span>\n            <div class=\"dx-vrow-items\" style=\"display: flex; gap: var(--space-3); align-items: center;\">\n              <div class=\"ren-skeleton demo-sk demo-sk-circle\" style=\"width: 24px; height: 24px;\" aria-hidden=\"true\"></div>\n              <div class=\"ren-skeleton demo-sk demo-sk-circle\" style=\"width: 40px; height: 40px;\" aria-hidden=\"true\"></div>\n              <div class=\"ren-skeleton demo-sk demo-sk-circle\" style=\"width: 64px; height: 64px;\" aria-hidden=\"true\"></div>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Rectangle</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-skeleton demo-sk demo-sk-rect\" style=\"max-width: 280px;\" aria-hidden=\"true\"></div>\n            </div>\n          </div>\n\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-skeleton</code></td><td>Base placeholder. Pulsing background, fully rounded corners on small heights.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-skeleton-line</code></td><td>Text line. 12 px tall. Combine with <code>style=\"width: 70%\"</code> to vary line lengths and avoid the \"stack of identical bars\" look.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-skeleton-line-sm</code></td><td>Smaller line, 10 px tall. For body copy under headings.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-skeleton-circle</code></td><td>Avatar / icon placeholder. Use inline width/height to match the real avatar size.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-skeleton-rect</code></td><td>Image / hero placeholder. Pair with <code>aspect-ratio</code> to lock it to the final image proportions.</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li>Mark each skeleton element <code>aria-hidden=\"true\"</code>. Loading shapes should not be announced piece by piece.</li>\n          <li>The container that swaps in the real content should have <code>aria-busy=\"true\"</code> while loading and <code>aria-live=\"polite\"</code> if the change is significant.</li>\n          <li>Pulse animation respects <code>prefers-reduced-motion</code> — the animation stops automatically when the user opts out.</li>\n          <li>Don't show a skeleton forever. If a request is slow, swap to a clearer empty state or error message after a few seconds.</li>\n        </ul>\n      </section>\n\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-skeleton.html",
      "id": "docs:docs/components/ren-skeleton.html",
      "type": "docs_page",
      "name": "ren-skeleton docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Slider — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-direction: column; gap: var(--space-3); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    input[type=\"range\"].ren-slider { width: 100%; }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\" aria-current=\"page\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Slider</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Slider <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">Range input for numeric values: volume, opacity, brightness, price ranges. Built on the native <code>&lt;input type=\"range\"&gt;</code> with a styled track and thumb. Single-handle by default; dual handle for ranges.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Sliders are the right control when the user picks a value from a continuous (or finely stepped) range. Use them when precision is approximate — opacity, font size, brightness. For exact numbers, use a Number Field. For 2-3 options, use a Toggle Group.</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <label style=\"display: flex; flex-direction: column; gap: var(--space-1); font-size: var(--text-sm);\">\n              <span style=\"display: flex; justify-content: space-between;\"><span>Volume</span><output id=\"vol-out\">60</output></span>\n              <input type=\"range\" class=\"ren-slider\" min=\"0\" max=\"100\" value=\"60\" oninput=\"document.getElementById('vol-out').value = this.value\">\n            </label>\n            <label style=\"display: flex; flex-direction: column; gap: var(--space-1); font-size: var(--text-sm);\">\n              <span style=\"display: flex; justify-content: space-between;\"><span>Brightness</span><output id=\"br-out\">80%</output></span>\n              <input type=\"range\" class=\"ren-slider\" min=\"0\" max=\"100\" value=\"80\" oninput=\"document.getElementById('br-out').value = this.value + '%'\">\n            </label>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;label&gt;\n  &lt;span&gt;Volume&lt;/span&gt;\n  &lt;input type=\"range\" class=\"ren-slider\" min=\"0\" max=\"100\" value=\"60\"&gt;\n&lt;/label&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class / attribute</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-slider</code></td><td>Apply on a native <code>&lt;input type=\"range\"&gt;</code>. Styles the track and thumb consistently across browsers.</td></tr>\n            <tr><td><code class=\"dx-api-name\">min</code> / <code class=\"dx-api-name\">max</code> / <code class=\"dx-api-name\">step</code></td><td>Native attributes. <code>step</code> lets you snap to discrete values (e.g. <code>step=\"5\"</code>).</td></tr>\n            <tr><td><code class=\"dx-api-name\">value</code></td><td>Current value. Read it via <code>input.valueAsNumber</code>.</td></tr>\n          </tbody>\n        </table>\n        <h3>Dual-handle (range)</h3>\n        <p>For \"from–to\" ranges, use <code>&lt;ren-slider type=\"range\"&gt;</code> — the Web Component renders two thumbs and exposes <code>value</code> as a tuple.</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li>Native <code>&lt;input type=\"range\"&gt;</code> already announces \"slider, value 60 of 100\".</li>\n          <li>Keyboard works out of the box: arrow keys step ±1, Page Up / Page Down step ±10, Home / End jump to min / max.</li>\n          <li>Always pair with a <code>&lt;label&gt;</code> describing what's being adjusted.</li>\n          <li>Show the current value visibly (<code>&lt;output&gt;</code> bound to the slider's <code>id</code>) — sliders without a numeric readout are hard to use precisely.</li>\n        </ul>\n      </section>\n\n    </main>\n  </div>\n  <script type=\"module\" src=\"../../components/composites/ren-slider/ren-slider.js\"></script>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-slider.html",
      "id": "docs:docs/components/ren-slider.html",
      "type": "docs_page",
      "name": "ren-slider docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Spinner — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-3); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\" aria-current=\"page\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Spinner</li></ol></nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Spinner <span class=\"dx-api-badge dx-api-badge-css\" title=\"Works without any JavaScript\">CSS-only</span></h1>\n        <p class=\"lede\">Loading indicator. Five sizes, plus a light variant for dark surfaces. Pure CSS — no JavaScript.</p>\n      </header>\n      <section class=\"dx-section\" id=\"overview\"><p class=\"dx-kicker\">About</p><h2>Overview</h2><p>Show a spinner when a button or panel is processing and the user can't see the result yet. For longer waits or while content is loading inline, prefer <a href=\"ren-skeleton.html\" class=\"ren-link\">Skeleton</a>.</p></section>\n      <section class=\"dx-section\" id=\"demo\"><p class=\"dx-kicker\">Live</p><h2>Demo</h2><div class=\"dx-demo\"><div class=\"dx-demo-preview\"><div class=\"ren-spinner ren-spinner-xs\"></div><div class=\"ren-spinner ren-spinner-sm\"></div><div class=\"ren-spinner\"></div><div class=\"ren-spinner ren-spinner-lg\"></div><div class=\"ren-spinner ren-spinner-xl\"></div></div><pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;div class=\"ren-spinner\"&gt;&lt;/div&gt;\n&lt;div class=\"ren-spinner ren-spinner-lg\"&gt;&lt;/div&gt;\n&lt;div class=\"ren-spinner ren-spinner-light\"&gt;&lt;/div&gt; &lt;!-- on dark bg --&gt;</code></pre></div></section>\n      <section class=\"dx-section\" id=\"api\"><p class=\"dx-kicker\">Reference</p><h2>API</h2><table class=\"dx-api\"><thead><tr><th>Class</th><th>Effect</th></tr></thead><tbody><tr><td><code class=\"dx-api-name\">.ren-spinner</code></td><td>Base. 24 px circular spinner.</td></tr><tr><td><code class=\"dx-api-name\">.ren-spinner-xs</code> / <code class=\"dx-api-name\">-sm</code> / <code class=\"dx-api-name\">-lg</code> / <code class=\"dx-api-name\">-xl</code></td><td>Size variants. 12 / 16 / 32 / 40 px.</td></tr><tr><td><code class=\"dx-api-name\">.ren-spinner-light</code></td><td>White spinner for dark backgrounds.</td></tr></tbody></table></section>\n      <section class=\"dx-section\" id=\"a11y\"><p class=\"dx-kicker\">Inclusive by default</p><h2>Accessibility</h2><ul><li>The container should have <code>role=\"status\"</code> and a screen-reader-only label: <code>&lt;span class=\"ren-sr-only\"&gt;Loading…&lt;/span&gt;</code>.</li><li>Respects <code>prefers-reduced-motion</code> — the spin slows or stops under the user's preference.</li></ul></section>\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-spinner.html",
      "id": "docs:docs/components/ren-spinner.html",
      "type": "docs_page",
      "name": "ren-spinner docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Switch — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-direction: column; gap: var(--space-3); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-4); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-direction: column; gap: var(--space-3); }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n  </style>\n</head>\n<body>\n\n  <header class=\"dx-nav\">\n    <div class=\"dx-nav-inner\">\n      <a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a>\n      <nav class=\"dx-nav-menu\" aria-label=\"Primary\">\n        <a href=\"../index.html\">Docs</a>\n        <a href=\"../components.html\" aria-current=\"page\">Components</a>\n        <a href=\"../../templates/index.html\">Templates</a>\n        <a href=\"../../create/index.html\">Theme Builder</a>\n      </nav>\n      <div class=\"dx-nav-actions\">\n        <a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a>\n        <a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\" aria-current=\"page\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\">\n          <ol>\n            <li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li>\n            <li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li>\n            <li aria-current=\"page\">Switch</li>\n          </ol>\n        </nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Switch <span class=\"dx-api-badge dx-api-badge-hybrid\" title=\"Renders without JS; JS adds enhancements\">Hybrid</span></h1>\n        <p class=\"lede\">A two-state toggle for settings that take effect immediately. Same underlying <code>&lt;input type=\"checkbox\"&gt;</code> as Checkbox, different visual metaphor and different mental model — flip a switch, the change happens now.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Switches differ from checkboxes in <em>when</em> their state is committed: a switch toggles the setting the moment you flip it, no Save button required. Use them for settings panels, theme toggles, feature flags, and notification preferences. Checkboxes go inside forms; switches go inside settings.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Pair the switch with the label, never standalone.</strong> A switch with no surrounding label leaves users guessing what's being toggled. Place the label on the left, the switch on the right.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <label class=\"ren-switch\" style=\"display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);\">\n              <span>Email notifications</span>\n              <span style=\"display: inline-flex; align-items: center; gap: var(--space-2);\">\n                <input type=\"checkbox\" checked>\n                <span class=\"ren-switch-track\"></span>\n              </span>\n            </label>\n            <label class=\"ren-switch\" style=\"display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);\">\n              <span>Beta features</span>\n              <span style=\"display: inline-flex; align-items: center; gap: var(--space-2);\">\n                <input type=\"checkbox\">\n                <span class=\"ren-switch-track\"></span>\n              </span>\n            </label>\n            <label class=\"ren-switch\" style=\"display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); opacity: 0.6;\">\n              <span>Analytics tracking (managed by your admin)</span>\n              <span style=\"display: inline-flex; align-items: center; gap: var(--space-2);\">\n                <input type=\"checkbox\" disabled>\n                <span class=\"ren-switch-track\"></span>\n              </span>\n            </label>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;label class=\"ren-switch\"&gt;\n  &lt;input type=\"checkbox\" checked&gt;\n  &lt;span class=\"ren-switch-track\"&gt;&lt;/span&gt;\n  &lt;span&gt;Email notifications&lt;/span&gt;\n&lt;/label&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">States</p>\n        <h2>Variants</h2>\n        <div class=\"dx-vgrid\">\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">States</span>\n            <div class=\"dx-vrow-items\">\n              <label class=\"ren-switch\"><input type=\"checkbox\"><span class=\"ren-switch-track\"></span><span>Off</span></label>\n              <label class=\"ren-switch\"><input type=\"checkbox\" checked><span class=\"ren-switch-track\"></span><span>On</span></label>\n              <label class=\"ren-switch\"><input type=\"checkbox\" disabled><span class=\"ren-switch-track\"></span><span>Disabled (off)</span></label>\n              <label class=\"ren-switch\"><input type=\"checkbox\" checked disabled><span class=\"ren-switch-track\"></span><span>Disabled (on)</span></label>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <h3>CSS classes</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-switch</code></td><td>The label wrapper. Inline-flex with the input + track + text label.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-switch-track</code></td><td>The painted toggle. Background fills with the accent color when the input is checked; the thumb slides via <code>transform</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-switch &gt; input[type=\"checkbox\"]</code></td><td>The native input. Visually hidden but fully accessible. Submits with the form using the input's <code>name</code>.</td></tr>\n          </tbody>\n        </table>\n        <p>No JavaScript or Web Component. Pure CSS adjacent-sibling selectors do the work.</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <p>Native checkbox semantics. Screen readers announce \"checked\" / \"not checked\" — they don't say \"switch\" by default, which is fine. If you specifically need the \"switch\" role announced, add <code>role=\"switch\"</code> on the input.</p>\n        <h3>Keyboard</h3>\n        <div class=\"dx-keys\">\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Tab</kbd></span><span>Moves focus to the switch.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Space</kbd></span><span>Toggles on / off.</span></div>\n        </div>\n        <div class=\"dx-callout\">\n          <p><strong>Don't trigger destructive actions on toggle.</strong> A switch represents a state change, not a confirmation. If flipping the switch causes data loss, use a Checkbox + Save button or a confirmation Dialog instead.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n        <h3>Settings row</h3>\n        <p>Common pattern: label on the left, switch on the right, optional helper text below.</p>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;label class=\"ren-switch settings-row\"&gt;\n  &lt;span&gt;\n    &lt;strong&gt;Two-factor authentication&lt;/strong&gt;\n    &lt;span class=\"settings-row-help\"&gt;Adds an extra step at sign-in.&lt;/span&gt;\n  &lt;/span&gt;\n  &lt;span&gt;\n    &lt;input type=\"checkbox\" checked&gt;\n    &lt;span class=\"ren-switch-track\"&gt;&lt;/span&gt;\n  &lt;/span&gt;\n&lt;/label&gt;</code></pre>\n\n        <h3>Persist on change</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>document.querySelectorAll('.ren-switch input').forEach(input =&gt; {\n  input.addEventListener('change', async () =&gt; {\n    await fetch('/api/settings', {\n      method: 'PATCH',\n      body: JSON.stringify({ [input.name]: input.checked })\n    });\n    showToast('Saved.');\n  });\n});</code></pre>\n      </section>\n\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-switch.html",
      "id": "docs:docs/components/ren-switch.html",
      "type": "docs_page",
      "name": "ren-switch docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Data Table — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    /* Page-specific styles — most chrome and table visuals come from\n       components/patterns/ren-table/ren-table.css */\n    .dx-demo {\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n      overflow: hidden;\n      margin: var(--space-4) 0;\n    }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-5); }\n    .dx-demo-code   { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n\n    /* Variant rows */\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow  { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label {\n      font-size: var(--text-xs);\n      font-weight: var(--weight-semibold);\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--color-text-muted);\n      padding-top: var(--space-3);\n    }\n    .dx-vrow-items {\n      padding: var(--space-3);\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n    }\n    @media (max-width: 720px) {\n      .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); }\n      .dx-vrow-label { padding-top: 0; }\n    }\n\n    /* Demo tables need their own container width */\n    .dx-table-demo ren-table,\n    .dx-table-demo .ren-table-wrapper { width: 100%; }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\">\n    <div class=\"dx-nav-inner\">\n      <a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a>\n      <nav class=\"dx-nav-menu\" aria-label=\"Primary\">\n        <a href=\"../index.html\">Docs</a>\n        <a href=\"../components.html\" aria-current=\"page\">Components</a>\n        <a href=\"../../templates/index.html\">Templates</a>\n        <a href=\"../../create/index.html\">Theme Builder</a>\n      </nav>\n      <div class=\"dx-nav-actions\">\n        <a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a>\n        <a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\" aria-current=\"page\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n      </ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Data Table</li></ol></nav>\n        <p class=\"dx-kicker\">Pattern</p>\n        <h1>Data Table <span class=\"dx-api-badge dx-api-badge-hybrid\" title=\"Renders without JS; JS adds enhancements\">Hybrid</span></h1>\n        <p class=\"lede\">A comprehensive, accessible <code>&lt;table&gt;</code> with sortable headers, multi-select rows, sticky header and pinned columns, density variants, integrated client-side pagination, search/filter toolbar, empty state, and a loading shimmer. The workhorse of every CRUD admin.</p>\n      </header>\n\n      <!-- 1. OVERVIEW -->\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Data Table is the answer to \"I have a list and the user is going to scan, sort, filter, and act on rows.\" It is built on real semantic <code>&lt;table&gt;</code> — never a grid of divs — so every screen reader, every keyboard, and every assistive technology already knows what to do.</p>\n        <p>Without JavaScript you get a clean styled table. The <code>&lt;ren-table&gt;</code> custom element layers on sorting, selection (including shift-click ranges), pagination, search filtering, column resize, sticky/pinned columns, and density variants. You opt in to each feature by adding the matching attribute or markup — there is no monolithic config.</p>\n\n        <h3>When to use</h3>\n        <ul>\n          <li>Tabular data the user will sort, filter, or scan (invoices, users, orders, logs).</li>\n          <li>Datasets where selecting one-or-many rows triggers actions (delete, export, bulk-edit).</li>\n          <li>Lists big enough to need pagination but small enough to fit in memory client-side.</li>\n        </ul>\n\n        <h3>When not to use</h3>\n        <ul>\n          <li><strong>Cards / tiles instead of rows.</strong> If each item has a thumbnail and 2-3 lines of body, use a <a href=\"ren-card.html\" class=\"ren-link\">Card</a> grid.</li>\n          <li><strong>Tree / hierarchical view.</strong> Reach for a tree component (out of scope for v0.9).</li>\n          <li><strong>2-3 key/value pairs per item.</strong> Use a <code>&lt;dl&gt;</code> description list.</li>\n          <li><strong>A single metric.</strong> Use a stat / KPI card.</li>\n          <li><strong>Server-paged datasets bigger than memory.</strong> The built-in pager is client-side; use an external pager that fetches and replaces the table rows instead of relying on <code>data-page-size</code>.</li>\n        </ul>\n\n        <div class=\"dx-callout\">\n          <p><strong>Real <code>&lt;table&gt;</code>, semantically.</strong> The <code>&lt;ren-table&gt;</code> custom element wraps a native <code>&lt;table&gt;</code> in light DOM. Headers stay <code>&lt;th&gt;</code>, rows stay <code>&lt;tr&gt;</code>, cells stay <code>&lt;td&gt;</code>. Sort state, selection, and pagination all map to standard ARIA (<code>aria-sort</code>, <code>aria-selected</code>, <code>aria-live</code>) — you're not opting out of the platform.</p>\n        </div>\n      </section>\n\n      <!-- 2. ANATOMY -->\n      <section class=\"dx-section\" id=\"anatomy\">\n        <p class=\"dx-kicker\">Structure</p>\n        <h2>Anatomy</h2>\n        <p>A full-featured Data Table has five regions. All but the <code>&lt;table&gt;</code> itself are optional — drop them and the table still works.</p>\n        <ol>\n          <li><strong>Toolbar</strong> (<code>.ren-table-toolbar</code>) — top bar with search input (<code>[data-table-search]</code>) and bulk actions.</li>\n          <li><strong>Wrapper</strong> (<code>.ren-table-wrapper</code>) — horizontal scroll container. Required for sticky / pinned to anchor against.</li>\n          <li><strong>Header</strong> (<code>.ren-table-header</code> on <code>&lt;thead&gt;</code>) — sticky by default. Sortable columns add <code>.ren-th-sortable</code> + <code>data-column</code>.</li>\n          <li><strong>Body</strong> (<code>.ren-table-body</code> on <code>&lt;tbody&gt;</code>) — rows. Selection column uses <code>.ren-table-select</code>; pinned column uses <code>.ren-th-pinned</code> / <code>.ren-td-pinned</code>.</li>\n          <li><strong>Pagination</strong> (<code>.ren-table-pagination</code>) — info text + prev/next/page-size controls. The JS keeps it in sync with <code>data-page-size</code>.</li>\n        </ol>\n      </section>\n\n      <!-- 3. DEMO -->\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <p>Click the <strong>Project</strong> or <strong>Status</strong> header to sort. Use the checkboxes to select rows. The toolbar search filters by any visible cell.</p>\n        <div class=\"dx-demo dx-table-demo\">\n          <div class=\"dx-demo-preview\">\n            <ren-table data-page-size=\"5\">\n              <div class=\"ren-table-toolbar\">\n                <input class=\"ren-input ren-input-sm\" type=\"search\" placeholder=\"Search projects…\" data-table-search aria-label=\"Search projects\">\n              </div>\n              <div class=\"ren-table-wrapper\">\n                <table class=\"ren-table\">\n                  <thead class=\"ren-table-header\">\n                    <tr>\n                      <th class=\"ren-th ren-table-select\" scope=\"col\"><input type=\"checkbox\" aria-label=\"Select all\"></th>\n                      <th class=\"ren-th ren-th-sortable\" scope=\"col\" data-column=\"project\">Project</th>\n                      <th class=\"ren-th\" scope=\"col\">Owner</th>\n                      <th class=\"ren-th ren-th-sortable\" scope=\"col\" data-column=\"status\">Status</th>\n                    </tr>\n                  </thead>\n                  <tbody class=\"ren-table-body\">\n                    <tr class=\"ren-tr\" data-row-id=\"1\"><td class=\"ren-td ren-table-select\"><input type=\"checkbox\" aria-label=\"Select row Atlas\"></td><td class=\"ren-td\">Atlas</td><td class=\"ren-td\">Ren</td><td class=\"ren-td\"><span class=\"ren-badge ren-badge-success\">Active</span></td></tr>\n                    <tr class=\"ren-tr\" data-row-id=\"2\"><td class=\"ren-td ren-table-select\"><input type=\"checkbox\" aria-label=\"Select row Hermes\"></td><td class=\"ren-td\">Hermes</td><td class=\"ren-td\">Ana</td><td class=\"ren-td\"><span class=\"ren-badge ren-badge-warning\">Pending</span></td></tr>\n                    <tr class=\"ren-tr\" data-row-id=\"3\"><td class=\"ren-td ren-table-select\"><input type=\"checkbox\" aria-label=\"Select row Iris\"></td><td class=\"ren-td\">Iris</td><td class=\"ren-td\">Carlos</td><td class=\"ren-td\"><span class=\"ren-badge ren-badge-secondary\">Archived</span></td></tr>\n                    <tr class=\"ren-tr\" data-row-id=\"4\"><td class=\"ren-td ren-table-select\"><input type=\"checkbox\" aria-label=\"Select row Orion\"></td><td class=\"ren-td\">Orion</td><td class=\"ren-td\">Sofía</td><td class=\"ren-td\"><span class=\"ren-badge ren-badge-success\">Active</span></td></tr>\n                    <tr class=\"ren-tr\" data-row-id=\"5\"><td class=\"ren-td ren-table-select\"><input type=\"checkbox\" aria-label=\"Select row Lyra\"></td><td class=\"ren-td\">Lyra</td><td class=\"ren-td\">Diego</td><td class=\"ren-td\"><span class=\"ren-badge ren-badge-warning\">Pending</span></td></tr>\n                    <tr class=\"ren-tr\" data-row-id=\"6\"><td class=\"ren-td ren-table-select\"><input type=\"checkbox\" aria-label=\"Select row Vega\"></td><td class=\"ren-td\">Vega</td><td class=\"ren-td\">Lucía</td><td class=\"ren-td\"><span class=\"ren-badge ren-badge-success\">Active</span></td></tr>\n                    <tr class=\"ren-tr\" data-row-id=\"7\"><td class=\"ren-td ren-table-select\"><input type=\"checkbox\" aria-label=\"Select row Nova\"></td><td class=\"ren-td\">Nova</td><td class=\"ren-td\">Tomás</td><td class=\"ren-td\"><span class=\"ren-badge ren-badge-secondary\">Archived</span></td></tr>\n                  </tbody>\n                </table>\n              </div>\n              <div class=\"ren-table-pagination\">\n                <span class=\"ren-table-pagination-info\"></span>\n                <div class=\"ren-table-pagination-controls\">\n                  <button class=\"ren-table-pagination-button\" type=\"button\" data-page-prev aria-label=\"Previous page\">‹</button>\n                  <button class=\"ren-table-pagination-button\" type=\"button\" data-page-next aria-label=\"Next page\">›</button>\n                </div>\n              </div>\n            </ren-table>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-table data-page-size=\"5\"&gt;\n  &lt;div class=\"ren-table-toolbar\"&gt;\n    &lt;input class=\"ren-input ren-input-sm\" type=\"search\"\n           placeholder=\"Search…\" data-table-search&gt;\n  &lt;/div&gt;\n  &lt;div class=\"ren-table-wrapper\"&gt;\n    &lt;table class=\"ren-table\"&gt;\n      &lt;thead class=\"ren-table-header\"&gt;\n        &lt;tr&gt;\n          &lt;th class=\"ren-th ren-table-select\"&gt;\n            &lt;input type=\"checkbox\" aria-label=\"Select all\"&gt;\n          &lt;/th&gt;\n          &lt;th class=\"ren-th ren-th-sortable\" data-column=\"project\"&gt;Project&lt;/th&gt;\n          &lt;th class=\"ren-th\"&gt;Owner&lt;/th&gt;\n          &lt;th class=\"ren-th ren-th-sortable\" data-column=\"status\"&gt;Status&lt;/th&gt;\n        &lt;/tr&gt;\n      &lt;/thead&gt;\n      &lt;tbody class=\"ren-table-body\"&gt;\n        &lt;tr class=\"ren-tr\" data-row-id=\"1\"&gt;…&lt;/tr&gt;\n      &lt;/tbody&gt;\n    &lt;/table&gt;\n  &lt;/div&gt;\n  &lt;div class=\"ren-table-pagination\"&gt;\n    &lt;span class=\"ren-table-pagination-info\"&gt;&lt;/span&gt;\n    &lt;div class=\"ren-table-pagination-controls\"&gt;\n      &lt;button data-page-prev&gt;‹&lt;/button&gt;\n      &lt;button data-page-next&gt;›&lt;/button&gt;\n    &lt;/div&gt;\n  &lt;/div&gt;\n&lt;/ren-table&gt;</code></pre>\n        </div>\n      </section>\n\n      <!-- 4. VARIANTS -->\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Visual</p>\n        <h2>Variants</h2>\n\n        <div class=\"dx-vgrid\">\n          <div class=\"dx-vrow\">\n            <div class=\"dx-vrow-label\">Density</div>\n            <div class=\"dx-vrow-items\">\n              <p>Three densities, runtime-switchable on the host. Tighter rows mean more rows visible at once — useful in admin views.</p>\n              <ul>\n                <li><code>.ren-table-compact</code> — 36 px row height. For high-density scanning.</li>\n                <li><code>.ren-table-comfortable</code> (default) — 48 px row height. Mainstream balance.</li>\n                <li>Spacious (60 px) — set <code>--ren-table-row-height: 60px</code> via inline style or theme override.</li>\n              </ul>\n              <p>Toggle at runtime by adding / removing the class. Sticky header / pinned column offsets adjust automatically.</p>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <div class=\"dx-vrow-label\">Sticky header</div>\n            <div class=\"dx-vrow-items\">\n              <p>The header is sticky by default once the wrapper scrolls. No attribute required — works the moment the body overflows.</p>\n              <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;div class=\"ren-table-wrapper\" style=\"max-height: 400px\"&gt;\n  &lt;table class=\"ren-table\"&gt;…&lt;/table&gt;\n&lt;/div&gt;</code></pre>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <div class=\"dx-vrow-label\">Pinned column</div>\n            <div class=\"dx-vrow-items\">\n              <p>Pin the leftmost (typically selection or row label) column horizontally during scroll. Pinned offsets are managed by the CSS — never hardcode <code>left:</code> values.</p>\n              <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;th class=\"ren-th ren-th-pinned\"&gt;Name&lt;/th&gt;\n&lt;td class=\"ren-td ren-td-pinned\"&gt;Atlas&lt;/td&gt;</code></pre>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <div class=\"dx-vrow-label\">Empty state</div>\n            <div class=\"dx-vrow-items\">\n              <p>When filtering yields zero rows, the JS swaps the body for an empty state. Provide it explicitly so you control the copy.</p>\n              <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;div class=\"ren-table-empty\"&gt;\n  &lt;div class=\"ren-table-empty-icon\" aria-hidden=\"true\"&gt;🔍&lt;/div&gt;\n  &lt;h3 class=\"ren-table-empty-title\"&gt;No matching projects&lt;/h3&gt;\n  &lt;p class=\"ren-table-empty-description\"&gt;Try a different search term or clear filters.&lt;/p&gt;\n&lt;/div&gt;</code></pre>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <div class=\"dx-vrow-label\">Loading state</div>\n            <div class=\"dx-vrow-items\">\n              <p>Set <code>data-loading</code> on <code>&lt;ren-table&gt;</code> to dim rows, disable interaction, and show a shimmering overlay. Combine with an <code>aria-live=\"polite\"</code> announcement region for screen readers.</p>\n              <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;ren-table data-loading&gt;…&lt;/ren-table&gt;</code></pre>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <div class=\"dx-vrow-label\">Virtualization</div>\n            <div class=\"dx-vrow-items\">\n              <p><em>Not implemented in v0.9.</em> The built-in body renders all visible rows at once. For datasets &gt; ~5 000 rows, switch to an external server-side pager that fetches and replaces rows instead of trying to render everything client-side. A windowed virtualizer is on the v1.0 roadmap.</p>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      <!-- 5. API -->\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n\n        <h3>Custom element attributes</h3>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">data-page-size</code></td><td><span class=\"dx-api-type\">number</span></td><td><span class=\"dx-api-default\">10</span></td><td>Rows per page in the built-in client pager. Set <code>0</code> to disable pagination.</td></tr>\n            <tr><td><code class=\"dx-api-name\">data-loading</code></td><td><span class=\"dx-api-type\">boolean</span></td><td><span class=\"dx-api-default\">false</span></td><td>Shows the shimmer overlay and disables row interaction.</td></tr>\n          </tbody>\n        </table>\n\n        <h3>CSS classes</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-table</code></td><td>Apply on the <code>&lt;table&gt;</code>. Adds borders, padding, hover, focus rings.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-table-wrapper</code></td><td>Required scroll container. Anchors sticky header and pinned columns.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-table-header</code></td><td>On <code>&lt;thead&gt;</code>. Sticky by default once the wrapper scrolls.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-table-body</code></td><td>On <code>&lt;tbody&gt;</code>. Hosts the row hover and selection styles.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-th</code> / <code class=\"dx-api-name\">.ren-td</code></td><td>Header / body cells. Match the densitiy variant on the host.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-th-sortable</code></td><td>Marks a column sortable. Pair with <code>data-column=\"<i>key</i>\"</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-th-pinned</code> / <code class=\"dx-api-name\">.ren-td-pinned</code></td><td>Sticky column. Offsets handled by the CSS — do not set <code>left:</code> yourself.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-th-resize</code></td><td>Drag handle on header for column resize.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-table-select</code></td><td>On <code>th</code>/<code>td</code> hosting the selection checkbox column.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-tr</code></td><td>Body row. Required for selection state styling.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-table-toolbar</code></td><td>Top bar above the table. Holds the search input + bulk actions.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-table-toolbar-search</code></td><td>Optional wrapper around the search field for grouping with bulk actions.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-table-pagination</code></td><td>Footer with info text and prev / next / page-size controls.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-table-pagination-info</code></td><td>\"Showing 1-10 of 47\" label. JS keeps the text in sync.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-table-pagination-controls</code></td><td>Buttons cluster: prev, next, page-size <code>&lt;select&gt;</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-table-pagination-button</code></td><td>Style for prev / next buttons. Real <code>&lt;button&gt;</code>, 44 px touch target.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-table-empty</code></td><td>Empty / no-results state inside the body.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-table-empty-icon</code> / <code class=\"dx-api-name\">-title</code> / <code class=\"dx-api-name\">-description</code></td><td>Composable empty-state parts.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-table-loading-overlay</code></td><td>Shimmer layer rendered while <code>data-loading</code> is set.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-table-compact</code> / <code class=\"dx-api-name\">.ren-table-comfortable</code></td><td>Density variants on the host or table.</td></tr>\n          </tbody>\n        </table>\n\n        <h3>Data attributes (consumer-controlled)</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Attribute</th><th>Where</th><th>Purpose</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">data-column</code></td><td><code>&lt;th&gt;</code></td><td>Key for sort state. Must be unique per table.</td></tr>\n            <tr><td><code class=\"dx-api-name\">data-row-id</code></td><td><code>&lt;tr&gt;</code></td><td>Stable id for selection state. Use the entity id, not the index.</td></tr>\n            <tr><td><code class=\"dx-api-name\">data-table-search</code></td><td>input</td><td>Marker that wires the input to the table's filter pipeline.</td></tr>\n            <tr><td><code class=\"dx-api-name\">data-page-prev</code> / <code class=\"dx-api-name\">data-page-next</code></td><td>button</td><td>Pager controls. Press wires next / prev page.</td></tr>\n            <tr><td><code class=\"dx-api-name\">data-page-size-select</code></td><td><code>&lt;select&gt;</code></td><td>Page size dropdown. Options become page sizes.</td></tr>\n          </tbody>\n        </table>\n\n        <h3>States (read-only, set by the JS)</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>State</th><th>Where</th><th>Meaning</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">data-sort</code></td><td><code>&lt;th&gt;</code></td><td><code>asc</code> / <code>desc</code> / <code>none</code>. Mirrored to <code>aria-sort</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">aria-sort</code></td><td><code>&lt;th&gt;</code></td><td>Standard ARIA mirror of <code>data-sort</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">aria-selected</code></td><td><code>&lt;tr&gt;</code></td><td><code>true</code> when the row's selection checkbox is checked.</td></tr>\n            <tr><td><code class=\"dx-api-name\">data-loading</code></td><td><code>&lt;ren-table&gt;</code></td><td>Set by consumer; the JS responds by showing the overlay.</td></tr>\n          </tbody>\n        </table>\n\n        <h3>Events</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Event</th><th>Detail</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">ren-sort</code></td><td><code>{ column, direction }</code>. Fires after sort applies. Bubbles and is composed.</td></tr>\n            <tr><td><code class=\"dx-api-name\">ren-select</code></td><td><code>{ selected }</code>. Fires after row selection changes. Bubbles and is composed.</td></tr>\n            <tr><td><code class=\"dx-api-name\">ren-filter</code></td><td><code>{ value }</code>. Fires after the toolbar search filter applies. Bubbles and is composed.</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <!-- 6. PUBLIC TOKEN API -->\n      <section class=\"dx-section\" id=\"tokens\">\n        <p class=\"dx-kicker\">Theming</p>\n        <h2>Public token API</h2>\n        <p>Override these custom properties on <code>&lt;ren-table&gt;</code> or on a class to retheme the table without writing selectors against the internals.</p>\n        <table class=\"dx-api dx-api-cols-3\">\n          <thead><tr><th>Token</th><th>Default</th><th>Notes</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">--ren-table-border-color</code></td><td><code>var(--color-border)</code></td><td>All horizontal / vertical lines.</td></tr>\n            <tr><td><code class=\"dx-api-name\">--ren-table-cell-padding</code></td><td><code>var(--space-3)</code></td><td>Inline + block padding for every cell.</td></tr>\n            <tr><td><code class=\"dx-api-name\">--ren-table-row-height</code></td><td><code>auto</code></td><td>Force a fixed row height across the table.</td></tr>\n            <tr><td><code class=\"dx-api-name\">--ren-table-header-bg</code></td><td><code>var(--color-surface-sunken)</code></td><td>Header row background.</td></tr>\n            <tr><td><code class=\"dx-api-name\">--ren-table-header-color</code></td><td><code>var(--color-text-muted)</code></td><td>Header text.</td></tr>\n            <tr><td><code class=\"dx-api-name\">--ren-table-header-size</code></td><td><code>var(--text-xs)</code></td><td>Header font size.</td></tr>\n            <tr><td><code class=\"dx-api-name\">--ren-table-header-weight</code></td><td><code>var(--weight-semibold)</code></td><td>Header font weight.</td></tr>\n            <tr><td><code class=\"dx-api-name\">--ren-table-hover-bg</code></td><td><code>var(--color-fill-hover)</code></td><td>Row hover.</td></tr>\n            <tr><td><code class=\"dx-api-name\">--ren-table-selected-bg</code></td><td><code>var(--color-accent-subtle)</code></td><td>Selected row background.</td></tr>\n            <tr><td><code class=\"dx-api-name\">--ren-table-stripe-bg</code></td><td><code>transparent</code></td><td>Optional zebra striping. Set to <code>var(--color-surface-raised)</code> for stripes.</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <!-- 7. ACCESSIBILITY -->\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n\n        <h3>Keyboard</h3>\n        <div class=\"dx-keys\">\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Tab</kbd></span><span>Cycles into the toolbar, then into headers, then into the body, then into pagination.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Enter</kbd> / <kbd>Space</kbd></span><span>On a sortable <code>&lt;th&gt;</code>: cycles asc → desc → none. On a checkbox cell: toggles selection.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>↑</kbd> / <kbd>↓</kbd></span><span>Inside the body: moves focus to the same column in the row above / below.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Shift</kbd> + click</span><span>On a row checkbox: selects the range between the last clicked row and the current one.</span></div>\n        </div>\n\n        <h3>Required ARIA</h3>\n        <ul>\n          <li>Sortable <code>&lt;th&gt;</code> sets <code>aria-sort=\"ascending\" | \"descending\" | \"none\"</code> mirroring <code>data-sort</code>. The JS owns this — you don't touch <code>aria-sort</code> manually.</li>\n          <li>Selection checkboxes are real <code>&lt;input type=\"checkbox\"&gt;</code> with <code>aria-label</code> (\"Select all\" / \"Select row N\"). Rows reflect state via <code>aria-selected=\"true\"</code>.</li>\n          <li>Pagination buttons are real <code>&lt;button&gt;</code> with 44 px touch targets. Disabled uses the <code>disabled</code> attribute, not <code>opacity</code> alone.</li>\n          <li>Loading state pairs <code>data-loading</code> with an <code>aria-live=\"polite\"</code> announcement: \"Loading…\" → \"Loaded 47 rows\".</li>\n          <li>Empty state must include a visible <code>.ren-table-empty-title</code> — don't rely on the icon alone.</li>\n          <li>Always provide a <code>&lt;caption class=\"ren-sr-only\"&gt;</code> describing the table for screen readers.</li>\n        </ul>\n\n        <div class=\"dx-callout\">\n          <p><strong>Never fake a table with divs.</strong> <code>&lt;div role=\"grid\"&gt;</code> exists but it costs you the entire native screen-reader heuristic for column / row navigation. Real <code>&lt;table&gt;</code> + <code>&lt;th scope=\"col\"&gt;</code> + <code>&lt;td&gt;</code> is the right answer 99% of the time.</p>\n        </div>\n      </section>\n\n      <!-- 8. EXAMPLES -->\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n\n        <h3>Sorting (client-side)</h3>\n        <p>Add <code>.ren-th-sortable</code> + <code>data-column</code> on the header. The component owns asc → desc → none cycling and writes <code>data-sort</code> + <code>aria-sort</code>.</p>\n        <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;th class=\"ren-th ren-th-sortable\" data-column=\"name\"&gt;Name&lt;/th&gt;\n\n// Listen for state changes if you need to mirror to URL or analytics:\ntable.addEventListener('ren-sort', (e) =&gt; {\n  history.replaceState(null, '', `?sort=${e.detail.column}.${e.detail.direction}`);\n});</code></pre>\n\n        <h3>Server-side sorting</h3>\n        <p>Don't pre-sort the rows. Listen for <code>ren-sort</code> and fetch.</p>\n        <pre class=\"dx-pre\" tabindex=\"0\"><code>table.addEventListener('ren-sort', async (e) =&gt; {\n  table.dataset.loading = '';\n  const rows = await fetch(`/api/users?sort=${e.detail.column}&dir=${e.detail.direction}`)\n    .then(r =&gt; r.json());\n  renderBody(rows);          // your function — replace tbody innerHTML\n  delete table.dataset.loading;\n});</code></pre>\n\n        <h3>Row selection</h3>\n        <p>Real checkboxes with <code>aria-label</code>. Listen for <code>ren-select</code> to enable bulk actions when at least one row is selected.</p>\n        <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;tr class=\"ren-tr\" data-row-id=\"1\"&gt;\n  &lt;td class=\"ren-td ren-table-select\"&gt;\n    &lt;input type=\"checkbox\" aria-label=\"Select row Atlas\"&gt;\n  &lt;/td&gt;\n  …\n&lt;/tr&gt;\n\ntable.addEventListener('ren-select', (e) =&gt; {\n  bulkBar.hidden = e.detail.selected.length === 0;\n});</code></pre>\n\n        <h3>Pagination</h3>\n        <p>Set <code>data-page-size</code> on the host. The JS owns visible-row slicing and writes the \"Showing 1-10 of 47\" info.</p>\n        <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;ren-table data-page-size=\"25\"&gt;\n  …\n  &lt;div class=\"ren-table-pagination\"&gt;\n    &lt;span class=\"ren-table-pagination-info\"&gt;&lt;/span&gt;\n    &lt;div class=\"ren-table-pagination-controls\"&gt;\n      &lt;button data-page-prev&gt;Previous&lt;/button&gt;\n      &lt;button data-page-next&gt;Next&lt;/button&gt;\n      &lt;select data-page-size-select&gt;\n        &lt;option value=\"10\"&gt;10&lt;/option&gt;\n        &lt;option value=\"25\" selected&gt;25&lt;/option&gt;\n        &lt;option value=\"50\"&gt;50&lt;/option&gt;\n      &lt;/select&gt;\n    &lt;/div&gt;\n  &lt;/div&gt;\n&lt;/ren-table&gt;</code></pre>\n\n        <h3>Density</h3>\n        <p>Add the class on the table (or on a wrapper to apply to many at once).</p>\n        <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;table class=\"ren-table ren-table-compact\"&gt;…&lt;/table&gt;</code></pre>\n\n        <h3>Sticky header + pinned first column (admin combo)</h3>\n        <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;div class=\"ren-table-wrapper\" style=\"max-height: 480px\"&gt;\n  &lt;table class=\"ren-table\"&gt;\n    &lt;thead class=\"ren-table-header\"&gt;\n      &lt;tr&gt;\n        &lt;th class=\"ren-th ren-th-pinned\"&gt;Name&lt;/th&gt;\n        &lt;th class=\"ren-th\"&gt;Email&lt;/th&gt;\n        &lt;th class=\"ren-th\"&gt;Role&lt;/th&gt;\n        …\n      &lt;/tr&gt;\n    &lt;/thead&gt;\n    &lt;tbody class=\"ren-table-body\"&gt;\n      &lt;tr class=\"ren-tr\"&gt;\n        &lt;td class=\"ren-td ren-td-pinned\"&gt;Ren&lt;/td&gt;\n        &lt;td class=\"ren-td\"&gt;ren@example.com&lt;/td&gt;\n        …\n      &lt;/tr&gt;\n    &lt;/tbody&gt;\n  &lt;/table&gt;\n&lt;/div&gt;</code></pre>\n\n        <h3>Empty state on filter miss</h3>\n        <p>Drop the empty state inside the wrapper — the JS swaps it in when the filter pipeline yields zero rows.</p>\n        <pre class=\"dx-pre\" tabindex=\"0\"><code>&lt;div class=\"ren-table-empty\" hidden&gt;\n  &lt;div class=\"ren-table-empty-icon\" aria-hidden=\"true\"&gt;🔍&lt;/div&gt;\n  &lt;h3 class=\"ren-table-empty-title\"&gt;No matching projects&lt;/h3&gt;\n  &lt;p class=\"ren-table-empty-description\"&gt;Try a different search or clear filters.&lt;/p&gt;\n&lt;/div&gt;</code></pre>\n      </section>\n\n    </main>\n  </div>\n\n  <script type=\"module\" src=\"../../components/patterns/ren-table/ren-table.js\"></script>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-table.html",
      "id": "docs:docs/components/ren-table.html",
      "type": "docs_page",
      "name": "ren-table docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Tabs — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    /* Page-specific styles — chrome and tables live in shell.css */\n\n    /* Custom element default — <div class=\"ren-tabs\"> is display: inline by default,\n       which collapses its panels. Force a block layout. */\n    ren-tabs {\n      display: flex;\n      flex-direction: column;\n      width: 100%;\n    }\n\n    /* Demo block: preview frame + code block, stacked */\n    .dx-demo {\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n      overflow: hidden;\n      margin: var(--space-4) 0;\n    }\n    .dx-demo-preview {\n      background: var(--color-surface-raised);\n      padding: var(--space-6) var(--space-5);\n      min-height: 96px;\n    }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n\n    /* Variant rows — labeled groups of demos */\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label {\n      font-size: var(--text-xs);\n      font-weight: var(--weight-semibold);\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--color-text-muted);\n      padding-top: var(--space-3);\n    }\n    .dx-vrow-items {\n      padding: var(--space-4);\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n    }\n    @media (max-width: 720px) {\n      .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); }\n      .dx-vrow-label { padding-top: 0; }\n    }\n\n    /* Anatomy */\n    .dx-anatomy {\n      margin: var(--space-4) 0;\n      display: grid;\n      gap: var(--space-4);\n    }\n    .dx-anatomy-num {\n      display: inline-grid;\n      place-items: center;\n      width: 22px;\n      height: 22px;\n      border-radius: 50%;\n      background: var(--color-text);\n      color: var(--color-surface);\n      font-size: var(--text-xs);\n      font-weight: var(--weight-bold);\n      flex-shrink: 0;\n    }\n    .dx-anatomy-stage {\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n      padding: var(--space-6) var(--space-5);\n    }\n    .dx-anatomy-stage-label {\n      font-size: var(--text-xs);\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--color-text-muted);\n      font-weight: var(--weight-semibold);\n      margin: 0 0 var(--space-3);\n    }\n    .dx-anatomy-parts {\n      display: grid;\n      grid-template-columns: repeat(2, 1fr);\n      gap: var(--space-3);\n    }\n    @media (max-width: 720px) { .dx-anatomy-parts { grid-template-columns: 1fr; } }\n    .dx-anatomy-part {\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-md);\n      padding: var(--space-4);\n      display: grid;\n      gap: var(--space-2);\n    }\n    .dx-anatomy-part-header {\n      display: flex;\n      align-items: center;\n      gap: var(--space-2);\n    }\n    .dx-anatomy-part-name {\n      font-size: var(--text-sm);\n      font-weight: var(--weight-semibold);\n      color: var(--color-text);\n    }\n    .dx-anatomy-part-desc {\n      font-size: var(--text-sm);\n      line-height: 1.5;\n      color: var(--color-text-secondary);\n      margin: 0;\n    }\n    .dx-anatomy-part-desc code { font-size: 0.85em; }\n\n    /* Vertical tabs row — 1 col label · 2 col content for the orientation demo */\n    .demo-vertical-grid {\n      display: grid;\n      grid-template-columns: auto 1fr;\n      gap: var(--space-6);\n      align-items: start;\n    }\n    .demo-vertical-grid .ren-tab-list {\n      flex-direction: column;\n      border-bottom: none;\n      border-inline-end: 1px solid var(--color-border);\n      padding-inline-end: var(--space-3);\n      gap: var(--space-1);\n    }\n    .demo-vertical-grid .ren-tab {\n      justify-content: flex-start;\n      border-bottom: none;\n      border-inline-end: 2px solid transparent;\n    }\n    .demo-vertical-grid .ren-tab[aria-selected=\"true\"] {\n      border-inline-end-color: var(--color-accent);\n      border-bottom-color: transparent;\n    }\n  </style>\n</head>\n<body>\n\n  <!-- Top bar (shell.css) -->\n  <header class=\"dx-nav\">\n    <div class=\"dx-nav-inner\">\n      <a href=\"../index.html\" class=\"dx-brand\">\n        <span class=\"dx-brand-mark\">R</span>\n        <span>RenDS</span>\n        <span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span>\n      </a>\n      <nav class=\"dx-nav-menu\" aria-label=\"Primary\">\n        <a href=\"../index.html\">Docs</a>\n        <a href=\"../components.html\" aria-current=\"page\">Components</a>\n        <a href=\"../../templates/index.html\">Templates</a>\n        <a href=\"../../create/index.html\">Theme Builder</a>\n      </nav>\n      <div class=\"dx-nav-actions\">\n        <a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a>\n        <a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a>\n      </div>\n    </div>\n  </header>\n\n  <!-- Sidebar + content shell -->\n  <div class=\"dx-shell dx-shell-grid\">\n\n    <!-- Persistent sidebar -->\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\" aria-current=\"page\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <!-- Content -->\n    <main class=\"dx-content\">\n\n      <!-- Page header -->\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\">\n          <ol>\n            <li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li>\n            <li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li>\n            <li aria-current=\"page\">Tabs</li>\n          </ol>\n        </nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Tabs <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">Switch between content panels without leaving the page. Implements the ARIA tablist pattern with full keyboard navigation, three visual variants, and horizontal or vertical orientation. Wrap your tabs in <code>&lt;ren-tabs&gt;</code> and the keyboard, ARIA, and selection state are wired automatically.</p>\n      </header>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           1. OVERVIEW\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Tabs let a user step through several views of related content within the same screen — settings categories, dataset filters, code-vs-preview toggles. The component handles the parts that are easy to get wrong: roving tabindex, arrow-key navigation, ARIA selection state, panel visibility.</p>\n\n        <p>You write the markup as if it were already accessible (semantic <code>&lt;button role=\"tab\"&gt;</code> elements paired with <code>&lt;div role=\"tabpanel\"&gt;</code>), wrap the whole thing in <code>&lt;ren-tabs&gt;</code>, and the component fills in <code>aria-controls</code>, <code>aria-labelledby</code>, <code>aria-selected</code>, <code>id</code>s, and the <code>hidden</code> state on inactive panels.</p>\n\n        <div class=\"dx-callout\">\n          <p><strong>Tabs are for sibling views, not navigation.</strong> If clicking a \"tab\" should change the URL or load a new page, use <code>ren-nav</code> or plain links instead. Tabs imply that all the content already exists on this page and you're just switching the view.</p>\n        </div>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           2. ANATOMY\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"anatomy\">\n        <p class=\"dx-kicker\">Parts</p>\n        <h2>Anatomy</h2>\n        <p>A tabs component is built from three nested layers. Each has a single responsibility.</p>\n\n        <div class=\"dx-anatomy\">\n          <div class=\"dx-anatomy-stage\">\n            <p class=\"dx-anatomy-stage-label\">Assembled</p>\n            <div class=\"ren-tabs\">\n              <div role=\"tablist\" class=\"ren-tab-list ren-tab-list-underline\" aria-label=\"Anatomy preview\">\n                <button role=\"tab\" class=\"ren-tab\" type=\"button\">Overview</button>\n                <button role=\"tab\" class=\"ren-tab\" type=\"button\">Activity</button>\n                <button role=\"tab\" class=\"ren-tab\" type=\"button\">Settings</button>\n              </div>\n              <div role=\"tabpanel\" class=\"ren-tab-panel\">\n                <p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-secondary);\">The <strong>Overview</strong> panel content lives here. Switch tabs above to see the others.</p>\n              </div>\n              <div role=\"tabpanel\" class=\"ren-tab-panel\" hidden>\n                <p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-secondary);\">The <strong>Activity</strong> panel — typically a feed of recent events.</p>\n              </div>\n              <div role=\"tabpanel\" class=\"ren-tab-panel\" hidden>\n                <p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-secondary);\">The <strong>Settings</strong> panel — a form for configuring the resource.</p>\n              </div>\n            </div>\n          </div>\n\n          <div class=\"dx-anatomy-parts\">\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\">\n                <span class=\"dx-anatomy-num\">1</span>\n                <span class=\"dx-anatomy-part-name\">Container</span>\n              </div>\n              <p class=\"dx-anatomy-part-desc\"><code>&lt;ren-tabs&gt;</code> wrapper. Holds the tablist and the panels. Wires up the ARIA, keyboard, and selection state.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\">\n                <span class=\"dx-anatomy-num\">2</span>\n                <span class=\"dx-anatomy-part-name\">Tab list</span>\n              </div>\n              <p class=\"dx-anatomy-part-desc\"><code>.ren-tab-list</code> with <code>role=\"tablist\"</code>. The row of triggers. Add a variant modifier for the visual style.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\">\n                <span class=\"dx-anatomy-num\">3</span>\n                <span class=\"dx-anatomy-part-name\">Tab</span>\n              </div>\n              <p class=\"dx-anatomy-part-desc\"><code>&lt;button class=\"ren-tab\" role=\"tab\"&gt;</code>. One per panel. Always a button — never a link — because activating it doesn't navigate.</p>\n            </div>\n            <div class=\"dx-anatomy-part\">\n              <div class=\"dx-anatomy-part-header\">\n                <span class=\"dx-anatomy-num\">4</span>\n                <span class=\"dx-anatomy-part-name\">Panel</span>\n              </div>\n              <p class=\"dx-anatomy-part-desc\"><code>.ren-tab-panel</code> with <code>role=\"tabpanel\"</code>. One per tab, in the same order. Inactive panels get <code>hidden</code> automatically.</p>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           3. DEMO\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <p>Click the tabs below or focus one and use the arrow keys. Tab and Shift+Tab move in and out of the tablist; arrow keys move between tabs.</p>\n\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <div class=\"ren-tabs\">\n              <div role=\"tablist\" class=\"ren-tab-list ren-tab-list-underline\" aria-label=\"Project sections\">\n                <button role=\"tab\" class=\"ren-tab\" type=\"button\">Overview</button>\n                <button role=\"tab\" class=\"ren-tab\" type=\"button\">Activity</button>\n                <button role=\"tab\" class=\"ren-tab\" type=\"button\">Members</button>\n                <button role=\"tab\" class=\"ren-tab\" type=\"button\">Settings</button>\n              </div>\n              <div role=\"tabpanel\" class=\"ren-tab-panel\">\n                <h3 style=\"font-size: var(--text-base); font-weight: var(--weight-semibold); margin: 0 0 var(--space-2);\">Project overview</h3>\n                <p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-secondary); line-height: 1.55;\">A quick summary of the project's status, owner, and last activity. Most users land here first.</p>\n              </div>\n              <div role=\"tabpanel\" class=\"ren-tab-panel\" hidden>\n                <h3 style=\"font-size: var(--text-base); font-weight: var(--weight-semibold); margin: 0 0 var(--space-2);\">Recent activity</h3>\n                <p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-secondary); line-height: 1.55;\">A feed of the last few events — commits, comments, status changes. Filter by member from the sidebar.</p>\n              </div>\n              <div role=\"tabpanel\" class=\"ren-tab-panel\" hidden>\n                <h3 style=\"font-size: var(--text-base); font-weight: var(--weight-semibold); margin: 0 0 var(--space-2);\">Team members</h3>\n                <p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-secondary); line-height: 1.55;\">People with access to this project, their roles, and the last time they were active. Owners can invite or remove.</p>\n              </div>\n              <div role=\"tabpanel\" class=\"ren-tab-panel\" hidden>\n                <h3 style=\"font-size: var(--text-base); font-weight: var(--weight-semibold); margin: 0 0 var(--space-2);\">Project settings</h3>\n                <p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-secondary); line-height: 1.55;\">Name, description, visibility, danger-zone actions. Changes are saved as you go.</p>\n              </div>\n            </div>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-tabs&gt;\n  &lt;div role=\"tablist\" class=\"ren-tab-list ren-tab-list-underline\" aria-label=\"Project sections\"&gt;\n    &lt;button role=\"tab\" class=\"ren-tab\" type=\"button\"&gt;Overview&lt;/button&gt;\n    &lt;button role=\"tab\" class=\"ren-tab\" type=\"button\"&gt;Activity&lt;/button&gt;\n    &lt;button role=\"tab\" class=\"ren-tab\" type=\"button\"&gt;Members&lt;/button&gt;\n    &lt;button role=\"tab\" class=\"ren-tab\" type=\"button\"&gt;Settings&lt;/button&gt;\n  &lt;/div&gt;\n\n  &lt;div role=\"tabpanel\" class=\"ren-tab-panel\"&gt;Overview content&lt;/div&gt;\n  &lt;div role=\"tabpanel\" class=\"ren-tab-panel\" hidden&gt;Activity content&lt;/div&gt;\n  &lt;div role=\"tabpanel\" class=\"ren-tab-panel\" hidden&gt;Members content&lt;/div&gt;\n  &lt;div role=\"tabpanel\" class=\"ren-tab-panel\" hidden&gt;Settings content&lt;/div&gt;\n&lt;/ren-tabs&gt;</code></pre>\n        </div>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           4. VARIANTS\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Shapes</p>\n        <h2>Variants</h2>\n        <p>Three visual styles, two orientations, two activation modes. Pick by context: underline for primary navigation within a page, pills for filter / segmented controls, enclosed for \"documents in folders\" metaphors.</p>\n\n        <div class=\"dx-vgrid\">\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Underline</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-tabs\">\n                <div role=\"tablist\" class=\"ren-tab-list ren-tab-list-underline\" aria-label=\"Underline variant\">\n                  <button role=\"tab\" class=\"ren-tab\" type=\"button\">First</button>\n                  <button role=\"tab\" class=\"ren-tab\" type=\"button\">Second</button>\n                  <button role=\"tab\" class=\"ren-tab\" type=\"button\">Third</button>\n                </div>\n                <div role=\"tabpanel\" class=\"ren-tab-panel\" style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Default style. Selected tab gets a 2 px accent underline.</div>\n                <div role=\"tabpanel\" class=\"ren-tab-panel\" hidden style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Second panel.</div>\n                <div role=\"tabpanel\" class=\"ren-tab-panel\" hidden style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Third panel.</div>\n              </div>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Pills</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-tabs\">\n                <div role=\"tablist\" class=\"ren-tab-list ren-tab-list-pills\" aria-label=\"Pills variant\">\n                  <button role=\"tab\" class=\"ren-tab\" type=\"button\">All</button>\n                  <button role=\"tab\" class=\"ren-tab\" type=\"button\">Active</button>\n                  <button role=\"tab\" class=\"ren-tab\" type=\"button\">Archived</button>\n                </div>\n                <div role=\"tabpanel\" class=\"ren-tab-panel\" style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Pills feel like a segmented control — good for filters and view modes.</div>\n                <div role=\"tabpanel\" class=\"ren-tab-panel\" hidden style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Active items.</div>\n                <div role=\"tabpanel\" class=\"ren-tab-panel\" hidden style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Archived items.</div>\n              </div>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Enclosed</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-tabs\">\n                <div role=\"tablist\" class=\"ren-tab-list ren-tab-list-enclosed\" aria-label=\"Enclosed variant\">\n                  <button role=\"tab\" class=\"ren-tab\" type=\"button\">README</button>\n                  <button role=\"tab\" class=\"ren-tab\" type=\"button\">License</button>\n                  <button role=\"tab\" class=\"ren-tab\" type=\"button\">Changelog</button>\n                </div>\n                <div role=\"tabpanel\" class=\"ren-tab-panel\" style=\"font-size: var(--text-sm); color: var(--color-text-muted); border: 1px solid var(--color-border); border-top: none; padding: var(--space-4); border-radius: 0 0 var(--radius-md) var(--radius-md);\">Enclosed gives the \"files in a folder\" metaphor. Pair with a bordered panel.</div>\n                <div role=\"tabpanel\" class=\"ren-tab-panel\" hidden style=\"font-size: var(--text-sm); color: var(--color-text-muted); border: 1px solid var(--color-border); border-top: none; padding: var(--space-4); border-radius: 0 0 var(--radius-md) var(--radius-md);\">License contents.</div>\n                <div role=\"tabpanel\" class=\"ren-tab-panel\" hidden style=\"font-size: var(--text-sm); color: var(--color-text-muted); border: 1px solid var(--color-border); border-top: none; padding: var(--space-4); border-radius: 0 0 var(--radius-md) var(--radius-md);\">Changelog entries.</div>\n              </div>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Vertical</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-tabs\" data-orientation=\"vertical\">\n                <div class=\"demo-vertical-grid\">\n                  <div role=\"tablist\" class=\"ren-tab-list\" aria-label=\"Vertical tabs\" aria-orientation=\"vertical\">\n                    <button role=\"tab\" class=\"ren-tab\" type=\"button\">Profile</button>\n                    <button role=\"tab\" class=\"ren-tab\" type=\"button\">Account</button>\n                    <button role=\"tab\" class=\"ren-tab\" type=\"button\">Billing</button>\n                  </div>\n                  <div>\n                    <div role=\"tabpanel\" class=\"ren-tab-panel\" style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Profile settings — name, avatar, bio.</div>\n                    <div role=\"tabpanel\" class=\"ren-tab-panel\" hidden style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Account — email, password, 2FA.</div>\n                    <div role=\"tabpanel\" class=\"ren-tab-panel\" hidden style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Billing — plan, invoices, payment method.</div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Default</span>\n            <div class=\"dx-vrow-items\">\n              <div class=\"ren-tabs\" data-default=\"1\">\n                <div role=\"tablist\" class=\"ren-tab-list ren-tab-list-pills\" aria-label=\"Default tab demo\">\n                  <button role=\"tab\" class=\"ren-tab\" type=\"button\">Day</button>\n                  <button role=\"tab\" class=\"ren-tab\" type=\"button\">Week</button>\n                  <button role=\"tab\" class=\"ren-tab\" type=\"button\">Month</button>\n                </div>\n                <div role=\"tabpanel\" class=\"ren-tab-panel\" style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Day stats.</div>\n                <div role=\"tabpanel\" class=\"ren-tab-panel\" hidden style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Week stats — opens by default via <code>default-value=\"1\"</code>.</div>\n                <div role=\"tabpanel\" class=\"ren-tab-panel\" hidden style=\"font-size: var(--text-sm); color: var(--color-text-muted);\">Month stats.</div>\n              </div>\n            </div>\n          </div>\n\n        </div>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           5. API\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n\n        <h3>CSS classes</h3>\n        <table class=\"dx-api\">\n          <thead>\n            <tr>\n              <th>Class</th>\n              <th>Effect</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-tabs</code></td>\n              <td>Container. Sets up the inline-size container query so tabs can adapt to their available width.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-tab-list</code></td>\n              <td>The row of tab buttons. Used with <code>role=\"tablist\"</code>. Has a default underline divider.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-tab-list-underline</code></td>\n              <td>Default visual variant. Selected tab gets a 2 px accent underline; the rest sit on a divider line.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-tab-list-pills</code></td>\n              <td>Segmented-control look. Selected tab fills with <code>--color-fill</code>. No underline divider.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-tab-list-enclosed</code></td>\n              <td>\"Folder tab\" look. Selected tab merges into the panel below with a shared border.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-tab</code></td>\n              <td>Single tab button. Always a <code>&lt;button type=\"button\"&gt;</code> with <code>role=\"tab\"</code>.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">.ren-tab-panel</code></td>\n              <td>One content region. Used with <code>role=\"tabpanel\"</code>. Inactive panels get <code>hidden</code>.</td>\n            </tr>\n          </tbody>\n        </table>\n\n        <h3>Web Component attributes</h3>\n        <p>Set on the <code>&lt;ren-tabs&gt;</code> wrapper. Re-rendered on the fly when changed.</p>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead>\n            <tr>\n              <th>Attribute</th>\n              <th>Type</th>\n              <th>Default</th>\n              <th>Notes</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td><code class=\"dx-api-name\">activation</code></td>\n              <td><span class=\"dx-api-type\">\"manual\" | \"automatic\"</span></td>\n              <td><span class=\"dx-api-default\">\"manual\"</span></td>\n              <td>Manual: arrow keys move focus; <kbd>Enter</kbd> / <kbd>Space</kbd> selects. Automatic: arrow keys select immediately.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">orientation</code></td>\n              <td><span class=\"dx-api-type\">\"horizontal\" | \"vertical\"</span></td>\n              <td><span class=\"dx-api-default\">\"horizontal\"</span></td>\n              <td>Sets which arrow keys cycle the tabs (Left/Right vs Up/Down) and the <code>aria-orientation</code> attribute.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">default-value</code></td>\n              <td><span class=\"dx-api-type\">number | string</span></td>\n              <td><span class=\"dx-api-default\">0</span></td>\n              <td>Index (e.g. <code>\"1\"</code>) or tab <code>id</code> to select on mount.</td>\n            </tr>\n          </tbody>\n        </table>\n\n        <h3>JavaScript API</h3>\n        <table class=\"dx-api\">\n          <thead>\n            <tr>\n              <th>Member</th>\n              <th>Description</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td><code class=\"dx-api-name\">selectedIndex</code></td>\n              <td>Getter. Index of the currently selected tab.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">selectedTab</code></td>\n              <td>Getter. The selected <code>&lt;button role=\"tab\"&gt;</code> element, or <code>null</code> if none.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">selectedPanel</code></td>\n              <td>Getter. The currently visible <code>&lt;div role=\"tabpanel\"&gt;</code> element.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">tabs</code> / <code class=\"dx-api-name\">panels</code></td>\n              <td>Getters. Arrays of all tab and panel elements, in document order.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">selectTabByIndex(index)</code></td>\n              <td>Programmatically select a tab by its index. Fires <code>ren-tab-change</code>.</td>\n            </tr>\n            <tr>\n              <td><code class=\"dx-api-name\">selectTabById(id)</code></td>\n              <td>Programmatically select a tab by its <code>id</code>. Fires <code>ren-tab-change</code>.</td>\n            </tr>\n          </tbody>\n        </table>\n\n        <h3>Events</h3>\n        <table class=\"dx-api\">\n          <thead>\n            <tr>\n              <th>Event</th>\n              <th>Detail</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <td><code class=\"dx-api-name\">ren-tab-change</code></td>\n              <td>Bubbles and is composed. Fires whenever a tab becomes selected. <code>event.detail</code>: <code>{ tab, panel, index, id }</code>.</td>\n            </tr>\n          </tbody>\n        </table>\n\n        <div class=\"dx-pre\" tabindex=\"0\"><code>const tabs = document.querySelector('ren-tabs');\n\ntabs.addEventListener('ren-tab-change', (e) =&gt; {\n  console.log('Now showing:', e.detail.id, '(index', e.detail.index + ')');\n});\n\n// Programmatic switch\ntabs.selectTabByIndex(2);</code></div>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           6. ACCESSIBILITY\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <p>The component implements the <a href=\"https://www.w3.org/WAI/ARIA/apg/patterns/tabs/\" target=\"_blank\" rel=\"noopener\" class=\"ren-link\">WAI-ARIA Tabs pattern</a> end-to-end. You provide the markup; it provides the wiring.</p>\n\n        <h3>Keyboard</h3>\n        <div class=\"dx-keys\">\n          <div class=\"dx-keyrow\">\n            <span class=\"keys\"><kbd>Tab</kbd></span>\n            <span>Moves focus into the tablist. Subsequent <kbd>Tab</kbd> moves to the active panel, then out of the component entirely.</span>\n          </div>\n          <div class=\"dx-keyrow\">\n            <span class=\"keys\"><kbd>←</kbd> / <kbd>→</kbd> (horizontal) or <kbd>↑</kbd> / <kbd>↓</kbd> (vertical)</span>\n            <span>Move between tabs. In manual mode, focus only; in automatic mode, focus + select.</span>\n          </div>\n          <div class=\"dx-keyrow\">\n            <span class=\"keys\"><kbd>Enter</kbd> / <kbd>Space</kbd></span>\n            <span>Select the focused tab (manual mode). Already selected in automatic mode.</span>\n          </div>\n          <div class=\"dx-keyrow\">\n            <span class=\"keys\"><kbd>Home</kbd> / <kbd>End</kbd></span>\n            <span>Jump to the first or last tab.</span>\n          </div>\n        </div>\n\n        <h3>ARIA wiring</h3>\n        <p>You don't need to write any of the ARIA attributes — the component sets them on mount. What gets wired:</p>\n        <ul>\n          <li><code>role=\"tablist\"</code>, <code>role=\"tab\"</code>, <code>role=\"tabpanel\"</code> if missing.</li>\n          <li><code>aria-controls</code> on each tab pointing at its panel's <code>id</code>.</li>\n          <li><code>aria-labelledby</code> on each panel pointing at its tab's <code>id</code>.</li>\n          <li><code>aria-selected=\"true|false\"</code> kept in sync with the active tab.</li>\n          <li><code>aria-orientation=\"vertical\"</code> when <code>orientation=\"vertical\"</code>.</li>\n        </ul>\n\n        <h3>Manual vs automatic activation</h3>\n        <p>Default is <strong>manual</strong> — arrow keys move focus, the user explicitly presses <kbd>Enter</kbd> or <kbd>Space</kbd> to switch panels. Use this when each panel is expensive to render or has side effects (data fetching, scroll position reset).</p>\n        <p>Use <strong>automatic</strong> when panels are cheap and the user is browsing — arrow keys feel snappier when each one immediately swaps the view.</p>\n\n        <div class=\"dx-callout\">\n          <p><strong>Always provide an <code>aria-label</code> on the tablist</strong> when there's no visible heading describing what the tabs control. \"Project sections\" reads well; an unlabelled tablist is announced as just \"tab list, 4 tabs\" with no context.</p>\n        </div>\n\n        <h3>Motion</h3>\n        <p>The active-tab indicator uses the <code>--transition-tactile</code> motion token. Under <code>prefers-reduced-motion: reduce</code> the transition is disabled — the underline / fill snaps instead of sliding.</p>\n      </section>\n\n      <!-- ═══════════════════════════════════════════════════════════\n           7. EXAMPLES\n           ═══════════════════════════════════════════════════════════ -->\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n\n        <h3>Settings page</h3>\n        <p>A vertical tablist on the left, panels on the right. The pattern most settings UIs converge on.</p>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;ren-tabs orientation=\"vertical\"&gt;\n  &lt;div class=\"settings-grid\"&gt;  &lt;!-- your own grid layout --&gt;\n    &lt;div role=\"tablist\" class=\"ren-tab-list\" aria-label=\"Settings\" aria-orientation=\"vertical\"&gt;\n      &lt;button role=\"tab\" class=\"ren-tab\" type=\"button\"&gt;Profile&lt;/button&gt;\n      &lt;button role=\"tab\" class=\"ren-tab\" type=\"button\"&gt;Account&lt;/button&gt;\n      &lt;button role=\"tab\" class=\"ren-tab\" type=\"button\"&gt;Billing&lt;/button&gt;\n      &lt;button role=\"tab\" class=\"ren-tab\" type=\"button\"&gt;Notifications&lt;/button&gt;\n    &lt;/div&gt;\n\n    &lt;div&gt;\n      &lt;div role=\"tabpanel\" class=\"ren-tab-panel\"&gt;Profile form&lt;/div&gt;\n      &lt;div role=\"tabpanel\" class=\"ren-tab-panel\" hidden&gt;Account form&lt;/div&gt;\n      &lt;div role=\"tabpanel\" class=\"ren-tab-panel\" hidden&gt;Billing&lt;/div&gt;\n      &lt;div role=\"tabpanel\" class=\"ren-tab-panel\" hidden&gt;Notifications&lt;/div&gt;\n    &lt;/div&gt;\n  &lt;/div&gt;\n&lt;/ren-tabs&gt;</code></div>\n\n        <h3>Filter pills</h3>\n        <p>Pills work well as a filter row above a list. Pair with <code>activation=\"automatic\"</code> so the list updates as the user arrow-keys through the options.</p>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;ren-tabs activation=\"automatic\"&gt;\n  &lt;div role=\"tablist\" class=\"ren-tab-list ren-tab-list-pills\" aria-label=\"Filter projects\"&gt;\n    &lt;button role=\"tab\" class=\"ren-tab\" type=\"button\"&gt;All&lt;/button&gt;\n    &lt;button role=\"tab\" class=\"ren-tab\" type=\"button\"&gt;Active&lt;/button&gt;\n    &lt;button role=\"tab\" class=\"ren-tab\" type=\"button\"&gt;Archived&lt;/button&gt;\n  &lt;/div&gt;\n\n  &lt;div role=\"tabpanel\" class=\"ren-tab-panel\"&gt;... rendered list ...&lt;/div&gt;\n  &lt;div role=\"tabpanel\" class=\"ren-tab-panel\" hidden&gt;...&lt;/div&gt;\n  &lt;div role=\"tabpanel\" class=\"ren-tab-panel\" hidden&gt;...&lt;/div&gt;\n&lt;/ren-tabs&gt;</code></div>\n\n        <h3>Lazy-load panels on tab change</h3>\n        <p>Listen to <code>ren-tab-change</code> and fetch panel content on demand instead of rendering everything upfront.</p>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>const tabs = document.querySelector('#project-tabs');\n\ntabs.addEventListener('ren-tab-change', async (e) =&gt; {\n  const panel = e.detail.panel;\n  if (panel.dataset.loaded) return;\n\n  panel.innerHTML = '&lt;div class=\"ren-skeleton skeleton-text\"&gt;&lt;/div&gt;';\n  const data = await fetch(`/api/${e.detail.id}`).then(r =&gt; r.json());\n  panel.innerHTML = renderPanel(data);\n  panel.dataset.loaded = 'true';\n});</code></div>\n\n        <h3>Sync the active tab with the URL hash</h3>\n        <p>Useful for shareable links and back-button support.</p>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>const tabs = document.querySelector('#project-tabs');\n\n// Restore from hash on load\nif (location.hash) {\n  tabs.selectTabById(location.hash.slice(1));\n}\n\n// Update hash when the user switches tabs\ntabs.addEventListener('ren-tab-change', (e) =&gt; {\n  history.replaceState(null, '', `#${e.detail.id}`);\n});</code></div>\n      </section>\n\n    </main>\n\n  </div>\n\n  <!-- Web Component registration (handles arrow keys, ARIA wiring, etc.) -->\n  <script type=\"module\" src=\"../../components/composites/ren-tabs/ren-tabs.js\"></script>\n\n  <!-- Demo wiring — vanilla, runs against div.ren-tabs containers.\n       Toggles aria-selected and the hidden attribute on paired panels.\n       Falls back gracefully if there's no JS, since the first panel\n       starts visible in the markup. -->\n  <script>\n    document.querySelectorAll('.ren-tabs').forEach(container => {\n      const tabs = container.querySelectorAll('[role=\"tab\"]');\n      const panels = container.querySelectorAll('[role=\"tabpanel\"]');\n      if (!tabs.length || !panels.length) return;\n\n      // Initial selection: data-default attribute, then any aria-selected=\"true\",\n      // then index 0.\n      let initialIndex = parseInt(container.getAttribute('data-default'), 10);\n      if (isNaN(initialIndex)) {\n        initialIndex = Array.from(tabs).findIndex(t => t.getAttribute('aria-selected') === 'true');\n      }\n      if (initialIndex < 0 || initialIndex >= tabs.length) initialIndex = 0;\n\n      const select = (index) => {\n        tabs.forEach((t, j) => {\n          const active = j === index;\n          t.setAttribute('aria-selected', active ? 'true' : 'false');\n          t.setAttribute('tabindex', active ? '0' : '-1');\n          if (panels[j]) panels[j].toggleAttribute('hidden', !active);\n        });\n      };\n\n      select(initialIndex);\n\n      tabs.forEach((tab, i) => {\n        tab.addEventListener('click', () => select(i));\n        tab.addEventListener('keydown', (e) => {\n          const isVertical = container.getAttribute('data-orientation') === 'vertical';\n          const next = isVertical ? 'ArrowDown' : 'ArrowRight';\n          const prev = isVertical ? 'ArrowUp' : 'ArrowLeft';\n          let target = null;\n          if (e.key === next) target = (i + 1) % tabs.length;\n          else if (e.key === prev) target = (i - 1 + tabs.length) % tabs.length;\n          else if (e.key === 'Home') target = 0;\n          else if (e.key === 'End') target = tabs.length - 1;\n          if (target !== null) {\n            e.preventDefault();\n            select(target);\n            tabs[target].focus();\n          }\n        });\n      });\n    });\n  </script>\n\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-tabs.html",
      "id": "docs:docs/components/ren-tabs.html",
      "type": "docs_page",
      "name": "ren-tabs docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Tag — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: var(--space-3); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); align-items: center; }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\" aria-current=\"page\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Tag</li></ol></nav>\n        <p class=\"dx-kicker\">Primitive</p>\n        <h1>Tag <span class=\"dx-api-badge dx-api-badge-css\" title=\"Works without any JavaScript\">CSS-only</span></h1>\n        <p class=\"lede\">Interactive chips for filters, selections, and removable items. Cousin of Badge — same compact size, but Tags can be clicked, toggled, and dismissed.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>A Tag is a Badge that does something. Use it for: <strong>filter chips</strong> (\"Design\", \"Engineering\" — click to filter the list), <strong>selected items</strong> (\"Argentina<span style=\"display: inline-flex; vertical-align: middle; color: var(--color-text-muted); margin-inline-start: 2px;\"><svg viewBox=\"0 0 24 24\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg></span>\" inside a multi-select), <strong>category labels</strong> on a content card. The base styling matches Badge so they share the visual rhythm.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Tag vs Badge.</strong> If a click does nothing, it's a Badge. If a click filters, selects, or removes — it's a Tag.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <span class=\"ren-tag\">Design</span>\n            <span class=\"ren-tag ren-tag-primary\">Engineering</span>\n            <span class=\"ren-tag ren-tag-success\">Approved</span>\n            <button type=\"button\" class=\"ren-tag ren-tag-clickable\">Filter</button>\n            <button type=\"button\" class=\"ren-tag ren-tag-clickable\" aria-pressed=\"true\">Selected</button>\n            <span class=\"ren-tag\">design <button type=\"button\" class=\"ren-tag-dismiss\" aria-label=\"Remove design\">×</button></span>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;span class=\"ren-tag\"&gt;Design&lt;/span&gt;\n&lt;button class=\"ren-tag ren-tag-clickable\" aria-pressed=\"false\"&gt;Filter&lt;/button&gt;\n&lt;span class=\"ren-tag\"&gt;design\n  &lt;button class=\"ren-tag-dismiss\" aria-label=\"Remove\"&gt;×&lt;/button&gt;\n&lt;/span&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Shapes</p>\n        <h2>Variants</h2>\n        <div class=\"dx-vgrid\">\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Status</span><div class=\"dx-vrow-items\">\n            <span class=\"ren-tag ren-tag-primary\">Primary</span>\n            <span class=\"ren-tag ren-tag-success\">Success</span>\n            <span class=\"ren-tag ren-tag-warning\">Warning</span>\n            <span class=\"ren-tag ren-tag-danger\">Danger</span>\n          </div></div>\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Sizes</span><div class=\"dx-vrow-items\">\n            <span class=\"ren-tag ren-tag-sm\">Small</span>\n            <span class=\"ren-tag\">Medium</span>\n            <span class=\"ren-tag ren-tag-lg\">Large</span>\n          </div></div>\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Clickable</span><div class=\"dx-vrow-items\">\n            <button type=\"button\" class=\"ren-tag ren-tag-clickable\" aria-pressed=\"false\">All</button>\n            <button type=\"button\" class=\"ren-tag ren-tag-clickable\" aria-pressed=\"true\">Active</button>\n            <button type=\"button\" class=\"ren-tag ren-tag-clickable\" aria-pressed=\"false\">Archived</button>\n          </div></div>\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Removable</span><div class=\"dx-vrow-items\">\n            <span class=\"ren-tag\">design <button type=\"button\" class=\"ren-tag-dismiss\" aria-label=\"Remove design\">×</button></span>\n            <span class=\"ren-tag\">engineering <button type=\"button\" class=\"ren-tag-dismiss\" aria-label=\"Remove engineering\">×</button></span>\n            <span class=\"ren-tag ren-tag-primary\">a11y <button type=\"button\" class=\"ren-tag-dismiss\" aria-label=\"Remove a11y\">×</button></span>\n          </div></div>\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Group</span><div class=\"dx-vrow-items\">\n            <div class=\"ren-tag-group\" style=\"display: flex; flex-wrap: wrap; gap: var(--space-1);\">\n              <span class=\"ren-tag\">react</span>\n              <span class=\"ren-tag\">accessibility</span>\n              <span class=\"ren-tag\">forms</span>\n              <span class=\"ren-tag\">design-systems</span>\n            </div>\n          </div></div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <h3>CSS classes</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-tag</code></td><td>Base. Slightly more padding than Badge to accommodate the dismiss button.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-tag-primary</code> / <code class=\"dx-api-name\">-success</code> / <code class=\"dx-api-name\">-warning</code> / <code class=\"dx-api-name\">-danger</code></td><td>Status fills.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-tag-sm</code> / <code class=\"dx-api-name\">-lg</code></td><td>Size variants.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-tag-clickable</code></td><td>Adds hover and focus styles. Use on a real <code>&lt;button&gt;</code> with <code>aria-pressed</code> for toggle behavior.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-tag-dismiss</code></td><td>The × close button inside a removable tag. Always pair with <code>aria-label</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-tag-group</code></td><td>Container for multiple tags. Provides flex wrap and consistent gap.</td></tr>\n            <tr><td><code class=\"dx-api-name\">[aria-pressed=\"true\"]</code> or <code class=\"dx-api-name\">[data-selected]</code></td><td>Marks a clickable tag as currently selected.</td></tr>\n          </tbody>\n        </table>\n        <p>No JavaScript required. Click handling and state management live in your app.</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li><strong>Clickable tags</strong>: must be a real <code>&lt;button&gt;</code> with <code>aria-pressed</code> for toggle state. A clickable <code>&lt;span&gt;</code> is not keyboard-accessible.</li>\n          <li><strong>Dismiss buttons</strong>: always include <code>aria-label=\"Remove {tag-name}\"</code> so screen readers announce what's being removed.</li>\n          <li><strong>Tag groups for filters</strong>: wrap them in a labelled region — <code>&lt;div role=\"group\" aria-label=\"Filter by category\"&gt;</code>.</li>\n        </ul>\n        <h3>Keyboard</h3>\n        <div class=\"dx-keys\">\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Tab</kbd></span><span>Moves through clickable tags in document order.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Enter</kbd> / <kbd>Space</kbd></span><span>Activates the focused tag (toggle pressed state, run the action, dismiss).</span></div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n        <h3>Filter chips</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;div role=\"group\" aria-label=\"Filter by status\" class=\"ren-tag-group\"&gt;\n  &lt;button class=\"ren-tag ren-tag-clickable\" aria-pressed=\"true\"&gt;All&lt;/button&gt;\n  &lt;button class=\"ren-tag ren-tag-clickable\" aria-pressed=\"false\"&gt;Active&lt;/button&gt;\n  &lt;button class=\"ren-tag ren-tag-clickable\" aria-pressed=\"false\"&gt;Archived&lt;/button&gt;\n&lt;/div&gt;</code></div>\n\n        <h3>Removable selections</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;span class=\"ren-tag\"&gt;\n  argentina\n  &lt;button class=\"ren-tag-dismiss\" aria-label=\"Remove Argentina\"\n          onclick=\"removeCountry('ar')\"&gt;×&lt;/button&gt;\n&lt;/span&gt;</code></div>\n      </section>\n\n    </main>\n  </div>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-tag.html",
      "id": "docs:docs/components/ren-tag.html",
      "type": "docs_page",
      "name": "ren-tag docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Toast — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); justify-content: center; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-4); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-direction: column; gap: var(--space-3); }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n\n    /* Static toast preview */\n    .demo-toast {\n      display: flex; align-items: flex-start; gap: var(--space-3);\n      width: 320px;\n      padding: var(--space-3) var(--space-4);\n      background: var(--color-surface-raised);\n      border: 1px solid var(--color-border);\n      border-radius: var(--radius-lg);\n      box-shadow: var(--shadow-lg);\n    }\n    .demo-toast-icon { flex-shrink: 0; width: 20px; height: 20px; display: grid; place-items: center; }\n    .demo-toast-icon-success { color: var(--color-success); }\n    .demo-toast-icon-warning { color: var(--color-warning); }\n    .demo-toast-icon-danger { color: var(--color-danger); }\n    .demo-toast-icon-info { color: var(--color-info); }\n    .demo-toast-body { flex: 1; min-width: 0; }\n    .demo-toast-title { font-size: var(--text-sm); font-weight: var(--weight-semibold); margin: 0 0 2px; }\n    .demo-toast-desc { font-size: var(--text-xs); color: var(--color-text-muted); margin: 0; line-height: 1.5; }\n    .demo-toast-close { background: transparent; border: 0; padding: 2px; color: var(--color-text-muted); cursor: pointer; line-height: 1; }\n    .demo-toast-close:hover { color: var(--color-text); }\n  </style>\n</head>\n<body>\n\n  <header class=\"dx-nav\">\n    <div class=\"dx-nav-inner\">\n      <a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a>\n      <nav class=\"dx-nav-menu\" aria-label=\"Primary\">\n        <a href=\"../index.html\">Docs</a>\n        <a href=\"../components.html\" aria-current=\"page\">Components</a>\n        <a href=\"../../templates/index.html\">Templates</a>\n        <a href=\"../../create/index.html\">Theme Builder</a>\n      </nav>\n      <div class=\"dx-nav-actions\">\n        <a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a>\n        <a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a>\n      </div>\n    </div>\n  </header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\" aria-current=\"page\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\">\n          <ol>\n            <li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li>\n            <li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li>\n            <li aria-current=\"page\">Toast</li>\n          </ol>\n        </nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Toast <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">Transient notifications that appear in a corner of the screen and dismiss themselves after a few seconds. Announced to screen readers via live regions, dismissible by swipe / click, and stackable when several fire at once.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Use toasts for low-importance, transient feedback: \"Saved\", \"Copied to clipboard\", \"Connection restored\". They're the right primitive when you need to acknowledge an action without interrupting the user's flow. For high-stakes feedback (errors that block a flow), use a Dialog or an inline error message inside a <a href=\"ren-field.html\" class=\"ren-link\">Form Field</a> instead.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Don't put critical actions in a toast.</strong> Toasts disappear; users miss them. If the user must read the message or perform an action in response, use a Dialog. Toast is for fire-and-forget acknowledgement.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"anatomy\">\n        <p class=\"dx-kicker\">Parts</p>\n        <h2>Anatomy</h2>\n        <p>A toast lives in a fixed-position viewport (one per corner of the screen) and contains an icon, title, optional description, and an optional action.</p>\n        <div style=\"background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-6) var(--space-5); margin: var(--space-4) 0; display: flex; justify-content: center;\">\n          <div class=\"demo-toast\" role=\"status\">\n            <span class=\"demo-toast-icon demo-toast-icon-success\">\n              <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M20 6L9 17l-5-5\"/></svg>\n            </span>\n            <div class=\"demo-toast-body\">\n              <p class=\"demo-toast-title\">Project saved</p>\n              <p class=\"demo-toast-desc\">Your changes are visible to the team.</p>\n            </div>\n            <button class=\"demo-toast-close\" aria-label=\"Dismiss\">×</button>\n          </div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <p>Trigger a toast from the buttons below. The viewport renders in the bottom-right corner of this page; toasts auto-dismiss after 4 seconds, or click the × to dismiss early.</p>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-toast=\"success\">Success</button>\n            <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-toast=\"info\">Info</button>\n            <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-toast=\"warning\">Warning</button>\n            <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-toast=\"danger\">Danger</button>\n            <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-toast=\"action\">With action</button>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>// Imperative API — most common usage\nwindow.toast.success('Project saved', { description: 'Your changes are live.' });\nwindow.toast.info('Sync started');\nwindow.toast.warning('Rate limit approaching');\nwindow.toast.danger('Delete failed', { description: 'Please try again.' });\nwindow.toast.show({ title: 'Email sent', action: { label: 'Undo', onClick: () =&gt; rollback() } });</code></pre>\n        </div>\n\n        <!-- Toast viewport: a single instance is auto-created if missing.\n             Drop one in explicitly to control position. -->\n        <ren-toast-viewport data-position=\"bottom-right\"></ren-toast-viewport>\n      </section>\n\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Shapes</p>\n        <h2>Variants</h2>\n        <div class=\"dx-vgrid\">\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Status colors</span>\n            <div class=\"dx-vrow-items\"><p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Four status variants: <code>success</code>, <code>info</code>, <code>warning</code>, <code>danger</code>. Each tints the icon and the left border in the matching semantic color.</p></div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Position</span>\n            <div class=\"dx-vrow-items\"><p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">The viewport supports six positions via <code>data-position</code>: <code>top-right</code>, <code>top-left</code>, <code>top-center</code>, <code>bottom-right</code> (default), <code>bottom-left</code>, <code>bottom-center</code>. Pick one for your whole app.</p></div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">With action</span>\n            <div class=\"dx-vrow-items\"><p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Add an inline action button — <strong>Undo</strong>, <strong>Retry</strong>, <strong>View</strong>. Best paired with a slightly longer auto-dismiss timeout so the user has time to react.</p></div>\n          </div>\n          <div class=\"dx-vrow\">\n            <span class=\"dx-vrow-label\">Persistent</span>\n            <div class=\"dx-vrow-items\"><p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Set <code>duration: 0</code> to disable auto-dismiss — the user must close it manually. Use sparingly; reserve for important state changes the user has to acknowledge.</p></div>\n          </div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n\n        <h3>CSS classes</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-toast-viewport</code></td><td>Fixed-position container. One per app, picks a corner via <code>data-position</code>. Has <code>aria-live=\"polite\"</code> so screen readers announce additions.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-toast</code></td><td>Single notification. Card-like surface with shadow. Slides in from the viewport's edge.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-toast-icon</code></td><td>Leading status icon. Color matches the toast's variant.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-toast-body</code></td><td>Title + optional description region.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-toast-title</code></td><td>The bold one-line message.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-toast-description</code></td><td>Secondary line with detail. Muted color.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-toast-action</code></td><td>Inline action button. Use a real <code>&lt;button&gt;</code>; pair with a callback in the JS API.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-toast-close</code></td><td>Dismiss × button. Always present, sized to be tappable on touch.</td></tr>\n          </tbody>\n        </table>\n\n        <h3>JavaScript API</h3>\n        <p>The component exposes a global <code>window.toast</code> with one method per status, plus a generic <code>show()</code>. Status methods return an <code>id</code> you can later pass to <code>toast.dismiss(id)</code>.</p>\n        <table class=\"dx-api dx-api-cols-3\">\n          <thead><tr><th>Method</th><th>Returns</th><th>Description</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">toast.success(title, options?)</code></td><td><span class=\"dx-api-type\">string (id)</span></td><td>Green check, polite announce.</td></tr>\n            <tr><td><code class=\"dx-api-name\">toast.info(title, options?)</code></td><td><span class=\"dx-api-type\">string (id)</span></td><td>Blue info, polite.</td></tr>\n            <tr><td><code class=\"dx-api-name\">toast.warning(title, options?)</code></td><td><span class=\"dx-api-type\">string (id)</span></td><td>Amber, polite.</td></tr>\n            <tr><td><code class=\"dx-api-name\">toast.danger(title, options?)</code></td><td><span class=\"dx-api-type\">string (id)</span></td><td>Red, <em>assertive</em> announce — screen readers interrupt.</td></tr>\n            <tr><td><code class=\"dx-api-name\">toast.show(options)</code></td><td><span class=\"dx-api-type\">string (id)</span></td><td>Full control. <code>{ title, description, status, duration, action }</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">toast.dismiss(id)</code></td><td><span class=\"dx-api-type\">void</span></td><td>Dismiss a specific toast by the id returned from <code>show</code>/status methods.</td></tr>\n            <tr><td><code class=\"dx-api-name\">toast.dismissAll()</code></td><td><span class=\"dx-api-type\">void</span></td><td>Dismiss every visible toast (useful on route change).</td></tr>\n            <tr><td><code class=\"dx-api-name\">toast.promise(promise, opts)</code></td><td><span class=\"dx-api-type\">Promise</span></td><td>Shows a loading toast that swaps to success/error when the promise settles. <code>opts: { loading, success, error }</code> — each can be a string or function of the resolved/rejected value.</td></tr>\n          </tbody>\n        </table>\n\n        <h3>Options</h3>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead><tr><th>Option</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">title</code></td><td><span class=\"dx-api-type\">string</span></td><td><span class=\"dx-api-default\">—</span></td><td>The bold message. Required.</td></tr>\n            <tr><td><code class=\"dx-api-name\">description</code></td><td><span class=\"dx-api-type\">string</span></td><td><span class=\"dx-api-default\">—</span></td><td>Optional secondary line.</td></tr>\n            <tr><td><code class=\"dx-api-name\">status</code></td><td><span class=\"dx-api-type\">\"success\" | \"info\" | \"warning\" | \"danger\"</span></td><td><span class=\"dx-api-default\">\"info\"</span></td><td>Visual variant.</td></tr>\n            <tr><td><code class=\"dx-api-name\">duration</code></td><td><span class=\"dx-api-type\">number (ms)</span></td><td><span class=\"dx-api-default\">4000</span></td><td>Auto-dismiss after N ms. Pass <code>0</code> for persistent.</td></tr>\n            <tr><td><code class=\"dx-api-name\">action</code></td><td><span class=\"dx-api-type\">{ label, onClick }</span></td><td><span class=\"dx-api-default\">—</span></td><td>Inline action button. Click runs <code>onClick</code> then dismisses.</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <p>Toasts are tricky for accessibility — they appear without focus and disappear without warning. RenDS handles the announcement via live regions; you handle the message phrasing.</p>\n        <h3>Live region behavior</h3>\n        <ul>\n          <li><strong>Success / info / warning</strong> use <code>aria-live=\"polite\"</code> — announced when the screen reader is idle.</li>\n          <li><strong>Danger</strong> uses <code>aria-live=\"assertive\"</code> — interrupts the current announcement. Reserve for genuine errors.</li>\n        </ul>\n        <h3>Keyboard</h3>\n        <div class=\"dx-keys\">\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>F6</kbd></span><span>Moves focus to the toast viewport. Useful when an action is present and the user wants to act before it dismisses.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Esc</kbd></span><span>While focused on a toast: dismisses it.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Tab</kbd></span><span>Cycles through the action button and the close button inside a toast.</span></div>\n        </div>\n        <div class=\"dx-callout\">\n          <p><strong>Pause auto-dismiss on hover and focus.</strong> The viewport pauses the dismiss timer when the user hovers over it or any toast inside receives focus — they're trying to read it.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n\n        <h3>Confirm a save</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>async function save() {\n  await api.save();\n  toast.success('Saved', { description: 'Visible to the team in a few seconds.' });\n}</code></pre>\n\n        <h3>Undoable delete</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>function deleteRow(id) {\n  const backup = removeOptimistically(id);\n\n  toast.show({\n    title: 'Row deleted',\n    duration: 7000,\n    action: {\n      label: 'Undo',\n      onClick: () =&gt; restore(backup),\n    },\n  });\n}</code></pre>\n\n        <h3>Persistent error with retry</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>toast.danger('Connection lost', {\n  description: 'Reconnecting…',\n  duration: 0,            // sticks until dismissed\n  action: { label: 'Retry now', onClick: () =&gt; reconnect() },\n});</code></pre>\n\n        <h3>Dismiss a toast programmatically</h3>\n        <p>Status methods return an <code>id</code>. Store it and pass it to <code>toast.dismiss</code> when the underlying state changes.</p>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>// Show a persistent \"uploading\" toast, dismiss when done.\nconst uploadId = toast.info('Uploading…', { duration: 0 });\n\ntry {\n  await uploadFile(file);\n  toast.dismiss(uploadId);\n  toast.success('Upload complete');\n} catch (err) {\n  toast.dismiss(uploadId);\n  toast.danger('Upload failed', { description: err.message });\n}</code></pre>\n\n        <h3>Track an async operation with toast.promise</h3>\n        <p>One call covers loading → success / error. The toast updates in place.</p>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>toast.promise(api.saveProfile(data), {\n  loading: 'Saving profile…',\n  success: 'Profile saved',\n  error:   (err) =&gt; `Couldn't save: ${err.message}`,\n});</code></pre>\n      </section>\n\n    </main>\n  </div>\n\n  <script type=\"module\" src=\"../../components/composites/ren-toast/ren-toast.js\"></script>\n  <script type=\"module\">\n    // Wait for the viewport's custom element to register, then wire demo buttons\n    // to the real `window.toast` API.\n    await customElements.whenDefined('ren-toast-viewport');\n\n    document.querySelectorAll('[data-toast]').forEach((btn) => {\n      btn.addEventListener('click', () => {\n        const kind = btn.getAttribute('data-toast');\n        switch (kind) {\n          case 'success':\n            window.toast.success('Project saved', { description: 'Your changes are visible to the team.' });\n            break;\n          case 'info':\n            window.toast.info('Sync started', { description: 'Pulling the latest changes…' });\n            break;\n          case 'warning':\n            window.toast.warning('Rate limit', { description: \"You're close to your hourly limit.\" });\n            break;\n          case 'danger':\n            window.toast.danger('Delete failed', { description: 'Please try again.' });\n            break;\n          case 'action':\n            window.toast.show({\n              title: 'Email sent',\n              description: 'To 12 recipients.',\n              status: 'info',\n              action: { label: 'Undo', onClick: () => window.toast.success('Undone') },\n            });\n            break;\n        }\n      });\n    });\n  </script>\n\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-toast.html",
      "id": "docs:docs/components/ren-toast.html",
      "type": "docs_page",
      "name": "ren-toast docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Toggle Group — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-direction: column; gap: var(--space-3); align-items: flex-start; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\" aria-current=\"page\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Toggle Group</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Toggle Group <span class=\"dx-api-badge dx-api-badge-hybrid\" title=\"Renders without JS; JS adds enhancements\">Hybrid</span></h1>\n        <p class=\"lede\">Segmented control of 2–5 options. Pick exactly one (radio behavior) or any number (checkbox behavior). For text alignment, view modes, density toggles — anywhere a tight visual cluster beats a dropdown.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Toggle groups visually link related options into one control. Single mode is a stylized radio group (\"Day | Week | Month\"); multi mode is a stylized checkbox group (\"Bold + Italic + Underline\" all on at once).</p>\n        <div class=\"dx-callout\">\n          <p><strong>Toggle Group vs Tabs.</strong> Tabs change which content panel is shown. Toggle Group changes a <em>setting</em> on the same content. If clicking changes what you see, it's Tabs. If clicking changes how it's filtered or rendered, it's a Toggle Group.</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <div class=\"ren-toggle-group\" role=\"radiogroup\" aria-label=\"Time range\">\n              <button class=\"ren-toggle-item\" type=\"button\" role=\"radio\" aria-checked=\"true\">Day</button>\n              <button class=\"ren-toggle-item\" type=\"button\" role=\"radio\" aria-checked=\"false\">Week</button>\n              <button class=\"ren-toggle-item\" type=\"button\" role=\"radio\" aria-checked=\"false\">Month</button>\n              <button class=\"ren-toggle-item\" type=\"button\" role=\"radio\" aria-checked=\"false\">Year</button>\n            </div>\n            <div class=\"ren-toggle-group\" role=\"group\" aria-label=\"Text format\">\n              <button class=\"ren-toggle-item\" type=\"button\" aria-pressed=\"true\"><strong>B</strong></button>\n              <button class=\"ren-toggle-item\" type=\"button\" aria-pressed=\"false\"><em>I</em></button>\n              <button class=\"ren-toggle-item\" type=\"button\" aria-pressed=\"false\"><u>U</u></button>\n            </div>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;!-- Single (one selected at a time) --&gt;\n&lt;div class=\"ren-toggle-group\" role=\"radiogroup\" aria-label=\"Time range\"&gt;\n  &lt;button role=\"radio\" class=\"ren-toggle-item\" aria-checked=\"true\"&gt;Day&lt;/button&gt;\n  &lt;button role=\"radio\" class=\"ren-toggle-item\" aria-checked=\"false\"&gt;Week&lt;/button&gt;\n  &lt;button role=\"radio\" class=\"ren-toggle-item\" aria-checked=\"false\"&gt;Month&lt;/button&gt;\n&lt;/div&gt;\n\n&lt;!-- Multi (any number selected) --&gt;\n&lt;div class=\"ren-toggle-group\" role=\"group\" aria-label=\"Text format\"&gt;\n  &lt;button class=\"ren-toggle-item\" aria-pressed=\"true\"&gt;Bold&lt;/button&gt;\n  &lt;button class=\"ren-toggle-item\" aria-pressed=\"false\"&gt;Italic&lt;/button&gt;\n&lt;/div&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-toggle-group</code></td><td>The container. Inline-flex row, items share borders, no gaps. Single rounded shape.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-toggle-item</code></td><td>One option. Always a real <code>&lt;button type=\"button\"&gt;</code>. Selected state via <code>aria-checked=\"true\"</code> (radio mode) or <code>aria-pressed=\"true\"</code> (multi mode).</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li><strong>Single mode</strong>: wrap in <code>role=\"radiogroup\"</code>; items use <code>role=\"radio\"</code> + <code>aria-checked</code>. Arrow keys move selection.</li>\n          <li><strong>Multi mode</strong>: wrap in <code>role=\"group\"</code>; items use plain <code>aria-pressed</code>. Tab moves between, Space toggles each.</li>\n          <li>Always pair the group with <code>aria-label</code> or a visible label outside.</li>\n        </ul>\n      </section>\n\n    </main>\n  </div>\n  <script type=\"module\" src=\"../../components/composites/ren-toggle-group/ren-toggle-group.js\"></script>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-toggle-group.html",
      "id": "docs:docs/components/ren-toggle-group.html",
      "type": "docs_page",
      "name": "ren-toggle-group docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Toolbar — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); display: flex; flex-direction: column; gap: var(--space-3); }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .demo-tb { display: flex; align-items: center; gap: var(--space-1); padding: var(--space-2); background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-md); }\n    .demo-tb button { padding: 6px var(--space-2); background: transparent; border: 0; border-radius: var(--radius-sm); cursor: pointer; font-size: var(--text-sm); color: var(--color-text); }\n    .demo-tb button:hover, .demo-tb button:focus-visible { background: var(--color-fill); }\n    .demo-tb button[aria-pressed=\"true\"] { background: var(--color-fill); color: var(--color-text); font-weight: var(--weight-semibold); }\n    .demo-tb-sep { width: 1px; align-self: stretch; background: var(--color-border); margin-inline: var(--space-1); }\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\" aria-current=\"page\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Toolbar</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Toolbar <span class=\"dx-api-badge dx-api-badge-js\" title=\"JavaScript is required for interaction\">Requires JS</span></h1>\n        <p class=\"lede\">Grouped row of action buttons with arrow-key navigation. For editor toolbars, canvas controls, table headers — anywhere a tight cluster of related actions needs roving-tabindex keyboard support.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Toolbars present a row of actions as a single tab-stop. <kbd>Tab</kbd> enters the toolbar; arrow keys navigate between actions; <kbd>Tab</kbd> again leaves it. This avoids the \"press Tab 12 times to get past the formatting bar\" anti-pattern.</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <div class=\"demo-tb\" role=\"toolbar\" aria-label=\"Text formatting\">\n              <button type=\"button\" aria-pressed=\"false\" tabindex=\"0\">Undo</button>\n              <button type=\"button\" aria-pressed=\"false\" tabindex=\"-1\">Redo</button>\n              <span class=\"demo-tb-sep\" role=\"separator\" aria-orientation=\"vertical\"></span>\n              <button type=\"button\" aria-pressed=\"true\" tabindex=\"-1\"><strong>B</strong></button>\n              <button type=\"button\" aria-pressed=\"false\" tabindex=\"-1\"><em>I</em></button>\n              <button type=\"button\" aria-pressed=\"false\" tabindex=\"-1\"><u>U</u></button>\n              <span class=\"demo-tb-sep\" role=\"separator\" aria-orientation=\"vertical\"></span>\n              <button type=\"button\" tabindex=\"-1\">≡</button>\n              <button type=\"button\" tabindex=\"-1\">⊞</button>\n            </div>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;ren-toolbar aria-label=\"Text formatting\"&gt;\n  &lt;button&gt;Undo&lt;/button&gt;\n  &lt;button&gt;Redo&lt;/button&gt;\n  &lt;hr role=\"separator\"&gt;\n  &lt;button aria-pressed=\"true\"&gt;&lt;b&gt;B&lt;/b&gt;&lt;/button&gt;\n  &lt;button aria-pressed=\"false\"&gt;&lt;i&gt;I&lt;/i&gt;&lt;/button&gt;\n&lt;/ren-toolbar&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-toolbar</code></td><td>The wrapper. <code>role=\"toolbar\"</code>. Inline-flex row with consistent gap.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-toolbar-separator</code></td><td>Vertical divider. Use a <code>&lt;span&gt;</code> with <code>role=\"separator\"</code>.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-toolbar-group</code></td><td>Optional sub-grouping for related actions inside the toolbar.</td></tr>\n          </tbody>\n        </table>\n        <p>The Web Component handles the roving tabindex automatically — buttons inside <code>&lt;ren-toolbar&gt;</code> get the right <code>tabindex</code> attributes on mount.</p>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <h3>Keyboard</h3>\n        <div class=\"dx-keys\">\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Tab</kbd></span><span>Enter / leave the toolbar.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>←</kbd> / <kbd>→</kbd></span><span>Move focus between buttons. Wraps at the ends.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Home</kbd> / <kbd>End</kbd></span><span>Jump to the first / last button.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Enter</kbd> / <kbd>Space</kbd></span><span>Activate the focused button.</span></div>\n        </div>\n        <p>Always set <code>aria-label</code> on the toolbar so screen readers announce its purpose (\"Text formatting toolbar\").</p>\n      </section>\n\n    </main>\n  </div>\n  <script type=\"module\" src=\"../../components/composites/ren-toolbar/ren-toolbar.js\"></script>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-toolbar.html",
      "id": "docs:docs/components/ren-toolbar.html",
      "type": "docs_page",
      "name": "ren-toolbar docs"
    },
    {
      "data": {},
      "body": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Tooltip — RenDS Components</title>\n  <link rel=\"stylesheet\" href=\"../../index.css\">\n  <link rel=\"stylesheet\" href=\"../../components/index.css\">\n  <link rel=\"stylesheet\" href=\"../../themes/appearance.css\">\n  <link rel=\"stylesheet\" href=\"../../tokens/component/tokens.css\">\n  <link rel=\"stylesheet\" href=\"../../site/shell.css\">\n  <style>\n    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: visible; margin: var(--space-4) 0; }\n    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-8) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; justify-content: center; min-height: 120px; }\n    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }\n    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }\n    .dx-vrow { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }\n    .dx-vrow-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); padding-top: var(--space-3); }\n    .dx-vrow-items { padding: var(--space-3); background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }\n    @media (max-width: 720px) { .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); } .dx-vrow-label { padding-top: 0; } }\n\n  </style>\n</head>\n<body>\n  <header class=\"dx-nav\"><div class=\"dx-nav-inner\"><a href=\"../index.html\" class=\"dx-brand\"><span class=\"dx-brand-mark\">R</span><span>RenDS</span><span class=\"ren-badge ren-badge-secondary\" style=\"margin-left: var(--space-1);\">v0.13.0</span></a><nav class=\"dx-nav-menu\" aria-label=\"Primary\"><a href=\"../index.html\">Docs</a><a href=\"../components.html\" aria-current=\"page\">Components</a><a href=\"../../templates/index.html\">Templates</a><a href=\"../../create/index.html\">Theme Builder</a></nav><div class=\"dx-nav-actions\"><a href=\"https://github.com/Rensoconese/ren10\" class=\"ren-btn ren-btn-ghost ren-btn-sm\">GitHub</a><a href=\"../getting-started.html\" class=\"ren-btn ren-btn-primary ren-btn-sm\">Get started</a></div></div></header>\n\n  <div class=\"dx-shell dx-shell-grid\">\n    <aside class=\"dx-sidebar\" aria-label=\"Site navigation\">\n      <h3>Guides</h3>\n      <ul>\n        <li><a href=\"../getting-started.html\">Getting Started</a></li>\n        <li><a href=\"../theming.html\">Theming</a></li>\n        <li><a href=\"../accessibility.html\">Accessibility</a></li>\n        <li><a href=\"../cli.html\">CLI</a></li>\n      </ul>\n\n      <h3>Foundations</h3>\n      <ul>\n        <li><a href=\"../primitive-zero.html\">Primitive Zero</a></li>\n        <li><a href=\"../tokens.html\">Tokens</a></li>\n        <li><a href=\"../layouts.html\">Layouts</a></li>\n      </ul>\n\n      <h3>Primitives</h3>\n      <ul>\n        <li><a href=\"ren-button.html\">Button</a></li>\n        <li><a href=\"ren-card.html\">Card</a></li>\n        <li><a href=\"ren-badge.html\">Badge</a></li>\n        <li><a href=\"ren-tag.html\">Tag</a></li>\n        <li><a href=\"ren-link.html\">Link</a></li>\n        <li><a href=\"ren-banner.html\">Banner</a></li>\n        <li><a href=\"ren-breadcrumb.html\">Breadcrumb</a></li>\n        <li><a href=\"ren-pagination.html\">Pagination</a></li>\n        <li><a href=\"ren-separator.html\">Separator</a></li>\n        <li><a href=\"ren-avatar.html\">Avatar</a></li>\n        <li><a href=\"ren-spinner.html\">Spinner</a></li>\n        <li><a href=\"ren-skeleton.html\">Skeleton</a></li>\n        <li><a href=\"ren-kbd.html\">Keyboard Key</a></li>\n        <li><a href=\"ren-icon.html\">Icons</a></li>\n        <li><a href=\"ren-field.html\">Field</a></li>\n        <li><a href=\"ren-checkbox.html\">Checkbox</a></li>\n        <li><a href=\"ren-switch.html\">Switch</a></li>\n        <li><a href=\"ren-radio.html\">Radio</a></li>\n        <li><a href=\"ren-progress.html\">Progress</a></li>\n      </ul>\n\n      <h3>Composites</h3>\n      <ul>\n        <li><a href=\"ren-tabs.html\">Tabs</a></li>\n        <li><a href=\"ren-accordion.html\">Accordion</a></li>\n        <li><a href=\"ren-dialog.html\">Dialog</a></li>\n        <li><a href=\"ren-alert-dialog.html\">Alert Dialog</a></li>\n        <li><a href=\"ren-toast.html\">Toast</a></li>\n        <li><a href=\"ren-tooltip.html\" aria-current=\"page\">Tooltip</a></li>\n        <li><a href=\"ren-popover.html\">Popover</a></li>\n        <li><a href=\"ren-hover-card.html\">Hover Card</a></li>\n        <li><a href=\"ren-sheet.html\">Sheet</a></li>\n        <li><a href=\"ren-collapsible.html\">Collapsible</a></li>\n        <li><a href=\"ren-toolbar.html\">Toolbar</a></li>\n        <li><a href=\"ren-dropzone.html\">Dropzone</a></li>\n        <li><a href=\"ren-combobox.html\">Combobox</a></li>\n        <li><a href=\"ren-slider.html\">Slider</a></li>\n        <li><a href=\"ren-toggle-group.html\">Toggle Group</a></li>\n        <li><a href=\"ren-scroll-area.html\">Scroll Area</a></li>\n        <li><a href=\"ren-select.html\">Select</a></li>\n        <li><a href=\"ren-menu.html\">Menu</a></li>\n        <li><a href=\"ren-menubar.html\">Menubar</a></li>\n        <li><a href=\"ren-context-menu.html\">Context Menu</a></li>\n        <li><a href=\"ren-command.html\">Command Palette</a></li>\n        <li><a href=\"ren-number-field.html\">Number Field</a></li>\n        <li><a href=\"ren-otp.html\">Input OTP</a></li>\n        <li><a href=\"ren-color-picker.html\">Color Picker</a></li>\n        <li><a href=\"ren-calendar.html\">Calendar</a></li>\n        <li><a href=\"ren-date-picker.html\">Date Picker</a></li>\n        <li><a href=\"ren-date-range-picker.html\">Date Range Picker</a></li>\n        <li><a href=\"ren-carousel.html\">Carousel</a></li>\n      </ul>\n\n      <h3>Patterns</h3>\n      <ul>\n        <li><a href=\"ren-nav.html\">Nav</a></li>\n        <li><a href=\"ren-sidebar.html\">Sidebar</a></li>\n        <li><a href=\"ren-empty-state.html\">Empty State</a></li>\n        <li><a href=\"ren-table.html\">Data Table</a></li>\n        <li><a href=\"ren-form.html\">Form Validation</a></li>\n        <li><a href=\"ren-ai.html\">AI Patterns</a></li>\n      </ul>\n\n      <h3>Reference</h3>\n      <ul>\n        <li><a href=\"../components.html\">Components catalog</a></li>\n</ul>\n    </aside>\n\n    <main class=\"dx-content\">\n      <header class=\"dx-header\">\n        <nav class=\"ren-breadcrumb\" aria-label=\"Breadcrumb\" style=\"margin-bottom: var(--space-4);\"><ol><li><a href=\"../index.html\" class=\"ren-link-plain\">Docs</a></li><li><a href=\"../components.html\" class=\"ren-link-plain\">Components</a></li><li aria-current=\"page\">Tooltip</li></ol></nav>\n        <p class=\"dx-kicker\">Composite</p>\n        <h1>Tooltip <span class=\"dx-api-badge dx-api-badge-hybrid\" title=\"Renders without JS; JS adds enhancements\">Hybrid</span></h1>\n        <p class=\"lede\">Brief contextual text that appears on hover and focus. For supplementary hints — never the primary explanation. Reveals on hover after a short delay, on focus immediately, on touch via long-press.</p>\n      </header>\n\n      <section class=\"dx-section\" id=\"overview\">\n        <p class=\"dx-kicker\">About</p>\n        <h2>Overview</h2>\n        <p>Tooltips show short text labels next to icon buttons, abbreviations, or anything where a tiny extra hint helps. The trigger is always interactive (a button or link), the tooltip is always supplementary — never put critical information in a tooltip alone, because touch users may not see it.</p>\n        <div class=\"dx-callout\">\n          <p><strong>Tooltip vs Popover vs Hover Card.</strong> Tooltip = short text. Popover = rich content + keyboard interaction. Hover Card = preview content with a slightly longer hover delay (think Twitter profile previews).</p>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"demo\">\n        <p class=\"dx-kicker\">Live</p>\n        <h2>Demo</h2>\n        <p>Hover or focus the buttons below.</p>\n        <div class=\"dx-demo\">\n          <div class=\"dx-demo-preview\">\n            <button class=\"ren-btn ren-btn-secondary ren-tooltip-trigger\" type=\"button\">Save<ren-tooltip placement=\"top\" show-delay=\"0\">⌘S</ren-tooltip></button>\n            <button class=\"ren-btn ren-btn-secondary ren-tooltip-trigger\" type=\"button\" aria-label=\"Settings\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg><ren-tooltip placement=\"top\" show-delay=\"0\">Settings</ren-tooltip></button>\n            <button class=\"ren-btn ren-btn-ghost ren-tooltip-trigger\" type=\"button\">Help<ren-tooltip placement=\"top\" show-delay=\"0\">Press ? for shortcuts</ren-tooltip></button>\n          </div>\n          <pre class=\"dx-pre dx-demo-code\" tabindex=\"0\"><code>&lt;button class=\"ren-btn ren-btn-secondary ren-tooltip-trigger\" type=\"button\"&gt;\n  Save\n  &lt;ren-tooltip placement=\"top\"&gt;⌘S&lt;/ren-tooltip&gt;\n&lt;/button&gt;</code></pre>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"variants\">\n        <p class=\"dx-kicker\">Placements</p>\n        <h2>Variants</h2>\n        <div class=\"dx-vgrid\">\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Placement</span><div class=\"dx-vrow-items\">\n            <p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Four placements via <code>placement=\"top|right|bottom|left\"</code> (default <code>top</code>). The component flips to the opposite side automatically when there isn't room.</p>\n          </div></div>\n          <div class=\"dx-vrow\"><span class=\"dx-vrow-label\">Delay</span><div class=\"dx-vrow-items\">\n            <p style=\"margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);\">Default hover delay is 500 ms. Override it with <code>show-delay=\"700\"</code> for less-interruptive contexts.</p>\n          </div></div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"api\">\n        <p class=\"dx-kicker\">Reference</p>\n        <h2>API</h2>\n        <h3>CSS classes</h3>\n        <table class=\"dx-api\">\n          <thead><tr><th>Class</th><th>Effect</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">.ren-tooltip</code></td><td>The floating bubble. <code>role=\"tooltip\"</code>. Dark surface on light theme, light on dark.</td></tr>\n            <tr><td><code class=\"dx-api-name\">.ren-tooltip-arrow</code></td><td>Optional arrow pointing at the trigger.</td></tr>\n          </tbody>\n        </table>\n        <h3>Web Component attributes</h3>\n        <table class=\"dx-api dx-api-cols-4\">\n          <thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>\n          <tbody>\n            <tr><td><code class=\"dx-api-name\">placement</code></td><td><span class=\"dx-api-type\">\"top\" | \"right\" | \"bottom\" | \"left\"</span></td><td><span class=\"dx-api-default\">\"top\"</span></td><td>Preferred side. Auto-flips when there isn't room.</td></tr>\n            <tr><td><code class=\"dx-api-name\">show-delay</code></td><td><span class=\"dx-api-type\">number (ms)</span></td><td><span class=\"dx-api-default\">500</span></td><td>Delay before showing the tooltip.</td></tr>\n            <tr><td><code class=\"dx-api-name\">hide-delay</code></td><td><span class=\"dx-api-type\">number (ms)</span></td><td><span class=\"dx-api-default\">0</span></td><td>Delay before hiding the tooltip.</td></tr>\n          </tbody>\n        </table>\n      </section>\n\n      <section class=\"dx-section\" id=\"a11y\">\n        <p class=\"dx-kicker\">Inclusive by default</p>\n        <h2>Accessibility</h2>\n        <ul>\n          <li><strong>Trigger must be focusable.</strong> Tooltips on a non-interactive element aren't keyboard-reachable.</li>\n          <li><strong>Use <code>aria-describedby</code></strong> linking the trigger to the tooltip's <code>id</code>. Screen readers announce the tooltip after the trigger's accessible name.</li>\n          <li><strong>Show on focus, not just hover.</strong> Keyboard users never trigger a hover.</li>\n          <li><strong>Dismissable with Escape.</strong> Pressing Escape while the tooltip is open closes it.</li>\n          <li><strong>Don't put essential info in a tooltip.</strong> Touch users may never see it. Pair the tooltip with a visible label or icon-with-text on small viewports.</li>\n        </ul>\n        <h3>Keyboard</h3>\n        <div class=\"dx-keys\">\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Tab</kbd></span><span>Focus the trigger; tooltip shows immediately.</span></div>\n          <div class=\"dx-keyrow\"><span class=\"keys\"><kbd>Esc</kbd></span><span>Hides the tooltip while keeping focus on the trigger.</span></div>\n        </div>\n      </section>\n\n      <section class=\"dx-section\" id=\"examples\">\n        <p class=\"dx-kicker\">Patterns</p>\n        <h2>Examples</h2>\n        <h3>Icon-only button with hint</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;button class=\"ren-btn ren-btn-ghost ren-tooltip-trigger\" type=\"button\" aria-label=\"Settings\"&gt;\n  &lt;!-- gear SVG --&gt;\n  &lt;ren-tooltip placement=\"bottom\"&gt;Settings&lt;/ren-tooltip&gt;\n&lt;/button&gt;</code></div>\n        <h3>Keyboard shortcut hint</h3>\n        <div class=\"dx-pre\" tabindex=\"0\"><code>&lt;button class=\"ren-btn ren-btn-primary ren-tooltip-trigger\" type=\"button\"&gt;\n  Save\n  &lt;ren-tooltip&gt;⌘S to save&lt;/ren-tooltip&gt;\n&lt;/button&gt;</code></div>\n      </section>\n\n    </main>\n  </div>\n  <script type=\"module\" src=\"../../components/composites/ren-tooltip/ren-tooltip.js\"></script>\n  <script src=\"../../site/shell.js\" defer></script>\n</body>\n</html>\n",
      "path": "docs/components/ren-tooltip.html",
      "id": "docs:docs/components/ren-tooltip.html",
      "type": "docs_page",
      "name": "ren-tooltip docs"
    },
    {
      "data": {},
      "body": "<!doctype html>\n<html lang=\"en\" data-theme=\"default\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <title>RenDS · AI / status panel example</title>\n\n  <link rel=\"stylesheet\" href=\"../index.css\">\n  <link rel=\"stylesheet\" href=\"../components/index.css\">\n\n  <script type=\"module\" src=\"../components/primitives/ren-button/ren-button.js\"></script>\n</head>\n<body>\n  <!--\n    AI / status panel: ren-with-sidebar with main content on the left\n    and a secondary AI rail on the right. Use ren-ai pattern for the\n    assistant slot.\n  -->\n  <main class=\"ren-with-sidebar\" style=\"--sidebar-width: 22rem\">\n    <section class=\"ren-stack-lg\" style=\"padding: var(--space-6)\">\n      <header class=\"ren-stack-xs\">\n        <h1>Compose</h1>\n        <p style=\"color: var(--color-text-muted)\">Drafting a release announcement.</p>\n      </header>\n\n      <article class=\"ren-card\">\n        <div class=\"ren-card-body ren-stack\">\n          <h2>Announcing RenDS 0.8.2</h2>\n          <p>RenDS 0.8.2 ships an AI-compliance layer …</p>\n          <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>\n        </div>\n      </article>\n    </section>\n\n    <!-- AI rail -->\n    <aside class=\"ren-stack\" style=\"padding: var(--space-6); border-inline-start: var(--stroke-1) solid var(--color-border)\">\n      <header class=\"ren-stack-xs\">\n        <h2 class=\"ren-row\" style=\"gap: var(--space-2)\">\n          <span class=\"ren-ai-indicator\" aria-hidden=\"true\"></span>\n          Assistant\n        </h2>\n        <p style=\"color: var(--color-text-muted)\">Ask for a rewrite, summary, or tone shift.</p>\n      </header>\n\n      <ol class=\"ren-ai-thread ren-stack\" style=\"list-style: none; padding: 0; margin: 0\">\n        <li class=\"ren-ai-message ren-ai-message-user\">\n          <p>Tighten the opening paragraph.</p>\n        </li>\n        <li class=\"ren-ai-message ren-ai-message-assistant\">\n          <p>Here's a tighter version: \"RenDS 0.8.2 adds an AI-compliance\n          layer with stricter token enforcement, golden examples, and\n          per-component aiHints.\"</p>\n          <div class=\"ren-cluster\">\n            <button class=\"ren-btn ren-btn-sm ren-btn-secondary\" type=\"button\">Insert</button>\n            <button class=\"ren-btn ren-btn-sm ren-btn-ghost\" type=\"button\">Try again</button>\n          </div>\n        </li>\n        <li class=\"ren-ai-message ren-ai-message-assistant ren-ai-message-thinking\" aria-live=\"polite\">\n          <span class=\"ren-spinner\" aria-hidden=\"true\"></span>\n          <span>Thinking…</span>\n        </li>\n      </ol>\n\n      <form class=\"ren-stack-sm\" novalidate>\n        <label class=\"ren-sr-only\" for=\"ai-prompt\">Ask the assistant</label>\n        <textarea id=\"ai-prompt\" class=\"ren-input\" rows=\"3\"\n                  placeholder=\"Make it shorter, change the tone, etc.\"></textarea>\n        <div class=\"ren-row-spread\">\n          <span class=\"ren-tag\" data-status=\"info\">⌘+Enter to send</span>\n          <button class=\"ren-btn ren-btn-sm\" type=\"submit\">Ask</button>\n        </div>\n      </form>\n    </aside>\n  </main>\n</body>\n</html>\n",
      "path": "examples/ai-panel.html",
      "id": "example:examples/ai-panel.html",
      "type": "example",
      "name": "ai-panel.html"
    },
    {
      "data": {},
      "body": "<!doctype html>\n<html lang=\"en\" data-theme=\"default\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <title>RenDS · App sidebar example</title>\n\n  <link rel=\"stylesheet\" href=\"../index.css\">\n  <link rel=\"stylesheet\" href=\"../components/index.css\">\n\n  <script type=\"module\" src=\"../components/patterns/ren-sidebar/ren-sidebar.js\"></script>\n</head>\n<body>\n  <!--\n    App shell with collapsible sidebar.\n    Use this as the outer skeleton for any authenticated multi-page product.\n    The sidebar is the only navigation; routes go in the sidebar.\n  -->\n  <div class=\"ren-sidebar-layout\">\n    <ren-sidebar class=\"ren-sidebar\" aria-label=\"Primary\">\n      <div class=\"ren-sidebar-header\">\n        <a class=\"ren-sidebar-item\" href=\"/\" aria-label=\"Acme home\">\n          <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">⌂</span>\n          <span class=\"ren-sidebar-item-text\">Acme</span>\n        </a>\n      </div>\n\n      <nav class=\"ren-sidebar-nav\" aria-label=\"Workspace\">\n        <a class=\"ren-sidebar-item active\" href=\"/inbox\" aria-current=\"page\">\n          <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">📥</span>\n          <span class=\"ren-sidebar-item-text\">Inbox</span>\n        </a>\n        <a class=\"ren-sidebar-item\" href=\"/today\">\n          <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">📅</span>\n          <span class=\"ren-sidebar-item-text\">Today</span>\n        </a>\n        <a class=\"ren-sidebar-item\" href=\"/upcoming\">\n          <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">⏭</span>\n          <span class=\"ren-sidebar-item-text\">Upcoming</span>\n        </a>\n      </nav>\n\n      <div class=\"ren-sidebar-section\">\n        <div class=\"ren-sidebar-section-label\">Projects</div>\n        <a class=\"ren-sidebar-item\" href=\"/p/atlas\">\n          <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">●</span>\n          <span class=\"ren-sidebar-item-text\">Atlas</span>\n        </a>\n        <a class=\"ren-sidebar-item\" href=\"/p/mercury\">\n          <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">●</span>\n          <span class=\"ren-sidebar-item-text\">Mercury</span>\n        </a>\n        <a class=\"ren-sidebar-item\" href=\"/p/nimbus\">\n          <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">●</span>\n          <span class=\"ren-sidebar-item-text\">Nimbus</span>\n        </a>\n      </div>\n\n      <div class=\"ren-sidebar-footer\">\n        <a class=\"ren-sidebar-item\" href=\"/help\">\n          <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">？</span>\n          <span class=\"ren-sidebar-item-text\">Help</span>\n        </a>\n        <a class=\"ren-sidebar-item\" href=\"/settings\">\n          <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">⚙</span>\n          <span class=\"ren-sidebar-item-text\">Settings</span>\n        </a>\n      </div>\n\n      <button class=\"ren-sidebar-toggle\" type=\"button\" aria-label=\"Toggle sidebar\"></button>\n    </ren-sidebar>\n\n    <main class=\"ren-sidebar-main\">\n      <div class=\"ren-stack-lg\" style=\"padding: var(--space-6)\">\n        <header class=\"ren-row-spread\">\n          <h1>Inbox</h1>\n          <button class=\"ren-btn\" type=\"button\">New task</button>\n        </header>\n\n        <ul class=\"ren-stack\" style=\"list-style: none; padding: 0; margin: 0\">\n          <li class=\"ren-card ren-card-interactive ren-row-spread\" tabindex=\"0\">\n            <span>Review the Q3 launch checklist</span>\n            <span class=\"ren-badge ren-badge-warning\">Today</span>\n          </li>\n          <li class=\"ren-card ren-card-interactive ren-row-spread\" tabindex=\"0\">\n            <span>Approve invoice from Iván</span>\n            <span class=\"ren-badge\">Tomorrow</span>\n          </li>\n          <li class=\"ren-card ren-card-interactive ren-row-spread\" tabindex=\"0\">\n            <span>Plan offsite logistics</span>\n            <span class=\"ren-badge\">Next week</span>\n          </li>\n        </ul>\n      </div>\n    </main>\n  </div>\n</body>\n</html>\n",
      "path": "examples/app-sidebar.html",
      "id": "example:examples/app-sidebar.html",
      "type": "example",
      "name": "app-sidebar.html"
    },
    {
      "data": {},
      "body": "<!doctype html>\n<html lang=\"en\" data-theme=\"default\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <title>RenDS · Auth form example</title>\n\n  <!-- Foundation: tokens, reset, classless, utilities, layouts -->\n  <link rel=\"stylesheet\" href=\"../index.css\">\n\n  <!-- Component CSS (use components/index.css if importing many) -->\n  <link rel=\"stylesheet\" href=\"../components/primitives/ren-card/ren-card.css\">\n  <link rel=\"stylesheet\" href=\"../components/primitives/ren-field/ren-field.css\">\n  <link rel=\"stylesheet\" href=\"../components/primitives/ren-button/ren-button.css\">\n  <link rel=\"stylesheet\" href=\"../components/primitives/ren-link/ren-link.css\">\n  <link rel=\"stylesheet\" href=\"../components/primitives/ren-separator/ren-separator.css\">\n  <link rel=\"stylesheet\" href=\"../components/patterns/ren-form/ren-form.css\">\n\n  <!-- Component JS -->\n  <script type=\"module\" src=\"../components/primitives/ren-field/ren-field.js\"></script>\n  <script type=\"module\" src=\"../components/patterns/ren-form/ren-form.js\"></script>\n</head>\n<body>\n  <!--\n    Layout skeleton: ren-cover centers a full-viewport stack with optional\n    top / bottom regions. The middle child is the centered surface.\n  -->\n  <div class=\"ren-cover\">\n    <header></header>\n\n    <main class=\"ren-center-narrow\">\n      <article class=\"ren-card ren-stack-lg\">\n        <header class=\"ren-stack-sm\">\n          <h1 class=\"ren-card-title\">Sign in to Acme</h1>\n          <p class=\"ren-card-description\">Welcome back. Enter your credentials to continue.</p>\n        </header>\n\n        <ren-form data-validate=\"onTouched\">\n          <form class=\"ren-form ren-stack\" novalidate>\n            <div class=\"ren-form-error-summary\" role=\"alert\" tabindex=\"-1\" hidden>\n              <strong>Please fix the following errors:</strong>\n              <ul></ul>\n            </div>\n\n            <ren-field data-rules=\"required|email\">\n              <label>Email</label>\n              <input class=\"ren-input\" type=\"email\" name=\"email\" autocomplete=\"email\" required>\n              <span data-error></span>\n            </ren-field>\n\n            <ren-field data-rules=\"required|min:8\">\n              <label>Password</label>\n              <input class=\"ren-input\" type=\"password\" name=\"password\" autocomplete=\"current-password\" required minlength=\"8\">\n              <span data-description>At least 8 characters.</span>\n              <span data-error></span>\n            </ren-field>\n\n            <div class=\"ren-row-spread\">\n              <label class=\"ren-row\" style=\"gap: var(--space-2)\">\n                <input type=\"checkbox\" name=\"remember\">\n                <span>Remember me</span>\n              </label>\n              <a class=\"ren-link\" href=\"/forgot\">Forgot password?</a>\n            </div>\n\n            <div class=\"ren-form-actions\">\n              <button class=\"ren-btn ren-btn-full\" type=\"submit\">Sign in</button>\n            </div>\n          </form>\n        </ren-form>\n\n        <hr class=\"ren-separator\" aria-hidden=\"true\">\n\n        <a class=\"ren-btn ren-btn-secondary ren-btn-full\" href=\"/auth/google\">\n          Continue with Google\n        </a>\n\n        <p class=\"ren-stack-xs\" style=\"text-align: center; color: var(--color-text-muted)\">\n          Don't have an account?\n          <a class=\"ren-link\" href=\"/signup\">Create one</a>\n        </p>\n      </article>\n    </main>\n\n    <footer></footer>\n  </div>\n\n  <script type=\"module\">\n    document.querySelector('ren-form').addEventListener('ren-submit', (event) => {\n      event.preventDefault();\n      // POST event.detail.values to your auth endpoint here.\n      console.log('submit', event.detail.values);\n    });\n  </script>\n</body>\n</html>\n",
      "path": "examples/auth-form.html",
      "id": "example:examples/auth-form.html",
      "type": "example",
      "name": "auth-form.html"
    },
    {
      "data": {},
      "body": "<!doctype html>\n<html lang=\"en\" data-theme=\"default\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <title>RenDS · Dashboard shell example</title>\n\n  <link rel=\"stylesheet\" href=\"../index.css\">\n  <link rel=\"stylesheet\" href=\"../components/index.css\">\n\n  <script type=\"module\" src=\"../components/patterns/ren-sidebar/ren-sidebar.js\"></script>\n  <script type=\"module\" src=\"../components/primitives/ren-button/ren-button.js\"></script>\n  <script type=\"module\" src=\"../components/primitives/ren-field/ren-field.js\"></script>\n</head>\n<body>\n  <!--\n    App shell: ren-sidebar-layout = sidebar (left) + main (right).\n    Sidebar persists across routes; main hosts the page-level content.\n    Layout uses ren-stack-lg to space large sections inside main.\n  -->\n  <div class=\"ren-sidebar-layout\">\n    <ren-sidebar class=\"ren-sidebar\" aria-label=\"Primary\">\n      <div class=\"ren-sidebar-header\">\n        <a class=\"ren-sidebar-item\" href=\"/\" aria-label=\"Acme home\">\n          <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">⌂</span>\n          <span class=\"ren-sidebar-item-text\">Acme</span>\n        </a>\n      </div>\n\n      <nav class=\"ren-sidebar-nav\" aria-label=\"Main\">\n        <a class=\"ren-sidebar-item active\" href=\"/dashboard\" aria-current=\"page\">\n          <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">📊</span>\n          <span class=\"ren-sidebar-item-text\">Dashboard</span>\n        </a>\n        <a class=\"ren-sidebar-item\" href=\"/projects\">\n          <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">📁</span>\n          <span class=\"ren-sidebar-item-text\">Projects</span>\n        </a>\n        <a class=\"ren-sidebar-item\" href=\"/team\">\n          <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">👥</span>\n          <span class=\"ren-sidebar-item-text\">Team</span>\n        </a>\n        <a class=\"ren-sidebar-item\" href=\"/billing\">\n          <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">💳</span>\n          <span class=\"ren-sidebar-item-text\">Billing</span>\n        </a>\n      </nav>\n\n      <div class=\"ren-sidebar-section\">\n        <div class=\"ren-sidebar-section-label\">Account</div>\n        <a class=\"ren-sidebar-item\" href=\"/settings\">\n          <span class=\"ren-sidebar-item-icon\" aria-hidden=\"true\">⚙</span>\n          <span class=\"ren-sidebar-item-text\">Settings</span>\n        </a>\n      </div>\n\n      <button class=\"ren-sidebar-toggle\" type=\"button\" aria-label=\"Toggle sidebar\"></button>\n    </ren-sidebar>\n\n    <main class=\"ren-sidebar-main\">\n      <div class=\"ren-center-wide ren-stack-lg\" style=\"padding: var(--space-6)\">\n        <!-- Page header -->\n        <header class=\"ren-row-spread\">\n          <div class=\"ren-stack-xs\">\n            <h1>Dashboard</h1>\n            <p style=\"color: var(--color-text-muted)\">Welcome back, Renato.</p>\n          </div>\n          <div class=\"ren-cluster\">\n            <button class=\"ren-btn ren-btn-secondary\" type=\"button\">Export</button>\n            <button class=\"ren-btn\" type=\"button\">New project</button>\n          </div>\n        </header>\n\n        <!-- Stat tiles -->\n        <section class=\"ren-grid-3\" aria-label=\"Key metrics\">\n          <article class=\"ren-card\">\n            <div class=\"ren-card-header\">\n              <p class=\"ren-card-description\">Active users</p>\n            </div>\n            <div class=\"ren-card-body ren-row-spread\">\n              <strong style=\"font-size: var(--text-4xl)\">12,438</strong>\n              <span class=\"ren-badge ren-badge-success\">+8.2%</span>\n            </div>\n          </article>\n\n          <article class=\"ren-card\">\n            <div class=\"ren-card-header\">\n              <p class=\"ren-card-description\">Revenue (MRR)</p>\n            </div>\n            <div class=\"ren-card-body ren-row-spread\">\n              <strong style=\"font-size: var(--text-4xl)\">$48.2k</strong>\n              <span class=\"ren-badge ren-badge-success\">+3.1%</span>\n            </div>\n          </article>\n\n          <article class=\"ren-card\">\n            <div class=\"ren-card-header\">\n              <p class=\"ren-card-description\">Churn</p>\n            </div>\n            <div class=\"ren-card-body ren-row-spread\">\n              <strong style=\"font-size: var(--text-4xl)\">1.4%</strong>\n              <span class=\"ren-badge ren-badge-danger\">+0.3%</span>\n            </div>\n          </article>\n        </section>\n\n        <!-- Recent activity -->\n        <section class=\"ren-card ren-stack\">\n          <header class=\"ren-card-header ren-row-spread\">\n            <h2 class=\"ren-card-title\">Recent projects</h2>\n            <a class=\"ren-link\" href=\"/projects\">View all</a>\n          </header>\n          <div class=\"ren-card-body\">\n            <table class=\"ren-table\">\n              <caption class=\"ren-sr-only\">Recent projects</caption>\n              <thead>\n                <tr><th scope=\"col\">Name</th><th scope=\"col\">Owner</th><th scope=\"col\">Status</th><th scope=\"col\">Updated</th></tr>\n              </thead>\n              <tbody>\n                <tr><td>Atlas</td><td>Renato</td><td><span class=\"ren-badge ren-badge-success\">Active</span></td><td>2m ago</td></tr>\n                <tr><td>Mercury</td><td>Sofía</td><td><span class=\"ren-badge ren-badge-warning\">Paused</span></td><td>1h ago</td></tr>\n                <tr><td>Nimbus</td><td>Iván</td><td><span class=\"ren-badge\">Draft</span></td><td>Yesterday</td></tr>\n              </tbody>\n            </table>\n          </div>\n        </section>\n      </div>\n    </main>\n  </div>\n</body>\n</html>\n",
      "path": "examples/dashboard-shell.html",
      "id": "example:examples/dashboard-shell.html",
      "type": "example",
      "name": "dashboard-shell.html"
    },
    {
      "data": {},
      "body": "<!doctype html>\n<html lang=\"en\" data-theme=\"default\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <title>RenDS · Data table example</title>\n\n  <link rel=\"stylesheet\" href=\"../index.css\">\n  <link rel=\"stylesheet\" href=\"../components/index.css\">\n\n  <script type=\"module\" src=\"../components/primitives/ren-field/ren-field.js\"></script>\n</head>\n<body>\n  <!--\n    List management screen: filters at the top, table below, pagination\n    underneath. ren-center caps width, ren-stack-lg spaces sections.\n  -->\n  <main class=\"ren-center-wide ren-stack-lg\" style=\"padding: var(--space-6)\">\n    <header class=\"ren-row-spread\">\n      <div class=\"ren-stack-xs\">\n        <h1>Users</h1>\n        <p style=\"color: var(--color-text-muted)\">3,482 active accounts.</p>\n      </div>\n      <div class=\"ren-cluster\">\n        <button class=\"ren-btn ren-btn-secondary\" type=\"button\">Export CSV</button>\n        <button class=\"ren-btn\" type=\"button\">Invite user</button>\n      </div>\n    </header>\n\n    <!-- Filters -->\n    <section class=\"ren-card\">\n      <div class=\"ren-card-body ren-row\" style=\"gap: var(--space-3); flex-wrap: wrap\">\n        <ren-field style=\"flex: 1; min-width: 12rem\">\n          <label class=\"ren-sr-only\">Search users</label>\n          <span class=\"ren-input-wrapper\">\n            <span class=\"ren-input-icon\" aria-hidden=\"true\">🔍</span>\n            <input class=\"ren-input\" type=\"search\" name=\"q\" placeholder=\"Search by name or email\">\n          </span>\n        </ren-field>\n\n        <ren-field>\n          <label class=\"ren-sr-only\">Filter role</label>\n          <select class=\"ren-input\" name=\"role\">\n            <option>All roles</option>\n            <option>Admin</option>\n            <option>Member</option>\n            <option>Billing</option>\n          </select>\n        </ren-field>\n\n        <ren-field>\n          <label class=\"ren-sr-only\">Filter status</label>\n          <select class=\"ren-input\" name=\"status\">\n            <option>All statuses</option>\n            <option>Active</option>\n            <option>Pending</option>\n            <option>Suspended</option>\n          </select>\n        </ren-field>\n      </div>\n    </section>\n\n    <!-- Table -->\n    <section class=\"ren-card\">\n      <table class=\"ren-table\">\n        <caption class=\"ren-sr-only\">Users</caption>\n        <thead>\n          <tr>\n            <th scope=\"col\" style=\"width: 2rem\">\n              <label class=\"ren-checkbox\">\n                <input type=\"checkbox\" aria-label=\"Select all\">\n                <span class=\"ren-checkbox-control\"></span>\n              </label>\n            </th>\n            <th scope=\"col\">Name</th>\n            <th scope=\"col\">Email</th>\n            <th scope=\"col\">Role</th>\n            <th scope=\"col\">Status</th>\n            <th scope=\"col\">Last seen</th>\n            <th scope=\"col\"><span class=\"ren-sr-only\">Actions</span></th>\n          </tr>\n        </thead>\n        <tbody>\n          <tr>\n            <td>\n              <label class=\"ren-checkbox\">\n                <input type=\"checkbox\" aria-label=\"Select Sofía\">\n                <span class=\"ren-checkbox-control\"></span>\n              </label>\n            </td>\n            <td>\n              <div class=\"ren-row\" style=\"gap: var(--space-2)\">\n                <span class=\"ren-avatar\" aria-hidden=\"true\"><span>S</span></span>\n                <span>Sofía Reyes</span>\n              </div>\n            </td>\n            <td>sofia@acme.io</td>\n            <td><span class=\"ren-tag ren-tag-primary\">Admin</span></td>\n            <td><span class=\"ren-badge ren-badge-success\">Active</span></td>\n            <td>2m ago</td>\n            <td>\n              <button class=\"ren-btn ren-btn-ghost ren-btn-sm\" type=\"button\" aria-label=\"More actions\">⋯</button>\n            </td>\n          </tr>\n          <tr>\n            <td>\n              <label class=\"ren-checkbox\">\n                <input type=\"checkbox\" aria-label=\"Select Iván\">\n                <span class=\"ren-checkbox-control\"></span>\n              </label>\n            </td>\n            <td>\n              <div class=\"ren-row\" style=\"gap: var(--space-2)\">\n                <span class=\"ren-avatar\" aria-hidden=\"true\"><span>I</span></span>\n                <span>Iván Castro</span>\n              </div>\n            </td>\n            <td>ivan@acme.io</td>\n            <td><span class=\"ren-tag\">Member</span></td>\n            <td><span class=\"ren-badge ren-badge-warning\">Pending</span></td>\n            <td>1h ago</td>\n            <td>\n              <button class=\"ren-btn ren-btn-ghost ren-btn-sm\" type=\"button\" aria-label=\"More actions\">⋯</button>\n            </td>\n          </tr>\n          <tr>\n            <td>\n              <label class=\"ren-checkbox\">\n                <input type=\"checkbox\" aria-label=\"Select Lía\">\n                <span class=\"ren-checkbox-control\"></span>\n              </label>\n            </td>\n            <td>\n              <div class=\"ren-row\" style=\"gap: var(--space-2)\">\n                <span class=\"ren-avatar\" aria-hidden=\"true\"><span>L</span></span>\n                <span>Lía Vargas</span>\n              </div>\n            </td>\n            <td>lia@acme.io</td>\n            <td><span class=\"ren-tag\">Member</span></td>\n            <td><span class=\"ren-badge ren-badge-danger\">Suspended</span></td>\n            <td>3d ago</td>\n            <td>\n              <button class=\"ren-btn ren-btn-ghost ren-btn-sm\" type=\"button\" aria-label=\"More actions\">⋯</button>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    </section>\n\n    <!-- Pagination -->\n    <nav class=\"ren-pagination ren-row-spread\" aria-label=\"Pagination\">\n      <span style=\"color: var(--color-text-muted)\">Showing 1–3 of 3,482</span>\n      <div class=\"ren-cluster\">\n        <button class=\"ren-btn ren-btn-ghost\" type=\"button\" disabled>Previous</button>\n        <button class=\"ren-btn ren-btn-ghost\" type=\"button\">Next</button>\n      </div>\n    </nav>\n  </main>\n</body>\n</html>\n",
      "path": "examples/data-table.html",
      "id": "example:examples/data-table.html",
      "type": "example",
      "name": "data-table.html"
    },
    {
      "data": {},
      "body": "<!doctype html>\n<html lang=\"en\" data-theme=\"default\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <title>RenDS · Dialog workflow example</title>\n\n  <link rel=\"stylesheet\" href=\"../index.css\">\n  <link rel=\"stylesheet\" href=\"../components/index.css\">\n\n  <script type=\"module\" src=\"../components/composites/ren-dialog/ren-dialog.js\"></script>\n  <script type=\"module\" src=\"../components/primitives/ren-field/ren-field.js\"></script>\n  <script type=\"module\" src=\"../components/patterns/ren-form/ren-form.js\"></script>\n</head>\n<body>\n  <main class=\"ren-center ren-stack-lg\" style=\"padding: var(--space-6)\">\n    <header class=\"ren-stack-xs\">\n      <h1>Project: Atlas</h1>\n      <p style=\"color: var(--color-text-muted)\">Manage settings and dangerous operations.</p>\n    </header>\n\n    <section class=\"ren-card ren-stack\">\n      <header class=\"ren-card-header\">\n        <h2 class=\"ren-card-title\">Quick actions</h2>\n      </header>\n      <div class=\"ren-card-body ren-cluster\">\n        <button class=\"ren-btn ren-btn-secondary\" type=\"button\" data-dialog-trigger=\"invite-dialog\">\n          Invite collaborator\n        </button>\n        <button class=\"ren-btn ren-btn-danger\" type=\"button\" data-dialog-trigger=\"confirm-delete\">\n          Delete project\n        </button>\n      </div>\n    </section>\n\n    <!--\n      Modal dialog with form.\n      <ren-dialog> wraps a real <dialog>. The form uses method=\"dialog\"\n      so the browser closes the dialog on submit.\n    -->\n    <ren-dialog id=\"invite-dialog\">\n      <dialog class=\"ren-dialog ren-dialog-md\">\n        <div class=\"ren-dialog-header\">\n          <h2 class=\"ren-dialog-title\">Invite collaborator</h2>\n          <button class=\"ren-dialog-close\" data-dialog-close aria-label=\"Close\"></button>\n        </div>\n\n        <form method=\"dialog\" class=\"ren-dialog-body ren-stack\" id=\"invite-form\">\n          <ren-field>\n            <label>Email address</label>\n            <input class=\"ren-input\" type=\"email\" name=\"email\" autocomplete=\"email\" required>\n          </ren-field>\n          <ren-field>\n            <label>Role</label>\n            <select class=\"ren-input\" name=\"role\">\n              <option value=\"viewer\">Viewer</option>\n              <option value=\"editor\" selected>Editor</option>\n              <option value=\"admin\">Admin</option>\n            </select>\n          </ren-field>\n        </form>\n\n        <div class=\"ren-dialog-footer\">\n          <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-dialog-close>Cancel</button>\n          <button class=\"ren-btn\" type=\"submit\" form=\"invite-form\" data-dialog-close=\"invite\">\n            Send invite\n          </button>\n        </div>\n      </dialog>\n    </ren-dialog>\n\n    <!--\n      Alert dialog: critical destructive confirmation.\n      `alert` disables backdrop dismissal so it cannot be closed accidentally.\n    -->\n    <ren-dialog id=\"confirm-delete\" alert>\n      <dialog class=\"ren-dialog ren-dialog-sm ren-alert-dialog\">\n        <div class=\"ren-dialog-header\">\n          <h2 class=\"ren-dialog-title\">Delete project Atlas?</h2>\n        </div>\n        <div class=\"ren-dialog-body\">\n          <p>This permanently removes the project, its members, and all\n          historical data. This action cannot be undone.</p>\n        </div>\n        <div class=\"ren-dialog-footer\">\n          <button type=\"button\" class=\"ren-btn ren-btn-secondary\" data-dialog-close>Cancel</button>\n          <button type=\"button\" class=\"ren-btn ren-btn-danger\" data-dialog-close=\"delete\">\n            Yes, delete\n          </button>\n        </div>\n      </dialog>\n    </ren-dialog>\n  </main>\n\n  <script type=\"module\">\n    document.querySelector('#invite-dialog').addEventListener('ren-close', (event) => {\n      if (event.detail.returnValue === 'invite') {\n        const form = document.getElementById('invite-form');\n        const data = Object.fromEntries(new FormData(form));\n        console.log('invite', data);\n        // Send invite via API here.\n      }\n    });\n\n    document.querySelector('#confirm-delete').addEventListener('ren-close', (event) => {\n      if (event.detail.returnValue === 'delete') {\n        console.log('delete confirmed');\n      }\n    });\n  </script>\n</body>\n</html>\n",
      "path": "examples/dialog-workflow.html",
      "id": "example:examples/dialog-workflow.html",
      "type": "example",
      "name": "dialog-workflow.html"
    },
    {
      "data": {},
      "body": "<!doctype html>\n<html lang=\"en\" data-theme=\"default\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <title>RenDS · Settings form example</title>\n\n  <link rel=\"stylesheet\" href=\"../index.css\">\n  <link rel=\"stylesheet\" href=\"../components/index.css\">\n\n  <script type=\"module\" src=\"../components/primitives/ren-field/ren-field.js\"></script>\n  <script type=\"module\" src=\"../components/patterns/ren-form/ren-form.js\"></script>\n</head>\n<body>\n  <!--\n    Settings: ren-center caps content width, ren-stack-lg spaces sections,\n    each section is a ren-card with a ren-form inside.\n  -->\n  <main class=\"ren-center ren-stack-lg\" style=\"padding: var(--space-6) var(--space-4)\">\n    <header class=\"ren-stack-xs\">\n      <h1>Settings</h1>\n      <p style=\"color: var(--color-text-muted)\">Manage your profile, preferences, and security.</p>\n    </header>\n\n    <!-- Profile section -->\n    <section class=\"ren-card\">\n      <header class=\"ren-card-header\">\n        <h2 class=\"ren-card-title\">Profile</h2>\n        <p class=\"ren-card-description\">This information will be displayed publicly.</p>\n      </header>\n\n      <ren-form data-validate=\"onTouched\">\n        <form class=\"ren-form ren-stack\" novalidate>\n          <div class=\"ren-form-row\">\n            <ren-field data-rules=\"required\">\n              <label>First name</label>\n              <input class=\"ren-input\" type=\"text\" name=\"first\" autocomplete=\"given-name\" required>\n            </ren-field>\n\n            <ren-field data-rules=\"required\">\n              <label>Last name</label>\n              <input class=\"ren-input\" type=\"text\" name=\"last\" autocomplete=\"family-name\" required>\n            </ren-field>\n          </div>\n\n          <ren-field data-rules=\"required|email\">\n            <label>Email</label>\n            <input class=\"ren-input\" type=\"email\" name=\"email\" autocomplete=\"email\" required>\n            <span data-description>We'll send a confirmation link to this address.</span>\n            <span data-error></span>\n          </ren-field>\n\n          <ren-field>\n            <label>Bio</label>\n            <textarea class=\"ren-input\" name=\"bio\" rows=\"4\" maxlength=\"240\"></textarea>\n            <span data-description>Up to 240 characters.</span>\n          </ren-field>\n\n          <div class=\"ren-form-actions\">\n            <button class=\"ren-btn ren-btn-secondary\" type=\"button\">Reset</button>\n            <button class=\"ren-btn\" type=\"submit\">Save profile</button>\n          </div>\n        </form>\n      </ren-form>\n    </section>\n\n    <!-- Notifications -->\n    <section class=\"ren-card\">\n      <header class=\"ren-card-header\">\n        <h2 class=\"ren-card-title\">Notifications</h2>\n        <p class=\"ren-card-description\">Choose how we contact you.</p>\n      </header>\n\n      <form class=\"ren-card-body ren-stack\" novalidate>\n        <fieldset class=\"ren-stack-sm\" style=\"border: 0; padding: 0; margin: 0\">\n          <legend class=\"ren-field-label\">Email</legend>\n          <label class=\"ren-checkbox\">\n            <input type=\"checkbox\" name=\"email_product\" checked>\n            <span class=\"ren-checkbox-control\"></span>\n            <span>Product updates</span>\n          </label>\n          <label class=\"ren-checkbox\">\n            <input type=\"checkbox\" name=\"email_security\" checked>\n            <span class=\"ren-checkbox-control\"></span>\n            <span>Security alerts</span>\n          </label>\n          <label class=\"ren-checkbox\">\n            <input type=\"checkbox\" name=\"email_marketing\">\n            <span class=\"ren-checkbox-control\"></span>\n            <span>Marketing emails</span>\n          </label>\n        </fieldset>\n\n        <div class=\"ren-form-actions\">\n          <button class=\"ren-btn\" type=\"submit\">Save notifications</button>\n        </div>\n      </form>\n    </section>\n\n    <!-- Danger zone -->\n    <section class=\"ren-card ren-card-outline\" data-status=\"danger\">\n      <header class=\"ren-card-header\">\n        <h2 class=\"ren-card-title\">Danger zone</h2>\n        <p class=\"ren-card-description\">Permanent actions. Double-check before continuing.</p>\n      </header>\n      <div class=\"ren-card-body ren-row-spread\">\n        <p>Delete your account and all associated data.</p>\n        <button class=\"ren-btn ren-btn-danger\" type=\"button\" data-dialog-trigger=\"confirm-delete\">\n          Delete account\n        </button>\n      </div>\n    </section>\n  </main>\n</body>\n</html>\n",
      "path": "examples/settings-form.html",
      "id": "example:examples/settings-form.html",
      "type": "example",
      "name": "settings-form.html"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — <ren-accordion> Component\n * ═══════════════════════════════════\n * CSS-first accordion using native <details><summary>\n *\n * Features:\n *   - Smooth height animation using ::details-content\n *   - Chevron rotation on open state\n *   - Multiple variants: default, bordered, flush\n *   - Reduced motion support\n *   - Dark mode support (via semantic tokens)\n *   - Fallback for browsers without ::details-content support\n */\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* ACCORDION CONTAINER */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion {\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  border-top: 1px solid var(--color-border);\n\n  /* Default variant: divider lines between items */\n  & > details {\n    border-bottom: 1px solid var(--color-border);\n\n    &:last-child {\n      border-bottom: none;\n    }\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* TRIGGER / SUMMARY ELEMENT */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion-trigger,\n.ren-accordion > details > summary {\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  justify-content: space-between;\n  padding: var(--space-3) var(--space-4);\n  cursor: pointer;\n  font-weight: var(--label-lg-weight);\n  font-size: var(--label-lg-size);\n  line-height: 1.4;\n  color: var(--color-text);\n  background-color: transparent;\n  border: none;\n  min-height: var(--size-lg);\n  width: 100%;\n  text-align: start;\n  user-select: none;\n  list-style: none;\n  transition: var(--transition-tactile);\n\n  /* Remove default browser marker */\n  &::marker,\n  &::-webkit-details-marker {\n    display: none;\n    content: '';\n  }\n\n  /* Chevron icon via ::after pseudo-element */\n  &::after {\n    content: '';\n    display: inline-block;\n    width: 20px;\n    height: 20px;\n    margin-inline-start: var(--space-3);\n    flex-shrink: 0;\n    background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"></polyline></svg>');\n    background-repeat: no-repeat;\n    background-position: center;\n    background-size: contain;\n    transition: transform var(--duration-enter) var(--ease-enter);\n      border: none;\n    transform: rotate(0deg);\n  }\n\n  /* Hover state */\n  &:hover {\n    background-color: var(--color-fill);\n  }\n\n  /* Active state */\n  &:active {\n    background-color: var(--color-fill-active);\n  }\n\n  /* Focus ring */\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: -2px;\n  }\n\n  /* Disabled state */\n  &:disabled,\n  &[aria-disabled=\"true\"] {\n    opacity: 0.5;\n    cursor: not-allowed;\n\n    &:hover {\n      background-color: transparent;\n    }\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* REMOVE NATIVE DISCLOSURE TRIANGLE (WebKit fallback) */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion > details > summary::-webkit-details-marker {\n  display: none;\n}\n\n.ren-accordion-trigger::-webkit-details-marker {\n  display: none;\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* OPEN STATE — ROTATE CHEVRON */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion > details[open] > summary::after {\n  transform: rotate(180deg);\n}\n\n/* Fallback for custom markup */\n.ren-accordion-trigger[aria-expanded=\"true\"]::after {\n  transform: rotate(180deg);\n}\n\n/* Animate the native details content box. The global reset enables\n   keyword-size interpolation, so block-size can transition to and from auto. */\n@supports selector(details::details-content) {\n  .ren-accordion > details::details-content {\n    block-size: 0;\n    overflow: clip;\n    opacity: 0;\n    transition:\n      block-size var(--duration-exit) var(--ease-exit),\n      opacity var(--duration-exit) var(--ease-exit),\n      content-visibility var(--duration-exit) var(--ease-exit);\n    transition-behavior: allow-discrete;\n  }\n\n  .ren-accordion > details[open]::details-content {\n    block-size: auto;\n    opacity: 1;\n    transition-duration: var(--duration-enter);\n    transition-timing-function: var(--ease-enter);\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* CONTENT ANIMATION — MODERN APPROACH */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion > details {\n  /* Modern: use ::details-content for height animation */\n  & > .ren-accordion-content {\n    padding: 0 var(--space-4) var(--space-4);\n    color: var(--color-text-muted);\n    line-height: 1.6;\n    overflow: hidden;\n\n    /* Browsers with ::details-content support */\n    @supports selector(::-webkit-details-content) {\n      height: 0;\n      overflow: hidden;\n      transition:\n        height var(--duration-enter) var(--ease-enter),\n        opacity var(--duration-enter) var(--ease-enter);\n    }\n\n    /* Also try Chrome/V8 syntax (may vary by engine) */\n    @supports selector(::details-content) {\n      height: 0;\n      overflow: hidden;\n      transition:\n        height var(--duration-enter) var(--ease-enter),\n        opacity var(--duration-enter) var(--ease-enter);\n    }\n  }\n\n  /* Fallback for browsers without ::details-content: use opacity + max-height */\n  & > .ren-accordion-content {\n    opacity: 0;\n    max-height: 0;\n    transition:\n      opacity var(--duration-enter) var(--ease-enter),\n      max-height var(--duration-enter) var(--ease-enter);\n  }\n\n  /* When details is open */\n  &[open] > .ren-accordion-content {\n    opacity: 1;\n    max-height: 1000px; /* Reasonable fallback limit */\n\n    /* Modern: animate to auto height with ::details-content */\n    @supports selector(::details-content) {\n      height: auto;\n    }\n  }\n\n  /* Entry animation starting point (for ::details-content) */\n  @starting-style {\n    &[open] > .ren-accordion-content {\n      opacity: 0;\n      max-height: 0;\n    }\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* CONTENT ELEMENT */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion-content {\n  padding: 0 var(--space-4) var(--space-4);\n  color: var(--color-text-muted);\n  line-height: 1.6;\n}\n\n/* Hide custom content via hidden attribute (for non-details markup) */\n.ren-accordion-content[hidden] {\n  display: none;\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* VARIANT: BORDERED */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion-bordered {\n  border-top: none;\n\n  & > details,\n  & .ren-accordion-item {\n    border: 1px solid var(--color-border);\n    border-radius: var(--radius-md);\n    margin-bottom: var(--space-2);\n    overflow: hidden;\n\n    &:last-child {\n      margin-bottom: 0;\n    }\n  }\n\n  & > details > summary,\n  & .ren-accordion-trigger {\n    padding: var(--space-4) var(--space-4);\n  }\n\n  & > details > .ren-accordion-content,\n  & .ren-accordion-content {\n    padding: 0 var(--space-4) var(--space-4);\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* VARIANT: FLUSH (no borders, minimal spacing) */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n.ren-accordion-flush {\n  border-top: none;\n\n  & > details,\n  & .ren-accordion-item {\n    border-bottom: none;\n    padding: var(--space-2) 0;\n\n    &:last-child {\n      border-bottom: none;\n    }\n  }\n\n  & > details > summary,\n  & .ren-accordion-trigger {\n    padding: var(--space-3) 0;\n  }\n\n  & > details > .ren-accordion-content,\n  & .ren-accordion-content {\n    padding: 0 0 var(--space-3);\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* REDUCED MOTION */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-accordion-trigger::after,\n  .ren-accordion > details > summary::after {\n    transition: none;\n  }\n\n  .ren-accordion > details > .ren-accordion-content {\n    transition: none;\n  }\n\n  .ren-accordion > details::details-content {\n    transition: none;\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════════ */\n/* ACCESSIBILITY SUPPORT */\n/* ═══════════════════════════════════════════════════════════════════ */\n\n/* Ensure proper contrast for focus states */\n.ren-accordion > details > summary:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: -2px;\n}\n\n/* Dark mode: tokens will auto-adjust via CSS variables */\n@media (prefers-color-scheme: dark) {\n  /* Tokens like --color-text, --color-border, etc. should already be defined\n     in the design system's color token reset. No additional changes needed. */\n}\n",
      "path": "components/composites/ren-accordion/ren-accordion.css",
      "id": "file:components/composites/ren-accordion/ren-accordion.css",
      "type": "css",
      "name": "ren-accordion.css"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — <ren-accordion> (Minimal Enhancement)\n * ════════════════════════════════════════════════\n *\n * Native <details><summary> handles 90% of the work.\n * This JS only adds:\n *   - Exclusive mode (single): close others when one opens\n *     → Uses native <details name=\"...\"> when supported,\n *       with JS fallback for older browsers\n *   - default-value attribute: open items on init\n *   - ren-accordion-change event: custom event dispatch\n *\n * Attributes:\n *   type:          'single' (default) | 'multiple'\n *   collapsible:   boolean (single mode only) — allow all closed\n *   default-value: comma-separated indices to open on init\n *\n * Markup:\n *   <ren-accordion type=\"single\" default-value=\"0\">\n *     <details>\n *       <summary class=\"ren-accordion-trigger\">Item 1</summary>\n *       <div class=\"ren-accordion-content\">Content...</div>\n *     </details>\n *     <details>\n *       <summary class=\"ren-accordion-trigger\">Item 2</summary>\n *       <div class=\"ren-accordion-content\">Content...</div>\n *     </details>\n *   </ren-accordion>\n *\n * Events:\n *   ren-accordion-change: Fired when item open state changes\n *   - detail: { item, isOpen, index }\n */\n\n/**\n * Feature detect: does <details name=\"...\"> support exclusive mode natively?\n * When supported, setting the same `name` on multiple <details> makes them\n * behave like radio buttons — opening one closes the others automatically.\n */\nconst supportsDetailsName = (() => {\n  try {\n    const d1 = document.createElement('details');\n    const d2 = document.createElement('details');\n    d1.name = 'test';\n    d2.name = 'test';\n    d1.open = true;\n    d2.open = true;\n    const container = document.createElement('div');\n    container.append(d1, d2);\n    document.body.append(container);\n    // If native exclusive works, opening d2 should close d1\n    const works = !d1.open;\n    container.remove();\n    return works;\n  } catch {\n    return false;\n  }\n})();\n\nexport class RenAccordion extends HTMLElement {\n  static get observedAttributes() {\n    return ['type', 'collapsible', 'default-value'];\n  }\n\n  constructor() {\n    super();\n    /** @private */ this._useNativeExclusive = false;\n    /** @private */ this._groupName = '';\n    this._listenerController = null;\n    this._handleToggleBound = (event) => this._handleToggle(event);\n  }\n\n  connectedCallback() {\n    this._listenerController?.abort();\n    this._listenerController = new AbortController();\n    this.classList.add('ren-accordion');\n\n    // Determine mode (exclusive or multiple)\n    this._exclusive = this.getAttribute('type') !== 'multiple';\n    this._collapsible = this.hasAttribute('collapsible');\n\n    // Generate a unique group name for native <details name>\n    this._groupName = `ren-accordion-${this.id || Math.random().toString(36).slice(2, 8)}`;\n\n    // Use native exclusive mode when browser supports it\n    this._useNativeExclusive = supportsDetailsName && this._exclusive;\n    this._applyNativeExclusive();\n\n    // Listen for toggle events on all child details (event delegation)\n    this.addEventListener('toggle', this._handleToggleBound, {\n      capture: true,\n      signal: this._listenerController.signal,\n    });\n\n    // Apply default open items\n    const defaultValue = this.getAttribute('default-value');\n    if (defaultValue) {\n      this._setDefaultOpen(defaultValue);\n    }\n  }\n\n  disconnectedCallback() {\n    this._listenerController?.abort();\n    this._listenerController = null;\n  }\n\n  /**\n   * Apply or remove the native `name` attribute on child <details>.\n   * When supported, this lets the browser handle exclusive mode natively.\n   * @private\n   */\n  _applyNativeExclusive() {\n    this.items.forEach((detail) => {\n      // Closed accordion items must keep their summary visible. The global\n      // data-state helper hides closed surfaces unless they explicitly opt out.\n      detail.setAttribute('data-keep', '');\n\n      if (this._useNativeExclusive) {\n        detail.setAttribute('name', this._groupName);\n      } else {\n        // Remove name if switching to multiple mode or no native support\n        if (detail.getAttribute('name') === this._groupName) {\n          detail.removeAttribute('name');\n        }\n      }\n    });\n  }\n\n  /**\n   * Handle toggle event on child <details> elements\n   * @private\n   */\n  _handleToggle(event) {\n    const details = event.target;\n\n    // Only handle <details> that are direct children or nested in .ren-accordion-item\n    if (!this._isValidDetail(details)) {\n      return;\n    }\n\n    // Set data-state for CSS styling\n    details.setAttribute('data-state', details.open ? 'open' : 'closed');\n\n    // JS fallback for exclusive mode (only when native isn't handling it)\n    if (this._exclusive && !this._useNativeExclusive && details.open) {\n      this._closeOtherDetails(details);\n    }\n\n    // Sync data-state on siblings closed by native <details name>\n    if (this._useNativeExclusive && details.open) {\n      this.items.forEach((d) => {\n        if (d !== details) {\n          d.setAttribute('data-state', d.open ? 'open' : 'closed');\n        }\n      });\n    }\n\n    // Dispatch custom event\n    const index = this.items.indexOf(details);\n    this.dispatchEvent(\n      new CustomEvent('ren-accordion-change', {\n        detail: {\n          item: details,\n          isOpen: details.open,\n          index,\n        },\n        bubbles: true,\n      })\n    );\n  }\n\n  /**\n   * Check if a details element is a valid child\n   * @private\n   */\n  _isValidDetail(details) {\n    if (details.tagName !== 'DETAILS') {\n      return false;\n    }\n\n    // Direct child of accordion\n    if (details.parentElement === this) {\n      return true;\n    }\n\n    // Child of .ren-accordion-item within accordion\n    const parent = details.parentElement;\n    if (\n      parent &&\n      parent.classList &&\n      parent.classList.contains('ren-accordion-item') &&\n      parent.parentElement === this\n    ) {\n      return true;\n    }\n\n    return false;\n  }\n\n  /**\n   * Close all other details except the specified one\n   * @private\n   */\n  _closeOtherDetails(openDetails) {\n    this.items.forEach((detail) => {\n      if (detail !== openDetails && detail.open) {\n        detail.open = false;\n        detail.setAttribute('data-state', 'closed');\n      }\n    });\n  }\n\n  /**\n   * Set which items should be open by default\n   * @private\n   */\n  _setDefaultOpen(value) {\n    const indices = value\n      .split(',')\n      .map((s) => parseInt(s.trim(), 10))\n      .filter((i) => !isNaN(i));\n\n    this.items.forEach((detail, index) => {\n      detail.open = indices.includes(index);\n      detail.setAttribute('data-state', detail.open ? 'open' : 'closed');\n    });\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (!this.isConnected) return;\n\n    if (name === 'type') {\n      // Update exclusive mode\n      this._exclusive = newValue !== 'multiple';\n      this._useNativeExclusive = supportsDetailsName && this._exclusive;\n      this._applyNativeExclusive();\n    } else if (name === 'collapsible') {\n      // Collapsible flag updated\n      this._collapsible = this.hasAttribute('collapsible');\n    } else if (name === 'default-value') {\n      // Re-apply defaults\n      if (newValue) {\n        this._setDefaultOpen(newValue);\n      }\n    }\n  }\n\n  /* ═══════════════════════════════════════════ */\n  /* PUBLIC API */\n  /* ═══════════════════════════════════════════ */\n\n  /**\n   * Get all details elements\n   */\n  get items() {\n    const items = [];\n\n    // Direct <details> children\n    this.querySelectorAll(':scope > details').forEach((d) => items.push(d));\n\n    // <details> nested in .ren-accordion-item children\n    this.querySelectorAll(':scope > .ren-accordion-item > details').forEach(\n      (d) => items.push(d)\n    );\n\n    return items;\n  }\n\n  /**\n   * Open a specific item by index\n   */\n  openItem(index) {\n    const item = this.items[index];\n    if (!item) return;\n\n    // Exclusive mode: close others\n    if (this._exclusive) {\n      this._closeOtherDetails(item);\n    }\n\n    item.open = true;\n  }\n\n  /**\n   * Close a specific item by index\n   */\n  closeItem(index) {\n    const item = this.items[index];\n    if (!item) return;\n\n    // In exclusive mode without collapsible: prevent closing if only open item\n    if (\n      this._exclusive &&\n      !this._collapsible &&\n      this.items.filter((d) => d.open).length === 1 &&\n      item.open\n    ) {\n      return; // Keep at least one open\n    }\n\n    item.open = false;\n  }\n\n  /**\n   * Toggle a specific item by index\n   */\n  toggleItem(index) {\n    const item = this.items[index];\n    if (!item) return;\n\n    if (item.open) {\n      this.closeItem(index);\n    } else {\n      this.openItem(index);\n    }\n  }\n\n  /**\n   * Check if item is open\n   */\n  isOpen(index) {\n    return this.items[index]?.open ?? false;\n  }\n\n  /**\n   * Get all currently open indices\n   */\n  getOpenItems() {\n    return this.items\n      .map((item, index) => (item.open ? index : -1))\n      .filter((i) => i !== -1);\n  }\n}\n\n// Register the component\nif (!customElements.get('ren-accordion')) {\n  customElements.define('ren-accordion', RenAccordion);\n}\n",
      "path": "components/composites/ren-accordion/ren-accordion.js",
      "id": "file:components/composites/ren-accordion/ren-accordion.js",
      "type": "javascript",
      "name": "ren-accordion.js"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Alert Dialog Component\n   ============================================\n   Confirmation dialog that blocks interaction.\n   Extends native <dialog> like ren-dialog but:\n   - No dismiss on outside click (::backdrop blocks)\n   - No dismiss on Escape (optional)\n   - Requires explicit action (confirm/cancel)\n\n   Minimal JS: just showModal() and close().\n\n   Usage:\n     <dialog class=\"ren-alert-dialog\" id=\"confirm\">\n       <div class=\"ren-alert-dialog-icon\">⚠</div>\n       <h2 class=\"ren-alert-dialog-title\">Delete item?</h2>\n       <p class=\"ren-alert-dialog-description\">\n         This action cannot be undone.\n       </p>\n       <div class=\"ren-alert-dialog-actions\">\n         <button class=\"ren-btn-secondary\" onclick=\"this.closest('dialog').close('cancel')\">\n           Cancel\n         </button>\n         <button class=\"ren-btn-danger\" onclick=\"this.closest('dialog').close('confirm')\">\n           Delete\n         </button>\n       </div>\n     </dialog>\n\n   Open: document.getElementById('confirm').showModal()\n   Result: dialog.addEventListener('close', () => dialog.returnValue)\n   ============================================ */\n\n.ren-alert-dialog {\n  container-type: inline-size;\n  container-name: ren-alert-dialog;\n\n  /* Structure */\n  margin: auto;\n  padding: var(--space-6);\n  border: none;\n\n  /* Sizing */\n  max-width: 24rem;\n  width: 90vw;\n\n  /* Appearance */\n  background: var(--color-surface);\n  color: var(--color-text);\n  border-radius: var(--radius-xl);\n  box-shadow: var(--shadow-xl);\n  text-align: center;\n\n  /* Animation — semantic tokens keep this in sync with ren-dialog\n     and respect prefers-reduced-motion via tokens/semantic/motion.css. */\n  opacity: 1;\n  transform: scale(1);\n  transition:\n    opacity   var(--duration-enter) var(--ease-enter),\n    transform var(--duration-enter) var(--ease-enter),\n    overlay   var(--duration-enter) var(--ease-enter) allow-discrete,\n    display   var(--duration-enter) var(--ease-enter) allow-discrete;\n}\n\n/* Entry animation */\n@starting-style {\n  .ren-alert-dialog[open] {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n}\n\n/* Exit */\n.ren-alert-dialog:not([open]) {\n  opacity: 0;\n  transform: scale(0.95);\n}\n\n/* Backdrop — shared overlay transition keeps all scrims in sync. */\n.ren-alert-dialog::backdrop {\n  background-color: var(--color-overlay);\n  transition: var(--transition-overlay);\n}\n\n@starting-style {\n  .ren-alert-dialog[open]::backdrop {\n    opacity: 0;\n  }\n}\n\n/* ─── Icon (optional) ─── */\n.ren-alert-dialog-icon {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: var(--icon-xl);\n  height: var(--icon-xl);\n  margin: 0 auto var(--space-3);\n  font-size: var(--text-xl);\n  background-color: var(--color-fill);\n  border-radius: var(--radius-full);\n}\n\n/* Danger icon */\n.ren-alert-dialog-icon-danger {\n  background-color: var(--color-danger-subtle);\n  color: var(--color-danger);\n}\n\n/* Warning icon */\n.ren-alert-dialog-icon-warning {\n  background-color: var(--color-warning-subtle);\n  color: var(--color-warning);\n}\n\n/* ─── Title ─── */\n.ren-alert-dialog-title {\n  font-size: var(--title-sm-size, var(--text-lg));\n  font-weight: var(--weight-semibold);\n  color: var(--color-text);\n  margin-bottom: var(--space-2);\n}\n\n/* ─── Description ─── */\n.ren-alert-dialog-description {\n  font-size: var(--body-size);\n  color: var(--color-text-muted);\n  line-height: var(--leading-normal);\n  margin-bottom: var(--space-5);\n}\n\n/* ─── Actions ─── */\n.ren-alert-dialog-actions {\n  display: flex;\n  gap: var(--space-2);\n  justify-content: center;\n}\n\n/* Stack actions when dialog is narrow */\n@container ren-alert-dialog (max-width: 24rem) {\n  .ren-alert-dialog-actions {\n    flex-direction: column-reverse;\n  }\n\n  .ren-alert-dialog-actions > * {\n    width: 100%;\n  }\n}\n\n/* ─── Reduced motion ─── */\n@media (prefers-reduced-motion: reduce) {\n  .ren-alert-dialog {\n    transition: none;\n  }\n  .ren-alert-dialog::backdrop {\n    transition: none;\n  }\n}\n",
      "path": "components/composites/ren-alert-dialog/ren-alert-dialog.css",
      "id": "file:components/composites/ren-alert-dialog/ren-alert-dialog.css",
      "type": "css",
      "name": "ren-alert-dialog.css"
    },
    {
      "data": {},
      "body": "/* ═══ REN CALENDAR WEB COMPONENT ═══\n   A fully accessible, keyboard-navigable calendar component\n   with support for single, range, and multiple date selection.\n   Uses ARIA grid pattern with roving tabindex for keyboard navigation.\n   ══════════════════════════════════════════════════════════════════ */\n\n/* ═══ BASE CONTAINER ═══ */\n.ren-calendar {\n  container-type: inline-size;\n  container-name: ren-calendar;\n  background: var(--color-surface-raised);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-md);\n  padding: var(--space-4);\n  font-family: inherit;\n}\n\n/* ═══ HEADER WITH NAVIGATION ═══ */\n.ren-calendar-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  margin-bottom: var(--space-4);\n  gap: var(--space-2);\n}\n\n/* ═══ MONTH/YEAR TITLE ═══ */\n.ren-calendar-title {\n  font-size: var(--body-size);\n  font-weight: var(--label-weight);\n  color: var(--color-text);\n  flex: 1;\n  text-align: center;\n  min-width: 150px;\n  white-space: nowrap;\n}\n\n/* ═══ NAVIGATION BUTTONS ═══ */\n.ren-calendar-nav {\n  display: flex;\n  gap: var(--space-1);\n}\n\n.ren-calendar-nav button {\n  width: var(--touch-min);\n  height: var(--touch-min);\n  min-width: var(--touch-min);\n  min-height: var(--touch-min);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  background: transparent;\n  border: none;\n  border-radius: var(--radius-md);\n  color: var(--color-text);\n  cursor: pointer;\n  transition: var(--transition-tactile);\n  position: relative;\n\n  &:hover {\n    background: var(--color-fill);\n  }\n\n  &:active {\n    background: var(--color-fill-active);\n  }\n\n  &:disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  /* ═══ CHEVRON ICONS ═══ */\n  &.ren-calendar-prev::before {\n    content: '‹';\n    font-size: 1.5em;\n    font-weight: bold;\n  }\n\n  &.ren-calendar-next::before {\n    content: '›';\n    font-size: 1.5em;\n    font-weight: bold;\n  }\n}\n\n/* ═══ WEEKDAY HEADERS ═══ */\n.ren-calendar-weekdays {\n  display: grid;\n  grid-template-columns: repeat(7, 1fr);\n  gap: var(--space-1);\n  margin-bottom: var(--space-2);\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  color: var(--color-text-muted);\n  text-align: center;\n}\n\n.ren-calendar-weekday {\n  padding: var(--space-2) var(--space-1);\n  text-transform: uppercase;\n  letter-spacing: 0.5px;\n}\n\n/* ═══ CALENDAR GRID ═══ */\n.ren-calendar-grid {\n  display: grid;\n  grid-template-columns: repeat(7, 1fr);\n  gap: var(--space-1);\n}\n\n/* ═══ INDIVIDUAL DAY BUTTON ═══ */\n.ren-calendar-day {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-width: var(--touch-min);\n  min-height: var(--touch-min);\n  width: 100%;\n  aspect-ratio: 1;\n  padding: var(--space-0-5);\n  background: transparent;\n  border: none;\n  border-radius: var(--radius-md);\n  color: var(--color-text);\n  font-size: var(--body-size);\n  cursor: pointer;\n  transition: var(--transition-tactile);\n  position: relative;\n\n  /* ═══ DEFAULT STATE ═══ */\n  &:not([disabled]):hover {\n    background: var(--color-fill);\n  }\n\n  &:not([disabled]):active {\n    background: var(--color-fill-active);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  /* ═══ SELECTED DATE ═══ */\n  &[aria-selected=\"true\"] {\n    background: var(--color-accent);\n    color: var(--color-on-accent);\n    font-weight: 600;\n  }\n\n  /* ═══ TODAY INDICATOR ═══ */\n  &[data-today] {\n    background: var(--color-accent-subtle);\n    color: var(--color-accent);\n    font-weight: 600;\n    box-shadow: inset 0 0 0 2px var(--color-accent);\n  }\n\n  &[data-today][aria-selected=\"true\"] {\n    background: var(--color-accent);\n    color: var(--color-on-accent);\n    box-shadow: none;\n  }\n\n  /* ═══ OUTSIDE MONTH DATES ═══ */\n  &[data-outside] {\n    color: var(--color-text-faint);\n    pointer-events: none;\n  }\n\n  /* ═══ DISABLED DATES ═══ */\n  &[disabled] {\n    color: var(--color-text-muted);\n    cursor: not-allowed;\n    opacity: 0.5;\n  }\n\n  /* ═══ RANGE START ═══ */\n  &.ren-calendar-day-range-start {\n    background: var(--color-accent);\n    color: var(--color-on-accent);\n    border-radius: var(--radius-md);\n\n    &::after {\n      content: '';\n      position: absolute;\n      right: -4px;\n      top: 0;\n      bottom: 0;\n      width: 50%;\n      background: var(--color-accent-subtle);\n      z-index: -1;\n    }\n  }\n\n  /* ═══ RANGE END ═══ */\n  &.ren-calendar-day-range-end {\n    background: var(--color-accent);\n    color: var(--color-on-accent);\n    border-radius: var(--radius-md);\n\n    &::before {\n      content: '';\n      position: absolute;\n      left: -4px;\n      top: 0;\n      bottom: 0;\n      width: 50%;\n      background: var(--color-accent-subtle);\n      z-index: -1;\n    }\n  }\n\n  /* ═══ IN RANGE ═══ */\n  &.ren-calendar-day-in-range {\n    background: var(--color-accent-subtle);\n    color: var(--color-text);\n    border-radius: 0;\n  }\n}\n\n/* ═══ ANIMATION FOR MONTH TRANSITIONS ═══ */\n@keyframes ren-calendar-fade-in {\n  from {\n    opacity: 0;\n    transform: translateY(-4px);\n  }\n\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n.ren-calendar-grid {\n  animation: ren-calendar-fade-in var(--duration-enter) var(--ease-enter);\n}\n\n/* ═══ REDUCED MOTION SUPPORT ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-calendar-grid {\n    animation: none;\n  }\n\n  .ren-calendar-nav button,\n  .ren-calendar-day {\n    transition: none;\n  }\n}\n\n/* ═══ SIZE VARIANT: SMALL ═══ */\n.ren-calendar-sm {\n  padding: var(--space-3);\n\n  .ren-calendar-header {\n    margin-bottom: var(--space-3);\n  }\n\n  .ren-calendar-title {\n    font-size: var(--label-size);\n  }\n\n  .ren-calendar-nav button {\n    width: 36px;\n    height: 36px;\n    min-width: 36px;\n    min-height: 36px;\n  }\n\n  .ren-calendar-weekday {\n    font-size: var(--caption-size);\n  }\n\n  .ren-calendar-day {\n    font-size: var(--label-size);\n    min-width: 36px;\n    min-height: 36px;\n  }\n}\n\n/* ═══ SIZE VARIANT: LARGE ═══ */\n.ren-calendar-lg {\n  padding: var(--space-6);\n\n  .ren-calendar-header {\n    margin-bottom: var(--space-6);\n    gap: var(--space-4);\n  }\n\n  .ren-calendar-title {\n    font-size: 1.25rem;\n  }\n\n  .ren-calendar-nav button {\n    width: 52px;\n    height: 52px;\n    min-width: 52px;\n    min-height: 52px;\n    font-size: 1.25rem;\n  }\n\n  .ren-calendar-weekdays {\n    margin-bottom: var(--space-4);\n  }\n\n  .ren-calendar-grid {\n    gap: var(--space-2);\n  }\n\n  .ren-calendar-day {\n    font-size: 1.125rem;\n    min-width: 52px;\n    min-height: 52px;\n  }\n}\n\n/* ═══ DARK MODE SUPPORT ═══ */\n@media (prefers-color-scheme: dark) {\n  .ren-calendar {\n    /* ═══ USES CSS VARIABLES - AUTO ADAPTS ═══ */\n  }\n}\n\n/* ═══ COMPACT LAYOUT (Container Query) ═══ */\n@container ren-calendar (max-width: 320px) {\n  .ren-calendar {\n    padding: var(--space-3);\n  }\n\n  .ren-calendar-grid {\n    gap: var(--space-0-5);\n  }\n\n  .ren-calendar-day {\n    min-width: 32px;\n    min-height: 32px;\n    font-size: var(--label-size);\n  }\n\n  .ren-calendar-nav button {\n    width: 40px;\n    height: 40px;\n    min-width: 40px;\n    min-height: 40px;\n  }\n}\n/* Appearance API bridge */\n.ren-calendar {\n  inline-size: var(--ren-calendar-width, 280px);\n  border-radius: var(--ren-calendar-radius, var(--radius-lg));\n  background: var(--ren-calendar-bg, var(--color-surface));\n  border-color: var(--ren-calendar-border, var(--color-border));\n}\n.ren-calendar [data-day], .ren-calendar-day { inline-size: var(--ren-calendar-day-size, var(--size-md)); block-size: var(--ren-calendar-day-size, var(--size-md)); }\n.ren-calendar [data-today] { background: var(--ren-calendar-today-bg, var(--color-accent-subtle)); }\n.ren-calendar [aria-selected=\"true\"], .ren-calendar [data-selected] { background: var(--ren-calendar-selected-bg, var(--color-accent)); color: var(--ren-calendar-selected-color, var(--color-on-accent)); }\n.ren-calendar [data-range], .ren-calendar-range { background: var(--ren-calendar-range-bg, var(--color-accent-subtle)); }\n\n@media (any-pointer: coarse), (pointer: coarse) {\n  .ren-calendar .ren-calendar-nav button,\n  .ren-calendar .ren-calendar-day {\n    inline-size: calc(var(--touch-min) + var(--stroke-1));\n    block-size: calc(var(--touch-min) + var(--stroke-1));\n    min-inline-size: calc(var(--touch-min) + var(--stroke-1));\n    min-block-size: calc(var(--touch-min) + var(--stroke-1));\n  }\n}\n",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "file:components/composites/ren-calendar/ren-calendar.css",
      "type": "css",
      "name": "ren-calendar.css"
    },
    {
      "data": {},
      "body": "import { formatLocalDate, parseLocalDate } from '../../../utils/local-date.js';\n\n/* ═══ REN CALENDAR WEB COMPONENT ═══\n   A fully accessible, keyboard-navigable calendar for date selection.\n\n   Features:\n   - Multiple selection modes: single, range, multiple\n   - Full keyboard navigation with ARIA grid pattern\n   - Locale-aware date formatting\n   - Customizable min/max dates\n   - Month navigation with prev/next buttons\n   - Today indicator and outside-month date styling\n\n   Usage:\n   <ren-calendar value=\"2026-03-31\" locale=\"en-US\" mode=\"single\"></ren-calendar>\n   ══════════════════════════════════════════════════════════════════ */\n\nexport class RenCalendar extends HTMLElement {\n  constructor() {\n    super();\n\n    /* ═══ STATE ═══ */\n    this.currentDate = new Date();\n    this.selectedDate = null;\n    this.selectedDates = new Set();\n    this.rangeStart = null;\n    this.rangeEnd = null;\n    this.focusedDate = null;\n\n    /* ═══ CONFIGURATION ═══ */\n    this.mode = 'single'; // single, range, multiple\n    this.locale = 'en-US';\n    this.firstDay = 0; // 0 = Sunday, 1 = Monday\n    this.minDate = null;\n    this.maxDate = null;\n\n    /* ═══ BIND METHODS ═══ */\n    this.handleDayClick = this.handleDayClick.bind(this);\n    this.handleDayKeyDown = this.handleDayKeyDown.bind(this);\n    this.handlePrevMonth = this.handlePrevMonth.bind(this);\n    this.handleNextMonth = this.handleNextMonth.bind(this);\n  }\n\n  connectedCallback() {\n    /* ═══ READ ATTRIBUTES ═══ */\n    this.mode = this.getAttribute('mode') || 'single';\n    this.locale = this.getAttribute('locale') || 'en-US';\n    const firstDayAttr = this.getAttribute('first-day');\n    if (firstDayAttr !== null) this.firstDay = parseInt(firstDayAttr, 10);\n\n    /* ═══ PARSE DATE ATTRIBUTES ═══ */\n    const valueAttr = this.getAttribute('value');\n    if (valueAttr) {\n      const parsed = this.parseDate(valueAttr);\n      if (!isNaN(parsed)) {\n        this.selectedDate = parsed;\n        this.currentDate = new Date(parsed);\n      }\n    }\n\n    const minAttr = this.getAttribute('min');\n    if (minAttr) this.minDate = this.parseDate(minAttr);\n\n    const maxAttr = this.getAttribute('max');\n    if (maxAttr) this.maxDate = this.parseDate(maxAttr);\n\n    /* ═══ RENDER CALENDAR ═══ */\n    this.render();\n  }\n\n  /* ═══ RENDER CALENDAR ═══ */\n  render() {\n    this.innerHTML = '';\n    this.classList.add('ren-calendar');\n\n    /* ═══ BUILD HEADER ═══ */\n    const header = document.createElement('div');\n    header.className = 'ren-calendar-header';\n\n    const title = document.createElement('div');\n    title.className = 'ren-calendar-title';\n    title.textContent = this.formatMonthYear();\n    title.setAttribute('aria-live', 'polite');\n\n    const navDiv = document.createElement('div');\n    navDiv.className = 'ren-calendar-nav';\n\n    const prevBtn = document.createElement('button');\n    prevBtn.className = 'ren-calendar-prev';\n    prevBtn.setAttribute('aria-label', `Previous month (${this.formatPrevMonth()})`);\n    prevBtn.addEventListener('click', this.handlePrevMonth);\n\n    const nextBtn = document.createElement('button');\n    nextBtn.className = 'ren-calendar-next';\n    nextBtn.setAttribute('aria-label', `Next month (${this.formatNextMonth()})`);\n    nextBtn.addEventListener('click', this.handleNextMonth);\n\n    navDiv.appendChild(prevBtn);\n    navDiv.appendChild(nextBtn);\n\n    header.appendChild(title);\n    header.appendChild(navDiv);\n    this.appendChild(header);\n\n    /* ═══ BUILD WEEKDAY HEADERS ═══ */\n    const weekdaysDiv = document.createElement('div');\n    weekdaysDiv.className = 'ren-calendar-weekdays';\n\n    const weekdayNames = this.getWeekdayNames();\n    weekdayNames.forEach((name) => {\n      const dayLabel = document.createElement('div');\n      dayLabel.className = 'ren-calendar-weekday';\n      dayLabel.textContent = name;\n      dayLabel.setAttribute('aria-label', name);\n      weekdaysDiv.appendChild(dayLabel);\n    });\n\n    this.appendChild(weekdaysDiv);\n\n    /* ═══ BUILD CALENDAR GRID ═══ */\n    const grid = document.createElement('div');\n    grid.className = 'ren-calendar-grid';\n    grid.setAttribute('role', 'grid');\n\n    const dates = this.generateCalendarDates();\n    dates.forEach((date, index) => {\n      const dayBtn = document.createElement('button');\n      dayBtn.className = 'ren-calendar-day';\n      dayBtn.textContent = date.date.getDate();\n      dayBtn.setAttribute('type', 'button');\n      dayBtn.setAttribute('role', 'gridcell');\n      dayBtn.setAttribute('data-date', this.dateToString(date.date));\n\n      /* ═══ HANDLE OUTSIDE MONTH DATES ═══ */\n      if (date.outside) {\n        dayBtn.setAttribute('data-outside', '');\n        dayBtn.setAttribute('aria-disabled', 'true');\n        dayBtn.disabled = true;\n      }\n\n      /* ═══ CHECK IF DATE IS DISABLED ═══ */\n      if (this.isDateDisabled(date.date)) {\n        dayBtn.disabled = true;\n        dayBtn.setAttribute('aria-disabled', 'true');\n      }\n\n      /* ═══ CHECK IF TODAY ═══ */\n      if (this.isToday(date.date)) {\n        dayBtn.setAttribute('data-today', '');\n      }\n\n      /* ═══ CHECK IF SELECTED (SINGLE MODE) ═══ */\n      if (this.mode === 'single' && this.selectedDate && this.isSameDay(date.date, this.selectedDate)) {\n        dayBtn.setAttribute('aria-selected', 'true');\n        this.focusedDate = date.date;\n      }\n\n      /* ═══ CHECK IF SELECTED (MULTIPLE MODE) ═══ */\n      if (this.mode === 'multiple' && this.selectedDates.has(this.dateToString(date.date))) {\n        dayBtn.setAttribute('aria-selected', 'true');\n      }\n\n      /* ═══ CHECK RANGE SELECTION ═══ */\n      if (this.mode === 'range' && this.rangeStart && this.rangeEnd) {\n        const dateStr = this.dateToString(date.date);\n        const rangeStart = this.dateToString(this.rangeStart);\n        const rangeEnd = this.dateToString(this.rangeEnd);\n\n        if (dateStr === rangeStart) {\n          dayBtn.classList.add('ren-calendar-day-range-start');\n          dayBtn.setAttribute('aria-selected', 'true');\n        } else if (dateStr === rangeEnd) {\n          dayBtn.classList.add('ren-calendar-day-range-end');\n          dayBtn.setAttribute('aria-selected', 'true');\n        } else if (dateStr > rangeStart && dateStr < rangeEnd) {\n          dayBtn.classList.add('ren-calendar-day-in-range');\n        }\n      }\n\n      /* ═══ EVENT LISTENERS ═══ */\n      dayBtn.addEventListener('click', (e) => this.handleDayClick(e, date.date));\n      dayBtn.addEventListener('keydown', (e) => this.handleDayKeyDown(e, date.date, index, dates.length));\n\n      grid.appendChild(dayBtn);\n    });\n\n    this.appendChild(grid);\n\n    /* ═══ SET TABINDEX FOR FOCUS MANAGEMENT ═══ */\n    this.updateTabIndex();\n  }\n\n  /* ═══ UPDATE TABINDEX FOR ROVING TABINDEX PATTERN ═══ */\n  updateTabIndex() {\n    const buttons = Array.from(this.querySelectorAll('.ren-calendar-day'));\n    const enabledButtons = buttons.filter((btn) => !btn.disabled);\n    let target = null;\n\n    if (this.focusedDate) {\n      const focusedDateString = this.dateToString(this.focusedDate);\n      target = enabledButtons.find((btn) => btn.dataset.date === focusedDateString);\n    }\n\n    if (!target && this.selectedDate) {\n      const selectedDateString = this.dateToString(this.selectedDate);\n      target = enabledButtons.find((btn) => btn.dataset.date === selectedDateString);\n    }\n\n    if (!target) {\n      target = enabledButtons.find((btn) => btn.hasAttribute('data-today'));\n    }\n\n    target ??= enabledButtons[0] ?? null;\n\n    buttons.forEach((btn) => {\n      btn.setAttribute('tabindex', btn === target ? '0' : '-1');\n    });\n  }\n\n  /* ═══ GENERATE CALENDAR DATES ═══ */\n  generateCalendarDates() {\n    const year = this.currentDate.getFullYear();\n    const month = this.currentDate.getMonth();\n\n    /* ═══ FIRST DAY OF MONTH AND WEEKDAY ═══ */\n    const firstDay = new Date(year, month, 1);\n    const lastDay = new Date(year, month + 1, 0);\n    const daysInMonth = lastDay.getDate();\n\n    /* ═══ STARTING WEEKDAY ═══ */\n    let startingDayOfWeek = firstDay.getDay() - this.firstDay;\n    if (startingDayOfWeek < 0) startingDayOfWeek += 7;\n\n    const dates = [];\n\n    /* ═══ PREVIOUS MONTH DATES ═══ */\n    const prevMonthLastDay = new Date(year, month, 0).getDate();\n    for (let i = startingDayOfWeek - 1; i >= 0; i--) {\n      const prevDate = new Date(year, month - 1, prevMonthLastDay - i);\n      dates.push({ date: prevDate, outside: true });\n    }\n\n    /* ═══ CURRENT MONTH DATES ═══ */\n    for (let day = 1; day <= daysInMonth; day++) {\n      dates.push({ date: new Date(year, month, day), outside: false });\n    }\n\n    /* ═══ NEXT MONTH DATES ═══ */\n    const remainingCells = 42 - dates.length; // 6 rows × 7 cols\n    for (let day = 1; day <= remainingCells; day++) {\n      dates.push({ date: new Date(year, month + 1, day), outside: true });\n    }\n\n    return dates;\n  }\n\n  /* ═══ GET WEEKDAY NAMES ═══ */\n  getWeekdayNames() {\n    const formatter = new Intl.DateTimeFormat(this.locale, { weekday: 'short' });\n    const weekdays = [];\n\n    for (let i = 0; i < 7; i++) {\n      const dayIndex = (i + this.firstDay) % 7;\n      const date = new Date(2024, 0, dayIndex + 7); // Sunday is Jan 7, 2024\n      weekdays.push(formatter.format(date).toUpperCase().substring(0, 2));\n    }\n\n    return weekdays;\n  }\n\n  /* ═══ FORMAT MONTH AND YEAR ═══ */\n  formatMonthYear() {\n    const formatter = new Intl.DateTimeFormat(this.locale, { month: 'long', year: 'numeric' });\n    return formatter.format(this.currentDate);\n  }\n\n  /* ═══ FORMAT PREVIOUS MONTH ═══ */\n  formatPrevMonth() {\n    const prevMonth = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() - 1);\n    const formatter = new Intl.DateTimeFormat(this.locale, { month: 'long', year: 'numeric' });\n    return formatter.format(prevMonth);\n  }\n\n  /* ═══ FORMAT NEXT MONTH ═══ */\n  formatNextMonth() {\n    const nextMonth = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() + 1);\n    const formatter = new Intl.DateTimeFormat(this.locale, { month: 'long', year: 'numeric' });\n    return formatter.format(nextMonth);\n  }\n\n  /* ═══ HANDLE DAY CLICK ═══ */\n  handleDayClick(event, date) {\n    if (event.target.disabled || event.target.hasAttribute('data-outside')) {\n      return;\n    }\n\n    this.selectDate(date);\n  }\n\n  /* ═══ SELECT DATE ═══ */\n  selectDate(date) {\n    if (this.mode === 'single') {\n      this.selectedDate = date;\n      this.focusedDate = date;\n      this.dispatchDateSelectEvent();\n    } else if (this.mode === 'range') {\n      if (!this.rangeStart) {\n        this.rangeStart = date;\n      } else if (!this.rangeEnd) {\n        if (date < this.rangeStart) {\n          this.rangeEnd = this.rangeStart;\n          this.rangeStart = date;\n        } else {\n          this.rangeEnd = date;\n        }\n      } else {\n        this.rangeStart = date;\n        this.rangeEnd = null;\n      }\n      this.dispatchDateSelectEvent();\n    } else if (this.mode === 'multiple') {\n      const dateStr = this.dateToString(date);\n      if (this.selectedDates.has(dateStr)) {\n        this.selectedDates.delete(dateStr);\n      } else {\n        this.selectedDates.add(dateStr);\n      }\n      this.dispatchDateSelectEvent();\n    }\n\n    this.render();\n  }\n\n  /* ═══ HANDLE DAY KEYBOARD NAVIGATION ═══ */\n  handleDayKeyDown(event, date, index, totalDates) {\n    const key = event.key;\n\n    if (key === 'Enter' || key === ' ') {\n      event.preventDefault();\n      this.selectDate(date);\n      return;\n    }\n\n    if (key === 'ArrowUp') {\n      event.preventDefault();\n      const newIndex = Math.max(0, index - 7);\n      this.focusDateAtIndex(newIndex);\n      return;\n    }\n\n    if (key === 'ArrowDown') {\n      event.preventDefault();\n      const newIndex = Math.min(totalDates - 1, index + 7);\n      this.focusDateAtIndex(newIndex);\n      return;\n    }\n\n    if (key === 'ArrowLeft') {\n      event.preventDefault();\n      const newIndex = Math.max(0, index - 1);\n      this.focusDateAtIndex(newIndex);\n      return;\n    }\n\n    if (key === 'ArrowRight') {\n      event.preventDefault();\n      const newIndex = Math.min(totalDates - 1, index + 1);\n      this.focusDateAtIndex(newIndex);\n      return;\n    }\n\n    if (key === 'Home') {\n      event.preventDefault();\n      const weekStart = Math.floor(index / 7) * 7;\n      this.focusDateAtIndex(weekStart);\n      return;\n    }\n\n    if (key === 'End') {\n      event.preventDefault();\n      const weekEnd = Math.ceil((index + 1) / 7) * 7 - 1;\n      this.focusDateAtIndex(Math.min(totalDates - 1, weekEnd));\n      return;\n    }\n\n    if (key === 'PageUp') {\n      event.preventDefault();\n      this.handlePrevMonth();\n      return;\n    }\n\n    if (key === 'PageDown') {\n      event.preventDefault();\n      this.handleNextMonth();\n      return;\n    }\n  }\n\n  /* ═══ FOCUS DATE AT INDEX ═══ */\n  focusDateAtIndex(index) {\n    const buttons = this.querySelectorAll('.ren-calendar-day');\n    if (buttons[index] && !buttons[index].disabled) {\n      this.focusedDate = parseLocalDate(buttons[index].dataset.date);\n      this.updateTabIndex();\n      buttons[index].focus();\n    }\n  }\n\n  /* ═══ HANDLE PREVIOUS MONTH ═══ */\n  handlePrevMonth() {\n    this.currentDate = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() - 1);\n    this.render();\n  }\n\n  /* ═══ HANDLE NEXT MONTH ═══ */\n  handleNextMonth() {\n    this.currentDate = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() + 1);\n    this.render();\n  }\n\n  /* ═══ GO TO MONTH ═══ */\n  goToMonth(year, month) {\n    this.currentDate = new Date(year, month);\n    this.render();\n  }\n\n  /* ═══ SET RANGE ═══ */\n  setRange(start, end) {\n    if (this.mode !== 'range') {\n      console.warn('RenCalendar: setRange() only works in range mode');\n      return;\n    }\n\n    this.rangeStart = this.parseDate(start);\n    this.rangeEnd = this.parseDate(end);\n    this.render();\n  }\n\n  /* ═══ DISPATCH CUSTOM EVENT ═══ */\n  dispatchDateSelectEvent() {\n    let eventData = { date: this.selectedDate };\n\n    if (this.mode === 'range') {\n      eventData = { range: { start: this.rangeStart, end: this.rangeEnd } };\n    } else if (this.mode === 'multiple') {\n      eventData = { dates: Array.from(this.selectedDates).map((d) => parseLocalDate(d)) };\n    }\n\n    this.dispatchEvent(\n      new CustomEvent('ren-date-select', {\n        detail: eventData,\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ UTILITY METHODS ═══ */\n\n  /* ═══ CHECK IF DATE IS TODAY ═══ */\n  isToday(date) {\n    const today = new Date();\n    return this.isSameDay(date, today);\n  }\n\n  /* ═══ CHECK IF TWO DATES ARE THE SAME DAY ═══ */\n  isSameDay(date1, date2) {\n    return (\n      date1.getFullYear() === date2.getFullYear() &&\n      date1.getMonth() === date2.getMonth() &&\n      date1.getDate() === date2.getDate()\n    );\n  }\n\n  /* ═══ CHECK IF DATE IS DISABLED ═══ */\n  isDateDisabled(date) {\n    if (this.minDate && date < this.minDate) return true;\n    if (this.maxDate && date > this.maxDate) return true;\n    return false;\n  }\n\n  /* ═══ CONVERT DATE TO ISO STRING ═══ */\n  dateToString(date) {\n    return formatLocalDate(date);\n  }\n\n  parseDate(value) {\n    return parseLocalDate(value);\n  }\n\n  /* ═══ PUBLIC METHODS ═══ */\n\n  /* ═══ GET VALUE ═══ */\n  getValue() {\n    if (this.mode === 'single') {\n      return this.selectedDate ? this.dateToString(this.selectedDate) : null;\n    } else if (this.mode === 'range') {\n      return this.rangeStart && this.rangeEnd\n        ? { start: this.dateToString(this.rangeStart), end: this.dateToString(this.rangeEnd) }\n        : null;\n    } else if (this.mode === 'multiple') {\n      return Array.from(this.selectedDates);\n    }\n  }\n\n  /* ═══ SET VALUE ═══ */\n  setValue(value) {\n    if (this.mode === 'single' && typeof value === 'string') {\n    this.selectedDate = this.parseDate(value);\n      this.render();\n    } else if (this.mode === 'range' && value && typeof value === 'object') {\n      this.rangeStart = this.parseDate(value.start);\n      this.rangeEnd = this.parseDate(value.end);\n      this.render();\n    } else if (this.mode === 'multiple' && Array.isArray(value)) {\n      this.selectedDates = new Set(value);\n      this.render();\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-calendar')) {\n  customElements.define('ren-calendar', RenCalendar);\n}\n",
      "path": "components/composites/ren-calendar/ren-calendar.js",
      "id": "file:components/composites/ren-calendar/ren-calendar.js",
      "type": "javascript",
      "name": "ren-calendar.js"
    },
    {
      "data": {},
      "body": "/* ═══════════════════════════════════════════════════════════════════════════\n   RenDS — <ren-carousel> Web Component\n   ═══════════════════════════════════════════════════════════════════════════\n   Accessible carousel/slider with scroll-snap, keyboard nav, autoplay, and dots.\n\n   Features:\n   - CSS scroll-snap for performant sliding\n   - Configurable slides-per-view (1, 2, 3, 4)\n   - Fade or slide transitions\n   - Touch-friendly swipe support\n   - Keyboard navigation (Arrow keys)\n   - Optional autoplay with progress indicator\n   - Responsive via container queries\n   - Respects prefers-reduced-motion\n   ═════════════════════════════════════════════════════════════════════════ */\n\n/* ═══ CAROUSEL OUTER CONTAINER ═══ */\n.ren-carousel {\n  container-type: inline-size;\n  container-name: ren-carousel;\n  position: relative;\n  width: 100%;\n  height: auto;\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-4);\n}\n\n/* ═══ VIEWPORT (SCROLL CONTAINER) ═══ */\n.ren-carousel-viewport {\n  position: relative;\n  width: 100%;\n  height: auto;\n  overflow-x: auto;\n  overflow-y: hidden;\n  scroll-behavior: smooth;\n  scroll-snap-type: x mandatory;\n  scrollbar-width: none; /* Firefox */\n  -ms-overflow-style: none; /* IE and Edge */\n  touch-action: pan-x pinch-zoom; /* Prevent vertical scroll interference during horizontal swipe */\n}\n\n/* Hide scrollbar in Chrome, Safari, and Opera */\n.ren-carousel-viewport::-webkit-scrollbar {\n  display: none;\n}\n\n/* Container for slides with flexbox */\n.ren-carousel-viewport {\n  display: flex;\n  flex-wrap: nowrap;\n}\n\n/* ═══ INDIVIDUAL SLIDES ═══ */\n.ren-carousel-slide {\n  flex: 0 0 100%;\n  min-width: 100%;\n  scroll-snap-align: start;\n  scroll-snap-stop: always;\n  height: auto;\n}\n\n/* Ensure slide content can be sized */\n.ren-carousel-slide > * {\n  display: block;\n  width: 100%;\n  height: auto;\n}\n\n/* ═══ SLIDES-PER-VIEW VARIANTS ═══ */\n\n/* 2 slides visible (50% each) */\n.ren-carousel-2 .ren-carousel-slide {\n  flex: 0 0 50%;\n  min-width: 50%;\n}\n\n/* 3 slides visible (33.33% each) */\n.ren-carousel-3 .ren-carousel-slide {\n  flex: 0 0 calc(100% / 3);\n  min-width: calc(100% / 3);\n}\n\n/* 4 slides visible (25% each) */\n.ren-carousel-4 .ren-carousel-slide {\n  flex: 0 0 25%;\n  min-width: 25%;\n}\n\n/* Responsive: stack to 1 or 2 on narrow screens via container queries */\n@supports (container-type: inline-size) {\n  @container (max-width: 640px) {\n    .ren-carousel-2 .ren-carousel-slide,\n    .ren-carousel-3 .ren-carousel-slide,\n    .ren-carousel-4 .ren-carousel-slide {\n      flex: 0 0 100%;\n      min-width: 100%;\n    }\n  }\n\n  @container (max-width: 1024px) and (min-width: 641px) {\n    .ren-carousel-3 .ren-carousel-slide,\n    .ren-carousel-4 .ren-carousel-slide {\n      flex: 0 0 50%;\n      min-width: 50%;\n    }\n  }\n}\n\n/* ═══ FADE TRANSITION VARIANT ═══ */\n.ren-carousel-fade {\n  position: relative;\n}\n\n.ren-carousel-fade .ren-carousel-viewport {\n  scroll-behavior: auto;\n}\n\n.ren-carousel-fade .ren-carousel-slide {\n  position: absolute;\n  left: 0;\n  top: 0;\n  width: 100%;\n  opacity: 0;\n  transition: opacity var(--duration-enter) var(--ease-enter);\n  pointer-events: none;\n}\n\n.ren-carousel-fade .ren-carousel-slide[aria-current=\"true\"] {\n  position: relative;\n  opacity: 1;\n  pointer-events: auto;\n}\n\n/* ═══ NAVIGATION ARROWS ═══ */\n.ren-carousel-prev,\n.ren-carousel-next {\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n  z-index: 10;\n\n  width: var(--touch-min);\n  height: var(--touch-min);\n  min-width: 44px;\n  min-height: 44px;\n\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  background-color: rgb(0, 0, 0, 0.4);\n  border: none;\n  border-radius: var(--radius-full);\n  cursor: pointer;\n  color: white;\n  font-size: 20px;\n  transition: var(--transition-tactile);\n}\n\n/* Arrow positioning */\n.ren-carousel-prev {\n  left: var(--space-3);\n}\n\n.ren-carousel-next {\n  right: var(--space-3);\n}\n\n/* Arrow chevron using ::before pseudo-element */\n.ren-carousel-prev::before {\n  content: '‹';\n  font-size: 24px;\n  font-weight: bold;\n  line-height: 1;\n}\n\n.ren-carousel-next::before {\n  content: '›';\n  font-size: 24px;\n  font-weight: bold;\n  line-height: 1;\n}\n\n/* Hover state — increase opacity */\n.ren-carousel-prev:hover:not(:disabled),\n.ren-carousel-next:hover:not(:disabled) {\n  background-color: rgb(0, 0, 0, 0.7);\n}\n\n/* Active/focus state */\n.ren-carousel-prev:focus-visible,\n.ren-carousel-next:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: 2px;\n}\n\n/* Hidden arrows at boundaries */\n.ren-carousel[data-at-start] .ren-carousel-prev,\n.ren-carousel[data-at-end] .ren-carousel-next {\n  opacity: 0;\n  pointer-events: none;\n}\n\n/* Disabled state */\n.ren-carousel-prev:disabled,\n.ren-carousel-next:disabled {\n  opacity: 0.4;\n  cursor: not-allowed;\n}\n\n/* ═══ PROGRESSIVE ENHANCEMENT: CSS-ONLY SCROLL BUTTONS ═══\n   Modern browsers support ::scroll-button() pseudo-elements for native\n   scroll container navigation without JavaScript.\n   When supported, these replace the JS-generated arrow buttons. */\n\n@supports selector(::scroll-button(inline-start)) {\n  .ren-carousel-viewport {\n    /* Enable native scroll buttons */\n    overflow-x: auto;\n  }\n\n  .ren-carousel-viewport::scroll-button(inline-start),\n  .ren-carousel-viewport::scroll-button(inline-end) {\n    position: absolute;\n    top: 50%;\n    transform: translateY(-50%);\n    z-index: 10;\n\n    width: var(--touch-min, 44px);\n    height: var(--touch-min, 44px);\n    min-width: 44px;\n    min-height: 44px;\n\n    display: flex;\n    align-items: center;\n    justify-content: center;\n\n    background-color: rgb(0, 0, 0, 0.4);\n    border: none;\n    border-radius: var(--radius-full, 9999px);\n    cursor: pointer;\n    color: white;\n\n    transition: var(--transition-tactile);\n  }\n\n  .ren-carousel-viewport::scroll-button(inline-start) {\n    content: '‹';\n    left: var(--space-3, 0.75rem);\n  }\n\n  .ren-carousel-viewport::scroll-button(inline-end) {\n    content: '›';\n    right: var(--space-3, 0.75rem);\n  }\n\n  .ren-carousel-viewport::scroll-button(inline-start):hover,\n  .ren-carousel-viewport::scroll-button(inline-end):hover {\n    background-color: rgb(0, 0, 0, 0.7);\n  }\n\n  /* Hide JS-generated arrows when native scroll buttons are available */\n  .ren-carousel .ren-carousel-prev,\n  .ren-carousel .ren-carousel-next {\n    display: none;\n  }\n}\n\n/* ═══ PAGINATION DOTS ═══ */\n.ren-carousel-dots {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--space-2);\n  margin-top: var(--space-3);\n  padding: 0;\n  list-style: none;\n}\n\n.ren-carousel-dot {\n  width: 8px;\n  height: 8px;\n  border-radius: var(--radius-full);\n  background-color: var(--color-border);\n  border: none;\n  cursor: pointer;\n  transition: var(--transition-tactile);\n  padding: 0;\n  margin: 0;\n}\n\n/* Active dot */\n.ren-carousel-dot[aria-current=\"true\"] {\n  background-color: var(--color-accent);\n  transform: scale(1.25);\n}\n\n/* Hover state */\n.ren-carousel-dot:hover:not([aria-current=\"true\"]) {\n  background-color: var(--color-border);\n  opacity: 0.7;\n}\n\n/* Focus visible */\n.ren-carousel-dot:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: 2px;\n  border-radius: var(--radius-full);\n}\n\n/* ═══ AUTOPLAY PROGRESS BAR ═══ */\n.ren-carousel-dot[aria-current=\"true\"]::after {\n  content: '';\n  position: absolute;\n  bottom: -4px;\n  left: 50%;\n  transform: translateX(-50%);\n  width: 6px;\n  height: 2px;\n  background-color: var(--color-accent);\n  border-radius: var(--radius-full);\n  opacity: 0;\n}\n\n/* Show progress bar when autoplay is active */\n.ren-carousel[data-autoplay] .ren-carousel-dot[aria-current=\"true\"]::after {\n  opacity: 1;\n  animation: carousel-progress linear forwards;\n}\n\n@keyframes carousel-progress {\n  from {\n    width: 0;\n  }\n  to {\n    width: 6px;\n  }\n}\n\n/* Dynamic progress bar timing — controlled by JS */\n.ren-carousel[data-autoplay-duration] .ren-carousel-dot[aria-current=\"true\"]::after {\n  animation-duration: inherit;\n}\n\n/* ═══ CAROUSEL COUNTER ═══ */\n.ren-carousel-counter {\n  text-align: center;\n  font-size: 14px;\n  color: var(--color-text-muted);\n  margin-top: var(--space-2);\n  font-variant-numeric: tabular-nums;\n}\n\n.ren-carousel-counter-current {\n  font-weight: 600;\n  color: var(--color-text);\n}\n\n/* ═══ ACCESSIBILITY & ANIMATIONS ═══ */\n\n/* Respect reduced motion preference */\n@media (prefers-reduced-motion: reduce) {\n  .ren-carousel-viewport {\n    scroll-behavior: auto;\n  }\n\n  .ren-carousel-slide,\n  .ren-carousel-dot,\n  .ren-carousel-prev,\n  .ren-carousel-next {\n    transition: none;\n    animation: none;\n  }\n\n  .ren-carousel[data-autoplay] .ren-carousel-dot[aria-current=\"true\"]::after {\n    animation: none;\n    width: 6px;\n    opacity: 1;\n  }\n}\n\n/* ═══ FOCUS VISIBLE FOR KEYBOARD NAVIGATION ═══ */\n.ren-carousel-slide:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: 2px;\n}\n\n/* ═══ RESPONSIVE ADJUSTMENTS (Container Queries) ═══ */\n\n/* Compact carousel */\n@container ren-carousel (max-width: 480px) {\n  .ren-carousel {\n    gap: var(--space-2);\n  }\n\n  .ren-carousel-prev,\n  .ren-carousel-next {\n    width: 40px;\n    height: 40px;\n    font-size: 16px;\n  }\n\n  .ren-carousel-prev {\n    left: var(--space-2);\n  }\n\n  .ren-carousel-next {\n    right: var(--space-2);\n  }\n\n  .ren-carousel-dots {\n    gap: var(--space-1);\n  }\n\n  .ren-carousel-dot {\n    width: 6px;\n    height: 6px;\n  }\n\n  .ren-carousel-dot[aria-current=\"true\"] {\n    transform: scale(1.1);\n  }\n}\n\n/* Wide carousel — hover reveal arrows */\n@container ren-carousel (min-width: 768px) {\n  .ren-carousel-prev,\n  .ren-carousel-next {\n    opacity: 0.6;\n  }\n\n  .ren-carousel:hover .ren-carousel-prev:not(:disabled),\n  .ren-carousel:hover .ren-carousel-next:not(:disabled) {\n    opacity: 1;\n  }\n}\n\n/* ═══ UTILITY CLASSES ═══ */\n\n/* Carousel without dots */\n.ren-carousel-no-dots .ren-carousel-dots {\n  display: none;\n}\n\n/* Carousel without arrows */\n.ren-carousel-no-arrows .ren-carousel-prev,\n.ren-carousel-no-arrows .ren-carousel-next {\n  display: none;\n}\n\n/* Carousel without counter */\n.ren-carousel-no-counter .ren-carousel-counter {\n  display: none;\n}\n\n/* Full width slides (1 per view) */\n.ren-carousel-full .ren-carousel-slide {\n  flex: 0 0 100%;\n  min-width: 100%;\n}\n\n/* Draggable appearance */\n.ren-carousel-viewport {\n  user-select: none;\n  -webkit-user-select: none;\n}\n\n.ren-carousel-slide {\n  user-select: none;\n  -webkit-user-select: none;\n}\n\n/* Touch feedback */\n.ren-carousel-slide {\n  cursor: grab;\n}\n\n.ren-carousel-slide:active {\n  cursor: grabbing;\n}\n\n/* ═══ LOADING STATE ═══ */\n.ren-carousel[aria-busy=\"true\"] {\n  opacity: 0.6;\n  pointer-events: none;\n}\n\n/* ═══ SEMANTIC HTML ADJUSTMENTS ═══ */\n.ren-carousel[role=\"group\"],\n.ren-carousel[role=\"region\"] {\n  /* Already defined above, this is for clarity */\n}\n\n/* Ensure images in slides maintain aspect ratio */\n.ren-carousel-slide img {\n  width: 100%;\n  height: auto;\n  display: block;\n  object-fit: cover;\n}\n\n/* Video in carousel */\n.ren-carousel-slide video {\n  width: 100%;\n  height: auto;\n  display: block;\n}\n/* Appearance API bridge */\n.ren-carousel { gap: var(--ren-carousel-gap, var(--space-4)); border-radius: var(--ren-carousel-radius, var(--radius-lg)); transition-duration: var(--ren-carousel-duration, var(--duration-normal)); transition-timing-function: var(--ren-carousel-easing, var(--ease-out)); }\n.ren-carousel .ren-carousel-arrow { inline-size: var(--ren-carousel-arrow-size, var(--size-lg)); block-size: var(--ren-carousel-arrow-size, var(--size-lg)); }\n.ren-carousel .ren-carousel-dot { inline-size: var(--ren-carousel-dot-size, 8px); block-size: var(--ren-carousel-dot-size, 8px); background: var(--ren-carousel-dot-color, var(--color-fill)); }\n.ren-carousel .ren-carousel-dot[aria-current=\"true\"], .ren-carousel .ren-carousel-dot.is-active { background: var(--ren-carousel-dot-active, var(--color-accent)); }\n",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "file:components/composites/ren-carousel/ren-carousel.css",
      "type": "css",
      "name": "ren-carousel.css"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — <ren-carousel> Web Component\n * ═════════════════════════════════════════════════════════════════════════\n * Accessible carousel/slider with native scroll-snap, keyboard navigation,\n * autoplay, pagination dots, and optional next/prev arrows.\n *\n * Features:\n * - Light DOM (no Shadow DOM)\n * - CSS scroll-snap for performant horizontal scrolling\n * - Keyboard navigation (Arrow Left/Right)\n * - Touch swipe support (native scroll-snap)\n * - Auto-generated pagination dots\n * - Optional next/prev navigation arrows\n * - Autoplay with configurable interval\n * - Loop mode (infinite carousel)\n * - Configurable slides-per-view (1, 2, 3, 4)\n * - Fade transition variant\n * - Custom events (ren-slide-change)\n * - Respects prefers-reduced-motion\n *\n * Attributes:\n *   autoplay: milliseconds interval for autoplay (e.g., 5000)\n *   loop: enable infinite looping\n *   slides-per-view: number of slides visible (default: 1)\n *   fade: enable fade transition instead of slide\n *\n * Methods:\n *   next() — go to next slide\n *   prev() — go to previous slide\n *   goTo(index) — jump to specific slide\n *   pause() — pause autoplay\n *   resume() — resume autoplay\n *\n * Events:\n *   ren-slide-change: detail = { index, total, id }\n *\n * Markup:\n *   <ren-carousel autoplay=\"5000\" loop slides-per-view=\"1\">\n *     <div class=\"ren-carousel-viewport\">\n *       <div class=\"ren-carousel-slide\">\n *         <img src=\"slide1.jpg\" alt=\"Slide 1\">\n *       </div>\n *       <div class=\"ren-carousel-slide\">\n *         <img src=\"slide2.jpg\" alt=\"Slide 2\">\n *       </div>\n *       <!-- Dots and arrows auto-generated by JS -->\n *     </div>\n *   </ren-carousel>\n * ═════════════════════════════════════════════════════════════════════════\n */\n\nlet nextId = 0;\n\nexport class RenCarousel extends HTMLElement {\n  static get observedAttributes() {\n    return ['autoplay', 'loop', 'slides-per-view', 'fade'];\n  }\n\n  constructor() {\n    super();\n    this.id = this.id || `ren-carousel-${++nextId}`;\n\n    this._viewport = null;\n    this._slides = [];\n    this._dotsContainer = null;\n    this._dots = [];\n    this._prevButton = null;\n    this._nextButton = null;\n    this._counterElement = null;\n\n    this._currentIndex = 0;\n    this._totalSlides = 0;\n    this._autoplayInterval = null;\n    this._autoplayDuration = null;\n    this._isAutoplayActive = false;\n    this._scrollTimeout = null;\n    this._intersectionObserver = null;\n    this._contentObserver = null;\n    this._refreshQueued = false;\n    this._nextSlideId = 0;\n\n    this._autoplayMs = 0;\n    this._shouldLoop = false;\n    this._slidePerView = 1;\n    this._isFade = false;\n    this._reducedMotionQuery = null;\n    this._handleReducedMotionChange = null;\n  }\n\n  /* ═══ LIFECYCLE ═══ */\n\n  connectedCallback() {\n    this._parseAttributes();\n    this._initialize();\n    this._observeContent();\n    this._attachReducedMotionListener();\n    this._attachEventListeners();\n    if (this._autoplayMs > 0) {\n      this._startAutoplay();\n    }\n  }\n\n  disconnectedCallback() {\n    this._stopAutoplay();\n    if (this._intersectionObserver) {\n      this._intersectionObserver.disconnect();\n    }\n    this._contentObserver?.disconnect();\n    this._contentObserver = null;\n    this._detachReducedMotionListener();\n    this._detachEventListeners();\n  }\n\n  _observeContent() {\n    if (typeof MutationObserver === 'undefined' || this._contentObserver) return;\n    this._contentObserver = new MutationObserver((mutations) => {\n      // Ignore mutations caused by our generated controls; refresh only when\n      // slide/viewport content actually changes.\n      if (!mutations.some((m) => [...m.addedNodes, ...m.removedNodes].some(\n        (node) => node.nodeType === Node.ELEMENT_NODE &&\n          (node.classList?.contains('ren-carousel-slide') ||\n            node.classList?.contains('ren-carousel-viewport'))\n      ))) return;\n      if (this._refreshQueued) return;\n      this._refreshQueued = true;\n      queueMicrotask(() => {\n        this._refreshQueued = false;\n        if (!this.isConnected) return;\n        this._contentObserver?.disconnect();\n        this._initialize();\n        this._contentObserver?.observe(this, { childList: true, subtree: true });\n      });\n    });\n    this._contentObserver.observe(this, { childList: true, subtree: true });\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (!this.isConnected) return;\n\n    switch (name) {\n      case 'autoplay':\n        this._autoplayMs = newValue ? parseInt(newValue, 10) : 0;\n        if (this._autoplayMs > 0) {\n          this._startAutoplay();\n        } else {\n          this._stopAutoplay();\n        }\n        break;\n      case 'loop':\n        this._shouldLoop = this.hasAttribute('loop');\n        this._updateArrowVisibility();\n        break;\n      case 'slides-per-view':\n        this._slidePerView = newValue ? parseInt(newValue, 10) : 1;\n        this._applySlidePerViewClass();\n        break;\n      case 'fade':\n        this._isFade = this.hasAttribute('fade');\n        this._updateFadeVariant();\n        this._setupIntersectionObserver();\n        break;\n    }\n  }\n\n  /* ═══ INITIALIZATION ═══ */\n\n  _parseAttributes() {\n    this._autoplayMs = this.hasAttribute('autoplay')\n      ? parseInt(this.getAttribute('autoplay'), 10)\n      : 0;\n    this._shouldLoop = this.hasAttribute('loop');\n    this._slidePerView = this.hasAttribute('slides-per-view')\n      ? parseInt(this.getAttribute('slides-per-view'), 10)\n      : 1;\n    this._isFade = this.hasAttribute('fade');\n  }\n\n  _initialize() {\n    // Rebuilds must tear down observers and generated navigation first. This\n    // keeps removal-to-empty and mode changes from retaining stale state.\n    this._intersectionObserver?.disconnect();\n    this._intersectionObserver = null;\n    this.querySelector('.ren-carousel-dots')?.remove();\n    this.querySelectorAll('.ren-carousel-prev, .ren-carousel-next').forEach((el) => el.remove());\n    this.querySelector('.ren-carousel-counter')?.remove();\n    // Set up ARIA attributes\n    this.setAttribute('role', 'group');\n    this.setAttribute('aria-roledescription', 'carousel');\n    if (!this.getAttribute('aria-label')) {\n      this.setAttribute('aria-label', 'Image carousel');\n    }\n\n    // Find viewport\n    this._viewport = this.querySelector('.ren-carousel-viewport');\n    if (!this._viewport) {\n      console.warn('RenCarousel: No viewport found');\n      return;\n    }\n\n    // Add scroll-snap to viewport if not already present\n    if (!this._viewport.classList.contains('ren-carousel-viewport')) {\n      this._viewport.classList.add('ren-carousel-viewport');\n    }\n\n    // Collect slides\n    this._slides = Array.from(\n      this.querySelectorAll('.ren-carousel-slide')\n    );\n\n    if (this._slides.length === 0) {\n      this._totalSlides = 0;\n      this._currentIndex = 0;\n      this._dots = [];\n      this._dotsContainer = null;\n      this._prevButton = null;\n      this._nextButton = null;\n      this._counterElement = null;\n      console.warn('RenCarousel: No slides found');\n      return;\n    }\n\n    this._totalSlides = this._slides.length;\n    this._currentIndex = Math.max(0, Math.min(this._currentIndex, this._totalSlides - 1));\n\n    // Set up slides with ARIA attributes\n    this._slides.forEach((slide, index) => {\n      if (!slide.id) {\n        let generatedId;\n        do {\n          generatedId = `${this.id}-slide-${++this._nextSlideId}`;\n        } while (\n          this._slides.some((candidate) => candidate !== slide && candidate.id === generatedId) ||\n          this.ownerDocument?.getElementById(generatedId)\n        );\n        slide.id = generatedId;\n      }\n      slide.setAttribute('role', 'group');\n      slide.setAttribute('aria-roledescription', 'slide');\n      slide.setAttribute('aria-label', `Slide ${index + 1} of ${this._totalSlides}`);\n      if (index === this._currentIndex) {\n        slide.setAttribute('aria-current', 'true');\n      } else {\n        slide.removeAttribute('aria-current');\n      }\n      slide.tabIndex = 0;\n    });\n\n    // Create navigation elements\n    this._createDots();\n    this._createArrows();\n    this._createCounter();\n\n    // Apply variant classes\n    this._applySlidePerViewClass();\n    this._updateFadeVariant();\n\n    // Set up Intersection Observer to track active slide\n    this._setupIntersectionObserver();\n\n    // Initial state\n    this._updateArrowVisibility();\n    this._updateDotState();\n    this._updateCounter();\n  }\n\n  _createDots() {\n    // Remove existing dots if any\n    const existing = this.querySelector('.ren-carousel-dots');\n    if (existing) existing.remove();\n\n    // Create dots container\n    this._dotsContainer = document.createElement('ul');\n    this._dotsContainer.className = 'ren-carousel-dots';\n    this._dotsContainer.setAttribute('aria-label', 'Carousel pagination');\n\n    // Create individual dots\n    this._dots = this._slides.map((_, index) => {\n      const dot = document.createElement('button');\n      dot.className = 'ren-carousel-dot';\n      dot.setAttribute('type', 'button');\n      dot.setAttribute('aria-label', `Go to slide ${index + 1}`);\n      dot.setAttribute('aria-controls', this._slides[index].id);\n      if (index === 0) {\n        dot.setAttribute('aria-current', 'true');\n      }\n\n      dot.addEventListener('click', () => this.goTo(index));\n      const li = document.createElement('li');\n      li.appendChild(dot);\n      this._dotsContainer.appendChild(li);\n\n      return dot;\n    });\n\n    this.appendChild(this._dotsContainer);\n  }\n\n  _createArrows() {\n    // Remove existing arrows if any\n    const existing = this.querySelectorAll('.ren-carousel-prev, .ren-carousel-next');\n    existing.forEach(el => el.remove());\n\n    // Create previous button\n    this._prevButton = document.createElement('button');\n    this._prevButton.className = 'ren-carousel-prev';\n    this._prevButton.setAttribute('type', 'button');\n    this._prevButton.setAttribute('aria-label', 'Previous slide');\n    this._prevButton.addEventListener('click', () => this.prev());\n\n    // Create next button\n    this._nextButton = document.createElement('button');\n    this._nextButton.className = 'ren-carousel-next';\n    this._nextButton.setAttribute('type', 'button');\n    this._nextButton.setAttribute('aria-label', 'Next slide');\n    this._nextButton.addEventListener('click', () => this.next());\n\n    // Insert after viewport\n    this._viewport.parentNode.insertBefore(this._prevButton, this._viewport.nextSibling);\n    this._viewport.parentNode.insertBefore(this._nextButton, this._viewport.nextSibling.nextSibling);\n  }\n\n  _createCounter() {\n    // Remove existing counter if any\n    const existing = this.querySelector('.ren-carousel-counter');\n    if (existing) existing.remove();\n\n    this._counterElement = document.createElement('div');\n    this._counterElement.className = 'ren-carousel-counter';\n    this._counterElement.setAttribute('aria-atomic', 'true');\n    this._syncLiveRegion();\n\n    this.appendChild(this._counterElement);\n  }\n\n  _applySlidePerViewClass() {\n    // Remove all slides-per-view classes\n    this.classList.remove('ren-carousel-1', 'ren-carousel-2', 'ren-carousel-3', 'ren-carousel-4');\n\n    // Add the appropriate class\n    if (this._slidePerView >= 1 && this._slidePerView <= 4) {\n      this.classList.add(`ren-carousel-${this._slidePerView}`);\n    }\n  }\n\n  _updateFadeVariant() {\n    if (this._isFade) {\n      this.classList.add('ren-carousel-fade');\n    } else {\n      this.classList.remove('ren-carousel-fade');\n    }\n  }\n\n  _setupIntersectionObserver() {\n    if (this._intersectionObserver) {\n      this._intersectionObserver.disconnect();\n      this._intersectionObserver = null;\n    }\n    if (this._isFade) {\n      // For fade mode, we'll handle active state differently\n      return;\n    }\n\n    const options = {\n      root: this._viewport,\n      threshold: 0.5,\n    };\n\n    this._intersectionObserver = new IntersectionObserver((entries) => {\n      entries.forEach((entry) => {\n        if (entry.isIntersecting) {\n          const index = this._slides.indexOf(entry.target);\n          if (index !== -1) {\n            this._setCurrentIndex(index);\n          }\n        }\n      });\n    }, options);\n\n    this._slides.forEach((slide) => {\n      this._intersectionObserver.observe(slide);\n    });\n  }\n\n  /* ═══ NAVIGATION METHODS ═══ */\n\n  next() {\n    if (this._currentIndex < this._totalSlides - 1) {\n      this.goTo(this._currentIndex + 1);\n    } else if (this._shouldLoop) {\n      this.goTo(0);\n    }\n  }\n\n  prev() {\n    if (this._currentIndex > 0) {\n      this.goTo(this._currentIndex - 1);\n    } else if (this._shouldLoop) {\n      this.goTo(this._totalSlides - 1);\n    }\n  }\n\n  goTo(index) {\n    // Clamp index\n    index = Math.max(0, Math.min(index, this._totalSlides - 1));\n\n    // Scroll to slide\n    const slide = this._slides[index];\n    if (slide) {\n      slide.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });\n      this._setCurrentIndex(index);\n\n      // Reset autoplay timer when navigating\n      if (this._autoplayMs > 0) {\n        this._stopAutoplay();\n        this._startAutoplay();\n      }\n    }\n  }\n\n  _setCurrentIndex(index) {\n    if (this._currentIndex === index) return;\n\n    // Update slides ARIA\n    this._slides.forEach((slide, i) => {\n      if (i === index) {\n        slide.setAttribute('aria-current', 'true');\n      } else {\n        slide.removeAttribute('aria-current');\n      }\n    });\n\n    this._currentIndex = index;\n    this._updateDotState();\n    this._updateArrowVisibility();\n    this._updateCounter();\n    this._dispatchChangeEvent();\n  }\n\n  /* ═══ STATE UPDATES ═══ */\n\n  _updateDotState() {\n    this._dots.forEach((dot, index) => {\n      if (index === this._currentIndex) {\n        dot.setAttribute('aria-current', 'true');\n      } else {\n        dot.removeAttribute('aria-current');\n      }\n    });\n  }\n\n  _updateArrowVisibility() {\n    if (this._prevButton && this._nextButton) {\n      const atStart = this._currentIndex === 0;\n      const atEnd = this._currentIndex === this._totalSlides - 1;\n\n      if (this._shouldLoop) {\n        // When looping, arrows are always visible\n        this.removeAttribute('data-at-start');\n        this.removeAttribute('data-at-end');\n        this._prevButton.removeAttribute('disabled');\n        this._nextButton.removeAttribute('disabled');\n      } else {\n        // Without loop, hide/disable arrows at boundaries\n        if (atStart) {\n          this.setAttribute('data-at-start', '');\n          this._prevButton.setAttribute('disabled', '');\n        } else {\n          this.removeAttribute('data-at-start');\n          this._prevButton.removeAttribute('disabled');\n        }\n\n        if (atEnd) {\n          this.setAttribute('data-at-end', '');\n          this._nextButton.setAttribute('disabled', '');\n        } else {\n          this.removeAttribute('data-at-end');\n          this._nextButton.removeAttribute('disabled');\n        }\n      }\n    }\n  }\n\n  _updateCounter() {\n    if (this._counterElement) {\n      this._counterElement.innerHTML = `\n        <span class=\"ren-carousel-counter-current\">${this._currentIndex + 1}</span>\n        <span> / ${this._totalSlides}</span>\n      `;\n    }\n  }\n\n  _syncLiveRegion() {\n    this._counterElement?.setAttribute('aria-live', this._isAutoplayActive ? 'off' : 'polite');\n  }\n\n  /* ═══ AUTOPLAY ═══ */\n\n  _prefersReducedMotion() {\n    return window.matchMedia?.('(prefers-reduced-motion: reduce)').matches === true;\n  }\n\n  _attachReducedMotionListener() {\n    this._reducedMotionQuery = window.matchMedia?.('(prefers-reduced-motion: reduce)') ?? null;\n    if (!this._reducedMotionQuery) return;\n\n    this._handleReducedMotionChange = (event) => {\n      if (event.matches) {\n        this._stopAutoplay();\n      } else if (this._autoplayMs > 0) {\n        this._startAutoplay();\n      }\n    };\n\n    this._reducedMotionQuery.addEventListener?.('change', this._handleReducedMotionChange);\n  }\n\n  _detachReducedMotionListener() {\n    if (this._reducedMotionQuery && this._handleReducedMotionChange) {\n      this._reducedMotionQuery.removeEventListener?.('change', this._handleReducedMotionChange);\n    }\n    this._reducedMotionQuery = null;\n    this._handleReducedMotionChange = null;\n  }\n\n  _startAutoplay() {\n    if (this._autoplayInterval || this._autoplayMs <= 0) return;\n    if (this._prefersReducedMotion()) {\n      this._stopAutoplay();\n      return;\n    }\n\n    this._isAutoplayActive = true;\n    this.setAttribute('data-autoplay', '');\n    this.setAttribute('data-autoplay-duration', `${this._autoplayMs}ms`);\n    this._syncLiveRegion();\n\n    // Start autoplay\n    this._autoplayInterval = setInterval(() => {\n      this.next();\n    }, this._autoplayMs);\n  }\n\n  _stopAutoplay() {\n    if (this._autoplayInterval) {\n      clearInterval(this._autoplayInterval);\n      this._autoplayInterval = null;\n    }\n    this._isAutoplayActive = false;\n    this.removeAttribute('data-autoplay');\n    this.removeAttribute('data-autoplay-duration');\n    this._syncLiveRegion();\n  }\n\n  pause() {\n    this._stopAutoplay();\n  }\n\n  resume() {\n    if (this._autoplayMs > 0) {\n      this._startAutoplay();\n    }\n  }\n\n  /* ═══ EVENT LISTENERS ═══ */\n\n  _attachEventListeners() {\n    // Keyboard navigation\n    this._handleKeydown = (e) => this._onKeydown(e);\n    this.addEventListener('keydown', this._handleKeydown);\n\n    // Pause autoplay on hover\n    this._handleMouseEnter = () => {\n      if (this._isAutoplayActive) {\n        this._stopAutoplay();\n      }\n    };\n    this._handleMouseLeave = () => {\n      if (this._autoplayMs > 0) {\n        this._startAutoplay();\n      }\n    };\n    this.addEventListener('mouseenter', this._handleMouseEnter);\n    this.addEventListener('mouseleave', this._handleMouseLeave);\n\n    // Pause autoplay on focus\n    this._handleFocus = () => {\n      if (this._isAutoplayActive) {\n        this._stopAutoplay();\n      }\n    };\n    this._handleBlur = () => {\n      if (this._autoplayMs > 0) {\n        this._startAutoplay();\n      }\n    };\n    this.addEventListener('focus', this._handleFocus, true);\n    this.addEventListener('blur', this._handleBlur, true);\n  }\n\n  _detachEventListeners() {\n    if (this._handleKeydown) {\n      this.removeEventListener('keydown', this._handleKeydown);\n    }\n    if (this._handleMouseEnter) {\n      this.removeEventListener('mouseenter', this._handleMouseEnter);\n    }\n    if (this._handleMouseLeave) {\n      this.removeEventListener('mouseleave', this._handleMouseLeave);\n    }\n    if (this._handleFocus) {\n      this.removeEventListener('focus', this._handleFocus, true);\n    }\n    if (this._handleBlur) {\n      this.removeEventListener('blur', this._handleBlur, true);\n    }\n  }\n\n  _onKeydown(e) {\n    if (e.key === 'ArrowLeft') {\n      e.preventDefault();\n      this.prev();\n    } else if (e.key === 'ArrowRight') {\n      e.preventDefault();\n      this.next();\n    } else if (e.key === ' ' || e.key === 'Enter') {\n      // Allow space/enter on dots\n      if (e.target.classList.contains('ren-carousel-dot')) {\n        e.preventDefault();\n        e.target.click();\n      }\n    }\n  }\n\n  /* ═══ EVENT DISPATCHING ═══ */\n\n  _dispatchChangeEvent() {\n    const event = new CustomEvent('ren-slide-change', {\n      detail: {\n        index: this._currentIndex,\n        total: this._totalSlides,\n        id: this.id,\n      },\n      bubbles: true,\n      composed: true,\n    });\n    this.dispatchEvent(event);\n  }\n}\n\n// Register the component\nif (!customElements.get('ren-carousel')) {\n  customElements.define('ren-carousel', RenCarousel);\n}\n",
      "path": "components/composites/ren-carousel/ren-carousel.js",
      "id": "file:components/composites/ren-carousel/ren-carousel.js",
      "type": "javascript",
      "name": "ren-carousel.js"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Collapsible Component\n   ============================================\n   Expand/collapse content panel.\n   Uses native <details>/<summary> — ZERO JS.\n\n   CSS-only animation with interpolate-size.\n\n   Different from Accordion: standalone, no group\n   exclusivity. For single expand/collapse sections.\n\n   Usage:\n     <details class=\"ren-collapsible\">\n       <summary>Click to expand</summary>\n       <div class=\"ren-collapsible-content\">\n         Hidden content here\n       </div>\n     </details>\n\n   Open by default:\n     <details class=\"ren-collapsible\" open>\n       ...\n     </details>\n   ============================================ */\n\n/* Enable height animation on details */\n.ren-collapsible {\n  interpolate-size: allow-keywords;\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-md);\n  overflow: hidden;\n}\n\n/* Summary (trigger) */\n.ren-collapsible > summary {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--space-2);\n  min-height: var(--touch-min);\n  padding: var(--space-3) var(--space-4);\n  font-size: var(--body-size);\n  font-weight: var(--weight-medium);\n  color: var(--color-text);\n  cursor: pointer;\n  user-select: none;\n  list-style: none; /* Remove default marker */\n  transition: var(--transition-tactile);\n}\n\n/* Remove default marker in WebKit */\n.ren-collapsible > summary::-webkit-details-marker {\n  display: none;\n}\n\n/* Custom chevron */\n.ren-collapsible > summary::after {\n  content: '';\n  width: 0.5rem;\n  height: 0.5rem;\n  border: solid var(--color-text-muted);\n  border-width: 0 2px 2px 0;\n  transform: rotate(45deg);\n  flex-shrink: 0;\n  transition: transform var(--duration-enter) var(--ease-enter);\n}\n\n.ren-collapsible[open] > summary::after {\n  transform: rotate(-135deg);\n}\n\n.ren-collapsible > summary:hover {\n  background-color: var(--color-fill);\n}\n\n.ren-collapsible > summary:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: -2px;\n}\n\n/* Content area */\n.ren-collapsible-content {\n  padding: 0 var(--space-4) var(--space-4);\n  font-size: var(--body-size);\n  color: var(--color-text-secondary);\n  line-height: var(--leading-normal);\n}\n\n/* ─── Ghost variant (no border) ─── */\n.ren-collapsible-ghost {\n  border: none;\n}\n\n.ren-collapsible-ghost > summary {\n  padding-inline: 0;\n}\n\n.ren-collapsible-ghost .ren-collapsible-content {\n  padding-inline: 0;\n}\n\n/* ─── Animate open/close ─── */\n/* Uses CSS content-visibility for animation */\n.ren-collapsible .ren-collapsible-content {\n  content-visibility: visible;\n}\n\n/* Reduced motion — no animation */\n@media (prefers-reduced-motion: reduce) {\n  .ren-collapsible > summary::after {\n    transition: none;\n  }\n}\n/* Appearance API bridge */\n.ren-collapsible, .ren-accordion { border-color: var(--ren-collapse-border, var(--color-border)); border-radius: var(--ren-collapse-radius, var(--radius-lg)); background: var(--ren-collapse-bg, var(--color-surface)); transition-duration: var(--ren-collapse-duration, var(--duration-normal)); transition-timing-function: var(--ren-collapse-easing, var(--ease-out)); }\n.ren-collapsible > summary, .ren-accordion > summary { padding: var(--ren-collapse-padding, var(--space-4)); font-size: var(--ren-collapse-trigger-font, var(--label-lg-size)); font-weight: var(--ren-collapse-trigger-weight, var(--weight-semibold)); }\n",
      "path": "components/composites/ren-collapsible/ren-collapsible.css",
      "id": "file:components/composites/ren-collapsible/ren-collapsible.css",
      "type": "css",
      "name": "ren-collapsible.css"
    },
    {
      "data": {},
      "body": "/* ═══════════════════════════════════════════════════════════════════\n   RenDS — Color Picker Composite Component\n   ═══════════════════════════════════════════════════════════════════\n   A fully accessible, feature-rich color picker with support for:\n   - HSV color model (internal) with conversion to/from HEX, RGB, HSL\n   - Saturation/brightness canvas area\n   - Hue slider\n   - Optional alpha/opacity slider\n   - Text inputs for manual color entry\n   - Preset color swatches\n   - EyeDropper API integration (when available)\n   - Keyboard navigation and ARIA labels\n   - Smooth animations with @starting-style support\n\n   Structure:\n   .ren-color-picker (container)\n     .ren-color-picker-trigger (button showing current color)\n     [popover] .ren-color-picker-dropdown\n       .ren-color-picker-saturation (canvas area for S/B)\n         .ren-color-picker-saturation-handle (circular cursor)\n       .ren-color-picker-hue (horizontal rainbow slider)\n         .ren-color-picker-hue-handle (thumb)\n       .ren-color-picker-alpha (opacity slider, optional)\n         .ren-color-picker-alpha-handle (thumb)\n       .ren-color-picker-preview (color display swatch)\n       .ren-color-picker-inputs\n         .ren-color-picker-hex-input\n         .ren-color-picker-channel (R/G/B or H/S/L)\n       .ren-color-picker-swatches (preset colors, optional)\n       .ren-color-picker-format-toggle (HEX/RGB/HSL switcher)\n       .ren-color-picker-eyedropper (screen picker, optional)\n   ═══════════════════════════════════════════════════════════════════ */\n\n/* ═══ COLOR PICKER CONTAINER ═══ */\n.ren-color-picker {\n  position: relative;\n  display: inline-flex;\n  flex-direction: column;\n  gap: var(--space-2);\n  font-family: inherit;\n}\n\n/* ═══ TRIGGER BUTTON (Shows Current Color) ═══ */\n.ren-color-picker-trigger {\n  anchor-name: --ren-color-picker-anchor;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--space-2);\n  width: 100%;\n  min-height: var(--touch-min);\n  padding: var(--space-2) var(--space-3);\n  font-size: var(--body-size);\n  font-family: inherit;\n  color: var(--color-text);\n  background-color: var(--color-input-bg);\n  border: 1px solid var(--color-input-border);\n  border-radius: var(--radius-md);\n  cursor: pointer;\n  transition: var(--transition-tactile);\n}\n\n.ren-color-picker-trigger:hover:not(:disabled) {\n  background-color: var(--color-input-bg);\n  border-color: var(--color-input-border-focus);\n}\n\n.ren-color-picker-trigger:focus-visible {\n  outline: none;\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n}\n\n.ren-color-picker-trigger[aria-expanded=\"true\"] {\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n}\n\n.ren-color-picker-trigger:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n  pointer-events: none;\n}\n\n/* Trigger swatch (small colored square) */\n.ren-color-picker-trigger-swatch {\n  flex-shrink: 0;\n  width: 24px;\n  height: 24px;\n  border-radius: var(--radius-sm);\n  border: 1px solid var(--color-border);\n  background-color: var(--white);\n  background-image: repeating-conic-gradient(\n    var(--color-border) 0deg 90deg,\n    transparent 90deg 180deg\n  ) 50% / 4px 4px;\n}\n\n/* Trigger value text */\n.ren-color-picker-trigger-value {\n  flex: 1;\n  text-align: start;\n  font-family: var(--font-mono);\n  font-size: var(--label-size);\n}\n\n/* ═══ DROPDOWN CONTAINER (Popover) ═══ */\n.ren-color-picker-dropdown {\n  position: absolute;\n  position-anchor: --ren-color-picker-anchor;\n  position-area: bottom span-all;\n  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;\n  inset: auto;\n  margin: 0;\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-3);\n  width: auto;\n  max-width: 300px;\n  padding: var(--space-4);\n  background-color: var(--color-surface-raised);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  z-index: 50;\n\n  /* Popover API defaults */\n  &:popover-open {\n    animation: ren-color-picker-open var(--duration-enter) var(--ease-enter);\n  }\n\n  @supports (animation-timeline: view()) {\n    &:popover-open {\n      animation: ren-color-picker-open var(--duration-enter) var(--ease-enter);\n      transition-behavior: allow-discrete;\n    }\n\n    @starting-style {\n      opacity: 0;\n      transform: scale(0.95);\n    }\n  }\n}\n\n.ren-color-picker-dropdown[data-side=\"bottom\"],\n.ren-color-picker-dropdown:not([data-side]) {\n  position-area: bottom span-all;\n  margin-block-start: var(--space-2);\n  margin-block-end: 0;\n  margin-inline: 0;\n}\n\n.ren-color-picker-dropdown[data-side=\"top\"] {\n  position-area: top span-all;\n  margin-block-start: 0;\n  margin-block-end: var(--space-2);\n  margin-inline: 0;\n}\n\n.ren-color-picker-dropdown[data-side=\"right\"] {\n  position-area: right span-all;\n  margin-block: 0;\n  margin-inline-start: var(--space-2);\n  margin-inline-end: 0;\n}\n\n.ren-color-picker-dropdown[data-side=\"left\"] {\n  position-area: left span-all;\n  margin-block: 0;\n  margin-inline-start: 0;\n  margin-inline-end: var(--space-2);\n}\n\n@supports not ((anchor-name: --ren-anchor) and\n  (position-anchor: --ren-anchor) and\n  (position-area: bottom span-all)) {\n  .ren-color-picker-dropdown {\n    position: absolute;\n    inset: auto;\n  }\n}\n\n@keyframes ren-color-picker-open {\n  from {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n  to {\n    opacity: 1;\n    transform: scale(1);\n  }\n}\n\n/* ═══ SATURATION/BRIGHTNESS AREA ═══ */\n.ren-color-picker-saturation {\n  position: relative;\n  width: 100%;\n  aspect-ratio: 1;\n  max-width: 240px;\n  border-radius: var(--radius-md);\n  border: 1px solid var(--color-border);\n  cursor: crosshair;\n  overflow: hidden;\n  user-select: none;\n  -webkit-user-select: none;\n}\n\n/* Canvas for saturation/brightness */\n.ren-color-picker-saturation-canvas {\n  display: block;\n  width: 100%;\n  height: 100%;\n  border-radius: var(--radius-md);\n}\n\n/* Saturation handle (circular cursor) */\n.ren-color-picker-saturation-handle {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 12px;\n  height: 12px;\n  border-radius: 50%;\n  border: 2px solid white;\n  box-shadow: 0 0 0 1px rgb(0, 0, 0, 0.3);\n  pointer-events: none;\n  transform: translate(-50%, -50%);\n}\n\n/* ═══ HUE SLIDER ═══ */\n.ren-color-picker-hue {\n  position: relative;\n  width: 100%;\n  height: 32px;\n  border-radius: var(--radius-md);\n  border: 1px solid var(--color-border);\n  cursor: pointer;\n  /* Hue rainbow. Intencionalmente hex literals: son los 6 puntos del espectro\n     HSL puros (red→yellow→green→cyan→blue→magenta→red), no colores de UI. */\n  background: linear-gradient(\n    to right,\n    #ff0000 0%,\n    #ffff00 16.67%,\n    #00ff00 33.33%,\n    #00ffff 50%,\n    #0000ff 66.67%,\n    #ff00ff 83.33%,\n    #ff0000 100%\n  );\n  overflow: hidden;\n}\n\n/* Hue slider handle/thumb */\n.ren-color-picker-hue-handle {\n  position: absolute;\n  top: 50%;\n  left: 0;\n  width: 4px;\n  height: 100%;\n  background-color: white;\n  box-shadow: 0 0 0 1px rgb(0, 0, 0, 0.3);\n  pointer-events: none;\n  transform: translateY(-50%);\n}\n\n/* ═══ ALPHA SLIDER (Optional) ═══ */\n.ren-color-picker-alpha {\n  position: relative;\n  width: 100%;\n  height: 32px;\n  border-radius: var(--radius-md);\n  border: 1px solid var(--color-border);\n  cursor: pointer;\n  overflow: hidden;\n\n  /* Checkerboard pattern for transparency */\n  background-image: repeating-conic-gradient(\n    var(--color-border) 0deg 90deg,\n    transparent 90deg 180deg\n  );\n  background-size: 8px 8px;\n  background-position: 0 0;\n}\n\n/* Gradient overlay for alpha */\n.ren-color-picker-alpha-gradient {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  border-radius: var(--radius-md);\n}\n\n/* Alpha slider handle/thumb */\n.ren-color-picker-alpha-handle {\n  position: absolute;\n  top: 50%;\n  left: 0;\n  width: 4px;\n  height: 100%;\n  background-color: white;\n  box-shadow: 0 0 0 1px rgb(0, 0, 0, 0.3);\n  pointer-events: none;\n  transform: translateY(-50%);\n}\n\n/* ═══ COLOR PREVIEW SWATCH ═══ */\n.ren-color-picker-preview {\n  display: flex;\n  gap: var(--space-2);\n  padding: var(--space-3);\n  background-color: var(--color-surface);\n  border-radius: var(--radius-md);\n  border: 1px solid var(--color-border);\n}\n\n.ren-color-picker-preview-swatch {\n  flex-shrink: 0;\n  width: 48px;\n  height: 48px;\n  border-radius: var(--radius-md);\n  border: 1px solid var(--color-border);\n\n  /* Checkerboard for transparency */\n  background-image: repeating-conic-gradient(\n    var(--color-border) 0deg 90deg,\n    transparent 90deg 180deg\n  );\n  background-size: 4px 4px;\n  background-position: 0 0;\n}\n\n.ren-color-picker-preview-info {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  flex: 1;\n}\n\n.ren-color-picker-preview-label {\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  font-weight: var(--label-weight);\n}\n\n.ren-color-picker-preview-value {\n  font-family: var(--font-mono);\n  font-size: var(--label-size);\n  color: var(--color-text);\n  margin-top: var(--space-1);\n}\n\n/* ═══ TEXT INPUTS SECTION ═══ */\n.ren-color-picker-inputs {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-2);\n}\n\n/* Hex input field */\n.ren-color-picker-hex-input {\n  width: 100%;\n  padding: var(--space-2) var(--space-3);\n  font-size: var(--body-size);\n  font-family: var(--font-mono);\n  color: var(--color-text);\n  background-color: var(--color-input-bg);\n  border: 1px solid var(--color-input-border);\n  border-radius: var(--radius-md);\n  transition: var(--transition-tactile);\n}\n\n.ren-color-picker-hex-input:focus {\n  outline: none;\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n}\n\n.ren-color-picker-hex-input::placeholder {\n  color: var(--color-text-muted);\n}\n\n/* Channel inputs container (RGB or HSL) */\n.ren-color-picker-channels {\n  display: grid;\n  grid-template-columns: 1fr 1fr 1fr;\n  gap: var(--space-2);\n}\n\n/* Individual channel input */\n.ren-color-picker-channel {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-1);\n}\n\n.ren-color-picker-channel-label {\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  font-weight: var(--label-weight);\n}\n\n.ren-color-picker-channel-input {\n  padding: var(--space-2) var(--space-2);\n  font-size: var(--body-size);\n  font-family: var(--font-mono);\n  color: var(--color-text);\n  background-color: var(--color-input-bg);\n  border: 1px solid var(--color-input-border);\n  border-radius: var(--radius-md);\n  text-align: center;\n  transition: var(--transition-tactile);\n}\n\n.ren-color-picker-channel-input:focus {\n  outline: none;\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n}\n\n.ren-color-picker-channel-input::-webkit-outer-spin-button,\n.ren-color-picker-channel-input::-webkit-inner-spin-button {\n  -webkit-appearance: none;\n  margin: 0;\n}\n\n.ren-color-picker-channel-input[type=\"number\"] {\n  -moz-appearance: textfield;\n}\n\n/* ═══ FORMAT TOGGLE BUTTON ═══ */\n.ren-color-picker-format-toggle {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  font-size: var(--label-size);\n  font-family: var(--font-mono);\n  color: var(--color-text);\n  background-color: var(--color-fill);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n  cursor: pointer;\n  white-space: nowrap;\n  transition: var(--transition-tactile);\n}\n\n.ren-color-picker-format-toggle:hover {\n  background-color: var(--color-fill-hover);\n  border-color: var(--color-border);\n}\n\n.ren-color-picker-format-toggle:focus-visible {\n  outline: none;\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n}\n\n.ren-color-picker-format-toggle:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n\n/* ═══ EYEDROPPER BUTTON ═══ */\n.ren-color-picker-eyedropper {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  font-size: var(--label-size);\n  color: var(--color-text);\n  background-color: var(--color-fill);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n  cursor: pointer;\n  transition: var(--transition-tactile);\n}\n\n.ren-color-picker-eyedropper:hover {\n  background-color: var(--color-fill-hover);\n  border-color: var(--color-border);\n}\n\n.ren-color-picker-eyedropper:focus-visible {\n  outline: none;\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n}\n\n.ren-color-picker-eyedropper:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n\n.ren-color-picker-eyedropper-icon {\n  width: 16px;\n  height: 16px;\n  display: inline-block;\n}\n\n/* ═══ PRESETS/SWATCHES GRID ═══ */\n.ren-color-picker-swatches {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(40px, 1fr));\n  gap: var(--space-2);\n  padding: var(--space-2);\n  background-color: var(--color-surface);\n  border-radius: var(--radius-md);\n  border: 1px solid var(--color-border);\n}\n\n.ren-color-picker-swatch {\n  position: relative;\n  width: 40px;\n  height: 40px;\n  border-radius: var(--radius-md);\n  border: 2px solid transparent;\n  cursor: pointer;\n  transition: var(--transition-tactile);\n  background-image: repeating-conic-gradient(\n    var(--color-border) 0deg 90deg,\n    transparent 90deg 180deg\n  );\n  background-size: 4px 4px;\n  background-position: 0 0;\n}\n\n.ren-color-picker-swatch:focus-visible {\n  outline: none;\n  border-color: var(--color-accent);\n  box-shadow: 0 0 0 2px var(--color-input-focus-ring);\n}\n\n.ren-color-picker-swatch:hover {\n  transform: scale(1.05);\n  border-color: var(--color-border);\n}\n\n.ren-color-picker-swatch[aria-selected=\"true\"] {\n  border-color: var(--color-accent);\n  box-shadow: 0 0 0 2px var(--color-surface), 0 0 0 4px var(--color-accent);\n}\n\n.ren-color-picker-swatch-color {\n  position: absolute;\n  inset: 0;\n  border-radius: var(--radius-md);\n}\n\n/* ═══ CONTROLS ROW (Format + Eyedropper) ═══ */\n.ren-color-picker-controls {\n  display: flex;\n  gap: var(--space-2);\n}\n\n.ren-color-picker-controls-left {\n  flex: 1;\n  display: flex;\n  gap: var(--space-2);\n}\n\n.ren-color-picker-controls-right {\n  display: flex;\n  gap: var(--space-2);\n}\n\n/* ═══ DISABLED STATE ═══ */\n.ren-color-picker[disabled],\n.ren-color-picker-trigger[disabled] {\n  opacity: 0.5;\n  cursor: not-allowed;\n  pointer-events: none;\n}\n\n/* ═══ ANIMATIONS & TRANSITIONS ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-color-picker-dropdown,\n  .ren-color-picker-trigger,\n  .ren-color-picker-hex-input,\n  .ren-color-picker-channel-input,\n  .ren-color-picker-format-toggle,\n  .ren-color-picker-swatch {\n    animation-duration: 0ms !important;\n    animation-iteration-count: 1 !important;\n    transition-duration: 0ms !important;\n  }\n}\n/* Appearance API bridge */\n.ren-color-picker { inline-size: var(--ren-cp-width, 280px); border-radius: var(--ren-cp-radius, var(--radius-lg)); background: var(--ren-cp-bg, var(--color-surface-overlay)); box-shadow: var(--ren-cp-shadow, var(--shadow-lg)); }\n.ren-color-picker .ren-color-picker-swatch { inline-size: var(--ren-cp-swatch-size, 24px); block-size: var(--ren-cp-swatch-size, 24px); }\n.ren-color-picker .ren-color-picker-handle { inline-size: var(--ren-cp-handle-size, 16px); block-size: var(--ren-cp-handle-size, 16px); }\n.ren-color-picker .ren-color-picker-track { block-size: var(--ren-cp-track-height, 12px); }\n",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "file:components/composites/ren-color-picker/ren-color-picker.css",
      "type": "css",
      "name": "ren-color-picker.css"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — Color Picker Composite Component\n * ═════════════════════════════════════════════════════════════════════\n *\n * A fully featured, accessible color picker with support for:\n * - HSV/HSB internal color model with conversion to/from HEX, RGB, HSL\n * - Interactive saturation/brightness canvas area\n * - Hue slider with rainbow gradient\n * - Optional alpha/opacity slider\n * - Text inputs for manual color entry (HEX, RGB, HSL)\n * - Preset color swatches\n * - EyeDropper API for screen color picking (when available)\n * - Full keyboard navigation (arrow keys, Tab, Enter)\n * - ARIA labels and semantic markup\n * - Smooth Popover API animations\n *\n * Features:\n * - Light DOM, no Shadow DOM\n * - Uses design tokens for all UI chrome\n * - Canvas-based saturation area for accurate color rendering\n * - Real-time color conversion and validation\n * - Custom events: ren-change (committed), ren-input (live)\n * - Automatic sRGB color space handling\n *\n * @example\n * <ren-color-picker\n *   value=\"#3b82f6\"\n *   alpha\n *   format=\"hex\"\n *   swatches=\"#ef4444,#f97316,#eab308,#22c55e,#3b82f6,#8b5cf6,#ec4899\">\n * </ren-color-picker>\n *\n * @fires ren-change - Dispatched when color is committed\n * @fires ren-input - Dispatched during interactive changes\n */\n\n/* ═════════════════════════════════════════════════════════════════════\n   COLOR CONVERSION UTILITIES\n   ═════════════════════════════════════════════════════════════════════ */\n\n/**\n * Convert RGB to HSV (hue, saturation, value/brightness)\n * Handles sRGB color space properly\n * Returns: { h: 0-360, s: 0-100, v: 0-100 }\n */\nfunction rgbToHsv(r, g, b, a = 1) {\n  r /= 255;\n  g /= 255;\n  b /= 255;\n\n  const max = Math.max(r, g, b);\n  const min = Math.min(r, g, b);\n  const delta = max - min;\n  let h = 0;\n  let s = 0;\n  let v = max;\n\n  if (delta !== 0) {\n    s = delta / max;\n\n    if (max === r) {\n      h = ((g - b) / delta + (g < b ? 6 : 0)) / 6;\n    } else if (max === g) {\n      h = ((b - r) / delta + 2) / 6;\n    } else {\n      h = ((r - g) / delta + 4) / 6;\n    }\n  }\n\n  return {\n    h: Math.round(h * 360),\n    s: Math.round(s * 100),\n    v: Math.round(v * 100),\n    a,\n  };\n}\n\n/**\n * Convert HSV to RGB\n * Inverse of rgbToHsv\n * Accepts: { h: 0-360, s: 0-100, v: 0-100, a: 0-1 }\n * Returns: { r: 0-255, g: 0-255, b: 0-255, a: 0-1 }\n */\nfunction hsvToRgb(h, s, v, a = 1) {\n  h = (h % 360) / 60;\n  s = s / 100;\n  v = v / 100;\n\n  const c = v * s;\n  const x = c * (1 - Math.abs((h % 2) - 1));\n  const m = v - c;\n\n  let r = 0,\n    g = 0,\n    b = 0;\n\n  if (h >= 0 && h < 1) {\n    r = c;\n    g = x;\n    b = 0;\n  } else if (h >= 1 && h < 2) {\n    r = x;\n    g = c;\n    b = 0;\n  } else if (h >= 2 && h < 3) {\n    r = 0;\n    g = c;\n    b = x;\n  } else if (h >= 3 && h < 4) {\n    r = 0;\n    g = x;\n    b = c;\n  } else if (h >= 4 && h < 5) {\n    r = x;\n    g = 0;\n    b = c;\n  } else if (h >= 5 && h < 6) {\n    r = c;\n    g = 0;\n    b = x;\n  }\n\n  return {\n    r: Math.round((r + m) * 255),\n    g: Math.round((g + m) * 255),\n    b: Math.round((b + m) * 255),\n    a,\n  };\n}\n\n/**\n * Convert RGB to HEX string\n * Returns: \"#RRGGBB\" or \"#RRGGBBAA\" if alpha < 1\n */\nfunction rgbToHex(r, g, b, a = 1) {\n  const toHex = (n) => {\n    const hex = Math.round(n).toString(16);\n    return hex.length === 1 ? '0' + hex : hex;\n  };\n\n  const hex = `#${toHex(r)}${toHex(g)}${toHex(b)}`;\n\n  if (a < 1) {\n    return hex + toHex(a * 255);\n  }\n  return hex;\n}\n\n/**\n * Parse HEX color string to RGB values\n * Accepts: \"#RGB\", \"#RRGGBB\", \"#RGBA\", \"#RRGGBBAA\"\n * Returns: { r, g, b, a } or null if invalid\n */\nfunction hexToRgb(hex) {\n  hex = hex.replace(/^#/, '');\n\n  // Handle shorthand\n  if (hex.length === 3 || hex.length === 4) {\n    hex = hex\n      .split('')\n      .map((c) => c + c)\n      .join('');\n  }\n\n  if (hex.length !== 6 && hex.length !== 8) {\n    return null;\n  }\n\n  const r = parseInt(hex.substring(0, 2), 16);\n  const g = parseInt(hex.substring(2, 4), 16);\n  const b = parseInt(hex.substring(4, 6), 16);\n  const a = hex.length === 8 ? parseInt(hex.substring(6, 8), 16) / 255 : 1;\n\n  if (isNaN(r) || isNaN(g) || isNaN(b)) {\n    return null;\n  }\n\n  return { r, g, b, a };\n}\n\n/**\n * Convert RGB to HSL\n * Returns: { h: 0-360, s: 0-100, l: 0-100, a: 0-1 }\n */\nfunction rgbToHsl(r, g, b, a = 1) {\n  r /= 255;\n  g /= 255;\n  b /= 255;\n\n  const max = Math.max(r, g, b);\n  const min = Math.min(r, g, b);\n  const delta = max - min;\n  let h = 0;\n  let s = 0;\n  const l = (max + min) / 2;\n\n  if (delta !== 0) {\n    s = delta / (1 - Math.abs(2 * l - 1));\n\n    if (max === r) {\n      h = ((g - b) / delta + (g < b ? 6 : 0)) / 6;\n    } else if (max === g) {\n      h = ((b - r) / delta + 2) / 6;\n    } else {\n      h = ((r - g) / delta + 4) / 6;\n    }\n  }\n\n  return {\n    h: Math.round(h * 360),\n    s: Math.round(s * 100),\n    l: Math.round(l * 100),\n    a,\n  };\n}\n\n/**\n * Convert HSL to RGB\n * Accepts: { h: 0-360, s: 0-100, l: 0-100, a: 0-1 }\n * Returns: { r: 0-255, g: 0-255, b: 0-255, a: 0-1 }\n */\nfunction hslToRgb(h, s, l, a = 1) {\n  h = (h % 360) / 360;\n  s = s / 100;\n  l = l / 100;\n\n  const hue2rgb = (p, q, t) => {\n    if (t < 0) t += 1;\n    if (t > 1) t -= 1;\n    if (t < 1 / 6) return p + (q - p) * 6 * t;\n    if (t < 1 / 2) return q;\n    if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;\n    return p;\n  };\n\n  let r, g, b;\n  if (s === 0) {\n    r = g = b = l;\n  } else {\n    const q = l < 0.5 ? l * (1 + s) : l + s - l * s;\n    const p = 2 * l - q;\n    r = hue2rgb(p, q, h + 1 / 3);\n    g = hue2rgb(p, q, h);\n    b = hue2rgb(p, q, h - 1 / 3);\n  }\n\n  return {\n    r: Math.round(r * 255),\n    g: Math.round(g * 255),\n    b: Math.round(b * 255),\n    a,\n  };\n}\n\n/**\n * Validate and normalize hex color string\n */\nfunction normalizeHex(hex) {\n  if (typeof hex !== 'string') return null;\n  hex = hex.trim();\n  if (!hex.startsWith('#')) hex = '#' + hex;\n  const result = hexToRgb(hex);\n  return result ? rgbToHex(result.r, result.g, result.b, result.a) : null;\n}\n\nconst COLOR_PICKER_SIDES = new Set(['top', 'right', 'bottom', 'left']);\n\nfunction normalizeColorPickerSide(value) {\n  const side = String(value || 'bottom').toLowerCase().split('-')[0];\n\n  return COLOR_PICKER_SIDES.has(side) ? side : 'bottom';\n}\n\n/* ═════════════════════════════════════════════════════════════════════\n   COLOR PICKER WEB COMPONENT\n   ═════════════════════════════════════════════════════════════════════ */\n\nexport class RenColorPicker extends HTMLElement {\n  static observedAttributes = ['placement'];\n\n  #isOpen = false;\n  #hsv = { h: 0, s: 100, v: 100, a: 1 };\n  #format = 'hex'; // 'hex', 'rgb', 'hsl'\n  #saturationCanvas = null;\n  #dragState = null;\n  #popover = null;\n  #trigger = null;\n  #isDragging = false;\n  #listenerController = null;\n\n  constructor() {\n    super();\n    this.handleTriggerClick = this.handleTriggerClick.bind(this);\n    this.handleSaturationMouseDown = this.handleSaturationMouseDown.bind(this);\n    this.handleHueMouseDown = this.handleHueMouseDown.bind(this);\n    this.handleAlphaMouseDown = this.handleAlphaMouseDown.bind(this);\n    this.handleMouseMove = this.handleMouseMove.bind(this);\n    this.handleMouseUp = this.handleMouseUp.bind(this);\n    this.handleInputChange = this.handleInputChange.bind(this);\n    this.handleFormatToggle = this.handleFormatToggle.bind(this);\n    this.handleSwatchClick = this.handleSwatchClick.bind(this);\n    this.handleEyedropper = this.handleEyedropper.bind(this);\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'placement' && oldValue !== newValue) {\n      this.#syncPlacement();\n    }\n  }\n\n  connectedCallback() {\n    this.#listenerController?.abort();\n    this.#listenerController = new AbortController();\n    const { signal } = this.#listenerController;\n\n    // ═══ PARSE ATTRIBUTES ═══\n    const value = this.getAttribute('value') || '#3b82f6';\n    const hasAlpha = this.hasAttribute('alpha');\n    const format = this.getAttribute('format') || 'hex';\n    const swatches = this.getAttribute('swatches') || '';\n    const disabled = this.hasAttribute('disabled');\n\n    this.#format = ['hex', 'rgb', 'hsl'].includes(format) ? format : 'hex';\n\n    // ═══ PARSE INITIAL COLOR ═══\n    this.setValue(value);\n\n    // ═══ RENDER TRIGGER ═══\n    this.#renderTrigger(disabled);\n\n    // ═══ SET UP EVENT LISTENERS ═══\n    if (this.#trigger) {\n      this.#trigger.addEventListener('click', this.handleTriggerClick, { signal });\n      this.#trigger.addEventListener('keydown', (e) => {\n        if (e.key === 'Enter' || e.key === ' ') {\n          this.handleTriggerClick();\n          e.preventDefault();\n        }\n      }, { signal });\n    }\n\n    // ═══ RENDER DROPDOWN (will be populated on open) ═══\n    this.#popover = this.querySelector(':scope > .ren-color-picker-dropdown');\n    if (!this.#popover) {\n      this.#popover = document.createElement('div');\n      this.#popover.className = 'ren-color-picker-dropdown';\n      this.appendChild(this.#popover);\n    }\n    const popoverId = this.#popover.id || `ren-color-picker-${Math.random().toString(36).slice(2)}`;\n    this.#popover.id = popoverId;\n    this.#popover.setAttribute('popover', 'auto');\n\n    if (this.#trigger) {\n      this.#trigger.setAttribute('aria-haspopup', 'dialog');\n      this.#trigger.setAttribute('aria-expanded', 'false');\n      this.#trigger.setAttribute('popovertarget', popoverId);\n    }\n\n    this.#syncPlacement();\n\n    // ═══ POPULATE POPOVER ON OPEN ═══\n    this.#popover.addEventListener('toggle', (e) => {\n      if (e.newState === 'open') {\n        this.#isOpen = true;\n        if (this.#trigger) {\n          this.#trigger.setAttribute('aria-expanded', 'true');\n        }\n        this.#renderDropdownContent(hasAlpha, swatches, disabled);\n      } else {\n        this.#isOpen = false;\n        if (this.#trigger) {\n          this.#trigger.setAttribute('aria-expanded', 'false');\n        }\n      }\n    }, { signal });\n  }\n\n  disconnectedCallback() {\n    this.#listenerController?.abort();\n    this.#listenerController = null;\n    document.removeEventListener('mousemove', this.handleMouseMove);\n    document.removeEventListener('mouseup', this.handleMouseUp);\n  }\n\n  /* ═════════════════════════════════════════════════════════════════\n     RENDERING METHODS\n     ═════════════════════════════════════════════════════════════════ */\n\n  #syncPlacement() {\n    const side = normalizeColorPickerSide(this.getAttribute('placement'));\n\n    this.setAttribute('data-side', side);\n    if (this.#popover) {\n      this.#popover.setAttribute('data-side', side);\n    }\n  }\n\n  /**\n   * Render the trigger button showing the selected color\n   */\n  #renderTrigger(disabled = false) {\n    // Remove existing trigger\n    const existing = this.querySelector('.ren-color-picker-trigger');\n    if (existing) existing.remove();\n\n    const trigger = document.createElement('button');\n    trigger.className = 'ren-color-picker-trigger';\n    trigger.type = 'button';\n    if (disabled) trigger.disabled = true;\n\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n    const hex = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n\n    trigger.innerHTML = `\n      <div class=\"ren-color-picker-trigger-swatch\" style=\"background-color: ${this.#hsvToCss()}\"></div>\n      <div class=\"ren-color-picker-trigger-value\">${hex}</div>\n    `;\n\n    trigger.setAttribute('aria-label', `Color picker, current color ${hex}`);\n\n    this.insertBefore(trigger, this.#popover);\n    this.#trigger = trigger;\n  }\n\n  /**\n   * Render the full dropdown content (called when popover opens)\n   */\n  #renderDropdownContent(hasAlpha = false, swatches = '', disabled = false) {\n    if (!this.#popover) return;\n\n    // Clear existing content\n    this.#popover.innerHTML = '';\n\n    // ═══ SATURATION/BRIGHTNESS AREA ═══\n    const saturationContainer = document.createElement('div');\n    saturationContainer.className = 'ren-color-picker-saturation';\n    saturationContainer.setAttribute('aria-label', 'Color saturation and brightness');\n\n    this.#saturationCanvas = document.createElement('canvas');\n    this.#saturationCanvas.className = 'ren-color-picker-saturation-canvas';\n    this.#saturationCanvas.width = 240;\n    this.#saturationCanvas.height = 240;\n\n    saturationContainer.appendChild(this.#saturationCanvas);\n\n    const handle = document.createElement('div');\n    handle.className = 'ren-color-picker-saturation-handle';\n    saturationContainer.appendChild(handle);\n\n    this.#drawSaturationCanvas();\n    this.#updateSaturationHandle();\n\n    saturationContainer.addEventListener('mousedown', this.handleSaturationMouseDown);\n    this.#popover.appendChild(saturationContainer);\n\n    // ═══ HUE SLIDER ═══\n    const hueContainer = document.createElement('div');\n    hueContainer.className = 'ren-color-picker-hue';\n    hueContainer.setAttribute('aria-label', 'Color hue');\n    hueContainer.setAttribute('role', 'slider');\n    hueContainer.setAttribute('aria-valuenow', this.#hsv.h);\n    hueContainer.setAttribute('aria-valuemin', '0');\n    hueContainer.setAttribute('aria-valuemax', '360');\n    hueContainer.setAttribute('aria-orientation', 'horizontal');\n    hueContainer.setAttribute('tabindex', '0');\n\n    const hueHandle = document.createElement('div');\n    hueHandle.className = 'ren-color-picker-hue-handle';\n    hueContainer.appendChild(hueHandle);\n\n    this.#updateHueHandle();\n\n    hueContainer.addEventListener('mousedown', this.handleHueMouseDown);\n    hueContainer.addEventListener('keydown', (e) => {\n      this.#handleHueKeydown(e, hueContainer);\n    });\n\n    this.#popover.appendChild(hueContainer);\n\n    // ═══ ALPHA SLIDER (Optional) ═══\n    if (hasAlpha) {\n      const alphaContainer = document.createElement('div');\n      alphaContainer.className = 'ren-color-picker-alpha';\n      alphaContainer.setAttribute('aria-label', 'Color opacity');\n      alphaContainer.setAttribute('role', 'slider');\n      alphaContainer.setAttribute('aria-valuenow', Math.round(this.#hsv.a * 100));\n      alphaContainer.setAttribute('aria-valuemin', '0');\n      alphaContainer.setAttribute('aria-valuemax', '100');\n      alphaContainer.setAttribute('aria-orientation', 'horizontal');\n      alphaContainer.setAttribute('tabindex', '0');\n\n      const alphaGradient = document.createElement('div');\n      alphaGradient.className = 'ren-color-picker-alpha-gradient';\n      alphaContainer.appendChild(alphaGradient);\n\n      const alphaHandle = document.createElement('div');\n      alphaHandle.className = 'ren-color-picker-alpha-handle';\n      alphaContainer.appendChild(alphaHandle);\n\n      this.#updateAlphaGradient();\n      this.#updateAlphaHandle();\n\n      alphaContainer.addEventListener('mousedown', this.handleAlphaMouseDown);\n      alphaContainer.addEventListener('keydown', (e) => {\n        this.#handleAlphaKeydown(e, alphaContainer);\n      });\n\n      this.#popover.appendChild(alphaContainer);\n    }\n\n    // ═══ COLOR PREVIEW ═══\n    const preview = document.createElement('div');\n    preview.className = 'ren-color-picker-preview';\n\n    const swatch = document.createElement('div');\n    swatch.className = 'ren-color-picker-preview-swatch';\n    swatch.style.backgroundColor = this.#hsvToCss();\n\n    const info = document.createElement('div');\n    info.className = 'ren-color-picker-preview-info';\n\n    const label = document.createElement('div');\n    label.className = 'ren-color-picker-preview-label';\n    label.textContent = 'Current';\n\n    const value = document.createElement('div');\n    value.className = 'ren-color-picker-preview-value';\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n    value.textContent = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n\n    info.appendChild(label);\n    info.appendChild(value);\n    preview.appendChild(swatch);\n    preview.appendChild(info);\n    this.#popover.appendChild(preview);\n\n    // ═══ TEXT INPUTS SECTION ═══\n    const inputsSection = document.createElement('div');\n    inputsSection.className = 'ren-color-picker-inputs';\n\n    // Hex input\n    const hexInput = document.createElement('input');\n    hexInput.className = 'ren-color-picker-hex-input';\n    hexInput.type = 'text';\n    hexInput.placeholder = '#000000';\n    hexInput.value = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n    hexInput.setAttribute('aria-label', 'Hex color value');\n    hexInput.disabled = disabled;\n    hexInput.addEventListener('change', this.handleInputChange);\n    inputsSection.appendChild(hexInput);\n\n    // RGB or HSL channels\n    const channelsContainer = document.createElement('div');\n    channelsContainer.className = 'ren-color-picker-channels';\n\n    let channels = [];\n    if (this.#format === 'rgb') {\n      channels = [\n        { label: 'R', value: rgb.r, min: 0, max: 255, key: 'r' },\n        { label: 'G', value: rgb.g, min: 0, max: 255, key: 'g' },\n        { label: 'B', value: rgb.b, min: 0, max: 255, key: 'b' },\n      ];\n    } else if (this.#format === 'hsl') {\n      const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b, rgb.a);\n      channels = [\n        { label: 'H', value: hsl.h, min: 0, max: 360, key: 'h' },\n        { label: 'S', value: hsl.s, min: 0, max: 100, key: 's' },\n        { label: 'L', value: hsl.l, min: 0, max: 100, key: 'l' },\n      ];\n    } else {\n      channels = [\n        { label: 'R', value: rgb.r, min: 0, max: 255, key: 'r' },\n        { label: 'G', value: rgb.g, min: 0, max: 255, key: 'g' },\n        { label: 'B', value: rgb.b, min: 0, max: 255, key: 'b' },\n      ];\n    }\n\n    channels.forEach(({ label, value, min, max, key }) => {\n      const channel = document.createElement('div');\n      channel.className = 'ren-color-picker-channel';\n\n      const channelLabel = document.createElement('label');\n      channelLabel.className = 'ren-color-picker-channel-label';\n      channelLabel.textContent = label;\n\n      const channelInput = document.createElement('input');\n      channelInput.className = 'ren-color-picker-channel-input';\n      channelInput.type = 'number';\n      channelInput.min = min;\n      channelInput.max = max;\n      channelInput.value = value;\n      channelInput.disabled = disabled;\n      channelInput.setAttribute('aria-label', `${label} channel`);\n      channelInput.addEventListener('change', this.handleInputChange);\n\n      channel.appendChild(channelLabel);\n      channel.appendChild(channelInput);\n      channelsContainer.appendChild(channel);\n    });\n\n    inputsSection.appendChild(channelsContainer);\n    this.#popover.appendChild(inputsSection);\n\n    // ═══ CONTROLS (Format Toggle + Eyedropper) ═══\n    const controls = document.createElement('div');\n    controls.className = 'ren-color-picker-controls';\n\n    const controlsLeft = document.createElement('div');\n    controlsLeft.className = 'ren-color-picker-controls-left';\n\n    const formatToggle = document.createElement('button');\n    formatToggle.className = 'ren-color-picker-format-toggle';\n    formatToggle.type = 'button';\n    formatToggle.textContent = this.#format.toUpperCase();\n    formatToggle.setAttribute('aria-label', 'Toggle color format between HEX, RGB, and HSL');\n    formatToggle.disabled = disabled;\n    formatToggle.addEventListener('click', this.handleFormatToggle);\n\n    controlsLeft.appendChild(formatToggle);\n\n    // Eyedropper (if available)\n    if ('EyeDropper' in window) {\n      const eyedropperBtn = document.createElement('button');\n      eyedropperBtn.className = 'ren-color-picker-eyedropper';\n      eyedropperBtn.type = 'button';\n      eyedropperBtn.setAttribute('aria-label', 'Pick color from screen');\n      eyedropperBtn.disabled = disabled;\n\n      // Simple eyedropper icon (SVG)\n      const icon = document.createElement('span');\n      icon.className = 'ren-color-picker-eyedropper-icon';\n      icon.innerHTML = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 2L22 12L12 22L2 12Z\"></path></svg>`;\n\n      eyedropperBtn.appendChild(icon);\n      eyedropperBtn.addEventListener('click', this.handleEyedropper);\n      controlsLeft.appendChild(eyedropperBtn);\n    }\n\n    controls.appendChild(controlsLeft);\n    this.#popover.appendChild(controls);\n\n    // ═══ PRESETS/SWATCHES (Optional) ═══\n    if (swatches.trim()) {\n      const swatchesContainer = document.createElement('div');\n      swatchesContainer.className = 'ren-color-picker-swatches';\n      swatchesContainer.setAttribute('aria-label', 'Preset color swatches');\n\n      const colors = swatches.split(',').map((s) => s.trim());\n      colors.forEach((color) => {\n        const swatch = document.createElement('button');\n        swatch.className = 'ren-color-picker-swatch';\n        swatch.type = 'button';\n        swatch.setAttribute('aria-label', `Color ${color}`);\n\n        const colorDiv = document.createElement('div');\n        colorDiv.className = 'ren-color-picker-swatch-color';\n        colorDiv.style.backgroundColor = color;\n\n        swatch.appendChild(colorDiv);\n        swatch.addEventListener('click', this.handleSwatchClick);\n\n        // Check if this is the current color\n        const normalized = normalizeHex(color);\n        const currentHex = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n        if (normalized === currentHex) {\n          swatch.setAttribute('aria-selected', 'true');\n        }\n\n        swatchesContainer.appendChild(swatch);\n      });\n\n      this.#popover.appendChild(swatchesContainer);\n    }\n  }\n\n  /* ═════════════════════════════════════════════════════════════════\n     CANVAS & HANDLE UPDATES\n     ═════════════════════════════════════════════════════════════════ */\n\n  /**\n   * Draw the saturation/brightness canvas\n   */\n  #drawSaturationCanvas() {\n    if (!this.#saturationCanvas) return;\n\n    const ctx = this.#saturationCanvas.getContext('2d');\n    const { width, height } = this.#saturationCanvas;\n\n    // Create horizontal gradient: white (left) to hue (right)\n    const rgb = hsvToRgb(this.#hsv.h, 100, 100, 1);\n    const hueColor = `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;\n\n    const hGradient = ctx.createLinearGradient(0, 0, width, 0);\n    hGradient.addColorStop(0, 'white');\n    hGradient.addColorStop(1, hueColor);\n\n    ctx.fillStyle = hGradient;\n    ctx.fillRect(0, 0, width, height);\n\n    // Create vertical gradient overlay: transparent (top) to black (bottom)\n    const vGradient = ctx.createLinearGradient(0, 0, 0, height);\n    vGradient.addColorStop(0, 'rgba(0, 0, 0, 0)');\n    vGradient.addColorStop(1, 'rgba(0, 0, 0, 1)');\n\n    ctx.fillStyle = vGradient;\n    ctx.fillRect(0, 0, width, height);\n  }\n\n  /**\n   * Update saturation handle position\n   */\n  #updateSaturationHandle() {\n    if (!this.#saturationCanvas) return;\n\n    const handle = this.#saturationCanvas.parentElement.querySelector(\n      '.ren-color-picker-saturation-handle'\n    );\n    if (!handle) return;\n\n    const { width, height } = this.#saturationCanvas;\n    const x = (this.#hsv.s / 100) * width;\n    const y = ((100 - this.#hsv.v) / 100) * height;\n\n    handle.style.left = x + 'px';\n    handle.style.top = y + 'px';\n  }\n\n  /**\n   * Update hue handle position\n   */\n  #updateHueHandle() {\n    const hueContainer = this.#popover?.querySelector('.ren-color-picker-hue');\n    if (!hueContainer) return;\n\n    const handle = hueContainer.querySelector('.ren-color-picker-hue-handle');\n    if (!handle) return;\n\n    const percent = (this.#hsv.h / 360) * 100;\n    handle.style.left = percent + '%';\n\n    hueContainer.setAttribute('aria-valuenow', this.#hsv.h);\n  }\n\n  /**\n   * Update alpha gradient and handle\n   */\n  #updateAlphaGradient() {\n    const alphaContainer = this.#popover?.querySelector('.ren-color-picker-alpha');\n    if (!alphaContainer) return;\n\n    const gradient = alphaContainer.querySelector('.ren-color-picker-alpha-gradient');\n    if (!gradient) return;\n\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, 1);\n    const color = `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;\n\n    gradient.style.background = `linear-gradient(to right, rgba(0,0,0,0), ${color})`;\n  }\n\n  /**\n   * Update alpha handle position\n   */\n  #updateAlphaHandle() {\n    const alphaContainer = this.#popover?.querySelector('.ren-color-picker-alpha');\n    if (!alphaContainer) return;\n\n    const handle = alphaContainer.querySelector('.ren-color-picker-alpha-handle');\n    if (!handle) return;\n\n    const percent = this.#hsv.a * 100;\n    handle.style.left = percent + '%';\n\n    alphaContainer.setAttribute('aria-valuenow', Math.round(percent));\n  }\n\n  /* ═════════════════════════════════════════════════════════════════\n     EVENT HANDLERS\n     ═════════════════════════════════════════════════════════════════ */\n\n  handleTriggerClick(event) {\n    event?.preventDefault();\n    if (!this.#popover) return;\n\n    try {\n      if (this.#popover.matches(':popover-open')) {\n        this.#popover.hidePopover?.();\n      } else {\n        this.#popover.showPopover?.();\n      }\n    } catch {\n      // Native popover can throw if browser state changes mid-click.\n    }\n  }\n\n  handleSaturationMouseDown(e) {\n    e.preventDefault();\n    this.#isDragging = true;\n    this.#dragState = { type: 'saturation' };\n\n    this.#updateSaturationFromMouse(e);\n\n    document.addEventListener('mousemove', this.handleMouseMove);\n    document.addEventListener('mouseup', this.handleMouseUp);\n  }\n\n  handleHueMouseDown(e) {\n    e.preventDefault();\n    this.#isDragging = true;\n    this.#dragState = { type: 'hue' };\n\n    this.#updateHueFromMouse(e);\n\n    document.addEventListener('mousemove', this.handleMouseMove);\n    document.addEventListener('mouseup', this.handleMouseUp);\n  }\n\n  handleAlphaMouseDown(e) {\n    e.preventDefault();\n    this.#isDragging = true;\n    this.#dragState = { type: 'alpha' };\n\n    this.#updateAlphaFromMouse(e);\n\n    document.addEventListener('mousemove', this.handleMouseMove);\n    document.addEventListener('mouseup', this.handleMouseUp);\n  }\n\n  handleMouseMove(e) {\n    if (!this.#isDragging || !this.#dragState) return;\n\n    if (this.#dragState.type === 'saturation') {\n      this.#updateSaturationFromMouse(e);\n    } else if (this.#dragState.type === 'hue') {\n      this.#updateHueFromMouse(e);\n    } else if (this.#dragState.type === 'alpha') {\n      this.#updateAlphaFromMouse(e);\n    }\n  }\n\n  handleMouseUp(e) {\n    this.#isDragging = false;\n    this.#dragState = null;\n\n    document.removeEventListener('mousemove', this.handleMouseMove);\n    document.removeEventListener('mouseup', this.handleMouseUp);\n\n    this.#emitChange();\n  }\n\n  handleInputChange(e) {\n    const input = e.target;\n    const value = input.value.trim();\n\n    if (input.classList.contains('ren-color-picker-hex-input')) {\n      const rgb = hexToRgb(value);\n      if (rgb) {\n        const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b, rgb.a);\n        this.#hsv = hsv;\n        this.#updateAllViews();\n        this.#emitChange();\n      }\n    } else if (input.classList.contains('ren-color-picker-channel-input')) {\n      this.#updateFromChannelInputs();\n      this.#emitChange();\n    }\n  }\n\n  handleFormatToggle() {\n    const formats = ['hex', 'rgb', 'hsl'];\n    const currentIndex = formats.indexOf(this.#format);\n    this.#format = formats[(currentIndex + 1) % formats.length];\n\n    // Re-render inputs\n    this.#renderDropdownContent(\n      this.hasAttribute('alpha'),\n      this.getAttribute('swatches') || '',\n      this.hasAttribute('disabled')\n    );\n  }\n\n  handleSwatchClick(e) {\n    const swatch = e.currentTarget;\n    const colorDiv = swatch.querySelector('.ren-color-picker-swatch-color');\n    const color = colorDiv.style.backgroundColor;\n\n    this.setValue(color);\n    this.#updateAllViews();\n    this.#emitChange();\n  }\n\n  async handleEyedropper() {\n    try {\n      const eyeDropper = new EyeDropper();\n      const result = await eyeDropper.open();\n      this.setValue(result.sRGBHex);\n      this.#updateAllViews();\n      this.#emitChange();\n    } catch {\n      // User cancelled or not supported\n    }\n  }\n\n  /* ═════════════════════════════════════════════════════════════════\n     KEYBOARD NAVIGATION\n     ═════════════════════════════════════════════════════════════════ */\n\n  #handleHueKeydown(e, hueContainer) {\n    const step = e.shiftKey ? 10 : 1;\n\n    if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {\n      this.#hsv.h = (this.#hsv.h + step) % 360;\n      this.#updateAllViews();\n      this.#emitInput();\n      e.preventDefault();\n    } else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {\n      this.#hsv.h = (this.#hsv.h - step + 360) % 360;\n      this.#updateAllViews();\n      this.#emitInput();\n      e.preventDefault();\n    }\n  }\n\n  #handleAlphaKeydown(e, alphaContainer) {\n    const step = e.shiftKey ? 0.1 : 0.01;\n\n    if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {\n      this.#hsv.a = Math.min(1, this.#hsv.a + step);\n      this.#updateAllViews();\n      this.#emitInput();\n      e.preventDefault();\n    } else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {\n      this.#hsv.a = Math.max(0, this.#hsv.a - step);\n      this.#updateAllViews();\n      this.#emitInput();\n      e.preventDefault();\n    }\n  }\n\n  /* ═════════════════════════════════════════════════════════════════\n     INTERNAL UPDATES\n     ═════════════════════════════════════════════════════════════════ */\n\n  #updateSaturationFromMouse(e) {\n    if (!this.#saturationCanvas) return;\n\n    const rect = this.#saturationCanvas.getBoundingClientRect();\n    const x = Math.max(0, Math.min(rect.width, e.clientX - rect.left));\n    const y = Math.max(0, Math.min(rect.height, e.clientY - rect.top));\n\n    this.#hsv.s = Math.round((x / rect.width) * 100);\n    this.#hsv.v = Math.round(100 - (y / rect.height) * 100);\n\n    this.#updateSaturationHandle();\n    this.#updateAlphaGradient();\n    this.#updatePreview();\n    this.#emitInput();\n  }\n\n  #updateHueFromMouse(e) {\n    const hueContainer = this.#popover?.querySelector('.ren-color-picker-hue');\n    if (!hueContainer) return;\n\n    const rect = hueContainer.getBoundingClientRect();\n    const x = Math.max(0, Math.min(rect.width, e.clientX - rect.left));\n    this.#hsv.h = Math.round((x / rect.width) * 360) % 360;\n\n    this.#drawSaturationCanvas();\n    this.#updateSaturationHandle();\n    this.#updateHueHandle();\n    this.#updateAlphaGradient();\n    this.#updatePreview();\n    this.#updateChannelInputs();\n    this.#emitInput();\n  }\n\n  #updateAlphaFromMouse(e) {\n    const alphaContainer = this.#popover?.querySelector('.ren-color-picker-alpha');\n    if (!alphaContainer) return;\n\n    const rect = alphaContainer.getBoundingClientRect();\n    const x = Math.max(0, Math.min(rect.width, e.clientX - rect.left));\n    this.#hsv.a = Math.round((x / rect.width) * 100) / 100;\n\n    this.#updateAlphaHandle();\n    this.#updatePreview();\n    this.#emitInput();\n  }\n\n  #updateFromChannelInputs() {\n    const inputs = this.#popover?.querySelectorAll('.ren-color-picker-channel-input');\n    if (!inputs || inputs.length === 0) return;\n\n    const values = Array.from(inputs).map((inp) => parseFloat(inp.value) || 0);\n\n    if (this.#format === 'rgb') {\n      const [r, g, b] = values;\n      const hsv = rgbToHsv(r, g, b, this.#hsv.a);\n      this.#hsv = hsv;\n    } else if (this.#format === 'hsl') {\n      const [h, s, l] = values;\n      const rgb = hslToRgb(h, s, l, this.#hsv.a);\n      const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b, rgb.a);\n      this.#hsv = hsv;\n    } else {\n      const [r, g, b] = values;\n      const hsv = rgbToHsv(r, g, b, this.#hsv.a);\n      this.#hsv = hsv;\n    }\n\n    this.#updateAllViews();\n  }\n\n  #updateAllViews() {\n    this.#drawSaturationCanvas();\n    this.#updateSaturationHandle();\n    this.#updateHueHandle();\n    this.#updateAlphaGradient();\n    this.#updateAlphaHandle();\n    this.#updatePreview();\n    this.#updateChannelInputs();\n    this.#updateHexInput();\n    this.#updateTrigger();\n  }\n\n  #updatePreview() {\n    const preview = this.#popover?.querySelector('.ren-color-picker-preview-swatch');\n    const value = this.#popover?.querySelector('.ren-color-picker-preview-value');\n\n    if (preview) {\n      preview.style.backgroundColor = this.#hsvToCss();\n    }\n\n    if (value) {\n      const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n      value.textContent = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n    }\n  }\n\n  #updateHexInput() {\n    const input = this.#popover?.querySelector('.ren-color-picker-hex-input');\n    if (!input) return;\n\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n    input.value = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n  }\n\n  #updateChannelInputs() {\n    const inputs = this.#popover?.querySelectorAll('.ren-color-picker-channel-input');\n    if (!inputs || inputs.length === 0) return;\n\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n\n    if (this.#format === 'rgb') {\n      const values = [rgb.r, rgb.g, rgb.b];\n      Array.from(inputs).forEach((inp, i) => {\n        inp.value = values[i];\n      });\n    } else if (this.#format === 'hsl') {\n      const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b, rgb.a);\n      const values = [hsl.h, hsl.s, hsl.l];\n      Array.from(inputs).forEach((inp, i) => {\n        inp.value = values[i];\n      });\n    } else {\n      const values = [rgb.r, rgb.g, rgb.b];\n      Array.from(inputs).forEach((inp, i) => {\n        inp.value = values[i];\n      });\n    }\n  }\n\n  #updateTrigger() {\n    if (!this.#trigger) return;\n\n    const swatch = this.#trigger.querySelector('.ren-color-picker-trigger-swatch');\n    const value = this.#trigger.querySelector('.ren-color-picker-trigger-value');\n\n    if (swatch) {\n      swatch.style.backgroundColor = this.#hsvToCss();\n    }\n\n    if (value) {\n      const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n      value.textContent = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n    }\n  }\n\n  #hsvToCss() {\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n    if (this.#hsv.a < 1) {\n      return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`;\n    }\n    return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;\n  }\n\n  /* ═════════════════════════════════════════════════════════════════\n     EVENT DISPATCHERS\n     ═════════════════════════════════════════════════════════════════ */\n\n  #emitInput() {\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n    const hex = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n    const rgbStr = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`;\n    const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b, rgb.a);\n    const hslStr = `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${hsl.a})`;\n\n    this.dispatchEvent(\n      new CustomEvent('ren-input', {\n        detail: { hex, rgb: rgbStr, hsl: hslStr, alpha: rgb.a },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  #emitChange() {\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n    const hex = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n    const rgbStr = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`;\n    const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b, rgb.a);\n    const hslStr = `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${hsl.a})`;\n\n    this.dispatchEvent(\n      new CustomEvent('ren-change', {\n        detail: { hex, rgb: rgbStr, hsl: hslStr, alpha: rgb.a },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═════════════════════════════════════════════════════════════════\n     PUBLIC API\n     ═════════════════════════════════════════════════════════════════ */\n\n  /**\n   * Set the color value\n   * Accepts: hex string, rgb/rgba string, hsl/hsla string, or color name\n   */\n  setValue(color) {\n    let rgb = null;\n\n    if (typeof color === 'string') {\n      // Try to parse as hex\n      rgb = hexToRgb(color);\n\n      // If not hex, assume it's rgb/hsl/color name\n      if (!rgb) {\n        // Use temporary element to parse CSS color\n        const div = document.createElement('div');\n        div.style.color = color;\n        document.body.appendChild(div);\n        const computed = getComputedStyle(div).color;\n        document.body.removeChild(div);\n\n        // Parse rgb() string\n        const match = computed.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*([\\d.]+))?\\)/);\n        if (match) {\n          rgb = {\n            r: parseInt(match[1]),\n            g: parseInt(match[2]),\n            b: parseInt(match[3]),\n            a: match[4] ? parseFloat(match[4]) : 1,\n          };\n        }\n      }\n    }\n\n    if (rgb) {\n      this.#hsv = rgbToHsv(rgb.r, rgb.g, rgb.b, rgb.a);\n    }\n  }\n\n  /**\n   * Get the current color value\n   * Returns: { hex, rgb, hsl, alpha }\n   */\n  getValue() {\n    const rgb = hsvToRgb(this.#hsv.h, this.#hsv.s, this.#hsv.v, this.#hsv.a);\n    const hex = rgbToHex(rgb.r, rgb.g, rgb.b, rgb.a);\n    const rgbStr = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`;\n    const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b, rgb.a);\n    const hslStr = `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${hsl.a})`;\n\n    return {\n      hex,\n      rgb: rgbStr,\n      hsl: hslStr,\n      alpha: rgb.a,\n    };\n  }\n}\n\ncustomElements.define('ren-color-picker', RenColorPicker);\n",
      "path": "components/composites/ren-color-picker/ren-color-picker.js",
      "id": "file:components/composites/ren-color-picker/ren-color-picker.js",
      "type": "javascript",
      "name": "ren-color-picker.js"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — Combobox\n * =================\n * Light-DOM combobox. CSS works standalone; <ren-combobox> layers ARIA\n * and behavior on top.\n *\n * Markup:\n *   <div class=\"ren-combobox\">\n *     <input class=\"ren-combobox-input\">\n *     <div class=\"ren-combobox-list\" role=\"listbox\" hidden>\n *       <div class=\"ren-combobox-item\" role=\"option\">…</div>\n *     </div>\n *   </div>\n */\n\n.ren-combobox {\n  position: relative;\n  display: block;\n  width: 100%;\n\n  &-input {\n    width: 100%;\n    padding-block: var(--space-2);\n    padding-inline: var(--space-3);\n\n    border: 1px solid var(--color-border);\n    border-radius: var(--radius-md);\n    background: var(--color-surface);\n\n    font-family: inherit;\n    font-size: var(--text-sm);\n    line-height: var(--leading-5);\n    color: var(--color-text);\n\n    transition: var(--transition-tactile);\n\n    &::placeholder {\n      color: var(--color-text-muted);\n    }\n\n    &:focus {\n      outline: none;\n      border-color: var(--color-accent);\n      box-shadow: 0 0 0 3px var(--color-accent-subtle);\n    }\n\n    &:disabled {\n      background: var(--color-fill);\n      color: var(--color-text-muted);\n      cursor: not-allowed;\n    }\n\n    /* Hide native type=\"search\" decorations */\n    &::-webkit-search-cancel-button,\n    &::-webkit-search-decoration {\n      -webkit-appearance: none;\n      appearance: none;\n      display: none;\n    }\n  }\n\n  &-list {\n    position: absolute;\n    inset-inline: 0;\n    top: calc(100% + var(--space-1));\n    z-index: 50;\n\n    max-height: 15rem;\n    padding-block: var(--space-1);\n\n    background: var(--color-surface);\n    border: 1px solid var(--color-border);\n    border-radius: var(--radius-lg);\n    box-shadow: var(--shadow-lg);\n\n    overflow-y: auto;\n    overflow-x: hidden;\n\n    /* Scrollbar styling */\n    scrollbar-width: thin;\n    scrollbar-color: var(--color-fill-active) transparent;\n\n    &::-webkit-scrollbar {\n      width: 6px;\n    }\n\n    &::-webkit-scrollbar-track {\n      background: transparent;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      background: var(--color-fill-active);\n      border-radius: var(--radius-full);\n    }\n\n    &::-webkit-scrollbar-thumb:hover {\n      background: var(--color-fill-hover);\n    }\n\n    &[hidden] {\n      display: none;\n    }\n\n    /* Open animation */\n    @starting-style {\n      opacity: 0;\n      translate: 0 -4px;\n    }\n    animation: ren-combobox-open var(--duration-enter) var(--ease-enter);\n\n    @media (prefers-reduced-motion: reduce) {\n      animation: none;\n    }\n  }\n\n  &[data-side=\"top\"] &-list,\n  &-list[data-side=\"top\"] {\n    top: auto;\n    bottom: calc(100% + var(--space-1));\n  }\n\n  &[data-side=\"bottom\"] &-list,\n  &-list[data-side=\"bottom\"] {\n    top: calc(100% + var(--space-1));\n    bottom: auto;\n  }\n\n  &-item {\n    padding: var(--space-2) var(--space-3);\n    cursor: pointer;\n    border-radius: var(--radius-sm);\n\n    display: flex;\n    flex-direction: column;\n    gap: var(--space-0-5, 2px);\n\n    font-size: var(--text-sm);\n    color: var(--color-text);\n\n    transition: background-color var(--duration-micro) var(--ease-enter);\n\n    &[hidden] {\n      display: none;\n    }\n\n    &[data-highlighted] {\n      background: var(--color-fill);\n    }\n\n    &[aria-selected=\"true\"] {\n      background: var(--color-accent-subtle);\n      color: var(--color-accent);\n      font-weight: 500;\n    }\n\n    &[aria-disabled=\"true\"] {\n      opacity: 0.5;\n      cursor: not-allowed;\n\n      &:hover,\n      &[data-highlighted] {\n        background: transparent;\n      }\n    }\n  }\n\n  &-item-label {\n    font-weight: 500;\n    color: var(--color-text);\n  }\n\n  &-item-description {\n    font-size: var(--text-xs);\n    color: var(--color-text-muted);\n  }\n\n  &-empty {\n    padding: var(--space-8) var(--space-3);\n    text-align: center;\n    color: var(--color-text-muted);\n    font-size: var(--text-sm);\n\n    &[hidden] {\n      display: none;\n    }\n  }\n\n  &-loading {\n    padding: var(--space-3);\n    text-align: center;\n    color: var(--color-text-muted);\n    font-size: var(--text-sm);\n\n    &[hidden] {\n      display: none;\n    }\n\n    &::before {\n      content: '';\n      display: inline-block;\n      width: 0.875rem;\n      height: 0.875rem;\n      margin-inline-end: var(--space-2);\n      vertical-align: -2px;\n\n      border: 2px solid var(--color-border);\n      border-top-color: var(--color-accent);\n      border-radius: var(--radius-full);\n\n      animation: ren-combobox-spin var(--duration-slow, 800ms) linear infinite;\n\n      @media (prefers-reduced-motion: reduce) {\n        animation: none;\n      }\n    }\n  }\n\n  &-group {\n    & + & {\n      margin-top: var(--space-1);\n      border-top: 1px solid var(--color-border);\n      padding-top: var(--space-1);\n    }\n\n    &[hidden] {\n      display: none;\n    }\n  }\n\n  &-group-label {\n    padding: var(--space-2) var(--space-3);\n    padding-bottom: var(--space-1);\n\n    font-size: var(--text-xs);\n    font-weight: 600;\n    text-transform: uppercase;\n    letter-spacing: 0.05em;\n    color: var(--color-text-muted);\n  }\n}\n\n@keyframes ren-combobox-open {\n  from {\n    opacity: 0;\n    translate: 0 -4px;\n  }\n  to {\n    opacity: 1;\n    translate: 0 0;\n  }\n}\n\n@keyframes ren-combobox-spin {\n  to {\n    transform: rotate(360deg);\n  }\n}\n",
      "path": "components/composites/ren-combobox/ren-combobox.css",
      "id": "file:components/composites/ren-combobox/ren-combobox.css",
      "type": "css",
      "name": "ren-combobox.css"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — <ren-combobox> Web Component\n * =====================================\n * Searchable, accessible combobox built on light DOM.\n *\n * Progressive enhancement: the markup ships with classes that work\n * standalone (.ren-combobox, .ren-combobox-input, .ren-combobox-list,\n * .ren-combobox-item). The Web Component layers ARIA wiring, keyboard\n * navigation, filtering, and a hidden form input on top.\n *\n * No Shadow DOM — light DOM only, like the rest of RenDS.\n *\n * Markup:\n *   <ren-combobox name=\"country\" placeholder=\"Search a country…\" value=\"ar\">\n *     <div class=\"ren-combobox-item\" data-value=\"ar\">Argentina</div>\n *     <div class=\"ren-combobox-item\" data-value=\"br\">Brazil</div>\n *     <div class=\"ren-combobox-item\" data-value=\"cl\">Chile</div>\n *   </ren-combobox>\n *\n * Attributes:\n *   name        — form field name (a hidden input with this name carries the value)\n *   value       — initial selected data-value\n *   placeholder — input placeholder (default: \"Search...\")\n *   disabled    — locks the input\n *   async       — suppresses local filtering; consumer provides items via ren-search\n *\n * Public methods:\n *   .value          — getter / setter for the selected value\n *   .open()         — show the list\n *   .close()        — hide the list\n *   .setLoading(b)  — toggle the loading row\n *   .setItems(arr)  — replace items, each { value, label, description? }\n *\n * Events (all bubble):\n *   ren-change  — { value, item }   selection changed\n *   ren-search  — { query }         user typed (use for async loading)\n *   ren-open / ren-close            list visibility\n */\n\nimport { autoId } from '../../../utils/id-generator.js';\n\nconst COMBOBOX_SIDES = new Set(['top', 'right', 'bottom', 'left']);\n\nfunction normalizeComboboxSide(value) {\n  const side = String(value || 'bottom').toLowerCase().split('-')[0];\n\n  return COMBOBOX_SIDES.has(side) ? side : 'bottom';\n}\n\nexport class RenCombobox extends HTMLElement {\n  static get observedAttributes() {\n    return ['value', 'placeholder', 'disabled', 'placement'];\n  }\n\n  #input;\n  #list;\n  #empty;\n  #loading;\n  #hidden;\n  #liveRegion;\n  #isOpen = false;\n  #highlightedIndex = -1;\n  #upgraded = false;\n  #onDocClick;\n\n  connectedCallback() {\n    if (this.#upgraded) return;\n    this.#upgraded = true;\n    this.#enhance();\n    this.#wire();\n\n    // Apply initial value if present\n    const initial = this.getAttribute('value');\n    if (initial) {\n      this.#selectByValue(initial, { silent: true });\n    }\n  }\n\n  disconnectedCallback() {\n    if (this.#onDocClick) {\n      document.removeEventListener('click', this.#onDocClick);\n    }\n  }\n\n  attributeChangedCallback(name, oldVal, newVal) {\n    if (!this.#upgraded) return;\n    if (name === 'placeholder' && this.#input) {\n      this.#input.placeholder = newVal || 'Search...';\n    } else if (name === 'disabled' && this.#input) {\n      this.#input.disabled = this.hasAttribute('disabled');\n    } else if (name === 'value' && newVal !== oldVal) {\n      this.#selectByValue(newVal, { silent: true });\n    } else if (name === 'placement' && newVal !== oldVal) {\n      this.#syncPlacement();\n    }\n  }\n\n  /* ─── Enhancement: take light-DOM children and wire structure ─── */\n\n  #enhance() {\n    this.classList.add('ren-combobox');\n\n    // Items: anything the consumer wrote inside (items, groups, labels).\n    // Move them into a list container.\n    const fragment = document.createDocumentFragment();\n    while (this.firstChild) {\n      fragment.appendChild(this.firstChild);\n    }\n\n    // Input. type=\"search\" + an unguessable autocomplete value defeats the\n    // Chromium heuristic that autofills \"country\"-flavored fields.\n    this.#input = document.createElement('input');\n    this.#input.type = 'search';\n    this.#input.className = 'ren-combobox-input';\n    this.#input.setAttribute('autocomplete', 'ren-combobox-no-autofill');\n    this.#input.setAttribute('autocorrect', 'off');\n    this.#input.setAttribute('autocapitalize', 'off');\n    this.#input.setAttribute('data-1p-ignore', '');\n    this.#input.setAttribute('data-lpignore', 'true');\n    this.#input.spellcheck = false;\n    this.#input.placeholder = this.getAttribute('placeholder') || 'Search...';\n    if (this.hasAttribute('disabled')) this.#input.disabled = true;\n    autoId(this.#input, 'combobox-input');\n\n    // List\n    this.#list = document.createElement('div');\n    this.#list.className = 'ren-combobox-list';\n    this.#list.hidden = true;\n    autoId(this.#list, 'combobox-list');\n\n    // Move user content into the list\n    this.#list.appendChild(fragment);\n\n    // Ensure an empty-state row exists\n    this.#empty = this.#list.querySelector('.ren-combobox-empty');\n    if (!this.#empty) {\n      this.#empty = document.createElement('div');\n      this.#empty.className = 'ren-combobox-empty';\n      this.#empty.textContent = 'No results';\n      this.#empty.hidden = true;\n      this.#list.appendChild(this.#empty);\n    }\n\n    // Loading row (hidden by default)\n    this.#loading = this.#list.querySelector('.ren-combobox-loading');\n    if (!this.#loading) {\n      this.#loading = document.createElement('div');\n      this.#loading.className = 'ren-combobox-loading';\n      this.#loading.textContent = 'Loading…';\n      this.#loading.hidden = true;\n      this.#list.insertBefore(this.#loading, this.#list.firstChild);\n    }\n\n    // Hidden input for form submission\n    this.#hidden = document.createElement('input');\n    this.#hidden.type = 'hidden';\n    if (this.hasAttribute('name')) this.#hidden.name = this.getAttribute('name');\n\n    // Live region for screen-reader announcements\n    this.#liveRegion = document.createElement('div');\n    this.#liveRegion.className = 'ren-sr-only';\n    this.#liveRegion.setAttribute('role', 'status');\n    this.#liveRegion.setAttribute('aria-live', 'polite');\n\n    // ARIA wiring\n    this.#input.setAttribute('role', 'combobox');\n    this.#input.setAttribute('aria-autocomplete', 'list');\n    this.#input.setAttribute('aria-expanded', 'false');\n    this.#input.setAttribute('aria-controls', this.#list.id);\n    this.#list.setAttribute('role', 'listbox');\n\n    this.#decorateItems();\n\n    // Append in canonical order\n    this.appendChild(this.#input);\n    this.appendChild(this.#list);\n    this.appendChild(this.#hidden);\n    this.appendChild(this.#liveRegion);\n\n    this.#syncPlacement();\n  }\n\n  #syncPlacement() {\n    const side = normalizeComboboxSide(this.getAttribute('placement'));\n\n    this.setAttribute('data-side', side);\n    if (this.#list) {\n      this.#list.setAttribute('data-side', side);\n    }\n  }\n\n  #decorateItems() {\n    const items = this.#getItems();\n    items.forEach((item) => {\n      if (!item.hasAttribute('role')) item.setAttribute('role', 'option');\n      autoId(item, 'combobox-opt');\n      if (!item.hasAttribute('aria-selected')) {\n        item.setAttribute('aria-selected', 'false');\n      }\n      if (!item.hasAttribute('tabindex')) item.setAttribute('tabindex', '-1');\n    });\n  }\n\n  #getItems() {\n    return Array.from(this.#list.querySelectorAll('.ren-combobox-item'));\n  }\n\n  #getVisibleItems() {\n    return this.#getItems().filter((i) => !i.hidden);\n  }\n\n  /* ─── Event wiring ─── */\n\n  #wire() {\n    // Open on click (not focus): programmatic .focus() after selection\n    // shouldn't reopen the list.\n    this.#input.addEventListener('mousedown', () => this.open());\n    this.#input.addEventListener('input', (e) => this.#onInput(e));\n    this.#input.addEventListener('keydown', (e) => this.#onKeydown(e));\n\n    // Item selection (delegated)\n    this.#list.addEventListener('click', (e) => {\n      const item = e.target.closest('.ren-combobox-item');\n      if (item && !item.hasAttribute('aria-disabled')) {\n        this.#selectItem(item);\n      }\n    });\n\n    this.#list.addEventListener('mouseover', (e) => {\n      const item = e.target.closest('.ren-combobox-item');\n      if (!item || item.hidden) return;\n      const visible = this.#getVisibleItems();\n      const idx = visible.indexOf(item);\n      if (idx !== -1) this.#highlight(idx);\n    });\n\n    // Close on outside click\n    this.#onDocClick = (e) => {\n      if (!this.contains(e.target)) this.close();\n    };\n    document.addEventListener('click', this.#onDocClick);\n  }\n\n  #onInput(e) {\n    const query = e.target.value;\n\n    if (this.hasAttribute('async')) {\n      // Async mode: emit ren-search and let the consumer replace items\n      this.dispatchEvent(\n        new CustomEvent('ren-search', {\n          detail: { query },\n          bubbles: true,\n        })\n      );\n    } else {\n      this.#filter(query);\n    }\n\n    if (!this.#isOpen) this.open();\n  }\n\n  #onKeydown(e) {\n    const visible = this.#getVisibleItems();\n\n    switch (e.key) {\n      case 'ArrowDown':\n        e.preventDefault();\n        if (!this.#isOpen) this.open();\n        this.#highlight(Math.min(this.#highlightedIndex + 1, visible.length - 1));\n        break;\n      case 'ArrowUp':\n        e.preventDefault();\n        if (this.#isOpen) {\n          this.#highlight(Math.max(this.#highlightedIndex - 1, 0));\n        }\n        break;\n      case 'Home':\n        if (this.#isOpen) {\n          e.preventDefault();\n          this.#highlight(0);\n        }\n        break;\n      case 'End':\n        if (this.#isOpen) {\n          e.preventDefault();\n          this.#highlight(visible.length - 1);\n        }\n        break;\n      case 'Enter':\n        if (this.#isOpen && visible[this.#highlightedIndex]) {\n          e.preventDefault();\n          this.#selectItem(visible[this.#highlightedIndex]);\n        }\n        break;\n      case 'Escape':\n        if (this.#isOpen) {\n          e.preventDefault();\n          this.close();\n        } else if (this.#input.value) {\n          this.#input.value = '';\n          this.#filter('');\n        }\n        break;\n    }\n  }\n\n  /* ─── Filtering ─── */\n\n  #filter(query) {\n    const needle = (query || '').toLowerCase().trim();\n    const items = this.#getItems();\n\n    items.forEach((item) => {\n      const match = !needle || item.textContent.toLowerCase().includes(needle);\n      item.hidden = !match;\n    });\n\n    // Hide groups whose items are all hidden\n    this.querySelectorAll('.ren-combobox-group').forEach((group) => {\n      const groupItems = group.querySelectorAll('.ren-combobox-item');\n      const anyVisible = Array.from(groupItems).some((i) => !i.hidden);\n      group.hidden = !anyVisible;\n    });\n\n    const anyVisible = items.some((i) => !i.hidden);\n    this.#empty.hidden = anyVisible;\n    this.#highlightedIndex = -1;\n\n    // Clear ARIA active descendant\n    this.#input.removeAttribute('aria-activedescendant');\n\n    // Announce result count\n    const count = items.filter((i) => !i.hidden).length;\n    if (this.#liveRegion) {\n      this.#liveRegion.textContent = count\n        ? `${count} result${count === 1 ? '' : 's'} available`\n        : 'No results';\n    }\n  }\n\n  /* ─── Highlight management (aria-activedescendant pattern) ─── */\n\n  #highlight(index) {\n    const visible = this.#getVisibleItems();\n    if (!visible.length) {\n      this.#highlightedIndex = -1;\n      this.#input.removeAttribute('aria-activedescendant');\n      return;\n    }\n\n    this.#getItems().forEach((i) => i.removeAttribute('data-highlighted'));\n\n    const clamped = Math.max(0, Math.min(index, visible.length - 1));\n    const target = visible[clamped];\n    target.setAttribute('data-highlighted', '');\n    this.#input.setAttribute('aria-activedescendant', target.id);\n    this.#highlightedIndex = clamped;\n\n    // Best-effort scroll; not critical if unsupported.\n    if (typeof target.scrollIntoView === 'function') {\n      try {\n        target.scrollIntoView({ block: 'nearest' });\n      } catch {}\n    }\n  }\n\n  /* ─── Selection ─── */\n\n  #selectItem(item) {\n    const value = item.getAttribute('data-value') || item.textContent.trim();\n    const label = item.textContent.trim();\n\n    this.#input.value = label;\n    this.#hidden.value = value;\n    this.setAttribute('value', value);\n\n    this.#getItems().forEach((i) => {\n      i.setAttribute('aria-selected', i === item ? 'true' : 'false');\n    });\n\n    this.close();\n    this.#input.focus();\n\n    this.dispatchEvent(\n      new CustomEvent('ren-change', {\n        detail: { value, item },\n        bubbles: true,\n      })\n    );\n  }\n\n  #selectByValue(value, { silent = false } = {}) {\n    const item = this.#getItems().find(\n      (i) => i.getAttribute('data-value') === value\n    );\n    if (!item) return;\n\n    if (silent) {\n      this.#input.value = item.textContent.trim();\n      this.#hidden.value = value;\n      this.#getItems().forEach((i) => {\n        i.setAttribute('aria-selected', i === item ? 'true' : 'false');\n      });\n    } else {\n      this.#selectItem(item);\n    }\n  }\n\n  /* ─── Public API ─── */\n\n  open() {\n    if (this.#isOpen) return;\n    this.#isOpen = true;\n    this.#list.hidden = false;\n    this.#input.setAttribute('aria-expanded', 'true');\n    this.dispatchEvent(new CustomEvent('ren-open', { bubbles: true }));\n  }\n\n  close() {\n    if (!this.#isOpen) return;\n    this.#isOpen = false;\n    this.#list.hidden = true;\n    this.#input.setAttribute('aria-expanded', 'false');\n    this.#input.removeAttribute('aria-activedescendant');\n    this.#getItems().forEach((i) => i.removeAttribute('data-highlighted'));\n    this.#highlightedIndex = -1;\n    this.dispatchEvent(new CustomEvent('ren-close', { bubbles: true }));\n  }\n\n  setLoading(isLoading) {\n    if (!this.#loading) return;\n    this.#loading.hidden = !isLoading;\n    if (isLoading) this.#empty.hidden = true;\n  }\n\n  setItems(items) {\n    // Clear existing items (preserve groups, empty, loading)\n    this.#getItems().forEach((i) => i.remove());\n\n    const frag = document.createDocumentFragment();\n    items.forEach(({ value, label, description, disabled }) => {\n      const item = document.createElement('div');\n      item.className = 'ren-combobox-item';\n      item.setAttribute('role', 'option');\n      item.setAttribute('aria-selected', 'false');\n      item.setAttribute('tabindex', '-1');\n      autoId(item, 'combobox-opt');\n      item.dataset.value = value;\n      if (disabled) item.setAttribute('aria-disabled', 'true');\n\n      if (description) {\n        const main = document.createElement('div');\n        main.className = 'ren-combobox-item-label';\n        main.textContent = label;\n        const desc = document.createElement('div');\n        desc.className = 'ren-combobox-item-description';\n        desc.textContent = description;\n        item.append(main, desc);\n      } else {\n        item.textContent = label;\n      }\n      frag.appendChild(item);\n    });\n\n    this.#list.insertBefore(frag, this.#empty);\n    this.#empty.hidden = items.length > 0;\n    this.#highlightedIndex = -1;\n  }\n\n  get value() {\n    return this.#hidden ? this.#hidden.value : '';\n  }\n\n  set value(val) {\n    if (val == null || val === '') {\n      if (this.#input) this.#input.value = '';\n      if (this.#hidden) this.#hidden.value = '';\n      this.#getItems().forEach((i) => i.setAttribute('aria-selected', 'false'));\n      this.removeAttribute('value');\n    } else {\n      this.setAttribute('value', val);\n    }\n  }\n}\n\nif (!customElements.get('ren-combobox')) {\n  customElements.define('ren-combobox', RenCombobox);\n}\n",
      "path": "components/composites/ren-combobox/ren-combobox.js",
      "id": "file:components/composites/ren-combobox/ren-combobox.js",
      "type": "javascript",
      "name": "ren-combobox.js"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Context Menu Component\n   ============================================\n   Right-click menu using Popover API.\n   Reuses ren-menu styles for items.\n   Minimal JS for right-click trigger + positioning.\n\n   Usage:\n     <div class=\"ren-context-menu-trigger\" data-context=\"my-ctx-menu\">\n       Right-click this area\n     </div>\n\n     <div id=\"my-ctx-menu\" class=\"ren-context-menu\" popover>\n       <button class=\"ren-menu-item\">Cut</button>\n       <button class=\"ren-menu-item\">Copy</button>\n       <button class=\"ren-menu-item\">Paste</button>\n       <hr class=\"ren-menu-separator\">\n       <button class=\"ren-menu-item ren-menu-item-danger\">Delete</button>\n     </div>\n   ============================================ */\n\n.ren-context-menu {\n  /* Reset popover defaults */\n  margin: 0;\n  padding: 0;\n  border: none;\n  background: none;\n  inset: auto;\n\n  /* Menu appearance (matches ren-menu) */\n  background: var(--color-surface);\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  padding-block: var(--space-1);\n  min-width: 10rem;\n  max-height: calc(100vh - 32px);\n  overflow-y: auto;\n  overscroll-behavior: contain;\n\n  /* Position set by JS */\n  position: fixed;\n\n  /* Animation — semantic tokens keep context-menu in sync with\n     ren-menu / ren-popover. Collapses to 0ms under reduce. */\n  opacity: 0;\n  transform: scale(0.95);\n  transform-origin: top left;\n  transition:\n    opacity var(--duration-enter) var(--ease-enter),\n    transform var(--duration-enter) var(--ease-enter),\n    overlay var(--duration-enter) var(--ease-enter) allow-discrete,\n    display var(--duration-enter) var(--ease-enter) allow-discrete;\n}\n\n.ren-context-menu:popover-open,\n.ren-context-menu.ren-open {\n  opacity: 1;\n  transform: scale(1);\n}\n\n@starting-style {\n  .ren-context-menu:popover-open {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n}\n\n/* ─── Reuse menu item styles ─── */\n/* Already defined in ren-menu.css:\n   .ren-menu-item, .ren-menu-separator, etc.\n   Import ren-menu.css alongside this file.\n*/\n\n/* ─── Reduced motion ─── */\n@media (prefers-reduced-motion: reduce) {\n  .ren-context-menu {\n    transition: none;\n  }\n}\n",
      "path": "components/composites/ren-context-menu/ren-context-menu.css",
      "id": "file:components/composites/ren-context-menu/ren-context-menu.css",
      "type": "css",
      "name": "ren-context-menu.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Context Menu Controller\n   ============================================\n   Handles right-click trigger, positioning,\n   and keyboard navigation.\n\n   Usage:\n     import { initContextMenu } from './ren-context-menu.js';\n     initContextMenu('my-ctx-menu');\n\n   Or auto-init all:\n     import { initAllContextMenus } from './ren-context-menu.js';\n     initAllContextMenus();\n   ============================================ */\n\n/**\n * @param {string|HTMLElement} menuOrId - The popover element or its ID\n */\nexport function initContextMenu(menuOrId) {\n  const menu = typeof menuOrId === 'string'\n    ? document.getElementById(menuOrId)\n    : menuOrId;\n  if (!menu) return;\n\n  if (menu.dataset.renContextMenuInitialized === 'true') return;\n  menu.dataset.renContextMenuInitialized = 'true';\n  const controller = new AbortController();\n  menu.__renContextController = controller;\n  let returnTrigger = null;\n\n  menu.setAttribute('popover', 'manual');\n  menu.setAttribute('role', 'menu');\n  menu.classList.add('ren-context-menu');\n\n  // Find all triggers for this menu\n  const triggers = [...document.querySelectorAll(`[data-context=\"${menu.id}\"]`)];\n  const triggerId = menu.getAttribute('trigger-id');\n  const explicitTrigger = triggerId ? document.getElementById(triggerId) : null;\n  if (explicitTrigger && !triggers.includes(explicitTrigger)) triggers.push(explicitTrigger);\n  const itemSelector = [\n    '.ren-menu-item:not(:disabled):not([aria-disabled=\"true\"])',\n    '[role=\"menuitem\"]:not(:disabled):not([aria-disabled=\"true\"])',\n  ].join(', ');\n\n  const isOpen = () => menu.matches(':popover-open') || menu.classList.contains('ren-open');\n\n  const close = () => {\n    if (!isOpen()) return;\n\n    if ('hidePopover' in menu && menu.matches(':popover-open')) {\n      try {\n        menu.hidePopover();\n      } catch {\n        // Popover may have already been closed by the browser.\n      }\n    } else {\n      menu.classList.remove('ren-open');\n    }\n\n    menu.setAttribute('data-state', 'closed');\n    if (returnTrigger && document.contains(returnTrigger)) returnTrigger.focus();\n  };\n\n  const show = (x, y, target) => {\n    if (isOpen()) {\n      close();\n    }\n\n    // Position menu at cursor\n    menu.style.left = `${x}px`;\n    menu.style.top = `${y}px`;\n\n    if ('showPopover' in menu) {\n      try {\n        menu.showPopover();\n      } catch {\n        // Popover may already be open during rapid repeated contextmenu events.\n      }\n    } else {\n      menu.classList.add('ren-open');\n    }\n\n    menu.setAttribute('data-state', 'open');\n\n    // Adjust if overflows viewport\n    const rect = menu.getBoundingClientRect();\n    if (rect.right > window.innerWidth) {\n      menu.style.left = `${Math.max(8, x - rect.width)}px`;\n    }\n    if (rect.bottom > window.innerHeight) {\n      menu.style.top = `${Math.max(8, y - rect.height)}px`;\n    }\n\n    // Focus first item\n    const firstItem = menu.querySelector(itemSelector);\n    firstItem?.focus();\n\n    menu.dispatchEvent(new CustomEvent('ren-context-menu-open', {\n      bubbles: true,\n      composed: true,\n      detail: { x, y, target },\n    }));\n  };\n\n  triggers.forEach((trigger) => {\n    trigger.addEventListener('contextmenu', (e) => {\n      e.preventDefault();\n      returnTrigger = trigger;\n      show(e.clientX, e.clientY, e.target);\n    }, { signal: controller.signal });\n\n    trigger.addEventListener('keydown', (e) => {\n      if (e.key !== 'ContextMenu' && !(e.key === 'F10' && e.shiftKey)) return;\n\n      e.preventDefault();\n      const rect = trigger.getBoundingClientRect();\n      returnTrigger = trigger;\n      show(rect.left + 8, rect.bottom + 8, e.target);\n    }, { signal: controller.signal });\n  });\n\n  // Keyboard navigation inside menu\n  menu.addEventListener('keydown', (e) => {\n    const items = [...menu.querySelectorAll(itemSelector)];\n    if (items.length === 0) return;\n\n    const current = items.indexOf(document.activeElement);\n\n    if (e.key === 'ArrowDown') {\n      e.preventDefault();\n      items[(current + 1) % items.length]?.focus();\n    } else if (e.key === 'ArrowUp') {\n      e.preventDefault();\n      items[(current - 1 + items.length) % items.length]?.focus();\n    } else if (e.key === 'Home') {\n      e.preventDefault();\n      items[0]?.focus();\n    } else if (e.key === 'End') {\n      e.preventDefault();\n      items.at(-1)?.focus();\n    } else if (e.key === 'Escape') {\n      close();\n    } else if (e.key === 'Enter' || e.key === ' ') {\n      e.preventDefault();\n      document.activeElement?.click();\n      close();\n    }\n  }, { signal: controller.signal });\n\n  // Close on item click\n  menu.addEventListener('click', (e) => {\n    if (e.target.closest('.ren-menu-item')) {\n      close();\n    }\n  }, { signal: controller.signal });\n\n  const isMenuOrTrigger = (target) =>\n    target instanceof Node &&\n    (menu.contains(target) || triggers.some((trigger) => trigger.contains(target)));\n\n  document.addEventListener(\n    'pointerdown',\n    (e) => {\n      if (isOpen() && !isMenuOrTrigger(e.target)) {\n        close();\n      }\n    },\n    { capture: true, signal: controller.signal }\n  );\n\n  document.addEventListener(\n    'contextmenu',\n    (e) => {\n      if (isOpen() && !isMenuOrTrigger(e.target)) {\n        close();\n      }\n    },\n    { capture: true, signal: controller.signal }\n  );\n}\n\nexport function destroyContextMenu(menuOrId) {\n  const menu = typeof menuOrId === 'string'\n    ? document.getElementById(menuOrId)\n    : menuOrId;\n  if (!menu) return;\n  menu.__renContextController?.abort();\n  delete menu.__renContextController;\n  delete menu.dataset.renContextMenuInitialized;\n}\n\n/**\n * Auto-init all context menus.\n */\nexport function initAllContextMenus() {\n  const menuIds = new Set();\n  document.querySelectorAll('[data-context]').forEach((el) => {\n    menuIds.add(el.dataset.context);\n  });\n  menuIds.forEach(initContextMenu);\n}\n\n/**\n * Registry-friendly custom element facade over the same controller used by\n * initContextMenu(). There is one behavior owner and one reconnect-safe\n * AbortController regardless of which public entry point consumers use.\n */\nexport class RenContextMenu extends HTMLElement {\n  connectedCallback() {\n    if (!this.id) this.id = `ren-context-menu-${Math.random().toString(36).slice(2, 11)}`;\n    initContextMenu(this);\n  }\n\n  disconnectedCallback() {\n    destroyContextMenu(this);\n  }\n}\n\nif (!customElements.get('ren-context-menu')) {\n  customElements.define('ren-context-menu', RenContextMenu);\n}\n",
      "path": "components/composites/ren-context-menu/ren-context-menu.js",
      "id": "file:components/composites/ren-context-menu/ren-context-menu.js",
      "type": "javascript",
      "name": "ren-context-menu.js"
    },
    {
      "data": {},
      "body": "/* ═══ REN DATE PICKER WEB COMPONENT ═══\n   A composable date picker that wraps the calendar component\n   with an input field and popover dropdown.\n   Uses Popover API for positioning with CSS anchor positioning.\n   ══════════════════════════════════════════════════════════════════ */\n\n/* ═══ BASE WRAPPER ═══ */\n.ren-date-picker {\n  display: inline-flex;\n  flex-direction: column;\n  width: 100%;\n  max-width: 100%;\n  position: relative;\n}\n\n/* ═══ TRIGGER INPUT ═══ */\n.ren-date-picker-trigger {\n  anchor-name: --ren-date-picker-anchor;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--space-2);\n  width: 100%;\n  padding: var(--space-2) var(--space-3);\n  background: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n  color: var(--color-text);\n  font-size: var(--body-size);\n  font-family: inherit;\n  cursor: pointer;\n  transition: var(--transition-tactile);\n  min-height: var(--touch-min);\n\n  &:hover:not([disabled]) {\n    border-color: var(--color-border-strong);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n    border-color: var(--color-accent);\n  }\n\n  &[disabled] {\n    background: var(--color-fill);\n    color: var(--color-text-muted);\n    cursor: not-allowed;\n    opacity: 0.6;\n  }\n\n  /* ═══ PLACEHOLDER TEXT ═══ */\n  &::placeholder {\n    color: var(--color-text-muted);\n  }\n\n  /* ═══ CALENDAR ICON (::after) ═══ */\n  &::after {\n    content: '📅';\n    font-size: 1.25em;\n    flex-shrink: 0;\n    pointer-events: none;\n  }\n}\n\n/* ═══ TRIGGER TEXT CONTENT ═══ */\n.ren-date-picker-value {\n  flex: 1;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n/* ═══ EMPTY STATE ═══ */\n.ren-date-picker-trigger.ren-date-picker-empty {\n  color: var(--color-text-muted);\n\n  .ren-date-picker-value {\n    display: none;\n  }\n\n  &::before {\n    content: attr(data-placeholder);\n    color: var(--color-text-muted);\n  }\n}\n\n/* ═══ DROPDOWN CONTAINER (POPOVER) ═══ */\n.ren-date-picker-dropdown {\n  position-anchor: --ren-date-picker-anchor;\n  position: absolute;\n  position-area: bottom span-all;\n  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;\n  inset: auto;\n  margin: 0;\n  background: var(--color-surface-raised);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  z-index: 1000;\n  max-width: 400px;\n\n  /* ═══ POPOVER API ═══ */\n  &[popover] {\n    padding: 0;\n    margin: 0;\n  }\n\n  &:popover-open {\n    background: var(--color-surface-raised);\n    border: 1px solid var(--color-border);\n    border-radius: var(--radius-lg);\n    box-shadow: var(--shadow-lg);\n    animation: ren-date-picker-popup-in var(--duration-enter) var(--ease-enter) both;\n  }\n}\n\n.ren-date-picker-dropdown[data-side=\"bottom\"],\n.ren-date-picker-dropdown:not([data-side]) {\n  position-area: bottom span-all;\n  margin-block-start: var(--space-2);\n  margin-block-end: 0;\n  margin-inline: 0;\n}\n\n.ren-date-picker-dropdown[data-side=\"top\"] {\n  position-area: top span-all;\n  margin-block-start: 0;\n  margin-block-end: var(--space-2);\n  margin-inline: 0;\n}\n\n.ren-date-picker-dropdown[data-side=\"right\"] {\n  position-area: right span-all;\n  margin-block: 0;\n  margin-inline-start: var(--space-2);\n  margin-inline-end: 0;\n}\n\n.ren-date-picker-dropdown[data-side=\"left\"] {\n  position-area: left span-all;\n  margin-block: 0;\n  margin-inline-start: 0;\n  margin-inline-end: var(--space-2);\n}\n\n/* ═══ FALLBACK POSITIONING (no complete anchor support) ═══ */\n@supports not ((anchor-name: --ren-anchor) and\n  (position-anchor: --ren-anchor) and\n  (position-area: bottom span-all)) {\n  .ren-date-picker-dropdown {\n    position: fixed;\n    top: auto;\n    left: auto;\n  }\n}\n\n/* ═══ ANIMATION: POPUP IN ═══ */\n@starting-style {\n  .ren-date-picker-dropdown:popover-open {\n    opacity: 0;\n    translate: 0 -8px;\n  }\n}\n\n@keyframes ren-date-picker-popup-in {\n  from {\n    opacity: 0;\n    translate: 0 -8px;\n  }\n\n  to {\n    opacity: 1;\n    translate: 0 0;\n  }\n}\n\n/* ═══ CALENDAR INSIDE DROPDOWN ═══ */\n.ren-date-picker-dropdown .ren-calendar {\n  border: none;\n  box-shadow: none;\n  background: var(--color-surface-raised);\n  padding: var(--space-3);\n}\n\n/* ═══ PRESETS SECTION ═══ */\n.ren-date-picker-presets {\n  display: flex;\n  flex-wrap: wrap;\n  gap: var(--space-2);\n  padding: var(--space-3);\n  border-bottom: 1px solid var(--color-border);\n  background: var(--color-surface);\n}\n\n.ren-date-picker-preset {\n  padding: var(--space-1-5) var(--space-3);\n  background: var(--color-fill);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n  color: var(--color-text);\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  cursor: pointer;\n  transition: var(--transition-tactile);\n\n  &:hover {\n    background: var(--color-fill-hover);\n    border-color: var(--color-border-strong);\n  }\n\n  &:active {\n    background: var(--color-fill-active);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  &[disabled] {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n}\n\n/* ═══ RANGE MODE: SHOW START AND END ═══ */\n.ren-date-picker.ren-date-picker-range .ren-date-picker-value::after {\n  content: ' → ';\n  margin: 0 var(--space-1);\n}\n\n/* ═══ DISABLED STATE ═══ */\n.ren-date-picker[disabled] {\n  opacity: 0.6;\n  pointer-events: none;\n\n  .ren-date-picker-trigger {\n    cursor: not-allowed;\n  }\n}\n\n/* ═══ REDUCED MOTION SUPPORT ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-date-picker-dropdown:popover-open {\n    animation: none;\n    opacity: 1;\n    translate: 0 0;\n  }\n\n  .ren-date-picker-dropdown {\n    transition: none;\n  }\n}\n\n/* ═══ DARK MODE SUPPORT ═══ */\n@media (prefers-color-scheme: dark) {\n  .ren-date-picker-trigger,\n  .ren-date-picker-dropdown {\n    /* ═══ USES CSS VARIABLES - AUTO ADAPTS ═══ */\n  }\n}\n\n/* ═══ MOBILE RESPONSIVENESS ═══ */\n@media (max-width: 480px) {\n  .ren-date-picker-dropdown {\n    position: fixed !important;\n    inset: auto 0 0 0 !important;\n    max-width: none;\n    border-radius: var(--radius-lg) var(--radius-lg) 0 0;\n    max-height: 80vh;\n    overflow-y: auto;\n\n    &:popover-open {\n      animation: ren-date-picker-mobile-in var(--duration-enter) var(--ease-enter) both;\n    }\n  }\n\n  @starting-style {\n    .ren-date-picker-dropdown:popover-open {\n      translate: 0 100%;\n      opacity: 0;\n    }\n  }\n\n}\n\n@keyframes ren-date-picker-mobile-in {\n  from {\n    translate: 0 100%;\n    opacity: 0;\n  }\n\n  to {\n    translate: 0 0;\n    opacity: 1;\n  }\n}\n\n/* ═══ ERROR STATE ═══ */\n.ren-date-picker.ren-date-picker-error {\n  .ren-date-picker-trigger {\n    border-color: var(--color-danger);\n\n    &:focus-visible {\n      outline-color: var(--color-danger);\n      border-color: var(--color-danger);\n    }\n  }\n}\n\n/* ═══ SUCCESS STATE ═══ */\n.ren-date-picker.ren-date-picker-success {\n  .ren-date-picker-trigger {\n    border-color: var(--color-success);\n\n    &:focus-visible {\n      outline-color: var(--color-success);\n      border-color: var(--color-success);\n    }\n  }\n}\n\n/* ═══ FORM INTEGRATION ═══ */\n.ren-date-picker {\n  /* ═══ ALLOW FORM STYLING ═══ */\n\n  input[type='hidden'] {\n    display: none;\n  }\n}\n",
      "path": "components/composites/ren-date-picker/ren-date-picker.css",
      "id": "file:components/composites/ren-date-picker/ren-date-picker.css",
      "type": "css",
      "name": "ren-date-picker.css"
    },
    {
      "data": {},
      "body": "/* ═══ REN DATE PICKER WEB COMPONENT ═══\n   A composable date picker that combines an input field with a calendar dropdown.\n\n   Features:\n   - Integrates RenCalendar component internally\n   - Single and range date selection modes\n   - Preset shortcuts (Today, Tomorrow, Next Week, etc.)\n   - Popover API for dropdown positioning\n   - Locale-aware date formatting\n   - Focus management with blur/focus trapping\n   - Form integration with hidden input\n\n   Usage:\n   <ren-date-picker placeholder=\"Select date\" format=\"long\" mode=\"single\"></ren-date-picker>\n  ══════════════════════════════════════════════════════════════════ */\n\nconst DATE_PICKER_SIDES = new Set(['top', 'right', 'bottom', 'left']);\n\nfunction normalizeDatePickerSide(value) {\n  const side = String(value || 'bottom').toLowerCase().split('-')[0];\n\n  return DATE_PICKER_SIDES.has(side) ? side : 'bottom';\n}\n\nexport class RenDatePicker extends HTMLElement {\n  static observedAttributes = ['placement'];\n\n  constructor() {\n    super();\n\n    /* ═══ STATE ═══ */\n    this.calendar = null;\n    this.dropdown = null;\n    this.trigger = null;\n    this.isOpen = false;\n    this.selectedValue = null;\n    this.selectedRange = null;\n\n    /* ═══ CONFIGURATION ═══ */\n    this.format = 'short'; // short, medium, long\n    this.locale = 'en-US';\n    this.mode = 'single'; // single, range\n    this.placeholder = 'Select date';\n\n    /* ═══ BIND METHODS ═══ */\n    this.handleTriggerClick = this.handleTriggerClick.bind(this);\n    this.handleTriggerKeyDown = this.handleTriggerKeyDown.bind(this);\n    this.handleCalendarSelect = this.handleCalendarSelect.bind(this);\n    this.handleDocumentClick = this.handleDocumentClick.bind(this);\n  }\n\n  connectedCallback() {\n    /* ═══ READ ATTRIBUTES ═══ */\n    this.mode = this.getAttribute('mode') || 'single';\n    this.format = this.getAttribute('format') || 'short';\n    this.locale = this.getAttribute('locale') || 'en-US';\n    this.placeholder = this.getAttribute('placeholder') || 'Select date';\n\n    const minAttr = this.getAttribute('min');\n    const maxAttr = this.getAttribute('max');\n\n    /* ═══ RENDER COMPONENT ═══ */\n    this.render();\n\n    /* ═══ SET UP TRIGGER (must come after render) ═══ */\n    this.trigger = this.querySelector('.ren-date-picker-trigger');\n    if (this.trigger) {\n      this.trigger.addEventListener('click', this.handleTriggerClick);\n      this.trigger.addEventListener('keydown', this.handleTriggerKeyDown);\n    }\n\n    /* ═══ SET UP DROPDOWN (must come after render, before calendar) ═══ */\n    this.dropdown = this.querySelector('[popover]');\n    if (!this.dropdown) {\n      this.dropdown = document.createElement('div');\n      this.dropdown.className = 'ren-date-picker-dropdown';\n      this.dropdown.setAttribute('popover', 'manual');\n      this.appendChild(this.dropdown);\n    }\n\n    /* ═══ SET UP CALENDAR (must come after dropdown) ═══ */\n    this.calendar = this.querySelector('ren-calendar');\n    if (!this.calendar) {\n      this.calendar = document.createElement('ren-calendar');\n      this.calendar.setAttribute('mode', this.mode);\n      if (this.locale) this.calendar.setAttribute('locale', this.locale);\n      if (minAttr) this.calendar.setAttribute('min', minAttr);\n      if (maxAttr) this.calendar.setAttribute('max', maxAttr);\n      this.dropdown.appendChild(this.calendar);\n    }\n\n    this.syncPlacement();\n\n    /* ═══ LISTEN TO CALENDAR EVENTS ═══ */\n    this.calendar.addEventListener('ren-date-select', this.handleCalendarSelect);\n\n    /* ═══ LISTEN TO DOCUMENT CLICKS ═══ */\n    document.addEventListener('click', this.handleDocumentClick);\n\n    /* ═══ SET INITIAL VALUE ═══ */\n    const valueAttr = this.getAttribute('value');\n    if (valueAttr) {\n      this.setValue(valueAttr);\n    }\n\n    /* ═══ ADD TO FORM IF PARENT IS FORM ═══ */\n    this.addHiddenInput();\n  }\n\n  disconnectedCallback() {\n    if (this.trigger) {\n      this.trigger.removeEventListener('click', this.handleTriggerClick);\n      this.trigger.removeEventListener('keydown', this.handleTriggerKeyDown);\n    }\n\n    if (this.calendar) {\n      this.calendar.removeEventListener('ren-date-select', this.handleCalendarSelect);\n    }\n\n    document.removeEventListener('click', this.handleDocumentClick);\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'placement' && oldValue !== newValue) {\n      this.syncPlacement();\n    }\n  }\n\n  /* ═══ RENDER COMPONENT STRUCTURE ═══ */\n  render() {\n    this.classList.add('ren-date-picker');\n\n    if (this.mode === 'range') {\n      this.classList.add('ren-date-picker-range');\n    }\n\n    if (this.innerHTML.trim() === '') {\n      /* ═══ CREATE TRIGGER ═══ */\n      const trigger = document.createElement('button');\n      trigger.className = 'ren-date-picker-trigger ren-date-picker-empty';\n      trigger.setAttribute('type', 'button');\n      trigger.setAttribute('aria-haspopup', 'dialog');\n      trigger.setAttribute('aria-expanded', 'false');\n      trigger.setAttribute('data-placeholder', this.placeholder);\n\n      const valueSpan = document.createElement('span');\n      valueSpan.className = 'ren-date-picker-value';\n      trigger.appendChild(valueSpan);\n\n      this.appendChild(trigger);\n\n      /* ═══ CREATE DROPDOWN ═══ */\n      const dropdown = document.createElement('div');\n      dropdown.className = 'ren-date-picker-dropdown';\n      dropdown.setAttribute('popover', 'manual');\n\n      this.appendChild(dropdown);\n    }\n  }\n\n  /* ═══ MIRROR PLACEMENT TO PUBLIC DATA ATTRIBUTES ═══ */\n  syncPlacement() {\n    const side = normalizeDatePickerSide(this.getAttribute('placement'));\n\n    this.setAttribute('data-side', side);\n    if (this.dropdown) {\n      this.dropdown.setAttribute('data-side', side);\n    }\n  }\n\n  /* ═══ ADD HIDDEN INPUT FOR FORM SUBMISSION ═══ */\n  addHiddenInput() {\n    let hidden = this.querySelector('input[type=\"hidden\"]');\n    if (!hidden) {\n      hidden = document.createElement('input');\n      hidden.type = 'hidden';\n      hidden.name = this.getAttribute('name') || 'date-picker';\n      this.appendChild(hidden);\n    }\n    this.hiddenInput = hidden;\n  }\n\n  /* ═══ HANDLE TRIGGER CLICK ═══ */\n  handleTriggerClick(event) {\n    event.preventDefault();\n    event.stopPropagation();\n\n    if (this.hasAttribute('disabled')) return;\n\n    if (this.isOpen) {\n      this.close();\n    } else {\n      this.open();\n    }\n  }\n\n  /* ═══ HANDLE TRIGGER KEYBOARD ═══ */\n  handleTriggerKeyDown(event) {\n    if (this.hasAttribute('disabled')) return;\n\n    if (event.key === 'Enter' || event.key === ' ') {\n      event.preventDefault();\n      this.open();\n    }\n\n    if (event.key === 'Escape') {\n      this.close();\n    }\n  }\n\n  /* ═══ HANDLE CALENDAR DATE SELECT ═══ */\n  handleCalendarSelect(event) {\n    const detail = event.detail;\n\n    if (this.mode === 'single') {\n      this.selectedValue = detail.date;\n      this.updateTrigger(this.formatDate(detail.date));\n      this.dispatchChangeEvent();\n      this.close();\n    } else if (this.mode === 'range') {\n      this.selectedRange = detail.range;\n      if (detail.range && detail.range.start && detail.range.end) {\n        const formattedRange = `${this.formatDate(detail.range.start)} → ${this.formatDate(detail.range.end)}`;\n        this.updateTrigger(formattedRange);\n        this.dispatchChangeEvent();\n        this.close();\n      } else {\n        const start = detail.range?.start ? this.formatDate(detail.range.start) : '';\n        this.updateTrigger(start);\n      }\n    }\n  }\n\n  /* ═══ HANDLE DOCUMENT CLICK (CLOSE DROPDOWN ON OUTSIDE CLICK) ═══ */\n  handleDocumentClick(event) {\n    if (!this.contains(event.target) && this.isOpen) {\n      this.close();\n    }\n  }\n\n  /* ═══ OPEN DROPDOWN ═══ */\n  open() {\n    if (this.isOpen || this.hasAttribute('disabled')) return;\n\n    if (!this.dropdown) return;\n\n    /* ═══ SET UP POPOVER AND POSITIONING ═══ */\n    this.syncPlacement();\n\n    try {\n      this.dropdown.showPopover();\n    } catch {\n      /* ═══ FALLBACK FOR UNSUPPORTED BROWSERS ═══ */\n      this.dropdown.style.display = 'block';\n    }\n\n    this.isOpen = true;\n\n    if (this.trigger) {\n      this.trigger.setAttribute('aria-expanded', 'true');\n    }\n\n    /* ═══ FOCUS CALENDAR ═══ */\n    setTimeout(() => {\n      if (this.calendar) {\n        const firstDay = this.calendar.querySelector('.ren-calendar-day:not([disabled])');\n        if (firstDay) {\n          firstDay.focus();\n        }\n      }\n    }, 100);\n\n    this.dispatchEvent(\n      new CustomEvent('ren-date-picker-open', {\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ CLOSE DROPDOWN ═══ */\n  close() {\n    if (!this.isOpen) return;\n\n    if (!this.dropdown) return;\n\n    try {\n      this.dropdown.hidePopover();\n    } catch {\n      /* ═══ FALLBACK FOR UNSUPPORTED BROWSERS ═══ */\n      this.dropdown.style.display = 'none';\n    }\n\n    this.isOpen = false;\n\n    if (this.trigger) {\n      this.trigger.setAttribute('aria-expanded', 'false');\n      this.trigger.focus();\n    }\n\n    this.dispatchEvent(\n      new CustomEvent('ren-date-picker-close', {\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ UPDATE TRIGGER TEXT ═══ */\n  updateTrigger(formattedValue) {\n    if (!this.trigger) return;\n\n    const valueSpan = this.trigger.querySelector('.ren-date-picker-value');\n    if (valueSpan) {\n      valueSpan.textContent = formattedValue;\n    }\n\n    this.trigger.classList.remove('ren-date-picker-empty');\n\n    /* ═══ UPDATE HIDDEN INPUT ═══ */\n    if (this.hiddenInput) {\n      this.hiddenInput.value = formattedValue;\n    }\n  }\n\n  /* ═══ FORMAT DATE FOR DISPLAY ═══ */\n  formatDate(date) {\n    if (!date) return '';\n\n    const formatter = new Intl.DateTimeFormat(this.locale, {\n      year: 'numeric',\n      month: this.format === 'short' ? '2-digit' : this.format === 'long' ? 'long' : 'short',\n      day: '2-digit',\n    });\n\n    return formatter.format(date);\n  }\n\n  /* ═══ DISPATCH CHANGE EVENT ═══ */\n  dispatchChangeEvent() {\n    let value = null;\n    let formattedValue = '';\n\n    if (this.mode === 'single') {\n      value = this.selectedValue ? this.dateToString(this.selectedValue) : null;\n      formattedValue = this.selectedValue ? this.formatDate(this.selectedValue) : '';\n    } else if (this.mode === 'range') {\n      if (this.selectedRange && this.selectedRange.start && this.selectedRange.end) {\n        value = {\n          start: this.dateToString(this.selectedRange.start),\n          end: this.dateToString(this.selectedRange.end),\n        };\n        formattedValue = `${this.formatDate(this.selectedRange.start)} → ${this.formatDate(this.selectedRange.end)}`;\n      }\n    }\n\n    this.dispatchEvent(\n      new CustomEvent('ren-change', {\n        detail: {\n          value,\n          formattedValue,\n        },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ CONVERT DATE TO ISO STRING ═══ */\n  dateToString(date) {\n    const year = date.getFullYear();\n    const month = String(date.getMonth() + 1).padStart(2, '0');\n    const day = String(date.getDate()).padStart(2, '0');\n    return `${year}-${month}-${day}`;\n  }\n\n  parseDate(value) {\n    if (value instanceof Date) return new Date(value.getTime());\n    const match = String(value).match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n    return match ? new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3])) : new Date(value);\n  }\n\n  /* ═══ PUBLIC METHODS ═══ */\n\n  /* ═══ GET VALUE ═══ */\n  getValue() {\n    if (this.mode === 'single') {\n      return this.selectedValue ? this.dateToString(this.selectedValue) : null;\n    } else if (this.mode === 'range') {\n      return this.selectedRange\n        ? {\n            start: this.dateToString(this.selectedRange.start),\n            end: this.dateToString(this.selectedRange.end),\n          }\n        : null;\n    }\n  }\n\n  /* ═══ SET VALUE ═══ */\n  setValue(value) {\n    if (this.mode === 'single' && typeof value === 'string') {\n      this.selectedValue = this.parseDate(value);\n      this.calendar.setValue(value);\n      this.updateTrigger(this.formatDate(this.selectedValue));\n    } else if (this.mode === 'range' && value && typeof value === 'object') {\n      this.selectedRange = {\n        start: this.parseDate(value.start),\n        end: this.parseDate(value.end),\n      };\n      this.calendar.setRange(value.start, value.end);\n      const formatted = `${this.formatDate(this.selectedRange.start)} → ${this.formatDate(this.selectedRange.end)}`;\n      this.updateTrigger(formatted);\n    }\n  }\n\n  /* ═══ TOGGLE DROPDOWN ═══ */\n  toggle() {\n    if (this.isOpen) {\n      this.close();\n    } else {\n      this.open();\n    }\n  }\n\n  /* ═══ GETTERS AND SETTERS ═══ */\n  get disabled() {\n    return this.hasAttribute('disabled');\n  }\n\n  set disabled(val) {\n    if (val) {\n      this.setAttribute('disabled', '');\n      if (this.trigger) this.trigger.disabled = true;\n    } else {\n      this.removeAttribute('disabled');\n      if (this.trigger) this.trigger.disabled = false;\n    }\n  }\n\n  get isOpened() {\n    return this.isOpen;\n  }\n\n  set isOpened(val) {\n    if (val) {\n      this.open();\n    } else {\n      this.close();\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-date-picker')) {\n  customElements.define('ren-date-picker', RenDatePicker);\n}\n",
      "path": "components/composites/ren-date-picker/ren-date-picker.js",
      "id": "file:components/composites/ren-date-picker/ren-date-picker.js",
      "type": "javascript",
      "name": "ren-date-picker.js"
    },
    {
      "data": {},
      "body": "/* ═══ REN DATE RANGE PICKER WEB COMPONENT ═══\n   A composable date range picker with dual calendars,\n   preset shortcuts, and an input trigger with popover dropdown.\n   Built on top of RenCalendar for consistent behavior.\n\n   Usage:\n     <ren-date-range-picker\n       placeholder=\"Select date range\"\n       format=\"short\"\n       locale=\"en-US\"\n       presets=\"last7,last30,thisMonth,lastMonth\"\n     ></ren-date-range-picker>\n   ══════════════════════════════════════════════════════════════════ */\n\n/* ═══ BASE WRAPPER ═══ */\n.ren-date-range-picker {\n  display: inline-flex;\n  flex-direction: column;\n  width: 100%;\n  max-width: 100%;\n  position: relative;\n}\n\n/* ═══ TRIGGER BUTTON ═══ */\n.ren-date-range-trigger {\n  anchor-name: --ren-date-range-anchor;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--space-2);\n  width: 100%;\n  padding: var(--space-2) var(--space-3);\n  background: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n  color: var(--color-text);\n  font-size: var(--body-size);\n  font-family: inherit;\n  cursor: pointer;\n  transition: var(--transition-tactile);\n  min-height: var(--touch-min);\n\n  &:hover:not([disabled]) {\n    border-color: var(--color-border-strong);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n    border-color: var(--color-accent);\n  }\n\n  &[disabled] {\n    background: var(--color-fill);\n    color: var(--color-text-muted);\n    cursor: not-allowed;\n    opacity: 0.6;\n  }\n\n  /* ═══ CALENDAR ICON ═══ */\n  &::after {\n    content: '📅';\n    font-size: 1.25em;\n    flex-shrink: 0;\n    pointer-events: none;\n  }\n}\n\n/* ═══ TRIGGER VALUE DISPLAY ═══ */\n.ren-date-range-value {\n  flex: 1;\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  overflow: hidden;\n  white-space: nowrap;\n}\n\n.ren-date-range-start,\n.ren-date-range-end {\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.ren-date-range-separator {\n  color: var(--color-text-muted);\n  flex-shrink: 0;\n}\n\n/* ═══ EMPTY STATE ═══ */\n.ren-date-range-trigger.ren-date-range-empty {\n  color: var(--color-text-muted);\n\n  .ren-date-range-value {\n    display: none;\n  }\n\n  &::before {\n    content: attr(data-placeholder);\n    color: var(--color-text-muted);\n    flex: 1;\n  }\n}\n\n/* ═══ DROPDOWN CONTAINER (POPOVER) ═══ */\n.ren-date-range-dropdown {\n  position-anchor: --ren-date-range-anchor;\n  position: absolute;\n  position-area: bottom span-all;\n  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;\n  inset: auto;\n  margin: 0;\n  background: var(--color-surface-raised);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  z-index: 1000;\n\n  /* ═══ POPOVER API ═══ */\n  &[popover] {\n    padding: 0;\n    margin: 0;\n    border: none;\n    background: transparent;\n    box-shadow: none;\n  }\n\n  &:popover-open {\n    animation: ren-date-range-popup-in var(--duration-enter) var(--ease-enter) both;\n  }\n}\n\n.ren-date-range-dropdown[data-side=\"bottom\"],\n.ren-date-range-dropdown:not([data-side]) {\n  position-area: bottom span-all;\n  margin-block-start: var(--space-2);\n  margin-block-end: 0;\n  margin-inline: 0;\n}\n\n.ren-date-range-dropdown[data-side=\"top\"] {\n  position-area: top span-all;\n  margin-block-start: 0;\n  margin-block-end: var(--space-2);\n  margin-inline: 0;\n}\n\n.ren-date-range-dropdown[data-side=\"right\"] {\n  position-area: right span-all;\n  margin-block: 0;\n  margin-inline-start: var(--space-2);\n  margin-inline-end: 0;\n}\n\n.ren-date-range-dropdown[data-side=\"left\"] {\n  position-area: left span-all;\n  margin-block: 0;\n  margin-inline-start: 0;\n  margin-inline-end: var(--space-2);\n}\n\n/* ═══ FALLBACK POSITIONING (no complete anchor support) ═══ */\n@supports not ((anchor-name: --ren-anchor) and\n  (position-anchor: --ren-anchor) and\n  (position-area: bottom span-all)) {\n  .ren-date-range-dropdown {\n    position: fixed;\n    top: auto;\n    left: auto;\n  }\n}\n\n/* ═══ ANIMATION: POPUP IN ═══ */\n@starting-style {\n  .ren-date-range-dropdown:popover-open {\n    opacity: 0;\n    translate: 0 -8px;\n  }\n}\n\n@keyframes ren-date-range-popup-in {\n  from {\n    opacity: 0;\n    translate: 0 -8px;\n  }\n\n  to {\n    opacity: 1;\n    translate: 0 0;\n  }\n}\n\n/* ═══ DROPDOWN INNER WRAPPER ═══ */\n.ren-date-range-inner {\n  container-type: inline-size;\n  container-name: ren-date-range;\n  background: var(--color-surface-raised);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  overflow: hidden;\n}\n\n/* ═══ PRESETS SIDEBAR ═══ */\n.ren-date-range-presets {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-1);\n  padding: var(--space-3);\n  border-bottom: 1px solid var(--color-border);\n  background: var(--color-surface);\n}\n\n.ren-date-range-preset {\n  display: block;\n  width: 100%;\n  padding: var(--space-1-5) var(--space-3);\n  background: transparent;\n  border: none;\n  border-radius: var(--radius-md);\n  color: var(--color-text);\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  text-align: start;\n  cursor: pointer;\n  transition: var(--transition-tactile);\n\n  &:hover {\n    background: var(--color-fill);\n  }\n\n  &:active {\n    background: var(--color-fill-active);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  &[aria-selected=\"true\"] {\n    background: var(--color-accent-subtle);\n    color: var(--color-accent);\n    font-weight: var(--weight-semibold);\n  }\n}\n\n/* ═══ DUAL CALENDAR LAYOUT ═══ */\n.ren-date-range-calendars {\n  display: flex;\n  gap: 0;\n  padding: var(--space-3);\n}\n\n.ren-date-range-calendars > .ren-calendar {\n  border: none;\n  box-shadow: none;\n  background: transparent;\n  flex: 1;\n}\n\n/* ═══ DIVIDER BETWEEN CALENDARS ═══ */\n.ren-date-range-divider {\n  width: 1px;\n  align-self: stretch;\n  background: var(--color-border);\n  margin-block: var(--space-2);\n  flex-shrink: 0;\n}\n\n/* ═══ FOOTER WITH ACTIONS ═══ */\n.ren-date-range-footer {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--space-3);\n  padding: var(--space-3);\n  border-top: 1px solid var(--color-border);\n  background: var(--color-surface);\n}\n\n.ren-date-range-summary {\n  font-size: var(--label-size);\n  color: var(--color-text-secondary);\n}\n\n.ren-date-range-summary strong {\n  color: var(--color-text);\n  font-weight: var(--weight-semibold);\n}\n\n.ren-date-range-actions {\n  display: flex;\n  gap: var(--space-2);\n}\n\n.ren-date-range-cancel,\n.ren-date-range-apply {\n  padding: var(--space-1-5) var(--space-3);\n  border-radius: var(--radius-md);\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  cursor: pointer;\n  transition: var(--transition-tactile);\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n}\n\n.ren-date-range-cancel {\n  background: transparent;\n  border: 1px solid var(--color-border);\n  color: var(--color-text);\n\n  &:hover {\n    background: var(--color-fill);\n    border-color: var(--color-border-strong);\n  }\n}\n\n.ren-date-range-apply {\n  background: var(--color-accent);\n  border: 1px solid var(--color-accent);\n  color: var(--color-on-accent);\n\n  &:hover {\n    background: var(--color-accent-hover);\n  }\n\n  &[disabled] {\n    opacity: 0.5;\n    cursor: not-allowed;\n  }\n}\n\n/* ═══ DISABLED STATE ═══ */\n.ren-date-range-picker[disabled] {\n  opacity: 0.6;\n  pointer-events: none;\n}\n\n/* ═══ ERROR STATE ═══ */\n.ren-date-range-picker.ren-date-range-error {\n  .ren-date-range-trigger {\n    border-color: var(--color-danger);\n\n    &:focus-visible {\n      outline-color: var(--color-danger);\n    }\n  }\n}\n\n/* ═══ SUCCESS STATE ═══ */\n.ren-date-range-picker.ren-date-range-success {\n  .ren-date-range-trigger {\n    border-color: var(--color-success);\n\n    &:focus-visible {\n      outline-color: var(--color-success);\n    }\n  }\n}\n\n/* ═══ REDUCED MOTION ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-date-range-dropdown:popover-open {\n    animation: none;\n    opacity: 1;\n    translate: 0 0;\n  }\n\n  .ren-date-range-dropdown {\n    transition: none;\n  }\n}\n\n/* ═══ COMPACT LAYOUT (Container Query) ═══\n   Stacks calendars vertically and makes presets\n   horizontal when the dropdown is narrow —\n   works in sidebars, modals, or small viewports. */\n@container ren-date-range (max-width: 500px) {\n  .ren-date-range-calendars {\n    flex-direction: column;\n    gap: var(--space-2);\n  }\n\n  .ren-date-range-divider {\n    width: 100%;\n    height: 1px;\n    margin-inline: var(--space-2);\n    margin-block: 0;\n  }\n\n  .ren-date-range-presets {\n    flex-direction: row;\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n    gap: var(--space-2);\n    padding: var(--space-2) var(--space-3);\n  }\n\n  .ren-date-range-preset {\n    white-space: nowrap;\n    flex-shrink: 0;\n  }\n}\n\n/* ═══ MOBILE BOTTOM SHEET (Media Query) ═══\n   Fixed positioning requires viewport awareness,\n   so this stays as a media query. */\n@media (max-width: 680px) {\n  .ren-date-range-dropdown {\n    position: fixed !important;\n    inset: auto 0 0 0 !important;\n    max-width: none;\n    border-radius: var(--radius-lg) var(--radius-lg) 0 0;\n    max-height: 85vh;\n    overflow-y: auto;\n\n    &:popover-open {\n      animation: ren-date-range-mobile-in var(--duration-enter) var(--ease-enter) both;\n    }\n  }\n\n  @starting-style {\n    .ren-date-range-dropdown:popover-open {\n      translate: 0 100%;\n      opacity: 0;\n    }\n  }\n\n}\n\n@keyframes ren-date-range-mobile-in {\n  from {\n    translate: 0 100%;\n    opacity: 0;\n  }\n\n  to {\n    translate: 0 0;\n    opacity: 1;\n  }\n}\n\n/* ═══ FORM INTEGRATION ═══ */\n.ren-date-range-picker {\n  input[type='hidden'] {\n    display: none;\n  }\n}\n",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "file:components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "type": "css",
      "name": "ren-date-range-picker.css"
    },
    {
      "data": {},
      "body": "/* ═══ REN DATE RANGE PICKER WEB COMPONENT ═══\n   A composable date range picker with dual side-by-side calendars,\n   preset shortcuts, and confirmation actions.\n\n   Features:\n   - Dual calendar view (current + next month)\n   - Configurable preset shortcuts (Last 7 days, Last 30 days, etc.)\n   - Apply / Cancel workflow for intentional selection\n   - Locale-aware date formatting via Intl.DateTimeFormat\n   - Popover API dropdown with CSS anchor positioning\n   - Full keyboard navigation (delegates to RenCalendar)\n   - Focus management and outside-click dismissal\n   - Form integration with hidden inputs (start + end)\n   - Min/max date constraints\n\n   Dependencies:\n   - RenCalendar (ren-calendar.js) must be registered\n\n   Usage:\n   <ren-date-range-picker\n     placeholder=\"Select date range\"\n     format=\"short\"\n     locale=\"en-US\"\n     presets=\"last7,last30,thisMonth,lastMonth,thisYear\"\n     min=\"2024-01-01\"\n     max=\"2027-12-31\"\n   ></ren-date-range-picker>\n\n   Events:\n   - ren-change: { value: { start, end }, formattedValue, preset? }\n   - ren-date-range-open\n   - ren-date-range-close\n   ══════════════════════════════════════════════════════════════════ */\n\n/* ═══ PRESET DEFINITIONS ═══ */\nconst PRESETS = {\n  today: {\n    label: 'Today',\n    getRange: () => {\n      const now = new Date();\n      const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n      return { start: today, end: today };\n    },\n  },\n  yesterday: {\n    label: 'Yesterday',\n    getRange: () => {\n      const now = new Date();\n      const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);\n      return { start: yesterday, end: yesterday };\n    },\n  },\n  last7: {\n    label: 'Last 7 days',\n    getRange: () => {\n      const now = new Date();\n      const end = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n      const start = new Date(end);\n      start.setDate(start.getDate() - 6);\n      return { start, end };\n    },\n  },\n  last14: {\n    label: 'Last 14 days',\n    getRange: () => {\n      const now = new Date();\n      const end = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n      const start = new Date(end);\n      start.setDate(start.getDate() - 13);\n      return { start, end };\n    },\n  },\n  last30: {\n    label: 'Last 30 days',\n    getRange: () => {\n      const now = new Date();\n      const end = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n      const start = new Date(end);\n      start.setDate(start.getDate() - 29);\n      return { start, end };\n    },\n  },\n  last90: {\n    label: 'Last 90 days',\n    getRange: () => {\n      const now = new Date();\n      const end = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n      const start = new Date(end);\n      start.setDate(start.getDate() - 89);\n      return { start, end };\n    },\n  },\n  thisWeek: {\n    label: 'This week',\n    getRange: () => {\n      const now = new Date();\n      const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n      const dayOfWeek = today.getDay();\n      const start = new Date(today);\n      start.setDate(start.getDate() - dayOfWeek);\n      const end = new Date(start);\n      end.setDate(end.getDate() + 6);\n      return { start, end };\n    },\n  },\n  lastWeek: {\n    label: 'Last week',\n    getRange: () => {\n      const now = new Date();\n      const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n      const dayOfWeek = today.getDay();\n      const end = new Date(today);\n      end.setDate(end.getDate() - dayOfWeek - 1);\n      const start = new Date(end);\n      start.setDate(start.getDate() - 6);\n      return { start, end };\n    },\n  },\n  thisMonth: {\n    label: 'This month',\n    getRange: () => {\n      const now = new Date();\n      const start = new Date(now.getFullYear(), now.getMonth(), 1);\n      const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);\n      return { start, end };\n    },\n  },\n  lastMonth: {\n    label: 'Last month',\n    getRange: () => {\n      const now = new Date();\n      const start = new Date(now.getFullYear(), now.getMonth() - 1, 1);\n      const end = new Date(now.getFullYear(), now.getMonth(), 0);\n      return { start, end };\n    },\n  },\n  thisQuarter: {\n    label: 'This quarter',\n    getRange: () => {\n      const now = new Date();\n      const quarterStart = Math.floor(now.getMonth() / 3) * 3;\n      const start = new Date(now.getFullYear(), quarterStart, 1);\n      const end = new Date(now.getFullYear(), quarterStart + 3, 0);\n      return { start, end };\n    },\n  },\n  thisYear: {\n    label: 'This year',\n    getRange: () => {\n      const now = new Date();\n      const start = new Date(now.getFullYear(), 0, 1);\n      const end = new Date(now.getFullYear(), 11, 31);\n      return { start, end };\n    },\n  },\n  lastYear: {\n    label: 'Last year',\n    getRange: () => {\n      const now = new Date();\n      const start = new Date(now.getFullYear() - 1, 0, 1);\n      const end = new Date(now.getFullYear() - 1, 11, 31);\n      return { start, end };\n    },\n  },\n};\n\nconst DATE_RANGE_SIDES = new Set(['top', 'right', 'bottom', 'left']);\n\nfunction normalizeDateRangeSide(value) {\n  const side = String(value || 'bottom').toLowerCase().split('-')[0];\n\n  return DATE_RANGE_SIDES.has(side) ? side : 'bottom';\n}\n\nexport class RenDateRangePicker extends HTMLElement {\n  static observedAttributes = ['placement'];\n\n  constructor() {\n    super();\n\n    /* ═══ STATE ═══ */\n    this.calendarLeft = null;\n    this.calendarRight = null;\n    this.dropdown = null;\n    this.trigger = null;\n    this.isOpen = false;\n\n    /* ═══ CONFIRMED (applied) range ═══ */\n    this.confirmedStart = null;\n    this.confirmedEnd = null;\n\n    /* ═══ DRAFT (in-progress) range ═══ */\n    this.draftStart = null;\n    this.draftEnd = null;\n    this.activePreset = null;\n\n    /* ═══ CONFIGURATION ═══ */\n    this.format = 'short';\n    this.locale = 'en-US';\n    this.placeholder = 'Select date range';\n    this.presetKeys = ['last7', 'last30', 'thisMonth', 'lastMonth'];\n    this.minDate = null;\n    this.maxDate = null;\n\n    /* ═══ CALENDAR MONTH OFFSETS ═══ */\n    this.leftMonth = new Date();\n    this.rightMonth = new Date(\n      this.leftMonth.getFullYear(),\n      this.leftMonth.getMonth() + 1\n    );\n\n    /* ═══ BIND METHODS ═══ */\n    this.handleTriggerClick = this.handleTriggerClick.bind(this);\n    this.handleTriggerKeyDown = this.handleTriggerKeyDown.bind(this);\n    this.handleDocumentClick = this.handleDocumentClick.bind(this);\n    this.handleCalendarSelect = this.handleCalendarSelect.bind(this);\n    this.handleApply = this.handleApply.bind(this);\n    this.handleCancel = this.handleCancel.bind(this);\n  }\n\n  connectedCallback() {\n    /* ═══ READ ATTRIBUTES ═══ */\n    this.format = this.getAttribute('format') || 'short';\n    this.locale = this.getAttribute('locale') || 'en-US';\n    this.placeholder = this.getAttribute('placeholder') || 'Select date range';\n\n    const presetsAttr = this.getAttribute('presets');\n    if (presetsAttr) {\n      this.presetKeys = presetsAttr.split(',').map((k) => k.trim());\n    }\n\n    const minAttr = this.getAttribute('min');\n    if (minAttr) this.minDate = minAttr;\n\n    const maxAttr = this.getAttribute('max');\n    if (maxAttr) this.maxDate = maxAttr;\n\n    /* ═══ PARSE INITIAL VALUE ═══ */\n    const startAttr = this.getAttribute('start');\n    const endAttr = this.getAttribute('end');\n    if (startAttr && endAttr) {\n      this.confirmedStart = this.parseDate(startAttr);\n      this.confirmedEnd = this.parseDate(endAttr);\n      this.draftStart = this.parseDate(startAttr);\n      this.draftEnd = this.parseDate(endAttr);\n      this.leftMonth = new Date(this.confirmedStart.getFullYear(), this.confirmedStart.getMonth());\n      this.rightMonth = new Date(this.leftMonth.getFullYear(), this.leftMonth.getMonth() + 1);\n    }\n\n    /* ═══ RENDER ═══ */\n    this.render();\n    this.setupEventListeners();\n    this.addHiddenInputs();\n    this.updateTriggerDisplay();\n  }\n\n  disconnectedCallback() {\n    if (this.trigger) {\n      this.trigger.removeEventListener('click', this.handleTriggerClick);\n      this.trigger.removeEventListener('keydown', this.handleTriggerKeyDown);\n    }\n\n    document.removeEventListener('click', this.handleDocumentClick);\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'placement' && oldValue !== newValue) {\n      this.syncPlacement();\n    }\n  }\n\n  /* ═══ RENDER FULL COMPONENT ═══ */\n  render() {\n    this.innerHTML = '';\n    this.classList.add('ren-date-range-picker');\n\n    /* ═══ TRIGGER BUTTON ═══ */\n    const trigger = document.createElement('button');\n    trigger.className = 'ren-date-range-trigger ren-date-range-empty';\n    trigger.setAttribute('type', 'button');\n    trigger.setAttribute('aria-haspopup', 'dialog');\n    trigger.setAttribute('aria-expanded', 'false');\n    trigger.setAttribute('data-placeholder', this.placeholder);\n\n    const valueEl = document.createElement('span');\n    valueEl.className = 'ren-date-range-value';\n\n    const startEl = document.createElement('span');\n    startEl.className = 'ren-date-range-start';\n\n    const sepEl = document.createElement('span');\n    sepEl.className = 'ren-date-range-separator';\n    sepEl.textContent = '→';\n\n    const endEl = document.createElement('span');\n    endEl.className = 'ren-date-range-end';\n\n    valueEl.appendChild(startEl);\n    valueEl.appendChild(sepEl);\n    valueEl.appendChild(endEl);\n    trigger.appendChild(valueEl);\n    this.appendChild(trigger);\n    this.trigger = trigger;\n\n    /* ═══ DROPDOWN (POPOVER) ═══ */\n    const dropdown = document.createElement('div');\n    dropdown.className = 'ren-date-range-dropdown';\n    dropdown.setAttribute('popover', 'manual');\n    dropdown.setAttribute('role', 'dialog');\n    dropdown.setAttribute('aria-label', 'Date range selection');\n\n    const inner = document.createElement('div');\n    inner.className = 'ren-date-range-inner';\n\n    /* ═══ PRESETS ═══ */\n    if (this.presetKeys.length > 0) {\n      const presetsEl = document.createElement('div');\n      presetsEl.className = 'ren-date-range-presets';\n      presetsEl.setAttribute('role', 'listbox');\n      presetsEl.setAttribute('aria-label', 'Date range presets');\n\n      this.presetKeys.forEach((key) => {\n        const preset = PRESETS[key];\n        if (!preset) return;\n\n        const btn = document.createElement('button');\n        btn.className = 'ren-date-range-preset';\n        btn.setAttribute('type', 'button');\n        btn.setAttribute('role', 'option');\n        btn.setAttribute('data-preset', key);\n        btn.textContent = preset.label;\n\n        btn.addEventListener('click', () => this.applyPreset(key));\n        presetsEl.appendChild(btn);\n      });\n\n      inner.appendChild(presetsEl);\n    }\n\n    /* ═══ DUAL CALENDARS ═══ */\n    const calendarsEl = document.createElement('div');\n    calendarsEl.className = 'ren-date-range-calendars';\n\n    const calLeft = document.createElement('ren-calendar');\n    calLeft.setAttribute('mode', 'range');\n    calLeft.setAttribute('locale', this.locale);\n    if (this.minDate) calLeft.setAttribute('min', this.minDate);\n    if (this.maxDate) calLeft.setAttribute('max', this.maxDate);\n\n    const divider = document.createElement('div');\n    divider.className = 'ren-date-range-divider';\n    divider.setAttribute('aria-hidden', 'true');\n\n    const calRight = document.createElement('ren-calendar');\n    calRight.setAttribute('mode', 'range');\n    calRight.setAttribute('locale', this.locale);\n    if (this.minDate) calRight.setAttribute('min', this.minDate);\n    if (this.maxDate) calRight.setAttribute('max', this.maxDate);\n\n    calendarsEl.appendChild(calLeft);\n    calendarsEl.appendChild(divider);\n    calendarsEl.appendChild(calRight);\n    inner.appendChild(calendarsEl);\n\n    /* ═══ FOOTER WITH SUMMARY + ACTIONS ═══ */\n    const footer = document.createElement('div');\n    footer.className = 'ren-date-range-footer';\n\n    const summary = document.createElement('div');\n    summary.className = 'ren-date-range-summary';\n    summary.setAttribute('aria-live', 'polite');\n\n    const actions = document.createElement('div');\n    actions.className = 'ren-date-range-actions';\n\n    const cancelBtn = document.createElement('button');\n    cancelBtn.className = 'ren-date-range-cancel';\n    cancelBtn.setAttribute('type', 'button');\n    cancelBtn.textContent = 'Cancel';\n\n    const applyBtn = document.createElement('button');\n    applyBtn.className = 'ren-date-range-apply';\n    applyBtn.setAttribute('type', 'button');\n    applyBtn.textContent = 'Apply';\n    applyBtn.disabled = true;\n\n    actions.appendChild(cancelBtn);\n    actions.appendChild(applyBtn);\n    footer.appendChild(summary);\n    footer.appendChild(actions);\n    inner.appendChild(footer);\n\n    dropdown.appendChild(inner);\n    this.appendChild(dropdown);\n    this.dropdown = dropdown;\n\n    /* ═══ STORE REFS ═══ */\n    this.calendarLeft = calLeft;\n    this.calendarRight = calRight;\n    this.summaryEl = summary;\n    this.applyBtn = applyBtn;\n    this.cancelBtn = cancelBtn;\n\n    this.syncPlacement();\n  }\n\n  /* ═══ MIRROR PLACEMENT TO PUBLIC DATA ATTRIBUTES ═══ */\n  syncPlacement() {\n    const side = normalizeDateRangeSide(this.getAttribute('placement'));\n\n    this.setAttribute('data-side', side);\n    if (this.dropdown) {\n      this.dropdown.setAttribute('data-side', side);\n    }\n  }\n\n  /* ═══ SET UP EVENT LISTENERS ═══ */\n  setupEventListeners() {\n    this.trigger.addEventListener('click', this.handleTriggerClick);\n    this.trigger.addEventListener('keydown', this.handleTriggerKeyDown);\n    document.addEventListener('click', this.handleDocumentClick);\n\n    /* ═══ CALENDAR SELECT EVENTS ═══ */\n    this.calendarLeft.addEventListener('ren-date-select', this.handleCalendarSelect);\n    this.calendarRight.addEventListener('ren-date-select', this.handleCalendarSelect);\n\n    /* ═══ FOOTER ACTIONS ═══ */\n    this.applyBtn.addEventListener('click', this.handleApply);\n    this.cancelBtn.addEventListener('click', this.handleCancel);\n\n    /* ═══ SYNC CALENDAR MONTHS ═══ */\n    this.syncCalendarMonths();\n  }\n\n  /* ═══ SYNC LEFT/RIGHT CALENDAR MONTHS ═══ */\n  syncCalendarMonths() {\n    if (this.calendarLeft && this.calendarLeft.goToMonth) {\n      this.calendarLeft.goToMonth(\n        this.leftMonth.getFullYear(),\n        this.leftMonth.getMonth()\n      );\n    }\n\n    if (this.calendarRight && this.calendarRight.goToMonth) {\n      this.calendarRight.goToMonth(\n        this.rightMonth.getFullYear(),\n        this.rightMonth.getMonth()\n      );\n    }\n\n    /* ═══ APPLY DRAFT RANGE TO BOTH CALENDARS ═══ */\n    if (this.draftStart && this.draftEnd) {\n      const startStr = this.dateToString(this.draftStart);\n      const endStr = this.dateToString(this.draftEnd);\n\n      if (this.calendarLeft.setRange) {\n        this.calendarLeft.setRange(startStr, endStr);\n      }\n      if (this.calendarRight.setRange) {\n        this.calendarRight.setRange(startStr, endStr);\n      }\n    }\n  }\n\n  /* ═══ ADD HIDDEN INPUTS FOR FORM SUBMISSION ═══ */\n  addHiddenInputs() {\n    const name = this.getAttribute('name') || 'date-range';\n\n    let hiddenStart = this.querySelector('input[name$=\"-start\"]');\n    if (!hiddenStart) {\n      hiddenStart = document.createElement('input');\n      hiddenStart.type = 'hidden';\n      hiddenStart.name = `${name}-start`;\n      this.appendChild(hiddenStart);\n    }\n\n    let hiddenEnd = this.querySelector('input[name$=\"-end\"]');\n    if (!hiddenEnd) {\n      hiddenEnd = document.createElement('input');\n      hiddenEnd.type = 'hidden';\n      hiddenEnd.name = `${name}-end`;\n      this.appendChild(hiddenEnd);\n    }\n\n    this.hiddenStart = hiddenStart;\n    this.hiddenEnd = hiddenEnd;\n  }\n\n  /* ═══ HANDLE TRIGGER CLICK ═══ */\n  handleTriggerClick(event) {\n    event.preventDefault();\n    event.stopPropagation();\n    if (this.hasAttribute('disabled')) return;\n    this.isOpen ? this.close() : this.open();\n  }\n\n  /* ═══ HANDLE TRIGGER KEYBOARD ═══ */\n  handleTriggerKeyDown(event) {\n    if (this.hasAttribute('disabled')) return;\n\n    if (event.key === 'Enter' || event.key === ' ') {\n      event.preventDefault();\n      this.open();\n    }\n\n    if (event.key === 'Escape') {\n      this.close();\n    }\n  }\n\n  /* ═══ HANDLE OUTSIDE CLICK ═══ */\n  handleDocumentClick(event) {\n    if (!this.contains(event.target) && this.isOpen) {\n      this.handleCancel();\n    }\n  }\n\n  /* ═══ HANDLE CALENDAR DATE SELECT ═══ */\n  handleCalendarSelect(event) {\n    const detail = event.detail;\n\n    if (detail.range) {\n      /* ═══ UPDATE DRAFT RANGE ═══ */\n      if (detail.range.start) {\n        this.draftStart = detail.range.start;\n      }\n      if (detail.range.end) {\n        this.draftEnd = detail.range.end;\n      }\n\n      /* ═══ ENSURE START < END ═══ */\n      if (this.draftStart && this.draftEnd && this.draftStart > this.draftEnd) {\n        const tmp = this.draftStart;\n        this.draftStart = this.draftEnd;\n        this.draftEnd = tmp;\n      }\n\n      /* ═══ SYNC RANGE ACROSS BOTH CALENDARS ═══ */\n      if (this.draftStart && this.draftEnd) {\n        const startStr = this.dateToString(this.draftStart);\n        const endStr = this.dateToString(this.draftEnd);\n\n        [this.calendarLeft, this.calendarRight].forEach((cal) => {\n          if (cal !== event.target && cal.setRange) {\n            cal.setRange(startStr, endStr);\n          }\n        });\n      }\n\n      /* ═══ CLEAR ACTIVE PRESET ═══ */\n      this.activePreset = null;\n      this.updatePresetHighlight();\n\n      /* ═══ UPDATE SUMMARY + APPLY BUTTON ═══ */\n      this.updateSummary();\n      this.updateApplyState();\n    }\n  }\n\n  /* ═══ APPLY PRESET ═══ */\n  applyPreset(key) {\n    const preset = PRESETS[key];\n    if (!preset) return;\n\n    const range = preset.getRange();\n    this.draftStart = range.start;\n    this.draftEnd = range.end;\n    this.activePreset = key;\n\n    /* ═══ NAVIGATE CALENDARS TO SHOW THE RANGE ═══ */\n    this.leftMonth = new Date(range.start.getFullYear(), range.start.getMonth());\n    this.rightMonth = new Date(this.leftMonth.getFullYear(), this.leftMonth.getMonth() + 1);\n\n    /* ═══ IF END IS IN A DIFFERENT MONTH, ADJUST ═══ */\n    if (range.end.getMonth() !== range.start.getMonth() || range.end.getFullYear() !== range.start.getFullYear()) {\n      this.rightMonth = new Date(range.end.getFullYear(), range.end.getMonth());\n      /* ═══ ENSURE LEFT IS BEFORE RIGHT ═══ */\n      if (this.rightMonth <= this.leftMonth) {\n        this.leftMonth = new Date(this.rightMonth.getFullYear(), this.rightMonth.getMonth() - 1);\n      }\n    }\n\n    this.syncCalendarMonths();\n    this.updatePresetHighlight();\n    this.updateSummary();\n    this.updateApplyState();\n  }\n\n  /* ═══ UPDATE PRESET BUTTON HIGHLIGHT ═══ */\n  updatePresetHighlight() {\n    const presetBtns = this.querySelectorAll('.ren-date-range-preset');\n    presetBtns.forEach((btn) => {\n      const key = btn.getAttribute('data-preset');\n      if (key === this.activePreset) {\n        btn.setAttribute('aria-selected', 'true');\n      } else {\n        btn.removeAttribute('aria-selected');\n      }\n    });\n  }\n\n  /* ═══ UPDATE FOOTER SUMMARY ═══ */\n  updateSummary() {\n    if (!this.summaryEl) return;\n\n    if (this.draftStart && this.draftEnd) {\n      const start = this.formatDate(this.draftStart);\n      const end = this.formatDate(this.draftEnd);\n      const days = this.daysBetween(this.draftStart, this.draftEnd);\n      this.summaryEl.innerHTML = `<strong>${start}</strong> → <strong>${end}</strong> (${days} day${days !== 1 ? 's' : ''})`;\n    } else if (this.draftStart) {\n      this.summaryEl.innerHTML = `<strong>${this.formatDate(this.draftStart)}</strong> → ...`;\n    } else {\n      this.summaryEl.textContent = 'Select a start date';\n    }\n  }\n\n  /* ═══ UPDATE APPLY BUTTON STATE ═══ */\n  updateApplyState() {\n    if (this.applyBtn) {\n      this.applyBtn.disabled = !(this.draftStart && this.draftEnd);\n    }\n  }\n\n  /* ═══ HANDLE APPLY ═══ */\n  handleApply() {\n    if (!this.draftStart || !this.draftEnd) return;\n\n    this.confirmedStart = new Date(this.draftStart);\n    this.confirmedEnd = new Date(this.draftEnd);\n\n    this.updateTriggerDisplay();\n    this.updateHiddenInputs();\n    this.dispatchChangeEvent();\n    this.close();\n  }\n\n  /* ═══ HANDLE CANCEL ═══ */\n  handleCancel() {\n    /* ═══ REVERT DRAFT TO CONFIRMED ═══ */\n    this.draftStart = this.confirmedStart ? new Date(this.confirmedStart) : null;\n    this.draftEnd = this.confirmedEnd ? new Date(this.confirmedEnd) : null;\n    this.activePreset = null;\n    this.close();\n  }\n\n  /* ═══ OPEN DROPDOWN ═══ */\n  open() {\n    if (this.isOpen || this.hasAttribute('disabled')) return;\n    if (!this.dropdown) return;\n\n    /* ═══ RESET DRAFT TO CONFIRMED ═══ */\n    this.draftStart = this.confirmedStart ? new Date(this.confirmedStart) : null;\n    this.draftEnd = this.confirmedEnd ? new Date(this.confirmedEnd) : null;\n\n    /* ═══ SET CALENDAR MONTHS ═══ */\n    if (this.confirmedStart) {\n      this.leftMonth = new Date(this.confirmedStart.getFullYear(), this.confirmedStart.getMonth());\n      this.rightMonth = new Date(this.leftMonth.getFullYear(), this.leftMonth.getMonth() + 1);\n    }\n\n    this.syncPlacement();\n\n    try {\n      this.dropdown.showPopover();\n    } catch {\n      this.dropdown.style.display = 'block';\n    }\n\n    this.isOpen = true;\n\n    if (this.trigger) {\n      this.trigger.setAttribute('aria-expanded', 'true');\n    }\n\n    this.syncCalendarMonths();\n    this.updateSummary();\n    this.updateApplyState();\n    this.updatePresetHighlight();\n\n    /* ═══ FOCUS FIRST PRESET OR CALENDAR ═══ */\n    setTimeout(() => {\n      const firstPreset = this.querySelector('.ren-date-range-preset');\n      if (firstPreset) {\n        firstPreset.focus();\n      } else {\n        const firstDay = this.calendarLeft?.querySelector('.ren-calendar-day:not([disabled])');\n        if (firstDay) firstDay.focus();\n      }\n    }, 100);\n\n    this.dispatchEvent(\n      new CustomEvent('ren-date-range-open', {\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ CLOSE DROPDOWN ═══ */\n  close() {\n    if (!this.isOpen) return;\n    if (!this.dropdown) return;\n\n    try {\n      this.dropdown.hidePopover();\n    } catch {\n      this.dropdown.style.display = 'none';\n    }\n\n    this.isOpen = false;\n\n    if (this.trigger) {\n      this.trigger.setAttribute('aria-expanded', 'false');\n      this.trigger.focus();\n    }\n\n    this.dispatchEvent(\n      new CustomEvent('ren-date-range-close', {\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ UPDATE TRIGGER DISPLAY ═══ */\n  updateTriggerDisplay() {\n    if (!this.trigger) return;\n\n    if (this.confirmedStart && this.confirmedEnd) {\n      const startEl = this.trigger.querySelector('.ren-date-range-start');\n      const endEl = this.trigger.querySelector('.ren-date-range-end');\n\n      if (startEl) startEl.textContent = this.formatDate(this.confirmedStart);\n      if (endEl) endEl.textContent = this.formatDate(this.confirmedEnd);\n\n      this.trigger.classList.remove('ren-date-range-empty');\n    } else {\n      this.trigger.classList.add('ren-date-range-empty');\n    }\n  }\n\n  /* ═══ UPDATE HIDDEN INPUTS ═══ */\n  updateHiddenInputs() {\n    if (this.hiddenStart) {\n      this.hiddenStart.value = this.confirmedStart ? this.dateToString(this.confirmedStart) : '';\n    }\n    if (this.hiddenEnd) {\n      this.hiddenEnd.value = this.confirmedEnd ? this.dateToString(this.confirmedEnd) : '';\n    }\n  }\n\n  /* ═══ DISPATCH CHANGE EVENT ═══ */\n  dispatchChangeEvent() {\n    const value =\n      this.confirmedStart && this.confirmedEnd\n        ? {\n            start: this.dateToString(this.confirmedStart),\n            end: this.dateToString(this.confirmedEnd),\n          }\n        : null;\n\n    const formattedValue =\n      this.confirmedStart && this.confirmedEnd\n        ? `${this.formatDate(this.confirmedStart)} → ${this.formatDate(this.confirmedEnd)}`\n        : '';\n\n    this.dispatchEvent(\n      new CustomEvent('ren-change', {\n        detail: {\n          value,\n          formattedValue,\n          preset: this.activePreset || null,\n          days: this.confirmedStart && this.confirmedEnd\n            ? this.daysBetween(this.confirmedStart, this.confirmedEnd)\n            : 0,\n        },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ FORMAT DATE ═══ */\n  formatDate(date) {\n    if (!date) return '';\n    const formatter = new Intl.DateTimeFormat(this.locale, {\n      year: 'numeric',\n      month: this.format === 'short' ? '2-digit' : this.format === 'long' ? 'long' : 'short',\n      day: '2-digit',\n    });\n    return formatter.format(date);\n  }\n\n  /* ═══ DATE TO ISO STRING ═══ */\n  dateToString(date) {\n    const year = date.getFullYear();\n    const month = String(date.getMonth() + 1).padStart(2, '0');\n    const day = String(date.getDate()).padStart(2, '0');\n    return `${year}-${month}-${day}`;\n  }\n\n  parseDate(value) {\n    if (value instanceof Date) return new Date(value.getTime());\n    const match = String(value).match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n    return match ? new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3])) : new Date(value);\n  }\n\n  /* ═══ DAYS BETWEEN TWO DATES ═══ */\n  daysBetween(start, end) {\n    const MS_PER_DAY = 1000 * 60 * 60 * 24;\n    const diff = Math.abs(end.getTime() - start.getTime());\n    return Math.round(diff / MS_PER_DAY) + 1;\n  }\n\n  /* ═══ PUBLIC API ═══ */\n\n  /* ═══ GET VALUE ═══ */\n  getValue() {\n    if (!this.confirmedStart || !this.confirmedEnd) return null;\n    return {\n      start: this.dateToString(this.confirmedStart),\n      end: this.dateToString(this.confirmedEnd),\n    };\n  }\n\n  /* ═══ SET VALUE ═══ */\n  setValue(start, end) {\n    this.confirmedStart = this.parseDate(start);\n    this.confirmedEnd = this.parseDate(end);\n    this.draftStart = this.parseDate(start);\n    this.draftEnd = this.parseDate(end);\n    this.updateTriggerDisplay();\n    this.updateHiddenInputs();\n  }\n\n  /* ═══ CLEAR VALUE ═══ */\n  clear() {\n    this.confirmedStart = null;\n    this.confirmedEnd = null;\n    this.draftStart = null;\n    this.draftEnd = null;\n    this.activePreset = null;\n    this.updateTriggerDisplay();\n    this.updateHiddenInputs();\n  }\n\n  /* ═══ GETTERS/SETTERS ═══ */\n  get disabled() {\n    return this.hasAttribute('disabled');\n  }\n\n  set disabled(val) {\n    if (val) {\n      this.setAttribute('disabled', '');\n      if (this.trigger) this.trigger.disabled = true;\n    } else {\n      this.removeAttribute('disabled');\n      if (this.trigger) this.trigger.disabled = false;\n    }\n  }\n\n  get value() {\n    return this.getValue();\n  }\n\n  set value(val) {\n    if (val && val.start && val.end) {\n      this.setValue(val.start, val.end);\n    } else {\n      this.clear();\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-date-range-picker')) {\n  customElements.define('ren-date-range-picker', RenDateRangePicker);\n}\n",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.js",
      "id": "file:components/composites/ren-date-range-picker/ren-date-range-picker.js",
      "type": "javascript",
      "name": "ren-date-range-picker.js"
    },
    {
      "data": {},
      "body": "/* ═══════════════════════════════════════════════════════════════════════════\n   REN DIALOG / MODAL COMPONENT\n   Native <dialog> element enhancement with CSS animations and modern APIs\n   Uses @starting-style, transition-behavior: allow-discrete, and CSS nesting\n   ═══════════════════════════════════════════════════════════════════════════ */\n\n/* ═══ DIALOG CONTAINER ═══ */\n.ren-dialog {\n  /* Structure */\n  margin: auto;\n  padding: 0;\n  border: none;\n\n  /* Sizing */\n  max-width: var(--dialog-max-width, 32rem);\n  max-height: 85dvh;\n  width: 90vw;\n  overflow: auto;\n\n  /* Appearance */\n  background: var(--color-surface);\n  color: var(--color-text);\n  border-radius: var(--radius-xl);\n  box-shadow: var(--shadow-xl);\n\n  /* Smooth scrolling for body overflow */\n  scrollbar-gutter: stable;\n\n  /* ═══ ANIMATION STATES ═══\n     Uses semantic motion tokens so global motion tuning\n     cascades to every component. See tokens/semantic/motion.css. */\n  opacity: 1;\n  transform: scale(1) translateY(0);\n\n  transition:\n    opacity   var(--duration-enter) var(--ease-enter),\n    transform var(--duration-enter) var(--ease-enter),\n    overlay   var(--duration-enter) allow-discrete,\n    display   var(--duration-enter) allow-discrete;\n\n  /* ═══ ENTRY ANIMATION (CSS Transitions instead of keyframes) ═══ */\n  @starting-style {\n    opacity: 0;\n    transform: scale(0.95) translateY(8px);\n  }\n\n  /* ═══ BACKDROP STYLING ═══\n     Uses --transition-overlay so scrim fade/blur duration\n     is tuned in one place (tokens/semantic/motion.css). */\n  &::backdrop {\n    background-color: rgb(0, 0, 0, 0);\n    backdrop-filter: blur(0px);\n\n    transition: var(--transition-overlay);\n\n    /* ═══ BACKDROP ENTRY ANIMATION ═══ */\n    @starting-style {\n      background-color: rgb(0, 0, 0, 0);\n      backdrop-filter: blur(0px);\n    }\n  }\n\n  /* ═══ OPEN STATE ═══ */\n  &[open] {\n    opacity: 1;\n    transform: scale(1) translateY(0);\n\n    &::backdrop {\n      background-color: var(--color-overlay);\n      backdrop-filter: blur(4px);\n    }\n  }\n\n  /* ═══ REDUCED MOTION ═══ */\n  @media (prefers-reduced-motion: reduce) {\n    transition: none;\n\n    &::backdrop {\n      transition: none;\n    }\n  }\n}\n\n/* ═══ DIALOG HEADER ═══\n   No bottom border — separation from the body comes from spacing, not lines.\n   Header padding-bottom + body padding-top give the breathing room. */\n.ren-dialog-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--space-4);\n  padding: var(--space-6) var(--space-6) var(--space-3);\n  flex-shrink: 0;\n}\n\n/* ═══ DIALOG TITLE ═══ */\n.ren-dialog-title {\n  margin: 0;\n  font-size: 1.25rem; /* ~20px */\n  line-height: 1.5;\n  font-weight: 600;\n  color: var(--color-text);\n}\n\n/* ═══ DIALOG DESCRIPTION ═══ */\n.ren-dialog-description {\n  margin: 0;\n  margin-top: var(--space-2);\n  font-size: 0.875rem; /* caption */\n  line-height: 1.5;\n  color: var(--color-text-muted);\n}\n\n/* ═══ DIALOG BODY ═══ */\n.ren-dialog-body {\n  padding: var(--space-6);\n  overflow-y: auto;\n  flex: 1;\n  min-height: 0; /* Allow flex shrinking */\n\n  /* ═══ SCROLLBAR STYLING ═══ */\n  &::-webkit-scrollbar {\n    width: 8px;\n  }\n\n  &::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background: var(--color-border);\n    border-radius: 4px;\n\n    &:hover {\n      background: var(--color-border-hover);\n    }\n  }\n}\n\n/* ═══ DIALOG FOOTER ═══ */\n.ren-dialog-footer {\n  display: flex;\n  align-items: center;\n  justify-content: flex-end;\n  gap: var(--space-3);\n  padding: var(--space-6);\n  border-top: 1px solid var(--color-border);\n  flex-shrink: 0;\n}\n\n/* ═══ CLOSE BUTTON ═══ */\n.ren-dialog-close {\n  margin-inline-start: auto;\n  padding: var(--space-2);\n  background: transparent;\n  border: none;\n  cursor: pointer;\n  color: var(--color-text-muted);\n  border-radius: var(--radius-md);\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  line-height: 1;\n\n  /* Tactile transitions (hover / focus / press) */\n  transition: var(--transition-tactile);\n\n  &:hover {\n    background-color: var(--color-background-hover);\n    color: var(--color-text);\n  }\n\n  &:active {\n    background-color: var(--color-background-active);\n  }\n\n  &:focus {\n    outline: var(--ring-width) solid var(--color-focus-ring);\n    outline-offset: var(--ring-offset-width);\n  }\n\n  & svg {\n    width: 1.25rem;\n    height: 1.25rem;\n  }\n}\n\n/* ═══ SIZE VARIANTS ═══ */\n\n/* Small dialog (24rem) */\n.ren-dialog-sm {\n  --dialog-max-width: 24rem;\n}\n\n/* Medium dialog (32rem) - DEFAULT */\n.ren-dialog-md {\n  --dialog-max-width: 32rem;\n}\n\n/* Large dialog (42rem) */\n.ren-dialog-lg {\n  --dialog-max-width: 42rem;\n}\n\n/* Extra Large dialog (56rem) */\n.ren-dialog-xl {\n  --dialog-max-width: 56rem;\n}\n\n/* Full width dialog (95dvw) */\n.ren-dialog-full {\n  max-width: 95dvw;\n  max-height: 95dvh;\n  width: 95dvw;\n}\n\n/* ═══ ALERT DIALOG VARIANT ═══ */\n.ren-alert-dialog {\n  /* No additional styling needed - alert behavior handled in JS */\n}\n\n/* ═══ MOBILE RESPONSIVE ═══ */\n@media (max-width: 640px) {\n  .ren-dialog {\n    max-width: 100%;\n    max-height: 100dvh;\n    width: 100%;\n    border-radius: var(--radius-lg) var(--radius-lg) 0 0;\n\n    /* ═══ BOTTOM SHEET ANIMATION (Mobile) ═══ */\n    &[data-mobile-sheet] {\n      position: fixed;\n      bottom: 0;\n      left: 0;\n      right: 0;\n      margin: 0;\n      border-radius: var(--radius-lg) var(--radius-lg) 0 0;\n\n      transform: translateY(100%);\n      transition:\n        transform var(--duration-enter) var(--ease-enter),\n        opacity   var(--duration-enter) var(--ease-enter),\n        overlay   var(--duration-enter) allow-discrete,\n        display   var(--duration-enter) allow-discrete;\n\n      @starting-style {\n        transform: translateY(100%);\n      }\n\n      &[open] {\n        transform: translateY(0);\n      }\n    }\n  }\n\n  .ren-dialog-header,\n  .ren-dialog-body,\n  .ren-dialog-footer {\n    padding: var(--space-4);\n  }\n\n  .ren-dialog-title {\n    font-size: 1.125rem;\n  }\n}\n\n/* ═══ WEB COMPONENT CONTAINER ═══ */\nren-dialog {\n  display: contents;\n}\n\n.ren-dialog-wrapper {\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n}\n/* Appearance API bridge: public component tokens remain overridable at any scope. */\n.ren-dialog,\n.ren-alert-dialog {\n  width: var(--ren-dialog-width, min(90vw, 500px));\n  padding: var(--ren-dialog-padding, var(--space-6));\n  border-radius: var(--ren-dialog-radius, var(--radius-xl));\n  background: var(--ren-dialog-bg, var(--color-surface-overlay));\n  border-color: var(--ren-dialog-border-color, var(--color-border));\n  box-shadow: var(--ren-dialog-shadow, var(--shadow-xl));\n  gap: var(--ren-dialog-gap, var(--space-4));\n  transition-duration: var(--ren-dialog-duration, var(--duration-normal));\n  transition-timing-function: var(--ren-dialog-easing, var(--ease-out));\n}\n.ren-dialog::backdrop,\n.ren-alert-dialog::backdrop { background: var(--ren-dialog-backdrop, var(--color-overlay)); }\n",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "file:components/composites/ren-dialog/ren-dialog.css",
      "type": "css",
      "name": "ren-dialog.css"
    },
    {
      "data": {},
      "body": "import { createFocusTrap } from '../../../utils/focus-trap.js';\n\n/**\n * RenDialog - Modal/Dialog Web Component\n *\n * A minimal, modern dialog component built on the native <dialog> element.\n * Animations are handled entirely by CSS using @starting-style and transitions.\n *\n * Features:\n * - Native <dialog> element with `.ren-dialog` enhancement\n * - Auto-wiring of [data-dialog-trigger] and [data-dialog-close] buttons\n * - Progressive enhancement: [commandfor] and [command] attributes support\n * - Progressive enhancement: closedby attribute for modern browsers\n * - Focus trap management for accessibility\n * - Escape key support and click-outside dismissal via native dialog + simple click handler\n * - Size variants (sm, md, lg, xl, full)\n * - Mobile-responsive bottom sheet behavior\n * - Custom events: ren-open, ren-close\n *\n * @example\n * <ren-dialog size=\"md\">\n *   <dialog>\n *     <div class=\"ren-dialog-header\">\n *       <h2 class=\"ren-dialog-title\">Confirm Action</h2>\n *       <button class=\"ren-dialog-close\" data-dialog-close>&times;</button>\n *     </div>\n *     <div class=\"ren-dialog-body\">Are you sure?</div>\n *     <div class=\"ren-dialog-footer\">\n *       <button data-dialog-close>Cancel</button>\n *       <button>Confirm</button>\n *     </div>\n *   </dialog>\n * </ren-dialog>\n *\n * @example\n * <button data-dialog-trigger=\"my-dialog\">Open</button>\n * <ren-dialog id=\"my-dialog\">...</ren-dialog>\n *\n * @example\n * <button commandfor=\"my-dialog\" command=\"show-modal\">Open</button>\n * <ren-dialog id=\"my-dialog\">...</ren-dialog>\n */\nexport class RenDialog extends HTMLElement {\n  #dialogElement = null;\n  #focusTrap = null;\n  #abortController = null;\n  #returnFocus = null;\n  #inertElements = new WeakMap();\n  #closeReconciled = true;\n\n  static observedAttributes = ['open', 'alert', 'size'];\n\n  constructor() {\n    super();\n    this.#setupDialogElement();\n  }\n\n  connectedCallback() {\n    if (!this.#abortController || this.#abortController.signal.aborted) {\n      this.#abortController = new AbortController();\n    }\n    this.#initializeDialogElement();\n    this.#wireupTriggers();\n  }\n\n  disconnectedCallback() {\n    this.#abortController?.abort();\n    if (this.#dialogElement?.open) this.#setInert(false);\n    this.#teardownFocusTrap();\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (oldValue === newValue) return;\n\n    switch (name) {\n      case 'open':\n        newValue !== null ? this.show() : this.close();\n        break;\n      case 'size':\n        this.#updateSizeClass();\n        break;\n    }\n  }\n\n  /* ═══ PUBLIC API ═══ */\n\n  /**\n   * Show the dialog with modal behavior\n   * @public\n   */\n  show() {\n    if (!this.#dialogElement?.open) {\n      this.#returnFocus = document.activeElement;\n      this.#closeReconciled = false;\n      this.#dialogElement.showModal();\n      this.#setInert(true);\n      this.setAttribute('open', '');\n      this.setAttribute('data-state', 'open');\n      this.#setupFocusTrap();\n      this.#updateMobileSheet();\n      this.dispatchEvent(new CustomEvent('ren-open', { bubbles: true, composed: true }));\n    }\n  }\n\n  /**\n   * Alias for show()\n   * @public\n   */\n  open() {\n    this.show();\n  }\n\n  /**\n   * Close the dialog\n   * CSS transitions handle the exit animation automatically\n   *\n   * @public\n   * @param {string} [returnValue]\n   */\n  close(returnValue = '') {\n    if (this.#dialogElement?.open) {\n      this.#dialogElement.close(returnValue);\n    }\n  }\n\n  /**\n   * Check if dialog is open\n   * @public\n   * @returns {boolean}\n   */\n  get isOpen() {\n    return this.#dialogElement?.open ?? false;\n  }\n\n  /**\n   * Get the underlying dialog element\n   * @public\n   * @returns {HTMLDialogElement | null}\n   */\n  get dialog() {\n    return this.#dialogElement;\n  }\n\n  /* ═══ PRIVATE: INITIALIZATION ═══ */\n\n  /**\n   * Find or create the dialog element\n   * @private\n   */\n  #setupDialogElement() {\n    this.#dialogElement = this.querySelector('dialog');\n    if (!this.#dialogElement) {\n      this.#dialogElement = document.createElement('dialog');\n      this.appendChild(this.#dialogElement);\n    }\n  }\n\n  /**\n   * Initialize dialog with classes and event listeners\n   * @private\n   */\n  #initializeDialogElement() {\n    if (!this.#dialogElement) this.#setupDialogElement();\n\n    this.#dialogElement.classList.add('ren-dialog');\n    this.#updateSizeClass();\n\n    if (this.hasAttribute('alert')) {\n      this.#dialogElement.classList.add('ren-alert-dialog');\n      this.#dialogElement.setAttribute('role', 'alertdialog');\n    }\n\n    // Add aria-label fallback if no title found\n    if (!this.#dialogElement.getAttribute('aria-label')) {\n      const title = this.#dialogElement.querySelector('.ren-dialog-title, [role=\"heading\"]');\n      if (title) {\n        this.#dialogElement.setAttribute('aria-label', title.textContent?.trim() || 'Dialog');\n      } else {\n        this.#dialogElement.setAttribute('aria-label', 'Dialog');\n      }\n    }\n\n    // Block native Escape dismissal only for non-dismissable variants.\n    this.#dialogElement.addEventListener(\n      'cancel',\n      (e) => {\n        if (this.hasAttribute('no-escape') || this.hasAttribute('alert')) {\n          e.preventDefault();\n        }\n      },\n      { signal: this.#abortController.signal }\n    );\n\n    // Native Escape and <form method=\"dialog\"> both land here. All close\n    // paths reconcile through this single listener so lifecycle state and the\n    // public event cannot diverge or fire twice.\n    this.#dialogElement.addEventListener(\n      'close',\n      () => this.#reconcileNativeClose(),\n      { signal: this.#abortController.signal }\n    );\n\n    // Handle click-outside dismissal\n    // Clicking on the dialog itself when event.target === dialog means backdrop was clicked\n    this.#dialogElement.addEventListener(\n      'click',\n      (e) => {\n        if (e.target === this.#dialogElement && !this.hasAttribute('alert')) {\n          this.close();\n        }\n      },\n      { signal: this.#abortController.signal }\n    );\n\n    // Wire up close buttons\n    this.#wireupCloseButtons();\n\n    // Progressive enhancement: set closedby attribute for modern browsers\n    if ('closedBy' in HTMLDialogElement.prototype) {\n      if (this.hasAttribute('alert')) {\n        this.#dialogElement.closedBy = 'closerequest';\n      } else if (this.hasAttribute('no-escape')) {\n        this.#dialogElement.closedBy = 'none';\n      } else {\n        this.#dialogElement.closedBy = 'any';\n      }\n    }\n  }\n\n  /**\n   * Wire up trigger buttons that open this dialog\n   * Supports both [data-dialog-trigger] and native commandfor/command attributes\n   * @private\n   */\n  #wireupTriggers() {\n    const id = this.id;\n    if (!id) return;\n\n    const triggers = document.querySelectorAll(`[data-dialog-trigger=\"${id}\"]`);\n    triggers.forEach((trigger) => {\n      trigger.addEventListener(\n        'click',\n        (e) => {\n          e.preventDefault();\n          this.show();\n        },\n        { signal: this.#abortController.signal }\n      );\n    });\n\n    // Progressive enhancement: support command/commandfor in browsers that don't yet support it natively\n    if (!('commandForElement' in HTMLButtonElement.prototype)) {\n      const commandTriggers = document.querySelectorAll(`[commandfor=\"${id}\"]`);\n      commandTriggers.forEach((trigger) => {\n        const command = trigger.getAttribute('command');\n        if (command === 'show-modal' || command === 'show') {\n          trigger.addEventListener(\n            'click',\n            (e) => {\n              e.preventDefault();\n              this.show();\n            },\n            { signal: this.#abortController.signal }\n          );\n        } else if (command === 'close') {\n          trigger.addEventListener(\n            'click',\n            (e) => {\n              e.preventDefault();\n              this.close();\n            },\n            { signal: this.#abortController.signal }\n          );\n        }\n      });\n    }\n  }\n\n  /**\n   * Wire up close buttons within the dialog\n   * @private\n   */\n  #wireupCloseButtons() {\n    const closeButtons = this.#dialogElement?.querySelectorAll('[data-dialog-close]');\n    closeButtons?.forEach((button) => {\n      button.addEventListener(\n        'click',\n        (e) => {\n          e.preventDefault();\n          // [data-dialog-close=\"value\"] propagates `value` as ren-close.detail.returnValue.\n          // Bare [data-dialog-close] (no value) closes with \"\".\n          const returnValue = button.getAttribute('data-dialog-close') ?? '';\n          this.close(returnValue);\n        },\n        { signal: this.#abortController.signal }\n      );\n    });\n  }\n\n  /* ═══ PRIVATE: FOCUS MANAGEMENT ═══ */\n\n  /**\n   * Setup focus trap for keyboard navigation\n   * @private\n   */\n  #setupFocusTrap() {\n    if (this.#focusTrap) return;\n    this.#focusTrap = createFocusTrap(this.#dialogElement, {\n      initialFocus: this.#dialogElement.querySelector('[autofocus]'),\n      returnFocus: this.#returnFocus,\n    });\n    this.#focusTrap.activate();\n  }\n\n  /**\n   * Teardown focus trap\n   * @private\n   */\n  #teardownFocusTrap() {\n    if (this.#focusTrap) {\n      this.#focusTrap.deactivate();\n      this.#focusTrap = null;\n    }\n  }\n\n  #reconcileNativeClose() {\n    if (this.#closeReconciled) return;\n    this.#closeReconciled = true;\n    const returnValue = this.#dialogElement?.returnValue || '';\n\n    this.#setInert(false);\n    this.removeAttribute('open');\n    this.setAttribute('data-state', 'closed');\n    this.#teardownFocusTrap();\n    if (this.#returnFocus && document.contains(this.#returnFocus)) {\n      this.#returnFocus.focus({ preventScroll: true });\n    }\n    this.#returnFocus = null;\n    this.dispatchEvent(\n      new CustomEvent('ren-close', {\n        bubbles: true,\n        composed: true,\n        detail: { returnValue },\n      })\n    );\n  }\n\n  /* ═══ PRIVATE: UTILITIES ═══ */\n\n  /**\n   * Update size variant class\n   * @private\n   */\n  #updateSizeClass() {\n    if (!this.#dialogElement) return;\n    this.#dialogElement.classList.remove(\n      'ren-dialog-sm',\n      'ren-dialog-md',\n      'ren-dialog-lg',\n      'ren-dialog-xl',\n      'ren-dialog-full'\n    );\n    const size = this.getAttribute('size') || 'md';\n    this.#dialogElement.classList.add(`ren-dialog-${size}`);\n  }\n\n  /**\n   * Apply mobile bottom sheet styling on small screens\n   * @private\n   */\n  #updateMobileSheet() {\n    if (!this.#dialogElement) return;\n    if (window.innerWidth <= 640) {\n      this.#dialogElement.setAttribute('data-mobile-sheet', '');\n    } else {\n      this.#dialogElement.removeAttribute('data-mobile-sheet');\n    }\n  }\n\n  /**\n   * Toggle inert attribute on sibling elements\n   * When active, makes background elements non-interactive\n   * @private\n   * @param {boolean} active\n   */\n  #setInert(active) {\n    const children = document.body.children;\n    for (let i = 0; i < children.length; i++) {\n      const child = children[i];\n\n      // Skip the dialog itself and meta elements\n      if (\n        child === this ||\n        child.tagName === 'SCRIPT' ||\n        child.tagName === 'STYLE' ||\n        child.tagName === 'LINK'\n      ) {\n        continue;\n      }\n\n      if (active) {\n        // Save original inert state and set inert\n        if (!this.#inertElements.has(child)) {\n          this.#inertElements.set(child, child.inert);\n        }\n        child.inert = true;\n      } else {\n        // Restore original inert state\n        const originalInert = this.#inertElements.get(child);\n        if (originalInert !== undefined) {\n          child.inert = originalInert;\n          this.#inertElements.delete(child);\n        }\n      }\n    }\n  }\n}\n\n/**\n * RenAlertDialog - Alert Dialog Variant\n *\n * An alert dialog that prevents click-outside dismissal by default.\n * Escape key and other behavior can be controlled via attributes.\n *\n * @example\n * <ren-alert-dialog>\n *   <dialog>...</dialog>\n * </ren-alert-dialog>\n */\nexport class RenAlertDialog extends RenDialog {\n  constructor() {\n    super();\n    this.setAttribute('alert', '');\n  }\n}\n\n/* ═══ REGISTER CUSTOM ELEMENTS ═══ */\nif (!customElements.get('ren-dialog')) {\n  customElements.define('ren-dialog', RenDialog);\n}\nif (!customElements.get('ren-alert-dialog')) {\n  customElements.define('ren-alert-dialog', RenAlertDialog);\n}\n",
      "path": "components/composites/ren-dialog/ren-dialog.js",
      "id": "file:components/composites/ren-dialog/ren-dialog.js",
      "type": "javascript",
      "name": "ren-dialog.js"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — DropZone Component\n   ============================================\n   Drag & drop file upload area.\n   Minimal JS for drag events — CSS handles visuals.\n\n   Uses native drag events, no library needed.\n\n   Usage:\n     <div class=\"ren-dropzone\" data-accept=\".jpg,.png,.pdf\" data-max-size=\"5MB\">\n       <div class=\"ren-dropzone-content\">\n         <span class=\"ren-dropzone-icon\">↑</span>\n         <p class=\"ren-dropzone-title\">Drop files here</p>\n         <p class=\"ren-dropzone-description\">or click to browse</p>\n       </div>\n       <input type=\"file\" class=\"ren-dropzone-input\" multiple accept=\".jpg,.png,.pdf\">\n     </div>\n   ============================================ */\n\n.ren-dropzone {\n  position: relative;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-height: 10rem;\n  padding: var(--space-6);\n  border: 2px dashed var(--color-border-strong);\n  border-radius: var(--radius-lg);\n  background-color: var(--color-surface-sunken);\n  cursor: pointer;\n  transition: var(--transition-tactile);\n}\n\n.ren-dropzone:hover {\n  border-color: var(--color-accent);\n  background-color: var(--color-accent-subtle);\n}\n\n/* Drag over state (set via JS: element.dataset.dragover = true) */\n.ren-dropzone[data-dragover] {\n  border-color: var(--color-accent);\n  background-color: var(--color-accent-subtle);\n  border-style: solid;\n}\n\n.ren-dropzone:focus-within {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: var(--ring-offset-width);\n}\n\n/* Hidden file input covers entire dropzone */\n.ren-dropzone-input {\n  position: absolute;\n  inset: 0;\n  width: 100%;\n  height: 100%;\n  opacity: 0;\n  cursor: pointer;\n}\n\n/* Content */\n.ren-dropzone-content {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: var(--space-2);\n  text-align: center;\n  pointer-events: none;\n}\n\n.ren-dropzone-icon {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 3rem;\n  height: 3rem;\n  font-size: var(--text-xl);\n  color: var(--color-text-muted);\n  background-color: var(--color-fill);\n  border-radius: var(--radius-full);\n  margin-bottom: var(--space-1);\n}\n\n.ren-dropzone[data-dragover] .ren-dropzone-icon {\n  color: var(--color-accent);\n  background-color: color-mix(in oklch, var(--color-accent), transparent 85%);\n}\n\n.ren-dropzone-title {\n  font-size: var(--body-size);\n  font-weight: var(--weight-medium);\n  color: var(--color-text);\n}\n\n.ren-dropzone-description {\n  font-size: var(--caption-size, var(--text-sm));\n  color: var(--color-text-muted);\n}\n\n/* ─── File list (after upload) ─── */\n.ren-dropzone-files {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-2);\n  width: 100%;\n  margin-top: var(--space-3);\n}\n\n.ren-dropzone-file {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  background-color: var(--color-surface-raised);\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-md);\n  font-size: var(--caption-size, var(--text-sm));\n}\n\n.ren-dropzone-file-name {\n  flex: 1;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.ren-dropzone-file-size {\n  color: var(--color-text-muted);\n  white-space: nowrap;\n}\n\n.ren-dropzone-file-remove {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 1.25rem;\n  height: 1.25rem;\n  padding: 0;\n  border: none;\n  background: none;\n  color: var(--color-text-muted);\n  cursor: pointer;\n  border-radius: var(--radius-sm);\n  font-size: var(--text-base);\n}\n\n.ren-dropzone-file-remove:hover {\n  color: var(--color-danger);\n}\n\n/* ─── Compact variant ─── */\n.ren-dropzone-compact {\n  min-height: auto;\n  padding: var(--space-3) var(--space-4);\n  flex-direction: row;\n  gap: var(--space-3);\n}\n\n.ren-dropzone-compact .ren-dropzone-content {\n  flex-direction: row;\n}\n\n.ren-dropzone-compact .ren-dropzone-icon {\n  width: 2rem;\n  height: 2rem;\n  font-size: var(--text-base);\n  margin-bottom: 0;\n}\n\n/* ─── Disabled ─── */\n.ren-dropzone[aria-disabled=\"true\"],\n.ren-dropzone:has(input:disabled) {\n  opacity: 0.5;\n  cursor: not-allowed;\n  pointer-events: none;\n}\n\n/* ─── Error state ─── */\n.ren-dropzone[data-error] {\n  border-color: var(--color-danger);\n}\n/* Appearance API bridge */\n.ren-dropzone { padding: var(--ren-dropzone-padding, var(--space-8)); border-radius: var(--ren-dropzone-radius, var(--radius-lg)); border-color: var(--ren-dropzone-border-color, var(--color-border-strong)); border-style: var(--ren-dropzone-border-style, dashed); border-width: var(--ren-dropzone-border-width, 2px); background: var(--ren-dropzone-bg, transparent); }\n.ren-dropzone:hover, .ren-dropzone[data-drag-over] { background: var(--ren-dropzone-hover-bg, var(--color-accent-subtle)); border-color: var(--ren-dropzone-hover-border, var(--color-accent)); }\n",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "file:components/composites/ren-dropzone/ren-dropzone.css",
      "type": "css",
      "name": "ren-dropzone.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — DropZone Drag & Drop Handler\n   ============================================\n   Minimal JS for drag events. Handles:\n   - Drag enter/leave visual state\n   - File drop processing\n   - Custom event dispatch\n\n   Usage:\n     import { initDropZone } from './ren-dropzone.js';\n     const dz = initDropZone(document.querySelector('.ren-dropzone'));\n     dz.addEventListener('ren-files-added', (e) => {\n       console.log(e.detail.files);\n     });\n   ============================================ */\n\n/**\n * @param {HTMLElement} dropzone\n * @returns {HTMLElement}\n */\nconst dropzoneControllers = new WeakMap();\n\nexport function initDropZone(dropzone) {\n  if (!dropzone) return null;\n\n  dropzoneControllers.get(dropzone)?.abort();\n  delete dropzone.dataset.dragover;\n  const controller = new AbortController();\n  dropzoneControllers.set(dropzone, controller);\n  const { signal } = controller;\n\n  let dragCounter = 0;\n  const input = dropzone.querySelector('.ren-dropzone-input');\n\n  const handleFiles = (files) => {\n    dropzone.dispatchEvent(\n      new CustomEvent('ren-files-added', {\n        detail: { files: [...files] },\n        bubbles: true,\n      })\n    );\n  };\n\n  dropzone.addEventListener('dragenter', (e) => {\n    e.preventDefault();\n    dragCounter++;\n    dropzone.dataset.dragover = '';\n  }, { signal });\n\n  dropzone.addEventListener('dragleave', () => {\n    dragCounter--;\n    if (dragCounter <= 0) {\n      dragCounter = 0;\n      delete dropzone.dataset.dragover;\n    }\n  }, { signal });\n\n  dropzone.addEventListener('dragover', (e) => {\n    e.preventDefault();\n  }, { signal });\n\n  dropzone.addEventListener('drop', (e) => {\n    e.preventDefault();\n    dragCounter = 0;\n    delete dropzone.dataset.dragover;\n    if (e.dataTransfer?.files?.length) {\n      handleFiles(e.dataTransfer.files);\n    }\n  }, { signal });\n\n  if (input) {\n    input.addEventListener('change', () => {\n      if (input.files?.length) {\n        handleFiles(input.files);\n      }\n    }, { signal });\n  }\n\n  return dropzone;\n}\n",
      "path": "components/composites/ren-dropzone/ren-dropzone.js",
      "id": "file:components/composites/ren-dropzone/ren-dropzone.js",
      "type": "javascript",
      "name": "ren-dropzone.js"
    },
    {
      "data": {},
      "body": "/* ═══ REN HOVER CARD COMPONENT ═══ */\n\n/* ═══ TRIGGER ELEMENT ═══ */\n.ren-hover-card-trigger {\n  anchor-name: --ren-hover-card-anchor;\n}\n\n/* ═══ BASE CARD ═══ */\n.ren-hover-card {\n  position-anchor: --ren-hover-card-anchor;\n  position: absolute;\n  position-area: bottom span-all;\n  inset: auto;\n  margin-block-start: var(--space-2);\n  background: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-xl);\n  padding: var(--space-4);\n  max-width: 20rem;\n  min-width: 15rem;\n  z-index: 1000;\n\n  /* ═══ POPOVER API ═══\n     Semantic tokens keep hover-card in sync with tooltip + popover. */\n  &:popover-open {\n    animation: ren-hover-card-in var(--duration-enter) var(--ease-enter) both;\n  }\n\n  /* ═══ ARROW STYLING ═══ */\n  &::before {\n    content: '';\n    position: absolute;\n    width: 8px;\n    height: 8px;\n    background: inherit;\n    border: inherit;\n    border-inline-end: none;\n    border-bottom: none;\n    transform: rotate(45deg);\n    z-index: -1;\n    top: -4px;\n    left: 50%;\n    margin-inline-start: -4px;\n  }\n\n  /* ═══ FALLBACK POSITIONING ═══ */\n  @supports not ((anchor-name: --ren-anchor) and\n    (position-anchor: --ren-anchor) and\n    (position-area: bottom span-all)) {\n    position: fixed;\n    top: auto;\n    left: auto;\n  }\n\n  /* ═══ VIEWPORT EDGE HANDLING ═══ */\n  @supports (position-try-fallbacks: flip-block) {\n    position-try-fallbacks: flip-block;\n  }\n}\n\n/* ═══ ANIMATION ═══ */\n@starting-style {\n  .ren-hover-card:popover-open {\n    opacity: 0;\n    translate: 0 -4px;\n  }\n}\n\n@keyframes ren-hover-card-in {\n  from {\n    opacity: 0;\n    translate: 0 -4px;\n  }\n\n  to {\n    opacity: 1;\n    translate: 0 0;\n  }\n}\n\n/* ═══ SECTIONS ═══ */\n.ren-hover-card-header {\n  margin-bottom: var(--space-3);\n\n  & h3,\n  & h4 {\n    margin: 0 0 var(--space-0-5) 0;\n    font-size: var(--font-size-body);\n    font-weight: 600;\n    color: var(--color-text);\n  }\n\n  & p {\n    margin: 0;\n    font-size: var(--font-size-xs);\n    color: var(--color-text-muted);\n  }\n}\n\n.ren-hover-card-body {\n  margin-bottom: var(--space-3);\n  font-size: var(--font-size-body-sm);\n  color: var(--color-text);\n  line-height: 1.5;\n\n  & :last-child {\n    margin-bottom: 0;\n  }\n}\n\n.ren-hover-card-footer {\n  display: flex;\n  gap: var(--space-2);\n  align-items: center;\n\n  & button,\n  & a {\n    font-size: var(--font-size-body-sm);\n    padding: var(--space-1) var(--space-2);\n    border-radius: var(--radius-md);\n    border: none;\n    cursor: pointer;\n    transition: var(--transition-tactile);\n\n    &:focus-visible {\n      outline: 2px solid var(--color-accent);\n      outline-offset: 2px;\n    }\n  }\n}\n\n/* ═══ REDUCED MOTION ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-hover-card {\n    &:popover-open {\n      animation: none;\n      opacity: 1;\n      translate: 0 0;\n    }\n\n    &::before {\n      display: none;\n    }\n  }\n}\n\n/* ═══ DARK MODE SUPPORT (OPTIONAL) ═══ */\n@media (prefers-color-scheme: dark) {\n  .ren-hover-card {\n    /* ═══ USES CSS VARIABLES - AUTO ADAPTS ═══ */\n  }\n}\n\n/* ═══ MOBILE RESPONSIVENESS ═══\n   Hover cards keep a media query here because they're\n   positioned absolutely relative to the viewport, and\n   the constraint is viewport space, not container width. */\n@media (max-width: 640px) {\n  .ren-hover-card {\n    max-width: calc(100vw - var(--space-4));\n    min-width: auto;\n    width: calc(100vw - var(--space-4));\n  }\n}\n\n/* ═══ LOADING STATE ═══ */\n.ren-hover-card.ren-hover-card-loading {\n  pointer-events: none;\n\n  & .ren-hover-card-body {\n    opacity: 0.6;\n  }\n}\n\n/* ═══ DISABLED STATE ═══ */\n.ren-hover-card.ren-hover-card-disabled {\n  display: none;\n}\n\n/* ═══ CUSTOM VARIANTS ═══ */\n.ren-hover-card {\n  &.ren-hover-card-sm {\n    padding: var(--space-2);\n    max-width: 15rem;\n    min-width: 12rem;\n\n    & .ren-hover-card-header {\n      margin-bottom: var(--space-2);\n\n      & h3,\n      & h4 {\n        font-size: var(--font-size-body-sm);\n        margin-bottom: var(--space-0-25);\n      }\n    }\n\n    & .ren-hover-card-body {\n      margin-bottom: var(--space-2);\n      font-size: var(--font-size-xs);\n    }\n  }\n\n  &.ren-hover-card-lg {\n    padding: var(--space-5);\n    max-width: 25rem;\n    min-width: 18rem;\n\n    & .ren-hover-card-header {\n      margin-bottom: var(--space-4);\n\n      & h3,\n      & h4 {\n        font-size: var(--font-size-body);\n      }\n    }\n\n    & .ren-hover-card-body {\n      margin-bottom: var(--space-4);\n      font-size: var(--font-size-body);\n    }\n  }\n}\n\n/* ═══ INNER LINKS STYLING ═══ */\n.ren-hover-card a {\n  color: var(--color-accent);\n  text-decoration: underline;\n  transition: var(--transition-tactile);\n\n  &:hover {\n    color: var(--color-accent-strong);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n}\n",
      "path": "components/composites/ren-hover-card/ren-hover-card.css",
      "id": "file:components/composites/ren-hover-card/ren-hover-card.css",
      "type": "css",
      "name": "ren-hover-card.css"
    },
    {
      "data": {},
      "body": "/* ═══ REN HOVER CARD WEB COMPONENT ═══ */\n\nexport class RenHoverCard extends HTMLElement {\n  constructor() {\n    super();\n    this.card = null;\n    this.trigger = null;\n    this.showDelay = 200;\n    this.hideDelay = 300;\n    this.showTimeout = null;\n    this.hideTimeout = null;\n    this.isOpen = false;\n\n    /* ═══ BIND METHODS ═══ */\n    this.handleTriggerMouseEnter = this.handleTriggerMouseEnter.bind(this);\n    this.handleTriggerMouseLeave = this.handleTriggerMouseLeave.bind(this);\n    this.handleTriggerFocusIn = this.handleTriggerFocusIn.bind(this);\n    this.handleTriggerFocusOut = this.handleTriggerFocusOut.bind(this);\n    this.handleCardMouseEnter = this.handleCardMouseEnter.bind(this);\n    this.handleCardMouseLeave = this.handleCardMouseLeave.bind(this);\n  }\n\n  connectedCallback() {\n    /* ═══ FIND OR CREATE CARD ═══ */\n    this.card = this.querySelector('[role=\"tooltip\"]') || this.querySelector('.ren-hover-card');\n\n    if (!this.card) {\n      console.warn('RenHoverCard: No card element found');\n      return;\n    }\n\n    /* ═══ ENSURE CARD HAS CLASS ═══ */\n    this.card.classList.add('ren-hover-card');\n\n    /* ═══ SET UP POPOVER ATTRIBUTE ═══ */\n    if (!this.card.hasAttribute('popover')) {\n      this.card.setAttribute('popover', 'manual');\n    }\n\n    /* ═══ FIND TRIGGER ═══ */\n    const triggerSelector = this.getAttribute('data-hover-trigger');\n    if (triggerSelector) {\n      this.trigger = document.querySelector(triggerSelector);\n    } else {\n      this.trigger = this.previousElementSibling;\n    }\n\n    if (!this.trigger) {\n      console.warn('RenHoverCard: No trigger element found');\n      return;\n    }\n\n    /* ═══ SET UP TRIGGER ARIA ATTRIBUTES ═══ */\n    this.trigger.classList.add('ren-hover-card-trigger');\n    this.trigger.setAttribute('aria-haspopup', 'tooltip');\n    this.trigger.setAttribute('aria-expanded', 'false');\n\n    /* ═══ READ DELAY ATTRIBUTES ═══ */\n    const showDelayAttr = this.getAttribute('show-delay');\n    const hideDelayAttr = this.getAttribute('hide-delay');\n    if (showDelayAttr) this.showDelay = parseInt(showDelayAttr, 10);\n    if (hideDelayAttr) this.hideDelay = parseInt(hideDelayAttr, 10);\n\n    /* ═══ SET ANCHOR NAME ON TRIGGER ═══ */\n    if (!this.trigger.style.anchorName) {\n      this.trigger.style.anchorName = '--ren-hover-card-anchor';\n    }\n\n    /* ═══ SET POSITION ANCHOR ON CARD ═══ */\n    if (!this.card.style.positionAnchor) {\n      this.card.style.positionAnchor = '--ren-hover-card-anchor';\n    }\n\n    /* ═══ ATTACH EVENT LISTENERS ═══ */\n    this.trigger.addEventListener('mouseenter', this.handleTriggerMouseEnter);\n    this.trigger.addEventListener('mouseleave', this.handleTriggerMouseLeave);\n    this.trigger.addEventListener('focusin', this.handleTriggerFocusIn);\n    this.trigger.addEventListener('focusout', this.handleTriggerFocusOut);\n\n    this.card.addEventListener('mouseenter', this.handleCardMouseEnter);\n    this.card.addEventListener('mouseleave', this.handleCardMouseLeave);\n  }\n\n  disconnectedCallback() {\n    if (this.trigger) {\n      this.trigger.removeEventListener('mouseenter', this.handleTriggerMouseEnter);\n      this.trigger.removeEventListener('mouseleave', this.handleTriggerMouseLeave);\n      this.trigger.removeEventListener('focusin', this.handleTriggerFocusIn);\n      this.trigger.removeEventListener('focusout', this.handleTriggerFocusOut);\n    }\n\n    if (this.card) {\n      this.card.removeEventListener('mouseenter', this.handleCardMouseEnter);\n      this.card.removeEventListener('mouseleave', this.handleCardMouseLeave);\n    }\n\n    this.clearTimeouts();\n  }\n\n  /* ═══ TRIGGER MOUSE ENTER ═══ */\n  handleTriggerMouseEnter() {\n    this.clearTimeouts();\n    this.showTimeout = setTimeout(() => this.show(), this.showDelay);\n  }\n\n  /* ═══ TRIGGER MOUSE LEAVE ═══ */\n  handleTriggerMouseLeave() {\n    this.clearShowTimeout();\n    /* ═══ GRACE PERIOD: KEEP OPEN IF MOUSE MOVES TO CARD ═══ */\n    this.hideTimeout = setTimeout(() => this.hide(), this.hideDelay);\n  }\n\n  /* ═══ TRIGGER FOCUS IN (KEYBOARD) ═══ */\n  handleTriggerFocusIn() {\n    this.clearTimeouts();\n    this.show();\n  }\n\n  /* ═══ TRIGGER FOCUS OUT (KEYBOARD) ═══ */\n  handleTriggerFocusOut() {\n    this.clearTimeouts();\n    this.hideTimeout = setTimeout(() => this.hide(), this.hideDelay);\n  }\n\n  /* ═══ CARD MOUSE ENTER (GRACE AREA) ═══ */\n  handleCardMouseEnter() {\n    this.clearHideTimeout();\n  }\n\n  /* ═══ CARD MOUSE LEAVE ═══ */\n  handleCardMouseLeave() {\n    this.hideTimeout = setTimeout(() => this.hide(), this.hideDelay);\n  }\n\n  /* ═══ SHOW CARD ═══ */\n  show() {\n    if (!this.card) return;\n\n    this.clearTimeouts();\n    this.card.showPopover();\n    this.trigger.setAttribute('aria-expanded', 'true');\n    this.isOpen = true;\n\n    this.dispatchEvent(\n      new CustomEvent('ren-hover-card-open', {\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ HIDE CARD ═══ */\n  hide() {\n    if (!this.card) return;\n\n    this.clearTimeouts();\n\n    try {\n      this.card.hidePopover();\n    } catch {\n      /* ═══ POPOVER API MAY NOT EXIST ═══ */\n      this.card.style.display = 'none';\n    }\n\n    this.trigger.setAttribute('aria-expanded', 'false');\n    this.isOpen = false;\n\n    this.dispatchEvent(\n      new CustomEvent('ren-hover-card-close', {\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ TOGGLE CARD ═══ */\n  toggle() {\n    if (this.isOpen) {\n      this.hide();\n    } else {\n      this.show();\n    }\n  }\n\n  /* ═══ CLEAR ALL TIMEOUTS ═══ */\n  clearTimeouts() {\n    this.clearShowTimeout();\n    this.clearHideTimeout();\n  }\n\n  /* ═══ CLEAR SHOW TIMEOUT ═══ */\n  clearShowTimeout() {\n    if (this.showTimeout) {\n      clearTimeout(this.showTimeout);\n      this.showTimeout = null;\n    }\n  }\n\n  /* ═══ CLEAR HIDE TIMEOUT ═══ */\n  clearHideTimeout() {\n    if (this.hideTimeout) {\n      clearTimeout(this.hideTimeout);\n      this.hideTimeout = null;\n    }\n  }\n\n  /* ═══ GETTERS AND SETTERS ═══ */\n  get open() {\n    return this.isOpen;\n  }\n\n  set open(val) {\n    if (val) {\n      this.show();\n    } else {\n      this.hide();\n    }\n  }\n\n  get disabled() {\n    return this.hasAttribute('disabled');\n  }\n\n  set disabled(val) {\n    if (val) {\n      this.setAttribute('disabled', '');\n      this.card?.classList.add('ren-hover-card-disabled');\n    } else {\n      this.removeAttribute('disabled');\n      this.card?.classList.remove('ren-hover-card-disabled');\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-hover-card')) {\n  customElements.define('ren-hover-card', RenHoverCard);\n}\n",
      "path": "components/composites/ren-hover-card/ren-hover-card.js",
      "id": "file:components/composites/ren-hover-card/ren-hover-card.js",
      "type": "javascript",
      "name": "ren-hover-card.js"
    },
    {
      "data": {},
      "body": "/* ═══ MENU CONTAINER ═══ */\n.ren-menu {\n  background: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg, 0.5rem);\n  box-shadow: var(--shadow-lg, 0 10px 25px rgb(0, 0, 0, 0.1));\n  padding-block: var(--space-1, 0.25rem);\n  min-width: 12rem;\n  z-index: var(--z-dropdown, 1000);\n  position: absolute;\n  animation: ren-menu-open var(--duration-enter) var(--ease-enter);\n  visibility: hidden;\n  opacity: 0;\n  inset: auto auto auto auto;\n  margin: 0;\n  max-height: calc(100vh - 32px);\n  overflow-y: auto;\n  overscroll-behavior: contain;\n}\n\n/* ═══ MENU OPEN STATE ═══ */\n.ren-menu[popover]:popover-open,\n.ren-menu.ren-open {\n  visibility: visible;\n  opacity: 1;\n}\n\n/* ═══ MENU ITEM BASE ═══ */\n.ren-menu-item {\n  padding: var(--space-2, 0.5rem) var(--space-3, 0.75rem);\n  display: flex;\n  align-items: center;\n  gap: var(--space-2, 0.5rem);\n  cursor: pointer;\n  font-size: var(--size-body, 1rem);\n  color: var(--color-text);\n  border-radius: var(--radius-sm, 0.25rem);\n  min-height: 2rem;\n  /* Tactile hover/focus — shared preset from semantic motion tokens */\n  transition: var(--transition-tactile);\n  white-space: nowrap;\n  text-decoration: none;\n}\n\n.ren-menu-item:hover,\n.ren-menu-item[data-highlighted] {\n  background-color: var(--color-fill);\n  color: var(--color-text);\n}\n\n.ren-menu-item:active {\n  background-color: var(--color-fill-hover);\n}\n\n/* ═══ MENU ITEM DISABLED STATE ═══ */\n.ren-menu-item[aria-disabled=\"true\"] {\n  opacity: 0.5;\n  cursor: not-allowed;\n  pointer-events: none;\n}\n\n/* ═══ MENU ITEM DANGER VARIANT ═══ */\n.ren-menu-item.ren-menu-item-danger:hover,\n.ren-menu-item.ren-menu-item-danger[data-highlighted] {\n  background-color: var(--color-danger-subtle);\n  color: var(--color-danger);\n}\n\n/* ═══ MENU ITEM ICON ═══ */\n.ren-menu-item-icon {\n  flex-shrink: 0;\n  width: var(--size-icon-md, 1.25rem);\n  height: var(--size-icon-md, 1.25rem);\n  color: var(--color-text-muted);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n/* ═══ MENU ITEM SHORTCUT/LABEL ═══ */\n.ren-menu-item-shortcut {\n  margin-inline-start: auto;\n  font-size: var(--size-caption, 0.75rem);\n  color: var(--color-text-muted);\n  font-family: monospace;\n  white-space: nowrap;\n  flex-shrink: 0;\n}\n\n/* ═══ MENU ITEM DESCRIPTION ═══ */\n.ren-menu-item-description {\n  font-size: var(--size-caption, 0.75rem);\n  color: var(--color-text-muted);\n  line-height: 1.3;\n}\n\n/* ═══ MENU SEPARATOR ═══ */\n.ren-menu-separator {\n  height: 1px;\n  background: var(--color-border);\n  margin-block: var(--space-1, 0.25rem);\n}\n\n/* ═══ MENU LABEL/GROUP HEADER ═══ */\n.ren-menu-label {\n  padding: var(--space-1, 0.25rem) var(--space-3, 0.75rem);\n  font-size: var(--size-caption, 0.75rem);\n  font-weight: 600;\n  color: var(--color-text-muted);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n}\n\n/* ═══ MENU GROUP ═══ */\n.ren-menu-group {\n  display: contents;\n}\n\n/* ═══ CHECKBOX MENU ITEM ═══ */\n.ren-menu-checkbox-item,\n.ren-menu-radio-item {\n  position: relative;\n  padding-inline-start: calc(var(--space-3, 0.75rem) + 20px + var(--space-2, 0.5rem));\n}\n\n.ren-menu-checkbox-item::before,\n.ren-menu-radio-item::before {\n  content: '';\n  position: absolute;\n  inset-inline-start: var(--space-3, 0.75rem);\n  width: 16px;\n  height: 16px;\n  border: 2px solid var(--color-border);\n  border-radius: 2px;\n  background: white;\n  /* Tactile — shared semantic preset */\n  transition: var(--transition-tactile);\n}\n\n.ren-menu-radio-item::before {\n  border-radius: 50%;\n}\n\n/* ═══ CHECKBOX/RADIO CHECKED STATE ═══ */\n.ren-menu-checkbox-item[aria-checked=\"true\"]::before,\n.ren-menu-radio-item[aria-checked=\"true\"]::before {\n  background: var(--color-accent);\n  border-color: var(--color-accent);\n}\n\n.ren-menu-checkbox-item[aria-checked=\"true\"]::after,\n.ren-menu-radio-item[aria-checked=\"true\"]::after {\n  content: '';\n  position: absolute;\n  inset-inline-start: calc(var(--space-3, 0.75rem) + 4px);\n  top: 50%;\n  transform: translateY(-50%);\n}\n\n.ren-menu-checkbox-item[aria-checked=\"true\"]::after {\n  width: 8px;\n  height: 4px;\n  border-inline-start: 2px solid white;\n  border-bottom: 2px solid white;\n  transform: translateY(-55%) rotate(-45deg);\n}\n\n.ren-menu-radio-item[aria-checked=\"true\"]::after {\n  width: 6px;\n  height: 6px;\n  background: white;\n  border-radius: 50%;\n  transform: translateY(-50%);\n}\n\n/* ═══ ANIMATIONS ═══ */\n@keyframes ren-menu-open {\n  from {\n    opacity: 0;\n    transform: translateY(-4px) scale(0.98);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0) scale(1);\n  }\n}\n\n/* ═══ CLOSING STATE WITH ANIMATION ═══ */\n.ren-menu[data-closing] {\n  animation: ren-menu-close var(--duration-exit) var(--ease-exit) forwards;\n}\n\n@keyframes ren-menu-close {\n  from {\n    opacity: 1;\n    transform: translateY(0) scale(1);\n  }\n  to {\n    opacity: 0;\n    transform: translateY(-4px) scale(0.98);\n  }\n}\n\n/* ═══ RESPECTS MOTION PREFERENCES ═══\n   Semantic --duration-* already collapse to 0ms under reduced-motion\n   (see tokens/semantic/motion.css). We keep this block as a\n   belt-and-suspenders fallback and to drop the scale/translate\n   keyframe so the menu simply appears/disappears in place. */\n@media (prefers-reduced-motion: reduce) {\n  .ren-menu {\n    animation: none;\n    transition: opacity var(--duration-enter);\n  }\n\n  .ren-menu:not(.ren-open) {\n    opacity: 0;\n  }\n\n  .ren-menu-item {\n    transition: background-color 0s, color 0s;\n  }\n\n  .ren-menu[data-closing] {\n    animation: none;\n  }\n}\n\n/* ═══ NATIVE POPOVER API SUPPORT ═══ */\n@supports (selector(:popover-open)) {\n  .ren-menu[popover] {\n    visibility: visible;\n    opacity: 0;\n    transition: opacity var(--duration-enter) var(--ease-enter),\n                overlay var(--duration-enter) var(--ease-enter) allow-discrete,\n                display var(--duration-enter) var(--ease-enter) allow-discrete;\n    animation: none;\n  }\n\n  @starting-style {\n    .ren-menu[popover]:popover-open {\n      opacity: 0;\n    }\n  }\n\n  .ren-menu[popover]:popover-open {\n    opacity: 1;\n  }\n}\n\n/* ═══ SCROLLBAR STYLING ═══ */\n.ren-menu::-webkit-scrollbar {\n  width: 6px;\n}\n\n.ren-menu::-webkit-scrollbar-track {\n  background: transparent;\n}\n\n.ren-menu::-webkit-scrollbar-thumb {\n  background: var(--color-border);\n  border-radius: 3px;\n}\n\n.ren-menu::-webkit-scrollbar-thumb:hover {\n  background: var(--color-text-muted);\n}\n/* Appearance API bridge */\n.ren-menu {\n  min-inline-size: var(--ren-menu-width, 200px);\n  padding: var(--ren-menu-padding, var(--space-1));\n  border-radius: var(--ren-menu-radius, var(--radius-lg));\n  background: var(--ren-menu-bg, var(--color-surface-overlay));\n  border-color: var(--ren-menu-border, var(--color-border));\n  box-shadow: var(--ren-menu-shadow, var(--shadow-lg));\n  transition-duration: var(--ren-menu-duration, var(--duration-fast));\n  transition-timing-function: var(--ren-menu-easing, var(--ease-out));\n}\n.ren-menu-item { min-block-size: var(--ren-menu-item-height, var(--size-md)); padding: var(--ren-menu-item-padding, var(--space-2) var(--space-3)); border-radius: var(--ren-menu-item-radius, var(--radius-sm)); }\n.ren-menu-item:hover, .ren-menu-item[data-highlighted] { background: var(--ren-menu-item-hover-bg, var(--color-fill)); }\n.ren-menu-item-danger { color: var(--ren-menu-danger-color, var(--color-danger)); }\n",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "file:components/composites/ren-menu/ren-menu.css",
      "type": "css",
      "name": "ren-menu.css"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — <ren-menu> Web Component\n * ==================================\n * Dropdown menu component with keyboard navigation, accessibility, and theming.\n * Supports items, groups, separators, checkboxes, and radio items.\n *\n * Uses Light DOM — no Shadow DOM.\n * Native Popover API when available, falls back to class-based positioning.\n *\n * Attributes:\n *   placement:     'bottom-start' (default) | 'bottom-end' | 'top-start' | 'top-end'\n *                  | 'right-start' | 'right-end' | 'left-start' | 'left-end'\n *                  - Controls menu position relative to trigger\n *\n * Markup:\n *   <button data-menu-trigger id=\"menu-trigger\">Menu</button>\n *   <ren-menu trigger-id=\"menu-trigger\" placement=\"bottom-start\">\n *     <button class=\"ren-menu-item\" role=\"menuitem\">Option 1</button>\n *     <button class=\"ren-menu-item\" role=\"menuitem\" data-value=\"opt2\">Option 2</button>\n *     <div class=\"ren-menu-separator\"></div>\n *     <button class=\"ren-menu-item ren-menu-item-danger\" role=\"menuitem\">Delete</button>\n *   </ren-menu>\n *\n * Features:\n *   - Arrow key navigation (vertical)\n *   - Home/End to first/last item\n *   - Enter/Space to select\n *   - Typeahead search\n *   - Click outside to close\n *   - Escape key to close\n *   - Focus management\n *   - Checkbox and radio item support\n *\n * Events:\n *   ren-menu-select:  Dispatched when an item is selected\n *   - detail: { item, value }\n *   ren-menu-open:    Dispatched when menu opens\n *   ren-menu-close:   Dispatched when menu closes\n */\n\nimport { createKeyboardNav } from '../../../utils/keyboard-nav.js';\nimport { createDismissable } from '../../../utils/dismissable.js';\n\nconst MENU_SIDES = new Set(['top', 'right', 'bottom', 'left']);\nconst MENU_ALIGNS = new Set(['start', 'end']);\n\nfunction normalizeMenuPlacement(value) {\n  const [sideValue, alignValue] = String(value || 'bottom-start')\n    .toLowerCase()\n    .split('-');\n  const side = MENU_SIDES.has(sideValue) ? sideValue : 'bottom';\n  const align = MENU_ALIGNS.has(alignValue) ? alignValue : 'start';\n\n  return { side, align };\n}\n\nexport class RenMenu extends HTMLElement {\n  static observedAttributes = ['placement'];\n\n  #trigger = null;\n  #isOpen = false;\n  #keyboardNav = null;\n  #dismissable = null;\n  #animationFrame = null;\n  #boundItemClick = null;\n  #boundTriggerClick = null;\n  #boundTriggerKeydown = null;\n  #returnFocus = true;\n  #closeTimer = null;\n\n  constructor() {\n    super();\n  }\n\n  /**\n   * Lifecycle: Element inserted into DOM\n   */\n  connectedCallback() {\n    this.loadStyles();\n    this.setupMenu();\n    this.findTrigger();\n    this.attachTriggerListeners();\n    this.attachItemListeners();\n  }\n\n  /**\n   * Lifecycle: Element removed from DOM\n   */\n  disconnectedCallback() {\n    this.cleanup();\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'placement' && oldValue !== newValue) {\n      this.syncPlacement();\n      if (this.#isOpen) {\n        this.positionMenu();\n      }\n    }\n  }\n\n  /* ═══ INITIALIZATION ═══ */\n\n  /**\n   * Load component styles\n   * @private\n   */\n  loadStyles() {\n    if (!document.getElementById('ren-menu-styles')) {\n      const style = document.createElement('style');\n      style.id = 'ren-menu-styles';\n      style.textContent = this.constructor.styles || '';\n      document.head.appendChild(style);\n    }\n  }\n\n  /**\n   * Setup menu element with required attributes and structure\n   * @private\n   */\n  setupMenu() {\n    this.classList.add('ren-menu');\n\n    // Set accessibility attributes\n    this.setAttribute('role', 'menu');\n    this.id = this.id || `ren-menu-${Math.random().toString(36).substr(2, 9)}`;\n\n    // Check for native Popover API support\n    if ('popover' in HTMLElement.prototype) {\n      this.setAttribute('popover', 'manual');\n    }\n\n    this.syncPlacement();\n\n    // Ensure all items have correct roles if not already set\n    this.querySelectorAll(\n      '.ren-menu-item, .ren-menu-checkbox-item, .ren-menu-radio-item, [role=\"menuitem\"], [role=\"menuitemcheckbox\"], [role=\"menuitemradio\"]'\n    ).forEach((item) => {\n      if (!item.hasAttribute('role')) {\n        if (item.classList.contains('ren-menu-checkbox-item')) {\n          item.setAttribute('role', 'menuitemcheckbox');\n        } else if (item.classList.contains('ren-menu-radio-item')) {\n          item.setAttribute('role', 'menuitemradio');\n        } else {\n          item.setAttribute('role', 'menuitem');\n        }\n      }\n\n      if (!item.hasAttribute('tabindex')) {\n        item.setAttribute('tabindex', '-1');\n      }\n    });\n  }\n\n  /**\n   * Find the trigger element\n   * Searches for: trigger-id attribute, data-menu-trigger, or previous sibling\n   * @private\n   */\n  findTrigger() {\n    const triggerId = this.getAttribute('trigger-id');\n    if (triggerId) {\n      this.#trigger = document.getElementById(triggerId);\n    }\n\n    if (!this.#trigger) {\n      this.#trigger = document.querySelector('[data-menu-trigger]');\n    }\n\n    if (!this.#trigger) {\n      this.#trigger = this.previousElementSibling;\n    }\n\n    if (this.#trigger) {\n      this.#trigger.setAttribute('aria-haspopup', 'menu');\n      this.#trigger.setAttribute('aria-controls', this.id);\n    }\n  }\n\n  /**\n   * Attach event listeners to trigger element\n   * @private\n   */\n  attachTriggerListeners() {\n    if (!this.#trigger) return;\n    this.#boundTriggerClick = (e) => this.handleTriggerClick(e);\n    this.#boundTriggerKeydown = (e) => this.handleTriggerKeydown(e);\n    this.#trigger.addEventListener('click', this.#boundTriggerClick);\n    this.#trigger.addEventListener('keydown', this.#boundTriggerKeydown);\n  }\n\n  /**\n   * Attach delegated item listeners\n   * @private\n   */\n  attachItemListeners() {\n    this.#boundItemClick = this.#boundItemClick || ((e) => this.handleItemClick(e));\n    this.removeEventListener('click', this.#boundItemClick);\n    this.addEventListener('click', this.#boundItemClick);\n  }\n\n  /* ═══ TRIGGER HANDLING ═══ */\n\n  /**\n   * Handle trigger click to open menu\n   * @private\n   */\n  handleTriggerClick(e) {\n    e.preventDefault();\n    if (this.#isOpen) {\n      this.close();\n    } else {\n      this.open();\n    }\n  }\n\n  /**\n   * Handle trigger keydown (arrow down to open)\n   * @private\n   */\n  handleTriggerKeydown(e) {\n    if ((e.key === 'ArrowDown' || e.key === 'ArrowUp') && !this.#isOpen) {\n      e.preventDefault();\n      this.open();\n    }\n  }\n\n  /**\n   * Handle item click selection\n   * @private\n   */\n  handleItemClick(e) {\n    const item = e.target.closest('[role=\"menuitem\"], [role=\"menuitemcheckbox\"], [role=\"menuitemradio\"]');\n\n    if (!item || !this.contains(item) || item.getAttribute('aria-disabled') === 'true') {\n      return;\n    }\n\n    e.preventDefault();\n    e.stopPropagation();\n    this.handleItemSelect(item);\n  }\n\n  /* ═══ POSITIONING ═══ */\n\n  /**\n   * Mirror preferred placement to public data attributes.\n   */\n  syncPlacement() {\n    const { side, align } = normalizeMenuPlacement(this.getAttribute('placement'));\n\n    this.setAttribute('data-side', side);\n    this.setAttribute('data-align', align);\n  }\n\n  /**\n   * Position the menu relative to trigger element\n   * @private\n   */\n  positionMenu() {\n    if (!this.#trigger) return;\n\n    const placement = normalizeMenuPlacement(this.getAttribute('placement'));\n    const triggerRect = this.#trigger.getBoundingClientRect();\n    const menuRect = this.getBoundingClientRect();\n    const gap = 8;\n    const margin = 8;\n\n    let finalSide = placement.side;\n    let left = triggerRect.left;\n    let top = triggerRect.bottom + gap;\n\n    if (placement.align === 'end') {\n      left = triggerRect.right - menuRect.width;\n    }\n\n    if (placement.side === 'top') {\n      top = triggerRect.top - menuRect.height - gap;\n    } else if (placement.side === 'right') {\n      left = triggerRect.right + gap;\n      top = placement.align === 'end'\n        ? triggerRect.bottom - menuRect.height\n        : triggerRect.top;\n    } else if (placement.side === 'left') {\n      left = triggerRect.left - menuRect.width - gap;\n      top = placement.align === 'end'\n        ? triggerRect.bottom - menuRect.height\n        : triggerRect.top;\n    }\n\n    // Viewport collision detection\n    const viewport = {\n      width: window.innerWidth,\n      height: window.innerHeight,\n    };\n\n    if (\n      placement.side === 'bottom' &&\n      top + menuRect.height > viewport.height - margin &&\n      triggerRect.top - menuRect.height - gap >= margin\n    ) {\n      finalSide = 'top';\n      top = triggerRect.top - menuRect.height - gap;\n    } else if (\n      placement.side === 'top' &&\n      top < margin &&\n      triggerRect.bottom + menuRect.height + gap <= viewport.height - margin\n    ) {\n      finalSide = 'bottom';\n      top = triggerRect.bottom + gap;\n    } else if (\n      placement.side === 'right' &&\n      left + menuRect.width > viewport.width - margin &&\n      triggerRect.left - menuRect.width - gap >= margin\n    ) {\n      finalSide = 'left';\n      left = triggerRect.left - menuRect.width - gap;\n    } else if (\n      placement.side === 'left' &&\n      left < margin &&\n      triggerRect.right + menuRect.width + gap <= viewport.width - margin\n    ) {\n      finalSide = 'right';\n      left = triggerRect.right + gap;\n    }\n\n    // Clamp horizontal and vertical position to viewport.\n    if (left < margin) {\n      left = margin;\n    } else if (left + menuRect.width > viewport.width - margin) {\n      left = viewport.width - menuRect.width - margin;\n    }\n\n    if (top < margin) {\n      top = margin;\n    } else if (top + menuRect.height > viewport.height - margin) {\n      top = viewport.height - menuRect.height - margin;\n    }\n\n    this.setAttribute('data-side', finalSide);\n    this.setAttribute('data-align', placement.align);\n    this.style.left = `${left}px`;\n    this.style.top = `${top}px`;\n  }\n\n  /* ═══ KEYBOARD NAVIGATION ═══ */\n\n  /**\n   * Setup keyboard navigation for menu items\n   * @private\n   */\n  setupKeyboardNav() {\n    if (this.#keyboardNav) {\n      this.#keyboardNav.detach();\n    }\n\n    this.#keyboardNav = createKeyboardNav(this, {\n      selector: '[role=\"menuitem\"]:not([aria-disabled=\"true\"]), [role=\"menuitemcheckbox\"]:not([aria-disabled=\"true\"]), [role=\"menuitemradio\"]:not([aria-disabled=\"true\"])',\n      orientation: 'vertical',\n      loop: true,\n      typeahead: true,\n      focusOnHover: true,\n      onActivate: (item) => {\n        item.focus();\n      },\n      onSelect: (item) => {\n        this.handleItemSelect(item);\n      },\n    });\n\n    this.#keyboardNav.attach();\n  }\n\n  /**\n   * Teardown keyboard navigation\n   * @private\n   */\n  teardownKeyboardNav() {\n    if (this.#keyboardNav) {\n      this.#keyboardNav.detach();\n      this.#keyboardNav = null;\n    }\n  }\n\n  /* ═══ DISMISSABLE LAYER ═══ */\n\n  /**\n   * Setup dismissable layer (click-outside, Escape key)\n   * @private\n   */\n  setupDismissable() {\n    if (this.#dismissable) {\n      this.#dismissable.deactivate();\n    }\n\n    this.#dismissable = createDismissable(this, {\n      onDismiss: () => this.close(),\n      triggerElement: this.#trigger,\n      escapeKey: true,\n      clickOutside: true,\n    });\n\n    this.#dismissable.activate();\n  }\n\n  /**\n   * Teardown dismissable layer\n   * @private\n   */\n  teardownDismissable() {\n    if (this.#dismissable) {\n      this.#dismissable.deactivate();\n      this.#dismissable = null;\n    }\n  }\n\n  /* ═══ ITEM SELECTION ═══ */\n\n  /**\n   * Handle menu item selection\n   * @private\n   */\n  handleItemSelect(item) {\n    const role = item.getAttribute('role');\n\n    // Handle checkbox items\n    if (item.classList.contains('ren-menu-checkbox-item') || role === 'menuitemcheckbox') {\n      const isChecked = item.getAttribute('aria-checked') === 'true';\n      item.setAttribute('aria-checked', !isChecked ? 'true' : 'false');\n      this.dispatchSelectEvent(item);\n      return;\n    }\n\n    // Handle radio items (exclusive selection within group)\n    if (item.classList.contains('ren-menu-radio-item') || role === 'menuitemradio') {\n      const group = item.closest('.ren-menu-radio-group') || this;\n      group\n        .querySelectorAll('.ren-menu-radio-item, [role=\"menuitemradio\"]')\n        .forEach((radioItem) => radioItem.setAttribute('aria-checked', 'false'));\n      item.setAttribute('aria-checked', 'true');\n      this.dispatchSelectEvent(item);\n      this.close();\n      return;\n    }\n\n    // Regular menu item: dispatch event and close\n    this.dispatchSelectEvent(item);\n    this.close();\n  }\n\n  /**\n   * Dispatch ren-menu-select event\n   * @private\n   */\n  dispatchSelectEvent(item) {\n    const value = item.getAttribute('data-value') || item.textContent.trim();\n    this.dispatchEvent(\n      new CustomEvent('ren-menu-select', {\n        bubbles: true,\n        detail: { item, value },\n      })\n    );\n  }\n\n  /* ═══ PUBLIC API ═══ */\n\n  /**\n   * Open the menu\n   */\n  open() {\n    if (this.#isOpen) return;\n\n    this.#isOpen = true;\n    this.#returnFocus = true;\n    this.setAttribute('data-state', 'open');\n    this.setupKeyboardNav();\n    this.setupDismissable();\n\n    this.positionMenu();\n\n    if ('popover' in HTMLElement.prototype) {\n      try {\n        this.showPopover();\n      } catch {\n        // Popover might already be open\n      }\n    } else {\n      this.classList.add('ren-open');\n    }\n\n    // Reposition on next frame for accuracy\n    this.#animationFrame = requestAnimationFrame(() => {\n      this.positionMenu();\n\n      // Focus first item\n      const firstItem = this.querySelector('[role=\"menuitem\"], [role=\"menuitemcheckbox\"], [role=\"menuitemradio\"]');\n      if (firstItem) {\n        firstItem.focus();\n      }\n    });\n\n    this.#trigger?.setAttribute('aria-expanded', 'true');\n    this.dispatchEvent(new CustomEvent('ren-menu-open', { bubbles: true }));\n  }\n\n  /**\n   * Close the menu\n   */\n  close() {\n    if (!this.#isOpen) return;\n\n    this.#isOpen = false;\n    this.setAttribute('data-state', 'closed');\n    this.teardownKeyboardNav();\n    this.teardownDismissable();\n\n    // Add closing animation class\n    this.setAttribute('data-closing', '');\n\n    // Wait for animation to finish before actually closing\n    const animationDuration = getComputedStyle(this).animationDuration;\n    const duration = parseFloat(animationDuration) * 1000;\n\n    if (this.#closeTimer) clearTimeout(this.#closeTimer);\n    this.#closeTimer = setTimeout(() => {\n      this.removeAttribute('data-closing');\n\n      if ('popover' in HTMLElement.prototype) {\n        try {\n          this.hidePopover();\n        } catch {\n          // Popover might already be closed\n        }\n      } else {\n        this.classList.remove('ren-open');\n      }\n\n      this.#trigger?.setAttribute('aria-expanded', 'false');\n      if (this.#returnFocus && this.#trigger && document.contains(this.#trigger)) {\n        this.#trigger.focus();\n      }\n      this.#closeTimer = null;\n    }, Math.min(duration, 150)); // Cap at 150ms\n\n    this.dispatchEvent(new CustomEvent('ren-menu-close', { bubbles: true }));\n  }\n\n  /**\n   * Check if menu is currently open\n   * @returns {boolean}\n   */\n  isOpen() {\n    return this.#isOpen;\n  }\n\n  /**\n   * Get the trigger element\n   * @returns {HTMLElement|null}\n   */\n  getTrigger() {\n    return this.#trigger;\n  }\n\n  /**\n   * Cleanup event listeners and resources\n   * @private\n   */\n  cleanup() {\n    this.close();\n    if (this.#closeTimer) clearTimeout(this.#closeTimer);\n    this.teardownKeyboardNav();\n    this.teardownDismissable();\n\n    if (this.#animationFrame) {\n      cancelAnimationFrame(this.#animationFrame);\n    }\n\n    if (this.#boundItemClick) {\n      this.removeEventListener('click', this.#boundItemClick);\n    }\n\n    if (this.#trigger) {\n      this.#trigger.removeEventListener('click', this.#boundTriggerClick);\n      this.#trigger.removeEventListener('keydown', this.#boundTriggerKeydown);\n    }\n  }\n}\n\nif (!customElements.get('ren-menu')) {\n  customElements.define('ren-menu', RenMenu);\n}\n\n// Compatibility export: context-menu behavior and registration are owned by\n// the colocated context-menu module so importing either historical path cannot\n// install a second implementation.\nexport { RenContextMenu } from '../ren-context-menu/ren-context-menu.js';\n",
      "path": "components/composites/ren-menu/ren-menu.js",
      "id": "file:components/composites/ren-menu/ren-menu.js",
      "type": "javascript",
      "name": "ren-menu.js"
    },
    {
      "data": {},
      "body": "/* ═══════════════════════════════════════════════════════════════\n   REN NUMBER FIELD (STEPPER) COMPONENT\n   ═══════════════════════════════════════════════════════════════\n   A controlled number input with increment/decrement buttons.\n   Supports keyboard navigation, long-press acceleration, and\n   min/max bounds. Follows RenDS design token system.\n\n   Usage:\n   <ren-number-field min=\"0\" max=\"100\" step=\"1\" value=\"5\">\n     <button class=\"ren-number-field-decrement\" aria-label=\"Decrease\">−</button>\n     <input class=\"ren-number-field-input\" type=\"number\" value=\"5\">\n     <button class=\"ren-number-field-increment\" aria-label=\"Increase\">+</button>\n   </ren-number-field>\n   ═══════════════════════════════════════════════════════════════ */\n\n/* ═══ BASE WRAPPER ═══ */\n.ren-number-field {\n  display: inline-flex;\n  align-items: center;\n  gap: 0;\n  border: var(--stroke-1) solid var(--color-input-border);\n  border-radius: var(--radius-md);\n  background-color: var(--color-input-bg);\n  transition: var(--transition-tactile);\n\n  /* ═══ FOCUS STATE ON GROUP ═══ */\n  &:has(.ren-number-field-input:focus) {\n    border-color: var(--color-input-border-focus);\n    box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n    background-color: var(--color-input-bg-hover);\n  }\n\n  /* ═══ HOVER STATE ═══ */\n  &:hover:not(:has(:disabled)) {\n    background-color: var(--color-input-bg-hover);\n  }\n\n  /* ═══ DISABLED STATE ═══ */\n  &:has(:disabled) {\n    background-color: var(--color-disabled-bg);\n    border-color: var(--color-border);\n    cursor: not-allowed;\n    opacity: 0.7;\n  }\n}\n\n/* ═══ STEPPER BUTTONS ═══ */\n.ren-number-field-decrement,\n.ren-number-field-increment {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-width: var(--touch-min);\n  height: var(--touch-min);\n  padding: 0 var(--space-2);\n  background: transparent;\n  border: none;\n  color: var(--color-text);\n  cursor: pointer;\n  font-size: var(--text-lg);\n  font-weight: 600;\n  font-family: var(--font-mono);\n  transition: var(--transition-tactile);\n\n  /* ═══ REMOVE FOCUS OUTLINE - GROUP HANDLES IT ═══ */\n  &:focus {\n    outline: none;\n  }\n\n  /* ═══ HOVER STATE ═══ */\n  &:hover:not(:disabled) {\n    background-color: var(--color-fill-hover);\n  }\n\n  /* ═══ ACTIVE STATE ═══ */\n  &:active:not(:disabled) {\n    background-color: var(--color-fill-active);\n  }\n\n  /* ═══ DISABLED STATE ═══ */\n  &:disabled {\n    color: var(--color-disabled-text);\n    cursor: not-allowed;\n    opacity: 0.5;\n  }\n}\n\n/* ═══ DECREMENT BUTTON (LEFT) ═══ */\n.ren-number-field-decrement {\n  border-inline-end: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-md) 0 0 var(--radius-md);\n\n  /* ═══ MINUS ICON ═══ */\n  &::before {\n    content: '−';\n  }\n}\n\n/* ═══ INCREMENT BUTTON (RIGHT) ═══ */\n.ren-number-field-increment {\n  border-inline-start: var(--stroke-1) solid var(--color-border);\n  border-radius: 0 var(--radius-md) var(--radius-md) 0;\n\n  /* ═══ PLUS ICON ═══ */\n  &::before {\n    content: '+';\n  }\n}\n\n/* ═══ INPUT FIELD (CENTER) ═══ */\n.ren-number-field-input {\n  flex: 1;\n  min-width: 3rem;\n  padding: var(--space-2) var(--space-3);\n  font-size: var(--body-size);\n  font-family: var(--font-mono);\n  font-weight: 500;\n  color: var(--color-text);\n  text-align: center;\n  background: transparent;\n  border: none;\n  outline: none;\n  cursor: text;\n\n  /* ═══ HIDE NATIVE NUMBER SPINNER ═══ */\n  &::-webkit-outer-spin-button,\n  &::-webkit-inner-spin-button {\n    -webkit-appearance: none;\n    margin: 0;\n  }\n\n  &[type='number'] {\n    -moz-appearance: textfield;\n  }\n\n  /* ═══ PLACEHOLDER ═══ */\n  &::placeholder {\n    color: var(--color-input-placeholder);\n  }\n\n  /* ═══ DISABLED ═══ */\n  &:disabled {\n    cursor: not-allowed;\n    color: var(--color-disabled-text);\n  }\n}\n\n/* ═══ VARIANT: SMALL ═══ */\n.ren-number-field-sm {\n  height: var(--size-sm);\n  border-radius: var(--radius-sm);\n\n  & .ren-number-field-decrement,\n  & .ren-number-field-increment {\n    min-width: var(--size-sm);\n    height: var(--size-sm);\n    padding: 0 var(--space-1);\n    font-size: var(--text-sm);\n  }\n\n  & .ren-number-field-decrement {\n    border-radius: var(--radius-sm) 0 0 var(--radius-sm);\n  }\n\n  & .ren-number-field-increment {\n    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;\n  }\n\n  & .ren-number-field-input {\n    padding: var(--space-1) var(--space-2);\n    font-size: var(--text-sm);\n  }\n}\n\n/* ═══ VARIANT: LARGE ═══ */\n.ren-number-field-lg {\n  height: var(--size-lg);\n  border-radius: var(--radius-lg);\n\n  & .ren-number-field-decrement,\n  & .ren-number-field-increment {\n    min-width: var(--size-lg);\n    height: var(--size-lg);\n    padding: 0 var(--space-3);\n    font-size: var(--text-lg);\n  }\n\n  & .ren-number-field-decrement {\n    border-radius: var(--radius-lg) 0 0 var(--radius-lg);\n  }\n\n  & .ren-number-field-increment {\n    border-radius: 0 var(--radius-lg) var(--radius-lg) 0;\n  }\n\n  & .ren-number-field-input {\n    padding: var(--space-3) var(--space-4);\n    font-size: var(--text-lg);\n  }\n}\n\n/* ═══ ERROR STATE ═══ */\n.ren-number-field[data-invalid] {\n  border-color: var(--color-danger);\n\n  &:focus-within {\n    box-shadow: 0 0 0 3px rgb(255, 59, 48, 0.15);\n  }\n}\n\n/* ═══ SUCCESS STATE ═══ */\n.ren-number-field[data-valid] {\n  border-color: var(--color-success);\n\n  &:focus-within {\n    box-shadow: 0 0 0 3px rgb(52, 199, 89, 0.15);\n  }\n}\n\n/* ═══ REDUCED MOTION ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-number-field,\n  .ren-number-field-decrement,\n  .ren-number-field-increment {\n    transition: none;\n  }\n}\n",
      "path": "components/composites/ren-number-field/ren-number-field.css",
      "id": "file:components/composites/ren-number-field/ren-number-field.css",
      "type": "css",
      "name": "ren-number-field.css"
    },
    {
      "data": {},
      "body": "/* ═══════════════════════════════════════════════════════════════\n   REN NUMBER FIELD (STEPPER) WEB COMPONENT\n   ═══════════════════════════════════════════════════════════════\n   A light-DOM web component that enhances a number input with\n   increment/decrement buttons. Handles keyboard navigation,\n   long-press auto-repeat with acceleration, and min/max bounds.\n\n   Attributes:\n   - min: Minimum value (default: 0)\n   - max: Maximum value (default: 100)\n   - step: Step size (default: 1)\n   - value: Current value\n   - disabled: Disable the field\n\n   Events:\n   - ren-change: Dispatched on value change { value, oldValue }\n\n   Methods:\n   - increment(): Increase value by step\n   - decrement(): Decrease value by step\n   - getValue(): Return current numeric value\n   - setValue(val): Set value and clamp to bounds\n   ═══════════════════════════════════════════════════════════════ */\n\nexport class RenNumberField extends HTMLElement {\n  constructor() {\n    super();\n\n    /* ═══ STATE ═══ */\n    this.min = 0;\n    this.max = 100;\n    this.step = 1;\n    this.value = 0;\n    this.pressTimeout = null;\n    this.pressInterval = null;\n    this.pressRepeatDelay = 500;\n    this.pressRepeatInterval = 100;\n    this.pressRepeatAcceleration = 0.95;\n\n    /* ═══ BIND METHODS ═══ */\n    this.handleDecrementPress = this.handleDecrementPress.bind(this);\n    this.handleIncrementPress = this.handleIncrementPress.bind(this);\n    this.handleDecrementRelease = this.handleDecrementRelease.bind(this);\n    this.handleIncrementRelease = this.handleIncrementRelease.bind(this);\n    this.handleInputChange = this.handleInputChange.bind(this);\n    this.handleInputKeydown = this.handleInputKeydown.bind(this);\n  }\n\n  connectedCallback() {\n    /* ═══ READ ATTRIBUTES ═══ */\n    const minAttr = this.getAttribute('min');\n    const maxAttr = this.getAttribute('max');\n    const stepAttr = this.getAttribute('step');\n    const valueAttr = this.getAttribute('value');\n    const disabledAttr = this.hasAttribute('disabled');\n\n    if (minAttr !== null) this.min = parseFloat(minAttr);\n    if (maxAttr !== null) this.max = parseFloat(maxAttr);\n    if (stepAttr !== null) this.step = parseFloat(stepAttr);\n    if (valueAttr !== null) this.value = parseFloat(valueAttr);\n\n    /* ═══ GET OR CREATE ELEMENTS ═══ */\n    this.input = this.querySelector('.ren-number-field-input');\n    this.decrementBtn = this.querySelector('.ren-number-field-decrement');\n    this.incrementBtn = this.querySelector('.ren-number-field-increment');\n\n    /* ═══ CREATE IF MISSING ═══ */\n    if (!this.input) {\n      this.input = document.createElement('input');\n      this.input.type = 'number';\n      this.input.className = 'ren-number-field-input';\n      this.appendChild(this.input);\n    }\n\n    if (!this.decrementBtn) {\n      this.decrementBtn = document.createElement('button');\n      this.decrementBtn.className = 'ren-number-field-decrement';\n      this.decrementBtn.setAttribute('aria-label', 'Decrease');\n      this.insertBefore(this.decrementBtn, this.input);\n    }\n\n    if (!this.incrementBtn) {\n      this.incrementBtn = document.createElement('button');\n      this.incrementBtn.className = 'ren-number-field-increment';\n      this.incrementBtn.setAttribute('aria-label', 'Increase');\n      this.appendChild(this.incrementBtn);\n    }\n\n    // Steppers are controls for the number input, never form submitters.\n    this.decrementBtn.type = 'button';\n    this.incrementBtn.type = 'button';\n\n    /* ═══ SET INITIAL VALUES ═══ */\n    this.input.value = this.value.toString();\n    this.input.min = this.min.toString();\n    this.input.max = this.max.toString();\n    this.input.step = this.step.toString();\n\n    if (disabledAttr) {\n      this.input.disabled = true;\n      this.decrementBtn.disabled = true;\n      this.incrementBtn.disabled = true;\n    }\n\n    /* ═══ ATTACH EVENT LISTENERS ═══ */\n    this.input.addEventListener('change', this.handleInputChange);\n    this.input.addEventListener('keydown', this.handleInputKeydown);\n\n    /* ═══ BUTTON PRESS & HOLD ═══ */\n    this.decrementBtn.addEventListener('mousedown', this.handleDecrementPress);\n    this.decrementBtn.addEventListener('mouseup', this.handleDecrementRelease);\n    this.decrementBtn.addEventListener('mouseleave', this.handleDecrementRelease);\n    this.decrementBtn.addEventListener('touchstart', this.handleDecrementPress);\n    this.decrementBtn.addEventListener('touchend', this.handleDecrementRelease);\n\n    this.incrementBtn.addEventListener('mousedown', this.handleIncrementPress);\n    this.incrementBtn.addEventListener('mouseup', this.handleIncrementRelease);\n    this.incrementBtn.addEventListener('mouseleave', this.handleIncrementRelease);\n    this.incrementBtn.addEventListener('touchstart', this.handleIncrementPress);\n    this.incrementBtn.addEventListener('touchend', this.handleIncrementRelease);\n  }\n\n  disconnectedCallback() {\n    /* ═══ CLEAN UP EVENT LISTENERS ═══ */\n    if (this.input) {\n      this.input.removeEventListener('change', this.handleInputChange);\n      this.input.removeEventListener('keydown', this.handleInputKeydown);\n    }\n\n    if (this.decrementBtn) {\n      this.decrementBtn.removeEventListener('mousedown', this.handleDecrementPress);\n      this.decrementBtn.removeEventListener('mouseup', this.handleDecrementRelease);\n      this.decrementBtn.removeEventListener('mouseleave', this.handleDecrementRelease);\n      this.decrementBtn.removeEventListener('touchstart', this.handleDecrementPress);\n      this.decrementBtn.removeEventListener('touchend', this.handleDecrementRelease);\n    }\n\n    if (this.incrementBtn) {\n      this.incrementBtn.removeEventListener('mousedown', this.handleIncrementPress);\n      this.incrementBtn.removeEventListener('mouseup', this.handleIncrementRelease);\n      this.incrementBtn.removeEventListener('mouseleave', this.handleIncrementRelease);\n      this.incrementBtn.removeEventListener('touchstart', this.handleIncrementPress);\n      this.incrementBtn.removeEventListener('touchend', this.handleIncrementRelease);\n    }\n\n    /* ═══ CLEAR TIMERS ═══ */\n    this.clearPressTimers();\n  }\n\n  /* ═══════════════════════════════════════════════════════════════\n     DECREMENT BUTTON HANDLERS\n     ═══════════════════════════════════════════════════════════════ */\n  handleDecrementPress = (e) => {\n    if (this.input.disabled || this.decrementBtn.disabled) return;\n    e.preventDefault();\n\n    this.decrement();\n\n    /* ═══ START LONG PRESS REPEAT ═══ */\n    this.pressTimeout = setTimeout(() => {\n      let repeatInterval = this.pressRepeatInterval;\n\n      this.pressInterval = setInterval(() => {\n        this.decrement();\n        /* ═══ ACCELERATE ═══ */\n        repeatInterval *= this.pressRepeatAcceleration;\n        clearInterval(this.pressInterval);\n\n        this.pressInterval = setInterval(\n          () => this.decrement(),\n          Math.max(50, repeatInterval)\n        );\n      }, this.pressRepeatInterval);\n    }, this.pressRepeatDelay);\n  };\n\n  handleDecrementRelease = () => {\n    this.clearPressTimers();\n  };\n\n  /* ═══════════════════════════════════════════════════════════════\n     INCREMENT BUTTON HANDLERS\n     ═══════════════════════════════════════════════════════════════ */\n  handleIncrementPress = (e) => {\n    if (this.input.disabled || this.incrementBtn.disabled) return;\n    e.preventDefault();\n\n    this.increment();\n\n    /* ═══ START LONG PRESS REPEAT ═══ */\n    this.pressTimeout = setTimeout(() => {\n      let repeatInterval = this.pressRepeatInterval;\n\n      this.pressInterval = setInterval(() => {\n        this.increment();\n        /* ═══ ACCELERATE ═══ */\n        repeatInterval *= this.pressRepeatAcceleration;\n        clearInterval(this.pressInterval);\n\n        this.pressInterval = setInterval(\n          () => this.increment(),\n          Math.max(50, repeatInterval)\n        );\n      }, this.pressRepeatInterval);\n    }, this.pressRepeatDelay);\n  };\n\n  handleIncrementRelease = () => {\n    this.clearPressTimers();\n  };\n\n  /* ═══════════════════════════════════════════════════════════════\n     INPUT HANDLERS\n     ═══════════════════════════════════════════════════════════════ */\n  handleInputChange = (e) => {\n    const newValue = parseFloat(e.target.value);\n\n    if (!isNaN(newValue)) {\n      const oldValue = this.value;\n      this.value = this.clamp(newValue);\n      this.input.value = this.value.toString();\n\n      this.dispatchChangeEvent(oldValue);\n    }\n  };\n\n  handleInputKeydown = (e) => {\n    if (e.key === 'ArrowUp') {\n      e.preventDefault();\n      this.increment();\n    } else if (e.key === 'ArrowDown') {\n      e.preventDefault();\n      this.decrement();\n    } else if (e.key === 'Home') {\n      e.preventDefault();\n      const oldValue = this.value;\n      this.value = this.min;\n      this.input.value = this.value.toString();\n      this.dispatchChangeEvent(oldValue);\n    } else if (e.key === 'End') {\n      e.preventDefault();\n      const oldValue = this.value;\n      this.value = this.max;\n      this.input.value = this.value.toString();\n      this.dispatchChangeEvent(oldValue);\n    }\n  };\n\n  /* ═══════════════════════════════════════════════════════════════\n     PUBLIC METHODS\n     ═══════════════════════════════════════════════════════════════ */\n  increment() {\n    const oldValue = this.value;\n    this.value = this.clamp(this.value + this.step);\n    this.input.value = this.value.toString();\n    this.dispatchChangeEvent(oldValue);\n  }\n\n  decrement() {\n    const oldValue = this.value;\n    this.value = this.clamp(this.value - this.step);\n    this.input.value = this.value.toString();\n    this.dispatchChangeEvent(oldValue);\n  }\n\n  getValue() {\n    return this.value;\n  }\n\n  setValue(val) {\n    const oldValue = this.value;\n    this.value = this.clamp(parseFloat(val) || 0);\n    this.input.value = this.value.toString();\n    this.dispatchChangeEvent(oldValue);\n  }\n\n  /* ═══════════════════════════════════════════════════════════════\n     UTILITY METHODS\n     ═══════════════════════════════════════════════════════════════ */\n  clamp(val) {\n    return Math.max(this.min, Math.min(this.max, val));\n  }\n\n  dispatchChangeEvent(oldValue) {\n    if (this.value !== oldValue) {\n      this.dispatchEvent(\n        new CustomEvent('ren-change', {\n          bubbles: true,\n          composed: true,\n          detail: {\n            value: this.value,\n            oldValue: oldValue,\n          },\n        })\n      );\n    }\n  }\n\n  clearPressTimers() {\n    if (this.pressTimeout) {\n      clearTimeout(this.pressTimeout);\n      this.pressTimeout = null;\n    }\n\n    if (this.pressInterval) {\n      clearInterval(this.pressInterval);\n      this.pressInterval = null;\n    }\n  }\n\n  /* ═══════════════════════════════════════════════════════════════\n     PROPERTIES\n     ═══════════════════════════════════════════════════════════════ */\n  get disabled() {\n    return this.hasAttribute('disabled');\n  }\n\n  set disabled(val) {\n    if (val) {\n      this.setAttribute('disabled', '');\n      if (this.input) this.input.disabled = true;\n      if (this.decrementBtn) this.decrementBtn.disabled = true;\n      if (this.incrementBtn) this.incrementBtn.disabled = true;\n    } else {\n      this.removeAttribute('disabled');\n      if (this.input) this.input.disabled = false;\n      if (this.decrementBtn) this.decrementBtn.disabled = false;\n      if (this.incrementBtn) this.incrementBtn.disabled = false;\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-number-field')) {\n  customElements.define('ren-number-field', RenNumberField);\n}\n",
      "path": "components/composites/ren-number-field/ren-number-field.js",
      "id": "file:components/composites/ren-number-field/ren-number-field.js",
      "type": "javascript",
      "name": "ren-number-field.js"
    },
    {
      "data": {},
      "body": "/* ═══════════════════════════════════════════════════════════════\n   REN OTP (ONE-TIME PASSWORD) INPUT COMPONENT\n   ═══════════════════════════════════════════════════════════════\n   A flexible OTP input with auto-advancing slots, paste support,\n   and validation states. Handles numeric and alphanumeric codes.\n   Follows RenDS design token system.\n\n   Usage:\n   <ren-otp length=\"6\" type=\"numeric\">\n     \\3c !-- Slots auto-generated by JS -->\n   </ren-otp>\n\n   Attributes:\n   - length: Number of OTP digits (default: 6)\n   - type: \"numeric\" or \"alphanumeric\" (default: \"numeric\")\n   - disabled: Disable all slots\n\n   Data Attributes:\n   - data-valid: Success state\n   - data-invalid: Error state\n   - data-filled: On individual slots when filled\n   ═══════════════════════════════════════════════════════════════ */\n\n/* ═══ BASE WRAPPER ═══ */\n.ren-otp {\n  container-type: inline-size;\n  container-name: ren-otp;\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  flex-wrap: wrap;\n}\n\n/* ═══ INDIVIDUAL SLOT ═══ */\n.ren-otp-slot {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: var(--touch-min);\n  height: var(--touch-min);\n  padding: 0;\n  font-size: var(--text-lg);\n  font-family: var(--font-mono);\n  font-weight: 600;\n  color: var(--color-text);\n  background-color: var(--color-input-bg);\n  border: var(--stroke-1) solid var(--color-input-border);\n  border-radius: var(--radius-md);\n  outline: none;\n  cursor: text;\n  caret-color: var(--color-accent);\n  text-align: center;\n  transition: var(--transition-tactile);\n\n  /* ═══ PLACEHOLDER STYLING ═══ */\n  &::placeholder {\n    color: var(--color-input-placeholder);\n  }\n\n  /* ═══ HOVER STATE ═══ */\n  &:hover:not(:disabled):not(:focus) {\n    background-color: var(--color-input-bg-hover);\n  }\n\n  /* ═══ FOCUS STATE ═══ */\n  &:focus {\n    border-color: var(--color-input-border-focus);\n    box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n    background-color: var(--color-input-bg-hover);\n  }\n\n  /* ═══ FILLED STATE ═══ */\n  &[data-filled] {\n    background-color: var(--color-fill);\n    border-color: var(--color-input-border);\n  }\n\n  /* ═══ DISABLED STATE ═══ */\n  &:disabled {\n    background-color: var(--color-disabled-bg);\n    color: var(--color-disabled-text);\n    cursor: not-allowed;\n    opacity: 0.7;\n    border-color: var(--color-border);\n  }\n}\n\n/* ═══ SEPARATOR ═══ */\n.ren-otp-separator {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: auto;\n  height: var(--touch-min);\n  padding: 0 var(--space-1);\n  color: var(--color-text-muted);\n  font-size: var(--text-lg);\n  font-weight: 300;\n  user-select: none;\n  pointer-events: none;\n}\n\n/* ═══════════════════════════════════════════════════════════════\n   VALIDATION STATES\n   ═══════════════════════════════════════════════════════════════ */\n\n/* ═══ ERROR STATE ═══ */\n.ren-otp[data-invalid] .ren-otp-slot {\n  border-color: var(--color-danger);\n\n  &:focus {\n    box-shadow: 0 0 0 3px rgb(255, 59, 48, 0.15);\n    border-color: var(--color-danger);\n  }\n}\n\n/* ═══ SUCCESS STATE ═══ */\n.ren-otp[data-valid] .ren-otp-slot {\n  border-color: var(--color-success);\n\n  &:focus {\n    box-shadow: 0 0 0 3px rgb(52, 199, 89, 0.15);\n    border-color: var(--color-success);\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════\n   SIZE VARIANTS\n   ═══════════════════════════════════════════════════════════════ */\n\n/* ═══ SMALL VARIANT ═══ */\n.ren-otp-sm {\n  gap: var(--space-1);\n\n  & .ren-otp-slot {\n    width: var(--size-sm);\n    height: var(--size-sm);\n    font-size: var(--text-sm);\n    border-radius: var(--radius-sm);\n  }\n\n  & .ren-otp-separator {\n    height: var(--size-sm);\n    font-size: var(--text-sm);\n  }\n}\n\n/* ═══ LARGE VARIANT ═══ */\n.ren-otp-lg {\n  gap: var(--space-3);\n\n  & .ren-otp-slot {\n    width: var(--size-lg);\n    height: var(--size-lg);\n    font-size: var(--body-size);\n    border-radius: var(--radius-lg);\n  }\n\n  & .ren-otp-separator {\n    height: var(--size-lg);\n    font-size: var(--body-size);\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════\n   ACCESSIBILITY\n   ═══════════════════════════════════════════════════════════════ */\n\n/* ═══ FOCUS VISIBLE (KEYBOARD NAVIGATION) ═══ */\n.ren-otp-slot:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: 2px;\n}\n\n/* ═══ DISABLED WRAPPER ═══ */\n.ren-otp[disabled] .ren-otp-slot {\n  cursor: not-allowed;\n  opacity: 0.6;\n}\n\n/* ═══════════════════════════════════════════════════════════════\n   REDUCED MOTION\n   ═══════════════════════════════════════════════════════════════ */\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-otp-slot {\n    transition: none;\n  }\n}\n\n/* ═══════════════════════════════════════════════════════════════\n   MOBILE RESPONSIVENESS\n   ═══════════════════════════════════════════════════════════════ */\n\n@container ren-otp (max-width: 360px) {\n  .ren-otp {\n    gap: var(--space-1);\n\n    & .ren-otp-slot {\n      width: var(--size-sm);\n      height: var(--size-sm);\n      font-size: var(--text-sm);\n    }\n\n    & .ren-otp-separator {\n      height: var(--size-sm);\n      font-size: var(--text-sm);\n      padding: 0 var(--space-0-5);\n    }\n  }\n}\n",
      "path": "components/composites/ren-otp/ren-otp.css",
      "id": "file:components/composites/ren-otp/ren-otp.css",
      "type": "css",
      "name": "ren-otp.css"
    },
    {
      "data": {},
      "body": "/* ═══════════════════════════════════════════════════════════════\n   REN OTP (ONE-TIME PASSWORD) INPUT WEB COMPONENT\n   ═══════════════════════════════════════════════════════════════\n   A light-DOM web component for collecting OTP/PIN codes.\n   Auto-creates input slots, manages focus transitions,\n   handles paste events, and validates completion.\n\n   Attributes:\n   - length: Number of code slots (default: 6)\n   - type: \"numeric\" or \"alphanumeric\" (default: \"numeric\")\n   - disabled: Disable all slots\n\n   Data Attributes:\n   - data-valid: Set to mark as success\n   - data-invalid: Set to mark as error\n   - data-filled: Set on individual slots when they have content\n\n   Events:\n   - ren-change: Dispatched on any slot change { value: partial }\n   - ren-complete: Dispatched when all slots filled { value: complete }\n\n   Methods:\n   - getValue(): Return current combined code\n   - reset(): Clear all slots\n   - focus(): Focus first slot\n   ═══════════════════════════════════════════════════════════════ */\n\nexport class RenOtp extends HTMLElement {\n  constructor() {\n    super();\n\n    /* ═══ STATE ═══ */\n    this.length = 6;\n    this.type = 'numeric';\n    this.slots = [];\n\n    /* ═══ BIND METHODS ═══ */\n    this.handleSlotInput = this.handleSlotInput.bind(this);\n    this.handleSlotKeydown = this.handleSlotKeydown.bind(this);\n    this.handleSlotPaste = this.handleSlotPaste.bind(this);\n    this.handleSlotFocus = this.handleSlotFocus.bind(this);\n  }\n\n  connectedCallback() {\n    /* ═══ READ ATTRIBUTES ═══ */\n    const lengthAttr = this.getAttribute('length');\n    const typeAttr = this.getAttribute('type');\n    const disabledAttr = this.hasAttribute('disabled');\n\n    if (lengthAttr !== null) this.length = parseInt(lengthAttr, 10);\n    if (typeAttr !== null) this.type = typeAttr;\n\n    /* ═══ CREATE SLOTS ═══ */\n    this.createSlots(disabledAttr);\n  }\n\n  /* ═══════════════════════════════════════════════════════════════\n     SLOT CREATION & SETUP\n     ═══════════════════════════════════════════════════════════════ */\n  createSlots(disabled = false) {\n    /* ═══ CLEAR EXISTING SLOTS ═══ */\n    this.slots = [];\n    const existingSlots = this.querySelectorAll('.ren-otp-slot');\n    existingSlots.forEach((slot) => slot.remove());\n\n    /* ═══ CREATE NEW SLOTS ═══ */\n    for (let i = 0; i < this.length; i++) {\n      const slot = document.createElement('input');\n      slot.type = 'text';\n      slot.className = 'ren-otp-slot';\n      slot.setAttribute('inputmode', this.type === 'numeric' ? 'numeric' : 'text');\n      slot.setAttribute('maxlength', '1');\n      slot.setAttribute('placeholder', '•');\n      slot.setAttribute('autocomplete', 'off');\n      slot.setAttribute('data-index', i.toString());\n      slot.setAttribute('aria-label', `Code digit ${i + 1} of ${this.length}`);\n\n      if (disabled) {\n        slot.disabled = true;\n      }\n\n      /* ═══ ATTACH EVENT LISTENERS ═══ */\n      slot.addEventListener('input', this.handleSlotInput);\n      slot.addEventListener('keydown', this.handleSlotKeydown);\n      slot.addEventListener('paste', this.handleSlotPaste);\n      slot.addEventListener('focus', this.handleSlotFocus);\n\n      this.appendChild(slot);\n      this.slots.push(slot);\n    }\n  }\n\n  /* ═══════════════════════════════════════════════════════════════\n     EVENT HANDLERS\n     ═══════════════════════════════════════════════════════════════ */\n\n  handleSlotInput = (e) => {\n    const slot = e.target;\n    const index = parseInt(slot.getAttribute('data-index'), 10);\n    let value = slot.value;\n\n    /* ═══ VALIDATE INPUT ═══ */\n    if (this.type === 'numeric') {\n      value = value.replace(/[^0-9]/g, '');\n    } else if (this.type === 'alphanumeric') {\n      value = value.replace(/[^a-zA-Z0-9]/g, '');\n    }\n\n    /* ═══ ENFORCE MAX LENGTH ═══ */\n    if (value.length > 1) {\n      value = value.slice(0, 1);\n    }\n\n    slot.value = value;\n\n    /* ═══ UPDATE FILLED STATE ═══ */\n    if (value) {\n      slot.setAttribute('data-filled', '');\n    } else {\n      slot.removeAttribute('data-filled');\n    }\n\n    /* ═══ AUTO-ADVANCE TO NEXT SLOT ═══ */\n    if (value && index < this.slots.length - 1) {\n      this.slots[index + 1].focus();\n    }\n\n    /* ═══ DISPATCH CHANGE EVENT ═══ */\n    this.dispatchChangeEvent();\n\n    /* ═══ CHECK COMPLETION ═══ */\n    this.checkCompletion();\n  };\n\n  handleSlotKeydown = (e) => {\n    const slot = e.target;\n    const index = parseInt(slot.getAttribute('data-index'), 10);\n\n    /* ═══ BACKSPACE: MOVE TO PREVIOUS SLOT ═══ */\n    if (e.key === 'Backspace') {\n      if (slot.value) {\n        slot.value = '';\n        slot.removeAttribute('data-filled');\n        this.dispatchChangeEvent();\n      } else if (index > 0) {\n        e.preventDefault();\n        const prevSlot = this.slots[index - 1];\n        prevSlot.value = '';\n        prevSlot.removeAttribute('data-filled');\n        prevSlot.focus();\n        this.dispatchChangeEvent();\n      }\n    }\n\n    /* ═══ ARROW KEYS: NAVIGATE SLOTS ═══ */\n    if (e.key === 'ArrowLeft' && index > 0) {\n      e.preventDefault();\n      this.slots[index - 1].focus();\n    } else if (e.key === 'ArrowRight' && index < this.slots.length - 1) {\n      e.preventDefault();\n      this.slots[index + 1].focus();\n    }\n\n    /* ═══ HOME/END: FIRST/LAST SLOT ═══ */\n    if (e.key === 'Home') {\n      e.preventDefault();\n      this.slots[0].focus();\n    } else if (e.key === 'End') {\n      e.preventDefault();\n      this.slots[this.slots.length - 1].focus();\n    }\n  };\n\n  handleSlotPaste = (e) => {\n    e.preventDefault();\n    const pastedData = e.clipboardData?.getData('text') || '';\n\n    if (!pastedData) return;\n\n    /* ═══ VALIDATE PASTED DATA ═══ */\n    let sanitized = pastedData;\n    if (this.type === 'numeric') {\n      sanitized = pastedData.replace(/[^0-9]/g, '');\n    } else if (this.type === 'alphanumeric') {\n      sanitized = pastedData.replace(/[^a-zA-Z0-9]/g, '');\n    }\n\n    /* ═══ DISTRIBUTE ACROSS SLOTS ═══ */\n    const index = parseInt(e.target.getAttribute('data-index'), 10);\n    for (let i = 0; i < sanitized.length && index + i < this.slots.length; i++) {\n      const slot = this.slots[index + i];\n      slot.value = sanitized[i];\n      slot.setAttribute('data-filled', '');\n    }\n\n    /* ═══ FOCUS NEXT EMPTY OR LAST SLOT ═══ */\n    const nextEmptyIndex = this.slots.findIndex(\n      (s, i) => i >= index && s.value === ''\n    );\n    if (nextEmptyIndex !== -1) {\n      this.slots[nextEmptyIndex].focus();\n    } else {\n      this.slots[this.slots.length - 1].focus();\n    }\n\n    /* ═══ DISPATCH CHANGE & CHECK COMPLETION ═══ */\n    this.dispatchChangeEvent();\n    this.checkCompletion();\n  };\n\n  handleSlotFocus = (e) => {\n    /* ═══ SELECT ALL TEXT WHEN FOCUSED ═══ */\n    e.target.select();\n  };\n\n  /* ═══════════════════════════════════════════════════════════════\n     PUBLIC METHODS\n     ═══════════════════════════════════════════════════════════════ */\n\n  getValue() {\n    return this.slots.map((slot) => slot.value).join('');\n  }\n\n  reset() {\n    this.slots.forEach((slot) => {\n      slot.value = '';\n      slot.removeAttribute('data-filled');\n    });\n\n    this.removeAttribute('data-valid');\n    this.removeAttribute('data-invalid');\n    this.dispatchChangeEvent();\n\n    if (this.slots.length > 0) {\n      this.slots[0].focus();\n    }\n  }\n\n  setValid(isValid) {\n    if (isValid) {\n      this.setAttribute('data-valid', '');\n      this.removeAttribute('data-invalid');\n    } else {\n      this.setAttribute('data-invalid', '');\n      this.removeAttribute('data-valid');\n    }\n  }\n\n  clearValidation() {\n    this.removeAttribute('data-valid');\n    this.removeAttribute('data-invalid');\n  }\n\n  /* ═══════════════════════════════════════════════════════════════\n     UTILITY METHODS\n     ═══════════════════════════════════════════════════════════════ */\n\n  checkCompletion() {\n    const value = this.getValue();\n    const isComplete = value.length === this.length;\n\n    if (isComplete) {\n      this.dispatchCompleteEvent(value);\n    }\n  }\n\n  dispatchChangeEvent() {\n    const value = this.getValue();\n\n    this.dispatchEvent(\n      new CustomEvent('ren-change', {\n        bubbles: true,\n        composed: true,\n        detail: {\n          value: value,\n        },\n      })\n    );\n  }\n\n  dispatchCompleteEvent(value) {\n    this.dispatchEvent(\n      new CustomEvent('ren-complete', {\n        bubbles: true,\n        composed: true,\n        detail: {\n          value: value,\n        },\n      })\n    );\n  }\n\n  /* ═══════════════════════════════════════════════════════════════\n     PROPERTIES\n     ═══════════════════════════════════════════════════════════════ */\n\n  get disabled() {\n    return this.hasAttribute('disabled');\n  }\n\n  set disabled(val) {\n    if (val) {\n      this.setAttribute('disabled', '');\n      this.slots.forEach((slot) => {\n        slot.disabled = true;\n      });\n    } else {\n      this.removeAttribute('disabled');\n      this.slots.forEach((slot) => {\n        slot.disabled = false;\n      });\n    }\n  }\n\n  /* ═══ OVERRIDE FOCUS METHOD ═══ */\n  focus() {\n    if (this.slots.length > 0) {\n      this.slots[0].focus();\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-otp')) {\n  customElements.define('ren-otp', RenOtp);\n}\n",
      "path": "components/composites/ren-otp/ren-otp.js",
      "id": "file:components/composites/ren-otp/ren-otp.js",
      "type": "javascript",
      "name": "ren-otp.js"
    },
    {
      "data": {},
      "body": "/* ═══ ANCHOR POSITIONING & BASE STYLES ═══ */\n.ren-popover {\n  position: absolute;\n  background: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  padding: var(--space-4);\n  max-width: 20rem;\n  z-index: var(--z-popover, 1000);\n  pointer-events: auto;\n  margin: 0;\n  inset: auto;\n  border-top: none;\n\n  /* Anchor positioning (modern browsers) */\n  position-anchor: --popover-anchor;\n\n  /* Default: below the trigger with automatic fallback */\n  position-area: bottom span-all;\n\n  /* Automatic flip when near viewport edges */\n  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;\n\n  /* Enter/exit transitions driven by semantic motion tokens. */\n  opacity: 0;\n  transform: translateY(-4px) scale(0.95);\n  transition:\n    opacity   var(--duration-enter) var(--ease-enter),\n    transform var(--duration-enter) var(--ease-enter),\n    overlay   var(--duration-enter) var(--ease-enter) allow-discrete,\n    display   var(--duration-enter) var(--ease-enter) allow-discrete;\n}\n\n/* ═══ POPOVER OPEN STATE ═══ */\n.ren-popover:popover-open,\n.ren-popover.ren-open {\n  opacity: 1;\n  transform: translateY(0) scale(1);\n}\n\n/* ═══ POPOVER OPEN WITH @starting-style ═══ */\n@starting-style {\n  .ren-popover:popover-open {\n    opacity: 0;\n    transform: translateY(-4px) scale(0.95);\n  }\n}\n\n/* ═══ POPOVER ARROW/CARET ═══ */\n.ren-popover-arrow {\n  position: absolute;\n  width: 8px;\n  height: 8px;\n  background: inherit;\n  border: inherit;\n  border-top: none;\n  border-inline-start: none;\n  transform: rotate(45deg);\n  z-index: -1;\n}\n\n/* ═══ PLACEMENT VARIANTS ═══ */\n/* Bottom (default) */\n.ren-popover[data-side=\"bottom\"],\n.ren-popover:not([data-side]) {\n  position-area: bottom span-all;\n  margin-block-start: var(--space-2);\n  margin-block-end: 0;\n  margin-inline: 0;\n\n  & .ren-popover-arrow {\n    top: -4px;\n    left: 50%;\n    transform: translateX(-50%) rotate(225deg);\n  }\n}\n\n/* Top */\n.ren-popover[data-side=\"top\"] {\n  position-area: top span-all;\n  margin-block-start: 0;\n  margin-block-end: var(--space-2);\n  margin-inline: 0;\n\n  & .ren-popover-arrow {\n    bottom: -4px;\n    left: 50%;\n    transform: translateX(-50%) rotate(45deg);\n  }\n}\n\n/* Right */\n.ren-popover[data-side=\"right\"] {\n  position-area: right span-all;\n  margin-block: 0;\n  margin-inline-start: var(--space-2);\n  margin-inline-end: 0;\n\n  & .ren-popover-arrow {\n    left: -4px;\n    top: 50%;\n    transform: translateY(-50%) rotate(135deg);\n  }\n}\n\n/* Left */\n.ren-popover[data-side=\"left\"] {\n  position-area: left span-all;\n  margin-block: 0;\n  margin-inline-start: 0;\n  margin-inline-end: var(--space-2);\n\n  & .ren-popover-arrow {\n    right: -4px;\n    top: 50%;\n    transform: translateY(-50%) rotate(315deg);\n  }\n}\n\n/* ═══ POPOVER SECTIONS ═══ */\n.ren-popover-header {\n  margin-bottom: var(--space-2);\n  font-weight: 600;\n  color: var(--color-text-primary);\n}\n\n.ren-popover-body {\n  color: var(--color-text-secondary);\n  font-size: var(--size-body-sm);\n  line-height: 1.5;\n}\n\n.ren-popover-footer {\n  margin-top: var(--space-3);\n  padding-top: var(--space-3);\n  border-top: 1px solid var(--color-border);\n  display: flex;\n  gap: var(--space-2);\n}\n\n/* ═══ RESPECTS MOTION PREFERENCES ═══\n   Semantic --duration-enter already collapses to 0ms under\n   reduced-motion. We keep this block to drop the transform\n   (scale/translate) in the starting-style, so the popover\n   fades in place instead of moving. */\n@media (prefers-reduced-motion: reduce) {\n  .ren-popover {\n    transition:\n      opacity var(--duration-enter),\n      overlay var(--duration-enter) allow-discrete,\n      display var(--duration-enter) allow-discrete;\n    transform: none;\n  }\n\n  @starting-style {\n    .ren-popover:popover-open {\n      opacity: 0;\n      transform: none;\n    }\n  }\n}\n\n/* ═══ FALLBACK FOR BROWSERS WITHOUT COMPLETE ANCHOR POSITIONING ═══ */\n@supports not ((anchor-name: --ren-anchor) and\n  (position-anchor: --ren-anchor) and\n  (position-area: bottom span-all)) {\n  .ren-popover {\n    position: absolute;\n    inset: auto auto auto auto;\n  }\n\n  [data-popover-trigger],\n  .ren-popover-trigger {\n    position: relative;\n  }\n\n  /* Fallback animations */\n  .ren-popover {\n    animation: ren-popover-fallback-open var(--duration-enter) var(--ease-enter);\n  }\n\n  .ren-popover:popover-open,\n  .ren-popover.ren-open {\n    animation: ren-popover-fallback-open var(--duration-enter) var(--ease-enter);\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    .ren-popover {\n      animation: none;\n    }\n  }\n}\n\n@keyframes ren-popover-fallback-open {\n  from {\n    opacity: 0;\n    transform: translateY(-4px) scale(0.95);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0) scale(1);\n  }\n}\n\n/* Appearance API bridge */\n.ren-popover {\n  width: var(--ren-popover-width, 280px);\n  max-width: var(--ren-popover-width, 280px);\n  padding: var(--ren-popover-padding, var(--space-4));\n  border-radius: var(--ren-popover-radius, var(--radius-lg));\n  background: var(--ren-popover-bg, var(--color-surface-overlay));\n  border-color: var(--ren-popover-border-color, var(--color-border));\n  box-shadow: var(--ren-popover-shadow, var(--shadow-lg));\n  transition-duration: var(--ren-popover-duration, var(--duration-normal));\n  transition-timing-function: var(--ren-popover-easing, var(--ease-out));\n}\n",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "file:components/composites/ren-popover/ren-popover.css",
      "type": "css",
      "name": "ren-popover.css"
    },
    {
      "data": {},
      "body": "let nextPopoverId = 0;\nconst FOCUSABLE_SELECTOR = [\n  'a[href]',\n  'button:not([disabled])',\n  'input:not([disabled]):not([type=\"hidden\"])',\n  'select:not([disabled])',\n  'textarea:not([disabled])',\n  '[tabindex]:not([tabindex=\"-1\"])',\n].join(', ');\nconst PLACEMENTS = new Set(['top', 'right', 'bottom', 'left']);\n\nfunction supportsAnchorPositioning() {\n  return (\n    typeof CSS !== 'undefined' &&\n    typeof CSS.supports === 'function' &&\n    CSS.supports('anchor-name', '--ren-anchor') &&\n    CSS.supports('position-anchor', '--ren-anchor') &&\n    CSS.supports('position-area', 'bottom span-all')\n  );\n}\n\nfunction normalizePlacement(value, fallback = 'bottom') {\n  return PLACEMENTS.has(value) ? value : fallback;\n}\n\n/**\n * Fallback position computation for browsers without CSS anchor positioning.\n * Used only when full CSS anchor positioning support is unavailable.\n *\n * @param {HTMLElement} trigger - The trigger element\n * @param {HTMLElement} popover - The popover element\n * @param {string} placement - Placement: 'top', 'right', 'bottom', 'left'\n * @param {number} offset - Offset in pixels between trigger and popover\n * @returns {Object} Position object with x, y, and finalPlacement properties\n */\nfunction computePosition(trigger, popover, placement = 'bottom', offset = 8) {\n  const triggerRect = trigger.getBoundingClientRect();\n  const popoverRect = popover.getBoundingClientRect();\n  const viewport = { width: window.innerWidth, height: window.innerHeight };\n\n  let x = 0;\n  let y = 0;\n  let finalPlacement = placement;\n\n  const placements = {\n    top: () => {\n      x = triggerRect.left + (triggerRect.width - popoverRect.width) / 2;\n      y = triggerRect.top - popoverRect.height - offset;\n      if (y < 0) {\n        finalPlacement = 'bottom';\n        return placements.bottom();\n      }\n      return { x, y };\n    },\n    bottom: () => {\n      x = triggerRect.left + (triggerRect.width - popoverRect.width) / 2;\n      y = triggerRect.bottom + offset;\n      if (y + popoverRect.height > viewport.height) {\n        finalPlacement = 'top';\n        return placements.top();\n      }\n      return { x, y };\n    },\n    left: () => {\n      x = triggerRect.left - popoverRect.width - offset;\n      y = triggerRect.top + (triggerRect.height - popoverRect.height) / 2;\n      if (x < 0) {\n        finalPlacement = 'right';\n        return placements.right();\n      }\n      return { x, y };\n    },\n    right: () => {\n      x = triggerRect.right + offset;\n      y = triggerRect.top + (triggerRect.height - popoverRect.height) / 2;\n      if (x + popoverRect.width > viewport.width) {\n        finalPlacement = 'left';\n        return placements.left();\n      }\n      return { x, y };\n    },\n  };\n\n  const result = placements[placement]?.() || placements.bottom();\n\n  // Clamp X position within viewport\n  if (result.x < 0) result.x = 8;\n  else if (result.x + popoverRect.width > viewport.width)\n    result.x = viewport.width - popoverRect.width - 8;\n\n  return { ...result, finalPlacement };\n}\n\n/**\n * Popover component with CSS Anchor Positioning and native Popover API\n *\n * Displays content relative to a trigger element with automatic viewport collision\n * handling via CSS anchor positioning. Falls back to JS positioning in unsupported browsers.\n *\n * @example\n * <button data-popover-trigger>Open Popover</button>\n * <ren-popover placement=\"bottom\" offset=\"8\">\n *   <div class=\"ren-popover-header\">Title</div>\n *   <div class=\"ren-popover-body\">Content</div>\n * </ren-popover>\n *\n * @fires ren-open - Fired when popover opens\n * @fires ren-close - Fired when popover closes\n */\nexport class RenPopover extends HTMLElement {\n  static observedAttributes = ['placement'];\n  static supportsAnchor = supportsAnchorPositioning();\n\n  #trigger = null;\n  #triggerController = null;\n  #dismissController = null;\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'placement' && oldValue !== newValue) {\n      this.#syncPlacement();\n    }\n  }\n\n  connectedCallback() {\n    this.setupPopover();\n    this.findTrigger();\n    this.attachTriggerListener();\n  }\n\n  disconnectedCallback() {\n    this.cleanup();\n  }\n\n  /**\n   * Setup popover element with required attributes\n   * @private\n   */\n  setupPopover() {\n    this.classList.add('ren-popover');\n\n    if (!this.id) {\n      this.id = `ren-popover-${++nextPopoverId}`;\n    }\n\n    // Add arrow if not present\n    if (!this.querySelector('.ren-popover-arrow')) {\n      const arrow = document.createElement('div');\n      arrow.className = 'ren-popover-arrow';\n      this.appendChild(arrow);\n    }\n\n    // Setup native Popover API\n    if ('popover' in HTMLElement.prototype) {\n      this.setAttribute('popover', 'manual');\n    }\n\n    // Set accessibility attributes\n    this.setAttribute('role', 'dialog');\n    this.setAttribute('aria-modal', 'false');\n    this.#syncPlacement();\n  }\n\n  /**\n   * Find the trigger element\n   * @private\n   */\n  findTrigger() {\n    // Explicit trigger ID\n    const triggerId = this.getAttribute('trigger-id');\n    if (triggerId) {\n      this.#trigger = document.getElementById(triggerId);\n    }\n\n    // Query selector for data-popover-trigger\n    if (!this.#trigger) {\n      this.#trigger = this.querySelector('[data-popover-trigger]');\n    }\n\n    // Fall back to previous sibling\n    if (!this.#trigger) {\n      this.#trigger = this.previousElementSibling;\n    }\n  }\n\n  /**\n   * Attach trigger listener and setup anchor relationship\n   * @private\n   */\n  attachTriggerListener() {\n    if (!this.#trigger) return;\n\n    this.#triggerController?.abort();\n    this.#triggerController = new AbortController();\n\n    // Set up anchor relationship if CSS anchors are supported\n    if (RenPopover.supportsAnchor) {\n      this.#trigger.style.anchorName = '--popover-anchor';\n    } else {\n      // Fallback: ensure trigger can be positioned relative to\n      if (getComputedStyle(this.#trigger.parentElement).position === 'static') {\n        this.#trigger.parentElement.style.position = 'relative';\n      }\n    }\n\n    // Wire up popovertarget if not already set\n    if (!this.#trigger.hasAttribute('popovertarget')) {\n      this.#trigger.setAttribute('popovertarget', this.id);\n    }\n    this.#trigger.setAttribute('aria-haspopup', 'dialog');\n    this.#trigger.setAttribute('aria-controls', this.id);\n    this.#trigger.setAttribute('aria-expanded', this.isOpen() ? 'true' : 'false');\n\n    // Click handler\n    this.#trigger.addEventListener(\n      'click',\n      (e) => {\n        e.preventDefault();\n        e.stopPropagation();\n        this.toggle();\n      },\n      { signal: this.#triggerController.signal }\n    );\n\n    // Setup dismiss behavior (click outside, Escape key)\n    this.#dismissController?.abort();\n    this.#dismissController = new AbortController();\n\n    document.addEventListener(\n      'click',\n      (e) => {\n        if (\n          this.isOpen() &&\n          e.target !== this &&\n          !this.contains(e.target) &&\n          e.target !== this.#trigger &&\n          !this.#trigger?.contains(e.target)\n        ) {\n          this.close();\n        }\n      },\n      { signal: this.#dismissController.signal }\n    );\n\n    document.addEventListener(\n      'keydown',\n      (e) => {\n        if (e.key === 'Escape' && this.isOpen()) {\n          this.close();\n        }\n      },\n      { signal: this.#dismissController.signal }\n    );\n  }\n\n  /**\n   * Position popover relative to trigger (fallback for non-anchor browsers)\n   * @private\n   */\n  positionPopover() {\n    if (!this.#trigger || RenPopover.supportsAnchor) return;\n\n    const placement = normalizePlacement(this.getAttribute('placement'), 'bottom');\n    const offset = parseInt(this.getAttribute('offset')) || 8;\n\n    const { x, y, finalPlacement } = computePosition(\n      this.#trigger,\n      this,\n      placement,\n      offset\n    );\n\n    this.style.left = `${x}px`;\n    this.style.top = `${y}px`;\n    this.setAttribute('data-side', finalPlacement);\n  }\n\n  #syncPlacement() {\n    const placement = normalizePlacement(this.getAttribute('placement'), 'bottom');\n    this.setAttribute('data-side', placement);\n  }\n\n  /**\n   * Open the popover\n   */\n  open() {\n    if (this.isOpen()) return;\n\n    this.positionPopover();\n    this.setAttribute('data-state', 'open');\n\n    if ('popover' in HTMLElement.prototype) {\n      try {\n        this.showPopover();\n      } catch {\n        // Already open or other error\n      }\n    } else {\n      this.classList.add('ren-open');\n    }\n\n    // Popover is explicitly non-modal (see component.md).\n    // No aria-modal or focus trap — those are for ren-dialog / ren-sheet.\n    this.setAttribute('aria-modal', 'false');\n    this.#trigger?.setAttribute('aria-expanded', 'true');\n    // Note: no focus trap for non-modal popover.\n    requestAnimationFrame(() => this.#focusInitialElement());\n    this.dispatchEvent(new CustomEvent('ren-open', { bubbles: true }));\n  }\n\n  /**\n   * Close the popover\n   */\n  close() {\n    if (!this.isOpen()) return;\n\n    const activeElement = document.activeElement;\n    const shouldRestoreFocus =\n      this.#trigger &&\n      activeElement instanceof HTMLElement &&\n      this.contains(activeElement);\n\n    this.setAttribute('data-state', 'closed');\n\n    if ('popover' in HTMLElement.prototype) {\n      try {\n        this.hidePopover();\n      } catch {\n        // Already closed or other error\n      }\n    } else {\n      this.classList.remove('ren-open');\n    }\n\n    this.setAttribute('aria-modal', 'false');\n    this.#trigger?.setAttribute('aria-expanded', 'false');\n    if (shouldRestoreFocus) {\n      this.#trigger.focus({ preventScroll: true });\n    }\n    this.dispatchEvent(new CustomEvent('ren-close', { bubbles: true }));\n  }\n\n  /**\n   * Toggle popover open/closed state\n   */\n  toggle() {\n    if (this.isOpen()) {\n      this.close();\n    } else {\n      this.open();\n    }\n  }\n\n  /**\n   * Check if popover is currently open\n   * @returns {boolean}\n   */\n  isOpen() {\n    if ('popover' in HTMLElement.prototype) {\n      return this.matches(':popover-open');\n    }\n    return this.classList.contains('ren-open');\n  }\n\n  /**\n   * Cleanup event listeners\n   * @private\n   */\n  cleanup() {\n    this.#triggerController?.abort();\n    this.#triggerController = null;\n    this.#dismissController?.abort();\n    this.#dismissController = null;\n  }\n\n  #focusInitialElement() {\n    const target = this.querySelector(FOCUSABLE_SELECTOR) || this;\n    if (target === this && !this.hasAttribute('tabindex')) {\n      this.setAttribute('tabindex', '-1');\n    }\n    target.focus({ preventScroll: true });\n  }\n\n  /**\n   * Get the trigger element\n   * @returns {HTMLElement|null}\n   */\n  getTrigger() {\n    return this.#trigger;\n  }\n\n  /**\n   * Set the trigger element\n   * @param {HTMLElement} trigger\n   */\n  setTrigger(trigger) {\n    this.#trigger = trigger;\n    this.attachTriggerListener();\n  }\n}\n\nif (!customElements.get('ren-popover')) {\n  customElements.define('ren-popover', RenPopover);\n}\n",
      "path": "components/composites/ren-popover/ren-popover.js",
      "id": "file:components/composites/ren-popover/ren-popover.js",
      "type": "javascript",
      "name": "ren-popover.js"
    },
    {
      "data": {},
      "body": ".ren-scroll-area {\n  position: relative;\n  overflow: auto;\n\n  scrollbar-width: thin;\n  scrollbar-color: var(--color-fill-active) transparent;\n\n  transition: scrollbar-color var(--duration-enter) var(--ease-enter);\n\n  &::-webkit-scrollbar {\n    width: 8px;\n    height: 8px;\n  }\n\n  &::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background: var(--color-fill-active);\n    border-radius: var(--radius-full);\n    border: 2px solid transparent;\n    background-clip: content-box;\n\n    transition: var(--transition-tactile);\n  }\n\n  &::-webkit-scrollbar-thumb:hover {\n    background-color: var(--color-fill-hover);\n    background-clip: content-box;\n  }\n\n  &::-webkit-scrollbar-corner {\n    background: transparent;\n  }\n\n  /* Hover state for standard scrollbar */\n  &:hover {\n    scrollbar-color: var(--color-fill-hover) transparent;\n  }\n\n  /* Variants */\n  &.-auto {\n    scrollbar-width: none;\n\n    &::-webkit-scrollbar {\n      display: none;\n    }\n\n    /* Show on scroll */\n    &:is(:hover, :focus-within) {\n      scrollbar-width: thin;\n      scrollbar-color: var(--color-fill-active) transparent;\n\n      &::-webkit-scrollbar {\n        display: block;\n      }\n    }\n  }\n\n  &.-always {\n    scrollbar-width: thin;\n    scrollbar-color: var(--color-fill-active) transparent;\n\n    &::-webkit-scrollbar {\n      display: block;\n    }\n  }\n\n  &.-hidden {\n    scrollbar-width: none;\n\n    &::-webkit-scrollbar {\n      display: none;\n    }\n  }\n\n  /* Direction variants */\n  &.-x {\n    overflow-x: auto;\n    overflow-y: hidden;\n  }\n\n  &.-y {\n    overflow-x: hidden;\n    overflow-y: auto;\n  }\n\n  /* Fade edges for smooth content transitions */\n  &.-fade {\n    position: relative;\n\n    &::before {\n      content: '';\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      height: 20px;\n\n      pointer-events: none;\n      background: linear-gradient(\n        to bottom,\n        var(--color-surface) 0%,\n        transparent 100%\n      );\n\n      z-index: 10;\n    }\n\n    &::after {\n      content: '';\n      position: absolute;\n      bottom: 0;\n      left: 0;\n      right: 0;\n      height: 20px;\n\n      pointer-events: none;\n      background: linear-gradient(\n        to top,\n        var(--color-surface) 0%,\n        transparent 100%\n      );\n\n      z-index: 10;\n    }\n  }\n\n  /* Horizontal fade */\n  &.-fade-x {\n    position: relative;\n\n    &::before {\n      content: '';\n      position: absolute;\n      top: 0;\n      left: 0;\n      bottom: 0;\n      width: 20px;\n\n      pointer-events: none;\n      background: linear-gradient(\n        to right,\n        var(--color-surface) 0%,\n        transparent 100%\n      );\n\n      z-index: 10;\n    }\n\n    &::after {\n      content: '';\n      position: absolute;\n      top: 0;\n      right: 0;\n      bottom: 0;\n      width: 20px;\n\n      pointer-events: none;\n      background: linear-gradient(\n        to left,\n        var(--color-surface) 0%,\n        transparent 100%\n      );\n\n      z-index: 10;\n    }\n  }\n\n  /* Max-height with custom property */\n  &[--scroll-max] {\n    max-height: var(--scroll-max, 20rem);\n  }\n\n  /* Smooth scrolling behavior */\n  scroll-behavior: smooth;\n\n  @media (prefers-reduced-motion: reduce) {\n    scroll-behavior: auto;\n  }\n}\n\n/* Utility class for common heights */\n.ren-scroll-area-sm {\n  max-height: 12rem;\n}\n\n.ren-scroll-area-md {\n  max-height: 20rem;\n}\n\n.ren-scroll-area-lg {\n  max-height: 30rem;\n}\n\n.ren-scroll-area-xl {\n  max-height: 40rem;\n}\n\n.ren-scroll-area-full {\n  height: 100%;\n  max-height: none;\n}\n",
      "path": "components/composites/ren-scroll-area/ren-scroll-area.css",
      "id": "file:components/composites/ren-scroll-area/ren-scroll-area.css",
      "type": "css",
      "name": "ren-scroll-area.css"
    },
    {
      "data": {},
      "body": "/* ═══════════════════════════════════════════════════════════════════\n   RenDS — Select Component\n   ═══════════════════════════════════════════════════════════════════\n   Custom, accessible select/dropdown component with full keyboard nav,\n   ARIA support, and multi-select capability.\n\n   Structure:\n   .ren-select (container)\n     .ren-select-trigger (button showing selected value)\n       .ren-select-value (the displayed text)\n       .ren-select-placeholder (when nothing selected)\n       .ren-select-icon (chevron)\n     .ren-select-content (dropdown listbox)\n       .ren-select-item (option)\n       .ren-select-group (optional grouping)\n       .ren-select-label (group header)\n       .ren-select-separator (divider between groups)\n   ═══════════════════════════════════════════════════════════════════ */\n\n/* ═══ SELECT CONTAINER ═══ */\n.ren-select {\n  position: relative;\n  display: inline-flex;\n  flex-direction: column;\n  width: 100%;\n  font-family: inherit;\n}\n\n/* ═══ SELECT TRIGGER (Button) ═══ */\n.ren-select-trigger {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--space-2);\n\n  /* Input styling */\n  width: 100%;\n  min-height: var(--touch-min);\n  padding: var(--space-2) var(--space-3);\n  font-size: var(--body-size);\n  font-family: inherit;\n  color: var(--color-text);\n  background-color: var(--color-input-bg);\n  border: var(--stroke-1) solid var(--color-input-border);\n  border-radius: var(--radius-md);\n  cursor: pointer;\n  text-align: start;\n  white-space: nowrap;\n\n  /* Semantic preset keeps trigger hover/focus in sync with other\n     tactile inputs (button, checkbox, text field). */\n  transition: var(--transition-tactile);\n}\n\n.ren-select-trigger:hover:not(:disabled):not([aria-expanded=\"true\"]) {\n  background-color: var(--color-input-bg-hover);\n}\n\n.ren-select-trigger:focus-visible {\n  outline: none;\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n  background-color: var(--color-input-bg-hover);\n}\n\n.ren-select-trigger[aria-expanded=\"true\"] {\n  border-color: var(--color-input-border-focus);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n  background-color: var(--color-input-bg-hover);\n}\n\n.ren-select-trigger:disabled {\n  background-color: var(--color-input-disabled-bg);\n  color: var(--color-input-disabled-text);\n  cursor: not-allowed;\n  opacity: 0.7;\n}\n\n/* ═══ SELECT VALUE / PLACEHOLDER ═══ */\n.ren-select-value {\n  flex: 1;\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  color: var(--color-text);\n}\n\n.ren-select-placeholder {\n  color: var(--color-input-placeholder);\n  flex: 1;\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n/* ═══ SELECT ICON (Chevron) ═══ */\n.ren-select-icon {\n  flex-shrink: 0;\n  width: 1.25rem;\n  height: 1.25rem;\n  color: var(--color-text-muted);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  transition: transform var(--duration-micro) var(--ease-enter);\n  pointer-events: none;\n}\n\n.ren-select-trigger[aria-expanded=\"true\"] .ren-select-icon {\n  transform: rotate(180deg);\n}\n\n/* SVG chevron icon styling */\n.ren-select-icon svg {\n  width: 100%;\n  height: 100%;\n  stroke-width: 2;\n  stroke-linecap: round;\n  stroke-linejoin: round;\n}\n\n/* ═══ SELECT CONTENT (Dropdown Listbox) ═══ */\n.ren-select-content {\n  position: absolute;\n  top: 100%;\n  left: 0;\n  right: 0;\n  margin-top: var(--space-1);\n  background: var(--color-surface);\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  padding-block: var(--space-2);\n  max-height: 15rem;\n  overflow-y: auto;\n  overflow-x: hidden;\n  overscroll-behavior: contain;\n  z-index: var(--z-dropdown, 1000);\n\n  visibility: hidden;\n  opacity: 0;\n  transform: translateY(-4px) scale(0.98);\n  animation: ren-select-close var(--duration-exit) var(--ease-exit) forwards;\n}\n\n/* ═══ SELECT CONTENT OPEN STATE ═══ */\n.ren-select-content[popover]:popover-open,\n.ren-select-content.ren-open {\n  visibility: visible;\n  opacity: 1;\n  animation: ren-select-open var(--duration-enter) var(--ease-enter);\n}\n\n@keyframes ren-select-open {\n  from {\n    opacity: 0;\n    transform: translateY(-4px) scale(0.98);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0) scale(1);\n  }\n}\n\n@keyframes ren-select-close {\n  from {\n    opacity: 1;\n    transform: translateY(0) scale(1);\n  }\n  to {\n    opacity: 0;\n    transform: translateY(-4px) scale(0.98);\n  }\n}\n\n/* ═══ SELECT ITEM (Option) ═══ */\n.ren-select-item {\n  padding: var(--space-2) var(--space-3);\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  cursor: pointer;\n  color: var(--color-text);\n  border-radius: var(--radius-sm);\n  /* Micro-duration keeps option hover snappy; tokens/semantic/motion\n     collapses this to 0ms under reduced-motion. */\n  transition:\n    background-color var(--duration-micro) var(--ease-enter),\n    color var(--duration-micro) var(--ease-enter);\n  white-space: nowrap;\n  user-select: none;\n  min-height: 2rem;\n}\n\n.ren-select-item:hover:not([aria-disabled=\"true\"]),\n.ren-select-item[data-highlighted] {\n  background-color: var(--color-fill);\n  color: var(--color-text);\n}\n\n.ren-select-item[aria-selected=\"true\"] {\n  background-color: var(--color-accent-subtle);\n  color: var(--color-accent);\n  font-weight: 500;\n}\n\n.ren-select-item[aria-selected=\"true\"]::before {\n  content: '';\n  flex-shrink: 0;\n  width: 1.25rem;\n  height: 1.25rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  background: var(--color-accent);\n  border-radius: var(--radius-sm);\n  position: relative;\n}\n\n.ren-select-item[aria-selected=\"true\"]::before {\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");\n  background-repeat: no-repeat;\n  background-position: center;\n  background-size: 70%;\n}\n\n.ren-select-item[aria-disabled=\"true\"] {\n  opacity: 0.5;\n  cursor: not-allowed;\n  pointer-events: none;\n}\n\n/* ═══ SELECT GROUP ═══ */\n.ren-select-group {\n  display: block;\n  padding-block: var(--space-1);\n}\n\n/* ═══ SELECT GROUP LABEL ═══ */\n.ren-select-label {\n  padding: var(--space-1) var(--space-3);\n  font-size: var(--caption-size);\n  font-weight: 600;\n  color: var(--color-text-muted);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  white-space: nowrap;\n  user-select: none;\n}\n\n/* ═══ SELECT SEPARATOR ═══ */\n.ren-select-separator {\n  height: var(--stroke-1);\n  background: var(--color-border);\n  margin-block: var(--space-1);\n}\n\n/* ═══ SELECT EMPTY STATE ═══ */\n.ren-select-empty {\n  padding: var(--space-3) var(--space-4);\n  text-align: center;\n  color: var(--color-text-muted);\n  font-size: var(--body-size);\n  white-space: nowrap;\n}\n\n/* ═══ SIZE VARIANTS ═══ */\n.ren-select-sm .ren-select-trigger {\n  min-height: var(--size-sm);\n  padding: var(--space-1) var(--space-2);\n  font-size: var(--text-sm);\n  border-radius: var(--radius-sm);\n}\n\n.ren-select-lg .ren-select-trigger {\n  min-height: var(--size-xl);\n  padding: var(--space-3) var(--space-4);\n  font-size: var(--text-lg);\n}\n\n/* ═══ MULTI-SELECT CHIPS/TAGS ═══ */\n.ren-select-chips {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: var(--space-1);\n  width: 100%;\n}\n\n.ren-select-chip {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-1);\n  padding: var(--space-1) var(--space-2);\n  background: var(--color-fill);\n  border-radius: var(--radius-sm);\n  font-size: var(--text-sm);\n  color: var(--color-text);\n  white-space: nowrap;\n}\n\n.ren-select-chip-remove {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 1rem;\n  height: 1rem;\n  cursor: pointer;\n  color: var(--color-text-muted);\n  transition: var(--transition-tactile);\n}\n\n.ren-select-chip-remove:hover {\n  color: var(--color-text);\n}\n\n/* ═══ REDUCED MOTION ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-select-trigger,\n  .ren-select-icon,\n  .ren-select-item {\n    transition: none;\n  }\n\n  .ren-select-content {\n    animation: none;\n    opacity: 0;\n  }\n\n  .ren-select-content.ren-open {\n    opacity: 1;\n  }\n}\n\n/* ═══ SCROLLBAR STYLING ═══ */\n.ren-select-content::-webkit-scrollbar {\n  width: 6px;\n}\n\n.ren-select-content::-webkit-scrollbar-track {\n  background: transparent;\n}\n\n.ren-select-content::-webkit-scrollbar-thumb {\n  background: var(--color-border);\n  border-radius: 3px;\n}\n\n.ren-select-content::-webkit-scrollbar-thumb:hover {\n  background: var(--color-text-muted);\n}\n\n/* ═══ NATIVE POPOVER API SUPPORT ═══ */\n@supports (selector(:popover-open)) {\n  .ren-select-content[popover] {\n    visibility: visible;\n    opacity: 0;\n    transition: opacity var(--duration-enter) var(--ease-enter),\n                overlay var(--duration-enter) var(--ease-enter) allow-discrete,\n                display var(--duration-enter) var(--ease-enter) allow-discrete;\n    animation: none;\n  }\n\n  @starting-style {\n    .ren-select-content[popover]:popover-open {\n      opacity: 0;\n    }\n  }\n\n  .ren-select-content[popover]:popover-open {\n    opacity: 1;\n  }\n}\n",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "file:components/composites/ren-select/ren-select.css",
      "type": "css",
      "name": "ren-select.css"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — Select Component\n * ========================\n *\n * A custom, fully accessible select/dropdown component that extends native\n * form capabilities with keyboard navigation, ARIA support, grouping,\n * and multi-select capability.\n *\n * Features:\n * - Keyboard navigation (arrow keys, Enter, Space, Escape, Typeahead)\n * - Full ARIA support (combobox, listbox, role=\"option\")\n * - Click-outside dismissal with roving tabindex\n * - Optional multi-select mode\n * - Option groups and separators\n * - Form submission integration\n * - Preferred placement with viewport-aware positioning\n * - Fallback styling for native select elements\n * - Respects prefers-reduced-motion\n *\n * @example\n * <ren-select placeholder=\"Choose an option\" name=\"country\">\n *   <button data-select-trigger>Select a country</button>\n *   <div data-select-content>\n *     <div data-select-item data-value=\"us\">United States</div>\n *     <div data-select-item data-value=\"ca\">Canada</div>\n *   </div>\n * </ren-select>\n *\n * @fires ren-select-change - Dispatched when selection changes\n * @fires change - Standard change event for form submission\n */\n\nimport { createKeyboardNav } from '../../../utils/keyboard-nav.js';\nimport { createDismissable } from '../../../utils/dismissable.js';\nimport { autoId } from '../../../utils/id-generator.js';\n\nconst SELECT_SIDES = new Set(['top', 'right', 'bottom', 'left']);\nconst SELECT_ALIGNS = new Set(['start', 'end']);\n\nfunction normalizeSelectPlacement(value) {\n  const [sideValue, alignValue] = String(value || 'bottom')\n    .toLowerCase()\n    .split('-');\n  const side = SELECT_SIDES.has(sideValue) ? sideValue : 'bottom';\n  const align = SELECT_ALIGNS.has(alignValue) ? alignValue : 'start';\n\n  return { side, align };\n}\n\n/**\n * Compute dropdown position relative to trigger element.\n * Falls back above if not enough space below.\n *\n * @private\n * @param {HTMLElement} trigger - Trigger button element\n * @param {HTMLElement} content - Dropdown content element\n * @param {Object} placement - Normalized placement object\n * @returns {Object} Position with top, left, flipped, side, and align properties\n */\nfunction computePosition(trigger, content, placement = normalizeSelectPlacement()) {\n  const triggerRect = trigger.getBoundingClientRect();\n  const contentRect = content.getBoundingClientRect();\n  const viewport = {\n    width: window.innerWidth,\n    height: window.innerHeight,\n  };\n  const gap = 8;\n  const margin = 16;\n\n  let finalSide = placement.side;\n  let flipped = false;\n  let top = triggerRect.bottom + gap;\n  let left = triggerRect.left;\n\n  if (placement.align === 'end') {\n    left = triggerRect.right - contentRect.width;\n  }\n\n  if (placement.side === 'top') {\n    top = triggerRect.top - contentRect.height - gap;\n  } else if (placement.side === 'right') {\n    top = placement.align === 'end'\n      ? triggerRect.bottom - contentRect.height\n      : triggerRect.top;\n    left = triggerRect.right + gap;\n  } else if (placement.side === 'left') {\n    top = placement.align === 'end'\n      ? triggerRect.bottom - contentRect.height\n      : triggerRect.top;\n    left = triggerRect.left - contentRect.width - gap;\n  }\n\n  if (\n    placement.side === 'bottom' &&\n    top + contentRect.height > viewport.height - margin &&\n    triggerRect.top - contentRect.height - gap >= margin\n  ) {\n    top = triggerRect.top - contentRect.height - gap;\n    finalSide = 'top';\n    flipped = true;\n  } else if (\n    placement.side === 'top' &&\n    top < margin &&\n    triggerRect.bottom + contentRect.height + gap <= viewport.height - margin\n  ) {\n    top = triggerRect.bottom + gap;\n    finalSide = 'bottom';\n    flipped = true;\n  } else if (\n    placement.side === 'right' &&\n    left + contentRect.width > viewport.width - margin &&\n    triggerRect.left - contentRect.width - gap >= margin\n  ) {\n    left = triggerRect.left - contentRect.width - gap;\n    finalSide = 'left';\n    flipped = true;\n  } else if (\n    placement.side === 'left' &&\n    left < margin &&\n    triggerRect.right + contentRect.width + gap <= viewport.width - margin\n  ) {\n    left = triggerRect.right + gap;\n    finalSide = 'right';\n    flipped = true;\n  }\n\n  if (left + contentRect.width > viewport.width - margin) {\n    left = viewport.width - contentRect.width - margin;\n  }\n  if (left < margin) {\n    left = margin;\n  }\n\n  if (top + contentRect.height > viewport.height - margin) {\n    top = viewport.height - contentRect.height - margin;\n  }\n  if (top < margin) {\n    top = margin;\n  }\n\n  return { top, left, flipped, side: finalSide, align: placement.align };\n}\n\n/**\n * Select Component\n *\n * Custom, accessible select/dropdown with keyboard nav and ARIA support.\n *\n * @class RenSelect\n * @extends HTMLElement\n */\nexport class RenSelect extends HTMLElement {\n  static observedAttributes = ['placement'];\n\n  #trigger = null;\n  #content = null;\n  #items = [];\n  #isOpen = false;\n  #selectedValue = null;\n  #selectedItem = null;\n  #keyboardNav = null;\n  #dismissable = null;\n  #hiddenInputs = [];\n  #animationFrame = null;\n  #listenerController = null;\n  #scrollController = null;\n\n  constructor() {\n    super();\n    this.positionContent = this.positionContent.bind(this);\n  }\n\n  /* ─────────────────────────────────────────────────────────────\n     LIFECYCLE\n     ───────────────────────────────────────────────────────────── */\n\n  connectedCallback() {\n    if (this.hasAttribute('multiple') && !Array.isArray(this.#selectedValue)) {\n      this.#selectedValue = [];\n    }\n    this.loadStyles();\n    this.setupComponent();\n    this.bindElements();\n    this.setupARIA();\n    this.setupHiddenInput();\n    this.attachListeners();\n  }\n\n  disconnectedCallback() {\n    this.cleanup();\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'placement' && oldValue !== newValue) {\n      this.syncPlacement();\n      if (this.#isOpen) {\n        this.positionContent();\n      }\n    }\n  }\n\n  /* ─────────────────────────────────────────────────────────────\n     SETUP & INITIALIZATION\n     ───────────────────────────────────────────────────────────── */\n\n  /**\n   * Load component CSS styles into document\n   * @private\n   */\n  loadStyles() {\n    if (!document.getElementById('ren-select-styles')) {\n      const style = document.createElement('style');\n      style.id = 'ren-select-styles';\n      style.textContent = this.constructor.styles || '';\n      document.head.appendChild(style);\n    }\n  }\n\n  /**\n   * Setup container and trigger elements\n   * @private\n   */\n  setupComponent() {\n    this.classList.add('ren-select');\n    this.syncPlacement();\n\n    // Check for size variant\n    if (this.hasAttribute('size')) {\n      this.classList.add(`ren-select-${this.getAttribute('size')}`);\n    }\n  }\n\n  /**\n   * Find and cache trigger and content elements\n   * @private\n   */\n  bindElements() {\n    // Try to find explicitly marked elements\n    this.#trigger = this.querySelector('[data-select-trigger], .ren-select-trigger');\n    this.#content = this.querySelector('[data-select-content], .ren-select-content');\n\n    // If not found, look for first button and first div (fallback)\n    if (!this.#trigger) {\n      this.#trigger = this.querySelector('button');\n    }\n\n    if (!this.#content) {\n      this.#content = this.querySelector('[role=\"listbox\"]');\n    }\n\n    // If still not found, create default structure\n    if (!this.#trigger || !this.#content) {\n      this.createDefaultStructure();\n    }\n\n    // Cache items\n    this.updateItems();\n    this.syncPlacement();\n  }\n\n  /**\n   * Create default trigger and content if not provided\n   * @private\n   */\n  createDefaultStructure() {\n    // Create trigger button\n    if (!this.#trigger) {\n      this.#trigger = document.createElement('button');\n      this.#trigger.className = 'ren-select-trigger';\n      this.#trigger.type = 'button';\n      this.appendChild(this.#trigger);\n    }\n\n    // Create content container\n    if (!this.#content) {\n      this.#content = document.createElement('div');\n      this.#content.className = 'ren-select-content';\n      this.#content.setAttribute('role', 'listbox');\n      this.appendChild(this.#content);\n    }\n  }\n\n  /**\n   * Setup ARIA attributes on trigger and content\n   * @private\n   */\n  setupARIA() {\n    autoId(this.#trigger, 'select-trigger');\n    const contentId = autoId(this.#content, 'select-content');\n\n    // Trigger setup\n    this.#trigger.setAttribute('role', 'combobox');\n    this.#trigger.setAttribute('aria-expanded', 'false');\n    this.#trigger.setAttribute('aria-haspopup', 'listbox');\n    this.#trigger.setAttribute('aria-controls', contentId);\n    this.#trigger.type = 'button';\n\n    // Content setup\n    this.#content.setAttribute('role', 'listbox');\n    this.#content.id = contentId;\n    if (this.hasAttribute('multiple')) {\n      this.#content.setAttribute('aria-multiselectable', 'true');\n    }\n\n    // Set aria-label on content based on trigger text or placeholder\n    const placeholder = this.getAttribute('placeholder') || 'Select an option';\n    if (!this.#content.getAttribute('aria-label')) {\n      this.#content.setAttribute('aria-label', placeholder);\n    }\n\n    // Wire up items\n    this.updateItemsARIA();\n  }\n\n  /**\n   * Update items ARIA attributes\n   * @private\n   */\n  updateItemsARIA() {\n    this.#items.forEach((item, index) => {\n      if (item.hasAttribute('disabled') || item.getAttribute('aria-disabled') === 'true') {\n        item.setAttribute('aria-disabled', 'true');\n      }\n\n      if (!item.getAttribute('role')) {\n        item.setAttribute('role', 'option');\n      }\n\n      // Ensure item has tabindex\n      if (!item.hasAttribute('tabindex')) {\n        item.setAttribute('tabindex', '-1');\n      }\n\n      // Set aria-selected based on current selection\n      const value = item.getAttribute('data-value');\n      const isSelected = this.hasAttribute('multiple')\n        ? this.#selectedValue.includes(value)\n        : value === this.#selectedValue;\n      item.setAttribute('aria-selected', isSelected ? 'true' : 'false');\n    });\n  }\n\n  /**\n   * Setup hidden input for form submission\n   * @private\n   */\n  setupHiddenInput() {\n    const name = this.getAttribute('name');\n    if (!name) return;\n\n    this.syncHiddenInputs();\n  }\n\n  /**\n   * Attach event listeners to trigger and content\n   * @private\n   */\n  attachListeners() {\n    this.#listenerController?.abort();\n    this.#listenerController = new AbortController();\n    const { signal } = this.#listenerController;\n\n    // Trigger click\n    this.#trigger.addEventListener('click', (e) => this.handleTriggerClick(e), { signal });\n\n    // Keyboard on trigger\n    this.#trigger.addEventListener('keydown', (e) => this.handleTriggerKeyDown(e), { signal });\n\n    // Item selection\n    this.#content.addEventListener('click', (e) => this.handleItemClick(e), { signal });\n\n    // Setup keyboard navigation in content\n    this.setupKeyboardNav();\n\n    // Setup click-outside dismissal\n    this.setupDismissable();\n\n    // Initialize selected value\n    const initialValue = this.getAttribute('value');\n    if (initialValue) {\n      this.selectValue(initialValue, false);\n    }\n  }\n\n  /**\n   * Setup keyboard navigation for select items\n   * @private\n   */\n  setupKeyboardNav() {\n    this.#keyboardNav = createKeyboardNav(this.#content, {\n      selector: '[role=\"option\"]',\n      orientation: 'vertical',\n      loop: true,\n      typeahead: true,\n      focusOnHover: true,\n      onActivate: (item) => {\n        // Item is highlighted\n      },\n      onSelect: (item) => {\n        this.selectItem(item);\n      },\n    });\n\n    // Don't attach yet; will attach when dropdown opens\n  }\n\n  /**\n   * Setup click-outside and Escape key dismissal\n   * @private\n   */\n  setupDismissable() {\n    this.#dismissable = createDismissable(this.#content, {\n      triggerElement: this.#trigger,\n      escapeKey: true,\n      clickOutside: true,\n      onDismiss: (reason) => {\n        this.close();\n      },\n    });\n  }\n\n  /**\n   * Update the list of items from the content element\n   * @private\n   */\n  updateItems() {\n    this.#items = Array.from(\n      this.#content.querySelectorAll('[role=\"option\"], [data-select-item], .ren-select-item')\n    );\n  }\n\n  /* ─────────────────────────────────────────────────────────────\n     EVENT HANDLERS\n     ───────────────────────────────────────────────────────────── */\n\n  /**\n   * Handle trigger button click\n   * @private\n   */\n  handleTriggerClick(e) {\n    e.preventDefault();\n    e.stopPropagation();\n\n    if (this.#isOpen) {\n      this.close();\n    } else {\n      this.open();\n    }\n  }\n\n  /**\n   * Handle keyboard events on trigger\n   * @private\n   */\n  handleTriggerKeyDown(e) {\n    const isOpen = this.#isOpen;\n\n    switch (e.key) {\n      case 'Enter':\n      case ' ':\n        e.preventDefault();\n        e.stopPropagation();\n\n        if (!isOpen) {\n          this.open();\n        }\n        break;\n\n      case 'ArrowDown':\n        e.preventDefault();\n        e.stopPropagation();\n\n        if (!isOpen) {\n          this.open();\n          // Focus first item\n          setTimeout(() => {\n            this.#keyboardNav?.activateFirst();\n          }, 0);\n        } else {\n          this.#keyboardNav?.activateNext();\n        }\n        break;\n\n      case 'ArrowUp':\n        e.preventDefault();\n        e.stopPropagation();\n\n        if (!isOpen) {\n          this.open();\n          // Focus last item\n          setTimeout(() => {\n            this.#keyboardNav?.activateLast();\n          }, 0);\n        } else {\n          this.#keyboardNav?.activatePrevious();\n        }\n        break;\n\n      case 'Escape':\n        if (isOpen) {\n          e.preventDefault();\n          e.stopPropagation();\n          this.close();\n        }\n        break;\n\n      case 'ArrowLeft':\n      case 'ArrowRight':\n      case 'Tab':\n        // Don't intercept these; let default behavior\n        break;\n\n      default:\n        // Let other keys through\n        break;\n    }\n  }\n\n  /**\n   * Handle item click\n   * @private\n   */\n  handleItemClick(e) {\n    const item = e.target.closest('[role=\"option\"]');\n    if (item && !item.hasAttribute('disabled') && item.getAttribute('aria-disabled') !== 'true') {\n      e.preventDefault();\n      e.stopPropagation();\n      this.selectItem(item);\n    }\n  }\n\n  /* ─────────────────────────────────────────────────────────────\n     OPENING / CLOSING\n     ───────────────────────────────────────────────────────────── */\n\n  /**\n   * Open the dropdown\n   */\n  open() {\n    if (this.#isOpen || this.#trigger.disabled) return;\n\n    this.#isOpen = true;\n\n    // Show content\n    this.#content.classList.add('ren-open');\n    this.setAttribute('data-state', 'open');\n    this.#trigger.setAttribute('aria-expanded', 'true');\n\n    // Try native popover API\n    if ('popover' in HTMLElement.prototype && this.#content.hasAttribute('popover')) {\n      try {\n        this.#content.showPopover();\n      } catch {\n        // Fallback to CSS class\n      }\n    }\n\n    // Attach keyboard nav\n    if (this.#keyboardNav) {\n      this.#keyboardNav.attach();\n      // Activate selected or first item\n      const selectedIndex = this.#items.findIndex((item) => {\n        const value = item.getAttribute('data-value');\n        return this.hasAttribute('multiple')\n          ? this.#selectedValue.includes(value)\n          : value === this.#selectedValue;\n      });\n      if (selectedIndex !== -1) {\n        this.#keyboardNav.setActiveIndex(selectedIndex);\n      } else if (this.#items.length > 0) {\n        this.#keyboardNav.activateFirst();\n      }\n    }\n\n    // Activate dismissable layer\n    if (this.#dismissable) {\n      this.#dismissable.activate();\n    }\n\n    // Position content\n    this.positionContent();\n\n    // Reposition on scroll\n    this.#scrollController?.abort();\n    this.#scrollController = new AbortController();\n    window.addEventListener('scroll', this.positionContent, {\n      capture: true,\n      signal: this.#scrollController.signal,\n    });\n\n    // Announce results count for accessibility\n    const resultCount = this.#items.length;\n    const announcement = document.createElement('div');\n    announcement.setAttribute('role', 'status');\n    announcement.setAttribute('aria-live', 'polite');\n    announcement.className = 'ren-sr-only';\n    announcement.textContent = `${resultCount} option${resultCount !== 1 ? 's' : ''} available`;\n    this.appendChild(announcement);\n    setTimeout(() => announcement.remove(), 1000);\n\n    this.dispatchEvent(new CustomEvent('ren-select-open', { bubbles: true }));\n  }\n\n  /**\n   * Close the dropdown\n   */\n  close() {\n    if (!this.#isOpen) return;\n\n    this.#isOpen = false;\n\n    // Hide content\n    this.#content.classList.remove('ren-open');\n    this.setAttribute('data-state', 'closed');\n    this.#trigger.setAttribute('aria-expanded', 'false');\n\n    // Try native popover API\n    if ('popover' in HTMLElement.prototype && this.#content.hasAttribute('popover')) {\n      try {\n        this.#content.hidePopover();\n      } catch {\n        // Fallback\n      }\n    }\n\n    // Detach keyboard nav\n    if (this.#keyboardNav) {\n      this.#keyboardNav.detach();\n    }\n\n    // Deactivate dismissable layer\n    if (this.#dismissable) {\n      this.#dismissable.deactivate();\n    }\n\n    // Remove scroll listener\n    this.#scrollController?.abort();\n    this.#scrollController = null;\n\n    // Return focus to trigger\n    this.#trigger.focus();\n\n    this.dispatchEvent(new CustomEvent('ren-select-close', { bubbles: true }));\n  }\n\n  /**\n   * Position dropdown content below or above trigger\n   * @private\n   */\n  positionContent() {\n    const placement = normalizeSelectPlacement(this.getAttribute('placement'));\n    const { top, left, flipped, side, align } = computePosition(this.#trigger, this.#content, placement);\n\n    this.#content.style.position = 'fixed';\n    this.#content.style.top = `${top}px`;\n    this.#content.style.left = `${left}px`;\n    this.#content.style.width = `${this.#trigger.offsetWidth}px`;\n    this.#content.setAttribute('data-flipped', flipped ? 'true' : 'false');\n    this.#content.setAttribute('data-side', side);\n    this.#content.setAttribute('data-align', align);\n    this.setAttribute('data-side', side);\n    this.setAttribute('data-align', align);\n  }\n\n  /**\n   * Mirror preferred placement to public data attributes.\n   */\n  syncPlacement() {\n    const { side, align } = normalizeSelectPlacement(this.getAttribute('placement'));\n\n    this.setAttribute('data-side', side);\n    this.setAttribute('data-align', align);\n    if (this.#content) {\n      this.#content.setAttribute('data-side', side);\n      this.#content.setAttribute('data-align', align);\n    }\n  }\n\n  /* ─────────────────────────────────────────────────────────────\n     SELECTION\n     ───────────────────────────────────────────────────────────── */\n\n  /**\n   * Select an item by element\n   * @private\n   */\n  selectItem(item) {\n    const value = item.getAttribute('data-value');\n    const label = item.textContent?.trim() || '';\n\n    if (this.hasAttribute('multiple')) {\n      const values = [...this.#selectedValue];\n      const selectedIndex = values.indexOf(value);\n      if (selectedIndex === -1) values.push(value);\n      else values.splice(selectedIndex, 1);\n      this.selectValue(values, true);\n    } else {\n      this.selectValue(value, true);\n    }\n\n    this.dispatchEvent(\n      new CustomEvent('ren-select-change', {\n        bubbles: true,\n        detail: { value: this.value, label, item },\n      })\n    );\n\n    // Emit standard change event for forms\n    this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));\n\n    // Close dropdown (unless multi-select)\n    if (!this.hasAttribute('multiple')) {\n      this.close();\n    }\n  }\n\n  /**\n   * Select by value\n   * @private\n   */\n  selectValue(value, updateTrigger = true) {\n    if (this.hasAttribute('multiple')) {\n      const values = Array.isArray(value) ? value : (value == null ? [] : [value]);\n      this.#selectedValue = [...new Set(values.map(String))];\n    } else {\n      this.#selectedValue = value == null ? null : String(value);\n    }\n\n    // Find and update the corresponding item\n    const selectedValues = this.hasAttribute('multiple') ? this.#selectedValue : [this.#selectedValue];\n    const item = this.#items.find((el) => selectedValues.includes(el.getAttribute('data-value')));\n    if (item) {\n      this.#selectedItem = item;\n    } else {\n      this.#selectedItem = null;\n    }\n\n    // Update ARIA\n    this.updateItemsARIA();\n\n    // Update trigger display\n    if (updateTrigger) {\n      this.updateTriggerDisplay();\n    }\n\n    this.syncHiddenInputs();\n  }\n\n  syncHiddenInputs() {\n    const name = this.getAttribute('name');\n    this.querySelectorAll('input[type=\"hidden\"][data-ren-select-input]').forEach((input) => input.remove());\n    this.#hiddenInputs = [];\n    if (!name) return;\n\n    const values = this.hasAttribute('multiple')\n      ? this.#selectedValue\n      : [this.#selectedValue || ''];\n    values.forEach((value) => {\n      const input = document.createElement('input');\n      input.type = 'hidden';\n      input.name = name;\n      input.value = value;\n      input.setAttribute('data-ren-select-input', '');\n      this.appendChild(input);\n      this.#hiddenInputs.push(input);\n    });\n  }\n\n  /**\n   * Update the trigger button text/icon display\n   * @private\n   */\n  updateTriggerDisplay() {\n    const placeholder = this.getAttribute('placeholder') || 'Select an option';\n\n    this.querySelector(':scope > .ren-select-chips[data-ren-select-chips]')?.remove();\n\n    // Clear trigger content\n    this.#trigger.innerHTML = '';\n\n    if (this.hasAttribute('multiple') && this.#selectedValue.length > 0) {\n      const chips = document.createElement('span');\n      chips.className = 'ren-select-chips';\n      chips.setAttribute('data-ren-select-chips', '');\n      const selectedLabels = [];\n      this.#selectedValue.forEach((value) => {\n        const item = this.#items.find((option) => option.getAttribute('data-value') === value);\n        if (!item) return;\n\n        const chip = document.createElement('span');\n        chip.className = 'ren-select-chip';\n        const label = document.createElement('span');\n        label.textContent = item.textContent?.trim() || value;\n        selectedLabels.push(label.textContent);\n        const remove = document.createElement('button');\n        remove.type = 'button';\n        remove.className = 'ren-select-chip-remove';\n        remove.dataset.value = value;\n        remove.setAttribute('aria-label', `Remove ${label.textContent}`);\n        remove.textContent = '×';\n        remove.addEventListener('click', (event) => {\n          event.preventDefault();\n          event.stopPropagation();\n          this.selectValue(this.#selectedValue.filter((selected) => selected !== value), true);\n          this.dispatchEvent(new CustomEvent('ren-select-change', {\n            bubbles: true,\n            detail: { value: this.value, label: label.textContent, item },\n          }));\n          this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));\n        });\n        chip.append(label, remove);\n        chips.appendChild(chip);\n      });\n      this.insertBefore(chips, this.#trigger);\n\n      const valueSpan = document.createElement('span');\n      valueSpan.className = 'ren-select-value';\n      valueSpan.textContent = selectedLabels.join(', ');\n      this.#trigger.appendChild(valueSpan);\n    } else if (!this.#selectedValue || !this.#selectedItem) {\n      // Show placeholder\n      const placeholderSpan = document.createElement('span');\n      placeholderSpan.className = 'ren-select-placeholder';\n      placeholderSpan.textContent = placeholder;\n      this.#trigger.appendChild(placeholderSpan);\n    } else {\n      // Show selected value\n      const valueSpan = document.createElement('span');\n      valueSpan.className = 'ren-select-value';\n      valueSpan.textContent = this.#selectedItem.textContent?.trim() || '';\n      this.#trigger.appendChild(valueSpan);\n    }\n\n    // Add chevron icon\n    const icon = document.createElement('span');\n    icon.className = 'ren-select-icon';\n    icon.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"></polyline></svg>`;\n    this.#trigger.appendChild(icon);\n  }\n\n  /* ─────────────────────────────────────────────────────────────\n     CLEANUP\n     ───────────────────────────────────────────────────────────── */\n\n  /**\n   * Cleanup event listeners and resources\n   * @private\n   */\n  cleanup() {\n    if (this.#keyboardNav) {\n      this.#keyboardNav.detach();\n    }\n\n    if (this.#dismissable) {\n      this.#dismissable.deactivate();\n    }\n\n    if (this.#animationFrame) {\n      cancelAnimationFrame(this.#animationFrame);\n    }\n\n    this.#scrollController?.abort();\n    this.#scrollController = null;\n    this.#listenerController?.abort();\n    this.#listenerController = null;\n  }\n\n  /* ─────────────────────────────────────────────────────────────\n     PUBLIC API\n     ───────────────────────────────────────────────────────────── */\n\n  /**\n   * Get current selected value\n   * @returns {string|string[]|null} Selected value, ordered values, or null\n   */\n  get value() {\n    return Array.isArray(this.#selectedValue) ? [...this.#selectedValue] : this.#selectedValue;\n  }\n\n  /**\n   * Set value programmatically\n   * @param {string|string[]} value - Value or ordered values to select\n   */\n  set value(value) {\n    this.selectValue(value, true);\n  }\n\n  /**\n   * Get current selected item element\n   * @returns {HTMLElement|null}\n   */\n  get selectedOption() {\n    return this.#selectedItem || null;\n  }\n\n  /**\n   * Check if dropdown is open\n   * @returns {boolean}\n   */\n  get isOpen() {\n    return this.#isOpen;\n  }\n\n  /**\n   * Get all option items\n   * @returns {HTMLElement[]}\n   */\n  get options() {\n    return [...this.#items];\n  }\n\n  /**\n   * Refresh items from DOM (call after dynamically adding items)\n   */\n  refresh() {\n    this.updateItems();\n    this.updateItemsARIA();\n\n    if (this.#keyboardNav) {\n      this.#keyboardNav.refresh?.();\n    }\n  }\n}\n\n// Register the custom element\nif (!customElements.get('ren-select')) {\n  customElements.define('ren-select', RenSelect);\n}\n",
      "path": "components/composites/ren-select/ren-select.js",
      "id": "file:components/composites/ren-select/ren-select.js",
      "type": "javascript",
      "name": "ren-select.js"
    },
    {
      "data": {},
      "body": "/* The custom-element host is structural only. Keeping it out of layout prevents\n   an empty grid/flex item around the real dialog generated in light DOM. */\nren-sheet {\n  display: contents;\n}\n\n/* Hide the dialog when it isn't open. The browser default for\n   `<dialog>` without `open` is `display: none`, but the rule below\n   sets `display: flex` for layout, so without this explicit reset\n   the sheet would render in-flow on every page load. */\n.ren-sheet:not([open]) {\n  display: none;\n}\n\n.ren-sheet {\n  position: fixed;\n  margin: 0;\n  padding: 0;\n  border: none;\n\n  background: var(--color-surface);\n  color: var(--color-text);\n  border-radius: 0;\n\n  /* Header/body/footer stack: body grows, footer pins to the bottom */\n  display: flex;\n  flex-direction: column;\n\n  z-index: 50;\n  max-width: 100%;\n  max-height: 100%;\n  touch-action: none; /* Prevent scroll chaining; swipe-to-dismiss handled by JS */\n\n  &[data-side='right'] {\n    inset-inline-end: 0;\n    top: 0;\n    bottom: 0;\n\n    width: min(24rem, 90vw);\n    height: 100dvh;\n\n    @starting-style {\n      translate: 100% 0;\n    }\n\n    translate: 0 0;\n  }\n\n  &[data-side='left'] {\n    inset-inline-start: 0;\n    top: 0;\n    bottom: 0;\n\n    width: min(24rem, 90vw);\n    height: 100dvh;\n\n    @starting-style {\n      translate: -100% 0;\n    }\n\n    translate: 0 0;\n  }\n\n  &[data-side='bottom'] {\n    bottom: 0;\n    inset-inline: 0;\n\n    width: 100vw;\n    max-height: 85dvh;\n\n    @starting-style {\n      translate: 0 100%;\n    }\n\n    translate: 0 0;\n  }\n\n  &[data-side='top'] {\n    top: 0;\n    inset-inline: 0;\n\n    width: 100vw;\n    max-height: 85dvh;\n\n    @starting-style {\n      translate: 0 -100%;\n    }\n\n    translate: 0 0;\n  }\n\n  /* Size variants */\n  &.-sm {\n    width: min(18rem, 90vw);\n  }\n\n  &.-md {\n    width: min(24rem, 90vw);\n  }\n\n  &.-lg {\n    width: min(32rem, 90vw);\n  }\n\n  &.-xl {\n    width: min(42rem, 90vw);\n  }\n\n  &.-full {\n    width: 100vw;\n    height: 100dvh;\n  }\n\n  /* Transitions — semantic tokens keep sheet in sync with ren-dialog and\n     friends; --duration-enter / --ease-enter collapse to 0ms under\n     prefers-reduced-motion via tokens/semantic/motion.css. */\n  transition: translate var(--duration-enter) var(--ease-enter),\n    opacity var(--duration-enter) var(--ease-enter);\n  transition-behavior: allow-discrete;\n\n  /* Reduce motion */\n  @media (prefers-reduced-motion: reduce) {\n    transition: none;\n\n    @starting-style {\n      opacity: 0;\n    }\n\n    opacity: 1;\n  }\n\n  /* Shadow on appropriate edge */\n  &[data-side='right'] {\n    box-shadow: -12px 0 32px rgb(0, 0, 0, 0.12);\n  }\n\n  &[data-side='left'] {\n    box-shadow: 12px 0 32px rgb(0, 0, 0, 0.12);\n  }\n\n  &[data-side='bottom'] {\n    box-shadow: 0 -12px 32px rgb(0, 0, 0, 0.12);\n  }\n\n  &[data-side='top'] {\n    box-shadow: 0 12px 32px rgb(0, 0, 0, 0.12);\n  }\n\n  /* Backdrop animations — enter uses --duration-enter / --ease-enter,\n     close uses the exit pair so the scrim fades at a slightly brisker\n     clip than the entry, matching the rest of the overlay family. */\n  &[open]::backdrop {\n    animation: ren-sheet-backdrop-open var(--duration-enter) var(--ease-enter) forwards;\n  }\n\n  &::backdrop {\n    @starting-style {\n      opacity: 0;\n    }\n\n    opacity: 1;\n    background: var(--color-overlay);\n    backdrop-filter: blur(4px);\n\n    animation: ren-sheet-backdrop-close var(--duration-exit) var(--ease-exit) forwards;\n  }\n\n  &:modal::backdrop {\n    background: var(--color-overlay);\n    backdrop-filter: blur(4px);\n  }\n}\n\n.ren-sheet-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n\n  padding: var(--space-6);\n  padding-block: var(--space-4);\n\n  border-bottom: 1px solid var(--color-border);\n}\n\n.ren-sheet-title {\n  font-size: var(--text-lg);\n  font-weight: 600;\n  color: var(--color-text);\n  margin: 0;\n}\n\n.ren-sheet-description {\n  font-size: var(--text-sm);\n  color: var(--color-text-muted);\n  margin: var(--space-2) 0 0 0;\n}\n\n.ren-sheet-close {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  width: var(--space-8);\n  height: var(--space-8);\n\n  padding: 0;\n  border: none;\n  background: transparent;\n  cursor: pointer;\n\n  font-size: 1.5rem;\n  color: var(--color-text-muted);\n\n  transition: var(--transition-tactile);\n\n  &:hover {\n    color: var(--color-text);\n  }\n\n  &:active {\n    color: var(--color-text-muted);\n  }\n}\n\n.ren-sheet-body {\n  flex: 1;\n  padding: var(--space-6);\n\n  overflow-y: auto;\n  overflow-x: hidden;\n\n  /* Scrollbar styling */\n  scrollbar-width: thin;\n  scrollbar-color: var(--color-fill-active) transparent;\n\n  &::-webkit-scrollbar {\n    width: 6px;\n  }\n\n  &::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background: var(--color-fill-active);\n    border-radius: var(--radius-full);\n  }\n\n  &::-webkit-scrollbar-thumb:hover {\n    background: var(--color-fill-hover);\n  }\n}\n\n.ren-sheet-footer {\n  display: flex;\n  gap: var(--space-3);\n\n  padding: var(--space-6);\n  padding-block: var(--space-4);\n\n  border-top: 1px solid var(--color-border);\n}\n\n.ren-sheet-handle {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  width: 100%;\n  height: var(--space-3);\n\n  margin-bottom: var(--space-4);\n\n  &::before {\n    content: '';\n    display: block;\n\n    width: 40px;\n    height: 4px;\n\n    background: var(--color-fill);\n    border-radius: var(--radius-full);\n  }\n}\n\n@keyframes ren-sheet-backdrop-open {\n  from {\n    opacity: 0;\n  }\n\n  to {\n    opacity: 1;\n  }\n}\n\n@keyframes ren-sheet-backdrop-close {\n  from {\n    opacity: 1;\n  }\n\n  to {\n    opacity: 0;\n  }\n}\n/* Appearance API bridge */\n.ren-sheet {\n  width: var(--ren-sheet-width, 320px);\n  padding: var(--ren-sheet-padding, var(--space-6));\n  border-radius: var(--ren-sheet-radius, 0);\n  background: var(--ren-sheet-bg, var(--color-surface-overlay));\n  border-color: var(--ren-sheet-border, var(--color-border));\n  box-shadow: var(--ren-sheet-shadow, var(--shadow-xl));\n  transition-duration: var(--ren-sheet-duration, var(--duration-normal));\n  transition-timing-function: var(--ren-sheet-easing, var(--ease-out));\n}\n.ren-sheet::backdrop { background: var(--ren-sheet-backdrop, var(--color-overlay)); }\n",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "file:components/composites/ren-sheet/ren-sheet.css",
      "type": "css",
      "name": "ren-sheet.css"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — <ren-sheet> Web Component\n * ==================================\n * Edge-anchored modal panel. Light DOM only — wraps the consumer's children\n * in a native <dialog class=\"ren-sheet\"> so we get focus trap, ::backdrop,\n * Esc handling, and inert-on-body for free.\n *\n * Markup:\n *   <ren-sheet side=\"right\" id=\"filters\">\n *     <header class=\"ren-sheet-header\">\n *       <h2 class=\"ren-sheet-title\">Filters</h2>\n *       <button class=\"ren-sheet-close\" data-sheet-close aria-label=\"Close\">×</button>\n *     </header>\n *     <div class=\"ren-sheet-body\"> … </div>\n *     <footer class=\"ren-sheet-footer\"> … </footer>\n *   </ren-sheet>\n *\n *   <button data-sheet-trigger=\"filters\">Open</button>\n *\n * Attributes:\n *   side         — \"right\" | \"left\" | \"top\" | \"bottom\" (default \"right\")\n *   size         — \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\" (default \"md\")\n *   open         — reflects show()/close()\n *   dismissible  — if \"false\", only the close button dismisses\n *\n * Methods:\n *   .show()\n *   .close()\n *   .open  (getter)\n *\n * Events (bubble):\n *   ren-open\n *   ren-close\n *\n * Light-DOM hooks:\n *   [data-sheet-trigger=\"<id>\"]  anywhere on the page  → opens the sheet\n *   [data-sheet-close]           inside the sheet      → closes the sheet\n */\n\nimport { autoId } from '../../../utils/id-generator.js';\n\nexport class RenSheet extends HTMLElement {\n  static get observedAttributes() {\n    return ['side', 'size', 'data-side', 'data-size', 'open', 'dismissible'];\n  }\n\n  #dialog;\n  #titleId;\n  #upgraded = false;\n  #returnFocus = null;\n  #abortController = null;\n\n  // Touch swipe\n  #startX = 0;\n  #startY = 0;\n  #dragging = false;\n\n  connectedCallback() {\n    if (!this.#abortController || this.#abortController.signal.aborted) {\n      this.#abortController = new AbortController();\n    }\n    if (this.#upgraded) {\n      this.#wire();\n      return;\n    }\n    this.#upgraded = true;\n    autoId(this, 'sheet');\n    this.#enhance();\n    this.#wire();\n\n    // If consumer set open as initial attribute, show\n    if (this.hasAttribute('open')) {\n      // Defer to ensure dialog is connected\n      queueMicrotask(() => this.show());\n    }\n  }\n\n  disconnectedCallback() {\n    this.#dragging = false;\n    if (this.#dialog?.open) {\n      this.#dialog.close();\n      this.#reconcileNativeClose();\n    }\n    this.#abortController?.abort();\n  }\n\n  attributeChangedCallback(name, oldVal, newVal) {\n    if (!this.#upgraded || !this.#dialog) return;\n    if (name === 'side' || name === 'data-side') {\n      this.#dialog.setAttribute('data-side', this.#getSide());\n    } else if (name === 'size' || name === 'data-size') {\n      this.#syncSize();\n    } else if (name === 'open') {\n      const should = this.hasAttribute('open');\n      if (should && !this.#dialog.open) this.show();\n      if (!should && this.#dialog.open) this.close();\n    }\n  }\n\n  /* ─── Enhancement ─── */\n\n  #enhance() {\n    // Move existing children into a <dialog>\n    const fragment = document.createDocumentFragment();\n    while (this.firstChild) fragment.appendChild(this.firstChild);\n\n    this.#dialog = document.createElement('dialog');\n    this.#dialog.className = 'ren-sheet';\n    this.#dialog.setAttribute('role', 'dialog');\n    this.#dialog.setAttribute('data-side', this.#getSide());\n    this.#syncSize();\n\n    this.#dialog.appendChild(fragment);\n\n    // Wire aria-labelledby to a heading inside the sheet, if any\n    const heading =\n      this.#dialog.querySelector('.ren-sheet-title') ||\n      this.#dialog.querySelector('h1, h2, h3');\n    if (heading) {\n      this.#titleId = autoId(heading, 'sheet-title');\n      this.#dialog.setAttribute('aria-labelledby', this.#titleId);\n    }\n\n    this.appendChild(this.#dialog);\n  }\n\n  #getSide() {\n    return (\n      this.getAttribute('side') ||\n      this.getAttribute('data-side') ||\n      'right'\n    );\n  }\n\n  #getSize() {\n    return (\n      this.getAttribute('size') ||\n      this.getAttribute('data-size') ||\n      'md'\n    );\n  }\n\n  #syncSize() {\n    if (!this.#dialog) return;\n    // Drop any prior -sm/-md/-lg/-xl/-full classes\n    this.#dialog.classList.remove('-sm', '-md', '-lg', '-xl', '-full');\n    this.#dialog.classList.add(`-${this.#getSize()}`);\n  }\n\n  /* ─── Wiring ─── */\n\n  #wire() {\n    const { signal } = this.#abortController;\n    // Backdrop click: native <dialog> dispatches click on the dialog itself\n    // when the user clicks on the backdrop. e.target === dialog → it was the\n    // backdrop, not content inside.\n    this.#dialog.addEventListener('click', (e) => {\n      if (!this.#isDismissible()) return;\n      if (e.target === this.#dialog) this.close();\n    }, { signal });\n\n    // Esc — handled natively, but we intercept to honor dismissible=false\n    this.#dialog.addEventListener('cancel', (e) => {\n      if (!this.#isDismissible()) e.preventDefault();\n    }, { signal });\n\n    // Escape and <form method=\"dialog\"> close the native dialog directly.\n    // Reconcile the host and public lifecycle from the native close event.\n    this.#dialog.addEventListener('close', () => {\n      this.#reconcileNativeClose();\n    }, { signal });\n\n    // [data-sheet-close] anywhere inside\n    this.#dialog.addEventListener('click', (e) => {\n      const closer = e.target.closest('[data-sheet-close]');\n      if (closer && this.#dialog.contains(closer)) {\n        this.close(closer.getAttribute('data-sheet-close') || '');\n      }\n    }, { signal });\n\n    // Swipe to dismiss\n    this.#dialog.addEventListener('touchstart', (e) => {\n      if (!this.#isDismissible()) return;\n      this.#startX = e.touches[0].clientX;\n      this.#startY = e.touches[0].clientY;\n      this.#dragging = true;\n    }, { passive: true, signal });\n\n    this.#dialog.addEventListener('touchmove', (e) => {\n      if (!this.#dragging) return;\n      const dx = e.touches[0].clientX - this.#startX;\n      const dy = e.touches[0].clientY - this.#startY;\n      const side = this.#getSide();\n      const threshold = 50;\n      if (\n        (side === 'right' && dx > threshold) ||\n        (side === 'left' && dx < -threshold) ||\n        (side === 'bottom' && dy > threshold) ||\n        (side === 'top' && dy < -threshold)\n      ) {\n        this.#dragging = false;\n        this.close();\n      }\n    }, { passive: true, signal });\n\n    this.#dialog.addEventListener('touchend', () => {\n      this.#dragging = false;\n    }, { signal });\n  }\n\n  #isDismissible() {\n    return this.getAttribute('dismissible') !== 'false';\n  }\n\n  /* ─── Public API ─── */\n\n  show() {\n    if (!this.#dialog || this.#dialog.open) return;\n    this.#returnFocus = document.activeElement;\n    this.#dialog.showModal();\n    this.setAttribute('open', '');\n\n    // Focus the first usable element inside, or the close button\n    const focusable =\n      this.#dialog.querySelector('[autofocus]') ||\n      this.#dialog.querySelector(\n        'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n      );\n    if (focusable) {\n      focusable.focus({ preventScroll: true });\n    }\n\n    this.dispatchEvent(new CustomEvent('ren-open', { bubbles: true }));\n  }\n\n  close(returnValue = '') {\n    if (!this.#dialog || !this.#dialog.open) return;\n    this.#dialog.close(returnValue);\n  }\n\n  #reconcileNativeClose() {\n    this.removeAttribute('open');\n\n    // Restore focus to the element that opened the sheet\n    if (this.#returnFocus && document.contains(this.#returnFocus)) {\n      this.#returnFocus.focus({ preventScroll: true });\n    }\n    this.#returnFocus = null;\n\n    this.dispatchEvent(new CustomEvent('ren-close', {\n      bubbles: true,\n      composed: true,\n      detail: { returnValue: this.#dialog.returnValue || '' },\n    }));\n  }\n\n  get open() {\n    return !!(this.#dialog && this.#dialog.open);\n  }\n}\n\nif (!customElements.get('ren-sheet')) {\n  customElements.define('ren-sheet', RenSheet);\n}\n\n/* ─── Global trigger delegation ─── */\n/**\n * <button data-sheet-trigger=\"some-id\"> opens <ren-sheet id=\"some-id\">.\n *\n * One global listener handles every trigger on the page so consumers don't\n * need any wiring of their own. Idempotent across module re-imports.\n */\nif (typeof window !== 'undefined' && !window.__renSheetTriggerWired) {\n  window.__renSheetTriggerWired = true;\n  document.addEventListener('click', (e) => {\n    const trigger = e.target.closest('[data-sheet-trigger]');\n    if (!trigger) return;\n    const targetId = trigger.getAttribute('data-sheet-trigger');\n    if (!targetId) return;\n    const sheet = document.getElementById(targetId);\n    if (sheet && typeof sheet.show === 'function') {\n      e.preventDefault();\n      sheet.show();\n    }\n  });\n}\n",
      "path": "components/composites/ren-sheet/ren-sheet.js",
      "id": "file:components/composites/ren-sheet/ren-sheet.js",
      "type": "javascript",
      "name": "ren-sheet.js"
    },
    {
      "data": {},
      "body": "/* ═══ REN SLIDER COMPONENT ═══ */\n\n/* ═══ BASE WRAPPER ═══ */\n.ren-slider {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-1);\n}\n\n/* ═══ TRACK CONTAINER ═══ */\n.ren-slider-track {\n  position: relative;\n  display: flex;\n  align-items: center;\n  width: 100%;\n  height: 20px;\n  cursor: pointer;\n}\n\n/* ═══ RANGE INPUT STYLING ═══ */\n.ren-slider-track input[type=\"range\"] {\n  appearance: none;\n  -webkit-appearance: none;\n  width: 100%;\n  height: 6px;\n  border-radius: var(--radius-full);\n  background: var(--color-fill-active);\n  outline: none;\n  margin: 0;\n  padding: 0;\n\n  /* ═══ TRACK FILL WITH CSS VARIABLE ═══ */\n  background: linear-gradient(\n    to right,\n    var(--color-accent) 0%,\n    var(--color-accent) var(--value, 0%),\n    var(--color-fill-active) var(--value, 0%),\n    var(--color-fill-active) 100%\n  );\n\n  & ::-webkit-slider-thumb {\n    appearance: none;\n    -webkit-appearance: none;\n    width: 20px;\n    height: 20px;\n    border-radius: 50%;\n    background: var(--color-accent);\n    border: 2px solid white;\n    box-shadow: var(--shadow-sm);\n    cursor: pointer;\n    transition: var(--transition-tactile);\n\n    &:hover {\n      transform: scale(1.1);\n    }\n\n    &:active {\n      transform: scale(0.95);\n    }\n  }\n\n  & ::-moz-range-thumb {\n    width: 20px;\n    height: 20px;\n    border-radius: 50%;\n    background: var(--color-accent);\n    border: 2px solid white;\n    box-shadow: var(--shadow-sm);\n    cursor: pointer;\n    transition: var(--transition-tactile);\n\n    &:hover {\n      transform: scale(1.1);\n    }\n\n    &:active {\n      transform: scale(0.95);\n    }\n  }\n\n  & ::-webkit-slider-thumb:focus-visible,\n  & ::-moz-range-thumb:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  &:disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n\n    & ::-webkit-slider-thumb,\n    & ::-moz-range-thumb {\n      cursor: not-allowed;\n    }\n  }\n}\n\n/* ═══ LABEL ═══ */\n.ren-slider-label {\n  font-size: var(--font-size-label);\n  font-weight: 500;\n  color: var(--color-text);\n}\n\n/* ═══ VALUE DISPLAY ═══ */\n.ren-slider-value {\n  font-size: var(--font-size-body-sm);\n  color: var(--color-text-muted);\n  font-variant-numeric: tabular-nums;\n}\n\n/* ═══ TICK MARKS ═══ */\n.ren-slider-marks {\n  display: flex;\n  justify-content: space-between;\n  gap: 0;\n  width: 100%;\n  margin-top: var(--space-1);\n\n  & .ren-slider-mark {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    flex: 0 0 auto;\n\n    & .ren-slider-mark-tick {\n      width: 2px;\n      height: 6px;\n      background: var(--color-fill-subtle);\n      border-radius: 1px;\n      margin-bottom: var(--space-0-5);\n    }\n\n    & .ren-slider-mark-label {\n      font-size: var(--font-size-xs);\n      color: var(--color-text-muted);\n    }\n  }\n}\n\n/* ═══ SIZE VARIANTS ═══ */\n.ren-slider-track {\n  &.ren-slider-sm input[type=\"range\"] {\n    height: 4px;\n    background: linear-gradient(\n      to right,\n      var(--color-accent) 0%,\n      var(--color-accent) var(--value, 0%),\n      var(--color-fill-active) var(--value, 0%),\n      var(--color-fill-active) 100%\n    );\n\n    & ::-webkit-slider-thumb {\n      width: 16px;\n      height: 16px;\n    }\n\n    & ::-moz-range-thumb {\n      width: 16px;\n      height: 16px;\n    }\n  }\n\n  &.ren-slider-lg input[type=\"range\"] {\n    height: 8px;\n    background: linear-gradient(\n      to right,\n      var(--color-accent) 0%,\n      var(--color-accent) var(--value, 0%),\n      var(--color-fill-active) var(--value, 0%),\n      var(--color-fill-active) 100%\n    );\n\n    & ::-webkit-slider-thumb {\n      width: 24px;\n      height: 24px;\n    }\n\n    & ::-moz-range-thumb {\n      width: 24px;\n      height: 24px;\n    }\n  }\n}\n\n/* ═══ COLOR VARIANTS ═══ */\n.ren-slider-track {\n  &.ren-slider-success input[type=\"range\"] {\n    background: linear-gradient(\n      to right,\n      var(--color-success) 0%,\n      var(--color-success) var(--value, 0%),\n      var(--color-fill-active) var(--value, 0%),\n      var(--color-fill-active) 100%\n    );\n\n    & ::-webkit-slider-thumb {\n      background: var(--color-success);\n    }\n\n    & ::-moz-range-thumb {\n      background: var(--color-success);\n    }\n  }\n\n  &.ren-slider-warning input[type=\"range\"] {\n    background: linear-gradient(\n      to right,\n      var(--color-warning) 0%,\n      var(--color-warning) var(--value, 0%),\n      var(--color-fill-active) var(--value, 0%),\n      var(--color-fill-active) 100%\n    );\n\n    & ::-webkit-slider-thumb {\n      background: var(--color-warning);\n    }\n\n    & ::-moz-range-thumb {\n      background: var(--color-warning);\n    }\n  }\n\n  &.ren-slider-danger input[type=\"range\"] {\n    background: linear-gradient(\n      to right,\n      var(--color-danger) 0%,\n      var(--color-danger) var(--value, 0%),\n      var(--color-fill-active) var(--value, 0%),\n      var(--color-fill-active) 100%\n    );\n\n    & ::-webkit-slider-thumb {\n      background: var(--color-danger);\n    }\n\n    & ::-moz-range-thumb {\n      background: var(--color-danger);\n    }\n  }\n}\n\n/* ═══ VERTICAL ORIENTATION ═══ */\n.ren-slider-track {\n  &.ren-slider-vertical {\n    width: auto;\n    height: 200px;\n    writing-mode: vertical-lr;\n\n    & input[type=\"range\"] {\n      width: auto;\n      height: 200px;\n      writing-mode: vertical-lr;\n      direction: rtl;\n    }\n  }\n}\n\n/* ═══ RANGE MODE (DUAL THUMBS) ═══ */\n.ren-slider-range {\n  position: relative;\n  display: flex;\n  align-items: center;\n  width: 100%;\n  height: 20px;\n  cursor: pointer;\n\n  & .ren-slider-track-input {\n    position: absolute;\n    width: 100%;\n    height: 6px;\n    border-radius: var(--radius-full);\n    background: linear-gradient(\n      to right,\n      var(--color-fill-active) 0 var(--value-start, 0%),\n      var(--ren-slider-fill-color, var(--color-accent)) var(--value-start, 0%) var(--value-end, 100%),\n      var(--color-fill-active) var(--value-end, 100%) 100%\n    );\n    pointer-events: none;\n    margin: 0;\n    padding: 0;\n  }\n\n  & input[type=\"range\"] {\n    position: absolute;\n    inset: 0;\n    width: 100%;\n    z-index: 5;\n    margin: 0;\n    padding: 0;\n    appearance: none;\n    -webkit-appearance: none;\n    background: none;\n    height: 20px;\n    border-radius: var(--radius-full);\n    outline: none;\n    pointer-events: none;\n\n    &::-webkit-slider-thumb {\n      appearance: none;\n      -webkit-appearance: none;\n      width: 20px;\n      height: 20px;\n      border-radius: 50%;\n      background: var(--color-accent);\n      border: 2px solid white;\n      box-shadow: var(--shadow-sm);\n      cursor: pointer;\n      transition: var(--transition-tactile);\n      z-index: 6;\n      pointer-events: auto;\n\n      &:hover {\n        transform: scale(1.1);\n      }\n\n      &:active {\n        transform: scale(0.95);\n      }\n    }\n\n    &::-moz-range-thumb {\n      width: 20px;\n      height: 20px;\n      border-radius: 50%;\n      background: var(--color-accent);\n      border: 2px solid white;\n      box-shadow: var(--shadow-sm);\n      cursor: pointer;\n      transition: var(--transition-tactile);\n      z-index: 6;\n      pointer-events: auto;\n\n      &:hover {\n        transform: scale(1.1);\n      }\n\n      &:active {\n        transform: scale(0.95);\n      }\n    }\n  }\n}\n\n/* ═══ REDUCED MOTION ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-slider,\n  .ren-slider * {\n    animation-duration: 0ms !important;\n    transition-duration: 0ms !important;\n  }\n}\n/* Appearance API bridge; pseudo-elements intentionally have no selector whitespace. */\n.ren-slider-track input[type=\"range\"] {\n  block-size: var(--ren-slider-track-height, 6px);\n  border-radius: var(--ren-slider-track-radius, var(--radius-full));\n  background: var(--ren-slider-track-bg, var(--color-fill-active));\n  transition-duration: var(--ren-slider-duration, var(--duration-fast));\n  transition-timing-function: var(--ren-slider-easing, var(--ease-out));\n}\n.ren-slider-track input[type=\"range\"]::-webkit-slider-thumb { inline-size: var(--ren-slider-thumb-size, 20px); block-size: var(--ren-slider-thumb-size, 20px); background: var(--ren-slider-thumb-bg, var(--color-surface)); border-color: var(--ren-slider-thumb-border, var(--color-accent)); box-shadow: var(--ren-slider-thumb-shadow, var(--shadow-sm)); }\n.ren-slider-track input[type=\"range\"]::-moz-range-thumb { inline-size: var(--ren-slider-thumb-size, 20px); block-size: var(--ren-slider-thumb-size, 20px); background: var(--ren-slider-thumb-bg, var(--color-surface)); border-color: var(--ren-slider-thumb-border, var(--color-accent)); box-shadow: var(--ren-slider-thumb-shadow, var(--shadow-sm)); }\n.ren-slider-track input[type=\"range\"] { background-image: linear-gradient(to right, var(--ren-slider-fill-color, var(--color-accent)) 0%, var(--ren-slider-fill-color, var(--color-accent)) var(--value, 0%), var(--ren-slider-track-bg, var(--color-fill-active)) var(--value, 0%), var(--ren-slider-track-bg, var(--color-fill-active)) 100%); }\n",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "file:components/composites/ren-slider/ren-slider.css",
      "type": "css",
      "name": "ren-slider.css"
    },
    {
      "data": {},
      "body": "/* ═══ REN SLIDER WEB COMPONENT ═══ */\n\nexport class RenSlider extends HTMLElement {\n  constructor() {\n    super();\n    this.handleInput = this.handleInput.bind(this);\n    this.handleChange = this.handleChange.bind(this);\n    this.handleRangePointerDown = this.handleRangePointerDown.bind(this);\n    this.handleRangePointerMove = this.handleRangePointerMove.bind(this);\n    this.handleRangePointerEnd = this.handleRangePointerEnd.bind(this);\n  }\n\n  connectedCallback() {\n    this.inputs = [...this.querySelectorAll('input[type=\"range\"]')];\n    this.input = this.inputs[0] || null;\n\n    if (!this.input) {\n      console.warn('RenSlider: No input[type=\"range\"] found');\n      return;\n    }\n\n    /* ═══ SET UP TRACK CLASS ═══ */\n    const trackContainer = this.input.parentElement || this.input;\n    trackContainer.classList.add('ren-slider-track');\n\n    /* ═══ INITIALIZE VALUE ═══ */\n    this.updateValue();\n\n    /* ═══ EVENT LISTENERS ═══ */\n    this.inputs.forEach((input) => {\n      input.addEventListener('input', this.handleInput);\n      input.addEventListener('change', this.handleChange);\n    });\n\n    this.range = this.inputs.length === 2 ? this.querySelector('.ren-slider-range') : null;\n    this.range?.addEventListener('pointerdown', this.handleRangePointerDown, true);\n    this.range?.addEventListener('pointermove', this.handleRangePointerMove, true);\n    this.range?.addEventListener('pointerup', this.handleRangePointerEnd, true);\n    this.range?.addEventListener('pointercancel', this.handleRangePointerEnd, true);\n\n    /* ═══ HANDLE LABEL DISPLAY ═══ */\n    const label = this.getAttribute('label');\n    if (label && !this.querySelector('.ren-slider-label')) {\n      const labelEl = document.createElement('div');\n      labelEl.className = 'ren-slider-label';\n      labelEl.textContent = label;\n      this.insertBefore(labelEl, this.input.parentElement);\n    }\n\n    /* ═══ HANDLE VALUE DISPLAY ═══ */\n    if (this.hasAttribute('show-value')) {\n      this.valueDisplay = this.querySelector('.ren-slider-value');\n      if (!this.valueDisplay) {\n        const valueEl = document.createElement('div');\n        valueEl.className = 'ren-slider-value';\n        this.valueDisplay = valueEl;\n        this.input.parentElement.appendChild(valueEl);\n      }\n      this.updateValueDisplay();\n    }\n\n    /* ═══ TRANSFER VARIANT CLASSES ═══ */\n    this.transferVariantClasses();\n  }\n\n  disconnectedCallback() {\n    this.inputs?.forEach((input) => {\n      input.removeEventListener('input', this.handleInput);\n      input.removeEventListener('change', this.handleChange);\n    });\n    this.range?.removeEventListener('pointerdown', this.handleRangePointerDown, true);\n    this.range?.removeEventListener('pointermove', this.handleRangePointerMove, true);\n    this.range?.removeEventListener('pointerup', this.handleRangePointerEnd, true);\n    this.range?.removeEventListener('pointercancel', this.handleRangePointerEnd, true);\n    this.pointerDrag = null;\n  }\n\n  /* ═══ UPDATE CSS VARIABLE FOR TRACK FILL ═══ */\n  updateValue() {\n    if (!this.input) return;\n\n    const percentages = this.inputs.map((input) => {\n      const min = Number(input.min || 0);\n      const max = Number(input.max || 100);\n      const value = input.valueAsNumber;\n      const percentage = ((value - min) / (max - min)) * 100;\n      input.style.setProperty('--value', `${percentage}%`);\n      return percentage;\n    });\n\n    if (percentages.length === 2) {\n      const track = this.querySelector('.ren-slider-track-input');\n      track?.style.setProperty('--value-start', `${Math.min(...percentages)}%`);\n      track?.style.setProperty('--value-end', `${Math.max(...percentages)}%`);\n    }\n  }\n\n  /* ═══ UPDATE VALUE DISPLAY TEXT ═══ */\n  updateValueDisplay() {\n    if (!this.valueDisplay || !this.input) return;\n\n    const value = this.inputs.length === 2\n      ? this.inputs.map((input) => input.value).join(' – ')\n      : this.input.value;\n    const unit = this.getAttribute('unit') || '';\n    this.valueDisplay.textContent = `${value}${unit}`;\n  }\n\n  /* ═══ INPUT EVENT HANDLER ═══ */\n  handleInput(event) {\n    if (this.inputs.length === 2) {\n      const [lower, upper] = this.inputs;\n      if (event.target === lower && lower.valueAsNumber > upper.valueAsNumber) {\n        lower.value = upper.value;\n      } else if (event.target === upper && upper.valueAsNumber < lower.valueAsNumber) {\n        upper.value = lower.value;\n      }\n    }\n    this.updateValue();\n    this.updateValueDisplay();\n\n    this.dispatchEvent(\n      new CustomEvent('ren-slider-input', {\n        detail: { value: this.value },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  isVerticalRange() {\n    return this.classList.contains('ren-slider-vertical');\n  }\n\n  pointerPosition(event) {\n    return this.isVerticalRange() ? -event.clientY : event.clientX;\n  }\n\n  valueFromPointer(input, event) {\n    const rect = this.range.getBoundingClientRect();\n    const min = Number(input.min || 0);\n    const max = Number(input.max || 100);\n    const step = input.step === 'any' ? 0 : Number(input.step || 1);\n    const ratio = this.isVerticalRange()\n      ? Math.max(0, Math.min(1, (rect.bottom - event.clientY) / rect.height))\n      : Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width));\n    let value = min + ratio * (max - min);\n    if (step > 0) value = min + Math.round((value - min) / step) * step;\n    return Math.max(min, Math.min(max, value));\n  }\n\n  handleRangePointerDown(event) {\n    if (this.inputs.length !== 2 || this.disabled) return;\n    const [lower, upper] = this.inputs;\n    if (lower.valueAsNumber !== upper.valueAsNumber) return;\n    const enabledInputs = this.inputs.filter((input) => !input.disabled);\n    if (enabledInputs.length === 0) return;\n\n    event.preventDefault();\n    this.pointerDrag = {\n      pointerId: event.pointerId,\n      startPosition: this.pointerPosition(event),\n      enabledInputs,\n      activeInput: null,\n    };\n    this.range.setPointerCapture?.(event.pointerId);\n  }\n\n  handleRangePointerMove(event) {\n    const drag = this.pointerDrag;\n    if (!drag || drag.pointerId !== event.pointerId) return;\n    event.preventDefault();\n\n    if (!drag.activeInput) {\n      const position = this.pointerPosition(event);\n      if (position === drag.startPosition) return;\n      const directionalInput = position < drag.startPosition ? this.inputs[0] : this.inputs[1];\n      drag.activeInput = directionalInput.disabled ? drag.enabledInputs[0] : directionalInput;\n      drag.activeInput.focus();\n    }\n\n    drag.activeInput.value = this.valueFromPointer(drag.activeInput, event);\n    drag.activeInput.dispatchEvent(new Event('input', { bubbles: true }));\n  }\n\n  handleRangePointerEnd(event) {\n    const drag = this.pointerDrag;\n    if (!drag || drag.pointerId !== event.pointerId) return;\n    event.preventDefault();\n    if (drag.activeInput) drag.activeInput.dispatchEvent(new Event('change', { bubbles: true }));\n    this.range.releasePointerCapture?.(event.pointerId);\n    this.pointerDrag = null;\n  }\n\n  /* ═══ CHANGE EVENT HANDLER ═══ */\n  handleChange(event) {\n    this.dispatchEvent(\n      new CustomEvent('ren-slider-change', {\n        detail: { value: this.value },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ TRANSFER VARIANT CLASSES TO TRACK ═══ */\n  transferVariantClasses() {\n    if (!this.input) return;\n\n    const trackContainer = this.input.parentElement || this.input;\n    const variants = ['sm', 'lg', 'success', 'warning', 'danger', 'vertical'];\n\n    variants.forEach((variant) => {\n      if (this.classList.contains(`ren-slider-${variant}`)) {\n        trackContainer.classList.add(`ren-slider-${variant}`);\n      }\n    });\n  }\n\n  /* ═══ GETTERS AND SETTERS ═══ */\n  get value() {\n    if (!this.input) return null;\n    const values = this.inputs.map((input) => input.valueAsNumber);\n    return values.length === 2 ? values : values[0];\n  }\n\n  set value(val) {\n    if (!this.input) return;\n    const values = Array.isArray(val) ? [...val] : [val];\n    if (this.inputs.length === 2 && values.length >= 2) {\n      values.splice(0, 2, ...values.slice(0, 2).map(Number).sort((a, b) => a - b));\n    }\n    this.inputs.forEach((input, index) => {\n      if (values[index] !== undefined) input.value = values[index];\n    });\n    this.updateValue();\n    this.updateValueDisplay();\n  }\n\n  get min() {\n    return this.input ? parseFloat(this.input.min) : null;\n  }\n\n  set min(val) {\n    if (this.input) {\n      this.inputs.forEach((input) => input.min = val);\n      this.updateValue();\n    }\n  }\n\n  get max() {\n    return this.input ? parseFloat(this.input.max) : null;\n  }\n\n  set max(val) {\n    if (this.input) {\n      this.inputs.forEach((input) => input.max = val);\n      this.updateValue();\n    }\n  }\n\n  get disabled() {\n    return this.input ? this.inputs.every((input) => input.disabled) : false;\n  }\n\n  set disabled(val) {\n    if (this.input) {\n      this.inputs.forEach((input) => input.disabled = val);\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-slider')) {\n  customElements.define('ren-slider', RenSlider);\n}\n",
      "path": "components/composites/ren-slider/ren-slider.js",
      "id": "file:components/composites/ren-slider/ren-slider.js",
      "type": "javascript",
      "name": "ren-slider.js"
    },
    {
      "data": {},
      "body": "/* ═══ TABS CONTAINER ═══ */\n.ren-tabs {\n  container-type: inline-size;\n  container-name: ren-tabs;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n}\n\n/* ═══ TAB LIST ═══ */\n.ren-tab-list {\n  display: flex;\n  flex-direction: row;\n  gap: var(--space-2);\n  border-bottom: 1px solid var(--color-border);\n  overflow-x: auto;\n  -webkit-overflow-scrolling: touch;\n}\n\n/* Smooth scrolling for horizontal tabs */\n.ren-tab-list {\n  scroll-behavior: smooth;\n}\n\n/* ═══ TAB ITEM (UNDERLINE VARIANT - DEFAULT) ═══ */\n.ren-tab {\n  position: relative;\n  padding: var(--space-2) var(--space-4);\n  font-size: var(--label-md-size);\n  font-weight: var(--label-md-weight);\n  line-height: 1;\n  color: var(--color-text-muted);\n  cursor: pointer;\n  border-bottom: 2px solid transparent;\n  min-height: var(--size-lg);\n  display: flex;\n  align-items: center;\n  white-space: nowrap;\n  user-select: none;\n  transition: var(--transition-tactile);\n}\n\n/* Active tab */\n.ren-tab[aria-selected=\"true\"] {\n  color: var(--color-text);\n  border-bottom-color: var(--color-accent);\n  font-weight: var(--label-lg-weight);\n}\n\n/* Tab hover state */\n.ren-tab:hover:not([aria-disabled=\"true\"]):not(:disabled) {\n  color: var(--color-text);\n  background-color: var(--color-fill);\n}\n\n/* Tab focus ring */\n.ren-tab:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: -2px;\n  border-radius: var(--radius-sm);\n}\n\n/* Disabled tab */\n.ren-tab:disabled,\n.ren-tab[aria-disabled=\"true\"] {\n  opacity: 0.5;\n  cursor: not-allowed;\n  color: var(--color-text-muted);\n}\n\n.ren-tab:disabled:hover,\n.ren-tab[aria-disabled=\"true\"]:hover {\n  background-color: transparent;\n}\n\n/* ═══ TAB PANELS ═══ */\n.ren-tab-panel {\n  padding-block: var(--space-4);\n}\n\n.ren-tab-panel[hidden] {\n  display: none;\n}\n\n/* Panel focus ring */\n.ren-tab-panel:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: 2px;\n  border-radius: var(--radius-md);\n}\n\n/* ═══ VARIANT: PILLS ═══ */\n.ren-tab-list-pills {\n  border-bottom: none;\n  gap: var(--space-1);\n}\n\n.ren-tab-list-pills .ren-tab {\n  border-bottom: none;\n  border-radius: var(--radius-md);\n  background-color: transparent;\n  padding: var(--space-2) var(--space-3);\n}\n\n.ren-tab-list-pills .ren-tab[aria-selected=\"true\"] {\n  background-color: var(--color-fill);\n  border-bottom-color: transparent;\n  font-weight: var(--label-lg-weight);\n}\n\n.ren-tab-list-pills .ren-tab:hover:not([aria-disabled=\"true\"]):not(:disabled) {\n  background-color: var(--color-fill-hover);\n}\n\n/* ═══ VARIANT: UNDERLINE (DEFAULT) ═══ */\n.ren-tab-list-underline {\n  border-bottom: 1px solid var(--color-border);\n  gap: var(--space-2);\n}\n\n.ren-tab-list-underline .ren-tab {\n  border-bottom: 2px solid transparent;\n}\n\n.ren-tab-list-underline .ren-tab[aria-selected=\"true\"] {\n  border-bottom-color: var(--color-accent);\n}\n\n/* ═══ VARIANT: ENCLOSED ═══ */\n.ren-tab-list-enclosed {\n  border-bottom: none;\n  gap: var(--space-2);\n}\n\n.ren-tab-list-enclosed .ren-tab {\n  border: 1px solid var(--color-border);\n  border-bottom: 1px solid var(--color-border);\n  border-radius: var(--radius-md) var(--radius-md) 0 0;\n  background-color: var(--color-fill);\n  padding: var(--space-3) var(--space-4);\n}\n\n.ren-tab-list-enclosed .ren-tab[aria-selected=\"true\"] {\n  border-color: var(--color-border);\n  border-bottom-color: var(--color-surface);\n  background-color: var(--color-surface);\n  color: var(--color-text);\n  position: relative;\n  z-index: 1;\n}\n\n.ren-tab-list-enclosed .ren-tab[aria-selected=\"true\"]::after {\n  content: '';\n  position: absolute;\n  bottom: -1px;\n  left: -1px;\n  right: -1px;\n  height: 1px;\n  background-color: var(--color-surface);\n}\n\n.ren-tab-list-enclosed .ren-tab:hover:not([aria-disabled=\"true\"]):not(:disabled) {\n  background-color: var(--color-fill-hover);\n}\n\n/* ═══ COMPACT LAYOUT (Container Query) ═══ */\n@container ren-tabs (max-width: 480px) {\n  .ren-tab-list {\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n    /* Add scrollbar styling for better UX */\n    scrollbar-width: thin;\n    scrollbar-color: var(--color-border) transparent;\n  }\n\n  .ren-tab-list::-webkit-scrollbar {\n    height: 4px;\n  }\n\n  .ren-tab-list::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  .ren-tab-list::-webkit-scrollbar-thumb {\n    background-color: var(--color-border);\n    border-radius: 2px;\n  }\n}\n\n/* ═══ REDUCED MOTION ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-tab {\n    transition: none;\n  }\n}\n/* Appearance API bridge */\n.ren-tabs {\n  min-block-size: var(--ren-tabs-height, var(--size-lg));\n  font-size: var(--ren-tabs-font-size, var(--label-size));\n  font-weight: var(--ren-tabs-font-weight, var(--label-weight));\n  color: var(--ren-tabs-color, var(--color-text-muted));\n  border-color: var(--ren-tabs-border-color, var(--color-border));\n  gap: var(--ren-tabs-gap, 0);\n  transition-duration: var(--ren-tabs-duration, var(--duration-fast));\n  transition-timing-function: var(--ren-tabs-easing, var(--ease-out));\n}\n.ren-tabs [aria-selected=\"true\"], .ren-tabs [data-state=\"active\"] { color: var(--ren-tabs-active-color, var(--color-text)); }\n.ren-tabs [data-indicator], .ren-tabs-indicator { background: var(--ren-tabs-indicator-color, var(--color-accent)); }\n",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "file:components/composites/ren-tabs/ren-tabs.css",
      "type": "css",
      "name": "ren-tabs.css"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — <ren-tabs> Web Component\n * ==================================\n * Accessible tabbed interface with keyboard navigation.\n * Implements ARIA tablist pattern with manual or automatic activation.\n *\n * Uses Light DOM — no Shadow DOM.\n * Works with semantic HTML or custom tab markup.\n *\n * Attributes:\n *   activation:    'manual' (default) | 'automatic'\n *                  - manual: arrow keys move focus, Enter/Space selects\n *                  - automatic: arrow keys immediately select tabs\n *   default-value: index or tab ID to select initially\n *   orientation:   'horizontal' (default) | 'vertical'\n *\n * Markup:\n *   <ren-tabs>\n *     <div role=\"tablist\" class=\"ren-tab-list\">\n *       <button role=\"tab\" class=\"ren-tab\">Tab 1</button>\n *       <button role=\"tab\" class=\"ren-tab\">Tab 2</button>\n *     </div>\n *     <div role=\"tabpanel\" class=\"ren-tab-panel\">\n *       Panel 1 content\n *     </div>\n *     <div role=\"tabpanel\" class=\"ren-tab-panel\">\n *       Panel 2 content\n *     </div>\n *   </ren-tabs>\n *\n * Events:\n *   ren-tab-change: Dispatched when a tab is selected\n *   - detail: { tab, panel, index, id }\n */\n\nimport { createKeyboardNav } from '../../../utils/keyboard-nav.js';\nimport { autoId } from '../../../utils/id-generator.js';\n\nexport class RenTabs extends HTMLElement {\n  static get observedAttributes() {\n    return ['activation', 'orientation', 'default-value'];\n  }\n\n  constructor() {\n    super();\n    this._nav = null;\n    this._tabList = null;\n    this._tabs = [];\n    this._panels = [];\n    this._selectedIndex = -1;\n  }\n\n  /**\n   * Lifecycle: Element inserted into DOM\n   */\n  connectedCallback() {\n    this._initialize();\n  }\n\n  /**\n   * Lifecycle: Element removed from DOM\n   */\n  disconnectedCallback() {\n    if (this._nav) {\n      this._nav.detach();\n      this._nav = null;\n    }\n  }\n\n  /**\n   * Lifecycle: Observed attribute changed\n   */\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'default-value' && this.isConnected) {\n      this._selectTabByValue(newValue);\n    }\n  }\n\n  /* ═══ INITIALIZATION ═══ */\n\n  /**\n   * Initialize the tabs component\n   * @private\n   */\n  _initialize() {\n    // Find tablist container\n    this._tabList = this.querySelector('[role=\"tablist\"]') || this.querySelector('.ren-tab-list');\n    if (!this._tabList) {\n      console.warn('RenTabs: No tablist found');\n      return;\n    }\n\n    // Collect tabs and panels\n    this._tabs = Array.from(this.querySelectorAll('[role=\"tab\"], .ren-tab'));\n    this._panels = Array.from(this.querySelectorAll('[role=\"tabpanel\"], .ren-tab-panel'));\n\n    if (this._tabs.length === 0) {\n      console.warn('RenTabs: No tabs found');\n      return;\n    }\n\n    // Ensure all elements have proper ARIA roles\n    this._tabList.setAttribute('role', 'tablist');\n    this._tabs.forEach((tab) => {\n      if (!tab.hasAttribute('role')) {\n        tab.setAttribute('role', 'tab');\n      }\n    });\n    this._panels.forEach((panel) => {\n      if (!panel.hasAttribute('role')) {\n        panel.setAttribute('role', 'tabpanel');\n      }\n    });\n\n    // Wire up ARIA relationships\n    this._wireAria();\n\n    // Set up keyboard navigation\n    this._setupKeyboardNav();\n\n    // Select initial tab\n    const defaultValue = this.getAttribute('default-value');\n    if (defaultValue !== null) {\n      this._selectTabByValue(defaultValue);\n    } else {\n      // Select first tab by default\n      this._selectTab(0);\n    }\n\n    // Listen for clicks on tabs\n    this._tabList.addEventListener('click', this._handleTabClick.bind(this));\n  }\n\n  /**\n   * Wire up ARIA attributes between tabs and panels\n   * @private\n   */\n  _wireAria() {\n    this._tabs.forEach((tab, index) => {\n      // Ensure tab has an ID\n      autoId(tab, `tab-${index}`);\n\n      // Find corresponding panel\n      const panel = this._panels[index];\n      if (panel) {\n        // Ensure panel has an ID\n        autoId(panel, `tabpanel-${index}`);\n\n        // Wire aria-controls: tab controls panel\n        tab.setAttribute('aria-controls', panel.id);\n\n        // Wire aria-labelledby: panel is labeled by tab\n        panel.setAttribute('aria-labelledby', tab.id);\n      }\n\n      // Initialize aria-selected state\n      tab.setAttribute('aria-selected', index === this._selectedIndex ? 'true' : 'false');\n    });\n  }\n\n  /**\n   * Set up keyboard navigation using createKeyboardNav\n   * @private\n   */\n  _setupKeyboardNav() {\n    const activation = this.getAttribute('activation') || 'manual';\n    const orientation = this.getAttribute('orientation') || 'horizontal';\n\n    this._nav = createKeyboardNav(this._tabList, {\n      selector: '[role=\"tab\"]',\n      orientation,\n      loop: true,\n      typeahead: false,\n      focusOnHover: false,\n      onActivate: (tab, index) => {\n        // Arrow key navigation\n        if (activation === 'automatic') {\n          // Automatic: immediately select the tab\n          this._selectTab(index);\n        }\n        // Manual: just move focus, don't select\n        // (User must press Enter/Space to select)\n      },\n      onSelect: (tab, index) => {\n        // Enter/Space pressed on focused tab\n        this._selectTab(index);\n      },\n    });\n\n    this._nav.attach();\n  }\n\n  /**\n   * Handle direct click on a tab\n   * @private\n   */\n  _handleTabClick(event) {\n    const tab = event.target.closest('[role=\"tab\"]');\n    if (!tab) return;\n\n    const index = this._tabs.indexOf(tab);\n    if (index !== -1) {\n      this._selectTab(index);\n      // Ensure focus is on the clicked tab\n      tab.focus();\n    }\n  }\n\n  /* ═══ TAB SELECTION ═══ */\n\n  /**\n   * Select a tab by index\n   * @param {number} index - Tab index\n   * @private\n   */\n  _selectTab(index) {\n    if (index < 0 || index >= this._tabs.length) {\n      return;\n    }\n\n    // Deselect all tabs and hide all panels\n    this._tabs.forEach((tab, i) => {\n      const isSelected = i === index;\n      tab.setAttribute('aria-selected', isSelected ? 'true' : 'false');\n      tab.setAttribute('data-state', isSelected ? 'active' : 'inactive');\n      if (this._panels[i]) {\n        this._panels[i].toggleAttribute('hidden', !isSelected);\n      }\n    });\n\n    this._selectedIndex = index;\n\n    // Dispatch custom event\n    const tab = this._tabs[index];\n    const panel = this._panels[index];\n    this.dispatchEvent(\n      new CustomEvent('ren-tab-change', {\n        detail: {\n          tab,\n          panel,\n          index,\n          id: tab.id,\n        },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /**\n   * Select a tab by ID or index value\n   * @param {string|number} value - Tab ID or index string\n   * @private\n   */\n  _selectTabByValue(value) {\n    // Try parsing as index first\n    const indexValue = parseInt(value, 10);\n    if (!isNaN(indexValue) && indexValue >= 0 && indexValue < this._tabs.length) {\n      this._selectTab(indexValue);\n      return;\n    }\n\n    // Try finding by ID\n    const tab = this._tabs.find((t) => t.id === value);\n    if (tab) {\n      const index = this._tabs.indexOf(tab);\n      this._selectTab(index);\n    }\n  }\n\n  /* ═══ PUBLIC API ═══ */\n\n  /**\n   * Get the currently selected tab index\n   * @returns {number}\n   */\n  get selectedIndex() {\n    return this._selectedIndex;\n  }\n\n  /**\n   * Get the currently selected tab element\n   * @returns {HTMLElement|null}\n   */\n  get selectedTab() {\n    return this._selectedIndex >= 0 ? this._tabs[this._selectedIndex] : null;\n  }\n\n  /**\n   * Get the currently selected panel element\n   * @returns {HTMLElement|null}\n   */\n  get selectedPanel() {\n    return this._selectedIndex >= 0 ? this._panels[this._selectedIndex] : null;\n  }\n\n  /**\n   * Programmatically select a tab by index\n   * @param {number} index\n   */\n  selectTabByIndex(index) {\n    this._selectTab(index);\n  }\n\n  /**\n   * Programmatically select a tab by ID\n   * @param {string} id\n   */\n  selectTabById(id) {\n    const index = this._tabs.findIndex((t) => t.id === id);\n    if (index !== -1) {\n      this._selectTab(index);\n    }\n  }\n\n  /**\n   * Get all tabs\n   * @returns {HTMLElement[]}\n   */\n  get tabs() {\n    return [...this._tabs];\n  }\n\n  /**\n   * Get all panels\n   * @returns {HTMLElement[]}\n   */\n  get panels() {\n    return [...this._panels];\n  }\n}\n\n// Register the component\nif (!customElements.get('ren-tabs')) {\n  customElements.define('ren-tabs', RenTabs);\n}\n",
      "path": "components/composites/ren-tabs/ren-tabs.js",
      "id": "file:components/composites/ren-tabs/ren-tabs.js",
      "type": "javascript",
      "name": "ren-tabs.js"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — Toast Notification System CSS\n * =====================================\n * Styles for notification toasts with animations,\n * positioning, variants, and responsive behavior.\n */\n\n/* ═══ VIEWPORT CONTAINER ═══ */\n.ren-toast-viewport {\n  position: fixed;\n  z-index: var(--z-toast, 9999);\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-3);\n  max-height: 100dvh;\n  overflow: hidden;\n  pointer-events: none;\n  max-width: min(420px, calc(100vw - 2rem));\n}\n\n/* Position variants */\n.ren-toast-viewport[data-position=\"top-right\"] {\n  top: var(--space-4);\n  right: var(--space-4);\n  flex-direction: column;\n}\n\n.ren-toast-viewport[data-position=\"top-left\"] {\n  top: var(--space-4);\n  left: var(--space-4);\n  flex-direction: column;\n}\n\n.ren-toast-viewport[data-position=\"top-center\"] {\n  top: var(--space-4);\n  left: 50%;\n  transform: translateX(-50%);\n  flex-direction: column;\n}\n\n.ren-toast-viewport[data-position=\"bottom-right\"] {\n  bottom: var(--space-4);\n  right: var(--space-4);\n  flex-direction: column-reverse;\n}\n\n.ren-toast-viewport[data-position=\"bottom-left\"] {\n  bottom: var(--space-4);\n  left: var(--space-4);\n  flex-direction: column-reverse;\n}\n\n.ren-toast-viewport[data-position=\"bottom-center\"] {\n  bottom: var(--space-4);\n  left: 50%;\n  transform: translateX(-50%);\n  flex-direction: column-reverse;\n}\n\n/* ═══ TOAST CONTAINER ═══ */\n.ren-toast {\n  pointer-events: auto;\n  background: var(--color-surface-raised);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-lg);\n  box-shadow: var(--shadow-lg);\n  padding: var(--space-3) var(--space-4);\n  display: flex;\n  gap: var(--space-3);\n  align-items: flex-start;\n  min-width: 20rem;\n  max-width: 100%;\n  position: relative;\n  overflow: hidden;\n  /* Enter + exit use semantic tokens so global motion tuning cascades here. */\n  animation: ren-toast-fade-in var(--duration-enter) var(--ease-enter);\n}\n\n.ren-toast[data-closing] {\n  animation: ren-toast-fade-out var(--duration-exit) var(--ease-exit);\n  opacity: 0;\n}\n\n/* Slide-in animations by position */\n.ren-toast-viewport[data-position=\"top-right\"] .ren-toast,\n.ren-toast-viewport[data-position=\"bottom-right\"] .ren-toast {\n  animation: ren-toast-slide-in-right var(--duration-enter) var(--ease-enter);\n}\n\n.ren-toast-viewport[data-position=\"top-right\"] .ren-toast[data-closing],\n.ren-toast-viewport[data-position=\"bottom-right\"] .ren-toast[data-closing] {\n  animation: ren-toast-slide-out-right var(--duration-exit) var(--ease-exit);\n}\n\n.ren-toast-viewport[data-position=\"top-left\"] .ren-toast,\n.ren-toast-viewport[data-position=\"bottom-left\"] .ren-toast {\n  animation: ren-toast-slide-in-left var(--duration-enter) var(--ease-enter);\n}\n\n.ren-toast-viewport[data-position=\"top-left\"] .ren-toast[data-closing],\n.ren-toast-viewport[data-position=\"bottom-left\"] .ren-toast[data-closing] {\n  animation: ren-toast-slide-out-left var(--duration-exit) var(--ease-exit);\n}\n\n.ren-toast-viewport[data-position=\"top-center\"] .ren-toast,\n.ren-toast-viewport[data-position=\"bottom-center\"] .ren-toast {\n  animation: ren-toast-fade-in var(--duration-enter) var(--ease-enter);\n}\n\n/* ═══ TOAST CONTENT STRUCTURE ═══ */\n.ren-toast-icon {\n  flex-shrink: 0;\n  width: 20px;\n  height: 20px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 16px;\n  line-height: 1;\n}\n\n/* Body wraps title + description + actions */\n.ren-toast-body {\n  flex: 1;\n  min-width: 0;\n}\n\n.ren-toast-title {\n  font-weight: var(--font-weight-semibold, 600);\n  font-size: var(--text-sm);\n  line-height: var(--leading-5);\n  color: var(--color-text);\n  margin: 0;\n}\n\n.ren-toast-description {\n  font-size: var(--text-xs);\n  line-height: 1.5;\n  color: var(--color-text-muted);\n  margin: var(--space-1) 0 0;\n}\n\n.ren-toast-actions {\n  display: flex;\n  gap: var(--space-2);\n  margin-top: var(--space-2);\n  flex-wrap: wrap;\n}\n\n/* The action button itself (the docs name it .ren-toast-action; we layer it\n   on top of the standard .ren-btn variants for consistent styling). */\n.ren-toast-action {\n  /* Inherits its visual from .ren-btn ren-btn-ghost ren-btn-sm — this rule\n     is just a hook for consumers who want to override per-toast. */\n}\n\n.ren-toast-close {\n  flex-shrink: 0;\n  width: 32px;\n  height: 32px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  background: transparent;\n  border: none;\n  border-radius: var(--radius-sm);\n  color: var(--color-text-muted);\n  cursor: pointer;\n  padding: var(--space-1);\n  font-size: 18px;\n  line-height: 1;\n  /* Tactile (hover/focus/press) — uses shared semantic preset */\n  transition: var(--transition-tactile);\n}\n\n.ren-toast-close:hover {\n  background-color: var(--color-surface-hover);\n  color: var(--color-text);\n}\n\n.ren-toast-close:active {\n  background-color: var(--color-surface-active);\n}\n\n/* ═══ TOAST VARIANTS ═══ */\n/* Success */\n.ren-toast-success {\n  background: color-mix(in srgb, var(--color-success-subtle) 65%, var(--color-surface-raised));\n}\n\n.ren-toast-success .ren-toast-icon {\n  color: var(--color-success);\n}\n\n/* Error / Danger */\n.ren-toast-error,\n.ren-toast-danger {\n  background: color-mix(in srgb, var(--color-danger-subtle) 65%, var(--color-surface-raised));\n}\n\n.ren-toast-error .ren-toast-icon,\n.ren-toast-danger .ren-toast-icon {\n  color: var(--color-danger);\n}\n\n/* Warning */\n.ren-toast-warning {\n  background: color-mix(in srgb, var(--color-warning-subtle) 65%, var(--color-surface-raised));\n}\n\n.ren-toast-warning .ren-toast-icon {\n  color: var(--color-warning);\n}\n\n/* Info */\n.ren-toast-info {\n  background: color-mix(in srgb, var(--color-info-subtle) 65%, var(--color-surface-raised));\n}\n\n.ren-toast-info .ren-toast-icon {\n  color: var(--color-info);\n}\n\n/* Loading */\n.ren-toast-loading .ren-toast-icon {\n  animation: ren-spinner-rotate 0.8s linear infinite;\n}\n\n/* ═══ PROGRESS BAR ═══ */\n.ren-toast-progress {\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  height: 2px;\n  background-color: var(--color-accent);\n  transition: width linear;\n}\n\n/* ═══ SWIPE STATE ═══ */\n.ren-toast[data-swiping] {\n  transition: none;\n}\n\n/* ═══ ANIMATIONS ═══ */\n@keyframes ren-toast-slide-in-right {\n  from {\n    transform: translateX(100%);\n    opacity: 0;\n  }\n  to {\n    transform: translateX(0);\n    opacity: 1;\n  }\n}\n\n@keyframes ren-toast-slide-in-left {\n  from {\n    transform: translateX(-100%);\n    opacity: 0;\n  }\n  to {\n    transform: translateX(0);\n    opacity: 1;\n  }\n}\n\n@keyframes ren-toast-slide-out-right {\n  from {\n    transform: translateX(0);\n    opacity: 1;\n  }\n  to {\n    transform: translateX(100%);\n    opacity: 0;\n  }\n}\n\n@keyframes ren-toast-slide-out-left {\n  from {\n    transform: translateX(0);\n    opacity: 1;\n  }\n  to {\n    transform: translateX(-100%);\n    opacity: 0;\n  }\n}\n\n@keyframes ren-toast-fade-in {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n\n@keyframes ren-toast-fade-out {\n  from {\n    opacity: 1;\n  }\n  to {\n    opacity: 0;\n  }\n}\n\n@keyframes ren-spinner-rotate {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n/* ═══ REDUCED MOTION ═══\n   Belt-and-suspenders: semantic --duration-* already collapse\n   to 0ms under reduced-motion (see tokens/semantic/motion.css),\n   so the animations above become instant. We keep this block\n   to force a plain fade (no slide) as an Apple-HIG alternative,\n   and to stop the loading spinner. */\n@media (prefers-reduced-motion: reduce) {\n  .ren-toast,\n  .ren-toast[data-closing],\n  .ren-toast-viewport[data-position=\"top-right\"] .ren-toast,\n  .ren-toast-viewport[data-position=\"bottom-right\"] .ren-toast,\n  .ren-toast-viewport[data-position=\"top-left\"] .ren-toast,\n  .ren-toast-viewport[data-position=\"bottom-left\"] .ren-toast,\n  .ren-toast-viewport[data-position=\"top-center\"] .ren-toast,\n  .ren-toast-viewport[data-position=\"bottom-center\"] .ren-toast {\n    animation: ren-toast-fade-in var(--duration-micro) linear !important;\n  }\n\n  .ren-toast[data-closing] {\n    animation: ren-toast-fade-out var(--duration-micro) linear !important;\n  }\n\n  .ren-toast-loading .ren-toast-icon {\n    animation: none;\n    opacity: 0.6;\n  }\n}\n\n/* ═══ RESPONSIVE ═══ */\n@media (max-width: 640px) {\n  .ren-toast-viewport {\n    max-width: calc(100vw - 1rem);\n    bottom: var(--space-3);\n    left: var(--space-2);\n    right: var(--space-2);\n    width: auto;\n  }\n\n  .ren-toast-viewport[data-position=\"top-right\"],\n  .ren-toast-viewport[data-position=\"top-left\"],\n  .ren-toast-viewport[data-position=\"top-center\"] {\n    bottom: auto;\n    left: var(--space-2);\n    right: var(--space-2);\n    width: auto;\n    transform: none;\n  }\n\n  .ren-toast-viewport[data-position=\"bottom-right\"],\n  .ren-toast-viewport[data-position=\"bottom-left\"],\n  .ren-toast-viewport[data-position=\"bottom-center\"] {\n    bottom: var(--space-3);\n    left: var(--space-2);\n    right: var(--space-2);\n    width: auto;\n    transform: none;\n  }\n\n  .ren-toast {\n    min-width: unset;\n    width: 100%;\n  }\n}\n/* Appearance API bridge */\n.ren-toast { inline-size: var(--ren-toast-width, 360px); padding: var(--ren-toast-padding, var(--space-4)); border-radius: var(--ren-toast-radius, var(--radius-lg)); background: var(--ren-toast-bg, var(--color-surface-raised)); border-color: var(--ren-toast-border, var(--color-border)); box-shadow: var(--ren-toast-shadow, var(--shadow-lg)); gap: var(--ren-toast-gap, var(--space-3)); animation-duration: var(--ren-toast-anim-duration, var(--duration-normal)); animation-timing-function: var(--ren-toast-easing, var(--ease-out)); }\n\n@media (any-pointer: coarse), (pointer: coarse) {\n  .ren-toast-close {\n    min-inline-size: var(--touch-min);\n    min-block-size: var(--touch-min);\n  }\n}\n",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "file:components/composites/ren-toast/ren-toast.css",
      "type": "css",
      "name": "ren-toast.css"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — Toast Notification System\n * ==================================\n * Transient notifications anchored to a corner of the viewport.\n *\n * Light DOM only. The viewport is a plain element with class\n * `.ren-toast-viewport` (or the convenience custom element\n * `<ren-toast-viewport>`). Toasts are plain `.ren-toast` divs\n * appended to it.\n *\n * Public API (also available as `window.toast`):\n *   toast.success(title, options?)\n *   toast.info(title, options?)\n *   toast.warning(title, options?)\n *   toast.danger(title, options?)\n *   toast.show(options)            — { title, description, status, duration, action }\n *   toast.dismiss(id)\n *   toast.dismissAll()\n *   toast.promise(promise, { loading, success, error })\n *\n * Options:\n *   title        — required string\n *   description  — optional secondary line\n *   status       — \"success\" | \"info\" | \"warning\" | \"danger\" | \"loading\"\n *   duration     — auto-dismiss ms (0 = persistent, default 4000 / 8000 for danger)\n *   dismissible  — show close button (default true)\n *   action       — { label, onClick } inline button\n */\n\nimport { announceAssertive, announcePolite } from '../../../utils/live-region.js';\n\n/* ─── Defaults ─── */\n\nconst DEFAULT_DURATIONS = {\n  success: 4000,\n  info: 4000,\n  warning: 5000,\n  danger: 8000,\n  loading: 0, // persistent\n};\n\nconst ICONS = {\n  success: '✓',\n  info: 'ℹ',\n  warning: '⚠',\n  danger: '✕',\n  loading: '⟳',\n};\n\nconst VALID_POSITIONS = new Set([\n  'top-right',\n  'top-left',\n  'top-center',\n  'bottom-right',\n  'bottom-left',\n  'bottom-center',\n]);\n\n/* ─── Custom element (convenience) ─── */\n\nexport class RenToastViewport extends HTMLElement {\n  connectedCallback() {\n    this._listenerController?.abort();\n    this._listenerController = new AbortController();\n    const { signal } = this._listenerController;\n\n    this.classList.add('ren-toast-viewport');\n    if (!this.hasAttribute('data-position')) {\n      this.setAttribute('data-position', 'bottom-right');\n    }\n    if (!VALID_POSITIONS.has(this.getAttribute('data-position'))) {\n      this.setAttribute('data-position', 'bottom-right');\n    }\n    if (!this.hasAttribute('aria-label')) {\n      this.setAttribute('aria-label', 'Notifications');\n    }\n    if (!this.hasAttribute('role')) this.setAttribute('role', 'region');\n\n    // Pause timers on hover or keyboard focus\n    this.addEventListener('mouseenter', () => pauseTimersFor(this), { signal });\n    this.addEventListener('mouseleave', () => resumeTimersFor(this), { signal });\n    this.addEventListener('focusin', () => pauseTimersFor(this), { signal });\n    this.addEventListener('focusout', (e) => {\n      if (!this.contains(e.relatedTarget)) resumeTimersFor(this);\n    }, { signal });\n  }\n\n  disconnectedCallback() {\n    this._listenerController?.abort();\n    this._listenerController = null;\n    resumeTimersFor(this);\n  }\n}\n\nif (!customElements.get('ren-toast-viewport')) {\n  customElements.define('ren-toast-viewport', RenToastViewport);\n}\n\n/* ─── Per-viewport timer book-keeping ─── */\n\nconst timerStore = new WeakMap(); // viewport → { timers: Map<id, {timerId, remaining, startedAt}>, paused: bool }\n\nfunction getStore(viewport) {\n  let store = timerStore.get(viewport);\n  if (!store) {\n    store = { timers: new Map(), paused: false };\n    timerStore.set(viewport, store);\n  }\n  return store;\n}\n\nfunction pauseTimersFor(viewport) {\n  const store = getStore(viewport);\n  if (store.paused) return;\n  store.paused = true;\n  for (const [id, info] of store.timers.entries()) {\n    if (info.timerId == null) continue;\n    clearTimeout(info.timerId);\n    info.timerId = null;\n    if (info.startedAt && info.remaining) {\n      info.remaining = Math.max(0, info.remaining - (Date.now() - info.startedAt));\n    }\n    // Pause progress bar (freeze its current width)\n    const toastEl = viewport.querySelector(`[data-toast-id=\"${CSS.escape(id)}\"]`);\n    if (toastEl) {\n      const progress = toastEl.querySelector('.ren-toast-progress');\n      if (progress) {\n        const width = getComputedStyle(progress).width;\n        progress.style.transition = 'none';\n        progress.style.width = width;\n      }\n    }\n  }\n}\n\nfunction resumeTimersFor(viewport) {\n  const store = getStore(viewport);\n  if (!store.paused) return;\n  store.paused = false;\n  for (const [id, info] of store.timers.entries()) {\n    if (info.remaining > 0) {\n      info.startedAt = Date.now();\n      info.timerId = setTimeout(() => dismissToast(viewport, id), info.remaining);\n      // Resume progress bar\n      const toastEl = viewport.querySelector(`[data-toast-id=\"${CSS.escape(id)}\"]`);\n      if (toastEl) {\n        const progress = toastEl.querySelector('.ren-toast-progress');\n        if (progress) {\n          // Force reflow then animate to 0\n          progress.style.transition = `width ${info.remaining}ms linear`;\n          requestAnimationFrame(() => {\n            progress.style.width = '0%';\n          });\n        }\n      }\n    }\n  }\n}\n\n/* ─── Viewport resolution ─── */\n\nlet _autoViewport = null;\n\nfunction resolveViewport(preferredId) {\n  if (preferredId) {\n    const v = document.getElementById(preferredId);\n    if (v) return v;\n  }\n  if (_autoViewport && document.body.contains(_autoViewport)) {\n    return _autoViewport;\n  }\n  // Look for any existing viewport in the page\n  const existing = document.querySelector('.ren-toast-viewport, ren-toast-viewport');\n  if (existing) {\n    _autoViewport = existing;\n    return existing;\n  }\n  // Auto-create\n  _autoViewport = document.createElement('ren-toast-viewport');\n  _autoViewport.setAttribute('data-position', 'bottom-right');\n  document.body.appendChild(_autoViewport);\n  return _autoViewport;\n}\n\n/* ─── Toast construction ─── */\n\nfunction generateId() {\n  return `toast-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction normalize(input, statusOverride) {\n  const opts = typeof input === 'string' ? { title: input } : { ...input };\n  opts.statusExplicit = statusOverride != null || opts.status != null;\n  if (statusOverride) opts.status = statusOverride;\n  if (!opts.status) opts.status = 'info';\n  opts.durationExplicit = opts.duration != null;\n  if (opts.duration == null) opts.duration = DEFAULT_DURATIONS[opts.status] ?? 4000;\n  if (opts.dismissible == null) opts.dismissible = true;\n  return opts;\n}\n\nfunction buildToast(id, opts) {\n  const { title, description, status, duration, dismissible, action } = opts;\n\n  const toast = document.createElement('div');\n  toast.className = `ren-toast ren-toast-${status}`;\n  toast.setAttribute('data-toast-id', id);\n  if (duration > 0) toast.setAttribute('data-duration', String(duration));\n  toast.setAttribute('role', status === 'danger' ? 'alert' : 'status');\n  toast.setAttribute('tabindex', '0'); // focusable for Esc/F6\n\n  // Icon\n  const icon = document.createElement('span');\n  icon.className = 'ren-toast-icon';\n  icon.setAttribute('aria-hidden', 'true');\n  icon.textContent = ICONS[status] ?? ICONS.info;\n  toast.appendChild(icon);\n\n  // Body (title + description + action)\n  const body = document.createElement('div');\n  body.className = 'ren-toast-body';\n\n  if (title) {\n    const t = document.createElement('p');\n    t.className = 'ren-toast-title';\n    t.textContent = title;\n    body.appendChild(t);\n  }\n\n  if (description) {\n    const d = document.createElement('p');\n    d.className = 'ren-toast-description';\n    d.textContent = description;\n    body.appendChild(d);\n  }\n\n  if (action && action.label) {\n    const actions = document.createElement('div');\n    actions.className = 'ren-toast-actions';\n\n    const btn = document.createElement('button');\n    btn.type = 'button';\n    btn.className = 'ren-toast-action ren-btn ren-btn-ghost ren-btn-sm';\n    btn.textContent = action.label;\n    btn.addEventListener('click', () => {\n      try {\n        action.onClick?.();\n      } finally {\n        // Find the host viewport at click time (toast may have moved? unlikely)\n        const viewport = toast.closest('.ren-toast-viewport, ren-toast-viewport');\n        if (viewport) dismissToast(viewport, id);\n      }\n    });\n    actions.appendChild(btn);\n    body.appendChild(actions);\n  }\n\n  toast.appendChild(body);\n\n  // Close button\n  if (dismissible) {\n    const close = document.createElement('button');\n    close.type = 'button';\n    close.className = 'ren-toast-close';\n    close.setAttribute('aria-label', 'Dismiss notification');\n    close.textContent = '×';\n    close.addEventListener('click', () => {\n      const viewport = toast.closest('.ren-toast-viewport, ren-toast-viewport');\n      if (viewport) dismissToast(viewport, id);\n    });\n    toast.appendChild(close);\n  }\n\n  // Progress bar (auto-dismiss countdown)\n  if (duration > 0) {\n    const progress = document.createElement('div');\n    progress.className = 'ren-toast-progress';\n    progress.style.width = '100%';\n    progress.style.transition = `width ${duration}ms linear`;\n    toast.appendChild(progress);\n    requestAnimationFrame(() => {\n      progress.style.width = '0%';\n    });\n  }\n\n  // Esc dismisses when toast itself has focus\n  toast.addEventListener('keydown', (e) => {\n    if (e.key === 'Escape') {\n      const viewport = toast.closest('.ren-toast-viewport, ren-toast-viewport');\n      if (viewport) dismissToast(viewport, id);\n    }\n  });\n\n  return toast;\n}\n\n/* ─── Show / dismiss ─── */\n\nfunction showOnViewport(viewport, opts) {\n  const id = opts.id || generateId();\n\n  // If a toast with this id exists, replace it\n  const existing = viewport.querySelector(`[data-toast-id=\"${CSS.escape(id)}\"]`);\n  if (existing) {\n    existing.remove();\n    const store = getStore(viewport);\n    const t = store.timers.get(id);\n    if (t?.timerId) clearTimeout(t.timerId);\n    store.timers.delete(id);\n  }\n\n  const toast = buildToast(id, opts);\n  viewport.appendChild(toast);\n\n  // Resolve the closest effective token scope, including overrides on the\n  // generated toast itself. Explicit duration/status options remain\n  // authoritative.\n  if (!opts.durationExplicit && !opts.statusExplicit) {\n    const tokenDuration = getComputedStyle(toast)\n      .getPropertyValue('--ren-toast-duration')\n      .trim();\n    const parsedDuration = parseFloat(tokenDuration);\n    if (Number.isFinite(parsedDuration) && parsedDuration >= 0) {\n      opts.duration = parsedDuration;\n      const progress = toast.querySelector('.ren-toast-progress');\n      if (opts.duration > 0) {\n        toast.setAttribute('data-duration', String(opts.duration));\n        if (progress) progress.style.transition = `width ${opts.duration}ms linear`;\n      } else if (progress) {\n        progress.remove();\n        toast.removeAttribute('data-duration');\n      }\n    }\n  }\n\n  if (!opts.durationExplicit && opts.duration > 0) toast.setAttribute('data-duration', String(opts.duration));\n\n  // Schedule auto-dismiss\n  if (opts.duration > 0) {\n    const store = getStore(viewport);\n    const startedAt = Date.now();\n    const timerId = store.paused\n      ? null\n      : setTimeout(() => dismissToast(viewport, id), opts.duration);\n    store.timers.set(id, { timerId, remaining: opts.duration, startedAt });\n  }\n\n  // Announce via live region (separate from the toast DOM so SRs don't double-up)\n  const announcement = [opts.title, opts.description].filter(Boolean).join('. ');\n  if (announcement) {\n    if (opts.status === 'danger') announceAssertive(announcement);\n    else announcePolite(announcement);\n  }\n\n  // Enforce optional max-toasts\n  enforceMax(viewport);\n\n  return id;\n}\n\nfunction enforceMax(viewport) {\n  const max = parseInt(viewport.getAttribute('data-max'), 10);\n  if (!max || isNaN(max)) return;\n  const toasts = viewport.querySelectorAll('.ren-toast:not([data-closing])');\n  if (toasts.length <= max) return;\n  for (let i = 0; i < toasts.length - max; i++) {\n    dismissToast(viewport, toasts[i].dataset.toastId);\n  }\n}\n\nfunction dismissToast(viewport, id) {\n  if (!viewport || !id) return;\n  const toast = viewport.querySelector(`[data-toast-id=\"${CSS.escape(id)}\"]`);\n  if (!toast) return;\n\n  const store = getStore(viewport);\n  const t = store.timers.get(id);\n  if (t?.timerId) clearTimeout(t.timerId);\n  store.timers.delete(id);\n\n  toast.setAttribute('data-closing', '');\n\n  // Wait for the exit animation, then remove\n  const dur = parseFloat(getComputedStyle(toast).animationDuration) * 1000 || 200;\n  setTimeout(() => toast.remove(), dur);\n}\n\n/* ─── F6: focus viewport globally ─── */\n\nfunction setupGlobalKeys() {\n  if (window.__renToastF6Wired) return;\n  window.__renToastF6Wired = true;\n  document.addEventListener('keydown', (e) => {\n    if (e.key === 'F6') {\n      const viewport = document.querySelector('ren-toast-viewport, .ren-toast-viewport');\n      if (!viewport) return;\n      const firstToast = viewport.querySelector('.ren-toast');\n      if (firstToast) {\n        e.preventDefault();\n        firstToast.focus();\n      }\n    }\n  });\n}\n\nsetupGlobalKeys();\n\n/* ─── Public API ─── */\n\nfunction show(input) {\n  const opts = normalize(input);\n  const viewport = resolveViewport();\n  return showOnViewport(viewport, opts);\n}\n\nfunction variantFn(status) {\n  return function (title, options = {}) {\n    const opts =\n      typeof title === 'string'\n        ? { ...options, title }\n        : { ...title, ...options };\n    return show(normalize(opts, status));\n  };\n}\n\nexport const toast = Object.assign(show, {\n  show,\n  success: variantFn('success'),\n  info: variantFn('info'),\n  warning: variantFn('warning'),\n  danger: variantFn('danger'),\n  // Common alias for users coming from other libs\n  error: variantFn('danger'),\n  loading: variantFn('loading'),\n\n  dismiss(id) {\n    const viewport = resolveViewport();\n    dismissToast(viewport, id);\n  },\n\n  dismissAll() {\n    const viewport = resolveViewport();\n    viewport.querySelectorAll('.ren-toast:not([data-closing])').forEach((t) => {\n      dismissToast(viewport, t.dataset.toastId);\n    });\n  },\n\n  update(id, updates) {\n    const viewport = resolveViewport();\n    const toastEl = viewport.querySelector(`[data-toast-id=\"${CSS.escape(id)}\"]`);\n    if (!toastEl) return;\n    if (updates.status) {\n      toastEl.className = `ren-toast ren-toast-${updates.status}`;\n      toastEl.setAttribute('data-toast-id', id);\n      toastEl.setAttribute(\n        'role',\n        updates.status === 'danger' ? 'alert' : 'status'\n      );\n      const icon = toastEl.querySelector('.ren-toast-icon');\n      if (icon) icon.textContent = ICONS[updates.status] ?? ICONS.info;\n    }\n    if (updates.title !== undefined) {\n      const t = toastEl.querySelector('.ren-toast-title');\n      if (t) t.textContent = updates.title;\n    }\n    if (updates.description !== undefined) {\n      const d = toastEl.querySelector('.ren-toast-description');\n      if (d) d.textContent = updates.description;\n    }\n  },\n\n  promise(promise, options = {}) {\n    const id = generateId();\n    const loadingOpts = normalize(options.loading || 'Loading...', 'loading');\n    loadingOpts.id = id;\n    const viewport = resolveViewport();\n    showOnViewport(viewport, loadingOpts);\n\n    return promise\n      .then((data) => {\n        const successInput =\n          typeof options.success === 'function'\n            ? options.success(data)\n            : options.success;\n        const successOpts = normalize(successInput || 'Done', 'success');\n        successOpts.id = id;\n        showOnViewport(viewport, successOpts);\n        return data;\n      })\n      .catch((err) => {\n        const errorInput =\n          typeof options.error === 'function' ? options.error(err) : options.error;\n        const errorOpts = normalize(errorInput || 'Failed', 'danger');\n        errorOpts.id = id;\n        showOnViewport(viewport, errorOpts);\n        throw err;\n      });\n  },\n});\n\n// Expose globally — the docs show `window.toast.success(...)` as the primary usage.\nif (typeof window !== 'undefined') {\n  window.toast = toast;\n}\n",
      "path": "components/composites/ren-toast/ren-toast.js",
      "id": "file:components/composites/ren-toast/ren-toast.js",
      "type": "javascript",
      "name": "ren-toast.js"
    },
    {
      "data": {},
      "body": "/* ═══ REN TOGGLE GROUP COMPONENT ═══ */\n\n/* ═══ BASE CONTAINER ═══ */\n.ren-toggle-group {\n  display: inline-flex;\n  gap: var(--space-0-5);\n  background: var(--color-fill);\n  border-radius: var(--radius-lg);\n  padding: 2px;\n  align-items: center;\n\n  & .ren-toggle-group-item {\n    padding: var(--space-1) var(--space-3);\n    border-radius: var(--radius-md);\n    font-size: var(--font-size-label);\n    color: var(--color-text-muted);\n    cursor: pointer;\n    min-height: calc(var(--touch-min, 44px) - 4px);\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    gap: var(--space-1);\n    transition: var(--transition-tactile);\n    user-select: none;\n    border: none;\n    background: transparent;\n    font-family: inherit;\n    font-weight: 400;\n    white-space: nowrap;\n\n    /* ═══ HOVER STATE ═══ */\n    &:hover:not([aria-pressed=\"true\"]) {\n      color: var(--color-text);\n    }\n\n    /* ═══ ACTIVE/PRESSED STATE ═══ */\n    &[aria-pressed=\"true\"],\n    &[data-state=\"on\"] {\n      background: var(--color-surface);\n      color: var(--color-text);\n      font-weight: 500;\n      box-shadow: var(--shadow-sm);\n    }\n\n    /* ═══ FOCUS VISIBLE ═══ */\n    &:focus-visible {\n      outline: 2px solid var(--color-accent);\n      outline-offset: 2px;\n    }\n\n    /* ═══ DISABLED STATE ═══ */\n    &:disabled {\n      opacity: 0.5;\n      cursor: not-allowed;\n      pointer-events: none;\n    }\n  }\n}\n\n/* ═══ OUTLINE VARIANT ═══ */\n.ren-toggle-group.ren-toggle-group-outline {\n  background: transparent;\n  padding: 0;\n  gap: var(--space-1);\n\n  & .ren-toggle-group-item {\n    border: 1px solid var(--color-border);\n    background: transparent;\n    color: var(--color-text-muted);\n\n    &:hover:not([aria-pressed=\"true\"]) {\n      border-color: var(--color-border-strong);\n      color: var(--color-text);\n    }\n\n    &[aria-pressed=\"true\"],\n    &[data-state=\"on\"] {\n      border-color: var(--color-accent);\n      background: color-mix(in srgb, var(--color-accent) 10%, transparent);\n      color: var(--color-accent);\n      font-weight: 500;\n      box-shadow: none;\n    }\n  }\n}\n\n/* ═══ SIZE VARIANTS ═══ */\n.ren-toggle-group {\n  &.ren-toggle-group-sm {\n    gap: 1px;\n    padding: 1px;\n\n    & .ren-toggle-group-item {\n      padding: var(--space-0-5) var(--space-2);\n      font-size: var(--font-size-xs);\n      min-height: calc(var(--touch-min, 44px) - 12px);\n    }\n  }\n\n  &.ren-toggle-group-lg {\n    gap: var(--space-1);\n    padding: var(--space-0-5);\n\n    & .ren-toggle-group-item {\n      padding: var(--space-2) var(--space-4);\n      font-size: var(--font-size-body);\n      min-height: calc(var(--touch-min, 44px) + 4px);\n    }\n  }\n}\n\n/* ═══ FULL WIDTH ═══ */\n.ren-toggle-group.ren-toggle-group-full {\n  display: flex;\n  width: 100%;\n\n  & .ren-toggle-group-item {\n    flex: 1;\n  }\n}\n\n/* ═══ VERTICAL ORIENTATION ═══ */\n.ren-toggle-group.ren-toggle-group-vertical {\n  flex-direction: column;\n  display: inline-flex;\n\n  &.ren-toggle-group-full {\n    display: flex;\n    width: 100%;\n  }\n}\n\n/* ═══ ICON SUPPORT ═══ */\n.ren-toggle-group-item {\n  & [role=\"img\"],\n  & svg {\n    width: 1em;\n    height: 1em;\n    display: inline-block;\n    vertical-align: middle;\n  }\n}\n\n/* ═══ TRANSITIONS ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-toggle-group-item {\n    transition: none;\n  }\n}\n/* Appearance API bridge */\n.ren-toggle-group { min-block-size: var(--ren-toggle-height, var(--size-lg)); padding-inline: var(--ren-toggle-padding-x, var(--space-4)); font-size: var(--ren-toggle-font-size, var(--label-size)); font-weight: var(--ren-toggle-font-weight, var(--label-weight)); border-radius: var(--ren-toggle-radius, var(--radius-md)); background: var(--ren-toggle-bg, var(--color-fill)); color: var(--ren-toggle-color, var(--color-text-muted)); }\n.ren-toggle-group [aria-pressed=\"true\"], .ren-toggle-group [data-state=\"on\"] { background: var(--ren-toggle-active-bg, var(--color-surface)); color: var(--ren-toggle-active-color, var(--color-text)); }\n\n@media (any-pointer: coarse), (pointer: coarse) {\n  .ren-toggle-group.ren-toggle-group-sm .ren-toggle-group-item,\n  .ren-toggle-group .ren-toggle-group-item {\n    min-inline-size: var(--touch-min);\n    min-block-size: var(--touch-min);\n  }\n}\n",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "file:components/composites/ren-toggle-group/ren-toggle-group.css",
      "type": "css",
      "name": "ren-toggle-group.css"
    },
    {
      "data": {},
      "body": "/* ═══ REN TOGGLE GROUP WEB COMPONENT ═══ */\n\nexport class RenToggleGroup extends HTMLElement {\n  constructor() {\n    super();\n    this.type = 'single';\n    this.selectedValue = null;\n    this.items = [];\n    this.handleItemClick = this.handleItemClick.bind(this);\n    this.handleKeyDown = this.handleKeyDown.bind(this);\n  }\n\n  connectedCallback() {\n    /* ═══ READ ATTRIBUTES ═══ */\n    this.type = this.getAttribute('type') || 'single';\n    const initialValue = this.getAttribute('value');\n\n    /* ═══ FIND ALL ITEMS ═══ */\n    this.items = Array.from(\n      this.querySelectorAll('.ren-toggle-group-item, [role=\"button\"]')\n    );\n\n    if (this.items.length === 0) {\n      console.warn('RenToggleGroup: No items found');\n      return;\n    }\n\n    /* ═══ SET UP ARIA ATTRIBUTES ═══ */\n    this.items.forEach((item, index) => {\n      item.setAttribute('role', 'button');\n      item.setAttribute('tabindex', index === 0 ? '0' : '-1');\n      item.setAttribute('aria-pressed', 'false');\n\n      /* ═══ ADD CLASSES ═══ */\n      item.classList.add('ren-toggle-group-item');\n\n      /* ═══ EVENT LISTENERS ═══ */\n      item.addEventListener('click', this.handleItemClick);\n      item.addEventListener('keydown', this.handleKeyDown);\n    });\n\n    /* ═══ SET INITIAL VALUE ═══ */\n    if (initialValue) {\n      this.setValue(initialValue);\n    }\n\n    /* ═══ ENSURE CONTAINER HAS CLASS ═══ */\n    this.classList.add('ren-toggle-group');\n  }\n\n  disconnectedCallback() {\n    this.items.forEach((item) => {\n      item.removeEventListener('click', this.handleItemClick);\n      item.removeEventListener('keydown', this.handleKeyDown);\n    });\n  }\n\n  /* ═══ ITEM CLICK HANDLER ═══ */\n  handleItemClick(event) {\n    const item = event.currentTarget;\n    const value = item.getAttribute('data-value') || item.textContent;\n\n    if (this.type === 'single') {\n      this.setValue(value);\n    } else {\n      /* ═══ MULTIPLE MODE: TOGGLE ═══ */\n      const isPressed = item.getAttribute('aria-pressed') === 'true';\n      item.setAttribute('aria-pressed', !isPressed);\n      this.dispatchChangeEvent();\n    }\n  }\n\n  /* ═══ KEYBOARD NAVIGATION ═══ */\n  handleKeyDown(event) {\n    const currentIndex = this.items.indexOf(event.currentTarget);\n    let nextIndex = currentIndex;\n\n    if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {\n      event.preventDefault();\n      nextIndex = (currentIndex + 1) % this.items.length;\n    } else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {\n      event.preventDefault();\n      nextIndex = (currentIndex - 1 + this.items.length) % this.items.length;\n    } else if (event.key === ' ' || event.key === 'Enter') {\n      event.preventDefault();\n      event.currentTarget.click();\n      return;\n    }\n\n    if (nextIndex !== currentIndex) {\n      this.items[currentIndex].setAttribute('tabindex', '-1');\n      const nextItem = this.items[nextIndex];\n      nextItem.setAttribute('tabindex', '0');\n      nextItem.focus();\n    }\n  }\n\n  /* ═══ SET SINGLE VALUE ═══ */\n  setValue(value) {\n    if (this.type !== 'single') {\n      console.warn('RenToggleGroup: setValue only works in single mode');\n      return;\n    }\n\n    /* ═══ CLEAR PREVIOUS SELECTION ═══ */\n    this.items.forEach((item) => {\n      item.setAttribute('aria-pressed', 'false');\n    });\n\n    /* ═══ SET NEW SELECTION ═══ */\n    const targetItem = this.items.find(\n      (item) =>\n        (item.getAttribute('data-value') || item.textContent) === value\n    );\n\n    if (targetItem) {\n      targetItem.setAttribute('aria-pressed', 'true');\n      this.selectedValue = value;\n      this.dispatchChangeEvent();\n    }\n  }\n\n  /* ═══ DISPATCH CHANGE EVENT ═══ */\n  dispatchChangeEvent() {\n    const selectedItems =\n      this.type === 'single'\n        ? [this.items.find((item) => item.getAttribute('aria-pressed') === 'true')]\n        : this.items.filter((item) => item.getAttribute('aria-pressed') === 'true');\n\n    const values = selectedItems\n      .filter(Boolean)\n      .map((item) => item.getAttribute('data-value') || item.textContent);\n\n    this.dispatchEvent(\n      new CustomEvent('ren-toggle-change', {\n        detail: {\n          value: this.type === 'single' ? values[0] : values,\n          items: selectedItems.filter(Boolean),\n        },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /* ═══ GETTERS AND SETTERS ═══ */\n  get value() {\n    if (this.type === 'single') {\n      return this.selectedValue;\n    } else {\n      return this.items\n        .filter((item) => item.getAttribute('aria-pressed') === 'true')\n        .map((item) => item.getAttribute('data-value') || item.textContent);\n    }\n  }\n\n  set value(val) {\n    if (this.type === 'single') {\n      this.setValue(val);\n    }\n  }\n\n  get disabled() {\n    return this.hasAttribute('disabled');\n  }\n\n  set disabled(val) {\n    if (val) {\n      this.setAttribute('disabled', '');\n      this.items.forEach((item) => item.setAttribute('disabled', ''));\n    } else {\n      this.removeAttribute('disabled');\n      this.items.forEach((item) => item.removeAttribute('disabled'));\n    }\n  }\n}\n\n/* ═══ REGISTER COMPONENT ═══ */\nif (!customElements.get('ren-toggle-group')) {\n  customElements.define('ren-toggle-group', RenToggleGroup);\n}\n",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.js",
      "id": "file:components/composites/ren-toggle-group/ren-toggle-group.js",
      "type": "javascript",
      "name": "ren-toggle-group.js"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Toolbar Component\n   ============================================\n   Grouped actions with roving tabindex pattern.\n   CSS-only for layout. JS only for keyboard nav.\n\n   Follows WAI-ARIA Toolbar pattern:\n   - Arrow keys move between items\n   - Tab moves focus in/out of toolbar\n   - Home/End go to first/last item\n\n   Usage:\n     <div class=\"ren-toolbar\" role=\"toolbar\" aria-label=\"Formatting\">\n       <button class=\"ren-toolbar-item\" tabindex=\"0\">Bold</button>\n       <button class=\"ren-toolbar-item\" tabindex=\"-1\">Italic</button>\n       <button class=\"ren-toolbar-item\" tabindex=\"-1\">Underline</button>\n       <div class=\"ren-toolbar-separator\" role=\"separator\"></div>\n       <button class=\"ren-toolbar-item\" tabindex=\"-1\">Left</button>\n       <button class=\"ren-toolbar-item\" tabindex=\"-1\">Center</button>\n       <button class=\"ren-toolbar-item\" tabindex=\"-1\">Right</button>\n     </div>\n\n   Note: Set tabindex=\"0\" on first item, \"-1\" on rest.\n   ren-toolbar.js handles roving tabindex automatically.\n   ============================================ */\n\n.ren-toolbar {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-1);\n  padding: var(--space-1);\n  background-color: var(--color-surface-sunken);\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-md);\n}\n\n/* ─── Toolbar Items ─── */\n.ren-toolbar-item {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  min-width: var(--size-sm);\n  min-height: var(--size-sm);\n  padding: var(--space-1) var(--space-2);\n  border: none;\n  background: none;\n  color: var(--color-text);\n  font-size: var(--caption-size, var(--text-sm));\n  font-weight: var(--weight-medium);\n  cursor: pointer;\n  border-radius: var(--radius-sm);\n  transition: var(--transition-tactile);\n}\n\n.ren-toolbar-item:hover {\n  background-color: var(--color-fill-hover);\n}\n\n.ren-toolbar-item:active {\n  background-color: var(--color-fill-active);\n}\n\n.ren-toolbar-item:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: -1px;\n}\n\n/* Active / Pressed state */\n.ren-toolbar-item[aria-pressed=\"true\"],\n.ren-toolbar-item[data-active] {\n  background-color: var(--color-surface-raised, var(--color-surface));\n  box-shadow: var(--shadow-xs);\n  color: var(--color-accent);\n}\n\n/* Disabled */\n.ren-toolbar-item:disabled,\n.ren-toolbar-item[aria-disabled=\"true\"] {\n  color: var(--color-disabled-text);\n  cursor: not-allowed;\n}\n\n/* ─── Separator ─── */\n.ren-toolbar-separator {\n  width: 1px;\n  height: 1.25rem;\n  background-color: var(--color-border);\n  margin-inline: var(--space-1);\n  flex-shrink: 0;\n}\n\n/* ─── Icon-only items ─── */\n.ren-toolbar-item-icon {\n  min-width: var(--size-sm);\n  padding: var(--space-1);\n  aspect-ratio: 1;\n}\n\n/* ─── Variants ─── */\n\n/* Ghost — no background on container */\n.ren-toolbar-ghost {\n  background: none;\n  border: none;\n  padding: 0;\n  gap: var(--space-1);\n}\n\n/* Vertical toolbar */\n.ren-toolbar-vertical {\n  flex-direction: column;\n}\n\n.ren-toolbar-vertical .ren-toolbar-separator {\n  width: auto;\n  height: 1px;\n  align-self: stretch;\n  margin-block: var(--space-1);\n  margin-inline: 0;\n}\n/* Appearance API bridge */\n.ren-toolbar { min-block-size: var(--ren-toolbar-height, var(--size-lg)); padding: var(--ren-toolbar-padding, var(--space-1)); border-radius: var(--ren-toolbar-radius, var(--radius-lg)); background: var(--ren-toolbar-bg, var(--color-surface-raised)); border-color: var(--ren-toolbar-border, var(--color-border)); }\n.ren-toolbar button, .ren-toolbar [role=\"button\"] { inline-size: var(--ren-toolbar-item-size, var(--size-md)); block-size: var(--ren-toolbar-item-size, var(--size-md)); border-radius: var(--ren-toolbar-item-radius, var(--radius-sm)); }\n.ren-toolbar button:hover, .ren-toolbar [role=\"button\"]:hover { background: var(--ren-toolbar-item-hover, var(--color-fill)); }\n.ren-toolbar button[aria-pressed=\"true\"], .ren-toolbar [data-state=\"on\"] { background: var(--ren-toolbar-active-bg, var(--color-accent-subtle)); }\n",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "file:components/composites/ren-toolbar/ren-toolbar.css",
      "type": "css",
      "name": "ren-toolbar.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Toolbar Keyboard Navigation\n   ============================================\n   Implements WAI-ARIA Toolbar pattern:\n   - Arrow Left/Right: move between items\n   - Home: first item\n   - End: last item\n   - Tab: exits toolbar\n\n   Usage:\n     import { initToolbar } from './ren-toolbar.js';\n     initToolbar(document.querySelector('.ren-toolbar'));\n\n   Or auto-init all:\n     import { initAllToolbars } from './ren-toolbar.js';\n     initAllToolbars();\n   ============================================ */\n\n/**\n * @param {HTMLElement} toolbar\n */\nconst toolbarControllers = new WeakMap();\n\nexport function initToolbar(toolbar) {\n  if (!toolbar || toolbar.getAttribute('role') !== 'toolbar') return;\n\n  toolbarControllers.get(toolbar)?.abort();\n  const controller = new AbortController();\n  toolbarControllers.set(toolbar, controller);\n\n  const getItems = () =>\n    [...toolbar.querySelectorAll('.ren-toolbar-item:not(:disabled):not([aria-disabled=\"true\"])')];\n\n  const isVertical = toolbar.classList.contains('ren-toolbar-vertical');\n  const prevKey = isVertical ? 'ArrowUp' : 'ArrowLeft';\n  const nextKey = isVertical ? 'ArrowDown' : 'ArrowRight';\n\n  toolbar.addEventListener('keydown', (e) => {\n    const items = getItems();\n    const current = items.indexOf(document.activeElement);\n    if (current === -1) return;\n\n    let next = -1;\n\n    if (e.key === nextKey) {\n      next = (current + 1) % items.length;\n    } else if (e.key === prevKey) {\n      next = (current - 1 + items.length) % items.length;\n    } else if (e.key === 'Home') {\n      next = 0;\n    } else if (e.key === 'End') {\n      next = items.length - 1;\n    }\n\n    if (next !== -1) {\n      e.preventDefault();\n      items.forEach((item) => item.setAttribute('tabindex', '-1'));\n      items[next].setAttribute('tabindex', '0');\n      items[next].focus();\n    }\n  }, { signal: controller.signal });\n}\n\n/**\n * Auto-init all toolbars in the document.\n */\nexport function initAllToolbars() {\n  document.querySelectorAll('[role=\"toolbar\"]').forEach(initToolbar);\n}\n",
      "path": "components/composites/ren-toolbar/ren-toolbar.js",
      "id": "file:components/composites/ren-toolbar/ren-toolbar.js",
      "type": "javascript",
      "name": "ren-toolbar.js"
    },
    {
      "data": {},
      "body": "/* ═══ ANCHOR POSITIONING & BASE STYLES ═══ */\n.ren-tooltip {\n  position: absolute;\n  background: var(--ren-tooltip-bg, var(--color-text));\n  color: var(--ren-tooltip-color, var(--color-text-inverted));\n  border-radius: var(--radius-md);\n  padding: var(--space-1) var(--space-2);\n  font-size: var(--size-caption, 0.75rem);\n  max-width: 15rem;\n  pointer-events: none;\n  z-index: var(--z-tooltip, 2000);\n  overflow-wrap: break-word;\n  white-space: normal;\n  line-height: 1.4;\n  margin: 0;\n  inset: auto;\n  overflow: visible;\n\n  /* Anchor positioning (modern browsers) */\n  position-anchor: --tooltip-anchor;\n\n  /* Default: above the trigger with automatic fallback */\n  position-area: top span-all;\n\n  /* Automatic flip when near viewport edges */\n  position-try-fallbacks: flip-block;\n\n  /* Smooth transitions — semantic tokens keep tooltip in sync with the\n     rest of the overlay family (popover, menu, dialog). Collapses to\n     0ms under prefers-reduced-motion via tokens/semantic/motion.css. */\n  opacity: 0;\n  transform: translateY(4px) scale(0.95);\n  transition:\n    opacity var(--duration-enter) var(--ease-enter),\n    transform var(--duration-enter) var(--ease-enter),\n    overlay var(--duration-enter) var(--ease-enter) allow-discrete,\n    display var(--duration-enter) var(--ease-enter) allow-discrete;\n}\n\n/* ═══ TOOLTIP OPEN STATE ═══ */\n.ren-tooltip:popover-open,\n.ren-tooltip.ren-open {\n  opacity: 1;\n  transform: translateY(0) scale(1);\n}\n\n/* ═══ TOOLTIP OPEN WITH @starting-style ═══ */\n@starting-style {\n  .ren-tooltip:popover-open {\n    opacity: 0;\n    transform: translateY(4px) scale(0.95);\n  }\n}\n\n/* ═══ TOOLTIP ARROW/CARET ═══ */\n.ren-tooltip-arrow {\n  position: absolute;\n  width: 6px;\n  height: 6px;\n  background: var(--ren-tooltip-bg, var(--color-text));\n  transform: rotate(45deg);\n  z-index: -1;\n}\n\n/* ═══ PLACEMENT VARIANTS ═══ */\n/* Bottom */\n.ren-tooltip[data-side=\"bottom\"] {\n  position-area: bottom span-all;\n  margin-block-start: var(--space-1);\n  margin-block-end: 0;\n  margin-inline: 0;\n\n  & .ren-tooltip-arrow {\n    top: -3px;\n    left: 50%;\n    transform: translateX(-50%) rotate(225deg);\n  }\n}\n\n/* Top (default) */\n.ren-tooltip[data-side=\"top\"],\n.ren-tooltip:not([data-side]) {\n  position-area: top span-all;\n  margin-block-start: 0;\n  margin-block-end: var(--space-1);\n  margin-inline: 0;\n\n  & .ren-tooltip-arrow {\n    bottom: -3px;\n    left: 50%;\n    transform: translateX(-50%) rotate(45deg);\n  }\n}\n\n/* Right */\n.ren-tooltip[data-side=\"right\"] {\n  position-area: right span-all;\n  margin-block: 0;\n  margin-inline-start: var(--space-1);\n  margin-inline-end: 0;\n\n  & .ren-tooltip-arrow {\n    left: -3px;\n    top: 50%;\n    transform: translateY(-50%) rotate(135deg);\n  }\n}\n\n/* Left */\n.ren-tooltip[data-side=\"left\"] {\n  position-area: left span-all;\n  margin-block: 0;\n  margin-inline-start: 0;\n  margin-inline-end: var(--space-1);\n\n  & .ren-tooltip-arrow {\n    right: -3px;\n    top: 50%;\n    transform: translateY(-50%) rotate(315deg);\n  }\n}\n\n/* ═══ RESPECTS MOTION PREFERENCES ═══\n   Semantic tokens already collapse to 0ms under reduce, but we keep\n   this belt-and-suspenders block so the transform-half can be skipped\n   entirely (no scale bounce) while overlay/display discrete swaps\n   still happen. */\n@media (prefers-reduced-motion: reduce) {\n  .ren-tooltip {\n    transition: opacity var(--duration-enter), transform var(--duration-enter),\n                overlay var(--duration-enter) allow-discrete,\n                display var(--duration-enter) allow-discrete;\n  }\n\n  @starting-style {\n    .ren-tooltip:popover-open {\n      opacity: 0;\n      transform: scale(1);\n    }\n  }\n}\n\n/* ═══ FALLBACK FOR BROWSERS WITHOUT COMPLETE ANCHOR POSITIONING ═══ */\n@supports not ((anchor-name: --ren-anchor) and\n  (position-anchor: --ren-anchor) and\n  (position-area: top span-all)) {\n  .ren-tooltip {\n    position: absolute;\n    inset: auto auto auto auto;\n  }\n\n  .ren-tooltip-trigger {\n    position: relative;\n  }\n\n  /* Fallback animations */\n  .ren-tooltip {\n    animation: ren-tooltip-fallback-open var(--duration-enter) var(--ease-enter);\n  }\n\n  .ren-tooltip:popover-open,\n  .ren-tooltip.ren-open {\n    animation: ren-tooltip-fallback-open var(--duration-enter) var(--ease-enter);\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    .ren-tooltip {\n      animation: none;\n    }\n  }\n}\n\n@keyframes ren-tooltip-fallback-open {\n  from {\n    opacity: 0;\n    transform: translateY(4px) scale(0.95);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0) scale(1);\n  }\n}\n\n/* Appearance API bridge */\n.ren-tooltip {\n  background: var(--ren-tooltip-bg, var(--color-text));\n  color: var(--ren-tooltip-color, var(--color-text-inverted));\n  font-size: var(--ren-tooltip-font-size, var(--caption-sm-size));\n  padding-inline: var(--ren-tooltip-padding-x, var(--space-3));\n  padding-block: var(--ren-tooltip-padding-y, var(--space-1));\n  border-radius: var(--ren-tooltip-radius, var(--radius-md));\n  box-shadow: var(--ren-tooltip-shadow, var(--shadow-sm));\n  transition-duration: var(--ren-tooltip-duration, var(--duration-fast));\n  transition-timing-function: var(--ren-tooltip-easing, var(--ease-out));\n}\n",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "file:components/composites/ren-tooltip/ren-tooltip.css",
      "type": "css",
      "name": "ren-tooltip.css"
    },
    {
      "data": {},
      "body": "const PLACEMENTS = new Set(['top', 'right', 'bottom', 'left']);\n\nfunction supportsAnchorPositioning() {\n  return (\n    typeof CSS !== 'undefined' &&\n    typeof CSS.supports === 'function' &&\n    CSS.supports('anchor-name', '--ren-anchor') &&\n    CSS.supports('position-anchor', '--ren-anchor') &&\n    CSS.supports('position-area', 'top span-all')\n  );\n}\n\nfunction normalizePlacement(value, fallback = 'top') {\n  return PLACEMENTS.has(value) ? value : fallback;\n}\n\n/**\n * Fallback position computation for browsers without CSS anchor positioning.\n * Used only when full CSS anchor positioning support is unavailable.\n *\n * @param {HTMLElement} trigger - The trigger element\n * @param {HTMLElement} tooltip - The tooltip element\n * @param {string} placement - Placement: 'top', 'right', 'bottom', 'left'\n * @param {number} offset - Offset in pixels between trigger and tooltip\n * @returns {Object} Position object with x, y, and finalPlacement properties\n */\nfunction computePosition(trigger, tooltip, placement = 'top', offset = 8) {\n  const triggerRect = trigger.getBoundingClientRect();\n  const tooltipRect = tooltip.getBoundingClientRect();\n  const viewport = { width: window.innerWidth, height: window.innerHeight };\n\n  let x = 0;\n  let y = 0;\n  let finalPlacement = placement;\n\n  const placements = {\n    top: () => {\n      x = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;\n      y = triggerRect.top - tooltipRect.height - offset;\n      if (y < 0) {\n        finalPlacement = 'bottom';\n        return placements.bottom();\n      }\n      return { x, y };\n    },\n    bottom: () => {\n      x = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;\n      y = triggerRect.bottom + offset;\n      if (y + tooltipRect.height > viewport.height) {\n        finalPlacement = 'top';\n        return placements.top();\n      }\n      return { x, y };\n    },\n    left: () => {\n      x = triggerRect.left - tooltipRect.width - offset;\n      y = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;\n      if (x < 0) {\n        finalPlacement = 'right';\n        return placements.right();\n      }\n      return { x, y };\n    },\n    right: () => {\n      x = triggerRect.right + offset;\n      y = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;\n      if (x + tooltipRect.width > viewport.width) {\n        finalPlacement = 'left';\n        return placements.left();\n      }\n      return { x, y };\n    },\n  };\n\n  const result = placements[placement]?.() || placements.top();\n\n  // Clamp X position within viewport\n  if (result.x < 0) result.x = 8;\n  else if (result.x + tooltipRect.width > viewport.width)\n    result.x = viewport.width - tooltipRect.width - 8;\n\n  return { ...result, finalPlacement };\n}\n\n/**\n * Tooltip component with CSS Anchor Positioning and native Popover API\n *\n * Lightweight tooltip that displays helpful text on hover/focus with automatic\n * viewport collision handling via CSS anchor positioning.\n *\n * @example\n * <button>Hover me\n *   <ren-tooltip placement=\"top\" show-delay=\"500\">Helpful text</ren-tooltip>\n * </button>\n *\n * @fires ren-open - Fired when tooltip opens\n * @fires ren-close - Fired when tooltip closes\n */\nexport class RenTooltip extends HTMLElement {\n  static observedAttributes = ['placement'];\n  static supportsAnchor = supportsAnchorPositioning();\n\n  #trigger = null;\n  #showTimeout = null;\n  #hideTimeout = null;\n  #touchTimer = null;\n  #listenerController = null;\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (name === 'placement' && oldValue !== newValue) {\n      this.#syncPlacement();\n    }\n  }\n\n  connectedCallback() {\n    this.setupTooltip();\n    this.findTrigger();\n    this.attachTriggerListeners();\n  }\n\n  disconnectedCallback() {\n    this.cleanup();\n  }\n\n  /**\n   * Setup tooltip element with required attributes and structure\n   * @private\n   */\n  setupTooltip() {\n    this.classList.add('ren-tooltip');\n\n    // Add arrow if not present\n    if (!this.querySelector('.ren-tooltip-arrow')) {\n      const arrow = document.createElement('div');\n      arrow.className = 'ren-tooltip-arrow';\n      this.appendChild(arrow);\n    }\n\n    // Set accessibility attributes\n    this.setAttribute('role', 'tooltip');\n    this.id = this.id || `ren-tooltip-${Math.random().toString(36).substr(2, 9)}`;\n\n    // Setup native Popover API with manual mode\n    if ('popover' in HTMLElement.prototype) {\n      this.setAttribute('popover', 'manual');\n    }\n\n    this.#syncPlacement();\n  }\n\n  /**\n   * Find the trigger element (parent that contains the tooltip)\n   * @private\n   */\n  findTrigger() {\n    this.#trigger = this.parentElement;\n\n    if (this.#trigger) {\n      // Wire aria-describedby relationship\n      const describedBy = new Set(\n        (this.#trigger.getAttribute('aria-describedby') || '').split(/\\s+/).filter(Boolean)\n      );\n      describedBy.add(this.id);\n      this.#trigger.setAttribute('aria-describedby', [...describedBy].join(' '));\n\n      // Set up anchor relationship if CSS anchors are supported\n      if (RenTooltip.supportsAnchor) {\n        this.#trigger.style.anchorName = '--tooltip-anchor';\n      } else {\n        // Fallback: ensure trigger can be positioned relative to\n        if (getComputedStyle(this.#trigger).position === 'static') {\n          this.#trigger.style.position = 'relative';\n        }\n      }\n    }\n  }\n\n  /**\n   * Attach event listeners to trigger\n   * @private\n   */\n  attachTriggerListeners() {\n    if (!this.#trigger) return;\n\n    this.#listenerController?.abort();\n    this.#listenerController = new AbortController();\n    const { signal } = this.#listenerController;\n\n    // Mouse events\n    this.#trigger.addEventListener('mouseenter', () => this.scheduleShow(), { signal });\n    this.#trigger.addEventListener('mouseleave', () => this.scheduleHide(), { signal });\n\n    // Focus events (use capture for better timing)\n    this.#trigger.addEventListener('focus', () => this.scheduleShow(), { capture: true, signal });\n    this.#trigger.addEventListener('blur', () => this.scheduleHide(), { capture: true, signal });\n\n    // Touch events for long-press detection\n    this.#trigger.addEventListener('touchstart', (e) => this.handleTouchStart(e), { signal });\n    this.#trigger.addEventListener('touchend', (e) => this.handleTouchEnd(e), { signal });\n  }\n\n  /**\n   * Handle touch start (long press detection)\n   * @private\n   */\n  handleTouchStart(e) {\n    this.#touchTimer = setTimeout(() => {\n      this.show();\n    }, 500);\n  }\n\n  /**\n   * Handle touch end\n   * @private\n   */\n  handleTouchEnd(e) {\n    if (this.#touchTimer) {\n      clearTimeout(this.#touchTimer);\n      this.#touchTimer = null;\n    }\n\n    if (this.isOpen()) {\n      this.hide();\n    }\n  }\n\n  /**\n   * Schedule tooltip show with delay\n   * @private\n   */\n  scheduleShow() {\n    this.clearTimeouts();\n\n    // Reduced-motion users should not wait through an animation-oriented\n    // reveal delay; the tooltip remains available on focus/hover.\n    if (typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches) {\n      this.show();\n      return;\n    }\n\n    const tokenDelay = getComputedStyle(this).getPropertyValue('--ren-tooltip-delay').trim();\n    const attrDelay = this.getAttribute('show-delay');\n    const parsedAttrDelay = attrDelay == null ? Number.NaN : parseInt(attrDelay, 10);\n    const parsedTokenDelay = parseInt(tokenDelay, 10);\n    const delay = Number.isFinite(parsedAttrDelay)\n      ? parsedAttrDelay\n      : Number.isFinite(parsedTokenDelay)\n        ? parsedTokenDelay\n        : 500;\n    this.#showTimeout = setTimeout(() => {\n      this.show();\n    }, delay);\n  }\n\n  /**\n   * Schedule tooltip hide with delay\n   * @private\n   */\n  scheduleHide() {\n    this.clearTimeouts();\n\n    const delay = parseInt(this.getAttribute('hide-delay')) || 0;\n    this.#hideTimeout = setTimeout(() => {\n      this.hide();\n    }, delay);\n  }\n\n  /**\n   * Clear all pending timeouts\n   * @private\n   */\n  clearTimeouts() {\n    if (this.#showTimeout) clearTimeout(this.#showTimeout);\n    if (this.#hideTimeout) clearTimeout(this.#hideTimeout);\n    if (this.#touchTimer) clearTimeout(this.#touchTimer);\n\n    this.#showTimeout = null;\n    this.#hideTimeout = null;\n    this.#touchTimer = null;\n  }\n\n  /**\n   * Position the tooltip relative to trigger (fallback for non-anchor browsers)\n   * @private\n   */\n  positionTooltip() {\n    if (!this.#trigger || RenTooltip.supportsAnchor) return;\n\n    const placement = normalizePlacement(this.getAttribute('placement'), 'top');\n    const offset = parseInt(this.getAttribute('offset')) || 8;\n\n    const { x, y, finalPlacement } = computePosition(\n      this.#trigger,\n      this,\n      placement,\n      offset\n    );\n\n    this.style.left = `${x}px`;\n    this.style.top = `${y}px`;\n    this.setAttribute('data-side', finalPlacement);\n  }\n\n  #syncPlacement() {\n    const placement = normalizePlacement(this.getAttribute('placement'), 'top');\n    this.setAttribute('data-side', placement);\n  }\n\n  /**\n   * Show the tooltip\n   */\n  show() {\n    if (this.isOpen()) return;\n\n    this.clearTimeouts();\n    this.positionTooltip();\n    this.setAttribute('data-state', 'open');\n\n    if ('popover' in HTMLElement.prototype) {\n      try {\n        this.showPopover();\n      } catch {\n        // Already open or other error\n      }\n    } else {\n      this.classList.add('ren-open');\n    }\n\n    this.dispatchEvent(new CustomEvent('ren-open', { bubbles: true }));\n  }\n\n  /**\n   * Hide the tooltip\n   */\n  hide() {\n    if (!this.isOpen()) return;\n\n    this.clearTimeouts();\n    this.setAttribute('data-state', 'closed');\n\n    if ('popover' in HTMLElement.prototype) {\n      try {\n        this.hidePopover();\n      } catch {\n        // Already closed or other error\n      }\n    } else {\n      this.classList.remove('ren-open');\n    }\n\n    this.dispatchEvent(new CustomEvent('ren-close', { bubbles: true }));\n  }\n\n  /**\n   * Check if tooltip is currently open\n   * @returns {boolean}\n   */\n  isOpen() {\n    if ('popover' in HTMLElement.prototype) {\n      return this.matches(':popover-open');\n    }\n    return this.classList.contains('ren-open');\n  }\n\n  /**\n   * Cleanup event listeners and timeouts\n   * @private\n   */\n  cleanup() {\n    this.clearTimeouts();\n    this.#listenerController?.abort();\n    this.#listenerController = null;\n    if (this.#trigger) {\n      const describedBy = (this.#trigger.getAttribute('aria-describedby') || '')\n        .split(/\\s+/)\n        .filter((token) => token && token !== this.id);\n      if (describedBy.length > 0) {\n        this.#trigger.setAttribute('aria-describedby', describedBy.join(' '));\n      } else {\n        this.#trigger.removeAttribute('aria-describedby');\n      }\n    }\n  }\n\n  /**\n   * Get the trigger element\n   * @returns {HTMLElement|null}\n   */\n  getTrigger() {\n    return this.#trigger;\n  }\n}\n\nif (!customElements.get('ren-tooltip')) {\n  customElements.define('ren-tooltip', RenTooltip);\n}\n",
      "path": "components/composites/ren-tooltip/ren-tooltip.js",
      "id": "file:components/composites/ren-tooltip/ren-tooltip.js",
      "type": "javascript",
      "name": "ren-tooltip.js"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Carbon for AI Patterns\n   ============================================\n   Design patterns for AI-generated content.\n   Inspired by IBM Carbon for AI.\n\n   Components:\n   - AI Message bubble (streaming + complete)\n   - AI Typing indicator\n   - AI Citation / Source reference\n   - AI Confidence indicator\n   - AI Regenerate action\n   - AI Prompt input\n   - AI Slug (inline AI label)\n\n   All CSS-only where possible, minimal JS for\n   streaming text animation.\n   ============================================ */\n\n/* ═══════════════════════════════════════════════\n   AI SLUG — Inline \"AI\" label\n   ═══════════════════════════════════════════════ */\n.ren-ai-slug {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-1);\n  padding: var(--space-0-5) var(--space-2);\n  font-size: var(--caption-sm-size);\n  font-weight: var(--weight-semibold);\n  letter-spacing: var(--tracking-wide);\n  text-transform: uppercase;\n  color: var(--color-ai);\n  background: color-mix(in srgb, var(--color-ai) 10%, transparent);\n  border-radius: var(--radius-sm);\n  line-height: 1;\n}\n\n.ren-ai-slug::before {\n  content: '✦';\n  font-size: 0.75em;\n}\n\n/* ═══════════════════════════════════════════════\n   AI MESSAGE — Chat bubble for AI responses\n   ═══════════════════════════════════════════════ */\n.ren-ai-message {\n  position: relative;\n  padding: var(--space-4);\n  border-radius: var(--radius-lg);\n  background: color-mix(in srgb, var(--color-ai-subtle) 70%, var(--color-surface-raised));\n  border: var(--stroke-1) solid var(--color-border);\n  line-height: var(--leading-relaxed);\n}\n\n.ren-ai-message-header {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  margin-bottom: var(--space-3);\n  font-size: var(--label-size);\n}\n\n.ren-ai-message-content {\n  font-size: var(--body-size);\n  color: var(--color-text);\n}\n\n.ren-ai-message-content p:last-child {\n  margin-bottom: 0;\n}\n\n.ren-ai-message-footer {\n  display: flex;\n  align-items: center;\n  gap: var(--space-3);\n  margin-top: var(--space-3);\n  padding-top: var(--space-3);\n  border-top: var(--stroke-1) solid var(--color-border-muted);\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n}\n\n/* ═══════════════════════════════════════════════\n   AI STREAMING — Typing animation\n   ═══════════════════════════════════════════════ */\n.ren-ai-streaming .ren-ai-message-content::after {\n  content: '▊';\n  display: inline;\n  animation: ren-ai-blink 0.8s step-end infinite;\n  color: var(--color-ai);\n  margin-inline-start: 1px;\n}\n\n@keyframes ren-ai-blink {\n  0%, 100% { opacity: 1; }\n  50%      { opacity: 0; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-ai-streaming .ren-ai-message-content::after {\n    animation: none;\n    opacity: 0.7;\n  }\n}\n\n/* ═══════════════════════════════════════════════\n   AI TYPING INDICATOR — \"AI is thinking...\"\n   ═══════════════════════════════════════════════ */\n.ren-ai-typing {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n}\n\n.ren-ai-typing-dots {\n  display: inline-flex;\n  gap: 3px;\n}\n\n.ren-ai-typing-dots span {\n  width: 6px;\n  height: 6px;\n  border-radius: var(--radius-full);\n  background: var(--color-ai);\n  animation: ren-ai-dot-bounce 1.4s ease-in-out infinite;\n}\n\n.ren-ai-typing-dots span:nth-child(2) { animation-delay: 0.2s; }\n.ren-ai-typing-dots span:nth-child(3) { animation-delay: 0.4s; }\n\n@keyframes ren-ai-dot-bounce {\n  0%, 80%, 100% { transform: translateY(0); opacity: 0.4; }\n  40%           { transform: translateY(-4px); opacity: 1; }\n}\n\n@keyframes ren-ai-dot-fade {\n  0%, 80%, 100% { opacity: 0.3; }\n  40%           { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-ai-typing-dots span {\n    animation: ren-ai-dot-fade 1.4s ease-in-out infinite;\n  }\n}\n\n/* ═══════════════════════════════════════════════\n   AI CITATION — Source reference\n   ═══════════════════════════════════════════════ */\n.ren-ai-citation {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-1);\n  padding: var(--space-0-5) var(--space-2);\n  font-size: var(--caption-sm-size);\n  font-weight: var(--weight-medium);\n  color: var(--color-ai);\n  background: color-mix(in srgb, var(--color-ai) 8%, transparent);\n  border-radius: var(--radius-sm);\n  text-decoration: none;\n  cursor: pointer;\n  transition: background var(--duration-tactile) var(--ease-enter);\n  vertical-align: super;\n  line-height: 1;\n}\n\n.ren-ai-citation:hover {\n  background: color-mix(in srgb, var(--color-ai) 14%, transparent);\n}\n\n/* Citation list at bottom of message */\n.ren-ai-sources {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-2);\n  margin-top: var(--space-3);\n  padding-top: var(--space-3);\n  border-top: var(--stroke-1) solid var(--color-border-muted);\n}\n\n.ren-ai-source {\n  display: flex;\n  align-items: flex-start;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  border-radius: var(--radius-md);\n  background: var(--color-surface-sunken);\n  font-size: var(--caption-size);\n  text-decoration: none;\n  color: var(--color-text);\n  transition: background var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-ai-source:hover {\n  background: var(--color-fill);\n}\n\n.ren-ai-source-number {\n  flex-shrink: 0;\n  width: 1.25rem;\n  height: 1.25rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: var(--caption-sm-size);\n  font-weight: var(--weight-bold);\n  color: var(--color-ai);\n  background: color-mix(in srgb, var(--color-ai) 10%, transparent);\n  border-radius: var(--radius-sm);\n}\n\n.ren-ai-source-title {\n  font-weight: var(--weight-medium);\n}\n\n.ren-ai-source-url {\n  color: var(--color-text-muted);\n  font-size: var(--caption-sm-size);\n}\n\n/* ═══════════════════════════════════════════════\n   AI CONFIDENCE — Certainty indicator\n   ═══════════════════════════════════════════════ */\n.ren-ai-confidence {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-2);\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n}\n\n.ren-ai-confidence-bar {\n  width: 3rem;\n  height: 4px;\n  border-radius: var(--radius-full);\n  background: var(--color-fill);\n  overflow: hidden;\n}\n\n.ren-ai-confidence-fill {\n  height: 100%;\n  border-radius: var(--radius-full);\n  transition: width var(--duration-state) var(--ease-enter);\n}\n\n.ren-ai-confidence[data-level=\"high\"] .ren-ai-confidence-fill {\n  width: 100%;\n  background: var(--color-success);\n}\n\n.ren-ai-confidence[data-level=\"medium\"] .ren-ai-confidence-fill {\n  width: 66%;\n  background: var(--color-warning);\n}\n\n.ren-ai-confidence[data-level=\"low\"] .ren-ai-confidence-fill {\n  width: 33%;\n  background: var(--color-danger);\n}\n\n/* ═══════════════════════════════════════════════\n   AI ACTIONS — Regenerate, Copy, Feedback\n   ═══════════════════════════════════════════════ */\n.ren-ai-actions {\n  display: flex;\n  align-items: center;\n  gap: var(--space-1);\n}\n\n.ren-ai-action {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--space-1);\n  padding: var(--space-1) var(--space-2);\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n  background: transparent;\n  border: none;\n  border-radius: var(--radius-sm);\n  cursor: pointer;\n  transition: all var(--duration-tactile) var(--ease-enter);\n  min-height: var(--size-sm);\n}\n\n.ren-ai-action:hover {\n  color: var(--color-text);\n  background: var(--color-fill);\n}\n\n.ren-ai-action:active {\n  transform: scale(0.95);\n}\n\n.ren-ai-action[data-active=\"true\"] {\n  color: var(--color-ai);\n}\n\n/* Feedback buttons (thumbs up/down) */\n.ren-ai-action-thumbs {\n  display: inline-flex;\n  gap: 0;\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-md);\n  overflow: hidden;\n}\n\n.ren-ai-action-thumbs .ren-ai-action {\n  border-radius: 0;\n  padding: var(--space-1) var(--space-2);\n}\n\n.ren-ai-action-thumbs .ren-ai-action + .ren-ai-action {\n  border-inline-start: var(--stroke-1) solid var(--color-border);\n}\n\n/* ═══════════════════════════════════════════════\n   AI PROMPT INPUT — Chat-style input\n   ═══════════════════════════════════════════════ */\n.ren-ai-prompt {\n  display: flex;\n  align-items: flex-end;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-xl);\n  background: var(--color-surface);\n  transition: border-color var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-ai-prompt:focus-within {\n  border-color: var(--color-ai);\n  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-ai) 15%, transparent);\n}\n\n.ren-ai-prompt-input {\n  flex: 1;\n  border: none;\n  background: transparent;\n  font-family: inherit;\n  font-size: var(--body-size);\n  color: var(--color-text);\n  resize: none;\n  outline: none;\n  min-height: 1.5rem;\n  max-height: 8rem;\n  field-sizing: content;\n  line-height: var(--leading-normal);\n}\n\n.ren-ai-prompt-input::placeholder {\n  color: var(--color-text-muted);\n}\n\n.ren-ai-prompt-send {\n  flex-shrink: 0;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: var(--size-md);\n  height: var(--size-md);\n  border: none;\n  border-radius: var(--radius-full);\n  background: var(--color-ai);\n  color: var(--color-on-ai);\n  cursor: pointer;\n  transition: all var(--duration-tactile) var(--ease-enter);\n  font-size: var(--text-lg);\n}\n\n.ren-ai-prompt-send:hover {\n  background: var(--color-ai-hover);\n  transform: scale(1.05);\n}\n\n.ren-ai-prompt-send:disabled {\n  opacity: 0.4;\n  cursor: not-allowed;\n  transform: none;\n}\n\n/* ═══════════════════════════════════════════════\n   AI SKELETON — Loading placeholder for AI\n   ═══════════════════════════════════════════════ */\n.ren-ai-skeleton {\n  padding: var(--space-4);\n  border-radius: var(--radius-lg);\n  background: color-mix(in srgb, var(--color-ai-subtle) 55%, var(--color-surface-raised));\n  border: var(--stroke-1) solid var(--color-border-muted);\n}\n\n.ren-ai-skeleton-line {\n  height: 0.875rem;\n  border-radius: var(--radius-sm);\n  background: linear-gradient(\n    90deg,\n    var(--color-fill) 25%,\n    color-mix(in srgb, var(--color-ai) 6%, var(--color-fill)) 50%,\n    var(--color-fill) 75%\n  );\n  background-size: 200% 100%;\n  animation: ren-ai-shimmer 1.5s ease-in-out infinite;\n}\n\n.ren-ai-skeleton-line + .ren-ai-skeleton-line {\n  margin-top: var(--space-2);\n}\n\n.ren-ai-skeleton-line:last-child {\n  width: 60%;\n}\n\n@keyframes ren-ai-shimmer {\n  0%   { background-position: 200% 0; }\n  100% { background-position: -200% 0; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-ai-skeleton-line {\n    animation: none;\n  }\n}\n\n/* ═══════════════════════════════════════════════\n   AI ERROR — Message failure state\n   ═══════════════════════════════════════════════ */\n.ren-ai-error {\n  position: relative;\n  padding: var(--space-4);\n  border-radius: var(--radius-lg);\n  background: color-mix(in srgb, var(--color-danger) 6%, var(--color-surface-raised));\n  border: var(--stroke-1) solid color-mix(in srgb, var(--color-danger) 30%, transparent);\n  display: grid;\n  gap: var(--space-2);\n}\n\n.ren-ai-error-message {\n  color: var(--color-text);\n  font-size: var(--body-size);\n  line-height: 1.5;\n}\n\n.ren-ai-error-message::before {\n  content: '⚠ ';\n  color: var(--color-danger);\n  margin-inline-end: var(--space-1);\n}\n\n/* ═══════════════════════════════════════════════\n   AI ACTION VARIANTS — Regenerate, edit-and-resend\n   These extend the existing .ren-ai-action base.\n   ═══════════════════════════════════════════════ */\n.ren-ai-action-regenerate::before {\n  content: '↻';\n  margin-inline-end: var(--space-1);\n  font-size: 1.1em;\n  display: inline-block;\n  /* Subtle hint that this re-runs inference */\n  transition: transform var(--duration-state, 200ms) var(--ease-enter, ease);\n}\n\n.ren-ai-action-regenerate:hover::before {\n  transform: rotate(-45deg);\n}\n\n.ren-ai-action-edit::before {\n  content: '✎';\n  margin-inline-end: var(--space-1);\n  font-size: 1.05em;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-ai-action-regenerate::before {\n    transition: none;\n  }\n}\n\n/* ═══════════════════════════════════════════════\n   AI FILE CHIP — Uploaded file reference\n   Compact pill showing a file the user attached,\n   with icon, name, size, and remove affordance.\n   ═══════════════════════════════════════════════ */\n.ren-ai-file-chip {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-2);\n  padding: var(--space-1) var(--space-2);\n  background: var(--color-surface-raised);\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-md);\n  font-size: var(--caption-size);\n  max-width: 220px;\n}\n\n.ren-ai-file-chip-icon {\n  flex-shrink: 0;\n  width: 1rem;\n  height: 1rem;\n  color: var(--color-text-muted);\n  display: grid;\n  place-items: center;\n}\n\n.ren-ai-file-chip-name {\n  flex: 1 1 auto;\n  min-width: 0;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  color: var(--color-text);\n  font-weight: var(--weight-medium);\n}\n\n.ren-ai-file-chip-size {\n  flex-shrink: 0;\n  color: var(--color-text-muted);\n  font-family: var(--font-mono);\n  font-size: var(--caption-sm-size);\n}\n\n.ren-ai-file-chip-remove {\n  flex-shrink: 0;\n  width: 1.25rem;\n  height: 1.25rem;\n  display: grid;\n  place-items: center;\n  border: 0;\n  background: transparent;\n  border-radius: var(--radius-sm);\n  color: var(--color-text-muted);\n  font-size: 1rem;\n  line-height: 1;\n  cursor: pointer;\n  padding: 0;\n}\n\n.ren-ai-file-chip-remove:hover,\n.ren-ai-file-chip-remove:focus-visible {\n  background: var(--color-fill);\n  color: var(--color-text);\n}\n\n.ren-ai-file-chip-remove:focus-visible {\n  outline: 2px solid var(--color-focus-ring);\n  outline-offset: 2px;\n}\n\n/* ═══════════════════════════════════════════════\n   AI TOOL CALL — Model invoking a tool / function\n   Collapsible block built on native <details>\n   so JS is optional. Status via data-status.\n   ═══════════════════════════════════════════════ */\n.ren-ai-tool-call {\n  border: var(--stroke-1) solid var(--color-border);\n  border-radius: var(--radius-md);\n  background: var(--color-surface-raised);\n  margin-block: var(--space-2);\n  overflow: hidden;\n}\n\n.ren-ai-tool-call-header {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  background: var(--color-surface-sunken);\n  font-size: var(--caption-size);\n  font-weight: var(--weight-medium);\n  color: var(--color-text);\n  cursor: pointer;\n  user-select: none;\n  list-style: none;\n}\n\n.ren-ai-tool-call-header::-webkit-details-marker { display: none; }\n\n.ren-ai-tool-call-header::before {\n  content: '▸';\n  font-size: 0.75em;\n  color: var(--color-text-muted);\n  transition: transform var(--duration-state, 200ms) var(--ease-enter, ease);\n}\n\n.ren-ai-tool-call[open] > .ren-ai-tool-call-header::before {\n  transform: rotate(90deg);\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-ai-tool-call-header::before {\n    transition: none;\n  }\n}\n\n.ren-ai-tool-call-name {\n  font-family: var(--font-mono);\n  color: var(--color-text);\n}\n\n.ren-ai-tool-call-status {\n  margin-inline-start: auto;\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-1);\n  padding: 2px var(--space-2);\n  border-radius: var(--radius-full);\n  font-size: var(--caption-sm-size);\n  font-weight: var(--weight-medium);\n  text-transform: uppercase;\n  letter-spacing: 0.04em;\n  border: var(--stroke-1) solid transparent;\n}\n\n.ren-ai-tool-call[data-status=\"pending\"] .ren-ai-tool-call-status {\n  color: var(--color-text-muted);\n  background: var(--color-fill);\n}\n\n.ren-ai-tool-call[data-status=\"running\"] .ren-ai-tool-call-status {\n  color: var(--color-warning);\n  background: color-mix(in srgb, var(--color-warning) 15%, transparent);\n}\n\n.ren-ai-tool-call[data-status=\"success\"] .ren-ai-tool-call-status {\n  color: var(--color-success);\n  background: color-mix(in srgb, var(--color-success) 15%, transparent);\n}\n\n.ren-ai-tool-call[data-status=\"error\"] .ren-ai-tool-call-status {\n  color: var(--color-danger);\n  background: color-mix(in srgb, var(--color-danger) 15%, transparent);\n}\n\n.ren-ai-tool-call-args,\n.ren-ai-tool-call-result {\n  padding: var(--space-3);\n  font-family: var(--font-mono);\n  font-size: var(--caption-size);\n  line-height: 1.5;\n  color: var(--color-text-secondary);\n  white-space: pre-wrap;\n  overflow-x: auto;\n}\n\n.ren-ai-tool-call-args {\n  border-bottom: var(--stroke-1) solid var(--color-border-muted);\n}\n\n.ren-ai-tool-call-result {\n  background: var(--color-surface);\n}\n\n.ren-ai-tool-call[data-status=\"error\"] .ren-ai-tool-call-result {\n  background: color-mix(in srgb, var(--color-danger) 4%, var(--color-surface));\n  color: var(--color-text);\n}\n",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "file:components/patterns/ren-ai/ren-ai.css",
      "type": "css",
      "name": "ren-ai.css"
    },
    {
      "data": {},
      "body": ".ren-command {\n  position: fixed;\n  top: 50%;\n  inset-inline-start: 50%;\n  transform: translate(-50%, -50%);\n  max-width: 32rem;\n  width: 100%;\n  max-height: 24rem;\n  background-color: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--ren-radius-xl, 0.75rem);\n  box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);\n  padding: 0;\n  overflow: hidden;\n  display: flex;\n  flex-direction: column;\n  z-index: var(--ren-z-modal, 1000);\n  margin: auto;\n  animation: ren-command-in var(--duration-enter) var(--ease-enter);\n\n  @starting-style {\n    opacity: 0;\n    transform: translate(-50%, -50%) scale(0.98);\n  }\n\n  @media (max-width: 32rem) {\n    width: calc(100% - 2rem);\n    max-height: 18rem;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    animation: none;\n  }\n\n  &::backdrop {\n    background-color: rgb(0 0 0 / 0.5);\n    backdrop-filter: blur(4px);\n    animation: ren-command-backdrop-in var(--duration-overlay) var(--ease-enter);\n\n    @starting-style {\n      background-color: rgb(0 0 0 / 0);\n      backdrop-filter: blur(0);\n    }\n\n    @media (prefers-reduced-motion: reduce) {\n      animation: none;\n    }\n  }\n}\n\n@keyframes ren-command-in {\n  from {\n    opacity: 0;\n    transform: translate(-50%, -50%) scale(0.98);\n  }\n\n  to {\n    opacity: 1;\n    transform: translate(-50%, -50%) scale(1);\n  }\n}\n\n@keyframes ren-command-backdrop-in {\n  from {\n    background-color: rgb(0 0 0 / 0);\n    backdrop-filter: blur(0);\n  }\n\n  to {\n    background-color: rgb(0 0 0 / 0.5);\n    backdrop-filter: blur(4px);\n  }\n}\n\n.ren-command-input-wrapper {\n  display: flex;\n  align-items: center;\n  border-bottom: 1px solid var(--color-border);\n  padding: var(--ren-space-2, 0.5rem) var(--ren-space-3, 0.75rem);\n  gap: var(--ren-space-2, 0.5rem);\n  flex-shrink: 0;\n\n  & svg {\n    width: 18px;\n    height: 18px;\n    color: var(--color-text-muted);\n    flex-shrink: 0;\n  }\n}\n\n.ren-command-input {\n  flex: 1;\n  border: none;\n  background-color: transparent;\n  font-size: var(--ren-body-size, 0.875rem);\n  color: var(--color-text);\n  outline: none;\n  padding: 0;\n  font-family: inherit;\n\n  &::placeholder {\n    color: var(--color-text-muted);\n  }\n\n  &:autofill {\n    box-shadow: inset 0 0 0 1000px var(--color-surface);\n    -webkit-text-fill-color: var(--color-text);\n  }\n}\n\n.ren-command-kbd {\n  margin-inline-start: auto;\n  display: flex;\n  gap: var(--ren-space-1, 0.25rem);\n  align-items: center;\n  font-size: var(--ren-caption-size, 0.75rem);\n  color: var(--color-text-muted);\n  font-family: var(--ren-font-mono, 'Monaco', 'Menlo', monospace);\n  white-space: nowrap;\n\n  & kbd {\n    padding: 0.125rem 0.375rem;\n    background-color: var(--color-fill);\n    border: 1px solid var(--color-border);\n    border-radius: 0.25rem;\n    font-size: inherit;\n  }\n\n  @media (max-width: 32rem) {\n    display: none;\n  }\n}\n\n.ren-command-list {\n  overflow-y: auto;\n  max-height: 18rem;\n  padding: var(--ren-space-1, 0.25rem);\n  flex: 1;\n  scrollbar-width: thin;\n  scrollbar-color: var(--color-border) transparent;\n\n  &::-webkit-scrollbar {\n    width: 6px;\n  }\n\n  &::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background-color: var(--color-border);\n    border-radius: 3px;\n\n    &:hover {\n      background-color: var(--color-text-muted);\n    }\n  }\n}\n\n.ren-command-group {\n  margin-bottom: var(--ren-space-2, 0.5rem);\n  overflow: hidden;\n\n  &[data-empty] {\n    display: none;\n  }\n}\n\n.ren-command-group-heading {\n  padding: var(--ren-space-1, 0.25rem) var(--ren-space-3, 0.75rem);\n  font-size: var(--ren-caption-size, 0.75rem);\n  font-weight: 600;\n  color: var(--color-text-faint);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  user-select: none;\n}\n\n.ren-command-item {\n  display: flex;\n  align-items: center;\n  gap: var(--ren-space-3, 0.75rem);\n  padding: var(--ren-space-2, 0.5rem) var(--ren-space-3, 0.75rem);\n  border-radius: 0.375rem;\n  cursor: pointer;\n  transition: background-color var(--duration-state) var(--ease-state-change);\n  user-select: none;\n  background-color: transparent;\n  border: none;\n  width: 100%;\n  text-align: start;\n  font-family: inherit;\n\n  &:hover {\n    background-color: var(--color-fill);\n  }\n\n  &:active {\n    background-color: var(--color-fill-hover);\n  }\n\n  &[data-highlighted] {\n    background-color: var(--color-fill);\n  }\n\n  &[aria-disabled=\"true\"] {\n    opacity: 0.5;\n    cursor: not-allowed;\n    pointer-events: none;\n  }\n\n  &:focus-visible {\n    outline: none;\n    background-color: var(--color-fill);\n  }\n}\n\n.ren-command-item-icon {\n  flex-shrink: 0;\n  width: 20px;\n  height: 20px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  color: var(--color-text-muted);\n\n  & svg {\n    width: 100%;\n    height: 100%;\n  }\n}\n\n.ren-command-item-content {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  gap: 0.125rem;\n  min-width: 0;\n}\n\n.ren-command-item-title {\n  font-size: var(--ren-body-size, 0.875rem);\n  color: var(--color-text);\n  font-weight: 500;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.ren-command-item-description {\n  font-size: var(--ren-caption-size, 0.75rem);\n  color: var(--color-text-muted);\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.ren-command-item-shortcut {\n  margin-inline-start: auto;\n  display: flex;\n  gap: var(--ren-space-1, 0.25rem);\n  font-family: var(--ren-font-mono, 'Monaco', 'Menlo', monospace);\n  font-size: var(--ren-caption-size, 0.75rem);\n  color: var(--color-text-muted);\n  white-space: nowrap;\n  flex-shrink: 0;\n\n  @media (max-width: 40rem) {\n    display: none;\n  }\n}\n\n.ren-command-separator {\n  height: 1px;\n  background-color: var(--color-separator);\n  margin: var(--ren-space-1, 0.25rem) 0;\n}\n\n.ren-command-empty {\n  padding: var(--ren-space-8, 2rem);\n  text-align: center;\n  color: var(--color-text-muted);\n  font-size: var(--ren-body-size, 0.875rem);\n}\n\n.ren-command-footer {\n  border-top: 1px solid var(--color-border);\n  padding: var(--ren-space-2, 0.5rem);\n  display: flex;\n  gap: var(--ren-space-4, 1rem);\n  font-size: var(--ren-caption-size, 0.75rem);\n  color: var(--color-text-muted);\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  justify-content: center;\n\n  @media (max-width: 32rem) {\n    padding: var(--ren-space-1, 0.25rem) var(--ren-space-2, 0.5rem);\n    gap: var(--ren-space-2, 0.5rem);\n  }\n}\n\n.ren-command-footer-hint {\n  display: flex;\n  gap: var(--ren-space-1, 0.25rem);\n  align-items: center;\n\n  & kbd {\n    padding: 0.125rem 0.375rem;\n    background-color: var(--color-fill);\n    border: 1px solid var(--color-border);\n    border-radius: 0.25rem;\n    font-size: inherit;\n    font-family: var(--ren-font-mono, 'Monaco', 'Menlo', monospace);\n  }\n}\n/* Appearance API bridge */\n.ren-command { inline-size: var(--ren-command-width, min(95vw, 560px)); max-block-size: var(--ren-command-max-height, 340px); border-radius: var(--ren-command-radius, var(--radius-xl)); background: var(--ren-command-bg, var(--color-surface-overlay)); border-color: var(--ren-command-border, var(--color-border)); box-shadow: var(--ren-command-shadow, var(--shadow-xl)); }\n.ren-command [role=\"option\"], .ren-command-item { min-block-size: var(--ren-command-item-height, var(--size-lg)); border-radius: var(--ren-command-item-radius, var(--radius-md)); }\n",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "file:components/patterns/ren-command/ren-command.css",
      "type": "css",
      "name": "ren-command.css"
    },
    {
      "data": {},
      "body": "/**\n * RenCommand Component - Command Palette / Spotlight search\n * Modern dialog-based command palette with keyboard navigation\n */\n\nconst KEYBOARD_CODES = {\n  Escape: 'Escape',\n  Enter: 'Enter',\n  ArrowDown: 'ArrowDown',\n  ArrowUp: 'ArrowUp'\n};\n\nclass RenCommand extends HTMLElement {\n  constructor() {\n    super();\n    this._dialog = null;\n    this._input = null;\n    this._list = null;\n    this._items = [];\n    this._groups = [];\n    this._highlightedIndex = 0;\n    this._shortcuts = new Map();\n    this._focusTrap = null;\n    this._lastQuery = '';\n    this._shortcutKey = 'k';\n    this._shortcutMeta = 'ctrl';\n    this._listenerController = null;\n  }\n\n  connectedCallback() {\n    this._initDialog();\n    this._initElements();\n    this._attachEventListeners();\n    this._setupGlobalShortcut();\n  }\n\n  disconnectedCallback() {\n    this._removeEventListeners();\n  }\n\n  _initDialog() {\n    this._dialog = this.querySelector('dialog') || this;\n    if (this._dialog !== this && this._dialog.tagName !== 'DIALOG') {\n      // Fallback: ensure we have a dialog\n      this._dialog = this;\n    }\n  }\n\n  _initElements() {\n    this._input = this.querySelector('.ren-command-input');\n    this._list = this.querySelector('.ren-command-list');\n    this._items = Array.from(this.querySelectorAll('.ren-command-item'));\n    this._groups = Array.from(this.querySelectorAll('.ren-command-group'));\n  }\n\n  _attachEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = new AbortController();\n    const { signal } = this._listenerController;\n\n    // Input events\n    if (this._input) {\n      this._input.addEventListener('input', (e) => this._handleInput(e), { signal });\n      this._input.addEventListener('keydown', (e) => this._handleKeydown(e), { signal });\n    }\n\n    // Item selection\n    this._items.forEach((item, index) => {\n      item.addEventListener('click', () => this._selectItem(index), { signal });\n      item.addEventListener('mouseenter', () => this._setHighlighted(index), { signal });\n      item.addEventListener('focus', () => this._setHighlighted(index), { signal });\n    });\n\n    // Dialog close\n    if (this._dialog && this._dialog.tagName === 'DIALOG') {\n      this._dialog.addEventListener('cancel', () => this._close(), { signal });\n    }\n\n    // Close on Escape\n    document.addEventListener('keydown', (e) => {\n      if (e.key === KEYBOARD_CODES.Escape && this._isOpen()) {\n        this._close();\n      }\n    }, { signal });\n  }\n\n  _removeEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = null;\n  }\n\n  _setupGlobalShortcut() {\n    const shortcut = this.getAttribute('data-shortcut') || 'ctrl+k';\n    const [meta, key] = shortcut.toLowerCase().split('+');\n    this._shortcutKey = key || 'k';\n    this._shortcutMeta = meta || 'ctrl';\n\n    document.addEventListener('keydown', (e) => {\n      const isMetaKey = this._shortcutMeta === 'ctrl' ? e.ctrlKey : e.metaKey;\n      if (isMetaKey && e.key.toLowerCase() === this._shortcutKey) {\n        e.preventDefault();\n        this._open();\n      }\n    }, { signal: this._listenerController?.signal });\n  }\n\n  _open() {\n    if (this._dialog.tagName === 'DIALOG') {\n      this._dialog.showModal?.();\n    } else {\n      this.style.display = 'block';\n    }\n\n    if (this._input) {\n      this._input.focus();\n      this._input.select();\n    }\n\n    this._highlightedIndex = 0;\n    this._updateUI();\n  }\n\n  _close() {\n    if (this._dialog.tagName === 'DIALOG') {\n      this._dialog.close?.();\n    } else {\n      this.style.display = 'none';\n    }\n\n    this._input?.blur();\n  }\n\n  _isOpen() {\n    if (this._dialog.tagName === 'DIALOG') {\n      return this._dialog.open;\n    }\n    return this.style.display !== 'none';\n  }\n\n  _handleInput(e) {\n    const query = e.target.value.toLowerCase().trim();\n    this._lastQuery = query;\n    this._filterItems(query);\n    this._highlightedIndex = 0;\n    this._updateUI();\n  }\n\n  _handleKeydown(e) {\n    switch (e.key) {\n      case KEYBOARD_CODES.ArrowDown:\n        e.preventDefault();\n        this._moveHighlight(1);\n        break;\n      case KEYBOARD_CODES.ArrowUp:\n        e.preventDefault();\n        this._moveHighlight(-1);\n        break;\n      case KEYBOARD_CODES.Enter:\n        e.preventDefault();\n        const visibleItem = this._getVisibleItems()[this._highlightedIndex];\n        if (visibleItem) {\n          this._selectItem(this._items.indexOf(visibleItem));\n        }\n        break;\n      case KEYBOARD_CODES.Escape:\n        e.preventDefault();\n        this._close();\n        break;\n    }\n  }\n\n  _filterItems(query) {\n    this._items.forEach(item => {\n      if (!query) {\n        item.style.display = '';\n        return;\n      }\n\n      const title = item.querySelector('.ren-command-item-title')?.textContent || '';\n      const description = item.querySelector('.ren-command-item-description')?.textContent || '';\n      const keywords = item.getAttribute('data-keywords') || '';\n      const searchableText = `${title} ${description} ${keywords}`.toLowerCase();\n\n      const matches = query.split(' ').every(term => searchableText.includes(term));\n      item.style.display = matches ? '' : 'none';\n    });\n\n    this._updateGroupVisibility();\n    this._announceResults();\n  }\n\n  _updateGroupVisibility() {\n    this._groups.forEach(group => {\n      const visibleItems = Array.from(group.querySelectorAll('.ren-command-item'))\n        .filter(item => item.style.display !== 'none');\n\n      if (visibleItems.length === 0) {\n        group.setAttribute('data-empty', '');\n      } else {\n        group.removeAttribute('data-empty');\n      }\n    });\n\n    const hasVisibleItems = this._getVisibleItems().length > 0;\n    const empty = this.querySelector('.ren-command-empty');\n    if (empty) {\n      empty.style.display = hasVisibleItems ? 'none' : 'block';\n    }\n  }\n\n  _getVisibleItems() {\n    return this._items.filter(item => item.style.display !== 'none');\n  }\n\n  /**\n   * Announce number of search results to screen readers\n   * @private\n   */\n  _announceResults() {\n    const visibleItems = this._getVisibleItems();\n    const resultCount = visibleItems.length;\n\n    // Find or create announcement region\n    let announcement = this.querySelector('[role=\"status\"][aria-live=\"polite\"]');\n    if (!announcement) {\n      announcement = document.createElement('div');\n      announcement.setAttribute('role', 'status');\n      announcement.setAttribute('aria-live', 'polite');\n      announcement.className = 'ren-sr-only';\n      this.appendChild(announcement);\n    }\n\n    if (resultCount === 0) {\n      announcement.textContent = 'No results found';\n    } else {\n      announcement.textContent = `${resultCount} result${resultCount !== 1 ? 's' : ''} available`;\n    }\n  }\n\n  _moveHighlight(direction) {\n    const visibleItems = this._getVisibleItems();\n    if (visibleItems.length === 0) return;\n\n    let newIndex = this._highlightedIndex + direction;\n    if (newIndex < 0) newIndex = visibleItems.length - 1;\n    if (newIndex >= visibleItems.length) newIndex = 0;\n\n    this._highlightedIndex = newIndex;\n    this._updateUI({ focus: true });\n\n    const item = visibleItems[newIndex];\n    item?.scrollIntoView({ block: 'nearest' });\n  }\n\n  _setHighlighted(itemIndex) {\n    this._highlightedIndex = this._getVisibleItems().indexOf(this._items[itemIndex]);\n    if (this._highlightedIndex === -1) this._highlightedIndex = 0;\n    this._updateUI();\n  }\n\n  _updateUI({ focus = false } = {}) {\n    const visibleItems = this._getVisibleItems();\n    visibleItems.forEach((item, index) => {\n      if (index === this._highlightedIndex) {\n        item.setAttribute('data-highlighted', '');\n        if (focus) {\n          item.focus();\n        }\n      } else {\n        item.removeAttribute('data-highlighted');\n      }\n    });\n  }\n\n  _selectItem(itemIndex) {\n    const item = this._items[itemIndex];\n    if (!item || item.style.display === 'none') return;\n\n    const value = item.getAttribute('data-value') || item.textContent;\n    const action = item.getAttribute('data-action');\n\n    const event = new CustomEvent('ren-command-select', {\n      detail: { item, value, action },\n      bubbles: true,\n      composed: true\n    });\n    this.dispatchEvent(event);\n\n    // Call registered action handler\n    if (action && this._shortcuts.has(action)) {\n      this._shortcuts.get(action)();\n    }\n\n    this._resetCommand();\n    this._close();\n  }\n\n  _resetCommand() {\n    if (this._input) {\n      this._input.value = '';\n      this._lastQuery = '';\n    }\n    this._filterItems('');\n    this._highlightedIndex = 0;\n    this._updateUI();\n  }\n\n  // Public API\n  open() {\n    this._open();\n  }\n\n  close() {\n    this._close();\n  }\n\n  registerAction(id, handler) {\n    if (typeof handler === 'function') {\n      this._shortcuts.set(id, handler);\n    }\n  }\n\n  unregisterAction(id) {\n    this._shortcuts.delete(id);\n  }\n\n  setItems(items) {\n    if (Array.isArray(items)) {\n      this._items = items;\n      this._initElements();\n      this._updateUI();\n    }\n  }\n\n  get query() {\n    return this._lastQuery;\n  }\n\n  get isOpen() {\n    return this._isOpen();\n  }\n}\n\ncustomElements.define('ren-command', RenCommand);\n",
      "path": "components/patterns/ren-command/ren-command.js",
      "id": "file:components/patterns/ren-command/ren-command.js",
      "type": "javascript",
      "name": "ren-command.js"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Empty State Pattern\n   ============================================\n   Reusable pattern for empty/no-data states.\n   CSS-only. No JS needed.\n\n   Use inside any data container (table, list,\n   card) when there's nothing to display.\n\n   Inspired by Polaris: every data component\n   should have a documented empty state.\n\n   Usage:\n     <div class=\"ren-empty-state\">\n       <div class=\"ren-empty-state-icon\">📦</div>\n       <h3 class=\"ren-empty-state-title\">No products yet</h3>\n       <p class=\"ren-empty-state-description\">\n         Add your first product to get started.\n       </p>\n       <div class=\"ren-empty-state-actions\">\n         <button class=\"ren-btn\">Add product</button>\n       </div>\n     </div>\n\n   Compact (inline):\n     <div class=\"ren-empty-state ren-empty-state-compact\">\n       <p class=\"ren-empty-state-description\">No results found.</p>\n     </div>\n   ============================================ */\n\n.ren-empty-state {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  text-align: center;\n  padding: var(--space-8) var(--space-4);\n  max-width: 28rem;\n  margin-inline: auto;\n}\n\n/* ─── Icon / Illustration ─── */\n.ren-empty-state-icon {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 4rem;\n  height: 4rem;\n  font-size: var(--text-3xl);\n  color: var(--color-text-muted);\n  background-color: var(--color-fill);\n  border-radius: var(--radius-full);\n  margin-bottom: var(--space-4);\n}\n\n/* ─── Title ─── */\n.ren-empty-state-title {\n  font-size: var(--title-sm-size, var(--text-lg));\n  font-weight: var(--weight-semibold);\n  color: var(--color-text);\n  margin-bottom: var(--space-2);\n}\n\n/* ─── Description ─── */\n.ren-empty-state-description {\n  font-size: var(--body-size);\n  color: var(--color-text-muted);\n  line-height: var(--leading-normal);\n  margin-bottom: var(--space-4);\n  max-width: var(--width-prose);\n}\n\n.ren-empty-state-description:last-child {\n  margin-bottom: 0;\n}\n\n/* ─── Actions ─── */\n.ren-empty-state-actions {\n  display: flex;\n  gap: var(--space-2);\n  flex-wrap: wrap;\n  justify-content: center;\n}\n\n/* ─── Compact variant (for inline empty states) ─── */\n.ren-empty-state-compact {\n  padding: var(--space-6) var(--space-4);\n}\n\n.ren-empty-state-compact .ren-empty-state-icon {\n  width: 2.5rem;\n  height: 2.5rem;\n  font-size: var(--text-xl);\n  margin-bottom: var(--space-2);\n}\n\n.ren-empty-state-compact .ren-empty-state-title {\n  font-size: var(--body-size);\n}\n\n.ren-empty-state-compact .ren-empty-state-description {\n  font-size: var(--caption-size, var(--text-sm));\n}\n\n/* ─── Bordered variant (for use inside cards/tables) ─── */\n.ren-empty-state-bordered {\n  border: 2px dashed var(--color-border);\n  border-radius: var(--radius-lg);\n  margin: var(--space-4);\n}\n/* Appearance API bridge */\n.ren-empty-state { max-inline-size: var(--ren-empty-max-width, 24rem); padding: var(--ren-empty-padding, var(--space-10)); gap: var(--ren-empty-gap, var(--space-3)); }\n.ren-empty-state .ren-empty-state-icon { inline-size: var(--ren-empty-icon-size, 3rem); block-size: var(--ren-empty-icon-size, 3rem); }\n.ren-empty-state h1, .ren-empty-state h2, .ren-empty-state h3 { font-size: var(--ren-empty-title-size, var(--title-sm-size)); }\n.ren-empty-state p { color: var(--ren-empty-desc-color, var(--color-text-muted)); }\n",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "file:components/patterns/ren-empty-state/ren-empty-state.css",
      "type": "css",
      "name": "ren-empty-state.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Form Validation System Organism\n   ============================================\n   Multi-step form with field validation,\n   error summaries, and accessibility features.\n\n   Includes:\n   - Form container with field stack layout\n   - Section grouping and horizontal rows\n   - Error summary boxes (CSS shown/hidden)\n   - Multi-step progress indicators\n   - Loading/submitting states\n   - Responsive design\n\n   Light DOM — no Shadow DOM.\n   ============================================ */\n\n/* ─── Form Container ─── */\n.ren-form {\n  container-type: inline-size;\n  container-name: ren-form;\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-6);\n  width: 100%;\n  max-width: 100%;\n}\n\n/* Submitting state — disable interactions */\n.ren-form[data-submitting] {\n  opacity: 0.6;\n  pointer-events: none;\n}\n\n/* ─── Error Summary ─── */\n.ren-form-error-summary {\n  padding: var(--space-4);\n  background-color: var(--color-danger-subtle);\n  border: 1px solid var(--color-danger);\n  border-radius: var(--radius-md);\n  transition: all var(--duration-enter) var(--ease-enter);\n}\n\n.ren-form-error-summary[hidden],\n.ren-form-error-summary:not([data-has-errors]) {\n  display: none;\n}\n\n.ren-form-error-summary strong {\n  display: block;\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  color: var(--color-danger);\n  margin-bottom: var(--space-2);\n}\n\n.ren-form-error-summary ul {\n  list-style: none;\n  margin: 0;\n  padding: 0;\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-1);\n}\n\n.ren-form-error-summary li {\n  font-size: var(--caption-size);\n  color: var(--color-danger);\n  line-height: var(--leading-normal);\n}\n\n.ren-form-error-summary a {\n  color: var(--color-danger);\n  text-decoration: underline;\n  cursor: pointer;\n}\n\n.ren-form-error-summary a:hover {\n  text-decoration: none;\n}\n\n/* ─── Success Message ─── */\n.ren-form-success {\n  padding: var(--space-4);\n  background-color: var(--color-success-subtle);\n  border: 1px solid var(--color-success);\n  border-radius: var(--radius-md);\n  font-size: var(--body-sm-size);\n  color: var(--color-success);\n  display: flex;\n  align-items: center;\n  gap: var(--space-3);\n  transition: all var(--duration-enter) var(--ease-enter);\n}\n\n.ren-form-success[hidden] {\n  display: none;\n}\n\n/* ─── Form Sections ─── */\n.ren-form-section {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-4);\n}\n\n.ren-form-section-title {\n  font-size: var(--title-sm-size);\n  font-weight: var(--title-sm-weight);\n  color: var(--color-text);\n  margin: 0;\n  padding: 0;\n  line-height: var(--title-sm-leading);\n}\n\n/* ─── Form Row (Horizontal Layout) ─── */\n.ren-form-row {\n  display: flex;\n  flex-wrap: wrap;\n  gap: var(--space-4);\n}\n\n.ren-form-row > .ren-field {\n  flex: 1;\n  min-width: 250px;\n}\n\n/* Stack on narrow containers */\n@container ren-form (max-width: 480px) {\n  .ren-form-row {\n    flex-direction: column;\n    gap: var(--space-4);\n  }\n\n  .ren-form-row > .ren-field {\n    min-width: 100%;\n    flex: none;\n  }\n}\n\n/* ─── Form Actions ─── */\n.ren-form-actions {\n  display: flex;\n  align-items: flex-end;\n  justify-content: flex-end;\n  gap: var(--space-3);\n  margin-top: var(--space-4);\n  flex-wrap: wrap;\n}\n\n.ren-form-actions button {\n  white-space: nowrap;\n}\n\n@container ren-form (max-width: 480px) {\n  .ren-form-actions {\n    flex-direction: column-reverse;\n  }\n\n  .ren-form-actions button {\n    width: 100%;\n  }\n}\n\n/* ─── Multi-Step Progress Indicator ─── */\n.ren-form-progress {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--space-4);\n  margin-bottom: var(--space-6);\n}\n\n.ren-form-step {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n}\n\n/* Step circle */\n.ren-form-step::before {\n  content: attr(data-step);\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem;\n  height: 2.5rem;\n  min-width: 2.5rem;\n  border-radius: 50%;\n  background-color: var(--color-border);\n  color: var(--color-text-muted);\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  transition: all var(--duration-state) var(--ease-enter);\n  border: 2px solid transparent;\n}\n\n/* Step label */\n.ren-form-step-label {\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  color: var(--color-text-muted);\n  white-space: nowrap;\n  transition: all var(--duration-state) var(--ease-enter);\n}\n\n/* Divider between steps */\n.ren-form-step:not(:last-child)::after {\n  content: '';\n  flex: 1;\n  height: 2px;\n  background-color: var(--color-border);\n  min-width: 1rem;\n  transition: background-color var(--duration-state) var(--ease-enter);\n}\n\n/* Active step */\n.ren-form-step[data-active] {\n  /* Step circle */\n}\n\n.ren-form-step[data-active]::before {\n  background-color: var(--color-accent);\n  color: var(--color-on-accent);\n  border-color: var(--color-accent);\n  box-shadow: 0 0 0 3px var(--color-accent-subtle);\n}\n\n.ren-form-step[data-active] .ren-form-step-label {\n  color: var(--color-accent);\n  font-weight: var(--weight-semibold);\n}\n\n/* Completed step */\n.ren-form-step[data-completed] {\n  /* Step circle */\n}\n\n.ren-form-step[data-completed]::before {\n  background-color: var(--color-success);\n  color: var(--color-on-success);\n  border-color: var(--color-success);\n  content: '✓';\n  font-size: 1.25rem;\n  font-weight: var(--weight-bold);\n}\n\n.ren-form-step[data-completed] .ren-form-step-label {\n  color: var(--color-text-muted);\n}\n\n/* Divider after completed step is colored */\n.ren-form-step[data-completed]:not(:last-child)::after {\n  background-color: var(--color-success);\n}\n\n/* ─── Disabled step ─── */\n.ren-form-step[data-disabled] {\n  pointer-events: none;\n}\n\n.ren-form-step[data-disabled]::before {\n  background-color: var(--color-fill);\n  color: var(--color-text-faint);\n  opacity: 0.5;\n}\n\n.ren-form-step[data-disabled] .ren-form-step-label {\n  color: var(--color-text-faint);\n  opacity: 0.5;\n}\n\n/* Stack steps vertically on narrow containers */\n@container ren-form (max-width: 360px) {\n  .ren-form-progress {\n    flex-direction: column;\n    gap: var(--space-3);\n  }\n\n  .ren-form-step:not(:last-child)::after {\n    content: '';\n    width: 2px;\n    height: 1.5rem;\n    background-color: var(--color-border);\n    min-width: 0;\n  }\n}\n\n/* ─── Smooth focus management ─── */\n.ren-form:has(.ren-field[data-invalid]) {\n  scroll-behavior: smooth;\n}\n\n/* ─── Animation for error entry ─── */\n@keyframes slideInError {\n  from {\n    opacity: 0;\n    transform: translateY(-0.5rem);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n.ren-form-error-summary[data-has-errors] {\n  animation: slideInError var(--duration-enter) var(--ease-enter);\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-form-error-summary[data-has-errors],\n  .ren-form-step::before,\n  .ren-form-step-label,\n  .ren-form-step:not(:last-child)::after {\n    animation: none;\n    transition: none;\n  }\n}\n/* Appearance API bridge delegates field chrome to the primitive family. */\n.ren-form input, .ren-form select, .ren-form textarea { min-block-size: var(--ren-field-height, var(--size-lg)); padding-inline: var(--ren-field-padding-x, var(--space-3)); padding-block: var(--ren-field-padding-y, var(--space-2)); font-size: var(--ren-field-font-size, var(--body-size)); border-radius: var(--ren-field-radius, var(--radius-md)); border: var(--ren-field-border-width, var(--stroke-1)) solid var(--ren-field-border-color, var(--color-border-interactive)); background: var(--ren-field-bg, var(--color-input-bg)); color: var(--ren-field-color, var(--color-text)); }\n.ren-form input::placeholder, .ren-form textarea::placeholder { color: var(--ren-field-placeholder, var(--color-input-placeholder)); }\n.ren-form input:focus, .ren-form select:focus, .ren-form textarea:focus { border-color: var(--ren-field-focus-color, var(--color-input-border-focus)); }\n.ren-form [aria-invalid=\"true\"] { border-color: var(--ren-field-error-color, var(--color-input-error-border)); }\n.ren-form [aria-invalid=\"false\"] { border-color: var(--ren-field-success-color, var(--color-success)); }\n",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "file:components/patterns/ren-form/ren-form.css",
      "type": "css",
      "name": "ren-form.css"
    },
    {
      "data": {},
      "body": "import { serializeFormEntries } from './serialize.js';\n\n/**\n * RenDS — <ren-form> Form Validation System\n * ==========================================\n * Multi-step form with field validation, error summaries,\n * and accessibility features. Supports custom validators,\n * multiple validation modes, and server-side errors.\n *\n * Light DOM — no Shadow DOM.\n *\n * Usage:\n *   <ren-form data-validate=\"onTouched\" data-steps=\"3\">\n *     <form class=\"ren-form\">\n *       <div class=\"ren-form-error-summary\" hidden>\n *         <strong>Please fix the following errors:</strong>\n *         <ul></ul>\n *       </div>\n *\n *       <div class=\"ren-form-progress\">\n *         <div class=\"ren-form-step\" data-step=\"1\" data-active>\n *           <span class=\"ren-form-step-label\">Account</span>\n *         </div>\n *         <!-- ... -->\n *       </div>\n *\n *       <div class=\"ren-form-section\">\n *         <div class=\"ren-field\" data-rules=\"required|email\">\n *           <label class=\"ren-field-label\" data-required>Email</label>\n *           <input class=\"ren-input\" type=\"email\" name=\"email\" required>\n *           <span class=\"ren-field-error\" hidden></span>\n *         </div>\n *       </div>\n *\n *       <div class=\"ren-form-actions\">\n *         <button type=\"button\">Previous</button>\n *         <button type=\"submit\">Submit</button>\n *       </div>\n *     </form>\n *   </ren-form>\n */\n\nconst DEFAULT_VALIDATION_MODE = 'onSubmit';\nconst DEBOUNCE_DELAY = 300;\nconst DEFAULT_MESSAGES = { required: 'This field is required', email: 'Please enter a valid email address', pattern: 'Invalid format', invalid: 'Please correct this field', min: (n) => `Must be at least ${n} characters`, max: (n) => `Must be no more than ${n} characters` };\n\n\n/** Built-in validators */\nconst builtInValidators = {\n  required: (value) => {\n    return value && value.trim().length > 0\n      ? null\n      : 'This field is required';\n  },\n\n  email: (value) => {\n    if (!value) return null;\n    const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n    return emailRegex.test(value) ? null : 'Please enter a valid email address';\n  },\n\n  min: (min) => (value) => {\n    if (!value) return null;\n    return value.length >= parseInt(min, 10)\n      ? null\n      : `Must be at least ${min} characters`;\n  },\n\n  max: (max) => (value) => {\n    if (!value) return null;\n    return value.length <= parseInt(max, 10)\n      ? null\n      : `Must be no more than ${max} characters`;\n  },\n\n  pattern: (pattern) => (value) => {\n    if (!value) return null;\n    try {\n      const regex = new RegExp(pattern);\n      return regex.test(value) ? null : 'Invalid format';\n    } catch {\n      return 'Invalid regex pattern';\n    }\n  },\n\n  match: (fieldName) => (value, form) => {\n    const targetField = Array.from(form?.elements || []).find(\n      (element) => element.name === fieldName\n    );\n    if (!targetField) return 'Target field not found';\n    return value === targetField.value ? null : 'Fields do not match';\n  },\n};\n\nexport class RenForm extends HTMLElement {\n  static #customValidators = new Map();\n  static #messages = new Map();\n\n  constructor() {\n    super();\n    this._form = null;\n    this._fields = [];\n    this._errors = new Map();\n    this._touched = new Set();\n    this._validationMode = DEFAULT_VALIDATION_MODE;\n    this._debounceTimers = new Map();\n    this._currentStep = 1;\n    this._totalSteps = 0;\n    this._isSubmitting = false;\n    this._errorSummary = null;\n    this._successMessage = null;\n    this._listenerController = null;\n    this._persistKey = null;\n    this._submitButtonStates = new Map();\n  }\n\n  static registerValidator(name, fn) {\n    RenForm.#customValidators.set(name, fn);\n  }\n  static registerMessages(locale, messages) { RenForm.#messages.set(locale, { ...messages }); }\n\n  connectedCallback() {\n    this._form = this.querySelector('form.ren-form');\n    if (!this._form) return;\n    if (!this.hasAttribute('dir')) {\n      const locale = this.getAttribute('lang') || document.documentElement.lang || '';\n      if (/^(ar|fa|he|ur)(-|$)/i.test(locale)) this.setAttribute('dir', 'rtl');\n    }\n\n    this._initElements();\n    this._attachEventListeners();\n    this._initMultiStep();\n    this._restorePersisted();\n  }\n\n  disconnectedCallback() {\n    this._removeEventListeners();\n    this._clearDebounceTimers();\n  }\n\n  _initElements() {\n    this._validationMode = this.getAttribute('data-validate') ?? DEFAULT_VALIDATION_MODE;\n    this._fields = Array.from(this._form.querySelectorAll('ren-field, .ren-field'))\n      .filter((field, index, fields) => fields.indexOf(field) === index);\n    this._errorSummary = this._form.querySelector('.ren-form-error-summary');\n    this._successMessage = this._form.querySelector('.ren-form-success');\n\n    // The error summary must be focusable so .focus() actually moves caret /\n    // SR position when validation fails. A plain <div role=\"alert\"> is not\n    // focusable by default; set tabindex=\"-1\" if the author did not.\n    if (this._errorSummary && !this._errorSummary.hasAttribute('tabindex')) {\n      this._errorSummary.setAttribute('tabindex', '-1');\n    }\n  }\n\n  _initMultiStep() {\n    const stepsAttr = this.getAttribute('data-steps');\n    if (!stepsAttr) return;\n\n    this._totalSteps = parseInt(stepsAttr, 10);\n    this._currentStep = 1;\n    this._updateProgressIndicators();\n  }\n\n  _attachEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = new AbortController();\n    const { signal } = this._listenerController;\n\n    // Form submit\n    this._form.addEventListener('submit', (e) => this._handleSubmit(e), { signal });\n\n    // Field validation\n    this._fields.forEach((field) => {\n      const input = field.querySelector('input, select, textarea');\n      if (!input) return;\n      input.addEventListener('input', () => this._persist(), { signal });\n\n      switch (this._validationMode) {\n        case 'onBlur':\n          input.addEventListener('blur', () => this._validateField(field, input), { signal });\n          break;\n\n        case 'onChange':\n          input.addEventListener('input', () => {\n            this._debounceValidation(field, input);\n          }, { signal });\n          break;\n\n        case 'onTouched':\n          input.addEventListener('blur', () => {\n            this._touched.add(input.name);\n            this._validateField(field, input);\n          }, { signal });\n          input.addEventListener('input', () => {\n            if (this._touched.has(input.name)) {\n              this._debounceValidation(field, input);\n            }\n          }, { signal });\n          break;\n\n        case 'onSubmit':\n        default:\n          // Only validate on submit\n          break;\n      }\n    });\n  }\n\n  _debounceValidation(field, input) {\n    // Clear existing timer\n    if (this._debounceTimers.has(input.name)) {\n      clearTimeout(this._debounceTimers.get(input.name));\n    }\n\n    // Set new timer\n    const timer = setTimeout(() => {\n      this._validateField(field, input);\n      this._debounceTimers.delete(input.name);\n    }, DEBOUNCE_DELAY);\n\n    this._debounceTimers.set(input.name, timer);\n  }\n\n  _clearDebounceTimers() {\n    this._debounceTimers.forEach((timer) => clearTimeout(timer));\n    this._debounceTimers.clear();\n  }\n\n  _removeEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = null;\n  }\n\n  async _handleSubmit(e) {\n    e.preventDefault();\n    if (this._isSubmitting) return;\n\n    this._isSubmitting = true;\n\n    let result;\n    try {\n      result = await this.validateAsync();\n    } catch (error) {\n      this._isSubmitting = false;\n      this._setSubmittingState(false);\n      this.dispatchEvent(new CustomEvent('ren-submit-error', {\n        detail: { error },\n        bubbles: true,\n        composed: true,\n      }));\n      return;\n    }\n\n    if (!result.valid) {\n      this._isSubmitting = false;\n      this.dispatchEvent(\n        new CustomEvent('ren-invalid', {\n          detail: { errors: result.errors },\n          bubbles: true,\n          composed: true,\n        })\n      );\n      this._showErrorSummary(result.errors);\n      this._scrollToFirstError();\n      return;\n    }\n\n    // Success\n    this._hideErrorSummary();\n    this._setSubmittingState(true);\n\n    const values = this.getValues();\n    const completions = [];\n    const event = new CustomEvent('ren-submit', {\n      detail: { values, form: this._form, waitUntil: (promise) => {\n        if (promise && typeof promise.then === 'function') completions.push(promise);\n      } },\n      bubbles: true,\n      composed: true,\n      cancelable: true,\n    });\n\n    this.dispatchEvent(event);\n\n    try { await Promise.all(completions); }\n    catch (error) {\n      this.dispatchEvent(new CustomEvent('ren-submit-error', { detail: { error }, bubbles: true, composed: true }));\n    } finally {\n      this._setSubmittingState(false);\n      this._isSubmitting = false;\n    }\n  }\n\n  _setSubmittingState(isSubmitting) {\n    this.toggleAttribute('data-submitting', isSubmitting);\n    this._form?.toggleAttribute('data-submitting', isSubmitting);\n\n    if (isSubmitting) {\n      this._submitButtonStates.clear();\n      const submitButtons = this._form?.querySelectorAll(\n        'button:not([type]), button[type=\"submit\"], input[type=\"submit\"], input[type=\"image\"]'\n      ) || [];\n      submitButtons.forEach((button) => {\n        this._submitButtonStates.set(button, button.disabled);\n        button.disabled = true;\n      });\n      return;\n    }\n\n    this._submitButtonStates.forEach((wasDisabled, button) => {\n      if (button.isConnected) button.disabled = wasDisabled;\n    });\n    this._submitButtonStates.clear();\n  }\n\n  _validateField(field, input) {\n    const rules = field.getAttribute('data-rules') ?? input.getAttribute('data-rules') ?? '';\n    const error = this._nativeValidationError(input) || this._runValidators(rules, input.value, input.name);\n\n    if (error) {\n      this._setFieldError(field, input, error);\n      this._errors.set(input.name, error);\n    } else {\n      this._clearFieldError(field, input);\n      this._errors.delete(input.name);\n    }\n\n    // Dispatch field validation event\n    this.dispatchEvent(\n      new CustomEvent('ren-field-validated', {\n        detail: {\n          name: input.name,\n          valid: !error,\n          error,\n        },\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  _runValidators(rulesString, value, fieldName) {\n    if (!rulesString) return null;\n\n    const rules = rulesString.split('|').map((r) => r.trim());\n\n    for (const rule of rules) {\n      let validator = null;\n      let param = null;\n\n      if (rule.includes(':')) {\n        [validator, param] = rule.split(':');\n      } else {\n        validator = rule;\n      }\n\n      // Get validator function\n      let validatorFn = builtInValidators[validator];\n      if (!validatorFn && RenForm.#customValidators.has(validator)) {\n        validatorFn = RenForm.#customValidators.get(validator);\n      }\n\n      if (!validatorFn) continue;\n\n      // Run validator\n      let error;\n      if (param) {\n        if (validator === 'match') {\n          error = validatorFn(param)(value, this._form);\n        } else {\n          error = validatorFn(param)(value);\n        }\n      } else {\n        error = validatorFn(value);\n      }\n\n      if (error) return this._message(error, validator, param);\n    }\n\n    return null;\n  }\n\n  async _runValidatorsAsync(rulesString, value) {\n    if (!rulesString) return null;\n    for (const rule of rulesString.split('|').map((r) => r.trim())) {\n      const [name, param] = rule.split(':');\n      const fn = builtInValidators[name] || RenForm.#customValidators.get(name);\n      if (!fn) continue;\n      let result = param ? fn(param)(value, this._form) : fn(value, this._form);\n      if (result && typeof result.then === 'function') result = await result;\n      if (result) return this._message(result, name, param);\n    }\n    return null;\n  }\n\n  _message(message, rule, param) {\n    const locale = this.getAttribute('lang') || document.documentElement.lang || 'en';\n    const custom = RenForm.#messages.get(locale) || RenForm.#messages.get(locale.split('-')[0]);\n    const value = custom?.[rule] ?? DEFAULT_MESSAGES[rule];\n    return typeof value === 'function' ? value(param) : (value || message);\n  }\n\n  _setFieldError(field, input, message) {\n    field.setAttribute('data-invalid', '');\n    input.setAttribute('aria-invalid', 'true');\n\n    const errorEl = field.querySelector('.ren-field-error');\n    if (errorEl) {\n      errorEl.textContent = message;\n      errorEl.hidden = false;\n      if (!errorEl.id) {\n        errorEl.id = `error-${input.name}`;\n      }\n      input.setAttribute('aria-errormessage', errorEl.id);\n    }\n  }\n\n  _clearFieldError(field, input) {\n    field.removeAttribute('data-invalid');\n    input.removeAttribute('aria-invalid');\n\n    const errorEl = field.querySelector('.ren-field-error');\n    if (errorEl) {\n      errorEl.hidden = true;\n      errorEl.textContent = '';\n    }\n  }\n\n  _showErrorSummary(errors) {\n    if (!this._errorSummary) return;\n\n    const ul = this._errorSummary.querySelector('ul');\n    if (!ul) return;\n\n    ul.replaceChildren();\n    errors.forEach((error) => {\n      const li = document.createElement('li');\n      const link = document.createElement('a');\n      const field = this._findFieldInput(error.name);\n\n      link.href = field?.id ? `#${field.id}` : '#';\n      link.textContent = `${error.name}: ${error.message}`;\n      link.addEventListener('click', (e) => {\n        e.preventDefault();\n        if (field) field.focus();\n      });\n\n      li.appendChild(link);\n      ul.appendChild(li);\n    });\n\n    this._errorSummary.setAttribute('data-has-errors', '');\n    this._errorSummary.removeAttribute('hidden');\n    this._errorSummary.focus();\n  }\n\n  _findFieldInput(name) {\n    return Array.from(this._form?.elements || []).find(\n      (element) => element.name === name\n    );\n  }\n\n  _hideErrorSummary() {\n    if (!this._errorSummary) return;\n    this._errorSummary.removeAttribute('data-has-errors');\n    this._errorSummary.setAttribute('hidden', '');\n  }\n\n  _scrollToFirstError() {\n    const firstInvalid = this._form.querySelector('[data-invalid]');\n    if (firstInvalid) {\n      firstInvalid.scrollIntoView?.({ behavior: 'smooth', block: 'center' });\n    }\n  }\n\n  _updateProgressIndicators() {\n    const steps = this.querySelectorAll('.ren-form-step');\n    steps.forEach((step, idx) => {\n      const stepNum = idx + 1;\n      step.setAttribute('data-step', stepNum);\n\n      if (stepNum < this._currentStep) {\n        step.setAttribute('data-completed', '');\n        step.removeAttribute('data-active');\n        step.removeAttribute('data-disabled');\n      } else if (stepNum === this._currentStep) {\n        step.removeAttribute('data-completed');\n        step.setAttribute('data-active', '');\n        step.removeAttribute('data-disabled');\n      } else {\n        step.removeAttribute('data-completed');\n        step.removeAttribute('data-active');\n        step.setAttribute('data-disabled', '');\n      }\n    });\n    this.querySelectorAll('.ren-form-section[data-step], [data-ren-step-panel]').forEach((panel) => {\n      const step = Number(panel.getAttribute('data-step') || panel.getAttribute('data-ren-step-panel'));\n      panel.hidden = step !== this._currentStep;\n      panel.toggleAttribute('data-active', step === this._currentStep);\n    });\n  }\n\n  // ─── Public API ───\n\n  validate() {\n    const errors = [];\n\n    this._fields.forEach((field) => {\n      const input = field.querySelector('input, select, textarea');\n      if (!input) return;\n\n      const rules = field.getAttribute('data-rules') ?? input.getAttribute('data-rules') ?? '';\n      const error = this._nativeValidationError(input) || this._runValidators(rules, input.value, input.name);\n\n      if (error) {\n        this._setFieldError(field, input, error);\n        errors.push({ name: input.name, message: error });\n      } else {\n        this._clearFieldError(field, input);\n      }\n    });\n\n    return {\n      valid: errors.length === 0,\n      errors,\n    };\n  }\n\n  async validateAsync() {\n    const errors = [];\n    for (const field of this._fields) {\n      const input = field.querySelector('input, select, textarea');\n      if (!input) continue;\n      const rules = field.getAttribute('data-rules') ?? input.getAttribute('data-rules') ?? '';\n      const error = this._nativeValidationError(input) || await this._runValidatorsAsync(rules, input.value);\n      if (error) { this._setFieldError(field, input, error); this._errors.set(input.name, error); errors.push({ name: input.name, message: error }); }\n      else this._clearFieldError(field, input);\n    }\n    return { valid: errors.length === 0, errors };\n  }\n\n  reset() {\n    this._form.reset();\n    this._setSubmittingState(false);\n    this._isSubmitting = false;\n    this._errors.clear();\n    this._touched.clear();\n    this._debounceTimers.forEach((timer) => clearTimeout(timer));\n    this._debounceTimers.clear();\n\n    this._fields.forEach((field) => {\n      const input = field.querySelector('input, select, textarea');\n      if (input) {\n        this._clearFieldError(field, input);\n      }\n    });\n\n    this._hideErrorSummary();\n    this._updateProgressIndicators();\n    this._clearPersisted();\n  }\n\n  _restorePersisted() {\n    this._persistKey = this.getAttribute('data-persist');\n    if (!this._persistKey) return;\n    try {\n      const values = JSON.parse(localStorage.getItem(this._persistKey) || '{}');\n      this._form.querySelectorAll('[name]').forEach((input) => {\n        const value = values[input.name]; if (value == null) return;\n        if (input.type === 'checkbox' || input.type === 'radio') input.checked = Array.isArray(value) ? value.includes(input.value) : Boolean(value);\n        else input.value = Array.isArray(value) ? value[0] : value;\n      });\n    } catch { /* storage is optional */ }\n  }\n\n  _persist() {\n    if (!this._persistKey) return;\n    try { localStorage.setItem(this._persistKey, JSON.stringify(this.getValues())); } catch { /* private mode/quota */ }\n  }\n\n  _clearPersisted() { if (this._persistKey) { try { localStorage.removeItem(this._persistKey); } catch { /* optional */ } } }\n\n  getValues() {\n    const formData = new FormData(this._form);\n    return serializeFormEntries(formData.entries());\n  }\n\n  setErrors(errors) {\n    this._errors.clear();\n\n    errors.forEach((error) => {\n      const field = this._form.querySelector(\n        `.ren-field:has([name=\"${error.name}\"])`\n      );\n      const input = field?.querySelector('input, select, textarea');\n\n      if (field && input) {\n        this._setFieldError(field, input, error.message);\n        this._errors.set(error.name, error.message);\n      }\n    });\n\n    this._showErrorSummary(errors);\n  }\n\n  setFieldError(name, message) {\n    const field = this._form.querySelector(\n      `.ren-field:has([name=\"${name}\"])`\n    );\n    const input = field?.querySelector('input, select, textarea');\n\n    if (field && input) {\n      this._setFieldError(field, input, message);\n      this._errors.set(name, message);\n    }\n  }\n\n  clearFieldError(name) {\n    const field = this._form.querySelector(\n      `.ren-field:has([name=\"${name}\"])`\n    );\n    const input = field?.querySelector('input, select, textarea');\n\n    if (field && input) {\n      this._clearFieldError(field, input);\n      this._errors.delete(name);\n    }\n  }\n\n  // ─── Multi-Step API ───\n\n  nextStep() {\n    if (this._currentStep >= this._totalSteps) return false;\n\n    // Validate current step fields\n    const currentStepFields = this._getStepFields(this._currentStep);\n    const errors = [];\n\n    currentStepFields.forEach((field) => {\n      const input = field.querySelector('input, select, textarea');\n      if (!input) return;\n\n      const rules = field.getAttribute('data-rules') ?? input.getAttribute('data-rules') ?? '';\n      const error = this._nativeValidationError(input) || this._runValidators(rules, input.value, input.name);\n\n      if (error) {\n        this._setFieldError(field, input, error);\n        errors.push({ name: input.name, message: error });\n      } else {\n        this._clearFieldError(field, input);\n      }\n    });\n\n    if (errors.length > 0) {\n      return false;\n    }\n\n    this._currentStep++;\n    this._updateProgressIndicators();\n    this._hideErrorSummary();\n\n    this.dispatchEvent(\n      new CustomEvent('ren-step-change', {\n        detail: { step: this._currentStep, totalSteps: this._totalSteps },\n        bubbles: true,\n        composed: true,\n      })\n    );\n\n    return true;\n  }\n\n  prevStep() {\n    if (this._currentStep <= 1) return false;\n\n    this._currentStep--;\n    this._updateProgressIndicators();\n    this._hideErrorSummary();\n\n    this.dispatchEvent(\n      new CustomEvent('ren-step-change', {\n        detail: { step: this._currentStep, totalSteps: this._totalSteps },\n        bubbles: true,\n        composed: true,\n      })\n    );\n\n    return true;\n  }\n\n  goToStep(stepNum) {\n    if (stepNum < 1 || stepNum > this._totalSteps) return false;\n\n    this._currentStep = stepNum;\n    this._updateProgressIndicators();\n    this._hideErrorSummary();\n\n    this.dispatchEvent(\n      new CustomEvent('ren-step-change', {\n        detail: { step: this._currentStep, totalSteps: this._totalSteps },\n        bubbles: true,\n        composed: true,\n      })\n    );\n\n    return true;\n  }\n\n  get currentStep() {\n    return this._currentStep;\n  }\n\n  get totalSteps() {\n    return this._totalSteps;\n  }\n\n  _getStepFields(stepNum) {\n    const panel = this.querySelector(`.ren-form-section[data-step=\"${stepNum}\"], [data-ren-step-panel=\"${stepNum}\"]`);\n    if (panel) return this._fields.filter((field) => panel.contains(field));\n    return this._fields.filter((field) => {\n      const owner = field.closest('[data-step]');\n      return !owner || owner.getAttribute('data-step') === String(stepNum);\n    });\n  }\n\n  _nativeValidationError(input) {\n    if (input.validity?.valid) return null;\n    if (input.validity?.valueMissing) return this._message('', 'required');\n    if (input.validity?.typeMismatch) return this._message('', 'email');\n    if (input.validity?.patternMismatch) return this._message('', 'pattern');\n    if (input.validity?.tooShort) return this._message('', 'min', input.minLength);\n    if (input.validity?.tooLong) return this._message('', 'max', input.maxLength);\n    if (input.validity?.rangeUnderflow) return this._message(`Value must be at least ${input.min}`, 'invalid');\n    if (input.validity?.rangeOverflow) return this._message(`Value must be no more than ${input.max}`, 'invalid');\n    return this._message('', 'invalid');\n  }\n}\n\nif (!customElements.get('ren-form')) {\n  customElements.define('ren-form', RenForm);\n}\n",
      "path": "components/patterns/ren-form/ren-form.js",
      "id": "file:components/patterns/ren-form/ren-form.js",
      "type": "javascript",
      "name": "ren-form.js"
    },
    {
      "data": {},
      "body": "/**\n * RenDS Menubar\n * Horizontal menu bar component (File, Edit, View, etc.)\n *\n * Implements WAI-ARIA Menubar pattern with keyboard navigation,\n * roving focus, submenu support, and checkbox/radio items.\n *\n * Classes:\n * - .ren-menubar: Root horizontal menu bar\n * - .ren-menubar-trigger: Top-level menu button\n * - .ren-menubar-menu: Dropdown menu panel\n * - .ren-menubar-item: Individual menu item\n * - .ren-menubar-separator: Divider between item groups\n * - .ren-menubar-shortcut: Keyboard shortcut indicator\n * - .ren-menubar-label: Non-interactive group label\n * - .ren-menubar-submenu: Submenu with chevron\n * - .ren-menubar-checkbox: Checkbox menu item\n * - .ren-menubar-radio: Radio menu item\n */\n\n/* =================================================================\n   ROOT MENUBAR\n   ================================================================= */\n\n.ren-menubar {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  padding: var(--space-2) var(--space-3);\n  background-color: var(--color-surface);\n  border-bottom: 1px solid var(--color-border);\n  position: relative;\n}\n\n/* =================================================================\n   MENUBAR TRIGGER (Top-level Menu Button)\n   ================================================================= */\n\n.ren-menubar-trigger {\n  position: relative;\n  padding: var(--space-1) var(--space-2);\n  background-color: transparent;\n  color: var(--color-text);\n  border: none;\n  border-radius: var(--radius-sm);\n  font-size: var(--body-size);\n  font-weight: var(--label-weight);\n  cursor: pointer;\n  transition: background-color var(--duration-tactile) var(--ease-enter);\n  white-space: nowrap;\n}\n\n.ren-menubar-trigger:hover:not([aria-expanded=\"true\"]) {\n  background-color: var(--color-fill-hover);\n}\n\n.ren-menubar-trigger[aria-expanded=\"true\"] {\n  background-color: var(--color-fill-active);\n  color: var(--color-text);\n}\n\n.ren-menubar-trigger:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: 2px;\n}\n\n.ren-menubar-trigger:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n\n/* =================================================================\n   MENUBAR MENU (Dropdown Panel)\n   ================================================================= */\n\n.ren-menubar-menu {\n  position: absolute;\n  top: 100%;\n  inset-inline-start: 0;\n  margin-top: var(--space-1);\n  min-width: 200px;\n  background-color: var(--color-surface-raised);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n  box-shadow: var(--shadow-lg);\n  z-index: 1000;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n}\n\n/* Visible state for menu open */\n.ren-menubar-menu:not([hidden]) {\n  display: flex;\n  animation: menubar-menu-open var(--duration-exit) var(--ease-enter);\n}\n\n/* Hidden state for menu closed */\n.ren-menubar-menu[hidden] {\n  display: none;\n  animation: menubar-menu-close var(--duration-exit) var(--ease-enter);\n}\n\n@starting-style {\n  .ren-menubar-menu:not([hidden]) {\n    opacity: 0;\n    transform: translateY(-4px);\n  }\n}\n\n@keyframes menubar-menu-open {\n  from {\n    opacity: 0;\n    transform: translateY(-4px);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n@keyframes menubar-menu-close {\n  from {\n    opacity: 1;\n    transform: translateY(0);\n  }\n  to {\n    opacity: 0;\n    transform: translateY(-4px);\n  }\n}\n\n/* =================================================================\n   MENUBAR ITEM (Menu Item)\n   ================================================================= */\n\n.ren-menubar-item {\n  display: flex;\n  align-items: center;\n  width: 100%;\n  padding: var(--space-2) var(--space-3);\n  background-color: transparent;\n  color: var(--color-text);\n  border: none;\n  font-size: var(--body-size);\n  font-weight: normal;\n  text-align: start;\n  cursor: pointer;\n  transition: background-color var(--duration-tactile) var(--ease-enter);\n  position: relative;\n  gap: var(--space-2);\n}\n\n.ren-menubar-item:hover:not([data-disabled]) {\n  background-color: var(--color-fill-hover);\n}\n\n.ren-menubar-item:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: -2px;\n}\n\n.ren-menubar-item[data-disabled] {\n  color: var(--color-text-muted);\n  cursor: not-allowed;\n}\n\n.ren-menubar-item[data-disabled]:hover {\n  background-color: transparent;\n}\n\n/* =================================================================\n   MENUBAR SHORTCUT (Keyboard Shortcut Indicator)\n   ================================================================= */\n\n.ren-menubar-shortcut {\n  display: inline-block;\n  margin-inline-start: auto;\n  padding-inline-start: var(--space-3);\n  color: var(--color-text-muted);\n  font-size: var(--caption-size);\n  font-family: monospace;\n  flex-shrink: 0;\n}\n\n/* =================================================================\n   MENUBAR SEPARATOR (Divider)\n   ================================================================= */\n\n.ren-menubar-separator {\n  height: 1px;\n  background-color: var(--color-separator);\n  margin: var(--space-1) 0;\n}\n\n/* =================================================================\n   MENUBAR LABEL (Group Label)\n   ================================================================= */\n\n.ren-menubar-label {\n  padding: var(--space-2) var(--space-3);\n  color: var(--color-text-muted);\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  user-select: none;\n  pointer-events: none;\n}\n\n/* =================================================================\n   MENUBAR SUBMENU (With Chevron)\n   ================================================================= */\n\n.ren-menubar-submenu {\n  position: relative;\n}\n\n/* Chevron indicator via ::after */\n.ren-menubar-submenu::after {\n  content: '›';\n  display: inline-block;\n  margin-inline-start: auto;\n  padding-inline-start: var(--space-3);\n  color: var(--color-text-muted);\n  font-size: var(--body-size);\n  flex-shrink: 0;\n}\n\n/* Submenu panel positioning (higher z-index for layering) */\n.ren-menubar-submenu > .ren-menubar-menu {\n  top: 0;\n  inset-inline-start: calc(100% + var(--space-1));\n  margin-top: 0;\n  z-index: 1100;\n}\n\n/* =================================================================\n   MENUBAR CHECKBOX (Checkbox Menu Item)\n   ================================================================= */\n\n.ren-menubar-checkbox {\n  position: relative;\n}\n\n/* Checkmark via ::before */\n.ren-menubar-checkbox::before {\n  content: '';\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 1em;\n  height: 1em;\n  margin-inline-end: var(--space-1);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-sm);\n  background-color: transparent;\n  flex-shrink: 0;\n}\n\n.ren-menubar-checkbox[aria-checked=\"true\"]::before {\n  content: '✓';\n  background-color: var(--color-accent);\n  border-color: var(--color-accent);\n  color: var(--color-on-accent);\n  font-size: 0.75em;\n  font-weight: bold;\n}\n\n/* =================================================================\n   MENUBAR RADIO (Radio Menu Item)\n   ================================================================= */\n\n.ren-menubar-radio {\n  position: relative;\n}\n\n/* Radio indicator via ::before */\n.ren-menubar-radio::before {\n  content: '';\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 1em;\n  height: 1em;\n  margin-inline-end: var(--space-1);\n  border: 1px solid var(--color-border);\n  border-radius: 50%;\n  background-color: transparent;\n  flex-shrink: 0;\n}\n\n.ren-menubar-radio[aria-checked=\"true\"]::before {\n  content: '';\n  background-color: var(--color-accent);\n  border-color: var(--color-accent);\n  box-shadow: inset 0 0 0 2px var(--color-surface-raised);\n}\n\n/* =================================================================\n   ACCESSIBILITY & INTERACTION STATES\n   ================================================================= */\n\n/* Hover fill for interactive items */\n.ren-menubar-item:not([data-disabled]):not(.ren-menubar-label) {\n  user-select: none;\n}\n\n/* Focus management for submenus */\n.ren-menubar-menu [role=\"menuitem\"]:focus-visible,\n.ren-menubar-menu [role=\"menuitemcheckbox\"]:focus-visible,\n.ren-menubar-menu [role=\"menuitemradio\"]:focus-visible {\n  outline: 2px solid var(--color-accent);\n  outline-offset: -2px;\n}\n\n/* Ensure disabled items don't respond to pointer events */\n.ren-menubar-item[data-disabled] {\n  pointer-events: none;\n}\n\n/* Active/selected state */\n.ren-menubar-item.is-focused {\n  background-color: var(--color-fill-hover);\n}\n\n/* =================================================================\n   MEDIA QUERIES & RESPONSIVE\n   ================================================================= */\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-menubar-trigger,\n  .ren-menubar-item,\n  .ren-menubar-menu {\n    transition: none;\n    animation: none;\n  }\n}\n",
      "path": "components/patterns/ren-menubar/ren-menubar.css",
      "id": "file:components/patterns/ren-menubar/ren-menubar.css",
      "type": "css",
      "name": "ren-menubar.css"
    },
    {
      "data": {},
      "body": "/**\n * RenDS Menubar Component\n * Horizontal menu bar with WAI-ARIA Menubar pattern support\n *\n * Features:\n * - Keyboard navigation (Arrow keys, Enter, Space, Escape, Home, End)\n * - Roving focus with \"menubar glide\" (hover opens adjacent menus when one is open)\n * - Typeahead character matching for quick item selection\n * - Checkbox and radio menu items with state management\n * - Submenu support with nested navigation\n * - Full accessibility compliance with ARIA roles and attributes\n *\n * Usage:\n * <ren-menubar>\n *   <div class=\"ren-menubar\" role=\"menubar\">\n *     <button class=\"ren-menubar-trigger\">File</button>\n *     <div class=\"ren-menubar-menu\" role=\"menu\">\n *       <button class=\"ren-menubar-item\" role=\"menuitem\">New</button>\n *     </div>\n *   </div>\n * </ren-menubar>\n *\n * Events:\n * - ren-menubar-select: Fired when a menu item is activated\n *   detail: { item: Element, value: string, checked?: boolean }\n *\n * Public Methods:\n * - closeAll(): Close all open menus\n * - openMenu(triggerIndex): Open menu at specified trigger index\n */\n\nexport class RenMenubar extends HTMLElement {\n  constructor() {\n    super();\n    this.triggers = [];\n    this.menus = [];\n    this.activeMenuIndex = -1;\n    this.currentMenu = null;\n    this.focusedItem = null;\n    this.typeaheadBuffer = '';\n    this.typeaheadTimeout = null;\n  }\n\n  /* ===================================================================\n     LIFECYCLE HOOKS\n     =================================================================== */\n\n  connectedCallback() {\n    this.initialize();\n  }\n\n  /* ===================================================================\n     INITIALIZATION\n     =================================================================== */\n\n  /**\n   * Initialize the menubar:\n   * - Cache triggers and menus\n   * - Set up ARIA attributes\n   * - Attach event listeners\n   */\n  initialize() {\n    const menubarEl = this.querySelector('[role=\"menubar\"]');\n    if (!menubarEl) return;\n\n    // Find all triggers and their associated menus\n    this.triggers = Array.from(menubarEl.querySelectorAll('.ren-menubar-trigger'));\n    this.menus = this.triggers.map((trigger) => {\n      // Menu is the next sibling after the trigger\n      const menu = trigger.nextElementSibling;\n      if (menu && menu.classList.contains('ren-menubar-menu')) {\n        return menu;\n      }\n      return null;\n    }).filter(Boolean);\n\n    // Ensure triggers and menus have proper ARIA attributes\n    this.triggers.forEach((trigger, index) => {\n      trigger.setAttribute('aria-haspopup', 'true');\n      trigger.setAttribute('aria-expanded', 'false');\n      trigger.setAttribute('role', 'button');\n      trigger.addEventListener('click', () => this.toggleMenu(index));\n      trigger.addEventListener('keydown', (e) => this.handleTriggerKeydown(e, index));\n      trigger.addEventListener('mouseenter', () => this.handleTriggerMouseenter(index));\n    });\n\n    // Set up menu items\n    this.menus.forEach((menu, menuIndex) => {\n      if (menu) {\n        const items = this.getMenuItems(menu);\n        items.forEach((item, itemIndex) => {\n          item.addEventListener('click', (e) => this.handleItemClick(e, menuIndex, itemIndex));\n          item.addEventListener('keydown', (e) => this.handleItemKeydown(e, menuIndex, itemIndex));\n          item.addEventListener('mouseenter', () => this.handleItemMouseenter(menuIndex, itemIndex));\n        });\n      }\n    });\n\n    // Close menu when clicking outside\n    document.addEventListener('click', (e) => {\n      if (!this.contains(e.target)) {\n        this.closeAll();\n      }\n    });\n  }\n\n  /* ===================================================================\n     MENU MANAGEMENT\n     =================================================================== */\n\n  /**\n   * Toggle menu visibility for trigger at index\n   */\n  toggleMenu(index) {\n    if (this.activeMenuIndex === index) {\n      this.closeAll();\n    } else {\n      this.openMenu(index);\n    }\n  }\n\n  /**\n   * Open menu at specified trigger index and focus first item\n   */\n  openMenu(index) {\n    if (index < 0 || index >= this.triggers.length) return;\n\n    // Close any currently open menu\n    if (this.activeMenuIndex !== -1) {\n      this.closeMenu(this.activeMenuIndex);\n    }\n\n    this.activeMenuIndex = index;\n    const trigger = this.triggers[index];\n    const menu = this.menus[index];\n\n    if (menu) {\n      menu.removeAttribute('hidden');\n      trigger.setAttribute('aria-expanded', 'true');\n      this.currentMenu = menu;\n\n      // Focus first focusable item in menu\n      const items = this.getMenuItems(menu);\n      if (items.length > 0) {\n        this.focusItem(items[0]);\n      }\n    }\n  }\n\n  /**\n   * Close menu at specified index\n   */\n  closeMenu(index) {\n    if (index < 0 || index >= this.triggers.length) return;\n\n    const trigger = this.triggers[index];\n    const menu = this.menus[index];\n\n    if (menu) {\n      menu.setAttribute('hidden', '');\n      trigger.setAttribute('aria-expanded', 'false');\n    }\n\n    if (this.activeMenuIndex === index) {\n      this.activeMenuIndex = -1;\n      this.currentMenu = null;\n      this.focusedItem = null;\n      trigger.focus();\n    }\n  }\n\n  /**\n   * Close all open menus and return focus to the last active trigger\n   */\n  closeAll() {\n    if (this.activeMenuIndex !== -1) {\n      this.closeMenu(this.activeMenuIndex);\n    }\n  }\n\n  /* ===================================================================\n     ITEM NAVIGATION & FOCUS\n     =================================================================== */\n\n  /**\n   * Get focusable menu items (excluding separators and labels)\n   */\n  getMenuItems(menu) {\n    const items = Array.from(menu.querySelectorAll(\n      '[role=\"menuitem\"], [role=\"menuitemcheckbox\"], [role=\"menuitemradio\"]'\n    ));\n    return items.filter(item => !item.hasAttribute('data-disabled'));\n  }\n\n  /**\n   * Focus a specific menu item and update aria-selected state\n   */\n  focusItem(item) {\n    if (!item) return;\n\n    // Remove focus class from previously focused item\n    if (this.focusedItem && this.focusedItem !== item) {\n      this.focusedItem.classList.remove('is-focused');\n    }\n\n    this.focusedItem = item;\n    item.classList.add('is-focused');\n    item.focus();\n  }\n\n  /**\n   * Get the next focusable item in menu (wraps around)\n   */\n  getNextItem(menu, currentItem, direction = 1) {\n    const items = this.getMenuItems(menu);\n    if (items.length === 0) return null;\n\n    const currentIndex = items.indexOf(currentItem);\n    const nextIndex = (currentIndex + direction + items.length) % items.length;\n    return items[nextIndex];\n  }\n\n  /* ===================================================================\n     EVENT HANDLERS: TRIGGERS\n     =================================================================== */\n\n  /**\n   * Handle keyboard events on trigger buttons\n   */\n  handleTriggerKeydown(event, triggerIndex) {\n    const { key } = event;\n\n    // ArrowRight: move to next trigger\n    if (key === 'ArrowRight') {\n      event.preventDefault();\n      const nextIndex = (triggerIndex + 1) % this.triggers.length;\n      this.openMenu(nextIndex);\n    }\n\n    // ArrowLeft: move to previous trigger\n    if (key === 'ArrowLeft') {\n      event.preventDefault();\n      const prevIndex = (triggerIndex - 1 + this.triggers.length) % this.triggers.length;\n      this.openMenu(prevIndex);\n    }\n\n    // ArrowDown: open menu\n    if (key === 'ArrowDown' || key === ' ' || key === 'Enter') {\n      event.preventDefault();\n      this.openMenu(triggerIndex);\n    }\n\n    // Home: first trigger\n    if (key === 'Home') {\n      event.preventDefault();\n      this.openMenu(0);\n    }\n\n    // End: last trigger\n    if (key === 'End') {\n      event.preventDefault();\n      this.openMenu(this.triggers.length - 1);\n    }\n  }\n\n  /**\n   * Handle mouse enter on trigger (roving focus \"glide\" behavior)\n   * If a menu is already open, open the new menu instead\n   */\n  handleTriggerMouseenter(triggerIndex) {\n    if (this.activeMenuIndex !== -1 && this.activeMenuIndex !== triggerIndex) {\n      this.openMenu(triggerIndex);\n    }\n  }\n\n  /* ===================================================================\n     EVENT HANDLERS: MENU ITEMS\n     =================================================================== */\n\n  /**\n   * Handle click on menu item\n   */\n  handleItemClick(event, menuIndex, itemIndex) {\n    event.preventDefault();\n    const menu = this.menus[menuIndex];\n    const items = this.getMenuItems(menu);\n    const item = items[itemIndex];\n\n    if (!item || item.hasAttribute('data-disabled')) return;\n\n    this.activateItem(item);\n  }\n\n  /**\n   * Handle keyboard events within menu\n   */\n  handleItemKeydown(event, menuIndex, itemIndex) {\n    const { key } = event;\n    const menu = this.menus[menuIndex];\n    const items = this.getMenuItems(menu);\n    const item = items[itemIndex];\n\n    if (!item) return;\n\n    // ArrowDown: next item\n    if (key === 'ArrowDown') {\n      event.preventDefault();\n      const nextItem = this.getNextItem(menu, item, 1);\n      if (nextItem) this.focusItem(nextItem);\n    }\n\n    // ArrowUp: previous item\n    if (key === 'ArrowUp') {\n      event.preventDefault();\n      const prevItem = this.getNextItem(menu, item, -1);\n      if (prevItem) this.focusItem(prevItem);\n    }\n\n    // ArrowRight: open submenu or move to next trigger\n    if (key === 'ArrowRight') {\n      event.preventDefault();\n      const submenu = item.querySelector('.ren-menubar-menu');\n      if (submenu && item.classList.contains('ren-menubar-submenu')) {\n        submenu.removeAttribute('hidden');\n        const subItems = this.getMenuItems(submenu);\n        if (subItems.length > 0) this.focusItem(subItems[0]);\n      } else {\n        // Move to next trigger\n        const nextIndex = (menuIndex + 1) % this.triggers.length;\n        this.openMenu(nextIndex);\n      }\n    }\n\n    // ArrowLeft: close submenu or go back to previous menu\n    if (key === 'ArrowLeft') {\n      event.preventDefault();\n      const parentMenu = item.closest('.ren-menubar-menu');\n      const parentTrigger = parentMenu?.previousElementSibling;\n      if (parentTrigger && parentTrigger.classList.contains('ren-menubar-submenu')) {\n        const submenu = parentTrigger.querySelector('.ren-menubar-menu');\n        if (submenu) submenu.setAttribute('hidden', '');\n        this.focusItem(parentTrigger);\n      } else {\n        const prevIndex = (menuIndex - 1 + this.triggers.length) % this.triggers.length;\n        this.openMenu(prevIndex);\n      }\n    }\n\n    // Enter or Space: activate item\n    if (key === 'Enter' || key === ' ') {\n      event.preventDefault();\n      this.activateItem(item);\n    }\n\n    // Escape: close menu\n    if (key === 'Escape') {\n      event.preventDefault();\n      this.closeMenu(menuIndex);\n    }\n\n    // Home: first item\n    if (key === 'Home') {\n      event.preventDefault();\n      if (items.length > 0) this.focusItem(items[0]);\n    }\n\n    // End: last item\n    if (key === 'End') {\n      event.preventDefault();\n      if (items.length > 0) this.focusItem(items[items.length - 1]);\n    }\n\n    // Typeahead: match item by first letter\n    if (key.length === 1 && /^[a-z0-9]$/i.test(key)) {\n      event.preventDefault();\n      this.handleTypeahead(key, menu);\n    }\n  }\n\n  /**\n   * Handle mouse enter on item (focus and close typeahead)\n   */\n  handleItemMouseenter(menuIndex, itemIndex) {\n    const menu = this.menus[menuIndex];\n    const items = this.getMenuItems(menu);\n    const item = items[itemIndex];\n\n    if (item) {\n      this.focusItem(item);\n    }\n\n    // Clear typeahead buffer on mouse movement\n    this.typeaheadBuffer = '';\n    if (this.typeaheadTimeout) {\n      clearTimeout(this.typeaheadTimeout);\n    }\n  }\n\n  /* ===================================================================\n     ITEM ACTIVATION & STATE MANAGEMENT\n     =================================================================== */\n\n  /**\n   * Activate a menu item (handle checkbox/radio states and emit event)\n   */\n  activateItem(item) {\n    if (item.hasAttribute('data-disabled')) return;\n\n    const role = item.getAttribute('role');\n\n    // Handle checkbox items\n    if (role === 'menuitemcheckbox') {\n      const isChecked = item.getAttribute('aria-checked') === 'true';\n      item.setAttribute('aria-checked', !isChecked ? 'true' : 'false');\n      this.emitSelectEvent(item, !isChecked);\n    }\n    // Handle radio items\n    else if (role === 'menuitemradio') {\n      const name = item.getAttribute('name');\n      const menu = item.closest('[role=\"menu\"]');\n      const radioGroup = menu.querySelectorAll(`[role=\"menuitemradio\"][name=\"${name}\"]`);\n      radioGroup.forEach(radio => radio.setAttribute('aria-checked', 'false'));\n      item.setAttribute('aria-checked', 'true');\n      this.emitSelectEvent(item, true);\n    }\n    // Handle standard menu items\n    else {\n      this.emitSelectEvent(item);\n    }\n\n    // Close menu after activation (unless it's a checkbox/radio)\n    if (role !== 'menuitemcheckbox' && role !== 'menuitemradio') {\n      this.closeAll();\n    }\n  }\n\n  /**\n   * Emit ren-menubar-select event with item details\n   */\n  emitSelectEvent(item, checked = undefined) {\n    const detail = {\n      item,\n      value: item.textContent?.trim() || item.getAttribute('value') || '',\n    };\n\n    if (checked !== undefined) {\n      detail.checked = checked;\n    }\n\n    this.dispatchEvent(new CustomEvent('ren-menubar-select', {\n      detail,\n      bubbles: true,\n      composed: true,\n    }));\n  }\n\n  /* ===================================================================\n     TYPEAHEAD SUPPORT\n     =================================================================== */\n\n  /**\n   * Handle typeahead character matching\n   * Type characters to jump to matching items\n   */\n  handleTypeahead(char, menu) {\n    // Clear buffer after 500ms of inactivity\n    if (this.typeaheadTimeout) {\n      clearTimeout(this.typeaheadTimeout);\n    }\n\n    this.typeaheadBuffer += char.toLowerCase();\n\n    // Find item matching buffer\n    const items = this.getMenuItems(menu);\n    const currentIndex = items.indexOf(this.focusedItem);\n    const startIndex = (currentIndex + 1) % items.length;\n\n    let matchedItem = null;\n    for (let i = 0; i < items.length; i++) {\n      const index = (startIndex + i) % items.length;\n      const item = items[index];\n      const text = item.textContent?.trim().toLowerCase() || '';\n      if (text.startsWith(this.typeaheadBuffer)) {\n        matchedItem = item;\n        break;\n      }\n    }\n\n    if (matchedItem) {\n      this.focusItem(matchedItem);\n    }\n\n    // Clear buffer after 500ms\n    this.typeaheadTimeout = setTimeout(() => {\n      this.typeaheadBuffer = '';\n    }, 500);\n  }\n\n  /* ===================================================================\n     PUBLIC API\n     =================================================================== */\n\n  /**\n   * Close all open menus\n   * @public\n   */\n  closeAll() {\n    if (this.activeMenuIndex !== -1) {\n      this.closeMenu(this.activeMenuIndex);\n    }\n  }\n\n  /**\n   * Open menu at specified trigger index\n   * @param {number} triggerIndex - Index of the trigger to open\n   * @public\n   */\n  openMenu(triggerIndex) {\n    if (triggerIndex < 0 || triggerIndex >= this.triggers.length) {\n      throw new RangeError(`Invalid trigger index: ${triggerIndex}`);\n    }\n    this.openMenu(triggerIndex);\n  }\n}\n\n// Register custom element\nif (!customElements.get('ren-menubar')) {\n  customElements.define('ren-menubar', RenMenubar);\n}\n",
      "path": "components/patterns/ren-menubar/ren-menubar.js",
      "id": "file:components/patterns/ren-menubar/ren-menubar.js",
      "type": "javascript",
      "name": "ren-menubar.js"
    },
    {
      "data": {},
      "body": ".ren-nav {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: var(--ren-space-2, 0.5rem) var(--ren-space-4, 1rem);\n  background-color: var(--color-surface);\n  border-bottom: 1px solid var(--color-border);\n  min-height: 3.5rem;\n  transition: backdrop-filter var(--duration-overlay) var(--ease-state-change), background-color var(--duration-overlay) var(--ease-state-change);\n\n  &[data-open] .ren-nav-links,\n  &:has(.ren-nav-toggle[aria-expanded=\"true\"]) .ren-nav-links {\n    display: flex;\n  }\n\n  &.ren-nav-sticky {\n    position: sticky;\n    top: 0;\n    z-index: var(--ren-z-sticky, 100);\n    backdrop-filter: blur(8px);\n    background-color: color-mix(in srgb, var(--color-surface) 95%, transparent);\n  }\n\n  &.ren-nav-transparent {\n    background-color: transparent;\n    border-bottom-color: transparent;\n  }\n}\n\n.ren-nav-brand {\n  display: flex;\n  align-items: center;\n  gap: var(--ren-space-2, 0.5rem);\n  font-weight: 600;\n  font-size: var(--ren-title-sm-size, 1.125rem);\n  color: var(--color-text);\n  text-decoration: none;\n  flex-shrink: 0;\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n    border-radius: 0.375rem;\n  }\n}\n\n.ren-nav-links {\n  display: flex;\n  align-items: center;\n  gap: var(--ren-space-1, 0.25rem);\n  list-style: none;\n  margin: 0;\n  padding: 0;\n\n  @media (max-width: 48rem) {\n    display: none;\n    position: absolute;\n    top: 3.5rem;\n    inset-inline: 0;\n    flex-direction: column;\n    gap: 0;\n    background-color: var(--color-surface);\n    border-bottom: 1px solid var(--color-border);\n    padding: var(--ren-space-2, 0.5rem) 0;\n    animation: ren-nav-slide-down var(--duration-enter) var(--ease-enter);\n    z-index: 1000;\n\n    @media (prefers-reduced-motion: reduce) {\n      animation: none;\n    }\n  }\n}\n\n@keyframes ren-nav-slide-down {\n  from {\n    opacity: 0;\n    transform: translateY(-0.5rem);\n  }\n\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n.ren-nav-link {\n  padding: var(--ren-space-2, 0.5rem) var(--ren-space-3, 0.75rem);\n  color: var(--color-text-muted);\n  font-size: var(--ren-label-size, 0.875rem);\n  border-radius: 0.375rem;\n  transition: background-color var(--duration-state) var(--ease-state-change), color var(--duration-state) var(--ease-state-change);\n  text-decoration: none;\n  white-space: nowrap;\n  cursor: pointer;\n\n  &:hover {\n    background-color: var(--color-fill);\n    color: var(--color-text);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: -2px;\n  }\n\n  &[aria-current=\"page\"],\n  &.active {\n    color: var(--color-accent);\n    font-weight: 500;\n    background-color: var(--color-accent-subtle);\n  }\n\n  @media (max-width: 48rem) {\n    padding: var(--ren-space-2, 0.5rem) var(--ren-space-4, 1rem);\n    border-radius: 0;\n\n    &:hover {\n      background-color: var(--color-fill);\n    }\n  }\n}\n\n.ren-nav-actions {\n  display: flex;\n  align-items: center;\n  gap: var(--ren-space-2, 0.5rem);\n  margin-inline-start: auto;\n}\n\n.ren-nav-toggle {\n  display: none;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  background: none;\n  border: none;\n  padding: var(--ren-space-2, 0.5rem);\n  cursor: pointer;\n  color: var(--color-text);\n  min-width: 44px;\n  min-height: 44px;\n  border-radius: 0.375rem;\n  transition: background-color var(--duration-state) var(--ease-state-change);\n\n  &:hover {\n    background-color: var(--color-fill);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  & span {\n    width: 1.5rem;\n    height: 2px;\n    background-color: currentColor;\n    border-radius: 1px;\n    transition: transform var(--duration-route) var(--ease-state-change), opacity var(--duration-route) var(--ease-state-change);\n    transform-origin: center;\n  }\n\n  &[aria-expanded=\"true\"] {\n    & span:nth-child(1) {\n      transform: translateY(0.5rem) rotate(45deg);\n    }\n\n    & span:nth-child(2) {\n      opacity: 0;\n      transform: scaleX(0);\n    }\n\n    & span:nth-child(3) {\n      transform: translateY(-0.5rem) rotate(-45deg);\n    }\n  }\n\n  @media (max-width: 48rem) {\n    display: flex;\n  }\n}\n\n.ren-nav-dropdown {\n  position: relative;\n\n  & [popover] {\n    padding: var(--ren-space-1, 0.25rem);\n    background-color: var(--color-surface);\n    border: 1px solid var(--color-border);\n    border-radius: 0.5rem;\n    box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);\n    margin-top: var(--ren-space-1, 0.25rem);\n    list-style: none;\n    padding-inline-start: 0;\n\n    &[popover] {\n      inset: auto auto auto auto;\n    }\n  }\n\n  & .ren-nav-link[aria-expanded=\"true\"] {\n    background-color: var(--color-fill);\n    color: var(--color-accent);\n  }\n}\n/* Appearance API bridge */\n.ren-nav { min-block-size: var(--ren-nav-height, 3.5rem); padding-inline: var(--ren-nav-padding-x, var(--space-4)); background: var(--ren-nav-bg, var(--color-surface)); border-color: var(--ren-nav-border, var(--color-border)); gap: var(--ren-nav-gap, var(--space-6)); }\n.ren-nav a { color: var(--ren-nav-link-color, var(--color-text-muted)); }\n.ren-nav a[aria-current=\"page\"], .ren-nav a[data-active] { color: var(--ren-nav-link-active, var(--color-text)); }\n",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "file:components/patterns/ren-nav/ren-nav.css",
      "type": "css",
      "name": "ren-nav.css"
    },
    {
      "data": {},
      "body": "/**\n * RenNav Component - Responsive Navigation Bar\n * Modern web component with keyboard navigation and mobile support\n */\n\nconst MOBILE_BREAKPOINT = 768;\nconst KEYBOARD_CODES = { Escape: 'Escape' };\n\nclass RenNav extends HTMLElement {\n  constructor() {\n    super();\n    this._toggleBtn = null;\n    this._linksContainer = null;\n    this._links = [];\n    this._isOpen = false;\n    this._dropdowns = new Map();\n    this._listenerController = null;\n  }\n\n  connectedCallback() {\n    this._initElements();\n    this._attachEventListeners();\n    this._initDropdowns();\n    this._updateMobileState();\n  }\n\n  disconnectedCallback() {\n    this._removeEventListeners();\n  }\n\n  _initElements() {\n    this._toggleBtn = this.querySelector('.ren-nav-toggle');\n    this._linksContainer = this.querySelector('.ren-nav-links');\n    this._links = Array.from(this.querySelectorAll('.ren-nav-link'));\n  }\n\n  _attachEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = new AbortController();\n    const { signal } = this._listenerController;\n\n    // Toggle button click\n    if (this._toggleBtn) {\n      this._toggleBtn.addEventListener('click', () => this._toggleMenu(), { signal });\n    }\n\n    // Close menu on link click\n    this._links.forEach(link => {\n      link.addEventListener('click', () => {\n        if (this._isOpen) {\n          this._closeMenu();\n        }\n      }, { signal });\n    });\n\n    // Close on Escape\n    document.addEventListener('keydown', (e) => {\n      if (e.key === KEYBOARD_CODES.Escape && this._isOpen) {\n        this._closeMenu();\n      }\n    }, { signal });\n\n    // Handle window resize\n    window.addEventListener('resize', () => {\n      this._updateMobileState();\n    }, { signal });\n\n    // Close menu on outside click\n    document.addEventListener('click', (e) => {\n      if (this._isOpen && !this.contains(e.target)) {\n        this._closeMenu();\n      }\n    }, { signal });\n  }\n\n  _initDropdowns() {\n    const signal = this._listenerController?.signal;\n    const dropdownToggles = this.querySelectorAll('[data-dropdown]');\n    dropdownToggles.forEach(toggle => {\n      const popoverId = toggle.getAttribute('aria-controls');\n      if (popoverId) {\n        const popover = this.querySelector(`#${popoverId}`);\n        if (popover) {\n          this._dropdowns.set(toggle, popover);\n          toggle.addEventListener('click', (e) => {\n            e.stopPropagation();\n            this._toggleDropdown(toggle, popover);\n          }, { signal });\n\n          // Close dropdown on item click\n          popover.querySelectorAll('a, button').forEach(item => {\n            item.addEventListener('click', () => {\n              this._closeDropdown(toggle, popover);\n            }, { signal });\n          });\n        }\n      }\n    });\n  }\n\n  _toggleDropdown(toggle, popover) {\n    const isOpen = toggle.getAttribute('aria-expanded') === 'true';\n    if (isOpen) {\n      this._closeDropdown(toggle, popover);\n    } else {\n      // Close other dropdowns\n      this._dropdowns.forEach((pop, tog) => {\n        if (tog !== toggle) {\n          this._closeDropdown(tog, pop);\n        }\n      });\n\n      toggle.setAttribute('aria-expanded', 'true');\n      popover.showPopover?.();\n    }\n  }\n\n  _closeDropdown(toggle, popover) {\n    toggle.setAttribute('aria-expanded', 'false');\n    popover.hidePopover?.();\n  }\n\n  _toggleMenu() {\n    if (this._isOpen) {\n      this._closeMenu();\n    } else {\n      this._openMenu();\n    }\n  }\n\n  _openMenu() {\n    this._isOpen = true;\n    this.setAttribute('data-open', '');\n    if (this._toggleBtn) {\n      this._toggleBtn.setAttribute('aria-expanded', 'true');\n    }\n  }\n\n  _closeMenu() {\n    this._isOpen = false;\n    this.removeAttribute('data-open');\n    if (this._toggleBtn) {\n      this._toggleBtn.setAttribute('aria-expanded', 'false');\n    }\n  }\n\n  _updateMobileState() {\n    const isMobile = window.innerWidth < MOBILE_BREAKPOINT;\n    if (!isMobile && this._isOpen) {\n      this._closeMenu();\n    }\n  }\n\n  _removeEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = null;\n  }\n\n  // Public API\n  setActiveLink(href) {\n    this._links.forEach(link => {\n      if (link.getAttribute('href') === href) {\n        link.setAttribute('aria-current', 'page');\n        link.classList.add('active');\n      } else {\n        link.removeAttribute('aria-current');\n        link.classList.remove('active');\n      }\n    });\n  }\n\n  get isOpen() {\n    return this._isOpen;\n  }\n}\n\ncustomElements.define('ren-nav', RenNav);\n",
      "path": "components/patterns/ren-nav/ren-nav.js",
      "id": "file:components/patterns/ren-nav/ren-nav.js",
      "type": "javascript",
      "name": "ren-nav.js"
    },
    {
      "data": {},
      "body": ".ren-sidebar-layout {\n  display: flex;\n  min-height: 100dvh;\n  background-color: var(--color-surface);\n}\n\n.ren-sidebar {\n  --sidebar-width: var(--ren-sidebar-width, 16rem);\n  --sidebar-collapsed-width: var(--ren-sidebar-collapsed-width, 4rem);\n\n  width: var(--sidebar-width);\n  flex-shrink: 0;\n  background-color: var(--color-surface-sunken);\n  border-inline-end: 1px solid var(--color-border);\n  display: flex;\n  flex-direction: column;\n  transition: width var(--duration-enter) var(--ease-state-change), transform var(--duration-enter) var(--ease-state-change);\n  overflow: hidden;\n  z-index: 50;\n\n  &[data-collapsed] {\n    width: var(--sidebar-collapsed-width);\n\n    & .ren-sidebar-item-text {\n      opacity: 0;\n      width: 0;\n      overflow: hidden;\n      transition: opacity var(--duration-enter) var(--ease-state-change), width var(--duration-enter) var(--ease-state-change);\n    }\n\n    & .ren-sidebar-section-label {\n      opacity: 0;\n      width: 0;\n      overflow: hidden;\n      transition: opacity var(--duration-enter) var(--ease-state-change), width var(--duration-enter) var(--ease-state-change);\n    }\n\n    & .ren-sidebar-item {\n      justify-content: center;\n      position: relative;\n\n      &:hover::after {\n        content: attr(data-tooltip);\n        position: absolute;\n        inset-inline-start: 100%;\n        top: 50%;\n        transform: translateY(-50%);\n        margin-inline-start: var(--ren-space-2, 0.5rem);\n        padding: var(--ren-space-2, 0.5rem) var(--ren-space-3, 0.75rem);\n        background-color: var(--color-text);\n        color: var(--color-surface);\n        border-radius: 0.375rem;\n        font-size: var(--ren-caption-size, 0.75rem);\n        white-space: nowrap;\n        z-index: 1000;\n        pointer-events: none;\n      }\n    }\n  }\n\n  @media (max-width: 48rem) {\n    position: fixed;\n    top: 0;\n    inset-inline-start: 0;\n    height: 100dvh;\n    width: var(--sidebar-width);\n    transform: translateX(-100%);\n    transition: transform var(--duration-enter) var(--ease-state-change);\n\n    &[data-open] {\n      transform: translateX(0);\n      box-shadow: 4px 0 12px rgb(0 0 0 / 0.15);\n    }\n\n    &[data-collapsed] {\n      transform: translateX(-100%);\n      width: var(--sidebar-width);\n    }\n  }\n}\n\n.ren-sidebar-header {\n  padding: var(--ren-space-3, 0.75rem) var(--ren-space-4, 1rem);\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  border-bottom: 1px solid var(--color-border);\n  flex-shrink: 0;\n\n  @media (max-width: 48rem) {\n    padding: var(--ren-space-3, 0.75rem) var(--ren-space-4, 1rem);\n  }\n}\n\n.ren-sidebar-content {\n  flex: 1;\n  overflow-y: auto;\n  padding: var(--ren-space-2, 0.5rem);\n  scrollbar-width: thin;\n  scrollbar-color: var(--color-border) transparent;\n\n  &::-webkit-scrollbar {\n    width: 6px;\n  }\n\n  &::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background-color: var(--color-border);\n    border-radius: 3px;\n\n    &:hover {\n      background-color: var(--color-text-muted);\n    }\n  }\n}\n\n.ren-sidebar-footer {\n  padding: var(--ren-space-3, 0.75rem) var(--ren-space-4, 1rem);\n  border-top: 1px solid var(--color-border);\n  flex-shrink: 0;\n\n  @media (max-width: 48rem) {\n    padding: var(--ren-space-3, 0.75rem) var(--ren-space-4, 1rem);\n  }\n}\n\n.ren-sidebar-nav {\n  list-style: none;\n  padding: 0;\n  margin: 0;\n  display: flex;\n  flex-direction: column;\n  gap: var(--ren-space-1, 0.25rem);\n}\n\n.ren-sidebar-section {\n  margin-top: var(--ren-space-4, 1rem);\n\n  &:first-child {\n    margin-top: 0;\n  }\n}\n\n.ren-sidebar-section-label {\n  padding: var(--ren-space-1, 0.25rem) var(--ren-space-3, 0.75rem);\n  font-size: var(--ren-caption-size, 0.75rem);\n  font-weight: 600;\n  color: var(--color-text-faint);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  transition: opacity var(--duration-enter) var(--ease-state-change);\n  white-space: nowrap;\n  overflow: hidden;\n}\n\n.ren-sidebar-item {\n  display: flex;\n  align-items: center;\n  gap: var(--ren-space-3, 0.75rem);\n  padding: var(--ren-space-2, 0.5rem) var(--ren-space-3, 0.75rem);\n  border-radius: 0.375rem;\n  color: var(--color-text-muted);\n  font-size: var(--ren-label-size, 0.875rem);\n  cursor: pointer;\n  transition: background-color var(--duration-state) var(--ease-state-change), color var(--duration-state) var(--ease-state-change);\n  text-decoration: none;\n  white-space: nowrap;\n  user-select: none;\n\n  &:hover {\n    background-color: var(--color-fill);\n    color: var(--color-text);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: -2px;\n  }\n\n  &.active,\n  &[aria-current=\"page\"] {\n    background-color: var(--color-accent-subtle);\n    color: var(--color-accent);\n    font-weight: 500;\n\n    & .ren-sidebar-item-icon {\n      color: var(--color-accent);\n    }\n  }\n\n  &[aria-disabled=\"true\"] {\n    opacity: 0.5;\n    cursor: not-allowed;\n    pointer-events: none;\n  }\n}\n\n.ren-sidebar-item-icon {\n  flex-shrink: 0;\n  width: 20px;\n  height: 20px;\n  color: var(--color-text-muted);\n  transition: color var(--duration-state) var(--ease-state-change);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.ren-sidebar-item-text {\n  flex: 1;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  transition: opacity var(--duration-enter) var(--ease-state-change), width var(--duration-enter) var(--ease-state-change);\n}\n\n.ren-sidebar-divider {\n  height: 1px;\n  background-color: var(--color-separator);\n  margin: var(--ren-space-2, 0.5rem) 0;\n}\n\n.ren-sidebar-toggle {\n  background: none;\n  border: none;\n  padding: var(--ren-space-2, 0.5rem);\n  cursor: pointer;\n  color: var(--color-text-muted);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-width: 32px;\n  min-height: 32px;\n  border-radius: 0.375rem;\n  transition: background-color var(--duration-state) var(--ease-state-change), color var(--duration-state) var(--ease-state-change);\n\n  &:hover {\n    background-color: var(--color-fill);\n    color: var(--color-text);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  @media (max-width: 48rem) {\n    display: none;\n  }\n}\n\n.ren-sidebar-main {\n  flex: 1;\n  overflow-y: auto;\n  display: flex;\n  flex-direction: column;\n  scrollbar-width: thin;\n  scrollbar-color: var(--color-border) transparent;\n\n  &::-webkit-scrollbar {\n    width: 6px;\n  }\n\n  &::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background-color: var(--color-border);\n    border-radius: 3px;\n\n    &:hover {\n      background-color: var(--color-text-muted);\n    }\n  }\n\n  @media (max-width: 48rem) {\n    margin-inline-start: 0;\n  }\n}\n\n.ren-sidebar-overlay {\n  position: fixed;\n  inset: 0;\n  background-color: rgb(0 0 0 / 0.5);\n  opacity: 0;\n  pointer-events: none;\n  transition: opacity var(--duration-enter) var(--ease-state-change);\n  z-index: 40;\n\n  @media (max-width: 48rem) {\n    .ren-sidebar[data-open] ~ & {\n      opacity: 1;\n      pointer-events: auto;\n    }\n  }\n}\n\n/* ═══ REDUCED MOTION ═══ */\n@media (prefers-reduced-motion: reduce) {\n  .ren-sidebar,\n  .ren-sidebar * {\n    animation-duration: 0ms !important;\n    transition-duration: 0ms !important;\n  }\n}\n/* Appearance API bridge */\n.ren-sidebar { inline-size: var(--ren-sidebar-width, 260px); background: var(--ren-sidebar-bg, var(--color-surface-sunken)); border-color: var(--ren-sidebar-border, var(--color-border)); transition-duration: var(--ren-sidebar-duration, var(--duration-normal)); transition-timing-function: var(--ren-sidebar-easing, var(--ease-out)); }\n.ren-sidebar.is-collapsed, .ren-sidebar[data-collapsed=\"true\"] { inline-size: var(--ren-sidebar-collapsed-width, 60px); }\n.ren-sidebar a, .ren-sidebar button { min-block-size: var(--ren-sidebar-item-height, var(--size-lg)); padding: var(--ren-sidebar-item-padding, var(--space-2) var(--space-3)); border-radius: var(--ren-sidebar-item-radius, var(--radius-md)); }\n.ren-sidebar a[aria-current=\"page\"], .ren-sidebar a[data-active] { background: var(--ren-sidebar-active-bg, var(--color-accent-subtle)); color: var(--ren-sidebar-active-color, var(--color-accent)); }\n\n@media (any-pointer: coarse), (pointer: coarse) {\n  .ren-sidebar-toggle {\n    min-inline-size: var(--touch-min);\n    min-block-size: var(--touch-min);\n  }\n}\n",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "file:components/patterns/ren-sidebar/ren-sidebar.css",
      "type": "css",
      "name": "ren-sidebar.css"
    },
    {
      "data": {},
      "body": "/**\n * RenSidebar Component - Collapsible sidebar for app layouts\n * Supports desktop collapse and mobile overlay modes\n */\n\nconst MOBILE_BREAKPOINT = 768;\nconst STORAGE_KEY = 'ren-sidebar-collapsed';\nconst KEYBOARD_CODES = { Escape: 'Escape' };\n\nclass RenSidebar extends HTMLElement {\n  constructor() {\n    super();\n    this._toggleBtn = null;\n    this._isCollapsed = false;\n    this._isOpen = false;\n    this._isMobile = false;\n    this._navItems = [];\n    this._listenerController = null;\n    this._storageKey = STORAGE_KEY;\n  }\n\n  connectedCallback() {\n    this._initElements();\n    this._restoreState();\n    this._attachEventListeners();\n    this._updateMobileState();\n  }\n\n  disconnectedCallback() {\n    this._removeEventListeners();\n  }\n\n  _initElements() {\n    this._toggleBtn = this.querySelector('.ren-sidebar-toggle');\n    this._navItems = Array.from(this.querySelectorAll('.ren-sidebar-item'));\n    this._storageKey = this.getAttribute('storage-key') || STORAGE_KEY;\n  }\n\n  _attachEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = new AbortController();\n    const { signal } = this._listenerController;\n\n    // Toggle collapse button\n    if (this._toggleBtn) {\n      this._toggleBtn.addEventListener('click', () => this._toggleCollapse(), { signal });\n    }\n\n    // Nav item clicks\n    this._navItems.forEach(item => {\n      item.addEventListener('click', (e) => {\n        if (e.currentTarget.hasAttribute('href')) {\n          this._setActiveItem(e.currentTarget);\n        }\n        if (this._isMobile && this._isOpen) {\n          this._closeMenu();\n        }\n      }, { signal });\n    });\n\n    // Close on Escape (mobile)\n    document.addEventListener('keydown', (e) => {\n      if (e.key === KEYBOARD_CODES.Escape && this._isMobile && this._isOpen) {\n        this._closeMenu();\n      }\n    }, { signal });\n\n    // Handle window resize\n    window.addEventListener('resize', () => {\n      this._updateMobileState();\n    }, { signal });\n\n    // Close menu on outside click (mobile)\n    document.addEventListener('click', (e) => {\n      if (this._isMobile && this._isOpen && !this.contains(e.target)) {\n        const overlay = this.nextElementSibling;\n        if (overlay && overlay.classList.contains('ren-sidebar-overlay')) {\n          if (overlay.contains(e.target)) {\n            this._closeMenu();\n          }\n        }\n      }\n    }, { signal });\n\n    // Expose toggle menu for mobile\n    this._setupMobileToggle();\n  }\n\n  _setupMobileToggle() {\n    // Allow external control via method\n    this.toggleMenu = () => {\n      if (this._isOpen) {\n        this._closeMenu();\n      } else {\n        this._openMenu();\n      }\n    };\n  }\n\n  _removeEventListeners() {\n    this._listenerController?.abort();\n    this._listenerController = null;\n  }\n\n  _toggleCollapse() {\n    if (this._isCollapsed) {\n      this._expand();\n    } else {\n      this._collapse();\n    }\n  }\n\n  _collapse() {\n    this._isCollapsed = true;\n    this.setAttribute('data-collapsed', '');\n    this._writeCollapsedState(true);\n    this._dispatchToggleEvent();\n  }\n\n  _expand() {\n    this._isCollapsed = false;\n    this.removeAttribute('data-collapsed');\n    this._writeCollapsedState(false);\n    this._dispatchToggleEvent();\n  }\n\n  _toggleMenu() {\n    if (this._isOpen) {\n      this._closeMenu();\n    } else {\n      this._openMenu();\n    }\n  }\n\n  _openMenu() {\n    if (!this._isMobile) return;\n    this._isOpen = true;\n    this.setAttribute('data-open', '');\n  }\n\n  _closeMenu() {\n    if (!this._isMobile) return;\n    this._isOpen = false;\n    this.removeAttribute('data-open');\n  }\n\n  _updateMobileState() {\n    const wasMobile = this._isMobile;\n    this._isMobile = window.innerWidth < MOBILE_BREAKPOINT;\n\n    // Close menu when transitioning to desktop\n    if (wasMobile && !this._isMobile && this._isOpen) {\n      this._closeMenu();\n    }\n\n    // Reset collapse on mobile\n    if (this._isMobile && this._isCollapsed) {\n      this._expand();\n    }\n  }\n\n  _restoreState() {\n    const isCollapsed = this._readCollapsedState();\n    if (isCollapsed && !this._isMobile) {\n      this._collapse();\n    }\n  }\n\n  _readCollapsedState() {\n    try {\n      return localStorage.getItem(this._storageKey) === 'true';\n    } catch {\n      return false;\n    }\n  }\n\n  _writeCollapsedState(isCollapsed) {\n    try {\n      localStorage.setItem(this._storageKey, isCollapsed ? 'true' : 'false');\n    } catch {\n      // Storage can be blocked in private contexts; visual state still updates.\n    }\n  }\n\n  _setActiveItem(item) {\n    this._navItems.forEach(navItem => {\n      navItem.classList.remove('active');\n      navItem.removeAttribute('aria-current');\n    });\n\n    item.classList.add('active');\n    item.setAttribute('aria-current', 'page');\n  }\n\n  _dispatchToggleEvent() {\n    const event = new CustomEvent('ren-sidebar-toggle', {\n      detail: { collapsed: this._isCollapsed },\n      bubbles: true,\n      composed: true\n    });\n    this.dispatchEvent(event);\n  }\n\n  // Public API\n  get isCollapsed() {\n    return this._isCollapsed;\n  }\n\n  get isMobileOpen() {\n    return this._isOpen;\n  }\n\n  setActiveItem(href) {\n    const item = this._navItems.find((navItem) => navItem.getAttribute('href') === href);\n    if (item && item.classList.contains('ren-sidebar-item')) {\n      this._setActiveItem(item);\n    }\n  }\n\n  openMobileMenu() {\n    if (this._isMobile) {\n      this._openMenu();\n    }\n  }\n\n  closeMobileMenu() {\n    if (this._isMobile) {\n      this._closeMenu();\n    }\n  }\n}\n\ncustomElements.define('ren-sidebar', RenSidebar);\n",
      "path": "components/patterns/ren-sidebar/ren-sidebar.js",
      "id": "file:components/patterns/ren-sidebar/ren-sidebar.js",
      "type": "javascript",
      "name": "ren-sidebar.js"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — Data Table Organism\n * ============================\n *\n * A comprehensive, accessible data table component supporting:\n * - Sortable columns with directional indicators\n * - Row selection with multi-select and shift+click range selection\n * - Client-side pagination with configurable page size\n * - Keyboard navigation (arrow keys, Enter, Space)\n * - Column resizing via drag handle\n * - Pinned/sticky columns and headers\n * - Multiple density variants (compact, comfortable)\n * - Loading states with skeleton shimmer\n * - Empty state messaging\n * - Responsive horizontal scrolling\n * - Search/filter toolbar integration\n * - Full ARIA support for accessibility\n *\n * @example\n * <ren-table data-page-size=\"10\">\n *   <div class=\"ren-table-toolbar\">\n *     <input class=\"ren-input ren-input-sm\" placeholder=\"Search...\" data-table-search>\n *   </div>\n *   <div class=\"ren-table-wrapper\">\n *     <table class=\"ren-table\">\n *       <thead class=\"ren-table-header\">\n *         <tr>\n *           <th class=\"ren-th ren-table-select\"><input type=\"checkbox\" aria-label=\"Select all\"></th>\n *           <th class=\"ren-th ren-th-sortable\" data-column=\"name\">Name</th>\n *         </tr>\n *       </thead>\n *       <tbody class=\"ren-table-body\">\n *         <tr class=\"ren-tr\" data-row-id=\"1\">\n *           <td class=\"ren-td ren-table-select\"><input type=\"checkbox\" aria-label=\"Select row\"></td>\n *           <td class=\"ren-td\">John Doe</td>\n *         </tr>\n *       </tbody>\n *     </table>\n *   </div>\n *   <div class=\"ren-table-pagination\"></div>\n * </ren-table>\n */\n\n/* ===== Base Table Structure ===== */\n\nren-table {\n  container-type: inline-size;\n  container-name: ren-table;\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n  width: 100%;\n}\n\n.ren-table-wrapper {\n  flex: 1;\n  overflow-x: auto;\n  overflow-y: auto;\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n  scroll-behavior: smooth;\n  scrollbar-width: thin;\n  scrollbar-color: var(--color-border) transparent;\n\n  &::-webkit-scrollbar {\n    height: 8px;\n    width: 8px;\n  }\n\n  &::-webkit-scrollbar-track {\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background-color: var(--color-border);\n    border-radius: var(--radius-sm);\n    transition: background-color var(--duration-state) var(--ease-enter);\n\n    &:hover {\n      background-color: var(--color-text-muted);\n    }\n  }\n}\n\n.ren-table {\n  width: 100%;\n  border-collapse: collapse;\n  background-color: var(--color-surface);\n  font-size: var(--body-size);\n  color: var(--color-text);\n}\n\n/* ===== Header Styles ===== */\n\n.ren-table-header {\n  position: sticky;\n  top: 0;\n  z-index: 10;\n  background-color: var(--color-surface-raised);\n}\n\n.ren-table-header tr {\n  border-bottom: 1px solid var(--color-border);\n}\n\n.ren-th {\n  padding: var(--space-3) var(--space-4);\n  text-align: start;\n  font-weight: var(--label-weight);\n  font-size: var(--label-size);\n  color: var(--color-text);\n  user-select: none;\n  white-space: nowrap;\n  background-color: var(--color-surface-raised);\n  border-inline-end: 1px solid var(--color-border);\n  height: 44px;\n  display: table-cell;\n  vertical-align: middle;\n\n  &:last-child {\n    border-inline-end: none;\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: -2px;\n  }\n}\n\n.ren-th-sortable {\n  cursor: pointer;\n  position: relative;\n  transition: background-color var(--duration-tactile) var(--ease-enter);\n\n  &:hover {\n    background-color: var(--color-fill);\n  }\n\n  &::after {\n    content: '';\n    display: inline-block;\n    width: 12px;\n    height: 12px;\n    margin-inline-start: var(--space-2);\n    background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 12 12\" fill=\"none\"><path d=\"M3 8L6 5l3 3M3 4L6 7l3-3\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>');\n    background-size: contain;\n    background-repeat: no-repeat;\n    opacity: 0.4;\n    transition: opacity var(--duration-tactile) var(--ease-enter), transform var(--duration-tactile) var(--ease-enter);\n    vertical-align: middle;\n    color: var(--color-text-muted);\n  }\n\n  &[data-sort=\"asc\"]::after {\n    opacity: 1;\n    transform: scaleY(1);\n    background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 12 12\" fill=\"none\"><path d=\"M6 2v6M3 5l3-3 3 3\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>');\n  }\n\n  &[data-sort=\"desc\"]::after {\n    opacity: 1;\n    transform: scaleY(-1);\n    background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 12 12\" fill=\"none\"><path d=\"M6 2v6M3 5l3-3 3 3\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>');\n  }\n}\n\n.ren-th-resize {\n  position: absolute;\n  inset-inline-end: 0;\n  top: 0;\n  height: 100%;\n  width: 4px;\n  cursor: col-resize;\n  user-select: none;\n  background-color: transparent;\n  padding: 0;\n  margin: 0;\n  transition: background-color var(--duration-tactile) var(--ease-enter);\n\n  &:hover,\n  &:active {\n    background-color: var(--color-accent);\n  }\n}\n\n/* Pinned columns */\n.ren-th-pinned {\n  position: sticky;\n  z-index: 11;\n}\n\n/* ===== Body & Row Styles ===== */\n\n.ren-table-body {\n  background-color: var(--color-surface);\n}\n\n.ren-tr {\n  border-bottom: var(--stroke-1) solid var(--color-border);\n  transition: background-color var(--duration-tactile) var(--ease-enter);\n  height: 44px;\n\n  &:nth-child(even) {\n    background-color: var(--color-fill);\n  }\n\n  &:hover {\n    background-color: var(--color-fill-hover);\n  }\n\n  &[aria-selected=\"true\"] {\n    background-color: var(--color-accent-subtle);\n\n    .ren-td {\n      background-color: var(--color-accent-subtle);\n    }\n  }\n\n  &:focus-within {\n    outline: 2px solid var(--color-accent);\n    outline-offset: -1px;\n  }\n}\n\n.ren-td {\n  padding: var(--space-3) var(--space-4);\n  border-inline-end: 1px solid var(--color-border);\n  color: var(--color-text);\n  vertical-align: middle;\n  height: 44px;\n\n  &:last-child {\n    border-inline-end: none;\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: -2px;\n  }\n}\n\n/* Pinned column cells */\n.ren-td-pinned {\n  position: sticky;\n  z-index: 9;\n  background-color: inherit;\n}\n\n/* ===== Selection Styles ===== */\n\n.ren-table-select {\n  width: var(--space-10);\n  padding: var(--space-3);\n  text-align: center;\n\n  input[type=\"checkbox\"] {\n    width: 18px;\n    height: 18px;\n    cursor: pointer;\n    accent-color: var(--color-accent);\n    border: 1px solid var(--color-border);\n    border-radius: var(--radius-sm);\n    transition: background-color var(--duration-tactile) var(--ease-enter),\n                border-color var(--duration-tactile) var(--ease-enter);\n\n    &:hover:not(:disabled) {\n      border-color: var(--color-accent);\n    }\n\n    &:checked {\n      background-color: var(--color-accent);\n      border-color: var(--color-accent);\n      color: var(--color-on-accent);\n    }\n\n    &:focus-visible {\n      outline: 2px solid var(--color-accent);\n      outline-offset: 2px;\n    }\n\n    &:disabled {\n      opacity: 0.5;\n      cursor: not-allowed;\n    }\n  }\n}\n\n/* ===== Pagination ===== */\n\n.ren-table-pagination {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: var(--space-3) var(--space-4);\n  border-top: 1px solid var(--color-border);\n  background-color: var(--color-surface);\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n  gap: var(--space-4);\n\n  &:empty {\n    display: none;\n  }\n}\n\n.ren-table-pagination-info {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  font-size: var(--body-size);\n  color: var(--color-text);\n}\n\n.ren-table-pagination-controls {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n}\n\n.ren-table-pagination-button {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: var(--touch-min);\n  height: var(--touch-min);\n  padding: var(--space-2);\n  background-color: var(--color-fill);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-sm);\n  color: var(--color-text);\n  font-size: var(--body-size);\n  cursor: pointer;\n  transition: background-color var(--duration-tactile) var(--ease-enter),\n              border-color var(--duration-tactile) var(--ease-enter);\n  user-select: none;\n  min-width: 44px;\n  min-height: 44px;\n\n  &:hover:not(:disabled) {\n    background-color: var(--color-fill-hover);\n    border-color: var(--color-accent);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-accent);\n    outline-offset: 2px;\n  }\n\n  &:disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n    background-color: var(--color-fill);\n  }\n}\n\n/* ===== Toolbar Styles ===== */\n\n.ren-table-toolbar {\n  display: flex;\n  align-items: center;\n  gap: var(--space-3);\n  padding: var(--space-3) var(--space-4);\n  background-color: var(--color-surface);\n  border-bottom: 1px solid var(--color-border);\n  flex-wrap: wrap;\n\n  &:empty {\n    display: none;\n  }\n}\n\n.ren-table-toolbar-search {\n  flex: 1;\n  min-width: 200px;\n  max-width: 300px;\n}\n\n/* ===== Empty State ===== */\n\n.ren-table-empty {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  padding: var(--space-10);\n  gap: var(--space-4);\n  text-align: center;\n  color: var(--color-text-muted);\n  background-color: var(--color-surface);\n  min-height: 200px;\n\n  .ren-table-empty-icon {\n    font-size: 40px;\n    opacity: 0.4;\n  }\n\n  .ren-table-empty-title {\n    font-size: var(--label-size);\n    font-weight: var(--label-weight);\n    color: var(--color-text);\n  }\n\n  .ren-table-empty-description {\n    font-size: var(--caption-size);\n    color: var(--color-text-muted);\n    max-width: 300px;\n  }\n}\n\n/* ===== Loading State ===== */\n\nren-table[data-loading] {\n  .ren-table-body {\n    position: relative;\n  }\n\n  .ren-tr {\n    opacity: 0.5;\n    pointer-events: none;\n  }\n}\n\n.ren-table-loading-overlay {\n  position: absolute;\n  inset: 0;\n  background: linear-gradient(\n    90deg,\n    transparent,\n    rgb(255, 255, 255, 0.4),\n    transparent\n  );\n  background-size: 200% 100%;\n  animation: shimmer 1.5s infinite;\n  z-index: 20;\n}\n\n@keyframes shimmer {\n  0% {\n    background-position: -200% 0;\n  }\n\n  100% {\n    background-position: 200% 0;\n  }\n}\n\n/* ===== Density Variants ===== */\n\n.ren-table-compact {\n  .ren-th,\n  .ren-td {\n    padding: var(--space-2) var(--space-3);\n    height: 32px;\n  }\n\n  .ren-tr {\n    height: 32px;\n  }\n}\n\n.ren-table-comfortable {\n  .ren-th,\n  .ren-td {\n    padding: var(--space-4) var(--space-5);\n    height: 56px;\n  }\n\n  .ren-tr {\n    height: 56px;\n  }\n}\n\n/* ===== Responsive Design (Container Query) ===== */\n\n@container ren-table (max-width: 600px) {\n  .ren-table {\n    min-width: 600px;\n  }\n\n  .ren-table-pagination {\n    flex-direction: column;\n    align-items: flex-start;\n    gap: var(--space-3);\n  }\n\n  .ren-table-pagination-info {\n    width: 100%;\n  }\n\n  .ren-table-pagination-controls {\n    width: 100%;\n    justify-content: space-between;\n  }\n\n  .ren-table-toolbar {\n    flex-direction: column;\n  }\n\n  .ren-table-toolbar-search {\n    min-width: 100%;\n    max-width: 100%;\n  }\n}\n\n/* ===== Accessibility ===== */\n\n/* High contrast mode support */\n@media (prefers-contrast: more) {\n  .ren-th {\n    border: 1px solid var(--color-text);\n  }\n\n  .ren-td {\n    border: var(--stroke-1) solid var(--color-text);\n  }\n\n  .ren-th-sortable:hover {\n    background-color: var(--color-text);\n    color: var(--color-surface);\n  }\n}\n\n/* Reduced motion support */\n@media (prefers-reduced-motion: reduce) {\n  .ren-tr,\n  .ren-th-sortable,\n  .ren-table-pagination-button,\n  .ren-table-select input[type=\"checkbox\"] {\n    transition: none;\n  }\n\n  .ren-table-loading-overlay {\n    animation: none;\n    background: rgb(255, 255, 255, 0.2);\n  }\n}\n\n/* Dark mode support (if light/dark theming is used) */\n@media (prefers-color-scheme: dark) {\n  .ren-table {\n    /* Uses CSS custom properties from design tokens */\n    /* No additional overrides needed if tokens are properly defined */\n  }\n}\n/* Appearance API bridge */\n.ren-table { font-size: var(--ren-table-header-size, var(--body-size)); }\n.ren-table thead { background: var(--ren-table-header-bg, var(--color-surface-sunken)); color: var(--ren-table-header-color, var(--color-text-muted)); font-weight: var(--ren-table-header-weight, var(--weight-semibold)); }\n.ren-table tr { min-block-size: var(--ren-table-row-height, var(--size-lg)); block-size: var(--ren-table-row-height, var(--size-lg)); }\n.ren-table th, .ren-table td { padding: var(--ren-table-cell-padding, var(--space-3)); }\n.ren-table { border-color: var(--ren-table-border-color, var(--color-border-muted)); }\n.ren-table tbody tr:nth-child(even) { background: var(--ren-table-stripe-bg, var(--color-surface-sunken)); }\n.ren-table tbody tr:hover { background: var(--ren-table-hover-bg, var(--color-fill)); }\n.ren-table tbody tr[aria-selected=\"true\"], .ren-table tbody tr[data-selected] { background: var(--ren-table-selected-bg, var(--color-accent-subtle)); }\n",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "file:components/patterns/ren-table/ren-table.css",
      "type": "css",
      "name": "ren-table.css"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — Data Table Organism\n * ============================\n *\n * A comprehensive, accessible data table web component that extends native\n * HTML tables with powerful features while maintaining semantic structure.\n *\n * Features:\n * - Click-to-sort columns with asc/desc/none cycling\n * - Multi-select with select-all checkbox and shift+click range selection\n * - Client-side pagination with configurable page size\n * - Keyboard navigation (arrow keys, Enter to sort, Space to select)\n * - Client-side filtering/search\n * - Column resizing via drag handle\n * - Pinned/sticky column support\n * - Multiple density variants (compact, comfortable, default)\n * - Loading state with shimmer effect\n * - Empty state messaging\n * - Full ARIA support for accessibility\n * - Light DOM (no Shadow DOM) for easy styling and integration\n *\n * @example\n * <ren-table data-page-size=\"10\">\n *   <div class=\"ren-table-wrapper\">\n *     <table class=\"ren-table\">\n *       <thead class=\"ren-table-header\">\n *         <tr>\n *           <th class=\"ren-th ren-table-select\"><input type=\"checkbox\"></th>\n *           <th class=\"ren-th ren-th-sortable\" data-column=\"name\">Name</th>\n *         </tr>\n *       </thead>\n *       <tbody class=\"ren-table-body\">\n *         <tr class=\"ren-tr\" data-row-id=\"1\">\n *           <td class=\"ren-td ren-table-select\"><input type=\"checkbox\"></td>\n *           <td class=\"ren-td\">John Doe</td>\n *         </tr>\n *       </tbody>\n *     </table>\n *   </div>\n *   <div class=\"ren-table-pagination\"></div>\n * </ren-table>\n *\n * @fires ren-sort - Dispatched when column sort changes: { column, direction }\n * @fires ren-select - Dispatched when row selection changes: { selected }\n * @fires ren-page - Dispatched when page changes: { page, pageSize }\n * @fires ren-filter - Dispatched when filter changes: { columnIndex, value }\n */\n\nexport class RenTable extends HTMLElement {\n  #table = null;\n  #thead = null;\n  #tbody = null;\n  #headerCheckbox = null;\n  #rowCheckboxes = [];\n  #headerCells = [];\n  #rows = [];\n  #visibleRows = [];\n  #sortColumn = null;\n  #sortDirection = null;\n  #selectedRows = new Set();\n  #currentPage = 1;\n  #pageSize = 10;\n  #filterValues = new Map();\n  #columnWidths = new Map();\n  #resizingColumn = null;\n  #resizeStartX = 0;\n  #resizeStartWidth = 0;\n  #lastSelectedRowIndex = -1;\n  #listenerController = null;\n  #rowCheckboxController = null;\n\n  constructor() {\n    super();\n  }\n\n  connectedCallback() {\n    this._initializeComponent();\n    this._attachEventListeners();\n    this._setupPagination();\n  }\n\n  disconnectedCallback() {\n    this._removeEventListeners();\n  }\n\n  /**\n   * Initialize table references and attributes\n   * @private\n   */\n  _initializeComponent() {\n    this.#table = this.querySelector('.ren-table');\n    this.#thead = this.querySelector('.ren-table-header');\n    this.#tbody = this.querySelector('.ren-table-body');\n\n    if (!this.#table || !this.#thead || !this.#tbody) {\n      console.warn('RenTable: Missing required table structure');\n      return;\n    }\n\n    // Get page size from attribute\n    const pageSize = this.getAttribute('data-page-size');\n    if (pageSize) {\n      this.#pageSize = parseInt(pageSize, 10);\n    }\n\n    // Initialize header cells\n    this.#headerCells = Array.from(this.#thead.querySelectorAll('.ren-th'));\n    this.#headerCheckbox = this.#thead.querySelector('input[type=\"checkbox\"]');\n\n    // Initialize rows\n    this._refreshRows();\n\n    // Add ARIA roles\n    this.#table.setAttribute('role', 'table');\n    if (!this.#thead.hasAttribute('role')) {\n      this.#thead.setAttribute('role', 'rowgroup');\n    }\n    if (!this.#tbody.hasAttribute('role')) {\n      this.#tbody.setAttribute('role', 'rowgroup');\n    }\n  }\n\n  /**\n   * Attach all event listeners\n   * @private\n   */\n  _attachEventListeners() {\n    this.#listenerController?.abort();\n    this.#listenerController = new AbortController();\n    const { signal } = this.#listenerController;\n\n    // Sorting\n    this.#headerCells.forEach((cell, index) => {\n      if (cell.classList.contains('ren-th-sortable')) {\n        cell.addEventListener('click', () => this._handleSort(cell, index), { signal });\n        cell.addEventListener('keydown', (e) => {\n          if (e.key === 'Enter' || e.key === ' ') {\n            e.preventDefault();\n            this._handleSort(cell, index);\n          }\n        }, { signal });\n        cell.setAttribute('tabindex', '0');\n        cell.setAttribute('role', 'columnheader');\n        cell.setAttribute('aria-sort', 'none');\n      }\n    });\n\n    // Selection - header checkbox\n    if (this.#headerCheckbox) {\n      this.#headerCheckbox.addEventListener('change', () => this._handleSelectAll(), { signal });\n    }\n\n    // Selection - row checkboxes\n    this._attachRowCheckboxListeners();\n\n    // Keyboard navigation\n    this.#table.addEventListener('keydown', (e) => this._handleKeyboard(e), { signal });\n\n    // Column resizing\n    this._setupColumnResize(signal);\n\n    // Search input\n    const searchInput = this.querySelector('[data-table-search]');\n    if (searchInput) {\n      searchInput.addEventListener('input', (e) => {\n        this._filterRows(e.target.value);\n      }, { signal });\n    }\n\n    // Pagination button clicks\n    const paginationCtrls = this.querySelector('.ren-table-pagination-controls');\n    if (paginationCtrls) {\n      const prevBtn = paginationCtrls.querySelector('[data-page-prev]');\n      const nextBtn = paginationCtrls.querySelector('[data-page-next]');\n\n      if (prevBtn) prevBtn.addEventListener('click', () => this._previousPage(), { signal });\n      if (nextBtn) nextBtn.addEventListener('click', () => this._nextPage(), { signal });\n    }\n\n    // Pagination page size selector\n    const pageSizeSelect = this.querySelector('[data-page-size-select]');\n    if (pageSizeSelect) {\n      pageSizeSelect.addEventListener('change', (e) => {\n        this.#pageSize = parseInt(e.target.value, 10);\n        this.#currentPage = 1;\n        this._renderTable();\n      }, { signal });\n    }\n  }\n\n  /**\n   * Remove event listeners\n   * @private\n   */\n  _removeEventListeners() {\n    this.#listenerController?.abort();\n    this.#listenerController = null;\n    this.#rowCheckboxController?.abort();\n    this.#rowCheckboxController = null;\n    this.#resizingColumn = null;\n  }\n\n  /**\n   * Refresh the list of all rows\n   * @private\n   */\n  _refreshRows() {\n    this.#rows = Array.from(this.#tbody.querySelectorAll('.ren-tr'));\n    this.#visibleRows = [...this.#rows];\n    this._attachRowCheckboxListeners();\n  }\n\n  /**\n   * Attach listeners to row checkboxes\n   * @private\n   */\n  _attachRowCheckboxListeners() {\n    this.#rowCheckboxController?.abort();\n    this.#rowCheckboxController = new AbortController();\n    const { signal } = this.#rowCheckboxController;\n\n    const checkboxes = Array.from(this.#tbody.querySelectorAll('.ren-table-select input[type=\"checkbox\"]'));\n    checkboxes.forEach((checkbox, index) => {\n      checkbox.addEventListener('change', (e) => {\n        this._handleRowSelect(e, index);\n      }, { signal });\n    });\n    this.#rowCheckboxes = checkboxes;\n  }\n\n  /**\n   * Handle column header click for sorting\n   * @private\n   */\n  _handleSort(cell, index) {\n    const columnName = cell.getAttribute('data-column');\n    if (!columnName) return;\n\n    // Cycle through sort states: none -> asc -> desc -> none\n    let newDirection = null;\n    if (this.#sortColumn !== columnName) {\n      newDirection = 'asc';\n    } else if (this.#sortDirection === 'asc') {\n      newDirection = 'desc';\n    } else if (this.#sortDirection === 'desc') {\n      newDirection = null;\n    } else {\n      newDirection = 'asc';\n    }\n\n    this.#sortColumn = newDirection ? columnName : null;\n    this.#sortDirection = newDirection;\n\n    // Update visual indicators\n    this.#headerCells.forEach((hc) => {\n      if (hc.classList.contains('ren-th-sortable')) {\n        hc.removeAttribute('data-sort');\n        hc.setAttribute('aria-sort', 'none');\n      }\n    });\n\n    if (newDirection) {\n      cell.setAttribute('data-sort', newDirection);\n      cell.setAttribute('aria-sort', newDirection === 'asc' ? 'ascending' : 'descending');\n    }\n\n    // Dispatch event\n    this._dispatchEvent('ren-sort', {\n      column: columnName,\n      direction: newDirection,\n    });\n\n    // Re-render\n    this.#currentPage = 1;\n    this._renderTable();\n  }\n\n  /**\n   * Handle select all checkbox\n   * @private\n   */\n  _handleSelectAll() {\n    const isChecked = this.#headerCheckbox.checked;\n\n    this.#visibleRows.forEach((row) => {\n      const rowId = row.getAttribute('data-row-id');\n      const checkbox = row.querySelector('input[type=\"checkbox\"]');\n\n      if (isChecked) {\n        this.#selectedRows.add(rowId);\n        row.setAttribute('aria-selected', 'true');\n        if (checkbox) checkbox.checked = true;\n      } else {\n        this.#selectedRows.delete(rowId);\n        row.removeAttribute('aria-selected');\n        if (checkbox) checkbox.checked = false;\n      }\n    });\n\n    this._dispatchEvent('ren-select', {\n      selected: Array.from(this.#selectedRows),\n    });\n  }\n\n  /**\n   * Handle individual row selection\n   * @private\n   */\n  _handleRowSelect(event, rowIndex) {\n    const row = this.#visibleRows[rowIndex];\n    if (!row) return;\n\n    const rowId = row.getAttribute('data-row-id');\n    const isChecked = event.target.checked;\n    const isShiftClick = event.shiftKey;\n\n    if (isShiftClick && this.#lastSelectedRowIndex !== -1) {\n      // Range selection\n      const start = Math.min(this.#lastSelectedRowIndex, rowIndex);\n      const end = Math.max(this.#lastSelectedRowIndex, rowIndex);\n\n      for (let i = start; i <= end; i++) {\n        const rangeRow = this.#visibleRows[i];\n        const rangeRowId = rangeRow.getAttribute('data-row-id');\n        const rangeCheckbox = rangeRow.querySelector('input[type=\"checkbox\"]');\n\n        if (isChecked) {\n          this.#selectedRows.add(rangeRowId);\n          rangeRow.setAttribute('aria-selected', 'true');\n          if (rangeCheckbox) rangeCheckbox.checked = true;\n        } else {\n          this.#selectedRows.delete(rangeRowId);\n          rangeRow.removeAttribute('aria-selected');\n          if (rangeCheckbox) rangeCheckbox.checked = false;\n        }\n      }\n    } else {\n      // Single selection\n      if (isChecked) {\n        this.#selectedRows.add(rowId);\n        row.setAttribute('aria-selected', 'true');\n      } else {\n        this.#selectedRows.delete(rowId);\n        row.removeAttribute('aria-selected');\n      }\n    }\n\n    this.#lastSelectedRowIndex = rowIndex;\n\n    // Update select-all checkbox state\n    this._updateSelectAllCheckbox();\n\n    // Dispatch event\n    this._dispatchEvent('ren-select', {\n      selected: Array.from(this.#selectedRows),\n    });\n  }\n\n  /**\n   * Update select-all checkbox based on row selections\n   * @private\n   */\n  _updateSelectAllCheckbox() {\n    if (!this.#headerCheckbox) return;\n\n    const visibleRowIds = this.#visibleRows\n      .map((row) => row.getAttribute('data-row-id'))\n      .filter(Boolean);\n    const allVisibleSelected = visibleRowIds.every((id) =>\n      this.#selectedRows.has(id)\n    );\n    const someSelected = visibleRowIds.some((id) =>\n      this.#selectedRows.has(id)\n    );\n\n    this.#headerCheckbox.checked = allVisibleSelected;\n    this.#headerCheckbox.indeterminate = someSelected && !allVisibleSelected;\n  }\n\n  /**\n   * Handle keyboard navigation\n   * @private\n   */\n  _handleKeyboard(event) {\n    const { key, target } = event;\n\n    // Space to toggle row selection\n    if (key === ' ' && target.classList.contains('ren-tr')) {\n      event.preventDefault();\n      const checkbox = target.querySelector('input[type=\"checkbox\"]');\n      if (checkbox) {\n        checkbox.checked = !checkbox.checked;\n        checkbox.dispatchEvent(new Event('change', { bubbles: true }));\n      }\n    }\n\n    // Arrow keys for navigation\n    if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(key)) {\n      event.preventDefault();\n      this._navigateCells(target, key);\n    }\n\n    // Enter to sort (on header)\n    if (key === 'Enter' && target.classList.contains('ren-th-sortable')) {\n      event.preventDefault();\n      target.click();\n    }\n  }\n\n  /**\n   * Navigate between cells with arrow keys\n   * @private\n   */\n  _navigateCells(currentCell, direction) {\n    let nextCell = currentCell;\n\n    if (direction === 'ArrowUp') {\n      const row = currentCell.closest('tr');\n      const prevRow = row?.previousElementSibling;\n      if (prevRow) {\n        const cellIndex = Array.from(row.cells).indexOf(currentCell);\n        nextCell = prevRow.cells[cellIndex];\n      }\n    } else if (direction === 'ArrowDown') {\n      const row = currentCell.closest('tr');\n      const nextRow = row?.nextElementSibling;\n      if (nextRow) {\n        const cellIndex = Array.from(row.cells).indexOf(currentCell);\n        nextCell = nextRow.cells[cellIndex];\n      }\n    } else if (direction === 'ArrowLeft') {\n      nextCell = currentCell.previousElementSibling || currentCell;\n    } else if (direction === 'ArrowRight') {\n      nextCell = currentCell.nextElementSibling || currentCell;\n    }\n\n    if (nextCell && nextCell !== currentCell) {\n      nextCell.focus();\n    }\n  }\n\n  /**\n   * Setup column resizing\n   * @private\n   */\n  _setupColumnResize(signal) {\n    this.#headerCells.forEach((cell) => {\n      let resizeHandle = cell.querySelector('.ren-th-resize');\n\n      if (!resizeHandle) {\n        resizeHandle = document.createElement('div');\n        resizeHandle.className = 'ren-th-resize';\n        cell.appendChild(resizeHandle);\n      }\n\n      resizeHandle.addEventListener('mousedown', (e) => {\n        this._startResize(e, cell);\n      }, { signal });\n    });\n\n    document.addEventListener('mousemove', (e) => this._handleResize(e), { signal });\n    document.addEventListener('mouseup', () => this._endResize(), { signal });\n  }\n\n  /**\n   * Start column resize\n   * @private\n   */\n  _startResize(event, cell) {\n    this.#resizingColumn = cell;\n    this.#resizeStartX = event.clientX;\n    this.#resizeStartWidth = cell.offsetWidth;\n    cell.style.cursor = 'col-resize';\n  }\n\n  /**\n   * Handle column resize drag\n   * @private\n   */\n  _handleResize(event) {\n    if (!this.#resizingColumn) return;\n\n    const delta = event.clientX - this.#resizeStartX;\n    const newWidth = Math.max(80, this.#resizeStartWidth + delta);\n    this.#resizingColumn.style.width = `${newWidth}px`;\n  }\n\n  /**\n   * End column resize\n   * @private\n   */\n  _endResize() {\n    if (this.#resizingColumn) {\n      this.#resizingColumn.style.cursor = '';\n      this.#resizingColumn = null;\n    }\n  }\n\n  /**\n   * Filter rows by search value\n   * @private\n   */\n  _filterRows(searchValue) {\n    const lowerSearch = searchValue.toLowerCase();\n\n    this.#visibleRows = this.#rows.filter((row) => {\n      const cells = Array.from(row.querySelectorAll('.ren-td'));\n      return cells.some((cell) =>\n        cell.textContent.toLowerCase().includes(lowerSearch)\n      );\n    });\n\n    this.#currentPage = 1;\n    this._renderTable();\n\n    this._dispatchEvent('ren-filter', {\n      value: searchValue,\n    });\n  }\n\n  /**\n   * Sort visible rows\n   * @private\n   */\n  _sortVisibleRows() {\n    if (!this.#sortColumn || !this.#sortDirection) return;\n\n    const columnIndex = this.#headerCells.findIndex(\n      (cell) => cell.getAttribute('data-column') === this.#sortColumn\n    );\n\n    if (columnIndex === -1) return;\n\n    this.#visibleRows.sort((a, b) => {\n      const aValue = a.cells[columnIndex]?.textContent || '';\n      const bValue = b.cells[columnIndex]?.textContent || '';\n\n      // Try numeric comparison first\n      const aNum = parseFloat(aValue);\n      const bNum = parseFloat(bValue);\n\n      let comparison = 0;\n      if (!isNaN(aNum) && !isNaN(bNum)) {\n        comparison = aNum - bNum;\n      } else {\n        comparison = aValue.localeCompare(bValue);\n      }\n\n      return this.#sortDirection === 'asc' ? comparison : -comparison;\n    });\n  }\n\n  /**\n   * Setup pagination controls\n   * @private\n   */\n  _setupPagination() {\n    const paginationDiv = this.querySelector('.ren-table-pagination');\n    if (!paginationDiv) return;\n\n    // Only render pagination if there are rows\n    if (this.#visibleRows.length === 0) {\n      paginationDiv.innerHTML = '';\n      return;\n    }\n\n    const totalPages = Math.ceil(this.#visibleRows.length / this.#pageSize);\n    if (totalPages <= 1) {\n      paginationDiv.innerHTML = '';\n      return;\n    }\n\n    const startItem = (this.#currentPage - 1) * this.#pageSize + 1;\n    const endItem = Math.min(this.#currentPage * this.#pageSize, this.#visibleRows.length);\n\n    paginationDiv.innerHTML = `\n      <div class=\"ren-table-pagination-info\">\n        <span>Showing <strong>${startItem}</strong> to <strong>${endItem}</strong> of <strong>${this.#visibleRows.length}</strong></span>\n      </div>\n      <div class=\"ren-table-pagination-controls\">\n        <button class=\"ren-table-pagination-button\" data-page-prev aria-label=\"Previous page\" ${this.#currentPage === 1 ? 'disabled' : ''}>\n          ← Prev\n        </button>\n        <span style=\"padding: 0 var(--space-2); font-size: var(--caption-size); color: var(--color-text-muted);\">\n          Page ${this.#currentPage} of ${totalPages}\n        </span>\n        <button class=\"ren-table-pagination-button\" data-page-next aria-label=\"Next page\" ${this.#currentPage === totalPages ? 'disabled' : ''}>\n          Next →\n        </button>\n      </div>\n    `;\n\n    // Reattach listeners\n    const prevBtn = paginationDiv.querySelector('[data-page-prev]');\n    const nextBtn = paginationDiv.querySelector('[data-page-next]');\n    const listenerOptions = this.#listenerController\n      ? { signal: this.#listenerController.signal }\n      : undefined;\n\n    if (prevBtn) prevBtn.addEventListener('click', () => this._previousPage(), listenerOptions);\n    if (nextBtn) nextBtn.addEventListener('click', () => this._nextPage(), listenerOptions);\n  }\n\n  /**\n   * Go to previous page\n   * @private\n   */\n  _previousPage() {\n    if (this.#currentPage > 1) {\n      this.#currentPage--;\n      this._renderTable();\n    }\n  }\n\n  /**\n   * Go to next page\n   * @private\n   */\n  _nextPage() {\n    const totalPages = Math.ceil(this.#visibleRows.length / this.#pageSize);\n    if (this.#currentPage < totalPages) {\n      this.#currentPage++;\n      this._renderTable();\n    }\n  }\n\n  /**\n   * Render the table with current page, sort, and filter\n   * @private\n   */\n  _renderTable() {\n    // Apply sorting\n    this._sortVisibleRows();\n\n    // Show/hide rows based on current page\n    const startIndex = (this.#currentPage - 1) * this.#pageSize;\n    const endIndex = startIndex + this.#pageSize;\n\n    const visibleIndexByRow = new Map(\n      this.#visibleRows.map((row, visibleIndex) => [row, visibleIndex])\n    );\n\n    this.#rows.forEach((row) => {\n      const visibleIndex = visibleIndexByRow.get(row);\n      const isVisible = visibleIndex !== undefined &&\n        visibleIndex >= startIndex &&\n        visibleIndex < endIndex;\n\n      if (isVisible) {\n        row.style.display = '';\n        row.setAttribute('role', 'row');\n      } else {\n        row.style.display = 'none';\n      }\n    });\n\n    // Update pagination\n    this._setupPagination();\n\n    // Show/hide empty state\n    this._updateEmptyState();\n  }\n\n  /**\n   * Update empty state display\n   * @private\n   */\n  _updateEmptyState() {\n    let emptyState = this.querySelector('.ren-table-empty');\n\n    if (this.#visibleRows.length === 0) {\n      if (!emptyState) {\n        emptyState = document.createElement('div');\n        emptyState.className = 'ren-table-empty';\n        emptyState.innerHTML = `\n          <div class=\"ren-table-empty-icon\">📭</div>\n          <div class=\"ren-table-empty-title\">No results</div>\n          <div class=\"ren-table-empty-description\">No data matches your search or filters</div>\n        `;\n        const wrapper = this.querySelector('.ren-table-wrapper');\n        if (wrapper) {\n          wrapper.parentNode.insertBefore(emptyState, wrapper);\n        }\n      }\n      if (this.#tbody) {\n        this.#tbody.style.display = 'none';\n      }\n    } else {\n      if (emptyState) {\n        emptyState.remove();\n      }\n      if (this.#tbody) {\n        this.#tbody.style.display = '';\n      }\n    }\n  }\n\n  /**\n   * Dispatch custom event\n   * @private\n   */\n  _dispatchEvent(eventName, detail) {\n    this.dispatchEvent(\n      new CustomEvent(eventName, {\n        detail,\n        bubbles: true,\n        composed: true,\n      })\n    );\n  }\n\n  /**\n   * Public API: Filter rows by column\n   * @param {number} columnIndex - Index of column to filter\n   * @param {string} value - Filter value\n   */\n  filter(columnIndex, value) {\n    this.#filterValues.set(columnIndex, value);\n\n    this.#visibleRows = this.#rows.filter((row) => {\n      for (const [colIdx, filterVal] of this.#filterValues) {\n        const cell = row.cells[colIdx];\n        if (!cell || !cell.textContent.toLowerCase().includes(filterVal.toLowerCase())) {\n          return false;\n        }\n      }\n      return true;\n    });\n\n    this.#currentPage = 1;\n    this._renderTable();\n  }\n\n  /**\n   * Public API: Get selected row IDs\n   * @returns {string[]} Array of selected row IDs\n   */\n  getSelectedRows() {\n    return Array.from(this.#selectedRows);\n  }\n\n  /**\n   * Public API: Set loading state\n   * @param {boolean} isLoading - Whether table is loading\n   */\n  setLoading(isLoading) {\n    if (isLoading) {\n      this.setAttribute('data-loading', '');\n      const wrapper = this.querySelector('.ren-table-wrapper');\n      if (wrapper && !wrapper.querySelector('.ren-table-loading-overlay')) {\n        const overlay = document.createElement('div');\n        overlay.className = 'ren-table-loading-overlay';\n        wrapper.appendChild(overlay);\n      }\n    } else {\n      this.removeAttribute('data-loading');\n      const overlay = this.querySelector('.ren-table-loading-overlay');\n      if (overlay) overlay.remove();\n    }\n  }\n\n  /**\n   * Public API: Set density variant\n   * @param {'default' | 'compact' | 'comfortable'} density - Density variant\n   */\n  setDensity(density) {\n    this.#table.classList.remove('ren-table-compact', 'ren-table-comfortable');\n    if (density === 'compact' || density === 'comfortable') {\n      this.#table.classList.add(`ren-table-${density}`);\n    }\n  }\n\n  /**\n   * Public API: Clear all selections\n   */\n  clearSelection() {\n    this.#selectedRows.clear();\n    this.#rowCheckboxes.forEach((checkbox) => {\n      checkbox.checked = false;\n    });\n    if (this.#headerCheckbox) {\n      this.#headerCheckbox.checked = false;\n      this.#headerCheckbox.indeterminate = false;\n    }\n    this.#rows.forEach((row) => {\n      row.removeAttribute('aria-selected');\n    });\n    this._dispatchEvent('ren-select', { selected: [] });\n  }\n\n  /**\n   * Public API: Refresh table data\n   */\n  refresh() {\n    this._refreshRows();\n    this.#currentPage = 1;\n    this._renderTable();\n  }\n}\n\ncustomElements.define('ren-table', RenTable);\n",
      "path": "components/patterns/ren-table/ren-table.js",
      "id": "file:components/patterns/ren-table/ren-table.js",
      "type": "javascript",
      "name": "ren-table.js"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Avatar\n   ============================================\n   Circular (or square) user image with fallback\n   text, status indicator and group (overlap).\n\n   Usage:\n     <div class=\"ren-avatar\"><img src=\"…\" alt=\"\"></div>\n     <div class=\"ren-avatar ren-avatar-lg\">JD</div>\n\n     <div class=\"ren-avatar-wrapper\">\n       <div class=\"ren-avatar\"><img …></div>\n       <span class=\"ren-avatar-status\"></span>\n     </div>\n\n     <div class=\"ren-avatar-group\">\n       <div class=\"ren-avatar\">…</div>\n       <div class=\"ren-avatar\">…</div>\n     </div>\n   ============================================ */\n\n.ren-avatar {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: var(--ren-avatar-size);     /* 40px default */\n  height: var(--ren-avatar-size);\n  border-radius: var(--ren-avatar-radius);\n  overflow: hidden;\n  background-color: var(--ren-avatar-bg);\n  color: var(--ren-avatar-color);\n  font-weight: var(--weight-semibold);\n  font-size: var(--ren-avatar-font-size);\n  flex-shrink: 0;\n  user-select: none;\n}\n\n.ren-avatar > img {\n  width: 100%;\n  height: 100%;\n  object-fit: cover;\n}\n\n/* ─── Sizes ─── */\n.ren-avatar-xs  { width: var(--avatar-xs);  height: var(--avatar-xs);  font-size: var(--text-xs); }\n.ren-avatar-sm  { width: var(--avatar-sm);  height: var(--avatar-sm);  font-size: var(--text-xs); }\n.ren-avatar-lg  { width: var(--avatar-lg);  height: var(--avatar-lg);  font-size: var(--text-base); }\n.ren-avatar-xl  { width: var(--avatar-xl);  height: var(--avatar-xl);  font-size: var(--text-lg); }\n.ren-avatar-2xl { width: var(--avatar-2xl); height: var(--avatar-2xl); font-size: var(--text-xl); }\n\n/* ─── Square variant ─── */\n.ren-avatar-square {\n  border-radius: var(--radius-md);\n}\n\n/* ─── Status indicator ─── */\n.ren-avatar-wrapper {\n  position: relative;\n  display: inline-block;\n}\n\n.ren-avatar-status {\n  position: absolute;\n  bottom: 0;\n  right: 0;\n  width: 0.75rem;\n  height: 0.75rem;\n  border-radius: var(--radius-full);\n  border: 2px solid var(--color-surface);\n  background-color: var(--color-success);\n}\n\n.ren-avatar-status-offline { background-color: var(--gray-400); }\n.ren-avatar-status-busy    { background-color: var(--color-danger); }\n.ren-avatar-status-away    { background-color: var(--color-warning); }\n\n/* ─── Group (overlapping) ─── */\n.ren-avatar-group {\n  display: flex;\n  flex-direction: row-reverse;\n}\n\n.ren-avatar-group > .ren-avatar {\n  border: var(--ren-avatar-border);\n  margin-inline-start: -0.5rem;\n}\n\n.ren-avatar-group > .ren-avatar:last-child {\n  margin-inline-start: 0;\n}\n",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "file:components/primitives/ren-avatar/ren-avatar.css",
      "type": "css",
      "name": "ren-avatar.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Badge\n   ============================================\n   Small inline label for status, counts, or tags.\n   CSS-only, no JS needed.\n\n   Sibling primitives that used to live here are\n   now in their own folders:\n     - ren-separator\n     - ren-avatar\n     - ren-spinner\n     - ren-skeleton\n     - ren-kbd\n\n   Usage:\n     <span class=\"ren-badge\">3</span>\n     <span class=\"ren-badge ren-badge-success\">Active</span>\n     <span class=\"ren-badge-dot ren-badge-dot-danger\"></span>\n   ============================================ */\n\n.ren-badge {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-1);\n  min-height: var(--ren-badge-height);\n  padding: 0.15em var(--ren-badge-padding-x);\n  font-size: var(--ren-badge-font-size);\n  font-weight: var(--ren-badge-font-weight);\n  line-height: 1.5;\n  white-space: nowrap;\n  border-radius: var(--ren-badge-radius);\n  /* Default: neutral */\n  background-color: var(--ren-badge-bg);\n  color: var(--ren-badge-color);\n}\n\n/* ─── Variants ─── */\n.ren-badge-primary {\n  background-color: var(--color-accent);\n  color: var(--color-on-accent);\n}\n\n.ren-badge-secondary {\n  background-color: var(--color-fill-hover);\n  color: var(--color-text);\n}\n\n.ren-badge-success {\n  background-color: var(--color-success-subtle);\n  color: var(--color-success-strong);\n}\n\n.ren-badge-warning {\n  background-color: var(--color-warning-subtle);\n  color: var(--color-warning-strong);\n}\n\n.ren-badge-danger {\n  background-color: var(--color-danger-subtle);\n  color: var(--color-danger-strong);\n}\n\n.ren-badge-info {\n  background-color: var(--color-info-subtle);\n  color: var(--color-info-strong);\n}\n\n/* Outline variant */\n.ren-badge-outline {\n  background-color: transparent;\n  border: var(--stroke-1) solid var(--color-border);\n  color: var(--color-text-secondary);\n}\n\n/* ─── Dot indicator (no text) ─── */\n.ren-badge-dot {\n  width: 0.5rem;\n  height: 0.5rem;\n  padding: 0;\n  border-radius: var(--radius-full);\n  background-color: var(--color-accent);\n}\n\n.ren-badge-dot-success { background-color: var(--color-success); }\n.ren-badge-dot-warning { background-color: var(--color-warning); }\n.ren-badge-dot-danger  { background-color: var(--color-danger); }\n",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "file:components/primitives/ren-badge/ren-badge.css",
      "type": "css",
      "name": "ren-badge.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Banner / Alert Component\n   ============================================\n   Inline messages for success, warning, error, info.\n   CSS-only. No JS needed (dismiss is optional JS).\n\n   Different from Toast: Banner is inline, persistent,\n   part of the page flow. Toast is ephemeral overlay.\n\n   Usage:\n     <div class=\"ren-banner\" role=\"status\">\n       <span class=\"ren-banner-icon\">ℹ</span>\n       <div class=\"ren-banner-content\">\n         <strong class=\"ren-banner-title\">Info</strong>\n         <p class=\"ren-banner-message\">Your changes have been saved.</p>\n       </div>\n     </div>\n\n     <div class=\"ren-banner ren-banner-danger\" role=\"alert\">\n       <span class=\"ren-banner-icon\">!</span>\n       <div class=\"ren-banner-content\">\n         <strong class=\"ren-banner-title\">Error</strong>\n         <p class=\"ren-banner-message\">Could not save changes.</p>\n       </div>\n       <button class=\"ren-banner-dismiss\" aria-label=\"Dismiss\">&times;</button>\n     </div>\n\n   Simple (no title):\n     <div class=\"ren-banner ren-banner-success\" role=\"status\">\n       <span class=\"ren-banner-icon\">✓</span>\n       <p class=\"ren-banner-message\">File uploaded successfully.</p>\n     </div>\n   ============================================ */\n\n/* ─── Base Banner ─── */\n.ren-banner {\n  display: flex;\n  align-items: flex-start;\n  gap: var(--ren-banner-gap);\n  padding: var(--ren-banner-padding);\n  border-radius: var(--ren-banner-radius);\n  font-size: var(--body-size);\n  line-height: var(--leading-normal);\n  /* Default: info */\n  background-color: var(--ren-banner-bg);\n  border: var(--ren-banner-border-width) solid var(--ren-banner-border-color);\n  color: var(--ren-banner-color);\n}\n\n/* ─── Icon ─── */\n.ren-banner-icon {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n  width: var(--ren-banner-icon-size);\n  height: var(--ren-banner-icon-size);\n  font-size: var(--text-base);\n  margin-top: 1px;\n}\n\n/* ─── Content ─── */\n.ren-banner-content {\n  flex: 1;\n  min-width: 0;\n}\n\n.ren-banner-title {\n  display: block;\n  font-weight: var(--weight-semibold);\n  font-size: var(--label-size, var(--text-sm));\n  margin: 0 0 var(--space-1);\n}\n\n.ren-banner-message {\n  font-size: var(--caption-size, var(--text-sm));\n  color: var(--color-text-secondary);\n  margin: 0;\n}\n\n/* Title-only (no message) */\n.ren-banner-title:last-child {\n  margin-bottom: 0;\n}\n\n/* ─── Actions (buttons inside banner) ─── */\n.ren-banner-actions {\n  display: flex;\n  gap: var(--space-2);\n  margin-top: var(--space-2);\n}\n\n/* ─── Dismiss button ─── */\n.ren-banner-dismiss {\n  flex-shrink: 0;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 1.5rem;\n  height: 1.5rem;\n  padding: 0;\n  border: none;\n  background: none;\n  color: var(--color-text-muted);\n  cursor: pointer;\n  border-radius: var(--radius-sm);\n  font-size: var(--text-lg);\n  line-height: 1;\n  transition: color var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-banner-dismiss:hover {\n  color: var(--color-text);\n}\n\n.ren-banner-dismiss:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: 2px;\n}\n\n/* ─── Variants ─── */\n\n.ren-banner-success {\n  background-color: var(--color-success-subtle);\n  border-color: color-mix(in oklch, var(--color-success), transparent 70%);\n}\n\n.ren-banner-warning {\n  background-color: var(--color-warning-subtle);\n  border-color: color-mix(in oklch, var(--color-warning), transparent 70%);\n}\n\n.ren-banner-danger {\n  background-color: var(--color-danger-subtle);\n  border-color: color-mix(in oklch, var(--color-danger), transparent 70%);\n}\n\n.ren-banner-neutral {\n  background-color: var(--color-surface-sunken);\n  border-color: var(--color-border);\n}\n\n/* ─── Compact variant (less padding, single line) ─── */\n.ren-banner-compact {\n  align-items: center;\n  padding: var(--space-2) var(--space-3);\n  gap: var(--space-2);\n}\n\n.ren-banner-compact .ren-banner-icon {\n  width: 1.25rem;\n  height: 1.25rem;\n  font-size: var(--text-sm);\n}\n\n/* ─── Full-width variant (no border-radius, for page top) ─── */\n.ren-banner-full {\n  border-radius: 0;\n  border-inline-start: none;\n  border-inline-end: none;\n}\n\n/* ─── Dismiss animation ─── */\n.ren-banner[data-dismissing] {\n  animation: ren-banner-dismiss var(--duration-state) var(--ease-enter) forwards;\n}\n\n@keyframes ren-banner-dismiss {\n  to {\n    opacity: 0;\n    height: 0;\n    padding-block: 0;\n    margin-block: 0;\n    overflow: hidden;\n  }\n}\n\n/* Reduced motion */\n@media (prefers-reduced-motion: reduce) {\n  .ren-banner[data-dismissing] {\n    animation: none;\n    display: none;\n  }\n}\n",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "file:components/primitives/ren-banner/ren-banner.css",
      "type": "css",
      "name": "ren-banner.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Breadcrumb Component\n   ============================================\n   Hierarchical navigation trail.\n   CSS-only. No JS needed.\n\n   Uses semantic <nav> + <ol> for accessibility.\n   Screen readers announce \"Breadcrumb\" navigation.\n\n   Usage:\n     <nav aria-label=\"Breadcrumb\" class=\"ren-breadcrumb\">\n       <ol>\n         <li><a href=\"/\">Home</a></li>\n         <li><a href=\"/products\">Products</a></li>\n         <li><a href=\"/products/shoes\" aria-current=\"page\">Shoes</a></li>\n       </ol>\n     </nav>\n\n   With custom separator:\n     <nav aria-label=\"Breadcrumb\" class=\"ren-breadcrumb\"\n          style=\"--breadcrumb-separator: '/'\">\n       ...\n     </nav>\n   ============================================ */\n\n.ren-breadcrumb > ol {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: var(--ren-breadcrumb-gap);\n  list-style: none;\n  padding: 0;\n  margin: 0;\n  font-size: var(--ren-breadcrumb-font-size);\n}\n\n.ren-breadcrumb > ol > li {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--ren-breadcrumb-gap);\n  color: var(--ren-breadcrumb-color);\n}\n\n/* Separator */\n.ren-breadcrumb > ol > li + li::before {\n  content: var(--ren-breadcrumb-separator);\n  color: var(--color-text-faint);\n  font-size: 1.1em;\n  margin-inline-end: var(--space-1);\n}\n\n/* Links */\n.ren-breadcrumb > ol a {\n  color: var(--ren-breadcrumb-color);\n  text-decoration: none;\n  padding: var(--space-1) 0;\n  border-radius: var(--radius-sm);\n  transition: color var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-breadcrumb > ol a:hover {\n  color: var(--color-text);\n  text-decoration: underline;\n  text-underline-offset: 0.15em;\n}\n\n.ren-breadcrumb > ol a:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: 2px;\n}\n\n/* Current page (last item) */\n.ren-breadcrumb [aria-current=\"page\"] {\n  color: var(--ren-breadcrumb-active-color);\n  font-weight: var(--weight-medium);\n  text-decoration: none;\n  pointer-events: none;\n}\n\n/* ─── Truncated (for deep paths) ─── */\n.ren-breadcrumb-truncated > ol > li:not(:first-child):not(:last-child):not(:nth-last-child(2)) {\n  display: none;\n}\n\n/* Show ellipsis in place of hidden items */\n.ren-breadcrumb-truncated > ol > li:nth-child(2)::after {\n  content: '…';\n  color: var(--color-text-faint);\n  padding: 0 var(--space-1);\n}\n",
      "path": "components/primitives/ren-breadcrumb/ren-breadcrumb.css",
      "id": "file:components/primitives/ren-breadcrumb/ren-breadcrumb.css",
      "type": "css",
      "name": "ren-breadcrumb.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Button Component\n   ============================================\n   Accessible, multi-variant button.\n   Works as pure CSS classes OR as <ren-button>.\n\n   Variants: primary, secondary, ghost, danger, outline\n   Sizes: sm, md (default), lg\n   States: hover, active, disabled, loading\n\n   Touch target: 44px minimum (Apple HIG)\n   ============================================ */\n\n/* ─── Base Button ─── */\n.ren-btn {\n  /* Layout */\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--ren-btn-gap);\n\n  /* Sizing — default md */\n  min-height: var(--ren-btn-height);        /* 44px touch target */\n  padding: var(--ren-btn-padding-y) var(--ren-btn-padding-x);\n  font-size: var(--ren-btn-font-size);\n  font-weight: var(--ren-btn-font-weight);\n  line-height: 1;\n\n  /* Appearance */\n  border: var(--ren-btn-border-width) solid var(--ren-btn-border-color);\n  border-radius: var(--ren-btn-radius);\n  cursor: pointer;\n  user-select: none;\n  white-space: nowrap;\n  text-decoration: none;\n\n  /* Transition — intent: tactile feedback on hover/press/focus */\n  transition:\n    background-color var(--ren-btn-duration) var(--ren-btn-easing),\n    border-color var(--ren-btn-duration) var(--ren-btn-easing),\n    color var(--ren-btn-duration) var(--ren-btn-easing),\n    box-shadow var(--ren-btn-duration) var(--ren-btn-easing),\n    transform var(--duration-tactile) var(--ease-enter),\n    opacity var(--ren-btn-duration) var(--ren-btn-easing);\n}\n\n/* Active press effect */\n.ren-btn:active:not(:disabled):not([aria-disabled=\"true\"]) {\n  transform: scale(0.97);\n}\n\n/* ─── Variants ─── */\n\n/* Primary (default) */\n.ren-btn:not(.ren-btn-secondary):not(.ren-btn-ghost):not(.ren-btn-outline):not(.ren-btn-danger):not(.ren-btn-link):not(.ren-btn-accent),\n.ren-btn-primary {\n  background-color: var(--ren-btn-bg);\n  color: var(--ren-btn-color);\n  border-color: var(--ren-btn-border-color);\n}\n\n.ren-btn:not(.ren-btn-secondary):not(.ren-btn-ghost):not(.ren-btn-outline):not(.ren-btn-danger):not(.ren-btn-link):not(.ren-btn-accent):hover:not(:disabled):not([aria-disabled=\"true\"]),\n.ren-btn-primary:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--ren-btn-bg-hover);\n  border-color: var(--ren-btn-bg-hover);\n}\n\n.ren-btn:not(.ren-btn-secondary):not(.ren-btn-ghost):not(.ren-btn-outline):not(.ren-btn-danger):not(.ren-btn-link):not(.ren-btn-accent):active:not(:disabled):not([aria-disabled=\"true\"]),\n.ren-btn-primary:active:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--ren-btn-bg-active);\n  border-color: var(--ren-btn-bg-active);\n}\n\n/* Secondary */\n.ren-btn-secondary {\n  background-color: var(--color-fill);\n  color: var(--color-text);\n  border-color: var(--color-border);\n}\n\n.ren-btn-secondary:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--color-fill-hover);\n}\n\n.ren-btn-secondary:active:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--color-fill-active);\n}\n\n/* Ghost (no background) */\n.ren-btn-ghost {\n  background-color: transparent;\n  color: var(--color-text);\n  border-color: transparent;\n}\n\n.ren-btn-ghost:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--color-fill);\n}\n\n.ren-btn-ghost:active:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--color-fill-hover);\n}\n\n/* Outline */\n.ren-btn-outline {\n  background-color: transparent;\n  color: var(--color-accent);\n  border-color: var(--color-accent);\n}\n\n.ren-btn-outline:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--color-accent-subtle);\n}\n\n.ren-btn-outline:active:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--color-accent);\n  color: var(--color-on-accent);\n}\n\n/* Danger */\n.ren-btn-danger {\n  background-color: var(--color-danger);\n  color: var(--color-on-danger);\n  border-color: var(--color-danger);\n}\n\n.ren-btn-danger:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--red-500);\n  border-color: var(--red-500);\n}\n\n.ren-btn-danger:active:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--red-600);\n  border-color: var(--red-600);\n}\n\n/* Link style */\n.ren-btn-link {\n  background-color: transparent;\n  color: var(--color-text-link);\n  border-color: transparent;\n  padding-inline: var(--space-1);\n  min-height: auto;\n  text-decoration: underline;\n  text-underline-offset: 0.15em;\n}\n\n.ren-btn-link:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n  color: var(--color-text-link-hover);\n  text-decoration-thickness: 2px;\n}\n\n/* ─── Sizes ─── */\n\n.ren-btn-sm {\n  min-height: var(--size-sm);       /* 32px */\n  padding: var(--space-1) var(--space-3);\n  font-size: var(--label-size);\n  border-radius: var(--radius-sm);\n  gap: var(--space-1);\n}\n\n.ren-btn-lg {\n  min-height: var(--size-xl);       /* 52px */\n  padding: var(--space-3) var(--space-6);\n  font-size: var(--body-size);\n  border-radius: var(--radius-lg);\n}\n\n/* ─── Icon Button (square) ─── */\n.ren-btn-icon {\n  min-width: var(--size-lg);        /* 44px */\n  padding: 0;\n  aspect-ratio: 1;\n}\n\n.ren-btn-icon.ren-btn-sm {\n  min-width: var(--size-sm);\n  min-height: var(--size-sm);\n}\n\n.ren-btn-icon.ren-btn-lg {\n  min-width: var(--size-xl);\n  min-height: var(--size-xl);\n}\n\n/* ─── Full Width ─── */\n.ren-btn-full {\n  width: 100%;\n}\n\n/* ─── Button Group ─── */\n.ren-btn-group {\n  display: inline-flex;\n  isolation: isolate;  /* contain z-index stacking */\n}\n\n.ren-btn-group > .ren-btn {\n  border-radius: 0;\n  position: relative;\n}\n\n.ren-btn-group > .ren-btn:first-child {\n  border-start-start-radius: var(--radius-md);\n  border-end-start-radius: var(--radius-md);\n}\n\n.ren-btn-group > .ren-btn:last-child {\n  border-start-end-radius: var(--radius-md);\n  border-end-end-radius: var(--radius-md);\n}\n\n.ren-btn-group > .ren-btn + .ren-btn {\n  margin-inline-start: calc(var(--stroke-1) * -1);\n}\n\n/* Hovered/focused button rises above siblings so border isn't hidden */\n.ren-btn-group > .ren-btn:hover,\n.ren-btn-group > .ren-btn:focus-visible {\n  z-index: 1;\n}\n\n/* ─── States ─── */\n\n/* Disabled */\n.ren-btn:disabled,\n.ren-btn[aria-disabled=\"true\"] {\n  background-color: var(--color-disabled-bg);\n  color: var(--color-disabled-text);\n  border-color: var(--color-disabled-bg);\n  cursor: not-allowed;\n  transform: none;\n  box-shadow: none;\n}\n\n/* Loading */\n.ren-btn[data-loading] {\n  position: relative;\n  color: transparent !important;\n  pointer-events: none;\n}\n\n.ren-btn[data-loading]::after {\n  content: '';\n  position: absolute;\n  width: 1.2em;\n  height: 1.2em;\n  border: 2px solid currentColor;\n  border-inline-end-color: transparent;\n  border-radius: var(--radius-full);\n  animation: ren-btn-spin 600ms linear infinite;\n  /* Restore color since parent is transparent */\n  color: var(--color-on-accent);\n}\n\n.ren-btn-secondary[data-loading]::after,\n.ren-btn-ghost[data-loading]::after,\n.ren-btn-outline[data-loading]::after {\n  color: var(--color-text);\n}\n\n@keyframes ren-btn-spin {\n  to { transform: rotate(360deg); }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ren-btn[data-loading]::after {\n    animation-duration: 1.5s;\n  }\n  .ren-btn:active:not(:disabled):not([aria-disabled=\"true\"]) {\n    transform: none;\n  }\n}\n\n/* ─── Custom Accent (color-mix pattern) ───\n   Use a single --btn-accent color and derive hover/active automatically.\n   Example: <button class=\"ren-btn ren-btn-accent\" style=\"--btn-accent: #8B5CF6\">Purple</button>\n*/\n.ren-btn-accent {\n  --_hover: color-mix(in oklch, var(--btn-accent, var(--color-accent)), black 12%);\n  --_active: color-mix(in oklch, var(--btn-accent, var(--color-accent)), black 22%);\n  --_subtle: color-mix(in oklch, var(--btn-accent, var(--color-accent)), white 90%);\n\n  background-color: var(--btn-accent, var(--color-accent));\n  color: var(--color-on-accent);\n  border-color: var(--btn-accent, var(--color-accent));\n}\n\n.ren-btn-accent:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--_hover);\n  border-color: var(--_hover);\n}\n\n.ren-btn-accent:active:not(:disabled):not([aria-disabled=\"true\"]) {\n  background-color: var(--_active);\n  border-color: var(--_active);\n}\n\n/* ─── Focus ─── */\n.ren-btn:focus-visible {\n  outline: var(--ren-btn-ring-width) solid var(--ren-btn-ring-color);\n  outline-offset: var(--ren-btn-ring-offset);\n}\n",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "file:components/primitives/ren-button/ren-button.css",
      "type": "css",
      "name": "ren-button.css"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — <ren-button> Web Component\n * ====================================\n * Enhanced button with loading state, variants, and\n * proper accessibility out of the box.\n *\n * Uses Light DOM — no Shadow DOM needed for a button.\n * The CSS classes work standalone too.\n *\n * Attributes:\n *   variant:  primary | secondary | ghost | outline | danger | link\n *   size:     sm | md | lg\n *   loading:  boolean — shows spinner, disables interaction\n *   disabled: boolean — standard disabled state\n *   icon:     boolean — square icon button\n *   full:     boolean — full width\n *\n * Usage:\n *   <ren-button>Click me</ren-button>\n *   <ren-button variant=\"secondary\" size=\"sm\">Cancel</ren-button>\n *   <ren-button variant=\"danger\" loading>Deleting...</ren-button>\n *   <ren-button icon aria-label=\"Close\">✕</ren-button>\n */\n\nconst VARIANT_MAP = {\n  primary: 'ren-btn',\n  secondary: 'ren-btn ren-btn-secondary',\n  ghost: 'ren-btn ren-btn-ghost',\n  outline: 'ren-btn ren-btn-outline',\n  danger: 'ren-btn ren-btn-danger',\n  link: 'ren-btn ren-btn-link',\n};\n\nconst SIZE_MAP = {\n  sm: 'ren-btn-sm',\n  md: '',\n  lg: 'ren-btn-lg',\n};\n\nexport class RenButton extends HTMLElement {\n  static get observedAttributes() {\n    return ['variant', 'size', 'loading', 'disabled', 'icon', 'full'];\n  }\n\n  constructor() {\n    super();\n    this._button = null;\n  }\n\n  connectedCallback() {\n    // If this element wraps a <button>, enhance it\n    // If not, create one\n    this._button = this.querySelector('button, a, [role=\"button\"]');\n\n    if (!this._button) {\n      // Wrap content in a button\n      const button = document.createElement('button');\n      button.setAttribute('type', 'button');\n\n      // Move children into the button\n      while (this.firstChild) {\n        button.appendChild(this.firstChild);\n      }\n      this.appendChild(button);\n      this._button = button;\n    }\n\n    this._updateClasses();\n    this._updateState();\n  }\n\n  attributeChangedCallback() {\n    if (this._button) {\n      this._updateClasses();\n      this._updateState();\n    }\n  }\n\n  _updateClasses() {\n    if (!this._button) return;\n\n    const variant = this.getAttribute('variant') || 'primary';\n    const size = this.getAttribute('size') || 'md';\n\n    const classes = [\n      VARIANT_MAP[variant] || VARIANT_MAP.primary,\n      SIZE_MAP[size] || '',\n      this.hasAttribute('icon') ? 'ren-btn-icon' : '',\n      this.hasAttribute('full') ? 'ren-btn-full' : '',\n    ]\n      .filter(Boolean)\n      .join(' ');\n\n    this._button.className = classes;\n  }\n\n  _updateState() {\n    if (!this._button) return;\n\n    // Loading\n    if (this.hasAttribute('loading')) {\n      this._button.setAttribute('data-loading', '');\n      this._button.setAttribute('aria-busy', 'true');\n    } else {\n      this._button.removeAttribute('data-loading');\n      this._button.removeAttribute('aria-busy');\n    }\n\n    // Disabled\n    if (this.hasAttribute('disabled')) {\n      this._button.setAttribute('disabled', '');\n      this._button.setAttribute('aria-disabled', 'true');\n    } else {\n      this._button.removeAttribute('disabled');\n      this._button.removeAttribute('aria-disabled');\n    }\n  }\n\n  // ─── Programmatic API ───\n\n  get loading() {\n    return this.hasAttribute('loading');\n  }\n\n  set loading(val) {\n    if (val) {\n      this.setAttribute('loading', '');\n    } else {\n      this.removeAttribute('loading');\n    }\n  }\n\n  get disabled() {\n    return this.hasAttribute('disabled');\n  }\n\n  set disabled(val) {\n    if (val) {\n      this.setAttribute('disabled', '');\n    } else {\n      this.removeAttribute('disabled');\n    }\n  }\n\n  get variant() {\n    return this.getAttribute('variant') || 'primary';\n  }\n\n  set variant(val) {\n    this.setAttribute('variant', val);\n  }\n}\n\n// Register\nif (!customElements.get('ren-button')) {\n  customElements.define('ren-button', RenButton);\n}\n",
      "path": "components/primitives/ren-button/ren-button.js",
      "id": "file:components/primitives/ren-button/ren-button.js",
      "type": "javascript",
      "name": "ren-button.js"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Card Component\n   ============================================\n   Versatile container with header, body, footer.\n   CSS-only. No JS needed.\n\n   Constraint-driven: limited variants on purpose\n   to ensure consistent UI across projects.\n\n   Usage:\n     <div class=\"ren-card\">\n       <div class=\"ren-card-header\">\n         <h3 class=\"ren-card-title\">Title</h3>\n         <p class=\"ren-card-description\">Description here</p>\n       </div>\n       <div class=\"ren-card-body\">\n         Content\n       </div>\n       <div class=\"ren-card-footer\">\n         <button class=\"ren-btn\">Action</button>\n       </div>\n     </div>\n\n   Minimal:\n     <div class=\"ren-card\">\n       <p>Just content, no sections needed.</p>\n     </div>\n   ============================================ */\n\n/* ─── Base Card ─── */\n.ren-card {\n  background-color: var(--ren-card-bg);\n  border: var(--ren-card-border-width) solid var(--ren-card-border-color);\n  border-radius: var(--ren-card-radius);\n  box-shadow: var(--ren-card-shadow);\n  overflow: hidden;\n}\n\n/* ─── Sections ─── */\n\n.ren-card-header {\n  display: flex;\n  flex-direction: column;\n  gap: var(--ren-card-header-gap);\n  padding: var(--ren-card-padding) var(--ren-card-padding) 0;\n}\n\n/* Title and description are typically applied to <h2>/<h3>/<h4> and <p>\n   respectively, which carry browser-default margins that fight the header's\n   `gap`. Reset them here so .ren-card-header's flex gap is the actual visual\n   gap, regardless of the underlying tag. */\n.ren-card-title {\n  font-size: var(--title-sm-size, var(--text-lg));\n  font-weight: var(--weight-semibold);\n  color: var(--color-text);\n  line-height: var(--leading-snug);\n  margin: 0;\n}\n\n.ren-card-description {\n  font-size: var(--caption-size, var(--text-sm));\n  color: var(--color-text-muted);\n  line-height: var(--leading-normal);\n  margin: 0;\n}\n\n.ren-card-body {\n  padding: var(--ren-card-padding);\n}\n\n/* Remove top padding when body follows header */\n.ren-card-header + .ren-card-body {\n  padding-top: var(--space-3);\n}\n\n/* Optional spacing between documented card sections without forcing a new\n   flex/grid layout on the card root. */\n.ren-card > :is(.ren-card-header, .ren-card-body, .ren-card-footer, .ren-card-footer-border)\n  + :is(.ren-card-header, .ren-card-body, .ren-card-footer, .ren-card-footer-border) {\n  margin-block-start: var(--ren-card-gap);\n}\n\n.ren-card-footer {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  padding: 0 var(--ren-card-padding) var(--ren-card-padding);\n}\n\n/* Footer at bottom with border (for action-heavy cards) */\n.ren-card-footer-border {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  padding: var(--space-3) var(--ren-card-padding);\n  border-top: var(--stroke-1) solid var(--color-border-muted);\n}\n\n/* When the bordered footer follows the header directly (no body in between),\n   tighten the top padding so the divider line sits visually close to the\n   description, not floating ~17px below it (which is what you get from the\n   default --space-3 padding plus line-height extra of the description text). */\n.ren-card-header + .ren-card-footer-border {\n  padding-top: var(--space-2);\n}\n\n/* When card has no sections, just padding */\n.ren-card > :first-child:not(.ren-card-header):not(.ren-card-body):not(.ren-card-footer) {\n  padding-top: var(--ren-card-padding);\n}\n\n.ren-card > :last-child:not(.ren-card-header):not(.ren-card-body):not(.ren-card-footer) {\n  padding-bottom: var(--ren-card-padding);\n}\n\n/* Simple card (no sections) */\n.ren-card-simple {\n  padding: var(--ren-card-padding);\n}\n\n/* ─── Variants ─── */\n\n/* Elevated — more shadow, no border */\n.ren-card-elevated {\n  border-color: transparent;\n  box-shadow: var(--shadow-md);\n}\n\n/* Outline — emphasis border, no background difference */\n.ren-card-outline {\n  background-color: transparent;\n}\n\n/* Ghost — no border, no bg (content grouping) */\n.ren-card-ghost {\n  background-color: transparent;\n  border: none;\n}\n\n/* Sunken — recessed appearance */\n.ren-card-sunken {\n  background-color: var(--color-surface-sunken);\n  border-color: transparent;\n}\n\n/* ─── Interactive Card ─── */\n\n.ren-card-interactive {\n  cursor: pointer;\n  transition:\n    box-shadow var(--duration-state) var(--ease-enter),\n    border-color var(--duration-state) var(--ease-enter),\n    transform var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-card-interactive:hover {\n  border-color: var(--color-border-strong);\n  box-shadow: var(--shadow-md);\n}\n\n.ren-card-interactive:active {\n  transform: scale(0.99);\n}\n\n.ren-card-interactive:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: var(--ring-offset-width);\n}\n\n/* ─── Selectable Card ─── */\n\n.ren-card-selectable {\n  cursor: pointer;\n  transition:\n    border-color var(--duration-state) var(--ease-enter),\n    box-shadow var(--duration-state) var(--ease-enter);\n}\n\n.ren-card-selectable:hover {\n  border-color: var(--color-accent);\n}\n\n.ren-card-selectable[aria-selected=\"true\"],\n.ren-card-selectable[data-selected] {\n  border-color: var(--color-accent);\n  box-shadow: 0 0 0 1px var(--color-accent);\n}\n\n/* ─── Status accent (top border) ─── */\n\n.ren-card[data-status=\"success\"] { border-top: 3px solid var(--color-success); }\n.ren-card[data-status=\"warning\"] { border-top: 3px solid var(--color-warning); }\n.ren-card[data-status=\"danger\"]  { border-top: 3px solid var(--color-danger); }\n.ren-card[data-status=\"info\"]    { border-top: 3px solid var(--color-info); }\n\n/* ─── Card with cover image ─── */\n\n.ren-card-cover {\n  width: 100%;\n  height: auto;\n  object-fit: cover;\n  display: block;\n}\n\n/* ─── Card Group (grid of cards) ─── */\n\n.ren-card-group {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));\n  gap: var(--space-4);\n}\n",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "file:components/primitives/ren-card/ren-card.css",
      "type": "css",
      "name": "ren-card.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Checkbox Component\n   ============================================\n   Custom-styled checkbox built on native\n   <input type=\"checkbox\">.\n\n   Preserves native semantics and accessibility.\n   Custom visuals via CSS only.\n\n   Usage:\n     <label class=\"ren-checkbox\">\n       <input type=\"checkbox\">\n       <span class=\"ren-checkbox-control\"></span>\n       <span>Accept terms</span>\n     </label>\n\n   For toggle switches, see ren-switch.\n   ============================================ */\n\n.ren-checkbox {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--ren-checkbox-gap);\n  cursor: pointer;\n  font-size: var(--body-size);\n  font-weight: var(--weight-regular);\n  color: var(--color-text);\n  user-select: none;\n  /* Ensure touch target */\n  min-height: var(--touch-min);\n}\n\n/* Hide native checkbox visually but keep accessible */\n.ren-checkbox > input[type=\"checkbox\"] {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip-path: inset(50%);\n  white-space: nowrap;\n  border-width: 0;\n}\n\n/* Custom checkbox visual */\n.ren-checkbox-control {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: var(--ren-checkbox-size);   /* 20px */\n  height: var(--ren-checkbox-size);\n  flex-shrink: 0;\n  border: 2px solid var(--ren-checkbox-border);\n  border-radius: var(--ren-checkbox-radius);\n  background-color: transparent;\n  transition:\n    background-color var(--ren-checkbox-duration) var(--ease-enter),\n    border-color var(--ren-checkbox-duration) var(--ease-enter),\n    transform var(--duration-tactile) var(--ren-checkbox-easing);\n}\n\n/* Checkmark (hidden by default) */\n.ren-checkbox-control::after {\n  content: '';\n  display: block;\n  width: 0.45rem;\n  height: 0.7rem;\n  border: solid var(--ren-checkbox-checked-color);\n  border-width: 0 2px 2px 0;\n  transform: rotate(45deg) scale(0);\n  transition: transform var(--ren-checkbox-duration) var(--ren-checkbox-easing);\n  margin-top: -2px;\n}\n\n/* ─── States ─── */\n\n/* Checked */\n.ren-checkbox > input:checked + .ren-checkbox-control {\n  background-color: var(--ren-checkbox-checked-bg);\n  border-color: var(--ren-checkbox-checked-bg);\n}\n\n.ren-checkbox > input:checked + .ren-checkbox-control::after {\n  transform: rotate(45deg) scale(1);\n}\n\n/* Hover */\n.ren-checkbox:hover > .ren-checkbox-control {\n  border-color: var(--color-accent);\n}\n\n.ren-checkbox:hover > input:checked + .ren-checkbox-control {\n  background-color: var(--color-accent-hover);\n  border-color: var(--color-accent-hover);\n}\n\n/* Focus */\n.ren-checkbox > input:focus-visible + .ren-checkbox-control {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: var(--ring-offset-width);\n}\n\n/* Active / Press */\n.ren-checkbox:active > .ren-checkbox-control {\n  transform: scale(0.9);\n}\n\n/* Disabled */\n.ren-checkbox:has(input:disabled) {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n\n/* Indeterminate */\n.ren-checkbox > input:indeterminate + .ren-checkbox-control {\n  background-color: var(--ren-checkbox-checked-bg);\n  border-color: var(--ren-checkbox-checked-bg);\n}\n\n.ren-checkbox > input:indeterminate + .ren-checkbox-control::after {\n  border: none;\n  width: 0.6rem;\n  height: 2px;\n  background-color: var(--ren-checkbox-checked-color);\n  transform: none;\n  margin: 0;\n  border-radius: 1px;\n}\n",
      "path": "components/primitives/ren-checkbox/ren-checkbox.css",
      "id": "file:components/primitives/ren-checkbox/ren-checkbox.css",
      "type": "css",
      "name": "ren-checkbox.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Field Component (Input + Label + Error)\n   ============================================\n   Wraps any form control with label, description,\n   and error message — all properly wired with ARIA.\n\n   Works as pure CSS classes OR as <ren-field>.\n\n   Structure:\n   .ren-field\n     .ren-field-label       (label element)\n     .ren-field-control     (input/select/textarea wrapper)\n     .ren-field-description (helper text)\n     .ren-field-error       (error message)\n   ============================================ */\n\n/* ─── Field Container ─── */\n.ren-field {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-1);\n}\n\n/* ─── Label ─── */\n.ren-field-label {\n  display: flex;\n  align-items: center;\n  gap: var(--space-1);\n  font-size: var(--label-size);\n  font-weight: var(--label-weight);\n  color: var(--color-text);\n}\n\n/* Required indicator */\n.ren-field-label[data-required]::after {\n  content: '*';\n  color: var(--color-danger);\n  font-weight: var(--weight-bold);\n}\n\n/* ─── Input / Control ─── */\n.ren-input {\n  display: block;\n  width: 100%;\n  min-height: var(--ren-field-height);\n  padding: var(--ren-field-padding-y) var(--ren-field-padding-x);\n  font-size: var(--ren-field-font-size);\n  font-family: inherit;\n  color: var(--ren-field-color);\n  background-color: var(--ren-field-bg);\n  border: var(--ren-field-border-width) solid var(--ren-field-border-color);\n  border-radius: var(--ren-field-radius);\n  transition:\n    border-color var(--duration-state) var(--ease-enter),\n    background-color var(--duration-state) var(--ease-enter),\n    box-shadow var(--duration-state) var(--ease-enter);\n}\n\n.ren-input:hover:not(:disabled):not(:focus) {\n  background-color: var(--color-input-bg-hover);\n}\n\n.ren-input:focus {\n  outline: none;\n  border-color: var(--ren-field-focus-color);\n  box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n  background-color: var(--color-input-bg-hover);\n}\n\n.ren-input:disabled {\n  background-color: var(--color-input-disabled-bg);\n  color: var(--color-input-disabled-text);\n  cursor: not-allowed;\n  opacity: 0.7;\n}\n\n.ren-input::placeholder {\n  color: var(--ren-field-placeholder);\n  opacity: 1;\n}\n\n/* ─── Textarea ─── */\ntextarea.ren-input {\n  field-sizing: content;  /* Auto-grow with content — no JS needed */\n  min-height: calc(var(--touch-min) * 2);\n  max-height: 20rem;      /* Prevent runaway growth */\n  resize: vertical;\n}\n\n/* ─── Select ─── */\nselect.ren-input {\n  appearance: none;\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%238E8E93' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n  background-repeat: no-repeat;\n  background-position: right var(--space-3) center;\n  padding-inline-end: var(--space-10);\n}\n\n@supports (appearance: base-select) {\n  /* ── Opt-in ── */\n  select.ren-input,\n  select.ren-input::picker(select) {\n    appearance: base-select;\n  }\n\n  /* ── Trigger ── */\n  select.ren-input {\n    display: flex;\n    align-items: center;\n    background-image: none;\n    padding-inline-end: var(--space-3);\n  }\n\n  select.ren-input::picker-icon {\n    color: var(--color-text-muted);\n    opacity: 0.6;\n    margin-inline-start: auto;\n    padding-inline-start: var(--space-3);\n    transition: rotate var(--duration-tactile) var(--ease-enter);\n  }\n\n  select.ren-input:open::picker-icon {\n    rotate: 180deg;\n  }\n\n  select.ren-input option::checkmark {\n    display: none;\n  }\n\n  /* ── Dropdown ── */\n  select.ren-input::picker(select) {\n    background: var(--color-surface-overlay);\n    border: 1px solid var(--color-border);\n    border-radius: var(--radius-lg);\n    box-shadow: 0 4px 20px rgb(0, 0, 0, 0.08),\n                0 1px 3px rgb(0, 0, 0, 0.06);\n    padding: var(--space-1);\n    margin-block-start: var(--space-1);\n    overflow: clip;\n  }\n\n  /* ── Options ── */\n  select.ren-input option {\n    display: flex;\n    align-items: center;\n    padding: var(--space-2) var(--space-3);\n    border-radius: 4px;\n    color: var(--color-text);\n    background: transparent;\n    cursor: pointer;\n  }\n\n  select.ren-input option + option {\n    margin-block-start: 1px;\n  }\n\n  select.ren-input option:hover {\n    background: light-dark(var(--gray-50), var(--gray-800));\n  }\n\n  select.ren-input option:focus-visible,\n  select.ren-input option:active {\n    outline: 2px solid var(--color-accent);\n    outline-offset: -2px;\n    background: light-dark(var(--gray-50), var(--gray-800));\n  }\n\n  select.ren-input option:checked:not(:disabled) {\n    background: light-dark(var(--blue-50), var(--blue-900));\n    color: light-dark(var(--blue-700), var(--blue-200));\n    font-weight: var(--weight-medium);\n  }\n\n  select.ren-input option:checked:not(:disabled)::after {\n    content: \"✓\";\n    margin-inline-start: auto;\n    color: light-dark(var(--blue-600), var(--blue-300));\n    font-weight: var(--weight-bold);\n  }\n\n  select.ren-input option:checked:hover {\n    background: light-dark(var(--blue-100), var(--blue-800));\n  }\n\n  select.ren-input option:disabled {\n    color: light-dark(var(--gray-500), var(--gray-400));\n    cursor: not-allowed;\n  }\n\n  select.ren-input option[hidden] {\n    display: none;\n  }\n\n  /* ── Optgroups ── */\n  select.ren-input optgroup {\n    padding: var(--space-1) 0;\n    padding-inline-start: 0;\n  }\n\n  select.ren-input optgroup:not(:first-of-type) {\n    border-block-start: 1px solid var(--color-border);\n    margin-block-start: var(--space-2);\n    padding-block-start: var(--space-2);\n  }\n\n  select.ren-input optgroup > legend,\n  select.ren-input optgroup > label {\n    padding: var(--space-1) var(--space-3);\n    margin-inline-start: var(--space-3);\n  }\n\n  /* ── Open state ── */\n  select.ren-input:open {\n    border-color: var(--color-input-border-focus);\n    box-shadow: 0 0 0 3px var(--color-input-focus-ring);\n  }\n}\n\n/* ─── Input Sizes ─── */\n.ren-input-sm {\n  min-height: var(--size-sm);\n  padding: var(--space-1) var(--space-2);\n  font-size: var(--text-sm);\n  border-radius: var(--radius-sm);\n}\n\n.ren-input-lg {\n  min-height: var(--size-xl);\n  padding: var(--space-3) var(--space-4);\n  font-size: var(--text-lg);\n}\n\n/* ─── Input with icon ─── */\n.ren-input-wrapper {\n  position: relative;\n  display: flex;\n  align-items: center;\n}\n\n.ren-input-icon {\n  position: absolute;\n  left: var(--space-3);\n  color: var(--color-text-muted);\n  pointer-events: none;\n  display: flex;\n}\n\n.ren-input-icon + .ren-input,\n.ren-input-wrapper > .ren-input:has(+ .ren-input-icon) {\n  padding-inline-start: calc(var(--space-3) + var(--icon-lg) + var(--space-2));\n}\n\n.ren-input-icon-end {\n  position: absolute;\n  right: var(--space-3);\n  color: var(--color-text-muted);\n  pointer-events: none;\n  display: flex;\n}\n\n/* ─── Description ─── */\n.ren-field-description {\n  font-size: var(--caption-size);\n  color: var(--color-text-muted);\n  line-height: var(--leading-normal);\n  margin: 0;\n}\n\n/* ─── Error Message ─── */\n.ren-field-error {\n  font-size: var(--caption-size);\n  color: var(--ren-field-error-color);\n  line-height: var(--leading-normal);\n  margin: 0;\n  display: flex;\n  align-items: center;\n  gap: var(--space-1);\n}\n\n/* Error state on input */\n.ren-field[data-invalid] .ren-input,\n.ren-input[aria-invalid=\"true\"],\n.ren-input-error {\n  border-color: var(--ren-field-error-color);\n}\n\n.ren-field[data-invalid] .ren-input:focus,\n.ren-input[aria-invalid=\"true\"]:focus,\n.ren-input-error:focus {\n  box-shadow: 0 0 0 3px rgb(255, 59, 48, 0.15);\n  border-color: var(--ren-field-error-color);\n}\n\n/* Error state on label */\n.ren-field[data-invalid] .ren-field-label {\n  color: var(--ren-field-error-color);\n}\n\n/* ─── Success state ─── */\n.ren-field[data-valid] .ren-input,\n.ren-input-success {\n  border-color: var(--ren-field-success-color);\n}\n\n.ren-field[data-valid] .ren-input:focus,\n.ren-input-success:focus {\n  box-shadow: 0 0 0 3px rgb(52, 199, 89, 0.15);\n}\n\n/* ─── Modern Validation (no JS needed) ─── */\n/* Only show validation state AFTER user interaction */\n.ren-input:user-invalid {\n  border-color: var(--ren-field-error-color);\n}\n\n.ren-input:user-invalid:focus {\n  box-shadow: 0 0 0 3px rgb(255, 59, 48, 0.15);\n  border-color: var(--ren-field-error-color);\n}\n\n.ren-input:user-valid {\n  border-color: var(--ren-field-success-color);\n}\n\n.ren-input:user-valid:focus {\n  box-shadow: 0 0 0 3px rgb(52, 199, 89, 0.15);\n}\n\n/* Show/hide error and success messages based on validation */\n.ren-field:has(.ren-input:user-invalid) .ren-field-label {\n  color: var(--ren-field-error-color);\n}\n\n.ren-field:has(.ren-input:user-invalid) .ren-field-error {\n  display: flex;\n}\n\n.ren-field:has(.ren-input:user-valid) .ren-field-error {\n  display: none;\n}\n",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "file:components/primitives/ren-field/ren-field.css",
      "type": "css",
      "name": "ren-field.css"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — <ren-field> Web Component\n * ===================================\n * Auto-wires ARIA relationships between label, input,\n * description, and error message.\n *\n * Light DOM — no Shadow DOM. Just enhances existing HTML.\n *\n * Usage:\n *   <ren-field>\n *     <label>Email</label>\n *     <input type=\"email\" placeholder=\"name@example.com\">\n *     <span data-description>We'll never share your email.</span>\n *     <span data-error>Please enter a valid email.</span>\n *   </ren-field>\n *\n * The component automatically:\n * - Generates IDs and wires aria-labelledby\n * - Wires aria-describedby to description\n * - Wires aria-errormessage to error\n * - Sets aria-invalid when error is visible\n * - Adds data-required to label when input is required\n * - Adds .ren-field, .ren-field-label, .ren-input, etc. classes\n */\n\nimport { autoId } from '../../../utils/id-generator.js';\n\nexport class RenField extends HTMLElement {\n  connectedCallback() {\n    this._listenerController?.abort();\n    this._listenerController = new AbortController();\n    const { signal } = this._listenerController;\n    // Add base class\n    this.classList.add('ren-field');\n\n    // Find child elements\n    const label = this.querySelector('label');\n    const input = this.querySelector('input, select, textarea');\n    const description = this.querySelector('[data-description]');\n    const error = this.querySelector('[data-error]');\n\n    if (!input) return;\n\n    // ─── Add classes ───\n    label?.classList.add('ren-field-label');\n    input.classList.add('ren-input');\n    description?.classList.add('ren-field-description');\n    error?.classList.add('ren-field-error');\n\n    // ─── Generate IDs ───\n    const inputId = autoId(input, 'input');\n\n    // Wire label → input (using for attribute)\n    if (label && !label.getAttribute('for')) {\n      label.setAttribute('for', inputId);\n    }\n\n    // Wire description → input (aria-describedby)\n    if (description) {\n      const descId = autoId(description, 'desc');\n      const existing = input.getAttribute('aria-describedby');\n      if (!existing?.includes(descId)) {\n        input.setAttribute(\n          'aria-describedby',\n          existing ? `${existing} ${descId}` : descId\n        );\n      }\n    }\n\n    // Wire error → input (aria-errormessage)\n    if (error) {\n      const errorId = autoId(error, 'error');\n      input.setAttribute('aria-errormessage', errorId);\n      error.setAttribute('role', 'alert');\n      error.setAttribute('aria-live', 'polite');\n\n      // Initially hide error if not invalid\n      if (!this.hasAttribute('data-invalid')) {\n        error.hidden = true;\n      }\n    }\n\n    // ─── Required indicator ───\n    if (input.hasAttribute('required') && label) {\n      label.setAttribute('data-required', '');\n    }\n\n    // ─── Observe validity changes ───\n    this._input = input;\n    this._error = error;\n\n    // Listen for custom validation\n    input.addEventListener('invalid', () => this._setInvalid(true), { signal });\n    input.addEventListener('input', () => {\n      if (this.hasAttribute('data-invalid') && input.validity.valid) {\n        this._setInvalid(false);\n      }\n    }, { signal });\n  }\n\n  disconnectedCallback() {\n    this._listenerController?.abort();\n    this._listenerController = null;\n  }\n\n  _setInvalid(invalid) {\n    if (invalid) {\n      this.setAttribute('data-invalid', '');\n      this._input?.setAttribute('aria-invalid', 'true');\n      if (this._error) this._error.hidden = false;\n    } else {\n      this.removeAttribute('data-invalid');\n      this._input?.removeAttribute('aria-invalid');\n      if (this._error) this._error.hidden = true;\n    }\n  }\n\n  // ─── Public API ───\n\n  /** Set field to invalid state with a message */\n  setError(message) {\n    if (this._error && message) {\n      this._error.textContent = message;\n    }\n    this._setInvalid(true);\n  }\n\n  /** Clear error state */\n  clearError() {\n    this._setInvalid(false);\n  }\n\n  /** Check if field is currently invalid */\n  get invalid() {\n    return this.hasAttribute('data-invalid');\n  }\n\n  set invalid(val) {\n    this._setInvalid(val);\n  }\n}\n\nif (!customElements.get('ren-field')) {\n  customElements.define('ren-field', RenField);\n}\n",
      "path": "components/primitives/ren-field/ren-field.js",
      "id": "file:components/primitives/ren-field/ren-field.js",
      "type": "javascript",
      "name": "ren-field.js"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Icon Component\n   ============================================\n   Icon sizing, coloring, and animation utilities.\n   Works with any SVG or icon font.\n\n   The base class provides inline layout,\n   color inheritance, and shrink-to-fit.\n\n   Usage:\n     <span class=\"ren-icon ren-icon-md\">\n       <svg>...</svg>\n     </span>\n\n     <button>\n       <span class=\"ren-icon ren-icon-lg ren-icon-success\">\n         <svg>...</svg>\n       </span>\n       Export\n     </button>\n\n     <span class=\"ren-icon ren-icon-spin ren-icon-xl\">\n       <svg>...</svg>\n     </span>\n   ============================================ */\n\n/* ═══════════════════════════════\n   ICON BASE\n   ═══════════════════════════════ */\n\n.ren-icon {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n  width: var(--ren-icon-size);   /* 20px — default md */\n  height: var(--ren-icon-size);\n  color: var(--ren-icon-color);\n  fill: var(--ren-icon-fill);\n}\n\n.ren-icon > svg {\n  width: 100%;\n  height: 100%;\n  display: block;\n}\n\n/* ─── Sizes ─── */\n\n.ren-icon-xs {\n  width: 0.75rem;   /* 12px */\n  height: 0.75rem;\n}\n\n.ren-icon-sm {\n  width: 1rem;      /* 16px */\n  height: 1rem;\n}\n\n.ren-icon-md {\n  width: 1.25rem;   /* 20px */\n  height: 1.25rem;\n}\n\n.ren-icon-lg {\n  width: 1.5rem;    /* 24px */\n  height: 1.5rem;\n}\n\n.ren-icon-xl {\n  width: 2rem;      /* 32px */\n  height: 2rem;\n}\n\n.ren-icon-2xl {\n  width: 3rem;      /* 48px */\n  height: 3rem;\n}\n\n/* ─── Color Variants ─── */\n\n.ren-icon-primary {\n  color: var(--color-accent);\n}\n\n.ren-icon-success {\n  color: var(--color-success);\n}\n\n.ren-icon-warning {\n  color: var(--color-warning);\n}\n\n.ren-icon-danger {\n  color: var(--color-danger);\n}\n\n.ren-icon-muted {\n  color: var(--color-text-secondary);\n}\n\n/* ─── Animation ─── */\n\n.ren-icon-spin {\n  animation: ren-icon-spin var(--duration-loop) var(--ease-loop) infinite;\n}\n\n@keyframes ren-icon-spin {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n/* Respect prefers-reduced-motion */\n@media (prefers-reduced-motion: reduce) {\n  .ren-icon-spin {\n    animation: none;\n  }\n}\n",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "file:components/primitives/ren-icon/ren-icon.css",
      "type": "css",
      "name": "ren-icon.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Kbd (keyboard shortcut)\n   ============================================\n   Inline chip showing a keyboard key or shortcut\n   combo. Uses native <kbd> semantics.\n\n   Usage:\n     <kbd class=\"ren-kbd\">⌘</kbd>\n     <kbd class=\"ren-kbd\">K</kbd>\n\n   Combo:\n     <kbd class=\"ren-kbd\">⌘</kbd> + <kbd class=\"ren-kbd\">K</kbd>\n   ============================================ */\n\n.ren-kbd {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  min-width: 1.5em;\n  padding: var(--ren-kbd-padding);\n  font-family: var(--ren-kbd-font-family);\n  font-size: var(--ren-kbd-font-size);\n  font-weight: var(--weight-medium);\n  line-height: 1.4;\n  color: var(--ren-kbd-color);\n  background-color: var(--ren-kbd-bg);\n  border: var(--stroke-1) solid var(--ren-kbd-border);\n  border-bottom-width: 2px;\n  border-radius: var(--ren-kbd-radius);\n  white-space: nowrap;\n}\n",
      "path": "components/primitives/ren-kbd/ren-kbd.css",
      "id": "file:components/primitives/ren-kbd/ren-kbd.css",
      "type": "css",
      "name": "ren-kbd.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Link Component\n   ============================================\n   Styled anchor with variants.\n   CSS-only. No JS needed.\n\n   44px touch target on mobile via min-height.\n   Accessible focus ring on keyboard navigation.\n\n   Usage:\n     <a href=\"/page\" class=\"ren-link\">Standard link</a>\n     <a href=\"/page\" class=\"ren-link ren-link-muted\">Subtle link</a>\n     <a href=\"/page\" class=\"ren-link ren-link-external\">External ↗</a>\n   ============================================ */\n\n/* ─── Base Link ─── */\n.ren-link {\n  display: inline;\n  color: var(--ren-link-color);\n  font-weight: var(--ren-link-font-weight, inherit);\n  background: none;\n  text-decoration: underline;\n  text-decoration-color: color-mix(in oklch, var(--color-text-link), transparent 60%);\n  text-underline-offset: 0.15em;\n  text-decoration-thickness: var(--ren-link-underline);\n  cursor: pointer;\n  transition:\n    color var(--duration-tactile) var(--ease-enter),\n    text-decoration-color var(--duration-tactile) var(--ease-enter);\n  /* Inline elements: ensure touch target via padding */\n  -webkit-tap-highlight-color: transparent;\n}\n\n.ren-link:hover {\n  color: var(--ren-link-hover-color);\n  background: none;\n  text-decoration: underline;\n  text-decoration-color: color-mix(in oklch, var(--color-text-link-hover), transparent 30%);\n  text-decoration-thickness: calc(var(--ren-link-underline) * 2);\n}\n\n.ren-link:active {\n  opacity: 0.8;\n}\n\n.ren-link:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: 2px;\n  border-radius: var(--radius-sm);\n}\n\n/* ─── Muted / Subtle ─── */\n.ren-link-muted {\n  color: var(--color-text-muted);\n  text-decoration: none;\n}\n\n.ren-link-muted:hover {\n  color: var(--color-text);\n  text-decoration: underline;\n}\n\n/* ─── Unstyled (inherits parent color) ─── */\n.ren-link-plain {\n  color: inherit;\n  text-decoration: none;\n}\n\n.ren-link-plain:hover {\n  text-decoration: underline;\n}\n\n/* ─── External link indicator ─── */\n.ren-link-external::after {\n  content: ' ↗';\n  font-size: 0.85em;\n  text-decoration: none;\n  display: inline;\n}\n\n/* ─── Nav link (block-level, with padding for touch target) ─── */\n.ren-link-nav {\n  display: flex;\n  align-items: center;\n  gap: var(--space-2);\n  min-height: var(--touch-min);\n  padding: var(--space-1) var(--space-2);\n  color: var(--color-text);\n  text-decoration: none;\n  border-radius: var(--radius-md);\n  transition:\n    background-color var(--duration-tactile) var(--ease-enter),\n    color var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-link-nav:hover {\n  background-color: var(--color-fill);\n  color: var(--color-text);\n  text-decoration: none;\n}\n\n.ren-link-nav[aria-current=\"page\"],\n.ren-link-nav[data-active] {\n  background-color: var(--color-accent-subtle);\n  color: var(--color-accent);\n  font-weight: var(--weight-medium);\n}\n\n.ren-link-nav:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: -2px;\n}\n\n/* ─── Skip link (a11y) ─── */\n.ren-link-skip {\n  position: absolute;\n  top: -100%;\n  left: var(--space-3);\n  padding: var(--space-2) var(--space-4);\n  background-color: var(--color-accent);\n  color: var(--color-on-accent);\n  border-radius: var(--radius-md);\n  font-weight: var(--weight-semibold);\n  text-decoration: none;\n  z-index: 9999;\n  transition: top var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-link-skip:focus {\n  top: var(--space-3);\n}\n",
      "path": "components/primitives/ren-link/ren-link.css",
      "id": "file:components/primitives/ren-link/ren-link.css",
      "type": "css",
      "name": "ren-link.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Pagination Component\n   ============================================\n   Page navigation for lists/tables.\n   CSS-only. No JS needed for styling.\n\n   Semantic <nav> + links for accessibility.\n   44px touch targets on all interactive elements.\n\n   Usage (simple):\n     <nav aria-label=\"Pagination\" class=\"ren-pagination\">\n       <a href=\"?p=1\" class=\"ren-pagination-prev\" aria-label=\"Previous page\">&lsaquo;</a>\n       <a href=\"?p=1\" class=\"ren-pagination-item\">1</a>\n       <a href=\"?p=2\" class=\"ren-pagination-item\" aria-current=\"page\">2</a>\n       <a href=\"?p=3\" class=\"ren-pagination-item\">3</a>\n       <a href=\"?p=3\" class=\"ren-pagination-next\" aria-label=\"Next page\">&rsaquo;</a>\n     </nav>\n\n   Usage (minimal — just prev/next):\n     <nav aria-label=\"Pagination\" class=\"ren-pagination ren-pagination-simple\">\n       <a href=\"?p=1\" class=\"ren-pagination-prev\">Previous</a>\n       <span class=\"ren-pagination-info\">Page 2 of 10</span>\n       <a href=\"?p=3\" class=\"ren-pagination-next\">Next</a>\n     </nav>\n   ============================================ */\n\n.ren-pagination {\n  display: flex;\n  align-items: center;\n  gap: var(--ren-pagination-gap);\n}\n\n/* ─── Page items ─── */\n.ren-pagination-item,\n.ren-pagination-prev,\n.ren-pagination-next {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  min-width: var(--ren-pagination-size);\n  min-height: var(--ren-pagination-size);\n  padding: var(--space-1) var(--space-2);\n  font-size: var(--ren-pagination-font-size);\n  font-weight: var(--weight-medium);\n  color: var(--color-text);\n  text-decoration: none;\n  border-radius: var(--ren-pagination-radius);\n  border: var(--stroke-1) solid transparent;\n  background-color: var(--ren-pagination-bg);\n  transition:\n    background-color var(--duration-tactile) var(--ease-enter),\n    border-color var(--duration-tactile) var(--ease-enter);\n  user-select: none;\n}\n\n.ren-pagination-item:hover,\n.ren-pagination-prev:hover,\n.ren-pagination-next:hover {\n  background-color: var(--ren-pagination-hover-bg);\n}\n\n.ren-pagination-item:active,\n.ren-pagination-prev:active,\n.ren-pagination-next:active {\n  background-color: var(--color-fill-hover);\n}\n\n.ren-pagination-item:focus-visible,\n.ren-pagination-prev:focus-visible,\n.ren-pagination-next:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: var(--ring-offset-width);\n}\n\n/* Current page */\n.ren-pagination-item[aria-current=\"page\"] {\n  background-color: var(--ren-pagination-active-bg);\n  color: var(--ren-pagination-active-color);\n  pointer-events: none;\n}\n\n/* Disabled prev/next */\n.ren-pagination-prev[aria-disabled=\"true\"],\n.ren-pagination-next[aria-disabled=\"true\"],\n.ren-pagination-prev:disabled,\n.ren-pagination-next:disabled {\n  color: var(--color-disabled-text);\n  pointer-events: none;\n  opacity: 0.5;\n}\n\n/* Ellipsis */\n.ren-pagination-ellipsis {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  min-width: var(--touch-min);\n  min-height: var(--touch-min);\n  color: var(--color-text-muted);\n  font-size: var(--text-sm);\n  user-select: none;\n}\n\n/* ─── Simple variant (prev + info + next) ─── */\n.ren-pagination-simple {\n  gap: var(--space-2);\n}\n\n.ren-pagination-info {\n  font-size: var(--caption-size, var(--text-sm));\n  color: var(--color-text-muted);\n  white-space: nowrap;\n  padding: 0 var(--space-2);\n}\n\n/* ─── Compact variant (smaller touch targets) ─── */\n.ren-pagination-compact .ren-pagination-item,\n.ren-pagination-compact .ren-pagination-prev,\n.ren-pagination-compact .ren-pagination-next {\n  min-width: var(--size-sm);\n  min-height: var(--size-sm);\n  padding: var(--space-1);\n  font-size: var(--caption-size, var(--text-sm));\n}\n\n/* ─── Centered (for standalone pagination) ─── */\n.ren-pagination-centered {\n  justify-content: center;\n}\n",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "file:components/primitives/ren-pagination/ren-pagination.css",
      "type": "css",
      "name": "ren-pagination.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Progress Bar & Meter Components\n   ============================================\n   Progress indicators and meter displays.\n   CSS-only, works with <progress> and <meter>.\n\n   Supports multiple sizes, colors, and states.\n   Includes indeterminate animations.\n\n   Usage:\n     <div class=\"ren-progress\">\n       <div class=\"ren-progress-bar\" style=\"width: 65%\"></div>\n     </div>\n\n     <div class=\"ren-progress ren-progress-lg ren-progress-success\">\n       <div class=\"ren-progress-bar\" style=\"width: 85%\"></div>\n     </div>\n\n     <meter class=\"ren-meter\" value=\"6\" min=\"0\" max=\"10\"></meter>\n   ============================================ */\n\n/* ═══════════════════════════════\n   PROGRESS BAR\n   ═══════════════════════════════ */\n\n.ren-progress {\n  display: flex;\n  width: 100%;\n  height: var(--ren-progress-height);   /* default / md */\n  background-color: var(--ren-progress-bg);\n  border-radius: var(--ren-progress-radius);\n  overflow: hidden;\n}\n\n.ren-progress-bar {\n  height: 100%;\n  width: 0%;\n  background-color: var(--ren-progress-fill);\n  border-radius: var(--ren-progress-radius);\n  transition: width var(--ren-progress-duration) var(--ren-progress-easing);\n}\n\n/* ─── Sizes ─── */\n\n.ren-progress-sm {\n  height: 0.25rem;\n}\n\n.ren-progress-lg {\n  height: 0.75rem;\n}\n\n.ren-progress-xl {\n  height: 1rem;\n}\n\n/* ─── Color Variants ─── */\n\n.ren-progress-success .ren-progress-bar {\n  background-color: var(--color-success);\n}\n\n.ren-progress-warning .ren-progress-bar {\n  background-color: var(--color-warning);\n}\n\n.ren-progress-danger .ren-progress-bar {\n  background-color: var(--color-danger);\n}\n\n.ren-progress-info .ren-progress-bar {\n  background-color: var(--color-info);\n}\n\n/* ─── Indeterminate Animation ─── */\n\n.ren-progress-indeterminate .ren-progress-bar {\n  width: 30% !important;\n  animation: ren-progress-slide 1.5s var(--ease-enter) infinite;\n}\n\n@keyframes ren-progress-slide {\n  0% {\n    transform: translateX(-100%);\n  }\n  50% {\n    transform: translateX(500%);\n  }\n  100% {\n    transform: translateX(500%);\n  }\n}\n\n/* Respect prefers-reduced-motion */\n@media (prefers-reduced-motion: reduce) {\n  .ren-progress-indeterminate .ren-progress-bar {\n    animation: none;\n    width: 50% !important;\n  }\n\n  .ren-progress-bar {\n    transition: none;\n  }\n}\n\n/* ═══════════════════════════════\n   PROGRESS LABELS\n   ═══════════════════════════════ */\n\n.ren-progress-label {\n  display: block;\n  font-size: var(--label-size);\n  font-weight: var(--weight-medium);\n  color: var(--color-text);\n  margin: 0 0 var(--space-1);\n}\n\n.ren-progress-value {\n  display: block;\n  font-size: var(--label-size);\n  font-weight: var(--weight-regular);\n  color: var(--color-text-secondary);\n  margin: var(--space-1) 0 0;\n  text-align: end;\n}\n\n/* ═══════════════════════════════\n   METER ELEMENT\n   ═══════════════════════════════ */\n\n.ren-meter {\n  display: block;\n  width: 100%;\n  height: 0.5rem;\n  border: none;\n  border-radius: var(--radius-full);\n  overflow: hidden;\n  background-color: var(--color-fill);\n  appearance: none;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n}\n\n/* Webkit browsers (Chrome, Safari) */\n.ren-meter::-webkit-meter-inner-element {\n  border-radius: var(--radius-full);\n}\n\n.ren-meter::-webkit-meter-bar {\n  background-color: var(--color-fill);\n  border-radius: var(--radius-full);\n  overflow: hidden;\n  border: none;\n}\n\n.ren-meter::-webkit-meter-optimum-value {\n  background-color: var(--color-success);\n  border-radius: var(--radius-full);\n}\n\n.ren-meter::-webkit-meter-suboptimum-value {\n  background-color: var(--color-warning);\n  border-radius: var(--radius-full);\n}\n\n.ren-meter::-webkit-meter-even-less-good-value {\n  background-color: var(--color-danger);\n  border-radius: var(--radius-full);\n}\n\n/* Firefox */\n.ren-meter::-moz-meter-bar {\n  background: var(--color-success);\n  border-radius: var(--radius-full);\n  border: none;\n}\n\n/* ─── Meter Sizes ─── */\n\n.ren-meter-sm {\n  height: 0.25rem;\n}\n\n.ren-meter-lg {\n  height: 0.75rem;\n}\n\n.ren-meter-xl {\n  height: 1rem;\n}\n",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "file:components/primitives/ren-progress/ren-progress.css",
      "type": "css",
      "name": "ren-progress.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Radio Button & Radio Group Components\n   ============================================\n   Custom-styled radio buttons and radio groups\n   built on native <input type=\"radio\">.\n\n   Preserves native semantics and accessibility.\n   Custom visuals via CSS only.\n\n   Usage:\n     <div class=\"ren-radio-group\" role=\"radiogroup\">\n       <label class=\"ren-radio\">\n         <input type=\"radio\" name=\"option\" value=\"a\">\n         <span class=\"ren-radio-control\"></span>\n         <span>Option A</span>\n       </label>\n       <label class=\"ren-radio\">\n         <input type=\"radio\" name=\"option\" value=\"b\">\n         <span class=\"ren-radio-control\"></span>\n         <span>Option B</span>\n       </label>\n     </div>\n   ============================================ */\n\n/* ═══════════════════════════════\n   RADIO BUTTON\n   ═══════════════════════════════ */\n\n.ren-radio {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-2);\n  cursor: pointer;\n  font-size: var(--body-size);\n  font-weight: var(--weight-regular);\n  color: var(--color-text);\n  user-select: none;\n  /* Ensure touch target */\n  min-height: var(--touch-min);\n}\n\n/* Hide native radio visually but keep accessible */\n.ren-radio > input[type=\"radio\"] {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip-path: inset(50%);\n  white-space: nowrap;\n  border-width: 0;\n}\n\n/* Custom radio visual */\n.ren-radio-control {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 1.25rem;   /* 20px */\n  height: 1.25rem;\n  flex-shrink: 0;\n  border: 2px solid var(--color-border-interactive);\n  border-radius: var(--radius-full);\n  background-color: transparent;\n  transition:\n    background-color var(--duration-state) var(--ease-enter),\n    border-color var(--duration-state) var(--ease-enter),\n    transform var(--duration-tactile) var(--ease-playful);\n}\n\n/* Radio dot (hidden by default) */\n.ren-radio-control::after {\n  content: '';\n  display: block;\n  width: 0.5rem;\n  height: 0.5rem;\n  background-color: var(--color-on-accent);\n  border-radius: var(--radius-full);\n  transform: scale(0);\n  transition: transform var(--duration-state) var(--ease-playful);\n}\n\n/* ─── States ─── */\n\n/* Checked */\n.ren-radio > input:checked + .ren-radio-control {\n  background-color: var(--color-accent);\n  border-color: var(--color-accent);\n}\n\n.ren-radio > input:checked + .ren-radio-control::after {\n  transform: scale(1);\n}\n\n/* Hover */\n.ren-radio:hover > .ren-radio-control {\n  border-color: var(--color-accent);\n}\n\n.ren-radio:hover > input:checked + .ren-radio-control {\n  background-color: var(--color-accent-hover);\n  border-color: var(--color-accent-hover);\n}\n\n/* Focus */\n.ren-radio > input:focus-visible + .ren-radio-control {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: var(--ring-offset-width);\n}\n\n/* Active / Press */\n.ren-radio:active > .ren-radio-control {\n  transform: scale(0.9);\n}\n\n/* Disabled */\n.ren-radio:has(input:disabled) {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n\n/* ═══════════════════════════════\n   RADIO GROUP\n   ═══════════════════════════════ */\n\n.ren-radio-group {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-3);\n}\n\n.ren-radio-group-horizontal {\n  display: flex;\n  flex-direction: row;\n  gap: var(--space-4);\n}\n",
      "path": "components/primitives/ren-radio/ren-radio.css",
      "id": "file:components/primitives/ren-radio/ren-radio.css",
      "type": "css",
      "name": "ren-radio.css"
    },
    {
      "data": {},
      "body": "/**\n * RenDS — <ren-radio-group> Web Component\n * ========================================\n * Accessible radio group with keyboard navigation.\n * Implements arrow key navigation and roving tabindex.\n *\n * Uses Light DOM — no Shadow DOM.\n * Works with native <input type=\"radio\"> elements.\n *\n * Attributes:\n *   orientation: 'vertical' (default) | 'horizontal'\n *   role:        'radiogroup' (auto-set)\n *\n * Usage:\n *   <ren-radio-group>\n *     <label class=\"ren-radio\">\n *       <input type=\"radio\" name=\"option\" value=\"a\">\n *       <span class=\"ren-radio-control\"></span>\n *       <span>Option A</span>\n *     </label>\n *     <label class=\"ren-radio\">\n *       <input type=\"radio\" name=\"option\" value=\"b\">\n *       <span class=\"ren-radio-control\"></span>\n *       <span>Option B</span>\n *     </label>\n *   </ren-radio-group>\n *\n * Arrow keys navigate between radios.\n * Selecting a radio via arrow keys also checks it.\n */\n\nimport { createKeyboardNav } from '../../../utils/keyboard-nav.js';\n\nexport class RenRadioGroup extends HTMLElement {\n  static get observedAttributes() {\n    return ['orientation'];\n  }\n\n  constructor() {\n    super();\n    this._nav = null;\n  }\n\n  connectedCallback() {\n    // Set ARIA role\n    if (!this.hasAttribute('role')) {\n      this.setAttribute('role', 'radiogroup');\n    }\n\n    // Find all radio inputs\n    const radios = this.querySelectorAll('input[type=\"radio\"]');\n    if (radios.length === 0) return;\n\n    // Set up keyboard navigation using roving tabindex\n    const orientation = this.getAttribute('orientation') || 'vertical';\n\n    this._nav = createKeyboardNav(this, {\n      selector: 'input[type=\"radio\"]',\n      orientation,\n      loop: true,\n      typeahead: false,\n      focusOnHover: false,\n      onActivate: (radio, index) => {\n        // When arrow key navigation moves to a radio, check it\n        radio.checked = true;\n        // Dispatch change event to match native behavior\n        radio.dispatchEvent(new Event('change', { bubbles: true }));\n      },\n      onSelect: null,\n    });\n\n    this._nav.attach();\n\n    // Update class based on orientation\n    this._updateOrientationClass();\n  }\n\n  disconnectedCallback() {\n    if (this._nav) {\n      this._nav.detach();\n      this._nav = null;\n    }\n  }\n\n  attributeChangedCallback(name) {\n    if (name === 'orientation') {\n      this._updateOrientationClass();\n      // Detach and reattach nav with new orientation\n      if (this._nav) {\n        this._nav.detach();\n        const orientation = this.getAttribute('orientation') || 'vertical';\n        this._nav = createKeyboardNav(this, {\n          selector: 'input[type=\"radio\"]',\n          orientation,\n          loop: true,\n          typeahead: false,\n          focusOnHover: false,\n          onActivate: (radio, index) => {\n            radio.checked = true;\n            radio.dispatchEvent(new Event('change', { bubbles: true }));\n          },\n          onSelect: null,\n        });\n        this._nav.attach();\n      }\n    }\n  }\n\n  _updateOrientationClass() {\n    const orientation = this.getAttribute('orientation') || 'vertical';\n\n    // Remove existing orientation classes\n    this.classList.remove('ren-radio-group', 'ren-radio-group-horizontal');\n\n    // Add appropriate class\n    if (orientation === 'horizontal') {\n      this.classList.add('ren-radio-group-horizontal');\n    } else {\n      this.classList.add('ren-radio-group');\n    }\n  }\n\n  // ─── Programmatic API ───\n\n  /**\n   * Get the currently checked radio value\n   */\n  get value() {\n    const checked = this.querySelector('input[type=\"radio\"]:checked');\n    return checked ? checked.value : null;\n  }\n\n  /**\n   * Set which radio is checked by value\n   */\n  set value(val) {\n    const radio = this.querySelector(`input[type=\"radio\"][value=\"${val}\"]`);\n    if (radio) {\n      radio.checked = true;\n      radio.dispatchEvent(new Event('change', { bubbles: true }));\n    }\n  }\n\n  /**\n   * Get all radio inputs\n   */\n  get radios() {\n    return Array.from(this.querySelectorAll('input[type=\"radio\"]'));\n  }\n}\n\n// Register\nif (!customElements.get('ren-radio-group')) {\n  customElements.define('ren-radio-group', RenRadioGroup);\n}\n",
      "path": "components/primitives/ren-radio/ren-radio.js",
      "id": "file:components/primitives/ren-radio/ren-radio.js",
      "type": "javascript",
      "name": "ren-radio.js"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Separator\n   ============================================\n   Horizontal or vertical divider with optional\n   label. CSS-only, no JS needed.\n\n   Usage:\n     <hr class=\"ren-separator\">\n     <div class=\"ren-separator-vertical\"></div>\n     <div class=\"ren-separator-label\">or</div>\n   ============================================ */\n\n.ren-separator {\n  border: none;\n  background-color: var(--ren-separator-color);\n}\n\n/* Horizontal (default) */\n.ren-separator,\n.ren-separator-horizontal {\n  width: 100%;\n  height: var(--ren-separator-width);\n  margin-block: var(--ren-separator-margin);\n}\n\n/* Vertical */\n.ren-separator-vertical {\n  width: var(--ren-separator-width);\n  height: auto;\n  align-self: stretch;\n  margin-inline: var(--space-3);\n}\n\n/* With label (e.g. \"or\" between auth options) */\n.ren-separator-label {\n  display: flex;\n  align-items: center;\n  gap: var(--space-3);\n  color: var(--color-text-muted);\n  font-size: var(--caption-size);\n  margin-block: var(--ren-separator-margin);\n}\n\n.ren-separator-label::before,\n.ren-separator-label::after {\n  content: '';\n  flex: 1;\n  height: var(--ren-separator-width);\n  background-color: var(--ren-separator-color);\n}\n",
      "path": "components/primitives/ren-separator/ren-separator.css",
      "id": "file:components/primitives/ren-separator/ren-separator.css",
      "type": "css",
      "name": "ren-separator.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Skeleton\n   ============================================\n   Loading placeholder with shimmer animation.\n   Respects prefers-reduced-motion (static fill).\n\n   Usage:\n     <div class=\"ren-skeleton ren-skeleton-heading\"></div>\n     <div class=\"ren-skeleton ren-skeleton-text\"></div>\n     <div class=\"ren-skeleton ren-skeleton-circle\"></div>\n     <div class=\"ren-skeleton ren-skeleton-rect\"></div>\n\n   Accessibility:\n     - Wrap multiple skeletons in a container with\n       aria-busy=\"true\" aria-live=\"polite\"\n     - Replace with real content when loaded\n   ============================================ */\n\n.ren-skeleton {\n  display: block;\n  background: linear-gradient(\n    90deg,\n    var(--ren-skeleton-bg) 25%,\n    var(--ren-skeleton-shine) 50%,\n    var(--ren-skeleton-bg) 75%\n  );\n  background-size: 200% 100%;\n  animation: ren-skeleton-pulse var(--ren-skeleton-speed) var(--ease-loop-pulse) infinite;\n  border-radius: var(--ren-skeleton-radius);\n  min-height: 1rem;\n}\n\n/* ─── Variants ─── */\n.ren-skeleton-text {\n  height: 1em;\n  width: 100%;\n  border-radius: var(--radius-sm);\n}\n\n.ren-skeleton-heading {\n  height: 1.5em;\n  width: 60%;\n  border-radius: var(--radius-sm);\n}\n\n.ren-skeleton-circle {\n  width: var(--avatar-md);\n  height: var(--avatar-md);\n  border-radius: var(--radius-full);\n}\n\n.ren-skeleton-rect {\n  width: 100%;\n  height: 8rem;\n  border-radius: var(--radius-md);\n}\n\n@keyframes ren-skeleton-pulse {\n  0%   { background-position: 200% 0; }\n  100% { background-position: -200% 0; }\n}\n\n/* Respect reduced motion */\n@media (prefers-reduced-motion: reduce) {\n  .ren-skeleton {\n    animation: none;\n    background: var(--ren-skeleton-bg);\n  }\n}\n",
      "path": "components/primitives/ren-skeleton/ren-skeleton.css",
      "id": "file:components/primitives/ren-skeleton/ren-skeleton.css",
      "type": "css",
      "name": "ren-skeleton.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Spinner\n   ============================================\n   Circular loading indicator. CSS-only animation\n   that respects prefers-reduced-motion (falls\n   back to a gentle opacity pulse instead of\n   rotating).\n\n   Usage:\n     <span class=\"ren-spinner\" role=\"status\"\n           aria-label=\"Loading\"></span>\n     <span class=\"ren-spinner ren-spinner-lg\"></span>\n\n   Accessibility:\n     - Provide role=\"status\" and aria-label for SR\n     - On dark surfaces use .ren-spinner-light\n   ============================================ */\n\n.ren-spinner {\n  display: inline-block;\n  width: var(--ren-spinner-size);\n  height: var(--ren-spinner-size);\n  border: var(--ren-spinner-width) solid var(--color-fill-active);\n  border-top-color: var(--ren-spinner-color);\n  border-radius: var(--radius-full);\n  animation: ren-spin var(--ren-spinner-speed) var(--ease-loop-smooth) infinite;\n}\n\n/* ─── Sizes ─── */\n.ren-spinner-xs { width: 0.875rem; height: 0.875rem; border-width: 2px; }\n.ren-spinner-sm { width: 1rem;     height: 1rem;     border-width: 2px; }\n.ren-spinner-lg { width: 2rem;     height: 2rem;     border-width: 3px; }\n.ren-spinner-xl { width: 3rem;     height: 3rem;     border-width: 3px; }\n\n/* ─── Color variants ─── */\n.ren-spinner-light {\n  border-color: rgb(255, 255, 255, 0.3);\n  border-top-color: var(--white);\n}\n\n@keyframes ren-spin {\n  to { transform: rotate(360deg); }\n}\n\n@keyframes ren-spin-gentle {\n  0%, 100% { opacity: 1; }\n  50%      { opacity: 0.4; }\n}\n\n/* Reduced motion: pulse opacity instead of rotating */\n@media (prefers-reduced-motion: reduce) {\n  .ren-spinner {\n    animation: ren-spin-gentle var(--duration-loop-gentle) var(--ease-loop-pulse) infinite;\n  }\n}\n",
      "path": "components/primitives/ren-spinner/ren-spinner.css",
      "id": "file:components/primitives/ren-spinner/ren-spinner.css",
      "type": "css",
      "name": "ren-spinner.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Switch / Toggle Component\n   ============================================\n   Custom-styled toggle switch built on native\n   <input type=\"checkbox\" role=\"switch\">.\n\n   Preserves native semantics and accessibility.\n   Custom visuals via CSS only.\n\n   Usage:\n     <label class=\"ren-switch\">\n       <input type=\"checkbox\" role=\"switch\">\n       <span class=\"ren-switch-track\"></span>\n       <span>Dark mode</span>\n     </label>\n   ============================================ */\n\n.ren-switch {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-2);\n  cursor: pointer;\n  font-size: var(--body-size);\n  font-weight: var(--weight-regular);\n  color: var(--color-text);\n  user-select: none;\n  min-height: var(--touch-min);\n}\n\n/* Hide native input */\n.ren-switch > input[type=\"checkbox\"] {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip-path: inset(50%);\n  white-space: nowrap;\n  border-width: 0;\n}\n\n/* Track */\n.ren-switch-track {\n  position: relative;\n  width: var(--ren-switch-width);    /* 51px — Apple HIG */\n  height: var(--ren-switch-height);   /* 31px */\n  flex-shrink: 0;\n  background-color: var(--ren-switch-bg);\n  border-radius: var(--radius-full);\n  transition:\n    background-color var(--ren-switch-duration) var(--ren-switch-easing);\n}\n\n/* Thumb */\n.ren-switch-track::after {\n  content: '';\n  position: absolute;\n  top: 2px;\n  left: 2px;\n  width: var(--ren-switch-thumb-size);   /* 27px */\n  height: var(--ren-switch-thumb-size);\n  background-color: var(--ren-switch-thumb-color);\n  border-radius: var(--radius-full);\n  box-shadow: var(--shadow-sm);\n  transition:\n    transform var(--ren-switch-duration) var(--ren-switch-easing),\n    box-shadow var(--ren-switch-duration) var(--ren-switch-easing);\n}\n\n/* ─── States ─── */\n\n/* Checked (on) */\n.ren-switch > input:checked + .ren-switch-track {\n  background-color: var(--ren-switch-checked-bg);\n}\n\n.ren-switch > input:checked + .ren-switch-track::after {\n  transform: translateX(calc(var(--ren-switch-width) - var(--ren-switch-height)));\n}\n\n/* Hover */\n.ren-switch:hover > .ren-switch-track {\n  background-color: var(--color-fill-hover);\n}\n\n.ren-switch:hover > input:checked + .ren-switch-track {\n  background-color: var(--color-success-strong);\n}\n\n/* Focus */\n.ren-switch > input:focus-visible + .ren-switch-track {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: var(--ring-offset-width);\n}\n\n/* Active */\n.ren-switch:active > .ren-switch-track::after {\n  width: calc(var(--ren-switch-thumb-size) + 6px);\n}\n\n.ren-switch:active > input:checked + .ren-switch-track::after {\n  transform: translateX(calc(var(--ren-switch-width) - var(--ren-switch-height) - 6px));\n}\n\n/* Disabled */\n.ren-switch:has(input:disabled) {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n",
      "path": "components/primitives/ren-switch/ren-switch.css",
      "id": "file:components/primitives/ren-switch/ren-switch.css",
      "type": "css",
      "name": "ren-switch.css"
    },
    {
      "data": {},
      "body": "/* ============================================\n   RenDS — Tag / Chip Component\n   ============================================\n   Interactive badge with optional dismiss button.\n   CSS-only (dismiss needs minimal JS).\n\n   Different from Badge: Tag is interactive,\n   dismissable, and represents user-selected items.\n\n   Usage:\n     <span class=\"ren-tag\">\n       HTML\n     </span>\n\n     <span class=\"ren-tag ren-tag-removable\">\n       React\n       <button class=\"ren-tag-dismiss\" aria-label=\"Remove React\">&times;</button>\n     </span>\n\n     <span class=\"ren-tag ren-tag-clickable\" tabindex=\"0\" role=\"button\">\n       Filter: Active\n     </span>\n   ============================================ */\n\n.ren-tag {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--ren-tag-gap);\n  min-height: var(--ren-tag-height);\n  padding: 0.25em var(--ren-tag-padding-x);\n  font-size: var(--ren-tag-font-size);\n  font-weight: var(--ren-tag-font-weight);\n  line-height: 1.5;\n  color: var(--ren-tag-color);\n  background-color: var(--ren-tag-bg);\n  border: var(--stroke-1) solid var(--ren-tag-border-color);\n  border-radius: var(--ren-tag-radius);\n  white-space: nowrap;\n  user-select: none;\n  max-width: 100%;\n}\n\n/* Tag text truncation */\n.ren-tag > span {\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n/* ─── Dismiss button ─── */\n.ren-tag-dismiss {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 1.1em;\n  height: 1.1em;\n  padding: 0;\n  border: none;\n  background: none;\n  color: var(--color-text-muted);\n  cursor: pointer;\n  border-radius: var(--radius-full);\n  font-size: 1.1em;\n  line-height: 1;\n  flex-shrink: 0;\n  transition:\n    color var(--duration-tactile) var(--ease-enter),\n    background-color var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-tag-dismiss:hover {\n  color: var(--color-text);\n  background-color: var(--color-fill-hover);\n}\n\n.ren-tag-dismiss:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: 1px;\n}\n\n/* ─── Clickable / Selectable ─── */\n.ren-tag-clickable {\n  cursor: pointer;\n  transition:\n    background-color var(--duration-tactile) var(--ease-enter),\n    border-color var(--duration-tactile) var(--ease-enter);\n}\n\n.ren-tag-clickable:hover {\n  background-color: var(--color-fill-hover);\n  border-color: var(--color-border-strong);\n}\n\n.ren-tag-clickable:active {\n  background-color: var(--color-fill-active);\n}\n\n.ren-tag-clickable:focus-visible {\n  outline: var(--ring-width) solid var(--color-focus-ring);\n  outline-offset: var(--ring-offset-width);\n}\n\n/* Selected state */\n.ren-tag-clickable[aria-pressed=\"true\"],\n.ren-tag-clickable[data-selected] {\n  background-color: var(--color-accent);\n  color: var(--color-on-accent);\n  border-color: var(--color-accent);\n}\n\n/* ─── Color variants ─── */\n.ren-tag-primary {\n  background-color: var(--color-accent-subtle);\n  border-color: color-mix(in oklch, var(--color-accent), transparent 70%);\n  color: var(--color-accent);\n}\n\n.ren-tag-success {\n  background-color: var(--color-success-subtle);\n  border-color: color-mix(in oklch, var(--color-success), transparent 70%);\n  color: var(--color-success-strong);\n}\n\n.ren-tag-warning {\n  background-color: var(--color-warning-subtle);\n  border-color: color-mix(in oklch, var(--color-warning), transparent 70%);\n  color: var(--color-warning-strong);\n}\n\n.ren-tag-danger {\n  background-color: var(--color-danger-subtle);\n  border-color: color-mix(in oklch, var(--color-danger), transparent 70%);\n  color: var(--color-danger-strong);\n}\n\n/* ─── Size variants ─── */\n.ren-tag-sm {\n  padding: 0.1em 0.45em;\n  font-size: var(--text-xs);\n  gap: 0.15em;\n}\n\n.ren-tag-lg {\n  padding: 0.35em 0.8em;\n  font-size: var(--body-size);\n}\n\n/* ─── Tag group ─── */\n.ren-tag-group {\n  display: flex;\n  flex-wrap: wrap;\n  gap: var(--space-2);\n  align-items: center;\n}\n",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "file:components/primitives/ren-tag/ren-tag.css",
      "type": "css",
      "name": "ren-tag.css"
    },
    {
      "data": {},
      "body": "# Component Router\n\nLoad this file when deciding which RenDS component or pattern to use. After\nchoosing, load the exact colocated `component.md` or `pattern.md` before\nwriting markup, CSS, or JavaScript.\n\n## Counts\n\n- Primitives: 19\n- Composites: 26\n- Patterns: 8\n- Total: 53\n\n## Routing Rules\n\n- Use primitives for atomic UI: buttons, badges, cards, fields, links, status,\n  and simple form controls.\n- Use composites when behavior needs JS enhancement, ARIA relationships,\n  popover/dialog behavior, typeahead, roving focus, or state coordination.\n- Use patterns for page-level structures and repeated product workflows.\n- Do not invent component variants from memory. The colocated contract and CSS\n  file are the public source of truth.\n- Import component CSS explicitly unless the page already imports\n  `rends/components/index.css`.\n\n## Component Contracts\n\nEvery `component.md` and `pattern.md` follows the same schema:\n\n- Purpose\n- Use when\n- Do not use when\n- Required imports\n- Canonical markup\n- Variants\n- States and attributes\n- Public token API\n- Accessibility contract\n- Related files\n- Test expectations\n\n## Related Files\n\n- `components/index.css` - full component CSS bundle.\n- `components/primitives/*/component.md`\n- `components/composites/*/component.md`\n- `components/patterns/*/pattern.md`\n- `docs/components.html`\n- `docs/components-showcase.html`\n",
      "path": "components/components.md",
      "id": "foundation:component-router",
      "type": "component-router",
      "name": "Component router"
    },
    {
      "data": {},
      "body": "---\n# ────────────────────────────────────────────────────────────\n# RenDS — ren-design.md\n# A vanilla, accessible, atomic design system.\n# v0.13.0 · Default theme · WCAG 2.1 AA baseline, AAA opt-in\n# ────────────────────────────────────────────────────────────\n#\n# This is the RenDS-specific design contract for agents. It is not\n# generic external design-system file. Use it as the root router: load\n# this first, then load only the component, pattern, token, layout, or\n# foundation contract required for the current task.\n#\n# If a token is omitted here, it still lives in `tokens/**/*.css`\n# — this document privileges the tokens that compose day-to-day\n# UI decisions over exhaustive coverage.\n# ────────────────────────────────────────────────────────────\n\nsystem:\n  name: RenDS\n  version: 0.13.0\n  package: ren10\n  license: MIT\n  repo: https://github.com/Rensoconese/ren10\n  paradigm: vanilla-light-dom-atomic\n  accessibility: WCAG 2.1 AA (baseline), AAA (opt-in)\n  philosophy: >\n    Web standards first. Custom elements + CSS custom properties\n    only. No framework, no Shadow DOM, no runtime. Tokens are\n    layered: primitive → semantic → component. Components reach\n    for semantic tokens; primitives are the raw palette.\n  entry-css: index.css\n  import-style: ES module CSS import\n\nthemes:\n  default: default\n  variants:\n    - amber-editorial  # warm, serif-friendly, editorial surfaces\n    - cyber            # neon, dark-first, high-chroma\n    - minimal-mono     # neutral, grayscale, typographic\n  mechanism: data-theme attribute on :root + light-dark() CSS function\n  generator: rends/create  # Generate AA-safe theme from a single hex\n\n# ═════════════════════════════════════════════════════════════\n# COLOR\n# ═════════════════════════════════════════════════════════════\n# Primitives: Apple-HIG-derived scales (50→900 per hue).\n# Semantics: mapped with light-dark() so one token serves both modes.\n# Dark mode is honored via color-scheme + [data-theme=\"dark\"] override.\n# ═════════════════════════════════════════════════════════════\n\ncolors:\n\n  primitives:\n    blue:\n      50:  \"#EBF5FF\"\n      100: \"#D1E9FF\"\n      200: \"#A3D3FF\"\n      300: \"#75BDFF\"\n      400: \"#47A7FF\"\n      500: \"#007AFF\"   # Apple system blue\n      600: \"#0063D1\"\n      700: \"#004DA3\"\n      800: \"#003775\"\n      900: \"#002147\"\n    gray:\n      50:  \"#F9F9FB\"\n      100: \"#F2F2F7\"\n      200: \"#E5E5EA\"\n      300: \"#D1D1D6\"\n      400: \"#C7C7CC\"\n      500: \"#AEAEB2\"\n      600: \"#8E8E93\"\n      700: \"#636366\"\n      800: \"#48484A\"\n      900: \"#3A3A3C\"\n      950: \"#2C2C2E\"\n      1000: \"#1C1C1E\"\n    green:\n      50:  \"#E8FAF0\"\n      100: \"#C2F0D5\"\n      200: \"#7DDBA3\"\n      300: \"#4ECB71\"\n      400: \"#34C759\"   # Apple system green\n      500: \"#28A745\"\n      600: \"#1E8E3E\"\n      700: \"#167A32\"\n      800: \"#0F5B25\"\n      900: \"#083D19\"\n    red:\n      50:  \"#FFF0EF\"\n      100: \"#FFD6D4\"\n      200: \"#FFA8A3\"\n      300: \"#FF7A72\"\n      400: \"#FF3B30\"   # Apple system red\n      500: \"#D70015\"\n      600: \"#B80012\"\n      700: \"#990010\"\n      800: \"#7A000D\"\n      900: \"#5C000A\"\n    orange:\n      50:  \"#FFF5EB\"\n      100: \"#FFE5C7\"\n      200: \"#FFCB8F\"\n      300: \"#FFB157\"\n      400: \"#FF9500\"   # Apple system orange\n      500: \"#D97E00\"\n      600: \"#B36700\"\n      700: \"#8C5100\"\n      800: \"#663B00\"\n      900: \"#402500\"\n    yellow:\n      50:  \"#FFFBE5\"\n      100: \"#FFF4B8\"\n      200: \"#FFEA7A\"\n      300: \"#FFE03C\"\n      400: \"#FFCC00\"   # Apple system yellow\n      500: \"#D4AA00\"\n      600: \"#AA8800\"\n      700: \"#806600\"\n      800: \"#554400\"\n      900: \"#2B2200\"\n    teal:\n      50:  \"#E5FAFE\"\n      100: \"#BFF2FC\"\n      200: \"#80E5F9\"\n      300: \"#40D8F6\"\n      400: \"#5AC8FA\"   # Apple system teal\n      500: \"#32ACD6\"\n      600: \"#1E90B3\"\n      700: \"#14748F\"\n      800: \"#0C586C\"\n      900: \"#063C48\"\n    purple:\n      50:  \"#F5EEFF\"\n      100: \"#E5D4FF\"\n      200: \"#CBAAFF\"\n      300: \"#B180FF\"\n      400: \"#AF52DE\"   # Apple system purple\n      500: \"#8E3EBE\"\n      600: \"#6F2E9E\"\n      700: \"#52207E\"\n      800: \"#38155E\"\n      900: \"#200C3E\"\n    pink:\n      50:  \"#FFF0F5\"\n      100: \"#FFD6E5\"\n      200: \"#FFADCB\"\n      300: \"#FF85B1\"\n      400: \"#FF2D55\"   # Apple system pink\n      500: \"#D4234A\"\n      600: \"#AA1C3E\"\n      700: \"#801532\"\n      800: \"#550E26\"\n      900: \"#2B071A\"\n    absolutes:\n      white: \"#FFFFFF\"\n      black: \"#000000\"\n\n  # Semantic tokens are what components reach for. Each is\n  # expressed as light-dark(light-value, dark-value); both\n  # sides are tuned to pass WCAG AA against expected contexts.\n  semantic:\n    accent:\n      base:    light-dark(blue-600, blue-400)      # solid bg\n      hover:   light-dark(blue-700, blue-300)\n      active:  light-dark(blue-800, blue-200)\n      subtle:  light-dark(blue-50,  blue-900)\n      strong:  light-dark(blue-700, blue-300)      # AA text on light surface\n      on:      light-dark(white, black)            # fg on solid bg\n    status:\n      success:        light-dark(green-700,  green-400)\n      success-subtle: light-dark(green-50,   green-900)\n      success-strong: light-dark(green-700,  green-300)   # AA on surface\n      on-success:     light-dark(white, black)\n      warning:        light-dark(orange-700, orange-400)\n      warning-subtle: light-dark(orange-50,  orange-900)\n      warning-strong: light-dark(orange-700, orange-300)\n      on-warning:     light-dark(white, black)\n      danger:         light-dark(red-500,    red-400)\n      danger-subtle:  light-dark(red-50,     red-900)\n      danger-strong:  light-dark(red-500,    red-300)\n      on-danger:      light-dark(white, black)\n      info:           light-dark(teal-700,   teal-400)\n      info-subtle:    light-dark(teal-50,    teal-900)\n      info-strong:    light-dark(teal-700,   teal-300)\n      on-info:        light-dark(white, black)\n    ai:\n      base:    light-dark(purple-500, purple-300)\n      hover:   light-dark(purple-600, purple-200)\n      subtle:  light-dark(purple-50,  purple-900)\n      on:      light-dark(white, black)\n    text:\n      primary:    light-dark(black, white)\n      secondary:  light-dark(gray-700, gray-300)\n      muted:      light-dark(gray-700, gray-500)   # AA: 7.24:1 / 4.78:1\n      faint:      light-dark(gray-400, gray-800)   # INCIDENTAL ONLY (below AA)\n      inverted:   light-dark(white, black)\n      link:       accent-strong\n      link-hover: accent-active\n    surface:\n      base:    light-dark(white, black)\n      raised:  light-dark(white, gray-1000)\n      sunken:  light-dark(gray-100, black)\n      overlay: light-dark(white, gray-1000)\n      contrast: black\n      on-contrast: white\n    border:\n      base:        light-dark(gray-200, gray-900)   # decorative (may fail 3:1)\n      strong:      light-dark(gray-300, gray-800)\n      muted:       light-dark(gray-100, gray-950)\n      interactive: light-dark(gray-500, gray-500)   # WCAG 1.4.11 ≥3:1\n      accent:      accent.base\n    fill:\n      base:    light-dark(gray-100, gray-950)\n      hover:   light-dark(gray-200, gray-900)\n      active:  light-dark(gray-300, gray-800)\n      subtle:  light-dark(gray-50,  gray-950)\n    overlay-scrim: light-dark(rgba(0,0,0,0.4), rgba(0,0,0,0.6))\n    separator:     light-dark(gray-200, gray-900)\n    focus-ring:    accent.base\n    selection:\n      bg:   light-dark(blue-100, blue-900)\n      text: light-dark(black, white)\n    input:\n      bg:              light-dark(gray-100, gray-1000)\n      bg-hover:        light-dark(white, gray-950)\n      border:          border.interactive\n      border-focus:    accent.base\n      placeholder:     light-dark(gray-700, gray-500)\n      disabled-bg:     light-dark(gray-200, gray-950)\n      disabled-text:   light-dark(gray-400, gray-800)\n      error-border:    status.danger\n      focus-ring:      light-dark(rgba(0,122,255,0.15), rgba(10,132,255,0.25))\n    disabled:\n      bg:   light-dark(gray-200, gray-950)\n      text: light-dark(gray-400, gray-800)\n\n# ═════════════════════════════════════════════════════════════\n# TYPOGRAPHY\n# ═════════════════════════════════════════════════════════════\n\ntypography:\n\n  families:\n    sans: >\n      -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen,\n      Ubuntu, Cantarell, \"Helvetica Neue\", Arial, sans-serif,\n      \"Apple Color Emoji\", \"Segoe UI Emoji\"\n    mono: >\n      \"SF Mono\", ui-monospace, \"Cascadia Code\", \"Source Code Pro\",\n      Menlo, Monaco, Consolas, \"Courier New\", monospace\n\n  sizes:\n    # --text-* · absolute scale (rem). Floor is 11px (Apple HIG minimum).\n    xs:   0.6875rem   # 11px — minimum\n    sm:   0.8125rem   # 13px — footnote, caption\n    base: 1rem        # 16px — body default\n    md:   1.0625rem   # 17px — headline\n    lg:   1.125rem    # 18px — title-sm\n    xl:   1.25rem     # 20px — title-md\n    2xl:  1.375rem    # 22px — title-lg\n    3xl:  1.625rem    # 26px — display-sm\n    4xl:  1.75rem     # 28px — display-md\n    5xl:  2.125rem    # 34px — display-lg\n    6xl:  2.5rem      # 40px — hero\n    7xl:  3rem        # 48px — hero-lg\n    8xl:  3.75rem     # 60px — display-hero\n\n  weights:\n    thin:       100\n    light:      300\n    regular:    400\n    medium:     500\n    semibold:   600\n    bold:       700\n    extrabold:  800\n    black:      900\n\n  leading:\n    none:    1\n    tight:   1.2    # headlines / display\n    snug:    1.375  # titles\n    normal:  1.5    # body default\n    relaxed: 1.625\n    loose:   1.75\n    double:  2\n\n  tracking:\n    tighter: -0.05em\n    tight:   -0.025em\n    normal:   0\n    wide:     0.025em\n    wider:    0.05em\n    widest:   0.1em\n\n  roles:\n    # Pre-composed styles — pick a role, not a raw size.\n    display-lg:   { size: 5xl, weight: bold,     leading: tight, tracking: tight }\n    display-md:   { size: 4xl, weight: bold,     leading: tight, tracking: tight }\n    display-sm:   { size: 3xl, weight: semibold, leading: tight, tracking: tight }\n    title-lg:     { size: 2xl, weight: semibold, leading: snug }\n    title-md:     { size: xl,  weight: semibold, leading: snug }\n    title-sm:     { size: lg,  weight: semibold, leading: snug }\n    headline:     { size: md,  weight: semibold, leading: snug }\n    lead:         { size: xl,  weight: regular,  leading: relaxed }\n    body-lg:      { size: md,  weight: regular,  leading: normal }\n    body:         { size: base, weight: regular, leading: normal }\n    body-sm:      { size: sm,  weight: regular,  leading: normal }\n    label-lg:     { size: base, weight: medium }\n    label:        { size: sm,   weight: medium }\n    label-sm:     { size: xs,   weight: medium }\n    caption:      { size: sm,   weight: regular, leading: normal }\n    caption-sm:   { size: xs,   weight: regular }\n    code:         { size: 0.875em, weight: regular, family: mono }\n\n  modular-scale:\n    # Optional: projects that prefer a strict ratio over the absolute\n    # scale can set `<html data-type-ratio=\"X\">`.\n    default: major-third          # 1.250 · classic web\n    available:\n      - { name: minor-second,   ratio: 1.067 }\n      - { name: major-second,   ratio: 1.125 }\n      - { name: minor-third,    ratio: 1.200 }\n      - { name: major-third,    ratio: 1.250 }   # default\n      - { name: perfect-fourth, ratio: 1.333 }\n      - { name: aug-fourth,     ratio: 1.414 }\n      - { name: perfect-fifth,  ratio: 1.500 }\n      - { name: minor-sixth,    ratio: 1.600 }\n      - { name: golden-ratio,   ratio: 1.618 }\n      - { name: major-sixth,    ratio: 1.667 }\n      - { name: octave,         ratio: 2.000 }\n\n# ═════════════════════════════════════════════════════════════\n# LAYOUT — Spacing, sizing, grid\n# ═════════════════════════════════════════════════════════════\n\nlayout:\n\n  grid: 8pt         # Apple HIG. All spacing derives from multiples of 8.\n\n  spacing:\n    # --space-* · rem-based; 8px is the fundamental unit.\n    unit: 0.5rem       # 8px\n    scale:\n      0:     0\n      px:    1px\n      0-25:  0.0625rem    # 1px\n      0-5:   0.125rem     # 2px · hairline\n      1:     0.25rem      # 4px · half unit\n      1-5:   0.375rem     # 6px\n      2:     0.5rem       # 8px · 1u\n      3:     0.75rem      # 12px\n      4:     1rem         # 16px · standard\n      5:     1.25rem      # 20px\n      6:     1.5rem       # 24px\n      7:     1.75rem\n      8:     2rem         # 32px\n      9:     2.25rem\n      10:    2.5rem\n      12:    3rem         # 48px\n      14:    3.5rem\n      16:    4rem\n      20:    5rem\n      24:    6rem\n      32:    8rem\n      40:    10rem\n      48:    12rem\n      56:    14rem\n      64:    16rem        # 256px\n\n  spacing-semantic:\n    # Intent-based tokens for day-to-day layout.\n    card-padding:        space-6\n    card-padding-sm:     space-4\n    card-padding-lg:     space-8\n    panel-padding:       space-6\n    dialog-padding:      space-6\n    toast-padding:       space-4\n    form-gap:            space-5   # between fields\n    form-gap-tight:      space-3\n    form-gap-loose:      space-8   # between sections\n    field-label-gap:     space-2\n    field-helper-gap:    space-1\n    fieldset-gap:        space-6\n    list-gap:            space-2\n    list-gap-tight:      space-1\n    menu-item-gap:       space-1\n    inline-tight:        space-2   # button + icon\n    inline:              space-3\n    inline-loose:        space-4\n    section:             space-12  # between sections\n    section-lg:          space-16\n    section-sm:          space-8\n    page-top:            space-12\n    page-bottom:         space-16\n    stack-tight:         space-2\n    stack:               space-4\n    stack-loose:         space-6\n\n  density:\n    # Shift the semantic values via [data-density] on <html>.\n    comfortable: default                   # card-padding = space-6\n    compact:     { card-padding: space-4, form-gap: space-3 }\n    spacious:    { card-padding: space-8, form-gap: space-6 }\n\n  sizing:\n    touch-min: 2.75rem      # 44px — Apple HIG minimum touch target\n    component-heights:\n      xs:  1.5rem            # 24px\n      sm:  2rem              # 32px\n      md:  2.25rem           # 36px\n      lg:  2.75rem           # 44px · default / touch\n      xl:  3.25rem           # 52px\n      2xl: 3.75rem\n    icon-sizes:\n      xs:  0.75rem           # 12px\n      sm:  1rem              # 16px\n      md:  1.25rem           # 20px · default\n      lg:  1.5rem            # 24px\n      xl:  2rem\n      2xl: 3rem\n    avatar-sizes:\n      xs:  1.5rem\n      sm:  2rem\n      md:  2.5rem\n      lg:  3rem\n      xl:  4rem\n      2xl: 5rem\n    content-widths:\n      xs:    20rem           # 320px\n      sm:    24rem\n      md:    28rem\n      lg:    32rem\n      xl:    36rem\n      2xl:   42rem\n      3xl:   48rem\n      4xl:   56rem\n      5xl:   64rem           # 1024px\n      6xl:   72rem\n      7xl:   80rem           # 1280px\n      full:  100%\n      prose: 65ch            # optimal reading line length\n    stroke-widths:\n      0: 0\n      1: 1px\n      2: 2px\n      3: 3px\n      4: 4px\n\n# ═════════════════════════════════════════════════════════════\n# SHAPES — Border radius\n# ═════════════════════════════════════════════════════════════\n\nshapes:\n  radius:\n    none: 0\n    xs:   0.125rem    # 2px · subtle\n    sm:   0.25rem     # 4px · small controls\n    md:   0.5rem      # 8px · standard (Apple default)\n    lg:   0.75rem     # 12px · cards, modals\n    xl:   1rem        # 16px · large panels\n    2xl:  1.25rem     # 20px · sheets\n    3xl:  1.5rem      # 24px · large sheets\n    full: 9999px      # pill / circle\n\n# ═════════════════════════════════════════════════════════════\n# ELEVATION — Shadows and z-index\n# ═════════════════════════════════════════════════════════════\n\nelevation:\n\n  # Shadow colors are defined via light-dark() so the entire\n  # ladder swaps cleanly between modes. Light uses subtle black\n  # alpha; dark amps up to 0.3–0.5 alpha for visibility on dark\n  # surfaces.\n  shadow:\n    xs:\n      light: \"0 1px 2px rgba(0,0,0,0.05)\"\n      dark:  \"0 1px 2px rgba(0,0,0,0.30)\"\n    sm:\n      light: \"0 1px 3px rgba(0,0,0,0.10), 0 1px 2px rgba(0,0,0,0.06)\"\n      dark:  \"0 1px 3px rgba(0,0,0,0.40), 0 1px 2px rgba(0,0,0,0.30)\"\n    md:\n      light: \"0 4px 6px rgba(0,0,0,0.07), 0 2px 4px rgba(0,0,0,0.06)\"\n      dark:  \"0 4px 6px rgba(0,0,0,0.40), 0 2px 4px rgba(0,0,0,0.30)\"\n    lg:\n      light: \"0 10px 15px rgba(0,0,0,0.10), 0 4px 6px rgba(0,0,0,0.05)\"\n      dark:  \"0 10px 15px rgba(0,0,0,0.40), 0 4px 6px rgba(0,0,0,0.30)\"\n    xl:\n      light: \"0 20px 25px rgba(0,0,0,0.10), 0 8px 10px rgba(0,0,0,0.05)\"\n      dark:  \"0 20px 25px rgba(0,0,0,0.40), 0 8px 10px rgba(0,0,0,0.30)\"\n    2xl:\n      light: \"0 25px 50px rgba(0,0,0,0.15), 0 12px 24px rgba(0,0,0,0.08)\"\n      dark:  \"0 25px 50px rgba(0,0,0,0.50), 0 12px 24px rgba(0,0,0,0.40)\"\n    none:  none\n    inset:\n      light: \"inset 0 2px 4px rgba(0,0,0,0.06)\"\n      dark:  \"inset 0 2px 4px rgba(0,0,0,0.30)\"\n\n  z-index:\n    behind:    -1\n    base:       0\n    raised:     1     # slightly above siblings\n    dropdown:  10     # dropdown menus\n    sticky:    20     # sticky headers, navbars\n    overlay:   30     # overlays, backdrops\n    modal:     40     # modals, dialogs\n    popover:   50     # popovers, floating elements\n    toast:     60     # toast notifications\n    tooltip:   70     # tooltips (always on top)\n    max:     9999     # escape hatch (use sparingly)\n\n  focus-ring:\n    width:  2px\n    offset: 2px\n    color:  accent.base\n    # Rendered as: box-shadow: 0 0 0 offset surface, 0 0 0 (offset+width) accent\n\n# ═════════════════════════════════════════════════════════════\n# MOTION — Durations, easings, presets\n# ═════════════════════════════════════════════════════════════\n# All durations collapse to 0ms under prefers-reduced-motion.\n# ═════════════════════════════════════════════════════════════\n\nmotion:\n\n  durations:\n    # --duration-* primitives (ms)\n    primitives:\n      0:       0\n      fastest: 50\n      fast:    100\n      normal:  150\n      moderate: 250\n      slow:    300\n      slower:  450\n      slowest: 600\n    # Intent-named semantic durations\n    semantic:\n      micro:     50       # pixel-level feedback\n      tactile:   100      # hover / focus / press\n      state:     150      # toggle / checkbox\n      enter:     250      # widget appearing\n      exit:      100      # widget leaving (always < enter)\n      emphasize: 450      # hero transitions\n      route:     300      # page change\n      overlay:   250      # dialog / sheet / popover scrim\n\n  easings:\n    linear:        linear\n    default:       cubic-bezier(0.25, 0.10, 0.25, 1.00)\n    decelerate:    cubic-bezier(0.00, 0.00, 0.20, 1.00)   # arriving\n    accelerate:    cubic-bezier(0.40, 0.00, 1.00, 1.00)   # leaving\n    standard:      cubic-bezier(0.20, 0.00, 0.00, 1.00)   # in-flight\n    emphasized:    cubic-bezier(0.20, 0.00, 0.00, 1.00)\n    spring-soft:   cubic-bezier(0.34, 1.20, 0.64, 1.00)\n    spring-snappy: cubic-bezier(0.20, 1.50, 0.40, 1.00)\n    bounce:        cubic-bezier(0.34, 1.56, 0.64, 1.00)\n    # Intent-named:\n    enter:         decelerate      # --ease-enter\n    exit:          accelerate      # --ease-exit\n    state-change:  standard        # --ease-state-change\n    attention:     cubic-bezier(0.05, 0.70, 0.10, 1.00)\n    playful:       spring-soft\n\n  presets:\n    # Ready-to-paste `transition:` values.\n    tactile: >\n      color, background-color, border-color, box-shadow, opacity\n      — all at duration.tactile · ease.state-change\n    state: >\n      color, background-color, border-color, box-shadow, transform,\n      opacity — all at duration.state · ease.state-change\n    enter: >\n      opacity, transform, filter — duration.enter · ease.enter\n      (use with @starting-style for open animations)\n    exit: >\n      opacity, transform, filter — duration.exit · ease.exit\n    overlay: >\n      opacity, background-color, backdrop-filter, overlay (allow-discrete),\n      display (allow-discrete) — duration.overlay · ease.enter\n    emphasize: >\n      opacity, transform — duration.emphasize · ease.attention\n\n  choreography:\n    stagger-step: 40ms    # delay between sibling entries\n    stagger-max:  10      # cap before running serially\n\n# ═════════════════════════════════════════════════════════════\n# COMPONENT REGISTRY\n# ═════════════════════════════════════════════════════════════\n# Tier · atomic classification used inside the package.\n# Each entry carries: class, element tag (if web component),\n# a11y pattern, and the motion/token sub-surface it uses.\n# ═════════════════════════════════════════════════════════════\n\ncomponents:\n\n  primitives:\n    - { name: ren-avatar,     tag: none,              type: CSS-only }\n    - { name: ren-badge,      tag: none,              type: CSS-only }\n    - { name: ren-banner,     tag: none,              type: CSS-only }\n    - { name: ren-breadcrumb, tag: none,              type: CSS-only }\n    - { name: ren-button,     tag: button.ren-btn,    type: CSS-first + JS }\n    - { name: ren-card,       tag: none,              type: CSS-only }\n    - { name: ren-checkbox,   tag: native input,      type: CSS-only }\n    - { name: ren-field,      tag: ren-field,         type: web component }\n    - { name: ren-icon,       tag: none,              type: CSS-only }\n    - { name: ren-kbd,        tag: kbd,               type: CSS-only }\n    - { name: ren-link,       tag: a,                 type: CSS-only }\n    - { name: ren-pagination, tag: nav,               type: CSS-only }\n    - { name: ren-progress,   tag: progress,          type: CSS-only }\n    - { name: ren-radio,      tag: ren-radio-group,   type: web component }\n    - { name: ren-separator,  tag: hr,                type: CSS-only }\n    - { name: ren-skeleton,   tag: none,              type: CSS-only (animated) }\n    - { name: ren-spinner,    tag: none,              type: CSS-only (animated) }\n    - { name: ren-switch,     tag: native input,      type: CSS-only }\n    - { name: ren-tag,        tag: none,              type: CSS-only }\n\n  composites:\n    - { name: ren-accordion,          tag: ren-accordion,       a11y: native details/summary }\n    - { name: ren-alert-dialog,       tag: ren-alert-dialog,    a11y: non-dismissible dialog }\n    - { name: ren-calendar,           tag: ren-calendar,        a11y: ARIA grid }\n    - { name: ren-carousel,           tag: ren-carousel,        a11y: scroll-snap + dots }\n    - { name: ren-collapsible,        tag: details,             a11y: native }\n    - { name: ren-color-picker,       tag: ren-color-picker,    a11y: keyboard + EyeDropper }\n    - { name: ren-combobox,           tag: ren-combobox,        a11y: ARIA combobox }\n    - { name: ren-context-menu,       tag: ren-context-menu,    a11y: extends ren-menu }\n    - { name: ren-date-picker,        tag: ren-date-picker,     a11y: calendar popover }\n    - { name: ren-date-range-picker,  tag: ren-date-range-picker, a11y: range selection }\n    - { name: ren-dialog,             tag: ren-dialog,          a11y: native <dialog> + focus trap }\n    - { name: ren-dropzone,           tag: none,                a11y: input fallback + DnD }\n    - { name: ren-hover-card,         tag: none,                a11y: CSS anchor positioning }\n    - { name: ren-menu,               tag: ren-menu,            a11y: Popover API + roving tabindex }\n    - { name: ren-number-field,       tag: ren-number-field,    a11y: stepper }\n    - { name: ren-otp,                tag: ren-otp,             a11y: slot focus + paste }\n    - { name: ren-popover,            tag: ren-popover,         a11y: Popover API + anchor }\n    - { name: ren-scroll-area,        tag: none,                a11y: native scroll, styled bar }\n    - { name: ren-select,             tag: ren-select,          a11y: keyboard + typeahead }\n    - { name: ren-sheet,              tag: ren-sheet,           a11y: focus trap + Escape }\n    - { name: ren-slider,             tag: ren-slider,          a11y: native range + labels }\n    - { name: ren-tabs,               tag: ren-tabs,            a11y: ARIA tablist + roving tabindex }\n    - { name: ren-toast,              tag: none,                a11y: aria-live + dismissible }\n    - { name: ren-toggle-group,       tag: ren-toggle-group,    a11y: aria-pressed + roving tabindex }\n    - { name: ren-toolbar,            tag: none,                a11y: WAI-ARIA toolbar }\n    - { name: ren-tooltip,            tag: ren-tooltip,         a11y: hover + focus + long-press }\n\n  patterns:\n    - { name: ren-ai,          tag: none,         a11y: semantic HTML + reduced-motion }\n    - { name: ren-command,     tag: ren-command,  a11y: dialog + command palette shortcut }\n    - { name: ren-empty-state, tag: none,         a11y: semantic heading + text }\n    - { name: ren-form,        tag: ren-form,     a11y: aria-invalid + errormessage }\n    - { name: ren-menubar,     tag: ren-menubar,  a11y: WAI-ARIA menubar + typeahead }\n    - { name: ren-nav,         tag: ren-nav,      a11y: aria-expanded toggle + aria-current }\n    - { name: ren-sidebar,     tag: ren-sidebar,  a11y: aria-current + tooltips when collapsed }\n    - { name: ren-table,       tag: ren-table,    a11y: aria-sort + aria-selected + keyboard }\n\ncounts:\n  primitives: 19\n  composites: 26\n  patterns:    8\n  total:      53\n---\n\n# RenDS\n\nA vanilla, accessible, atomic design system. No framework required, no Shadow\nDOM, no runtime — just CSS custom properties, custom elements, and web\nstandards that have been in Chrome, Firefox, and Safari stable for years.\n\n## Agent Routing Contract\n\n`ren-design.md` is the root contract for AI agents working with RenDS. Read\nthis file first, then load only the smallest colocated contract needed for the\ntask:\n\n| Task | Load next |\n|---|---|\n| Native HTML or classless styling | `base/primitive-zero.md` |\n| Tokens, themes, colors, spacing, type, motion | `tokens/tokens.md` |\n| Page skeleton, responsive layout, utility classes | `base/layouts.md` |\n| Picking a component | `components/components.md` |\n| Editing or using a primitive/composite | `components/{tier}/{ren-name}/component.md` |\n| Editing or using a pattern | `components/patterns/{ren-name}/pattern.md` |\n\nCanonical order for UI generation:\n\n1. Load `ren-design.md`.\n2. Load `tokens/tokens.md` when choosing any visual value.\n3. Load `base/layouts.md` before writing custom layout CSS.\n4. Load `base/primitive-zero.md` when using native HTML elements without a\n   `ren-` class.\n5. Load the exact `component.md` or `pattern.md` for every RenDS part used.\n\nAgents must not invent RenDS APIs from this root file alone. Component and\npattern contracts near the implementation are the public API for markup,\nvariants, states, tokens, accessibility, JavaScript imports, and test\nexpectations.\n\n### AI Compliance Layer\n\nRenDS ships an enforcement layer aimed specifically at AI agents:\n\n- **Lowercase contract filenames only.** `ren-design.md`, `tokens.md`,\n  `layouts.md`, `primitive-zero.md`, `components.md`, `component.md`,\n  `pattern.md`. The uppercase variants (DESIGN, COMPONENT, TOKENS,\n  LAYOUTS, PRIMITIVE-ZERO, COMPONENTS) are forbidden and validated by the\n  routing checks listed in the repo-root `AGENTS.md`.\n- **`aiHints` blocks** in every high-value contract. Each\n  `component.md` / `pattern.md` for buttons, cards, fields, dialogs,\n  forms, and sidebars carries a YAML `aiHints` block with\n  `selectionCriteria`, `canonicalImports`, `requiredMarkup`,\n  `forbiddenPatterns`, `tokenPolicy`, and `accessibility`. Agents\n  should read the `aiHints` block first when deciding between two\n  components.\n- **Stylelint + token-policy lint.** `npm run lint` runs Stylelint\n  (`stylelint.config.mjs`) plus `scripts/lint-tokens.mjs`. Both block\n  primitive palette tokens (`--blue-*`, `--gray-*`, …) and hardcoded\n  hex / non-grayscale rgba in component CSS. Each documented exemption is\n  recorded in both files.\n- **Golden examples** under `rends/examples/*.html`. Concrete,\n  copy-correct flows (auth, dashboard shell, settings, data table, dialog\n  workflow, app sidebar, AI panel). Read these before generating a similar\n  flow.\n- **Evals** under `rends/evals/`. `prompts.json` is a deterministic\n  prompt set; `checklist.md` is the rule list an agent must self-check\n  before reporting completion.\n\nThe front-matter above is the wire format: tokens, names, values. Everything\nbelow this line is the prose that explains *why* those values exist, when to\nreach for which one, and how components layer on top of them. Agents building\nUI with RenDS should treat the two halves as complementary — the YAML gives\nexact values, the Markdown gives the editorial rationale.\n\n---\n\n## Overview\n\n### What RenDS is\n\nRenDS is a ~5,000-line, fully auditable design system that ships as a single\nCSS file plus a handful of custom elements. Components render in Light DOM so\nthe host app can style, slot, and select them with ordinary CSS. There is no\nbuild step required on the consumer side — `import 'rends'` and every token\nlisted above is globally available.\n\nThe system is organized in the classic atomic taxonomy: **primitives** (19)\nare the smallest cohesive units (buttons, badges, fields); **composites**\n(26) combine primitives with behavior (dialog, menu, tabs, select);\n**patterns** (8) are page-level organisms (nav, sidebar, table, form,\ncommand palette). Each tier leans on the same token layer, so swapping a\ntheme cascades cleanly from atoms to pages.\n\n### Philosophy\n\n1. **Tokens before components.** Every value a component uses — a color, a\n   radius, a duration — is a custom property. Components are opinionated\n   about *structure*; they are deliberately neutral about *values*. Override\n   tokens at `:root`, at `[data-theme]`, or at the component root to reskin\n   anything.\n\n2. **Semantic, not primitive.** Primitives (`--blue-600`, `--space-4`,\n   `--duration-normal`) exist so semantic tokens (`--color-accent`,\n   `--space-card-padding`, `--duration-enter`) can point at them. Components\n   consume the semantic layer. If a name describes *what* it is instead of\n   *what value* it holds, it belongs in the semantic layer.\n\n3. **Light DOM, always. No Shadow DOM, no exceptions.** Styling a web\n   component with the host's own CSS should be boring. Shadow DOM is\n   powerful but hostile to the everyday designer who wants to tweak padding\n   in DevTools, and it breaks the cascade-layer architecture that makes\n   overrides effortless. Every RenDS custom element keeps its markup in\n   light DOM and scopes through class names.\n\n   The pattern is **progressive enhancement** in two layers:\n\n   1. **CSS-only is usable on its own.** A button renders correctly as\n      `<button class=\"ren-btn\">Save</button>` without any JavaScript. A\n      combobox renders as `<div class=\"ren-combobox\"><input\n      class=\"ren-combobox-input\"><ul class=\"ren-combobox-list\">…</ul></div>`\n      without any JavaScript — it just won't filter or trap focus until JS\n      loads.\n   2. **Custom elements layer on top.** `<ren-button>` enhances the\n      underlying `<button>` with loading/variant state. `<ren-combobox>`\n      enhances its children by wiring ARIA, keyboard nav, and a hidden\n      form input. The element never replaces the markup; it improves it.\n\n   Composites that need encapsulated behavior (toast viewport, sheet,\n   combobox listbox) follow the same rule: they render their internals into\n   the light DOM of the host, declare ARIA via attributes, and expose state\n   through `data-*` attributes (`data-state`, `data-side`, `data-toast-id`)\n   so DevTools and external CSS can see and override everything. If you\n   reach for `attachShadow()`, stop — there is a way to do it in light DOM,\n   and the rest of the system depends on you finding it.\n\n4. **Accessibility is the default, not an add-on.** WCAG 2.1 AA is the\n   baseline: every text/background pair in the default theme meets 4.5:1\n   (small text) or 3:1 (large text / UI components). AAA is a one-line\n   opt-in via the theme generator or `[data-contrast=\"aaa\"]`. Focus rings\n   are visible, touch targets are 44px, and `prefers-reduced-motion`\n   collapses every duration to 0ms at the token layer.\n\n5. **Reduced-motion first.** Motion tokens are designed so that disabling\n   them doesn't break a single interaction — state still changes, just\n   instantly. Components never animate via hard-coded values.\n\n### Default theme\n\nThis document describes the **default** theme: Apple-HIG-derived palette,\nsystem font stack, 8pt grid, major-third type scale, decelerate-on-arrival\neasings. It is the theme every component is tuned and regression-tested\nagainst.\n\nThree variant themes ship alongside the default and can be activated via\n`<html data-theme=\"...\">`:\n\n- **amber-editorial** · warm neutrals, serif-friendly, designed for reading\n  surfaces (blogs, long-form, docs).\n- **cyber** · dark-first, saturated neon accents, high-contrast UI.\n- **minimal-mono** · grayscale / single-hue, typographic, deliberately\n  restrained.\n\nA hex-to-theme generator (`rends/create`) derives an AA-safe theme from any\nsingle brand hex. All three variants stay within the semantic token names\ndocumented here — nothing about a component changes when you swap themes.\n\n### How to read this document\n\nEach section below documents one token family — **Colors**, **Typography**,\n**Layout**, **Shapes**, **Elevation & Depth**, **Motion** — followed by the\ncomponent catalog and a consolidated **Do's and Don'ts** list. The tone is\nprescriptive: these are the decisions that have already been made, so an\nagent generating RenDS UI can stop making them.\n\n---\n\n## Colors\n\nRenDS' palette derives from Apple Human Interface Guidelines system colors.\nEach hue ships as a 10-step scale (50 → 900, plus a 950/1000 on gray for\ndark-mode surfaces). The scales are expressive enough for marketing\nillustration and tame enough for dashboard UI — the rule is that you never\npick from them directly in a component.\n\n### Two layers, one discipline\n\n- **Primitive colors** (`--blue-600`, `--gray-100`, `--red-500`) are the raw\n  palette. They live in `tokens/primitives/colors.css` and exist so that\n  semantic tokens have something to resolve to. Components never consume\n  primitives.\n- **Semantic colors** (`--color-accent`, `--color-text`, `--color-surface`,\n  `--color-border-interactive`, `--color-success-subtle`, …) describe *what\n  a color is for*. These are the only tokens a component touches. If a\n  semantic name does not exist for the job, add one in\n  `tokens/semantic/colors.css` before falling back to a primitive.\n\nThe reason for the split is simple: you can reskin the entire system by\nrewriting ~80 semantic tokens, because they all point into the primitive\npalette. You never have to audit a component for hard-coded colors.\n\n### light-dark() and color-scheme\n\nEvery semantic token is defined with the CSS `light-dark()` function:\n\n```css\n--color-text:    light-dark(var(--black), var(--white));\n--color-surface: light-dark(var(--white), var(--black));\n```\n\n`:root` declares `color-scheme: light dark`, which lets the browser pick the\nactive side based on the user's OS preference. Manual overrides are applied\nwith `<html data-theme=\"light\">` or `<html data-theme=\"dark\">`, which set\n`color-scheme` explicitly. There is no separate `.dark` class, no\nduplicated declaration blocks, no JS required. Dark mode is free.\n\n### Accent, status, AI\n\nFour semantic color families are tuned for maximum legibility on their solid\nbackgrounds:\n\n- **Accent** (`--color-accent` / `-hover` / `-active` / `-subtle` / `-on` /\n  `-strong`) — the brand color. Used for primary buttons, focused borders,\n  links, and selection states. `--color-on-accent` is the guaranteed-AA\n  text color to place on top of `--color-accent` (white in light, black in\n  dark). `--color-accent-strong` is the variant to use for accent-colored\n  *text* on a surface (blue-700 / blue-300); the base is calibrated as a\n  background, not as text on white.\n- **Status** (`success`, `warning`, `danger`, `info`) — each has the same\n  family of tokens (`-subtle`, `-strong`, `-on-X`). Semantics map to\n  intent: success = confirmations, warning = needs attention but not\n  blocking, danger = destructive / error, info = neutral system message.\n- **AI** (`--color-ai-*`) — the purple family used by `ren-ai` patterns\n  (slug, streaming cursor, citations). Carved out so AI surfaces are\n  visually distinct from ordinary accent UI even when a theme remaps the\n  accent family.\n\n### Text tokens and the `--color-text-faint` carve-out\n\nText colors are a short ladder: `primary` (full black/white),\n`secondary` (high-emphasis muted), `muted` (standard muted — AA at 7.24:1\nlight and 4.78:1 dark), `faint` (below AA), `inverted`, `link`, and\n`link-hover`.\n\n`--color-text-faint` (gray-400 light, gray-800 dark) **does not meet WCAG\nAA** and is reserved for incidental, WCAG-exempt content: disabled labels,\nplaceholder hints, decorative punctuation (breadcrumb separators, ellipsis,\ndivider dots), and the outside-month dates in a calendar grid. It must\n**never** be used for captions, eyebrows, table headers, step numbering,\nor any text the user is expected to read. Use `--color-text-muted` for\nthose cases — it is AA in both modes.\n\n### Borders and the 3:1 rule\n\nBorders serve two roles with very different contrast requirements:\n\n- **Decorative separators** (`--color-border`, `--color-border-muted`,\n  `--color-separator`) live between regions (card edges, dividers, section\n  lines). They intentionally fail the 3:1 UI-component contrast threshold\n  because they're not conveying state — they're visually tidying the\n  layout.\n- **Interactive borders** (`--color-border-interactive`) outline actionable\n  controls: inputs, unchecked checkboxes, toggle buttons. These must meet\n  WCAG 1.4.11 (≥3:1 vs their surface). `--color-border-interactive`\n  resolves to `gray-500` in both modes — 3.07:1 on white, 7.42:1 on black\n  — which clears AA everywhere the system expects a border to communicate\n  affordance.\n\nComponents enforce this split: `ren-field`, `ren-checkbox`, `ren-radio`,\n`ren-select`, `ren-toggle-group`, and `ren-button.ren-btn-outline` all use\n`--color-border-interactive`. `ren-card`, `ren-separator`, and\n`ren-sidebar` use `--color-border`. If you're deciding which to use, the\ntest is: *does removing this border make the component ambiguous about\nbeing interactive?* If yes, interactive. If no, decorative.\n\n### Fills, surfaces, and hover states\n\nSurfaces (`--color-surface`, `--color-surface-raised`, `--color-surface-sunken`,\n`--color-surface-overlay`) describe painted backgrounds at different\nelevation tiers. Dark mode surfaces use `gray-1000` (#1C1C1E) for raised\ncontent so shadows are still visible — pure black swallows shadow softly.\n\nFills (`--color-fill`, `-hover`, `-active`, `-subtle`) are the grayscale\nramp used for subtle backgrounds: menu-item hover, row zebra-striping,\ntag backgrounds, switch track off-state. They step through\ngray-100 → gray-200 → gray-300 in light, and gray-950 → gray-900 → gray-800\nin dark, so any hover/active pair reads as a single visual step.\n\n### Focus rings and selection\n\n`--color-focus-ring` is aliased to `--color-accent`. The focus ring is\nrendered as `box-shadow: 0 0 0 2px var(--color-surface), 0 0 0 4px var(--color-focus-ring)`\nso that the offset adapts to whatever surface the control sits on — you get\nthe right \"hollow\" effect in light and dark without redefinition.\n\nSelection uses `--color-selection-bg` (blue-100 light, blue-900 dark) with\n`--color-selection-text` as the complement. Applied via\n`::selection { background: var(--color-selection-bg); color: var(--color-selection-text); }`\nin `base/reset.css`.\n\n### AAA mode\n\n`[data-contrast=\"aaa\"]` on `:root` (or generated by the theme tool) lifts\nevery strong-variant and text-muted pair to WCAG AAA (≥7:1 for small text,\n≥4.5:1 for UI). The mechanism is a thin override on the `--color-*-strong`\ntokens — no component changes. Use AAA when targeting audiences with low\nvision or when a client spec demands it; the system has been regression\ntested under AAA with the `smoke-create-generator` script.\n\nThe shipped `[data-contrast=\"aaa\"]` scope also sets normal foreground,\nsurface, and on-accent pairs to a computed contrast of at least 7:1 in light\nand dark color schemes. It is intentionally ordered after named theme rules,\nso the opt-in wins when both attributes are present.\n\nCustom elements are progressive enhancements: server-rendered light-DOM\ncontent is visible before registration and remains usable without JavaScript.\nApplications may opt into a temporary hidden state with `[data-ren-pending]`;\nRenDS does not hide every `:not(:defined)` custom element globally.\n\n---\n\n## Typography\n\nThe type system is derived from the Apple HIG hierarchy: display → title →\nheadline → body → label → caption → code. Each role carries a pre-composed\nbundle of size, weight, line-height, and (where relevant) tracking, so a\ncomponent styles `.ren-title-md` rather than reaching for six individual\nproperties.\n\n### System font stack\n\nRenDS defaults to the OS system font — `-apple-system`, `BlinkMacSystemFont`,\n`Segoe UI`, `Roboto`, etc. This produces native-feeling UI across macOS,\nWindows, Linux, iOS, and Android with no web-font payload. The mono stack\nfavors `SF Mono` and `ui-monospace` for the same reason: consistent metrics\nwith zero download.\n\nProjects that want a custom face override `--font-sans` or `--font-mono` at\n`:root` — not at the component level. Every component inherits through the\ntoken chain, so overriding once reskins every text surface.\n\n### Size scale: 11 → 60 px\n\nThe primitive scale `--text-xs` through `--text-8xl` covers 11px (the Apple\nHIG minimum legible size) up to 60px (display hero). Values are expressed\nin `rem` so user-agent font-size scaling works out of the box. Never go\nbelow `--text-xs` in UI — accessibility audits will flag it.\n\nThe 11 → 22px band (xs through 2xl) does the bulk of interface work: body\ntext, labels, titles. The 26 → 60px band (3xl through 8xl) is reserved for\neditorial and marketing surfaces: display headlines, heroes, splash\nscreens.\n\n### Semantic roles\n\nInstead of choosing a raw size, compose from one of the role bundles:\n\n- **Display** (`display-lg/md/sm`) · large marketing or hero copy; tight\n  leading, tight tracking, bold weight.\n- **Title** (`title-lg/md/sm`) · section headings; semibold + snug leading.\n  Use these for H2/H3 inside app views.\n- **Headline** (`headline`) · emphasized body (17px semibold) — for\n  dialog titles, card titles, form section heads.\n- **Lead** (`lead`) · introductory paragraphs (20px regular, relaxed\n  leading). Good for the first paragraph of an article or the subhead\n  under a hero.\n- **Body** (`body-lg/body/body-sm`) · default reading text. `body` is\n  16px, which sets the browser-default baseline.\n- **Label** (`label-lg/label/label-sm`) · UI control text — button labels,\n  field labels, menu items. Always `weight-medium` so buttons read crisply\n  at small sizes without anti-alias fuzz.\n- **Caption** (`caption/caption-sm`) · secondary text — helper text,\n  timestamps, metadata.\n- **Code** (`code`) · `0.875em` monospace for inline code. Uses `em` so it\n  scales with surrounding prose.\n\nEach role has its own `--{role}-size`, `--{role}-weight`,\n`--{role}-leading`, and (where tuned) `--{role}-tracking` tokens. There are\nalias tokens (`--font-size-body`, `--line-height-caption`) kept for legacy\ncompatibility — prefer the canonical `--body-size`, `--caption-leading`\nforms in new code.\n\n### Dynamic Type\n\n`tokens/semantic/dynamic-type.css` exposes a parallel set of fluid sizes\nthat interpolate between a min and max viewport width, so the same\ncomponent can stay readable on mobile and breathe on desktop without\nmanual breakpoints. Opt in by consuming `--body-size-fluid` instead of\n`--body-size`. Not used by default — components ship with static sizes so\nQA across breakpoints is predictable.\n\n### Modular scale (optional)\n\nProjects that prefer a strict modular scale over the absolute values can\nset `<html data-type-ratio=\"perfect-fourth\">` and the `--scale-step-*`\ntokens will interpolate between base and their ratio. The default ratio\nis **major-third (1.250)** — classic web, balanced, friendly. Switching\nto `golden-ratio` or `octave` is a one-line change.\n\n### Line length\n\nThe content-width token `--width-prose` is defined as `65ch`, which is the\naccessibility guideline for optimal reading line length. Long-form text\nsurfaces (`ren-card` with article body, docs pages) should constrain to\nthis width. App UI surfaces (forms, tables) use the `--width-*` pixel\nladder instead because line length there is driven by data, not reading\ncomfort.\n\n### Tracking\n\nThe `--tracking-*` tokens are tuned for display and label roles.\nDisplay/title sizes benefit from `tracking-tight` to counteract optical\nloosening at scale. Labels at the `xs` size can bump to `tracking-wide`\nfor uppercase treatments (eyebrows, category tags, form-step labels) — the\nwidened spacing rescues legibility where lowercase x-height disappears.\n\n---\n\n## Layout\n\nEvery layout decision in RenDS flows from the **8pt grid**. If a value isn't\na multiple of 8 (or a half-unit 4), it shouldn't be in a layout declaration.\nThe spacing scale, component heights, touch targets, and icon sizes are all\nderived from this unit so stacks, clusters, and grids snap together without\npixel-hunting.\n\n### Spacing scale\n\nThe primitive scale `--space-0` through `--space-64` goes 0 → 256px in 8pt\nsteps, with half-units at 4/6/12/20px for tight spacing. In practice,\n`--space-2` (8px), `--space-3` (12px), `--space-4` (16px), `--space-6`\n(24px), and `--space-8` (32px) do most of the work. The hairline tokens\n(`--space-px`, `--space-0-5`) exist for borders and icon fine-tuning.\n\n### Semantic spacing\n\nLayouts consume spacing through intent-named tokens in\n`tokens/semantic/spacing.css`:\n\n- `--space-card-padding` / `-sm` / `-lg` · padding inside cards, panels,\n  dialogs.\n- `--space-form-gap` / `-tight` / `-loose` · vertical rhythm between\n  fields. `--space-field-label-gap` and `--space-field-helper-gap` set\n  the within-field rhythm.\n- `--space-inline` / `-tight` / `-loose` · horizontal gap inside clusters\n  (button + icon, tag + dismiss).\n- `--space-list-gap` / `-tight` · vertical gap in menus and lists.\n- `--space-section` / `-lg` / `-sm` · rhythm between page sections.\n- `--space-page-top` / `-bottom` · page-level breathing room.\n- `--space-stack` / `-tight` / `-loose` · defaults for `ren-stack`\n  utilities.\n\nPrefer these over raw `--space-*` in custom layouts — the resulting CSS\nreads as intent (\"card has a `card-padding`\") rather than magic numbers.\n\n### Density\n\nThree densities are driven by `[data-density]` on `<html>`:\n\n- `comfortable` (default) — card-padding = 24px, form-gap = 20px.\n- `compact` — card-padding = 16px, form-gap = 12px. Good for data-dense\n  admin dashboards.\n- `spacious` — card-padding = 32px, form-gap = 24px. Good for marketing\n  pages and onboarding.\n\nDensity only touches the semantic spacing tokens; the primitive scale is\ninvariant. This means custom components written against the semantic\nlayer pick up density switches automatically.\n\n### Touch targets\n\n`--touch-min` is 44px (2.75rem), matching the Apple HIG minimum. Every\ninteractive primitive honors this — `ren-button`, `ren-field`,\n`ren-checkbox`, `ren-radio`, `ren-tag[clickable]`, pagination items, menu\nitems, etc. — regardless of visual size. Small buttons use padding\ntop/bottom to *look* compact while keeping the hit area at 44px.\n\nDesigns that need a visually smaller control (icon button in a toolbar,\ninline close button on a chip) may drop below 44px visually, but must\nstill provide a 44px hit zone via `:before`/`:after` expanded padding or a\ntransparent overlay.\n\n### Component heights\n\nThe `--size-*` ladder (24 → 60px) is the canonical height for buttons and\ninputs:\n\n- `xs` 24px · compact inline (chips, small tags)\n- `sm` 32px · small button / badge\n- `md` 36px · medium\n- `lg` 44px · **default** and touch target\n- `xl` 52px · large button\n- `2xl` 60px · extra-large / marketing CTA\n\nKeep a single row of interleaved buttons and inputs at the same size tier\nso they baseline-align.\n\n### Icon and avatar sizes\n\nIcons follow a 12 → 48px scale; `--icon-md` (20px) is the default for most\ncontexts. Inline icons inside body text use `--icon-sm` (16px) to match\ncap-height. Avatars share a parallel 24 → 80px scale; pair `--avatar-sm`\n(32px) with `--size-md` buttons, `--avatar-md` (40px) with navigation\nchrome, and reserve `--avatar-xl` and up for profile cards.\n\n### Content widths\n\nPage- and container-level max-widths live in `--width-*` (320 → 1280px).\nUse these at the layout level, not on individual components. The\n`--width-prose` (65ch) token is specifically for reading text surfaces.\n\n### Responsive strategy\n\nRenDS is mobile-first. Container-based responsive rules (using\n`container-type: inline-size`) are preferred over viewport media queries\nbecause they let a component adapt to whatever column it's placed in.\n`ren-form`, `ren-sidebar`, and `ren-table` already use this pattern.\nViewport media queries are reserved for truly page-level shifts (e.g.,\nflipping a nav bar from horizontal to hamburger).\n\n### Utilities\n\n`base/utilities.css` ships a minimal atomic layer: `.ren-stack`,\n`.ren-cluster`, `.ren-sidebar-layout`, `.ren-grid`, visibility helpers,\nand `sr-only`. These are deliberately small — the system leans on custom\nelements for structure, not utility stacks.\n\n---\n\n## Elevation & Depth\n\nRenDS uses shadow tiers, not surface colors, to communicate depth. Surfaces\nare mostly flat (white, near-black, gray-1000); the hierarchy of stacked\ncontent reads through the shadow ladder.\n\n### Shadow ladder\n\nSix tiers from `--shadow-xs` to `--shadow-2xl`, plus `--shadow-inset` and\n`--shadow-none`:\n\n- **xs** · hairline lift (1px) · buttons in resting state, tags.\n- **sm** · card at rest · the typical card or input shadow.\n- **md** · raised card on hover · also used for dropdown/select panels.\n- **lg** · popover, dropdown menu, hover-card, tooltip.\n- **xl** · dialog panel, sheet.\n- **2xl** · modal over important content, full-screen overlays.\n- **inset** · pressed state for toggle-able buttons and track fills.\n\nEach tier uses two stacked shadows — one for ambient occlusion (wide, low\nopacity) and one for the key light (tight, slightly higher opacity). This\nmimics the Apple layered-light model and reads as depth rather than a\ndrop-shadow artifact.\n\n### Dark-mode shadow compensation\n\nShadows on dark surfaces have to work harder to be visible. The system\ndecomposes each tier into `--shadow-color-1` through `--shadow-color-6` —\ntheme-aware color tokens defined with `light-dark()` — and composes the\nfinal `--shadow-xs` … `--shadow-2xl` values from them. In practice, light\nmode uses 5–15% black alpha; dark mode uses 30–50% black alpha. The\ncomponent author writes `box-shadow: var(--shadow-md)` and gets the right\nvisual in both modes for free.\n\nA note on pure-black surfaces: dark mode surfaces use `gray-1000`\n(`#1C1C1E`), not pure black, for anything that needs to receive a shadow.\n`--color-surface` is true black in dark mode (matching the OLED-friendly\nHIG pattern), but `--color-surface-raised` steps up to `gray-1000` so\nshadows have something to fall onto.\n\n### Z-index ladder\n\nLayering uses a named scale instead of arbitrary numbers:\n\n- `--z-behind` (-1) · content pushed below the page (background shapes).\n- `--z-base` (0) · normal document flow.\n- `--z-raised` (1) · sibling lift (hover cards inside a grid).\n- `--z-dropdown` (10) · dropdown menus.\n- `--z-sticky` (20) · sticky headers, sticky nav.\n- `--z-overlay` (30) · overlays, backdrops, scrims.\n- `--z-modal` (40) · dialogs.\n- `--z-popover` (50) · popovers, floating UI.\n- `--z-toast` (60) · toast notifications.\n- `--z-tooltip` (70) · tooltips — always on top of interactive layers.\n- `--z-max` (9999) · escape hatch; use sparingly.\n\nEach layer is a full decimal step apart so components inside a layer can\nnudge themselves without colliding. New features should pick the\nclosest-named token instead of inventing numbers.\n\n### Focus ring\n\nFocus visibility is part of the depth story. The ring is 2px wide with a\n2px offset, colored `--color-focus-ring` (= `--color-accent`). Rendered as\na stacked `box-shadow` so the offset color matches the underlying surface:\n\n```css\nbox-shadow:\n  0 0 0 var(--focus-ring-offset) var(--color-surface),\n  0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--color-focus-ring);\n```\n\nThis guarantees a visible halo regardless of whether the control sits on\nwhite, gray, or a dark surface. Every interactive RenDS component uses\n`:focus-visible` (never `:focus`) so mouse users don't see the ring on\nclick.\n\n---\n\n## Shapes\n\nRounding is functional, not decorative. RenDS defaults to softly-rounded\ncorners — small enough that rectangles still read as rectangles, large\nenough that the UI doesn't feel pixel-sharp.\n\n### Radius scale\n\nThe nine-step ladder runs from `--radius-none` to `--radius-full`. Each\ntier has a job:\n\n- **none** (0) · rare; use for full-bleed surfaces and hairline dividers.\n- **xs** (2px) · subtle rounding for tags, kbd keys, micro-badges.\n- **sm** (4px) · small controls — buttons (xs/sm), inputs (sm), checkbox.\n- **md** (8px) · **standard**, Apple default — button (md/lg), input (md),\n  badge, most \"pill but not fully round\" surfaces.\n- **lg** (12px) · cards, dropdown menus, popovers, modal dialogs.\n- **xl** (16px) · large panels, side sheets, oversized cards.\n- **2xl** (20px) · sheets that feel like a drawer drawn up over the\n  viewport (iOS-style).\n- **3xl** (24px) · extra-large sheets, splash panels.\n- **full** (9999px) · pills and circles — avatars, round buttons,\n  progress bars, skeleton text lines.\n\n### Picking a radius\n\nThe internal rule: *smaller controls carry smaller radii*. A 24px chip\nwith a 12px radius reads as a blob; the same chip with a 4px radius reads\nas a clean tag. Match the radius to the smallest dimension of the\ncontrol — the radius should usually be no more than one-third of the\nheight for non-pill shapes.\n\nCards and modals break this rule deliberately: `--radius-lg` (12px) on a\n200px-tall dialog looks correct because the corner sweep is a small\nfraction of the overall surface. The rule is about *small controls*, not\nall surfaces.\n\n### Pills and circles\n\n`--radius-full` is the only value that ignores size matching. Use it for:\n\n- Avatars, user badges.\n- Progress bars (track and fill both).\n- Loading spinner borders.\n- Capsule buttons, status chips that want to emphasize their \"pill\" shape.\n- Skeleton placeholder lines — a pill-shaped skeleton reads as\n  generic-content faster than a sharp rectangle.\n\nNever mix pill and non-pill shapes within the same component family. A\nbutton group with three `--radius-md` items and one `--radius-full` item\nwill read as broken.\n\n### Per-component overrides\n\nEvery component that cares about corners exposes a component-level token\n(`--ren-card-radius`, `--ren-button-radius`, etc.) in\n`tokens/component/tokens.css`. Override the token, not the component CSS.\nThis keeps the system consistent if someone later flips `--radius-md`\nglobally.\n\n---\n\n## Motion\n\nMotion in RenDS is task-oriented: every transition carries information\nabout state change. Nothing animates for flavor. The semantic motion\ntokens describe the *intent* (`enter`, `exit`, `state`, `tactile`,\n`overlay`, `route`, `emphasize`), and each intent resolves to a\n`(duration, easing)` pair that has already been tuned correctly.\n\n### Durations by intent\n\n- **micro** (50ms) — pixel-level feedback, e.g., opacity flicker on a\n  pressed checkbox dot.\n- **tactile** (100ms) — hover, focus, and press transitions. Fast enough\n  to feel responsive; slow enough to avoid snapping.\n- **state** (150ms) — on/off toggles, expanded/collapsed state,\n  checked/unchecked. The \"something changed\" interval.\n- **enter** (250ms) — widgets *arriving* on screen (dialog open, menu\n  open, toast enter, popover open).\n- **exit** (100ms) — widgets *leaving*. Exits are always faster than\n  entrances — the UX rule of thumb is that leaving should feel like it\n  took you at your word.\n- **overlay** (250ms) — backdrops, scrims, dialog bodies composited with\n  a dim layer.\n- **route** (300ms) — page-level transitions (route changes in SPAs).\n- **emphasize** (450ms) — hero / onboarding / attention-seeking flows.\n\n### Easings by intent\n\n- **enter** (`cubic-bezier(0, 0, 0.2, 1)`) — decelerate, for anything\n  arriving at rest. Default for every open animation.\n- **exit** (`cubic-bezier(0.4, 0, 1, 1)`) — accelerate, for anything\n  leaving the viewport.\n- **state-change** (`cubic-bezier(0.2, 0, 0, 1)`) — symmetric, for motion\n  that happens in-flight between two on-screen states.\n- **attention** (emphasized-decelerate) — slow start, crisp stop, for\n  hero moments.\n- **playful** (spring-soft `cubic-bezier(0.34, 1.2, 0.64, 1)`) — gentle\n  overshoot. Use on primitives that benefit from a little personality\n  (active checkbox dot, tag appearing).\n\n### Presets\n\nReady-to-paste `transition:` values, composed from the intent tokens:\n\n- `--transition-tactile` — color/bg/border/shadow/opacity at `tactile`.\n- `--transition-state` — same plus `transform`, at `state`.\n- `--transition-enter` — opacity/transform/filter at `enter` · use with\n  `@starting-style` for open animations.\n- `--transition-exit` — opacity/transform/filter at `exit`.\n- `--transition-overlay` — opacity/bg/backdrop-filter/overlay/display at\n  `overlay` with `allow-discrete` for discrete-property animation.\n- `--transition-emphasize` — opacity/transform at `emphasize`.\n\nMost of the time a component reaches for one of these presets rather than\ncomposing a transition by hand.\n\n### `@starting-style` and `allow-discrete`\n\nOpen animations (dialog, menu, popover, toast) use `@starting-style` to\nanimate *into* the rendered state, and `transition-behavior: allow-discrete`\nso that `display: none → block` interpolates cleanly. Every RenDS overlay\ncomposite uses this pattern; consumers can rely on it rather than\nhand-rolling `requestAnimationFrame` hacks.\n\n### Choreography\n\nWhen multiple siblings enter at once (a grid of cards, a menu's items),\nthey stagger by `--motion-stagger-step` (40ms). The stagger caps at\n`--motion-stagger-max` (10 items) to keep total delay bounded. Applied\neither via the `.ren-stagger > *` utility or the `motion.js`\n`staggerChildren()` helper.\n\n### Reduced motion\n\nEvery primitive duration collapses to `0ms` under\n`@media (prefers-reduced-motion: reduce)`, which cascades through every\nsemantic duration automatically. The semantic layer reapplies the same\ncollapse so both layers stay in sync, and `--motion-stagger-step` drops\nto `0ms` so stagger also disappears. State changes still fire — an\naccordion still opens, a dialog still appears — they just do so\ninstantly. No component has to opt in; reduced-motion is a system\nproperty.\n\n---\n\n## Components\n\nRenDS ships 53 components across three tiers. Every component is\naccessible by default (keyboard, screen reader, reduced motion, 44px\ntouch), and every component consumes the token layer — swap a theme and\nevery component follows.\n\n### Primitives (19)\n\nPrimitives are the smallest cohesive unit: a button, a badge, a field. They\ncompose freely and never require another primitive to be present. Most are\nCSS-only; the handful with JS are there purely to wire ARIA relationships.\n\n#### ren-avatar\n\nCircular user picture with fallback initials and optional presence dot.\nShips six sizes (`xs` 24px → `2xl` 80px) matching the `--avatar-*` scale.\nPlace inside `ren-avatar-group` for overlap-and-pile clusters.\n\n- **Variants:** solid, outline, gradient.\n- **A11y:** decorative by default — provide `alt` on the inner image, or\n  `aria-label` on the initials variant.\n\n#### ren-badge\n\nSmall atomic label for status, category, or count. CSS-only. Pair with\ntext (\"Active\") or use the `.ren-badge-dot` variant for a pure indicator.\n\n- **Variants:** `primary`, `secondary`, `success`, `warning`, `danger`,\n  `info`, `outline`, `dot`.\n- **A11y:** semantic `<span>`. Add `role=\"status\"` if the badge\n  communicates a live update (e.g., a notification count that changes).\n\n#### ren-banner\n\nPersistent inline message for alerts, status updates, and notifications.\nUnlike toast, banners do not auto-dismiss — they occupy layout until the\nuser acknowledges them.\n\n- **Variants:** `success`, `warning`, `danger`, `neutral`; plus\n  `-compact`, `-full`, `-dismiss`, and `-actions` modifiers.\n- **States:** `[data-dismissing]` plays the exit animation.\n- **A11y:** `role=\"status\"` for passive updates, `role=\"alert\"` for\n  urgent ones. The dismiss button carries `aria-label`.\n\n#### ren-breadcrumb\n\nHierarchical navigation trail. Built on `<nav><ol>` so screen readers\nannounce it as a list. Default separator is `›` via the\n`--breadcrumb-separator` variable (override to \"/\", \"→\", \"·\", etc.).\n\n- **Special:** `.ren-breadcrumb-truncated` collapses middle items for\n  deep paths.\n- **A11y:** `aria-label=\"Breadcrumb\"` on the `<nav>`, `aria-current=\"page\"`\n  on the last item.\n\n#### ren-button\n\nThe workhorse. Seven variants × three sizes × loading/disabled states. Uses\n`--size-md` (36px) by default and honors `--touch-min` (44px) at `lg`.\n\n- **Variants:** `primary` (solid accent), `secondary` (filled gray),\n  `ghost` (transparent + hover fill), `outline` (border only), `danger`,\n  `link`, `accent`.\n- **Sizes:** `sm` / `md` (default) / `lg`.\n- **Special:** `.ren-btn-icon` (square icon-only), `.ren-btn-full`\n  (100% width), `.ren-btn-group` (segmented).\n- **States:** `disabled`, `[data-loading]` (sets `aria-busy=true` and\n  shows spinner), `[aria-disabled=\"true\"]`.\n- **A11y:** visible focus ring, 44px touch hit area at all sizes,\n  spinner announces loading state via `aria-busy`.\n\n#### ren-card\n\nVersatile container for grouped content. Supports header / body / footer\nsections and accent-border status variants. The most-used primitive after\n`ren-button`.\n\n- **Variants:** `elevated` (shadow-md), `outline` (border only), `ghost`\n  (no chrome), `sunken` (gray fill).\n- **Special:** `.ren-card-interactive` (full hover lift),\n  `.ren-card-selectable` (radio-behavior + aria-pressed),\n  `.ren-card-simple` (minimal padding).\n- **States:** `[data-status=\"success|warning|danger|info\"]` adds a\n  4px accent border on the leading edge.\n- **A11y:** use real heading tags inside `.ren-card-header`. The whole\n  card becomes a link via nested-anchor technique or full-card click\n  handler.\n\n#### ren-checkbox\n\nCustom-styled checkbox built on native `<input type=\"checkbox\">` — the\ncontrol is visually restyled while the input keeps all native semantics\n(form submission, `:checked`, tab focus).\n\n- **States:** `:checked`, `:indeterminate`, `:disabled`, `:focus-visible`.\n- **A11y:** the native input is hidden via `position: absolute; opacity: 0`\n  so screen readers still see it; the visible `.ren-checkbox-control` is\n  pure decoration.\n\nFor instant on/off toggles (apply immediately, no submit), use the\nsibling primitive `ren-switch` instead.\n\n#### ren-switch\n\nToggle switch built on native `<input type=\"checkbox\" role=\"switch\">` with\na pill-shaped track and animated thumb. Same hidden-input technique as\n`ren-checkbox`; the `role=\"switch\"` announces toggle semantics to assistive\ntechnology.\n\n- **Use when:** the value takes effect immediately (settings, preferences).\n  Use `ren-checkbox` when the value only applies on form submit.\n- **States:** `:checked`, `:disabled`, `:focus-visible`, `:hover`, `:active`.\n- **A11y:** `role=\"switch\"` on the input; the label wrapper carries the full\n  44px touch target via `min-height: var(--touch-min)`.\n\n#### ren-field\n\nWrapper custom element that auto-wires the ARIA relationships between a\nlabel, control, description, and error message. Write natural HTML inside\nit and get `aria-labelledby`, `aria-describedby`, and `aria-errormessage`\nassembled for you.\n\n- **States:** `[data-invalid]`, `[data-valid]`, plus the native\n  `:user-invalid` / `:user-valid` pseudos.\n- **Sizes on inputs:** `.ren-input-sm`, `.ren-input-lg`.\n- **Icons:** wrap the input in `.ren-input-wrapper` with a leading/trailing\n  `.ren-input-icon`.\n- **A11y:** auto-generated stable IDs via `utils/id-generator.js`;\n  required state uses `aria-required`; disabled state uses\n  `aria-disabled`.\n\n#### ren-icon\n\nSizing, coloring, and animation utilities for any SVG. Ships as a CSS\nclass on a wrapper `<span>` (or applied to an inline `<svg>`).\n\n- **Sizes:** `xs` / `sm` / `md` (default) / `lg` / `xl` / `2xl`.\n- **Colors:** `primary`, `success`, `warning`, `danger`, `muted` —\n  applied via `currentColor`.\n- **Animation:** `.ren-icon-spin` (respects reduced motion).\n- **A11y:** decorative by default (`aria-hidden=\"true\"` recommended).\n  Meaningful icons need an adjacent text label or `aria-label`.\n\n#### ren-kbd\n\nKeyboard shortcut badge. Styles `<kbd>` as a rounded chip with a slight\nbevel to read like a physical key. Group consecutive keys with `+`\nseparators.\n\n- **A11y:** preserves native `<kbd>` semantics — screen readers announce\n  \"keyboard input: Command K\".\n\n#### ren-link\n\nStyled `<a>` with variants for different contexts.\n\n- **Variants:** default (underlined accent), `muted` (secondary color),\n  `plain` (no underline), `external` (trailing arrow + `rel=\"noopener\"`),\n  `nav` (for nav bars), `skip` (skip-to-main link).\n- **A11y:** `aria-current=\"page\"` for active nav link; skip link becomes\n  visible on focus and is the first tab stop.\n\n#### ren-pagination\n\nPage navigation for long lists and tables. Hit-tested at 44px minimum.\n\n- **Variants:** default (full numbered set), `simple` (prev + info +\n  next only), `compact` (smaller targets), `centered`.\n- **States:** `[aria-disabled=\"true\"]` or `:disabled` on prev/next,\n  `[aria-current=\"page\"]` on the active number.\n- **A11y:** `<nav aria-label=\"Pagination\">`, real links on each page so\n  the navigation is shareable, `aria-label` on prev/next.\n\n#### ren-progress\n\nLinear progress bar and meter. Use `<progress>` or `<meter>` semantically;\nthe CSS styles their tracks and fills.\n\n- **Sizes:** default, `-sm`, `-lg`, `-xl`.\n- **Variants:** `success`, `warning`, `danger`, `info` recolor the fill.\n- **Special:** `.ren-progress-indeterminate` animates an indeterminate\n  loading bar.\n- **A11y:** `<progress value max>` is already perfect — screen readers\n  announce the percentage. Provide a visible label via\n  `.ren-progress-label`.\n\n#### ren-radio\n\nRadio button group with keyboard navigation (arrow keys, Home/End, roving\ntabindex). Use the `ren-radio-group` custom element as the container; each\nradio is a native `<input type=\"radio\">` in a styled wrapper.\n\n- **Attributes:** `orientation=\"vertical\"` (default) or `\"horizontal\"`.\n- **A11y:** `role=\"radiogroup\"` on the container, arrow-key navigation\n  auto-selects the focused radio, disabled radios skip in the roving\n  cycle.\n\n#### ren-separator\n\nThin horizontal or vertical divider. Pure CSS, no custom element — style\n`<hr class=\"ren-separator\">`.\n\n- **Variants:** default, `-strong`, `-dashed`.\n- **A11y:** native `<hr>` already announces as a separator. Use\n  `aria-orientation=\"vertical\"` on vertical separators inside\n  toolbars / menubars.\n\n#### ren-skeleton\n\nShimmer placeholder for loading states. Apply to any element to replace\nits content with an animated gradient.\n\n- **Shapes:** default rectangle, `.ren-skeleton-text` (pill), \n  `.ren-skeleton-circle` (avatar), `.ren-skeleton-line` (width + height\n  props).\n- **A11y:** announce the skeleton region as busy via `aria-busy=\"true\"`\n  on the parent container; drop a `sr-only` \"Loading…\" for screen\n  readers.\n\n#### ren-spinner\n\nCircular indeterminate loading indicator. 1s infinite rotation, respects\nreduced motion (falls back to a pulsing dot).\n\n- **Sizes:** `sm`, `md` (default), `lg`.\n- **Colors:** inherits `currentColor`; compose with `ren-icon-*` color\n  modifiers.\n- **A11y:** give it an `aria-label=\"Loading\"` or pair with a visible\n  status message in an `aria-live` region.\n\n#### ren-tag\n\nInteractive badge for selected items, filters, or categories. Unlike\n`ren-badge`, tags are actionable — they can be clicked or dismissed.\n\n- **Variants:** color (`primary` / `success` / `warning` / `danger`),\n  size (`sm` / `lg`).\n- **Special:** `.ren-tag-removable` includes a dismiss button,\n  `.ren-tag-clickable` treats the tag as a button (`role=\"button\"`,\n  `tabindex=\"0\"`).\n- **States:** `[aria-pressed=\"true\"]` or `[data-selected]` for selected\n  filter chips.\n- **A11y:** dismiss button needs `aria-label=\"Remove {tag name}\"`.\n\n### Composites (26)\n\nComposites combine primitives with interaction logic — dialogs, menus,\ntabs, date pickers. Most ship as custom elements (`<ren-dialog>`,\n`<ren-menu>`) that wrap semantic HTML and layer behavior on top.\n\n#### ren-accordion\n\nCSS-first accordion built on native `<details><summary>`. Two modes:\n`single` (only one item open at a time) and `multiple`.\n\n- **Variants:** default, `.ren-accordion-bordered`, `.ren-accordion-flush`.\n- **Attributes:** `type`, `collapsible`, `default-value`.\n- **API:** `openItem(i)`, `closeItem(i)`, `toggleItem(i)`,\n  `getOpenItems()`.\n- **Events:** `ren-accordion-change`.\n- **A11y:** native `<details>` semantics; keyboard Enter/Space on the\n  summary toggles.\n\n#### ren-alert-dialog\n\nNon-dismissible confirmation dialog. Extends `ren-dialog` but blocks\nclick-outside and (optionally) Escape. Use for destructive confirmations\nwhere the user must choose an action.\n\n- **Differences from `ren-dialog`:** `alert` attribute, no click-outside\n  dismissal, optional `no-escape` attribute.\n- **A11y:** `role=\"alertdialog\"`, focus trap, title + description wired\n  via `aria-labelledby` / `aria-describedby`.\n\n#### ren-calendar\n\nFully accessible calendar grid with single / range / multiple selection\nmodes. Keyboard-navigable via arrow keys; ARIA grid pattern.\n\n- **Attributes:** `mode` (`single` / `range` / `multiple`), `format`,\n  `min-date`, `max-date`.\n- **API:** `getSelectedDate()`, `setSelectedDate(d)`.\n- **A11y:** days are buttons inside `role=\"grid\"`; today is announced\n  via `aria-label`; outside-month days are `aria-disabled` and use\n  `--color-text-faint` (decorative context).\n\n#### ren-carousel\n\nScroll-snap carousel with keyboard nav, autoplay, loop, and pagination\ndots. Built on `scroll-snap-type` for native-feel momentum on touch.\n\n- **Attributes:** `autoplay` (ms), `loop`, `slides-per-view`, `fade`.\n- **API:** `next()`, `prev()`, `goTo(i)`, `pause()`, `resume()`.\n- **Events:** `ren-slide-change`.\n- **A11y:** arrow key navigation, auto-pauses on hover/focus, dots are\n  real buttons with `aria-label=\"Go to slide N\"`.\n\n#### ren-collapsible\n\nSingle-item CSS-only expand/collapse using `<details>`. For cases where\nan accordion would be overkill.\n\n- **A11y:** native.\n\n#### ren-color-picker\n\nHSV canvas + hue slider + alpha + swatches. Supports HEX / RGB / HSL\noutput formats and the EyeDropper API where available.\n\n- **Attributes:** `value`, `alpha`, `format`, `swatches`.\n- **API:** `getColor()`, `setColor(hex)`.\n- **A11y:** keyboard-navigable canvas (arrow keys = pixel movement, Shift\n  = large steps), all slider inputs are real `<input type=\"range\">`.\n\n#### ren-combobox\n\nSearchable autocomplete input with filtering. Implements the ARIA\ncombobox pattern (1.2 variant — listbox popup).\n\n- **A11y:** `role=\"combobox\"` on the input, `role=\"listbox\"` on the\n  popup, `aria-activedescendant` for keyboard navigation without moving\n  focus.\n\n#### ren-context-menu\n\nRight-click menu. Extends `ren-menu` with `contextmenu` event handling\nand viewport-edge collision positioning.\n\n- **Events:** `ren-context-menu-open { x, y, target }`.\n- **A11y:** reachable via keyboard through the target's context menu\n  key (Menu key or Shift+F10).\n\n#### ren-date-picker\n\nDate input with calendar popover. The field accepts typed input; the\ncalendar gives visual selection; both stay in sync.\n\n- **Attributes:** `format`, `placeholder`, `min-date`, `max-date`.\n- **API:** `open()`, `close()`, `getDate()`, `setDate(d)`.\n- **A11y:** field is a native date-formatted input with validation; the\n  popover uses Popover API so dismissal and focus restoration are\n  native.\n\n#### ren-date-range-picker\n\nTwo-calendar range selector. Start and end dates highlight with a\nshaded span between them; keyboard and pointer both supported.\n\n- **API:** `getRange()`, `setRange({ start, end })`.\n- **A11y:** range is announced as \"Start date: X, End date: Y\" on\n  selection change.\n\n#### ren-dialog\n\nModal dialog using the native `<dialog>` element. Size variants, focus\ntrap, Escape dismissal, click-outside dismissal, mobile bottom-sheet\nbehavior.\n\n- **Classes:** `.ren-dialog-sm / md / lg / xl / full`,\n  `.ren-dialog-header / body / footer`.\n- **API:** `show()`, `close()`, `isOpen`.\n- **Events:** `ren-open`, `ren-close`.\n- **A11y:** native `<dialog>` provides focus management; title and\n  description wired via `aria-labelledby` / `aria-describedby`.\n\n#### ren-dropzone\n\nDrag-and-drop file upload area with visual feedback. Works with a hidden\n`<input type=\"file\">` as the fallback / click-to-browse path.\n\n- **Events:** `ren-files-added { files }`.\n- **A11y:** the file input remains keyboard-focusable; drag-over state\n  announced via `aria-live`.\n\n#### ren-hover-card\n\nRich preview card on hover. Positioned with CSS anchor positioning;\noptional arrow indicator.\n\n- **A11y:** delayed show/hide to avoid flicker; hidden from keyboard users\n  by default (hover-only). For critical info, use `ren-popover` or\n  `ren-tooltip` instead.\n\n#### ren-menu\n\nDropdown menu with keyboard navigation, typeahead, checkbox / radio\nitems. Uses Popover API where available for native dismissal.\n\n- **Classes:** `.ren-menu-item`, `.ren-menu-item-danger`,\n  `.ren-menu-separator`, `.ren-menu-label`, `.ren-menu-icon`,\n  `.ren-menu-shortcut`.\n- **Attributes:** `placement` (top/bottom-start/end), `trigger-id`.\n- **API:** `open()`, `close()`, `isOpen()`.\n- **Events:** `ren-menu-select`, `ren-menu-open`, `ren-menu-close`.\n- **A11y:** WAI-ARIA menu pattern, arrow-key nav, typeahead, Escape to\n  close, returns focus to the trigger.\n\n#### ren-number-field\n\nNumeric input with increment/decrement buttons, min/max bounds, step.\n\n- **API:** `value`, `increment()`, `decrement()`.\n- **A11y:** input is `<input type=\"number\">`; buttons are\n  `aria-label`-ed \"Increment\" / \"Decrement\"; keyboard Up/Down also works\n  on the input.\n\n#### ren-otp\n\nOne-time password input with auto-advancing slots and clipboard paste.\n\n- **Attributes:** `length`, `type` (`numeric` / `alphanumeric`),\n  `disabled`.\n- **API:** `getValue()`, `setValue(str)`, `clear()`.\n- **A11y:** each slot is a real input with `autocomplete=\"one-time-code\"`\n  where supported; pasting a full code fills all slots.\n\n#### ren-popover\n\nFloating popover with CSS anchor positioning and automatic viewport\ncollision handling. Uses Popover API for native dismissal.\n\n- **Classes:** `.ren-popover-arrow`, `.ren-popover-header`,\n  `.ren-popover-body`, `.ren-popover-footer`.\n- **Attributes:** `placement`, `offset`, `trigger-id`.\n- **API:** `open()`, `close()`, `toggle()`, `isOpen()`.\n- **Events:** `ren-open`, `ren-close`.\n- **A11y:** click-outside dismissal, Escape key, `aria-modal`.\n\n#### ren-scroll-area\n\nCustom scrollbar styling with native scroll behavior. Does not\nre-implement scrolling — keyboard, momentum, wheel, and touch all use\nthe browser's native scroll.\n\n- **Variants:** `.ren-scroll-area-auto` (hide bar, show on scroll).\n- **A11y:** native; bar is decorative.\n\n#### ren-select\n\nCustom select/dropdown with keyboard navigation, multi-select, and\noption groups. Not a native `<select>` — rendered in markup so it can\nshow rich item content (icons, descriptions, shortcuts).\n\n- **Classes:** `.ren-select-trigger`, `.ren-select-icon`,\n  `.ren-select-item`, `.ren-select-label`, `.ren-select-separator`.\n- **Attributes:** `placeholder`, `name`, `multiple`, `size` (`sm`/`lg`).\n- **API:** `open()`, `close()`, `getValue()`, `setValue(val)`.\n- **Events:** `ren-select-change`.\n- **A11y:** `role=\"combobox\"` + `role=\"listbox\"`; typeahead; arrow keys\n  navigate options; Enter selects; Escape closes and returns focus.\n\n#### ren-sheet\n\nSide / bottom sheet with slide-in animation and swipe-to-dismiss. Uses\nPopover API fallback.\n\n- **Attributes:** `data-side` (`left` / `right` / `top` / `bottom`),\n  `data-size` (`sm` / `md` / `lg` / `xl` / `full`).\n- **API:** `show()`, `close()`.\n- **A11y:** focus trap, Escape dismissal, `aria-labelledby` /\n  `aria-describedby` via the header/description slots.\n\n#### ren-slider\n\nRange input with styled track and thumb. Wraps a real\n`<input type=\"range\">`, so it remains a native form control.\n\n- **Variants:** size (`sm` / `lg`), color (`success` / `warning` /\n  `danger`), vertical orientation.\n- **API:** `value` property.\n- **Events:** `ren-slider-input`, `ren-slider-change`.\n- **A11y:** native `<input type=\"range\">` with `aria-valuenow` mirrored\n  to the visible `.ren-slider-value` label; keyboard arrow keys step.\n\n#### ren-tabs\n\nAccessible tabbed interface. Arrow keys navigate; Home/End jump to first\n/ last; `activation=\"manual\"` (Enter/Space to activate) or `\"automatic\"`\n(focus = active).\n\n- **Variants:** `.ren-tab-list-underline` (default), `-pills`,\n  `-enclosed`.\n- **API:** `selectTabByIndex(i)`, `selectTabById(id)`, `selectedIndex`,\n  `selectedTab`, `selectedPanel`.\n- **Events:** `ren-tab-change`.\n- **A11y:** WAI-ARIA tablist pattern with roving tabindex; panels have\n  `role=\"tabpanel\"` and `aria-labelledby` pointing to their tab.\n\n#### ren-toast\n\nToast notification system. Imperative API (`toast(msg)`) creates a\nviewport-positioned stack with auto-dismiss.\n\n- **Types:** `success`, `error`, `warning`, `info`, `loading`.\n- **Positions:** six (top/bottom × left/center/right).\n- **A11y:** renders into a persistent `aria-live=\"polite\"` region\n  (or `assertive` for `error`); each toast has a real dismiss button.\n\n#### ren-toggle-group\n\nGrouped toggle buttons for single or multiple selection. Like radio\nbutton group, but button-shaped.\n\n- **Classes:** `.ren-toggle-group-item`, plus `-outline`, `-full`,\n  `-vertical` modifiers.\n- **Attributes:** `type` (`single` / `multiple`), `value`.\n- **A11y:** roving tabindex, arrow-key nav, `aria-pressed` on each\n  item (single mode uses `role=\"radiogroup\"` + `aria-checked`).\n\n#### ren-toolbar\n\nGrouped actions with roving tabindex and arrow-key keyboard nav.\nSeparators break the group visually and in screen reader announcements.\n\n- **Classes:** `.ren-toolbar-item`, `.ren-toolbar-separator`.\n- **A11y:** WAI-ARIA toolbar pattern, arrow keys, Home/End.\n\n#### ren-tooltip\n\nLightweight tooltip with hover / focus triggers and configurable delay.\n\n- **Attributes:** `placement`, `show-delay`, `hide-delay`, `offset`.\n- **API:** `show()`, `hide()`, `isOpen()`.\n- **A11y:** appears on hover *and* focus; auto-hides on blur /\n  mouseleave; long-press on touch. Tooltip content is wired to the\n  trigger via `aria-describedby`. Do not use a tooltip for\n  essential-to-read content — tooltips are progressive enhancement.\n\n### Patterns (8)\n\nPatterns are page-scale organisms: nav bars, sidebars, tables, full\nforms. They bring layout responsibilities of their own (responsive\nbreakpoints, container queries, mobile overlays) and compose multiple\ncomposites and primitives inside.\n\n#### ren-ai\n\nCSS-only visual patterns for AI-generated content, inspired by IBM\nCarbon for AI. Provides the vocabulary — slug, message bubble, streaming\ncursor, typing dots, citation marker, sources list, confidence bar,\nskeleton — without prescribing a chat framework.\n\n- **Classes:** `.ren-ai-slug`, `.ren-ai-message`, `.ren-ai-streaming`,\n  `.ren-ai-typing`, `.ren-ai-citation`, `.ren-ai-sources`,\n  `.ren-ai-confidence` (with `data-level=\"high|medium|low\"`),\n  `.ren-ai-prompt`, `.ren-ai-skeleton`.\n- **A11y:** every animated element respects reduced motion. The slug\n  reads as \"AI\"; citations link to their source. The streaming cursor\n  should be paired with an `aria-live` region that gets the finished\n  text, not the character-by-character stream.\n\n#### ren-command\n\nCommand palette / Spotlight. Built on `<dialog>` with real-time\nfiltering, keyboard navigation, groups, empty state, and a global\nkeyboard shortcut.\n\n- **Classes:** `.ren-command`, `.ren-command-input-wrapper`,\n  `.ren-command-input`, `.ren-command-list`, `.ren-command-group`\n  (hides with `data-empty`), `.ren-command-item` (highlighted via\n  `data-highlighted`), `.ren-command-empty`.\n- **Attributes:** `data-shortcut` (e.g., `ctrl+k`).\n- **API:** `open()`, `close()`, `registerAction(name, fn)`.\n- **Events:** `ren-command-select { item, value, action }`.\n- **A11y:** focus moves to the input on open; arrow keys walk the\n  (flat, across groups) item list; Escape closes and returns focus;\n  selection is announced by the input's aria-activedescendant.\n\n#### ren-empty-state\n\nReusable layout for empty / no-data states. CSS-only.\n\n- **Classes:** `.ren-empty-state`, `.ren-empty-state-icon`,\n  `.ren-empty-state-title`, `.ren-empty-state-description`,\n  `.ren-empty-state-actions`; plus `-compact` and `-bordered`\n  modifiers.\n- **A11y:** semantic heading inside the pattern (H2 or H3 depending on\n  page context). Text is load-bearing — do not communicate the empty\n  state through icon alone.\n\n#### ren-form\n\nMulti-step form with validation. Supports field-level rules, error\nsummaries, step progress, and server-side error injection.\n\n- **Classes:** `.ren-form`, `.ren-form-section`, `.ren-form-row`\n  (responsive field columns), `.ren-form-step` (`data-active`,\n  `data-completed`, `data-disabled`), `.ren-form-error-summary`\n  (shows with `data-has-errors`), `.ren-form-actions`.\n- **Validation rules:** `required`, `email`, `min:N`, `max:N`,\n  `pattern:/regex/`, `match:fieldName`, plus user-registered custom\n  rules via `RenForm.registerValidator(name, fn)`.\n- **API:** `validate()`, `getValues()`, `reset()`, `setErrors(...)`,\n  `setFieldError(name, message)`, `nextStep()`, `prevStep()`.\n- **Events:** `ren-submit`, `ren-invalid`, `ren-step-change`,\n  `ren-field-validated`.\n- **A11y:** `aria-invalid` on bad fields, `aria-errormessage` linking\n  to the error text, required fields marked with `data-required`, the\n  error summary receives focus on validation failure so screen readers\n  announce it and keyboard users land where they need to be.\n\n#### ren-menubar\n\nDesktop-style horizontal menu bar (File / Edit / View). Implements the\nfull WAI-ARIA menubar pattern with submenus, checkbox / radio items,\nseparators, labels, typeahead, and keyboard shortcuts display.\n\n- **Classes:** `.ren-menubar`, `.ren-menubar-trigger`,\n  `.ren-menubar-menu` (uses `hidden` attribute), `.ren-menubar-item`,\n  `.ren-menubar-separator`, `.ren-menubar-label`,\n  `.ren-menubar-submenu` (with chevron), `.ren-menubar-checkbox`\n  (with `aria-checked`), `.ren-menubar-radio`.\n- **API:** `closeAll()`, `openMenu(triggerIndex)`.\n- **Events:** `ren-menubar-select { item, value, checked? }`.\n- **Keyboard:** arrow Right/Left between triggers, arrow Down/Up inside\n  menus, Enter/Space to activate, Escape to close, Home/End to jump,\n  typeahead to land on an item by first letter.\n- **A11y:** full menubar pattern; `aria-expanded` on triggers,\n  `aria-checked` on checkbox/radio items; roving focus with visible\n  indicators.\n\n#### ren-nav\n\nResponsive top navigation bar with mobile hamburger menu, dropdown\nsupport, and sticky / transparent variants.\n\n- **Classes:** `.ren-nav`, `.ren-nav-sticky` (sticky + backdrop blur),\n  `.ren-nav-transparent`, `.ren-nav-links`, `.ren-nav-link`,\n  `.ren-nav-dropdown`, `.ren-nav-brand`, `.ren-nav-actions`,\n  `.ren-nav-toggle` (hamburger).\n- **API:** `setActiveLink(href)`, `isOpen` getter.\n- **Keyboard:** Escape closes mobile menu; Tab navigates.\n- **A11y:** `aria-expanded` on the toggle, `aria-current=\"page\"` on\n  the active link, skip-link friendly (nav is not trapped in focus\n  order).\n\n#### ren-sidebar\n\nApp-shell sidebar with desktop collapse, mobile overlay, state\npersistence, and tooltips when collapsed.\n\n- **Classes:** `.ren-sidebar` (uses `data-collapsed`, `data-open`),\n  `.ren-sidebar-header`, `.ren-sidebar-content`, `.ren-sidebar-nav`,\n  `.ren-sidebar-item`, `.ren-sidebar-item-icon`,\n  `.ren-sidebar-item-text`, `.ren-sidebar-section-label`,\n  `.ren-sidebar-overlay`, `.ren-sidebar-toggle`.\n- **API:** `toggleMenu()`, `setActiveItem(href)`, `openMobileMenu()`,\n  `isCollapsed` getter.\n- **Events:** `ren-sidebar-toggle { collapsed }`.\n- **Keyboard:** Escape closes mobile overlay.\n- **A11y:** `aria-current=\"page\"` on the active item; when collapsed,\n  each item exposes its label via `data-tooltip` → `ren-tooltip` so\n  the sidebar remains usable.\n\n#### ren-table\n\nData table with sorting, row selection, column resize, pagination, and\ndensity variants. The heaviest pattern in the system.\n\n- **Classes:** `.ren-table`, `.ren-table-wrapper`, `.ren-table-toolbar`,\n  `.ren-table-header`, `.ren-th`, `.ren-th-sortable` (gets\n  `data-sort=\"asc|desc\"`), `.ren-table-body`, `.ren-tr` (gets\n  `aria-selected`), `.ren-td`, `.ren-table-select` (checkbox column),\n  `.ren-table-pagination`; plus `-compact` and `-comfortable` density\n  modifiers.\n- **API:** `setLoading(bool)` (shimmer overlay), `setDensity(mode)`,\n  `getSelectedRows()`, `clearSelection()`, `filter(colIdx, value)`.\n- **Events:** `ren-sort`, `ren-select`, `ren-page`, `ren-filter`.\n- **Keyboard:** arrow keys navigate cells, Space toggles row selection,\n  Shift+Click range-selects, Enter on a sortable header sorts.\n- **A11y:** `aria-sort` on sortable headers, `aria-selected` on rows,\n  checkbox column has a clear `aria-label` on the select-all\n  checkbox.\n\n---\n\n## Do's and Don'ts\n\nShort, prescriptive rules distilled from the sections above. An agent\ngenerating RenDS UI should treat these as non-negotiable.\n\n### Tokens\n\n**Do** reach for the semantic layer (`--color-accent`, `--space-card-padding`,\n`--duration-enter`). Those names exist so components don't have to make\nthe decision twice.\n\n**Do** add a new semantic token when no existing name fits. Prefer\nextending the semantic layer over hard-coding a primitive value.\n\n**Don't** consume primitive tokens directly in a component (`--blue-600`,\n`--space-4`, `--duration-normal`). Primitives are the palette the\nsemantic layer paints from — they are not the API surface for\ncomponents.\n\n**Don't** hard-code hex, rem, or px values inside a component stylesheet.\nIf a value is worth writing, it's worth naming.\n\n### Colors\n\n**Do** use `--color-border-interactive` on any control that's actionable\n(inputs, unchecked checkboxes, toggle-outline buttons). It's the only\nborder token guaranteed to pass WCAG 1.4.11 ≥3:1.\n\n**Do** use `--color-*-strong` for accent/status *text* on neutral\nsurfaces. The base variants are tuned as *backgrounds*, not as\nforeground text on white.\n\n**Do** place `--color-on-X` text on `--color-X` backgrounds. Those pairs\nare contrast-tuned together.\n\n**Don't** use `--color-text-faint` for anything the user is supposed to\nread. It's for breadcrumb separators, outside-month calendar dates, and\nplaceholder hints only — reserved as the WCAG-exempt incidental ramp.\n\n**Don't** write `.dark { ... }` overrides. Use `light-dark()` in the\nsemantic layer instead.\n\n**Don't** invent a new accent color per feature. Lean on the status\nfamily (success/warning/danger/info) or the AI family (`ren-ai`) before\nreaching for a primitive hue.\n\n### Typography\n\n**Do** compose from the semantic roles (`body`, `title-md`, `label`,\netc.). Each bundles the right weight, leading, and tracking.\n\n**Do** keep body text at `--body-size` (16px). The browser default\nexists for a reason, and accessibility audits check it.\n\n**Don't** drop below `--text-xs` (11px) anywhere in the UI. Apple HIG's\nminimum is audited against iOS; the web equivalent is the same rule.\n\n**Don't** pick sizes from the primitive scale in component CSS. Use the\nrole tokens so density and fluid-type overrides cascade.\n\n### Layout\n\n**Do** snap to the 8pt grid. All spacing should be a multiple of 8 (or a\nhalf-step of 4 for tight groups).\n\n**Do** prefer container queries over viewport media queries when\nresponsive behavior depends on the component's own width.\n\n**Do** honor the 44px minimum touch target, even when the visual control\nis smaller — use padding or `::before` to expand the hit area.\n\n**Don't** use `margin` between siblings when a parent `gap` would do.\nGap respects flow direction and RTL automatically.\n\n**Don't** set fixed widths on text containers unless you're constraining\nto `--width-prose` (65ch).\n\n### Elevation & Shapes\n\n**Do** communicate depth with the shadow ladder, not with surface color.\nThe system is tuned to stay legible in dark mode via the shadow-color\ndecomposition — you get depth for free.\n\n**Do** match radius to control size. Small controls get `--radius-sm`\n(4px); cards get `--radius-lg` (12px); sheets get `--radius-2xl` (20px).\n\n**Don't** mix pill and non-pill shapes within the same component family.\nButton groups with one round and three square items look broken.\n\n**Don't** set `z-index` to an arbitrary number. Pick the closest\n`--z-*` token — modal, popover, tooltip, etc.\n\n### Motion\n\n**Do** use the semantic durations (`--duration-enter`, `--duration-exit`,\n`--duration-tactile`, `--duration-state`). They encode the exit-faster-\nthan-enter rule.\n\n**Do** pair durations with their matching easing: enter → `--ease-enter`\n(decelerate), exit → `--ease-exit` (accelerate), state → `--ease-state-change`\n(standard).\n\n**Do** animate via the preset `--transition-*` variables when you need\na composed value — `--transition-tactile`, `--transition-state`,\n`--transition-enter`, `--transition-exit`, `--transition-overlay`.\n\n**Don't** write raw durations or easings in a component. The primitive\nvalues exist to back the semantic layer, not for component consumption.\n\n**Don't** add animation that doesn't communicate a state change.\nDecorative motion is noise; every transition should answer \"what just\nbecame true?\".\n\n### Accessibility\n\n**Do** use `:focus-visible`, never `:focus`. Mouse users don't need to\nsee the ring.\n\n**Do** wire ARIA relationships via `aria-labelledby` /\n`aria-describedby` / `aria-errormessage`. Let `ren-field` do this for\nforms — it already handles ID generation.\n\n**Do** label every icon-only button with `aria-label`.\n\n**Do** announce dynamic content via `aria-live` — errors, toasts, loading\ncompletions. Polite by default, assertive only when the user must stop\nand attend.\n\n**Don't** trap keyboard focus outside of modal dialogs and sheets. Every\nother component should let Tab walk out of it.\n\n**Don't** convey state through color alone. Pair danger red with an\nicon, a `danger` label, or `aria-invalid`. Same for success green.\n\n**Don't** use `autofocus` outside of modal dialogs. It fights with\nscreen readers and keyboard users on normal pages.\n\n### Theming\n\n**Do** set themes with `<html data-theme=\"name\">` and honor\n`color-scheme: light dark` on `:root`.\n\n**Do** use the `rends/create` generator to derive an AA-safe theme from\na brand hex. It handles strong-variant tuning automatically.\n\n**Don't** override semantic tokens inside a component — override at\n`:root` or at a theme scope so every component adopts the change.\n\n**Don't** ship multiple theme CSS files to the browser. RenDS expects one\nactive theme at a time via `[data-theme]`.\n\n### Reduced motion\n\n**Do** rely on the token-level collapse — every `--duration-*` becomes\n`0ms` under `prefers-reduced-motion`, and every component that uses\nsemantic durations inherits the behavior.\n\n**Don't** add `@media (prefers-reduced-motion: reduce) { animation: none }`\nas a patch. If the motion token system is being bypassed, fix that\ninstead.\n\n### Web components\n\n**Do** keep markup in light DOM. Style components with the host's own\nCSS; don't tuck interaction-critical structure inside Shadow DOM.\n\n**Do** expose state via attributes (`data-state`, `aria-expanded`,\n`data-loading`) so consumers can style and query without JS.\n\n**Don't** require JS for presentation. Every RenDS component works\nvisually with JS disabled — behaviors layer on top, they don't gate the\npaint.\n\n### Astro integration\n\n**Do** use `@ren10/astro` for Astro 7 projects. Import production components\nfrom direct subpaths such as `@ren10/astro/components/Button`; the generated\nwrappers preserve canonical Light DOM markup, CSS, and vanilla behavior.\n\n**Do** use `@ren10/astro/catalog.json` or `npx ren10 manifest --json` for\nmachine discovery. Read the catalog's colocated contract before composing a\ncomponent.\n\n**Don't** recreate RenDS components as framework islands. Astro is a build-time\ndelivery layer here, not a replacement component API. See `docs/astro.md`.\n",
      "path": "ren-design.md",
      "id": "foundation:design-contract",
      "type": "design-contract",
      "name": "RenDS design contract"
    },
    {
      "data": {},
      "body": "# Layout Contract\n\nLoad this file before writing custom layout CSS. RenDS layouts are public API:\nagents should compose with layout primitives first and only write bespoke\nlayout CSS when the primitive set does not cover the problem.\n\n## Purpose\n\n- Keep page structure consistent across RenDS consumers.\n- Avoid one-off `display: flex` and `display: grid` rules that duplicate the\n  foundation.\n- Ensure responsive behavior follows the same spacing, width, and container\n  rules as the rest of the system.\n\n## Routing Table\n\n| Need | Use | Avoid |\n|---|---|---|\n| Vertical flow | `ren-stack`, `ren-stack-sm`, `ren-stack-lg` | Custom column flex |\n| Inline wrapping group | `ren-cluster` | Custom wrap flex |\n| Horizontal row | `ren-row` | Repeated `display:flex; align-items:center` |\n| Left/right spread | `ren-row-spread` | Custom `justify-content: space-between` |\n| Centered content | `ren-center`, `ren-center-narrow`, `ren-center-wide`, `ren-center-prose` | Fixed width + auto margins |\n| Responsive cards | `ren-grid`, `ren-grid-2`, `ren-grid-3`, `ren-grid-4` | Hand-authored repeat grids |\n| Sidebar + content | `ren-with-sidebar` | Custom fixed sidebar layout |\n| Full-screen centered surface | `ren-cover` | `min-height:100vh` centering |\n| Aspect-ratio media | `ren-frame`, `ren-frame-square`, `ren-frame-photo` | Ad hoc aspect wrappers |\n| Responsive direction switch | `ren-switcher` | Viewport-only flex swaps |\n| Horizontal scroll | `ren-reel` | Raw overflow and scroll snap |\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/index.css\">\n```\n\n## Rules\n\n- Decide the page skeleton before selecting components.\n- Use layout primitives for structure, then place primitives/composites/patterns\n  inside them.\n- Prefer `gap` on the parent over margins on children.\n- Prefer container-query helpers when behavior depends on component width.\n- Keep nested layout primitives shallow; flatten structure when possible.\n- Use semantic spacing tokens such as `--space-*`, `--space-card-padding`, and\n  `--space-section` when a custom value is unavoidable.\n\n## Related Files\n\n- `base/layouts.css` - implementation source of truth.\n- `base/grid.css` - grid helpers.\n- `base/utilities.css` - small utilities.\n- `rends-skill/references/architecture.md` - expanded agent guidance.\n- `docs/layouts.html` - visual reference.\n\n## Test Expectations\n\n- Layout changes should keep responsive docs/pages readable at mobile and\n  desktop widths.\n- Do not add raw layout CSS when an existing primitive can express the same\n  structure.\n",
      "path": "base/layouts.md",
      "id": "foundation:layouts-contract",
      "type": "layouts-contract",
      "name": "Layout primitives contract"
    },
    {
      "data": {},
      "body": "# Primitive Zero Contract\n\nPrimitive Zero is RenDS' classless layer: native HTML elements styled through\n`base/classless.css` before any `ren-` component class is introduced. Load this\nfile when an agent is about to write semantic HTML, prose content, form\nstructure, tables, media, or document scaffolding without a component class.\n\n## Purpose\n\n- Preserve browser semantics first: headings, paragraphs, lists, tables, forms,\n  media, landmarks, and disclosure elements should be valid native HTML before\n  they become RenDS components.\n- Provide readable default typography, spacing, focus rings, and reduced-motion\n  behavior through tokens.\n- Define when a tag graduates from classless HTML into a primitive, composite,\n  or pattern.\n\n## Use Primitive Zero When\n\n- The element is meaningful HTML on its own: `h1`, `p`, `a`, `ul`, `ol`,\n  `blockquote`, `table`, `fieldset`, `details`, `summary`, `figure`, `code`,\n  `pre`, `kbd`, `mark`, `del`, `ins`, landmarks, and form labels.\n- The visual behavior can be handled by `base/classless.css` and semantic\n  tokens.\n- The user benefits from native browser behavior even if JavaScript is disabled.\n\n## Graduate To A Component When\n\n- The element needs a named public API, variants, state, or custom properties.\n- Interaction requires ARIA wiring, roving tabindex, focus trapping, dismiss\n  behavior, typeahead, live-region announcements, or JS enhancement.\n- A repeated UI object needs a `ren-` class so consumers can theme it\n  intentionally.\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/index.css\">\n```\n\n`rends/index.css` imports reset, tokens, base classless styles, layouts, motion\npresets, and utilities. Component CSS is not required for Primitive Zero.\n\n## Accessibility Contract\n\n- Use landmarks for structure, not decoration. Primitive Zero applies no visual\n  chrome to `header`, `nav`, `main`, `article`, `section`, `aside`, or `footer`.\n- Use `:focus-visible` behavior from the foundation; do not remove the focus\n  ring.\n- Keep body text readable: do not drop below the RenDS minimum type scale.\n- Respect `prefers-reduced-motion`; foundation tokens collapse motion before\n  component overrides.\n- Do not use color alone for state. Pair error/success/warning with text,\n  icons, or ARIA where needed.\n\n## Related Files\n\n- `base/classless.css` - implementation source of truth.\n- `base/reset.css` - browser normalization.\n- `base/motion-alternatives.css` and `base/motion-presets.css` - motion safety.\n- `docs/primitive-zero.html` - visual reference and element catalog.\n- `_archive/PRIMITIVE-ZERO-A11Y-AUDIT.md` - historical audit context.\n\n## Test Expectations\n\n- `npm run test:a11y` must keep `docs/primitive-zero.html` free of axe\n  violations.\n- Manual review should cover keyboard focus, headings, links, table captions,\n  form labels, `details/summary`, and code/pre wrapping in both light and dark\n  themes.\n",
      "path": "base/primitive-zero.md",
      "id": "foundation:primitive-zero",
      "type": "primitive-zero",
      "name": "Native HTML contract"
    },
    {
      "data": {},
      "body": "# Token Contract\n\nLoad this file before choosing colors, spacing, typography, radius, shadows,\nmotion, z-index, or component-level theme overrides.\n\n## Purpose\n\nRenDS uses a three-layer token model:\n\n1. Primitive tokens are raw values: `--blue-500`, `--space-4`,\n   `--text-base`, `--radius-md`.\n2. Semantic tokens describe intent: `--color-accent`,\n   `--color-text-muted`, `--space-card-padding`, `--duration-enter`.\n3. Component tokens expose public override points:\n   `--ren-btn-bg`, `--ren-card-radius`, `--ren-dialog-width`.\n\nComponents consume semantic or component tokens. They do not consume primitive\ntokens directly.\n\n## Required Imports\n\n```html\n<link rel=\"stylesheet\" href=\"rends/index.css\">\n<link rel=\"stylesheet\" href=\"rends/tokens/component/tokens.css\">\n```\n\nUse `tokens/component/tokens.css` when a page or component depends on the\ncomponent token API.\n\n## Rules\n\n- Use semantic tokens for day-to-day UI values.\n- Add a semantic token when an intent is missing; do not hard-code raw values.\n- Use component tokens for surgical component theming.\n- Do not override semantic tokens inside a component implementation; override\n  at `:root`, `[data-theme]`, or a consumer-owned theme scope.\n- Use `light-dark()` in the semantic layer; do not write `.dark` patches.\n- Use `--color-border-interactive` for actionable control borders.\n- Use `--color-*-strong` for status/accent text on neutral surfaces.\n- Use `--color-on-*` only on the matching solid background.\n- Pair `--color-on-surface-contrast` only with\n  `--color-surface-contrast` for editorial surfaces that remain dark in every\n  theme.\n- Do not use `--color-text-faint` for information the user must read.\n- Use semantic motion tokens and transition presets; do not write raw\n  durations or easings in components.\n\n## Related Files\n\n- `tokens/primitives/*.css` - raw palettes and scales.\n- `tokens/semantic/*.css` - intent-based token layer.\n- `tokens/component/tokens.css` - public component override API.\n- `tokens/registered-properties.css` - typed CSS custom properties.\n- `rends-skill/references/tokens.md` - expanded agent reference.\n- `docs/tokens.html` - visual docs page.\n\n## Test Expectations\n\n- CSS lint should pass after token edits.\n- Accessibility/a11y checks should pass when color pairings change.\n- Any new component value should be expressible as a semantic or component\n  token before shipping.\n",
      "path": "tokens/tokens.md",
      "id": "foundation:tokens-contract",
      "type": "tokens-contract",
      "name": "Token contract"
    },
    {
      "data": {},
      "body": ".ren-accordion",
      "path": "components/composites/ren-collapsible/ren-collapsible.css",
      "id": "selector:.ren-accordion",
      "type": "selector",
      "name": ".ren-accordion"
    },
    {
      "data": {},
      "body": ".ren-accordion-bordered",
      "path": "components/composites/ren-accordion/ren-accordion.css",
      "id": "selector:.ren-accordion-bordered",
      "type": "selector",
      "name": ".ren-accordion-bordered"
    },
    {
      "data": {},
      "body": ".ren-accordion-content",
      "path": "components/composites/ren-accordion/ren-accordion.css",
      "id": "selector:.ren-accordion-content",
      "type": "selector",
      "name": ".ren-accordion-content"
    },
    {
      "data": {},
      "body": ".ren-accordion-flush",
      "path": "components/composites/ren-accordion/ren-accordion.css",
      "id": "selector:.ren-accordion-flush",
      "type": "selector",
      "name": ".ren-accordion-flush"
    },
    {
      "data": {},
      "body": ".ren-accordion-item",
      "path": "components/composites/ren-accordion/ren-accordion.css",
      "id": "selector:.ren-accordion-item",
      "type": "selector",
      "name": ".ren-accordion-item"
    },
    {
      "data": {},
      "body": ".ren-accordion-trigger",
      "path": "components/composites/ren-accordion/ren-accordion.css",
      "id": "selector:.ren-accordion-trigger",
      "type": "selector",
      "name": ".ren-accordion-trigger"
    },
    {
      "data": {},
      "body": ".ren-ai-action",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-action",
      "type": "selector",
      "name": ".ren-ai-action"
    },
    {
      "data": {},
      "body": ".ren-ai-action-edit",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-action-edit",
      "type": "selector",
      "name": ".ren-ai-action-edit"
    },
    {
      "data": {},
      "body": ".ren-ai-action-regenerate",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-action-regenerate",
      "type": "selector",
      "name": ".ren-ai-action-regenerate"
    },
    {
      "data": {},
      "body": ".ren-ai-action-thumbs",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-action-thumbs",
      "type": "selector",
      "name": ".ren-ai-action-thumbs"
    },
    {
      "data": {},
      "body": ".ren-ai-actions",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-actions",
      "type": "selector",
      "name": ".ren-ai-actions"
    },
    {
      "data": {},
      "body": ".ren-ai-citation",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-citation",
      "type": "selector",
      "name": ".ren-ai-citation"
    },
    {
      "data": {},
      "body": ".ren-ai-confidence",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-confidence",
      "type": "selector",
      "name": ".ren-ai-confidence"
    },
    {
      "data": {},
      "body": ".ren-ai-confidence-bar",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-confidence-bar",
      "type": "selector",
      "name": ".ren-ai-confidence-bar"
    },
    {
      "data": {},
      "body": ".ren-ai-confidence-fill",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-confidence-fill",
      "type": "selector",
      "name": ".ren-ai-confidence-fill"
    },
    {
      "data": {},
      "body": ".ren-ai-error",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-error",
      "type": "selector",
      "name": ".ren-ai-error"
    },
    {
      "data": {},
      "body": ".ren-ai-error-message",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-error-message",
      "type": "selector",
      "name": ".ren-ai-error-message"
    },
    {
      "data": {},
      "body": ".ren-ai-file-chip",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-file-chip",
      "type": "selector",
      "name": ".ren-ai-file-chip"
    },
    {
      "data": {},
      "body": ".ren-ai-file-chip-icon",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-file-chip-icon",
      "type": "selector",
      "name": ".ren-ai-file-chip-icon"
    },
    {
      "data": {},
      "body": ".ren-ai-file-chip-name",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-file-chip-name",
      "type": "selector",
      "name": ".ren-ai-file-chip-name"
    },
    {
      "data": {},
      "body": ".ren-ai-file-chip-remove",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-file-chip-remove",
      "type": "selector",
      "name": ".ren-ai-file-chip-remove"
    },
    {
      "data": {},
      "body": ".ren-ai-file-chip-size",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-file-chip-size",
      "type": "selector",
      "name": ".ren-ai-file-chip-size"
    },
    {
      "data": {},
      "body": ".ren-ai-message",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-message",
      "type": "selector",
      "name": ".ren-ai-message"
    },
    {
      "data": {},
      "body": ".ren-ai-message-content",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-message-content",
      "type": "selector",
      "name": ".ren-ai-message-content"
    },
    {
      "data": {},
      "body": ".ren-ai-message-footer",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-message-footer",
      "type": "selector",
      "name": ".ren-ai-message-footer"
    },
    {
      "data": {},
      "body": ".ren-ai-message-header",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-message-header",
      "type": "selector",
      "name": ".ren-ai-message-header"
    },
    {
      "data": {},
      "body": ".ren-ai-prompt",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-prompt",
      "type": "selector",
      "name": ".ren-ai-prompt"
    },
    {
      "data": {},
      "body": ".ren-ai-prompt-input",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-prompt-input",
      "type": "selector",
      "name": ".ren-ai-prompt-input"
    },
    {
      "data": {},
      "body": ".ren-ai-prompt-send",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-prompt-send",
      "type": "selector",
      "name": ".ren-ai-prompt-send"
    },
    {
      "data": {},
      "body": ".ren-ai-skeleton",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-skeleton",
      "type": "selector",
      "name": ".ren-ai-skeleton"
    },
    {
      "data": {},
      "body": ".ren-ai-skeleton-line",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-skeleton-line",
      "type": "selector",
      "name": ".ren-ai-skeleton-line"
    },
    {
      "data": {},
      "body": ".ren-ai-slug",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-slug",
      "type": "selector",
      "name": ".ren-ai-slug"
    },
    {
      "data": {},
      "body": ".ren-ai-source",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-source",
      "type": "selector",
      "name": ".ren-ai-source"
    },
    {
      "data": {},
      "body": ".ren-ai-source-number",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-source-number",
      "type": "selector",
      "name": ".ren-ai-source-number"
    },
    {
      "data": {},
      "body": ".ren-ai-source-title",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-source-title",
      "type": "selector",
      "name": ".ren-ai-source-title"
    },
    {
      "data": {},
      "body": ".ren-ai-source-url",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-source-url",
      "type": "selector",
      "name": ".ren-ai-source-url"
    },
    {
      "data": {},
      "body": ".ren-ai-sources",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-sources",
      "type": "selector",
      "name": ".ren-ai-sources"
    },
    {
      "data": {},
      "body": ".ren-ai-streaming",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-streaming",
      "type": "selector",
      "name": ".ren-ai-streaming"
    },
    {
      "data": {},
      "body": ".ren-ai-tool-call",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-tool-call",
      "type": "selector",
      "name": ".ren-ai-tool-call"
    },
    {
      "data": {},
      "body": ".ren-ai-tool-call-args",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-tool-call-args",
      "type": "selector",
      "name": ".ren-ai-tool-call-args"
    },
    {
      "data": {},
      "body": ".ren-ai-tool-call-header",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-tool-call-header",
      "type": "selector",
      "name": ".ren-ai-tool-call-header"
    },
    {
      "data": {},
      "body": ".ren-ai-tool-call-name",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-tool-call-name",
      "type": "selector",
      "name": ".ren-ai-tool-call-name"
    },
    {
      "data": {},
      "body": ".ren-ai-tool-call-result",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-tool-call-result",
      "type": "selector",
      "name": ".ren-ai-tool-call-result"
    },
    {
      "data": {},
      "body": ".ren-ai-tool-call-status",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-tool-call-status",
      "type": "selector",
      "name": ".ren-ai-tool-call-status"
    },
    {
      "data": {},
      "body": ".ren-ai-typing",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-typing",
      "type": "selector",
      "name": ".ren-ai-typing"
    },
    {
      "data": {},
      "body": ".ren-ai-typing-dots",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "selector:.ren-ai-typing-dots",
      "type": "selector",
      "name": ".ren-ai-typing-dots"
    },
    {
      "data": {},
      "body": ".ren-alert-dialog",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "selector:.ren-alert-dialog",
      "type": "selector",
      "name": ".ren-alert-dialog"
    },
    {
      "data": {},
      "body": ".ren-alert-dialog-actions",
      "path": "components/composites/ren-alert-dialog/ren-alert-dialog.css",
      "id": "selector:.ren-alert-dialog-actions",
      "type": "selector",
      "name": ".ren-alert-dialog-actions"
    },
    {
      "data": {},
      "body": ".ren-alert-dialog-description",
      "path": "components/composites/ren-alert-dialog/ren-alert-dialog.css",
      "id": "selector:.ren-alert-dialog-description",
      "type": "selector",
      "name": ".ren-alert-dialog-description"
    },
    {
      "data": {},
      "body": ".ren-alert-dialog-icon",
      "path": "components/composites/ren-alert-dialog/ren-alert-dialog.css",
      "id": "selector:.ren-alert-dialog-icon",
      "type": "selector",
      "name": ".ren-alert-dialog-icon"
    },
    {
      "data": {},
      "body": ".ren-alert-dialog-icon-danger",
      "path": "components/composites/ren-alert-dialog/ren-alert-dialog.css",
      "id": "selector:.ren-alert-dialog-icon-danger",
      "type": "selector",
      "name": ".ren-alert-dialog-icon-danger"
    },
    {
      "data": {},
      "body": ".ren-alert-dialog-icon-warning",
      "path": "components/composites/ren-alert-dialog/ren-alert-dialog.css",
      "id": "selector:.ren-alert-dialog-icon-warning",
      "type": "selector",
      "name": ".ren-alert-dialog-icon-warning"
    },
    {
      "data": {},
      "body": ".ren-alert-dialog-title",
      "path": "components/composites/ren-alert-dialog/ren-alert-dialog.css",
      "id": "selector:.ren-alert-dialog-title",
      "type": "selector",
      "name": ".ren-alert-dialog-title"
    },
    {
      "data": {},
      "body": ".ren-avatar",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "selector:.ren-avatar",
      "type": "selector",
      "name": ".ren-avatar"
    },
    {
      "data": {},
      "body": ".ren-avatar-2xl",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "selector:.ren-avatar-2xl",
      "type": "selector",
      "name": ".ren-avatar-2xl"
    },
    {
      "data": {},
      "body": ".ren-avatar-group",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "selector:.ren-avatar-group",
      "type": "selector",
      "name": ".ren-avatar-group"
    },
    {
      "data": {},
      "body": ".ren-avatar-lg",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "selector:.ren-avatar-lg",
      "type": "selector",
      "name": ".ren-avatar-lg"
    },
    {
      "data": {},
      "body": ".ren-avatar-sm",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "selector:.ren-avatar-sm",
      "type": "selector",
      "name": ".ren-avatar-sm"
    },
    {
      "data": {},
      "body": ".ren-avatar-square",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "selector:.ren-avatar-square",
      "type": "selector",
      "name": ".ren-avatar-square"
    },
    {
      "data": {},
      "body": ".ren-avatar-status",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "selector:.ren-avatar-status",
      "type": "selector",
      "name": ".ren-avatar-status"
    },
    {
      "data": {},
      "body": ".ren-avatar-status-away",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "selector:.ren-avatar-status-away",
      "type": "selector",
      "name": ".ren-avatar-status-away"
    },
    {
      "data": {},
      "body": ".ren-avatar-status-busy",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "selector:.ren-avatar-status-busy",
      "type": "selector",
      "name": ".ren-avatar-status-busy"
    },
    {
      "data": {},
      "body": ".ren-avatar-status-offline",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "selector:.ren-avatar-status-offline",
      "type": "selector",
      "name": ".ren-avatar-status-offline"
    },
    {
      "data": {},
      "body": ".ren-avatar-wrapper",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "selector:.ren-avatar-wrapper",
      "type": "selector",
      "name": ".ren-avatar-wrapper"
    },
    {
      "data": {},
      "body": ".ren-avatar-xl",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "selector:.ren-avatar-xl",
      "type": "selector",
      "name": ".ren-avatar-xl"
    },
    {
      "data": {},
      "body": ".ren-avatar-xs",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "selector:.ren-avatar-xs",
      "type": "selector",
      "name": ".ren-avatar-xs"
    },
    {
      "data": {},
      "body": ".ren-badge",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "selector:.ren-badge",
      "type": "selector",
      "name": ".ren-badge"
    },
    {
      "data": {},
      "body": ".ren-badge-danger",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "selector:.ren-badge-danger",
      "type": "selector",
      "name": ".ren-badge-danger"
    },
    {
      "data": {},
      "body": ".ren-badge-dot",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "selector:.ren-badge-dot",
      "type": "selector",
      "name": ".ren-badge-dot"
    },
    {
      "data": {},
      "body": ".ren-badge-dot-danger",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "selector:.ren-badge-dot-danger",
      "type": "selector",
      "name": ".ren-badge-dot-danger"
    },
    {
      "data": {},
      "body": ".ren-badge-dot-success",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "selector:.ren-badge-dot-success",
      "type": "selector",
      "name": ".ren-badge-dot-success"
    },
    {
      "data": {},
      "body": ".ren-badge-dot-warning",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "selector:.ren-badge-dot-warning",
      "type": "selector",
      "name": ".ren-badge-dot-warning"
    },
    {
      "data": {},
      "body": ".ren-badge-info",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "selector:.ren-badge-info",
      "type": "selector",
      "name": ".ren-badge-info"
    },
    {
      "data": {},
      "body": ".ren-badge-outline",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "selector:.ren-badge-outline",
      "type": "selector",
      "name": ".ren-badge-outline"
    },
    {
      "data": {},
      "body": ".ren-badge-primary",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "selector:.ren-badge-primary",
      "type": "selector",
      "name": ".ren-badge-primary"
    },
    {
      "data": {},
      "body": ".ren-badge-secondary",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "selector:.ren-badge-secondary",
      "type": "selector",
      "name": ".ren-badge-secondary"
    },
    {
      "data": {},
      "body": ".ren-badge-success",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "selector:.ren-badge-success",
      "type": "selector",
      "name": ".ren-badge-success"
    },
    {
      "data": {},
      "body": ".ren-badge-warning",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "selector:.ren-badge-warning",
      "type": "selector",
      "name": ".ren-badge-warning"
    },
    {
      "data": {},
      "body": ".ren-banner",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "selector:.ren-banner",
      "type": "selector",
      "name": ".ren-banner"
    },
    {
      "data": {},
      "body": ".ren-banner-actions",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "selector:.ren-banner-actions",
      "type": "selector",
      "name": ".ren-banner-actions"
    },
    {
      "data": {},
      "body": ".ren-banner-compact",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "selector:.ren-banner-compact",
      "type": "selector",
      "name": ".ren-banner-compact"
    },
    {
      "data": {},
      "body": ".ren-banner-content",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "selector:.ren-banner-content",
      "type": "selector",
      "name": ".ren-banner-content"
    },
    {
      "data": {},
      "body": ".ren-banner-danger",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "selector:.ren-banner-danger",
      "type": "selector",
      "name": ".ren-banner-danger"
    },
    {
      "data": {},
      "body": ".ren-banner-dismiss",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "selector:.ren-banner-dismiss",
      "type": "selector",
      "name": ".ren-banner-dismiss"
    },
    {
      "data": {},
      "body": ".ren-banner-full",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "selector:.ren-banner-full",
      "type": "selector",
      "name": ".ren-banner-full"
    },
    {
      "data": {},
      "body": ".ren-banner-icon",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "selector:.ren-banner-icon",
      "type": "selector",
      "name": ".ren-banner-icon"
    },
    {
      "data": {},
      "body": ".ren-banner-message",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "selector:.ren-banner-message",
      "type": "selector",
      "name": ".ren-banner-message"
    },
    {
      "data": {},
      "body": ".ren-banner-neutral",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "selector:.ren-banner-neutral",
      "type": "selector",
      "name": ".ren-banner-neutral"
    },
    {
      "data": {},
      "body": ".ren-banner-success",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "selector:.ren-banner-success",
      "type": "selector",
      "name": ".ren-banner-success"
    },
    {
      "data": {},
      "body": ".ren-banner-title",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "selector:.ren-banner-title",
      "type": "selector",
      "name": ".ren-banner-title"
    },
    {
      "data": {},
      "body": ".ren-banner-warning",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "selector:.ren-banner-warning",
      "type": "selector",
      "name": ".ren-banner-warning"
    },
    {
      "data": {},
      "body": ".ren-breadcrumb",
      "path": "components/primitives/ren-breadcrumb/ren-breadcrumb.css",
      "id": "selector:.ren-breadcrumb",
      "type": "selector",
      "name": ".ren-breadcrumb"
    },
    {
      "data": {},
      "body": ".ren-breadcrumb-truncated",
      "path": "components/primitives/ren-breadcrumb/ren-breadcrumb.css",
      "id": "selector:.ren-breadcrumb-truncated",
      "type": "selector",
      "name": ".ren-breadcrumb-truncated"
    },
    {
      "data": {},
      "body": ".ren-btn",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-btn",
      "type": "selector",
      "name": ".ren-btn"
    },
    {
      "data": {},
      "body": ".ren-btn-accent",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "selector:.ren-btn-accent",
      "type": "selector",
      "name": ".ren-btn-accent"
    },
    {
      "data": {},
      "body": ".ren-btn-danger",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "selector:.ren-btn-danger",
      "type": "selector",
      "name": ".ren-btn-danger"
    },
    {
      "data": {},
      "body": ".ren-btn-full",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "selector:.ren-btn-full",
      "type": "selector",
      "name": ".ren-btn-full"
    },
    {
      "data": {},
      "body": ".ren-btn-ghost",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "selector:.ren-btn-ghost",
      "type": "selector",
      "name": ".ren-btn-ghost"
    },
    {
      "data": {},
      "body": ".ren-btn-group",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "selector:.ren-btn-group",
      "type": "selector",
      "name": ".ren-btn-group"
    },
    {
      "data": {},
      "body": ".ren-btn-icon",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "selector:.ren-btn-icon",
      "type": "selector",
      "name": ".ren-btn-icon"
    },
    {
      "data": {},
      "body": ".ren-btn-lg",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "selector:.ren-btn-lg",
      "type": "selector",
      "name": ".ren-btn-lg"
    },
    {
      "data": {},
      "body": ".ren-btn-link",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "selector:.ren-btn-link",
      "type": "selector",
      "name": ".ren-btn-link"
    },
    {
      "data": {},
      "body": ".ren-btn-outline",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "selector:.ren-btn-outline",
      "type": "selector",
      "name": ".ren-btn-outline"
    },
    {
      "data": {},
      "body": ".ren-btn-primary",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "selector:.ren-btn-primary",
      "type": "selector",
      "name": ".ren-btn-primary"
    },
    {
      "data": {},
      "body": ".ren-btn-secondary",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "selector:.ren-btn-secondary",
      "type": "selector",
      "name": ".ren-btn-secondary"
    },
    {
      "data": {},
      "body": ".ren-btn-sm",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "selector:.ren-btn-sm",
      "type": "selector",
      "name": ".ren-btn-sm"
    },
    {
      "data": {},
      "body": ".ren-calendar",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-calendar",
      "type": "selector",
      "name": ".ren-calendar"
    },
    {
      "data": {},
      "body": ".ren-calendar-day",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "selector:.ren-calendar-day",
      "type": "selector",
      "name": ".ren-calendar-day"
    },
    {
      "data": {},
      "body": ".ren-calendar-day-in-range",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "selector:.ren-calendar-day-in-range",
      "type": "selector",
      "name": ".ren-calendar-day-in-range"
    },
    {
      "data": {},
      "body": ".ren-calendar-day-range-end",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "selector:.ren-calendar-day-range-end",
      "type": "selector",
      "name": ".ren-calendar-day-range-end"
    },
    {
      "data": {},
      "body": ".ren-calendar-day-range-start",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "selector:.ren-calendar-day-range-start",
      "type": "selector",
      "name": ".ren-calendar-day-range-start"
    },
    {
      "data": {},
      "body": ".ren-calendar-grid",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "selector:.ren-calendar-grid",
      "type": "selector",
      "name": ".ren-calendar-grid"
    },
    {
      "data": {},
      "body": ".ren-calendar-header",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "selector:.ren-calendar-header",
      "type": "selector",
      "name": ".ren-calendar-header"
    },
    {
      "data": {},
      "body": ".ren-calendar-lg",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "selector:.ren-calendar-lg",
      "type": "selector",
      "name": ".ren-calendar-lg"
    },
    {
      "data": {},
      "body": ".ren-calendar-nav",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "selector:.ren-calendar-nav",
      "type": "selector",
      "name": ".ren-calendar-nav"
    },
    {
      "data": {},
      "body": ".ren-calendar-next",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "selector:.ren-calendar-next",
      "type": "selector",
      "name": ".ren-calendar-next"
    },
    {
      "data": {},
      "body": ".ren-calendar-prev",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "selector:.ren-calendar-prev",
      "type": "selector",
      "name": ".ren-calendar-prev"
    },
    {
      "data": {},
      "body": ".ren-calendar-range",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "selector:.ren-calendar-range",
      "type": "selector",
      "name": ".ren-calendar-range"
    },
    {
      "data": {},
      "body": ".ren-calendar-sm",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "selector:.ren-calendar-sm",
      "type": "selector",
      "name": ".ren-calendar-sm"
    },
    {
      "data": {},
      "body": ".ren-calendar-title",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "selector:.ren-calendar-title",
      "type": "selector",
      "name": ".ren-calendar-title"
    },
    {
      "data": {},
      "body": ".ren-calendar-weekday",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "selector:.ren-calendar-weekday",
      "type": "selector",
      "name": ".ren-calendar-weekday"
    },
    {
      "data": {},
      "body": ".ren-calendar-weekdays",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "selector:.ren-calendar-weekdays",
      "type": "selector",
      "name": ".ren-calendar-weekdays"
    },
    {
      "data": {},
      "body": ".ren-card",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card",
      "type": "selector",
      "name": ".ren-card"
    },
    {
      "data": {},
      "body": ".ren-card-body",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card-body",
      "type": "selector",
      "name": ".ren-card-body"
    },
    {
      "data": {},
      "body": ".ren-card-cover",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card-cover",
      "type": "selector",
      "name": ".ren-card-cover"
    },
    {
      "data": {},
      "body": ".ren-card-description",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card-description",
      "type": "selector",
      "name": ".ren-card-description"
    },
    {
      "data": {},
      "body": ".ren-card-elevated",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card-elevated",
      "type": "selector",
      "name": ".ren-card-elevated"
    },
    {
      "data": {},
      "body": ".ren-card-footer",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card-footer",
      "type": "selector",
      "name": ".ren-card-footer"
    },
    {
      "data": {},
      "body": ".ren-card-footer-border",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card-footer-border",
      "type": "selector",
      "name": ".ren-card-footer-border"
    },
    {
      "data": {},
      "body": ".ren-card-ghost",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card-ghost",
      "type": "selector",
      "name": ".ren-card-ghost"
    },
    {
      "data": {},
      "body": ".ren-card-group",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card-group",
      "type": "selector",
      "name": ".ren-card-group"
    },
    {
      "data": {},
      "body": ".ren-card-header",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card-header",
      "type": "selector",
      "name": ".ren-card-header"
    },
    {
      "data": {},
      "body": ".ren-card-interactive",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card-interactive",
      "type": "selector",
      "name": ".ren-card-interactive"
    },
    {
      "data": {},
      "body": ".ren-card-outline",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card-outline",
      "type": "selector",
      "name": ".ren-card-outline"
    },
    {
      "data": {},
      "body": ".ren-card-selectable",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card-selectable",
      "type": "selector",
      "name": ".ren-card-selectable"
    },
    {
      "data": {},
      "body": ".ren-card-simple",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card-simple",
      "type": "selector",
      "name": ".ren-card-simple"
    },
    {
      "data": {},
      "body": ".ren-card-sunken",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card-sunken",
      "type": "selector",
      "name": ".ren-card-sunken"
    },
    {
      "data": {},
      "body": ".ren-card-title",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "selector:.ren-card-title",
      "type": "selector",
      "name": ".ren-card-title"
    },
    {
      "data": {},
      "body": ".ren-carousel",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel",
      "type": "selector",
      "name": ".ren-carousel"
    },
    {
      "data": {},
      "body": ".ren-carousel-2",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-2",
      "type": "selector",
      "name": ".ren-carousel-2"
    },
    {
      "data": {},
      "body": ".ren-carousel-3",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-3",
      "type": "selector",
      "name": ".ren-carousel-3"
    },
    {
      "data": {},
      "body": ".ren-carousel-4",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-4",
      "type": "selector",
      "name": ".ren-carousel-4"
    },
    {
      "data": {},
      "body": ".ren-carousel-arrow",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-arrow",
      "type": "selector",
      "name": ".ren-carousel-arrow"
    },
    {
      "data": {},
      "body": ".ren-carousel-counter",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-counter",
      "type": "selector",
      "name": ".ren-carousel-counter"
    },
    {
      "data": {},
      "body": ".ren-carousel-counter-current",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-counter-current",
      "type": "selector",
      "name": ".ren-carousel-counter-current"
    },
    {
      "data": {},
      "body": ".ren-carousel-dot",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-dot",
      "type": "selector",
      "name": ".ren-carousel-dot"
    },
    {
      "data": {},
      "body": ".ren-carousel-dots",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-dots",
      "type": "selector",
      "name": ".ren-carousel-dots"
    },
    {
      "data": {},
      "body": ".ren-carousel-fade",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-fade",
      "type": "selector",
      "name": ".ren-carousel-fade"
    },
    {
      "data": {},
      "body": ".ren-carousel-full",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-full",
      "type": "selector",
      "name": ".ren-carousel-full"
    },
    {
      "data": {},
      "body": ".ren-carousel-next",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-next",
      "type": "selector",
      "name": ".ren-carousel-next"
    },
    {
      "data": {},
      "body": ".ren-carousel-no-arrows",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-no-arrows",
      "type": "selector",
      "name": ".ren-carousel-no-arrows"
    },
    {
      "data": {},
      "body": ".ren-carousel-no-counter",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-no-counter",
      "type": "selector",
      "name": ".ren-carousel-no-counter"
    },
    {
      "data": {},
      "body": ".ren-carousel-no-dots",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-no-dots",
      "type": "selector",
      "name": ".ren-carousel-no-dots"
    },
    {
      "data": {},
      "body": ".ren-carousel-prev",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-prev",
      "type": "selector",
      "name": ".ren-carousel-prev"
    },
    {
      "data": {},
      "body": ".ren-carousel-slide",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-slide",
      "type": "selector",
      "name": ".ren-carousel-slide"
    },
    {
      "data": {},
      "body": ".ren-carousel-viewport",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "selector:.ren-carousel-viewport",
      "type": "selector",
      "name": ".ren-carousel-viewport"
    },
    {
      "data": {},
      "body": ".ren-checkbox",
      "path": "components/primitives/ren-checkbox/ren-checkbox.css",
      "id": "selector:.ren-checkbox",
      "type": "selector",
      "name": ".ren-checkbox"
    },
    {
      "data": {},
      "body": ".ren-checkbox-control",
      "path": "components/primitives/ren-checkbox/ren-checkbox.css",
      "id": "selector:.ren-checkbox-control",
      "type": "selector",
      "name": ".ren-checkbox-control"
    },
    {
      "data": {},
      "body": ".ren-collapsible",
      "path": "components/composites/ren-collapsible/ren-collapsible.css",
      "id": "selector:.ren-collapsible",
      "type": "selector",
      "name": ".ren-collapsible"
    },
    {
      "data": {},
      "body": ".ren-collapsible-content",
      "path": "components/composites/ren-collapsible/ren-collapsible.css",
      "id": "selector:.ren-collapsible-content",
      "type": "selector",
      "name": ".ren-collapsible-content"
    },
    {
      "data": {},
      "body": ".ren-collapsible-ghost",
      "path": "components/composites/ren-collapsible/ren-collapsible.css",
      "id": "selector:.ren-collapsible-ghost",
      "type": "selector",
      "name": ".ren-collapsible-ghost"
    },
    {
      "data": {},
      "body": ".ren-color-picker",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker",
      "type": "selector",
      "name": ".ren-color-picker"
    },
    {
      "data": {},
      "body": ".ren-color-picker-alpha",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-alpha",
      "type": "selector",
      "name": ".ren-color-picker-alpha"
    },
    {
      "data": {},
      "body": ".ren-color-picker-alpha-gradient",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-alpha-gradient",
      "type": "selector",
      "name": ".ren-color-picker-alpha-gradient"
    },
    {
      "data": {},
      "body": ".ren-color-picker-alpha-handle",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-alpha-handle",
      "type": "selector",
      "name": ".ren-color-picker-alpha-handle"
    },
    {
      "data": {},
      "body": ".ren-color-picker-channel",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-channel",
      "type": "selector",
      "name": ".ren-color-picker-channel"
    },
    {
      "data": {},
      "body": ".ren-color-picker-channel-input",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-channel-input",
      "type": "selector",
      "name": ".ren-color-picker-channel-input"
    },
    {
      "data": {},
      "body": ".ren-color-picker-channel-label",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-channel-label",
      "type": "selector",
      "name": ".ren-color-picker-channel-label"
    },
    {
      "data": {},
      "body": ".ren-color-picker-channels",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-channels",
      "type": "selector",
      "name": ".ren-color-picker-channels"
    },
    {
      "data": {},
      "body": ".ren-color-picker-controls",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-controls",
      "type": "selector",
      "name": ".ren-color-picker-controls"
    },
    {
      "data": {},
      "body": ".ren-color-picker-controls-left",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-controls-left",
      "type": "selector",
      "name": ".ren-color-picker-controls-left"
    },
    {
      "data": {},
      "body": ".ren-color-picker-controls-right",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-controls-right",
      "type": "selector",
      "name": ".ren-color-picker-controls-right"
    },
    {
      "data": {},
      "body": ".ren-color-picker-dropdown",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-dropdown",
      "type": "selector",
      "name": ".ren-color-picker-dropdown"
    },
    {
      "data": {},
      "body": ".ren-color-picker-eyedropper",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-eyedropper",
      "type": "selector",
      "name": ".ren-color-picker-eyedropper"
    },
    {
      "data": {},
      "body": ".ren-color-picker-eyedropper-icon",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-eyedropper-icon",
      "type": "selector",
      "name": ".ren-color-picker-eyedropper-icon"
    },
    {
      "data": {},
      "body": ".ren-color-picker-format-toggle",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-format-toggle",
      "type": "selector",
      "name": ".ren-color-picker-format-toggle"
    },
    {
      "data": {},
      "body": ".ren-color-picker-handle",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-handle",
      "type": "selector",
      "name": ".ren-color-picker-handle"
    },
    {
      "data": {},
      "body": ".ren-color-picker-hex-input",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-hex-input",
      "type": "selector",
      "name": ".ren-color-picker-hex-input"
    },
    {
      "data": {},
      "body": ".ren-color-picker-hue",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-hue",
      "type": "selector",
      "name": ".ren-color-picker-hue"
    },
    {
      "data": {},
      "body": ".ren-color-picker-hue-handle",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-hue-handle",
      "type": "selector",
      "name": ".ren-color-picker-hue-handle"
    },
    {
      "data": {},
      "body": ".ren-color-picker-inputs",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-inputs",
      "type": "selector",
      "name": ".ren-color-picker-inputs"
    },
    {
      "data": {},
      "body": ".ren-color-picker-preview",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-preview",
      "type": "selector",
      "name": ".ren-color-picker-preview"
    },
    {
      "data": {},
      "body": ".ren-color-picker-preview-info",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-preview-info",
      "type": "selector",
      "name": ".ren-color-picker-preview-info"
    },
    {
      "data": {},
      "body": ".ren-color-picker-preview-label",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-preview-label",
      "type": "selector",
      "name": ".ren-color-picker-preview-label"
    },
    {
      "data": {},
      "body": ".ren-color-picker-preview-swatch",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-preview-swatch",
      "type": "selector",
      "name": ".ren-color-picker-preview-swatch"
    },
    {
      "data": {},
      "body": ".ren-color-picker-preview-value",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-preview-value",
      "type": "selector",
      "name": ".ren-color-picker-preview-value"
    },
    {
      "data": {},
      "body": ".ren-color-picker-saturation",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-saturation",
      "type": "selector",
      "name": ".ren-color-picker-saturation"
    },
    {
      "data": {},
      "body": ".ren-color-picker-saturation-canvas",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-saturation-canvas",
      "type": "selector",
      "name": ".ren-color-picker-saturation-canvas"
    },
    {
      "data": {},
      "body": ".ren-color-picker-saturation-handle",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-saturation-handle",
      "type": "selector",
      "name": ".ren-color-picker-saturation-handle"
    },
    {
      "data": {},
      "body": ".ren-color-picker-swatch",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-swatch",
      "type": "selector",
      "name": ".ren-color-picker-swatch"
    },
    {
      "data": {},
      "body": ".ren-color-picker-swatch-color",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-swatch-color",
      "type": "selector",
      "name": ".ren-color-picker-swatch-color"
    },
    {
      "data": {},
      "body": ".ren-color-picker-swatches",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-swatches",
      "type": "selector",
      "name": ".ren-color-picker-swatches"
    },
    {
      "data": {},
      "body": ".ren-color-picker-track",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-track",
      "type": "selector",
      "name": ".ren-color-picker-track"
    },
    {
      "data": {},
      "body": ".ren-color-picker-trigger",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-trigger",
      "type": "selector",
      "name": ".ren-color-picker-trigger"
    },
    {
      "data": {},
      "body": ".ren-color-picker-trigger-swatch",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-trigger-swatch",
      "type": "selector",
      "name": ".ren-color-picker-trigger-swatch"
    },
    {
      "data": {},
      "body": ".ren-color-picker-trigger-value",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "selector:.ren-color-picker-trigger-value",
      "type": "selector",
      "name": ".ren-color-picker-trigger-value"
    },
    {
      "data": {},
      "body": ".ren-combobox",
      "path": "components/composites/ren-combobox/ren-combobox.css",
      "id": "selector:.ren-combobox",
      "type": "selector",
      "name": ".ren-combobox"
    },
    {
      "data": {},
      "body": ".ren-command",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command",
      "type": "selector",
      "name": ".ren-command"
    },
    {
      "data": {},
      "body": ".ren-command-empty",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-empty",
      "type": "selector",
      "name": ".ren-command-empty"
    },
    {
      "data": {},
      "body": ".ren-command-footer",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-footer",
      "type": "selector",
      "name": ".ren-command-footer"
    },
    {
      "data": {},
      "body": ".ren-command-footer-hint",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-footer-hint",
      "type": "selector",
      "name": ".ren-command-footer-hint"
    },
    {
      "data": {},
      "body": ".ren-command-group",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-group",
      "type": "selector",
      "name": ".ren-command-group"
    },
    {
      "data": {},
      "body": ".ren-command-group-heading",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-group-heading",
      "type": "selector",
      "name": ".ren-command-group-heading"
    },
    {
      "data": {},
      "body": ".ren-command-input",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-input",
      "type": "selector",
      "name": ".ren-command-input"
    },
    {
      "data": {},
      "body": ".ren-command-input-wrapper",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-input-wrapper",
      "type": "selector",
      "name": ".ren-command-input-wrapper"
    },
    {
      "data": {},
      "body": ".ren-command-item",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-item",
      "type": "selector",
      "name": ".ren-command-item"
    },
    {
      "data": {},
      "body": ".ren-command-item-content",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-item-content",
      "type": "selector",
      "name": ".ren-command-item-content"
    },
    {
      "data": {},
      "body": ".ren-command-item-description",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-item-description",
      "type": "selector",
      "name": ".ren-command-item-description"
    },
    {
      "data": {},
      "body": ".ren-command-item-icon",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-item-icon",
      "type": "selector",
      "name": ".ren-command-item-icon"
    },
    {
      "data": {},
      "body": ".ren-command-item-shortcut",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-item-shortcut",
      "type": "selector",
      "name": ".ren-command-item-shortcut"
    },
    {
      "data": {},
      "body": ".ren-command-item-title",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-item-title",
      "type": "selector",
      "name": ".ren-command-item-title"
    },
    {
      "data": {},
      "body": ".ren-command-kbd",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-kbd",
      "type": "selector",
      "name": ".ren-command-kbd"
    },
    {
      "data": {},
      "body": ".ren-command-list",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-list",
      "type": "selector",
      "name": ".ren-command-list"
    },
    {
      "data": {},
      "body": ".ren-command-separator",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "selector:.ren-command-separator",
      "type": "selector",
      "name": ".ren-command-separator"
    },
    {
      "data": {},
      "body": ".ren-context-menu",
      "path": "components/composites/ren-context-menu/ren-context-menu.css",
      "id": "selector:.ren-context-menu",
      "type": "selector",
      "name": ".ren-context-menu"
    },
    {
      "data": {},
      "body": ".ren-date-picker",
      "path": "components/composites/ren-date-picker/ren-date-picker.css",
      "id": "selector:.ren-date-picker",
      "type": "selector",
      "name": ".ren-date-picker"
    },
    {
      "data": {},
      "body": ".ren-date-picker-dropdown",
      "path": "components/composites/ren-date-picker/ren-date-picker.css",
      "id": "selector:.ren-date-picker-dropdown",
      "type": "selector",
      "name": ".ren-date-picker-dropdown"
    },
    {
      "data": {},
      "body": ".ren-date-picker-preset",
      "path": "components/composites/ren-date-picker/ren-date-picker.css",
      "id": "selector:.ren-date-picker-preset",
      "type": "selector",
      "name": ".ren-date-picker-preset"
    },
    {
      "data": {},
      "body": ".ren-date-picker-presets",
      "path": "components/composites/ren-date-picker/ren-date-picker.css",
      "id": "selector:.ren-date-picker-presets",
      "type": "selector",
      "name": ".ren-date-picker-presets"
    },
    {
      "data": {},
      "body": ".ren-date-picker-trigger",
      "path": "components/composites/ren-date-picker/ren-date-picker.css",
      "id": "selector:.ren-date-picker-trigger",
      "type": "selector",
      "name": ".ren-date-picker-trigger"
    },
    {
      "data": {},
      "body": ".ren-date-picker-value",
      "path": "components/composites/ren-date-picker/ren-date-picker.css",
      "id": "selector:.ren-date-picker-value",
      "type": "selector",
      "name": ".ren-date-picker-value"
    },
    {
      "data": {},
      "body": ".ren-date-range-actions",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-actions",
      "type": "selector",
      "name": ".ren-date-range-actions"
    },
    {
      "data": {},
      "body": ".ren-date-range-apply",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-apply",
      "type": "selector",
      "name": ".ren-date-range-apply"
    },
    {
      "data": {},
      "body": ".ren-date-range-calendars",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-calendars",
      "type": "selector",
      "name": ".ren-date-range-calendars"
    },
    {
      "data": {},
      "body": ".ren-date-range-cancel",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-cancel",
      "type": "selector",
      "name": ".ren-date-range-cancel"
    },
    {
      "data": {},
      "body": ".ren-date-range-divider",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-divider",
      "type": "selector",
      "name": ".ren-date-range-divider"
    },
    {
      "data": {},
      "body": ".ren-date-range-dropdown",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-dropdown",
      "type": "selector",
      "name": ".ren-date-range-dropdown"
    },
    {
      "data": {},
      "body": ".ren-date-range-end",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-end",
      "type": "selector",
      "name": ".ren-date-range-end"
    },
    {
      "data": {},
      "body": ".ren-date-range-footer",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-footer",
      "type": "selector",
      "name": ".ren-date-range-footer"
    },
    {
      "data": {},
      "body": ".ren-date-range-inner",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-inner",
      "type": "selector",
      "name": ".ren-date-range-inner"
    },
    {
      "data": {},
      "body": ".ren-date-range-picker",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-picker",
      "type": "selector",
      "name": ".ren-date-range-picker"
    },
    {
      "data": {},
      "body": ".ren-date-range-preset",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-preset",
      "type": "selector",
      "name": ".ren-date-range-preset"
    },
    {
      "data": {},
      "body": ".ren-date-range-presets",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-presets",
      "type": "selector",
      "name": ".ren-date-range-presets"
    },
    {
      "data": {},
      "body": ".ren-date-range-separator",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-separator",
      "type": "selector",
      "name": ".ren-date-range-separator"
    },
    {
      "data": {},
      "body": ".ren-date-range-start",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-start",
      "type": "selector",
      "name": ".ren-date-range-start"
    },
    {
      "data": {},
      "body": ".ren-date-range-summary",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-summary",
      "type": "selector",
      "name": ".ren-date-range-summary"
    },
    {
      "data": {},
      "body": ".ren-date-range-trigger",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-trigger",
      "type": "selector",
      "name": ".ren-date-range-trigger"
    },
    {
      "data": {},
      "body": ".ren-date-range-value",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "selector:.ren-date-range-value",
      "type": "selector",
      "name": ".ren-date-range-value"
    },
    {
      "data": {},
      "body": ".ren-dialog",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "selector:.ren-dialog",
      "type": "selector",
      "name": ".ren-dialog"
    },
    {
      "data": {},
      "body": ".ren-dialog-body",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "selector:.ren-dialog-body",
      "type": "selector",
      "name": ".ren-dialog-body"
    },
    {
      "data": {},
      "body": ".ren-dialog-close",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "selector:.ren-dialog-close",
      "type": "selector",
      "name": ".ren-dialog-close"
    },
    {
      "data": {},
      "body": ".ren-dialog-description",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "selector:.ren-dialog-description",
      "type": "selector",
      "name": ".ren-dialog-description"
    },
    {
      "data": {},
      "body": ".ren-dialog-footer",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "selector:.ren-dialog-footer",
      "type": "selector",
      "name": ".ren-dialog-footer"
    },
    {
      "data": {},
      "body": ".ren-dialog-full",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "selector:.ren-dialog-full",
      "type": "selector",
      "name": ".ren-dialog-full"
    },
    {
      "data": {},
      "body": ".ren-dialog-header",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "selector:.ren-dialog-header",
      "type": "selector",
      "name": ".ren-dialog-header"
    },
    {
      "data": {},
      "body": ".ren-dialog-lg",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "selector:.ren-dialog-lg",
      "type": "selector",
      "name": ".ren-dialog-lg"
    },
    {
      "data": {},
      "body": ".ren-dialog-md",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "selector:.ren-dialog-md",
      "type": "selector",
      "name": ".ren-dialog-md"
    },
    {
      "data": {},
      "body": ".ren-dialog-sm",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "selector:.ren-dialog-sm",
      "type": "selector",
      "name": ".ren-dialog-sm"
    },
    {
      "data": {},
      "body": ".ren-dialog-title",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "selector:.ren-dialog-title",
      "type": "selector",
      "name": ".ren-dialog-title"
    },
    {
      "data": {},
      "body": ".ren-dialog-wrapper",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "selector:.ren-dialog-wrapper",
      "type": "selector",
      "name": ".ren-dialog-wrapper"
    },
    {
      "data": {},
      "body": ".ren-dialog-xl",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "selector:.ren-dialog-xl",
      "type": "selector",
      "name": ".ren-dialog-xl"
    },
    {
      "data": {},
      "body": ".ren-dropzone",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "selector:.ren-dropzone",
      "type": "selector",
      "name": ".ren-dropzone"
    },
    {
      "data": {},
      "body": ".ren-dropzone-compact",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "selector:.ren-dropzone-compact",
      "type": "selector",
      "name": ".ren-dropzone-compact"
    },
    {
      "data": {},
      "body": ".ren-dropzone-content",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "selector:.ren-dropzone-content",
      "type": "selector",
      "name": ".ren-dropzone-content"
    },
    {
      "data": {},
      "body": ".ren-dropzone-description",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "selector:.ren-dropzone-description",
      "type": "selector",
      "name": ".ren-dropzone-description"
    },
    {
      "data": {},
      "body": ".ren-dropzone-file",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "selector:.ren-dropzone-file",
      "type": "selector",
      "name": ".ren-dropzone-file"
    },
    {
      "data": {},
      "body": ".ren-dropzone-file-name",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "selector:.ren-dropzone-file-name",
      "type": "selector",
      "name": ".ren-dropzone-file-name"
    },
    {
      "data": {},
      "body": ".ren-dropzone-file-remove",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "selector:.ren-dropzone-file-remove",
      "type": "selector",
      "name": ".ren-dropzone-file-remove"
    },
    {
      "data": {},
      "body": ".ren-dropzone-file-size",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "selector:.ren-dropzone-file-size",
      "type": "selector",
      "name": ".ren-dropzone-file-size"
    },
    {
      "data": {},
      "body": ".ren-dropzone-files",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "selector:.ren-dropzone-files",
      "type": "selector",
      "name": ".ren-dropzone-files"
    },
    {
      "data": {},
      "body": ".ren-dropzone-icon",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "selector:.ren-dropzone-icon",
      "type": "selector",
      "name": ".ren-dropzone-icon"
    },
    {
      "data": {},
      "body": ".ren-dropzone-input",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "selector:.ren-dropzone-input",
      "type": "selector",
      "name": ".ren-dropzone-input"
    },
    {
      "data": {},
      "body": ".ren-dropzone-title",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "selector:.ren-dropzone-title",
      "type": "selector",
      "name": ".ren-dropzone-title"
    },
    {
      "data": {},
      "body": ".ren-empty-state",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "selector:.ren-empty-state",
      "type": "selector",
      "name": ".ren-empty-state"
    },
    {
      "data": {},
      "body": ".ren-empty-state-actions",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "selector:.ren-empty-state-actions",
      "type": "selector",
      "name": ".ren-empty-state-actions"
    },
    {
      "data": {},
      "body": ".ren-empty-state-bordered",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "selector:.ren-empty-state-bordered",
      "type": "selector",
      "name": ".ren-empty-state-bordered"
    },
    {
      "data": {},
      "body": ".ren-empty-state-compact",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "selector:.ren-empty-state-compact",
      "type": "selector",
      "name": ".ren-empty-state-compact"
    },
    {
      "data": {},
      "body": ".ren-empty-state-description",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "selector:.ren-empty-state-description",
      "type": "selector",
      "name": ".ren-empty-state-description"
    },
    {
      "data": {},
      "body": ".ren-empty-state-icon",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "selector:.ren-empty-state-icon",
      "type": "selector",
      "name": ".ren-empty-state-icon"
    },
    {
      "data": {},
      "body": ".ren-empty-state-title",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "selector:.ren-empty-state-title",
      "type": "selector",
      "name": ".ren-empty-state-title"
    },
    {
      "data": {},
      "body": ".ren-field",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "selector:.ren-field",
      "type": "selector",
      "name": ".ren-field"
    },
    {
      "data": {},
      "body": ".ren-field-control",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "selector:.ren-field-control",
      "type": "selector",
      "name": ".ren-field-control"
    },
    {
      "data": {},
      "body": ".ren-field-description",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "selector:.ren-field-description",
      "type": "selector",
      "name": ".ren-field-description"
    },
    {
      "data": {},
      "body": ".ren-field-error",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "selector:.ren-field-error",
      "type": "selector",
      "name": ".ren-field-error"
    },
    {
      "data": {},
      "body": ".ren-field-label",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "selector:.ren-field-label",
      "type": "selector",
      "name": ".ren-field-label"
    },
    {
      "data": {},
      "body": ".ren-form",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "selector:.ren-form",
      "type": "selector",
      "name": ".ren-form"
    },
    {
      "data": {},
      "body": ".ren-form-actions",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "selector:.ren-form-actions",
      "type": "selector",
      "name": ".ren-form-actions"
    },
    {
      "data": {},
      "body": ".ren-form-error-summary",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "selector:.ren-form-error-summary",
      "type": "selector",
      "name": ".ren-form-error-summary"
    },
    {
      "data": {},
      "body": ".ren-form-progress",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "selector:.ren-form-progress",
      "type": "selector",
      "name": ".ren-form-progress"
    },
    {
      "data": {},
      "body": ".ren-form-row",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "selector:.ren-form-row",
      "type": "selector",
      "name": ".ren-form-row"
    },
    {
      "data": {},
      "body": ".ren-form-section",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "selector:.ren-form-section",
      "type": "selector",
      "name": ".ren-form-section"
    },
    {
      "data": {},
      "body": ".ren-form-section-title",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "selector:.ren-form-section-title",
      "type": "selector",
      "name": ".ren-form-section-title"
    },
    {
      "data": {},
      "body": ".ren-form-step",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "selector:.ren-form-step",
      "type": "selector",
      "name": ".ren-form-step"
    },
    {
      "data": {},
      "body": ".ren-form-step-label",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "selector:.ren-form-step-label",
      "type": "selector",
      "name": ".ren-form-step-label"
    },
    {
      "data": {},
      "body": ".ren-form-success",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "selector:.ren-form-success",
      "type": "selector",
      "name": ".ren-form-success"
    },
    {
      "data": {},
      "body": ".ren-hover-card",
      "path": "components/composites/ren-hover-card/ren-hover-card.css",
      "id": "selector:.ren-hover-card",
      "type": "selector",
      "name": ".ren-hover-card"
    },
    {
      "data": {},
      "body": ".ren-hover-card-body",
      "path": "components/composites/ren-hover-card/ren-hover-card.css",
      "id": "selector:.ren-hover-card-body",
      "type": "selector",
      "name": ".ren-hover-card-body"
    },
    {
      "data": {},
      "body": ".ren-hover-card-footer",
      "path": "components/composites/ren-hover-card/ren-hover-card.css",
      "id": "selector:.ren-hover-card-footer",
      "type": "selector",
      "name": ".ren-hover-card-footer"
    },
    {
      "data": {},
      "body": ".ren-hover-card-header",
      "path": "components/composites/ren-hover-card/ren-hover-card.css",
      "id": "selector:.ren-hover-card-header",
      "type": "selector",
      "name": ".ren-hover-card-header"
    },
    {
      "data": {},
      "body": ".ren-hover-card-lg",
      "path": "components/composites/ren-hover-card/ren-hover-card.css",
      "id": "selector:.ren-hover-card-lg",
      "type": "selector",
      "name": ".ren-hover-card-lg"
    },
    {
      "data": {},
      "body": ".ren-hover-card-sm",
      "path": "components/composites/ren-hover-card/ren-hover-card.css",
      "id": "selector:.ren-hover-card-sm",
      "type": "selector",
      "name": ".ren-hover-card-sm"
    },
    {
      "data": {},
      "body": ".ren-hover-card-trigger",
      "path": "components/composites/ren-hover-card/ren-hover-card.css",
      "id": "selector:.ren-hover-card-trigger",
      "type": "selector",
      "name": ".ren-hover-card-trigger"
    },
    {
      "data": {},
      "body": ".ren-icon",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "selector:.ren-icon",
      "type": "selector",
      "name": ".ren-icon"
    },
    {
      "data": {},
      "body": ".ren-icon-2xl",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "selector:.ren-icon-2xl",
      "type": "selector",
      "name": ".ren-icon-2xl"
    },
    {
      "data": {},
      "body": ".ren-icon-danger",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "selector:.ren-icon-danger",
      "type": "selector",
      "name": ".ren-icon-danger"
    },
    {
      "data": {},
      "body": ".ren-icon-lg",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "selector:.ren-icon-lg",
      "type": "selector",
      "name": ".ren-icon-lg"
    },
    {
      "data": {},
      "body": ".ren-icon-md",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "selector:.ren-icon-md",
      "type": "selector",
      "name": ".ren-icon-md"
    },
    {
      "data": {},
      "body": ".ren-icon-muted",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "selector:.ren-icon-muted",
      "type": "selector",
      "name": ".ren-icon-muted"
    },
    {
      "data": {},
      "body": ".ren-icon-primary",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "selector:.ren-icon-primary",
      "type": "selector",
      "name": ".ren-icon-primary"
    },
    {
      "data": {},
      "body": ".ren-icon-sm",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "selector:.ren-icon-sm",
      "type": "selector",
      "name": ".ren-icon-sm"
    },
    {
      "data": {},
      "body": ".ren-icon-spin",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "selector:.ren-icon-spin",
      "type": "selector",
      "name": ".ren-icon-spin"
    },
    {
      "data": {},
      "body": ".ren-icon-success",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "selector:.ren-icon-success",
      "type": "selector",
      "name": ".ren-icon-success"
    },
    {
      "data": {},
      "body": ".ren-icon-warning",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "selector:.ren-icon-warning",
      "type": "selector",
      "name": ".ren-icon-warning"
    },
    {
      "data": {},
      "body": ".ren-icon-xl",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "selector:.ren-icon-xl",
      "type": "selector",
      "name": ".ren-icon-xl"
    },
    {
      "data": {},
      "body": ".ren-icon-xs",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "selector:.ren-icon-xs",
      "type": "selector",
      "name": ".ren-icon-xs"
    },
    {
      "data": {},
      "body": ".ren-input",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "selector:.ren-input",
      "type": "selector",
      "name": ".ren-input"
    },
    {
      "data": {},
      "body": ".ren-input-error",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "selector:.ren-input-error",
      "type": "selector",
      "name": ".ren-input-error"
    },
    {
      "data": {},
      "body": ".ren-input-icon",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "selector:.ren-input-icon",
      "type": "selector",
      "name": ".ren-input-icon"
    },
    {
      "data": {},
      "body": ".ren-input-icon-end",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "selector:.ren-input-icon-end",
      "type": "selector",
      "name": ".ren-input-icon-end"
    },
    {
      "data": {},
      "body": ".ren-input-lg",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "selector:.ren-input-lg",
      "type": "selector",
      "name": ".ren-input-lg"
    },
    {
      "data": {},
      "body": ".ren-input-sm",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "selector:.ren-input-sm",
      "type": "selector",
      "name": ".ren-input-sm"
    },
    {
      "data": {},
      "body": ".ren-input-success",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "selector:.ren-input-success",
      "type": "selector",
      "name": ".ren-input-success"
    },
    {
      "data": {},
      "body": ".ren-input-wrapper",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "selector:.ren-input-wrapper",
      "type": "selector",
      "name": ".ren-input-wrapper"
    },
    {
      "data": {},
      "body": ".ren-kbd",
      "path": "components/primitives/ren-kbd/ren-kbd.css",
      "id": "selector:.ren-kbd",
      "type": "selector",
      "name": ".ren-kbd"
    },
    {
      "data": {},
      "body": ".ren-link",
      "path": "components/primitives/ren-link/ren-link.css",
      "id": "selector:.ren-link",
      "type": "selector",
      "name": ".ren-link"
    },
    {
      "data": {},
      "body": ".ren-link-external",
      "path": "components/primitives/ren-link/ren-link.css",
      "id": "selector:.ren-link-external",
      "type": "selector",
      "name": ".ren-link-external"
    },
    {
      "data": {},
      "body": ".ren-link-muted",
      "path": "components/primitives/ren-link/ren-link.css",
      "id": "selector:.ren-link-muted",
      "type": "selector",
      "name": ".ren-link-muted"
    },
    {
      "data": {},
      "body": ".ren-link-nav",
      "path": "components/primitives/ren-link/ren-link.css",
      "id": "selector:.ren-link-nav",
      "type": "selector",
      "name": ".ren-link-nav"
    },
    {
      "data": {},
      "body": ".ren-link-plain",
      "path": "components/primitives/ren-link/ren-link.css",
      "id": "selector:.ren-link-plain",
      "type": "selector",
      "name": ".ren-link-plain"
    },
    {
      "data": {},
      "body": ".ren-link-skip",
      "path": "components/primitives/ren-link/ren-link.css",
      "id": "selector:.ren-link-skip",
      "type": "selector",
      "name": ".ren-link-skip"
    },
    {
      "data": {},
      "body": ".ren-menu",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "selector:.ren-menu",
      "type": "selector",
      "name": ".ren-menu"
    },
    {
      "data": {},
      "body": ".ren-menu-checkbox-item",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "selector:.ren-menu-checkbox-item",
      "type": "selector",
      "name": ".ren-menu-checkbox-item"
    },
    {
      "data": {},
      "body": ".ren-menu-group",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "selector:.ren-menu-group",
      "type": "selector",
      "name": ".ren-menu-group"
    },
    {
      "data": {},
      "body": ".ren-menu-item",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "selector:.ren-menu-item",
      "type": "selector",
      "name": ".ren-menu-item"
    },
    {
      "data": {},
      "body": ".ren-menu-item-danger",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "selector:.ren-menu-item-danger",
      "type": "selector",
      "name": ".ren-menu-item-danger"
    },
    {
      "data": {},
      "body": ".ren-menu-item-description",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "selector:.ren-menu-item-description",
      "type": "selector",
      "name": ".ren-menu-item-description"
    },
    {
      "data": {},
      "body": ".ren-menu-item-icon",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "selector:.ren-menu-item-icon",
      "type": "selector",
      "name": ".ren-menu-item-icon"
    },
    {
      "data": {},
      "body": ".ren-menu-item-shortcut",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "selector:.ren-menu-item-shortcut",
      "type": "selector",
      "name": ".ren-menu-item-shortcut"
    },
    {
      "data": {},
      "body": ".ren-menu-label",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "selector:.ren-menu-label",
      "type": "selector",
      "name": ".ren-menu-label"
    },
    {
      "data": {},
      "body": ".ren-menu-radio-item",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "selector:.ren-menu-radio-item",
      "type": "selector",
      "name": ".ren-menu-radio-item"
    },
    {
      "data": {},
      "body": ".ren-menu-separator",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "selector:.ren-menu-separator",
      "type": "selector",
      "name": ".ren-menu-separator"
    },
    {
      "data": {},
      "body": ".ren-menubar",
      "path": "components/patterns/ren-menubar/ren-menubar.css",
      "id": "selector:.ren-menubar",
      "type": "selector",
      "name": ".ren-menubar"
    },
    {
      "data": {},
      "body": ".ren-menubar-checkbox",
      "path": "components/patterns/ren-menubar/ren-menubar.css",
      "id": "selector:.ren-menubar-checkbox",
      "type": "selector",
      "name": ".ren-menubar-checkbox"
    },
    {
      "data": {},
      "body": ".ren-menubar-item",
      "path": "components/patterns/ren-menubar/ren-menubar.css",
      "id": "selector:.ren-menubar-item",
      "type": "selector",
      "name": ".ren-menubar-item"
    },
    {
      "data": {},
      "body": ".ren-menubar-label",
      "path": "components/patterns/ren-menubar/ren-menubar.css",
      "id": "selector:.ren-menubar-label",
      "type": "selector",
      "name": ".ren-menubar-label"
    },
    {
      "data": {},
      "body": ".ren-menubar-menu",
      "path": "components/patterns/ren-menubar/ren-menubar.css",
      "id": "selector:.ren-menubar-menu",
      "type": "selector",
      "name": ".ren-menubar-menu"
    },
    {
      "data": {},
      "body": ".ren-menubar-radio",
      "path": "components/patterns/ren-menubar/ren-menubar.css",
      "id": "selector:.ren-menubar-radio",
      "type": "selector",
      "name": ".ren-menubar-radio"
    },
    {
      "data": {},
      "body": ".ren-menubar-separator",
      "path": "components/patterns/ren-menubar/ren-menubar.css",
      "id": "selector:.ren-menubar-separator",
      "type": "selector",
      "name": ".ren-menubar-separator"
    },
    {
      "data": {},
      "body": ".ren-menubar-shortcut",
      "path": "components/patterns/ren-menubar/ren-menubar.css",
      "id": "selector:.ren-menubar-shortcut",
      "type": "selector",
      "name": ".ren-menubar-shortcut"
    },
    {
      "data": {},
      "body": ".ren-menubar-submenu",
      "path": "components/patterns/ren-menubar/ren-menubar.css",
      "id": "selector:.ren-menubar-submenu",
      "type": "selector",
      "name": ".ren-menubar-submenu"
    },
    {
      "data": {},
      "body": ".ren-menubar-trigger",
      "path": "components/patterns/ren-menubar/ren-menubar.css",
      "id": "selector:.ren-menubar-trigger",
      "type": "selector",
      "name": ".ren-menubar-trigger"
    },
    {
      "data": {},
      "body": ".ren-meter",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-meter",
      "type": "selector",
      "name": ".ren-meter"
    },
    {
      "data": {},
      "body": ".ren-meter-lg",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-meter-lg",
      "type": "selector",
      "name": ".ren-meter-lg"
    },
    {
      "data": {},
      "body": ".ren-meter-sm",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-meter-sm",
      "type": "selector",
      "name": ".ren-meter-sm"
    },
    {
      "data": {},
      "body": ".ren-meter-xl",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-meter-xl",
      "type": "selector",
      "name": ".ren-meter-xl"
    },
    {
      "data": {},
      "body": ".ren-nav",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "selector:.ren-nav",
      "type": "selector",
      "name": ".ren-nav"
    },
    {
      "data": {},
      "body": ".ren-nav-actions",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "selector:.ren-nav-actions",
      "type": "selector",
      "name": ".ren-nav-actions"
    },
    {
      "data": {},
      "body": ".ren-nav-brand",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "selector:.ren-nav-brand",
      "type": "selector",
      "name": ".ren-nav-brand"
    },
    {
      "data": {},
      "body": ".ren-nav-dropdown",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "selector:.ren-nav-dropdown",
      "type": "selector",
      "name": ".ren-nav-dropdown"
    },
    {
      "data": {},
      "body": ".ren-nav-link",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "selector:.ren-nav-link",
      "type": "selector",
      "name": ".ren-nav-link"
    },
    {
      "data": {},
      "body": ".ren-nav-links",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "selector:.ren-nav-links",
      "type": "selector",
      "name": ".ren-nav-links"
    },
    {
      "data": {},
      "body": ".ren-nav-sticky",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "selector:.ren-nav-sticky",
      "type": "selector",
      "name": ".ren-nav-sticky"
    },
    {
      "data": {},
      "body": ".ren-nav-toggle",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "selector:.ren-nav-toggle",
      "type": "selector",
      "name": ".ren-nav-toggle"
    },
    {
      "data": {},
      "body": ".ren-nav-transparent",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "selector:.ren-nav-transparent",
      "type": "selector",
      "name": ".ren-nav-transparent"
    },
    {
      "data": {},
      "body": ".ren-number-field",
      "path": "components/composites/ren-number-field/ren-number-field.css",
      "id": "selector:.ren-number-field",
      "type": "selector",
      "name": ".ren-number-field"
    },
    {
      "data": {},
      "body": ".ren-number-field-decrement",
      "path": "components/composites/ren-number-field/ren-number-field.css",
      "id": "selector:.ren-number-field-decrement",
      "type": "selector",
      "name": ".ren-number-field-decrement"
    },
    {
      "data": {},
      "body": ".ren-number-field-increment",
      "path": "components/composites/ren-number-field/ren-number-field.css",
      "id": "selector:.ren-number-field-increment",
      "type": "selector",
      "name": ".ren-number-field-increment"
    },
    {
      "data": {},
      "body": ".ren-number-field-input",
      "path": "components/composites/ren-number-field/ren-number-field.css",
      "id": "selector:.ren-number-field-input",
      "type": "selector",
      "name": ".ren-number-field-input"
    },
    {
      "data": {},
      "body": ".ren-number-field-lg",
      "path": "components/composites/ren-number-field/ren-number-field.css",
      "id": "selector:.ren-number-field-lg",
      "type": "selector",
      "name": ".ren-number-field-lg"
    },
    {
      "data": {},
      "body": ".ren-number-field-sm",
      "path": "components/composites/ren-number-field/ren-number-field.css",
      "id": "selector:.ren-number-field-sm",
      "type": "selector",
      "name": ".ren-number-field-sm"
    },
    {
      "data": {},
      "body": ".ren-open",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "selector:.ren-open",
      "type": "selector",
      "name": ".ren-open"
    },
    {
      "data": {},
      "body": ".ren-otp",
      "path": "components/composites/ren-otp/ren-otp.css",
      "id": "selector:.ren-otp",
      "type": "selector",
      "name": ".ren-otp"
    },
    {
      "data": {},
      "body": ".ren-otp-lg",
      "path": "components/composites/ren-otp/ren-otp.css",
      "id": "selector:.ren-otp-lg",
      "type": "selector",
      "name": ".ren-otp-lg"
    },
    {
      "data": {},
      "body": ".ren-otp-separator",
      "path": "components/composites/ren-otp/ren-otp.css",
      "id": "selector:.ren-otp-separator",
      "type": "selector",
      "name": ".ren-otp-separator"
    },
    {
      "data": {},
      "body": ".ren-otp-slot",
      "path": "components/composites/ren-otp/ren-otp.css",
      "id": "selector:.ren-otp-slot",
      "type": "selector",
      "name": ".ren-otp-slot"
    },
    {
      "data": {},
      "body": ".ren-otp-sm",
      "path": "components/composites/ren-otp/ren-otp.css",
      "id": "selector:.ren-otp-sm",
      "type": "selector",
      "name": ".ren-otp-sm"
    },
    {
      "data": {},
      "body": ".ren-pagination",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "selector:.ren-pagination",
      "type": "selector",
      "name": ".ren-pagination"
    },
    {
      "data": {},
      "body": ".ren-pagination-centered",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "selector:.ren-pagination-centered",
      "type": "selector",
      "name": ".ren-pagination-centered"
    },
    {
      "data": {},
      "body": ".ren-pagination-compact",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "selector:.ren-pagination-compact",
      "type": "selector",
      "name": ".ren-pagination-compact"
    },
    {
      "data": {},
      "body": ".ren-pagination-ellipsis",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "selector:.ren-pagination-ellipsis",
      "type": "selector",
      "name": ".ren-pagination-ellipsis"
    },
    {
      "data": {},
      "body": ".ren-pagination-info",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "selector:.ren-pagination-info",
      "type": "selector",
      "name": ".ren-pagination-info"
    },
    {
      "data": {},
      "body": ".ren-pagination-item",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "selector:.ren-pagination-item",
      "type": "selector",
      "name": ".ren-pagination-item"
    },
    {
      "data": {},
      "body": ".ren-pagination-next",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "selector:.ren-pagination-next",
      "type": "selector",
      "name": ".ren-pagination-next"
    },
    {
      "data": {},
      "body": ".ren-pagination-prev",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "selector:.ren-pagination-prev",
      "type": "selector",
      "name": ".ren-pagination-prev"
    },
    {
      "data": {},
      "body": ".ren-pagination-simple",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "selector:.ren-pagination-simple",
      "type": "selector",
      "name": ".ren-pagination-simple"
    },
    {
      "data": {},
      "body": ".ren-popover",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "selector:.ren-popover",
      "type": "selector",
      "name": ".ren-popover"
    },
    {
      "data": {},
      "body": ".ren-popover-arrow",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "selector:.ren-popover-arrow",
      "type": "selector",
      "name": ".ren-popover-arrow"
    },
    {
      "data": {},
      "body": ".ren-popover-body",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "selector:.ren-popover-body",
      "type": "selector",
      "name": ".ren-popover-body"
    },
    {
      "data": {},
      "body": ".ren-popover-footer",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "selector:.ren-popover-footer",
      "type": "selector",
      "name": ".ren-popover-footer"
    },
    {
      "data": {},
      "body": ".ren-popover-header",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "selector:.ren-popover-header",
      "type": "selector",
      "name": ".ren-popover-header"
    },
    {
      "data": {},
      "body": ".ren-popover-trigger",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "selector:.ren-popover-trigger",
      "type": "selector",
      "name": ".ren-popover-trigger"
    },
    {
      "data": {},
      "body": ".ren-progress",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-progress",
      "type": "selector",
      "name": ".ren-progress"
    },
    {
      "data": {},
      "body": ".ren-progress-bar",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-progress-bar",
      "type": "selector",
      "name": ".ren-progress-bar"
    },
    {
      "data": {},
      "body": ".ren-progress-danger",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-progress-danger",
      "type": "selector",
      "name": ".ren-progress-danger"
    },
    {
      "data": {},
      "body": ".ren-progress-indeterminate",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-progress-indeterminate",
      "type": "selector",
      "name": ".ren-progress-indeterminate"
    },
    {
      "data": {},
      "body": ".ren-progress-info",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-progress-info",
      "type": "selector",
      "name": ".ren-progress-info"
    },
    {
      "data": {},
      "body": ".ren-progress-label",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-progress-label",
      "type": "selector",
      "name": ".ren-progress-label"
    },
    {
      "data": {},
      "body": ".ren-progress-lg",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-progress-lg",
      "type": "selector",
      "name": ".ren-progress-lg"
    },
    {
      "data": {},
      "body": ".ren-progress-sm",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-progress-sm",
      "type": "selector",
      "name": ".ren-progress-sm"
    },
    {
      "data": {},
      "body": ".ren-progress-success",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-progress-success",
      "type": "selector",
      "name": ".ren-progress-success"
    },
    {
      "data": {},
      "body": ".ren-progress-value",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-progress-value",
      "type": "selector",
      "name": ".ren-progress-value"
    },
    {
      "data": {},
      "body": ".ren-progress-warning",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-progress-warning",
      "type": "selector",
      "name": ".ren-progress-warning"
    },
    {
      "data": {},
      "body": ".ren-progress-xl",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "selector:.ren-progress-xl",
      "type": "selector",
      "name": ".ren-progress-xl"
    },
    {
      "data": {},
      "body": ".ren-radio",
      "path": "components/primitives/ren-radio/ren-radio.css",
      "id": "selector:.ren-radio",
      "type": "selector",
      "name": ".ren-radio"
    },
    {
      "data": {},
      "body": ".ren-radio-control",
      "path": "components/primitives/ren-radio/ren-radio.css",
      "id": "selector:.ren-radio-control",
      "type": "selector",
      "name": ".ren-radio-control"
    },
    {
      "data": {},
      "body": ".ren-radio-group",
      "path": "components/primitives/ren-radio/ren-radio.css",
      "id": "selector:.ren-radio-group",
      "type": "selector",
      "name": ".ren-radio-group"
    },
    {
      "data": {},
      "body": ".ren-radio-group-horizontal",
      "path": "components/primitives/ren-radio/ren-radio.css",
      "id": "selector:.ren-radio-group-horizontal",
      "type": "selector",
      "name": ".ren-radio-group-horizontal"
    },
    {
      "data": {},
      "body": ".ren-scroll-area",
      "path": "components/composites/ren-scroll-area/ren-scroll-area.css",
      "id": "selector:.ren-scroll-area",
      "type": "selector",
      "name": ".ren-scroll-area"
    },
    {
      "data": {},
      "body": ".ren-scroll-area-full",
      "path": "components/composites/ren-scroll-area/ren-scroll-area.css",
      "id": "selector:.ren-scroll-area-full",
      "type": "selector",
      "name": ".ren-scroll-area-full"
    },
    {
      "data": {},
      "body": ".ren-scroll-area-lg",
      "path": "components/composites/ren-scroll-area/ren-scroll-area.css",
      "id": "selector:.ren-scroll-area-lg",
      "type": "selector",
      "name": ".ren-scroll-area-lg"
    },
    {
      "data": {},
      "body": ".ren-scroll-area-md",
      "path": "components/composites/ren-scroll-area/ren-scroll-area.css",
      "id": "selector:.ren-scroll-area-md",
      "type": "selector",
      "name": ".ren-scroll-area-md"
    },
    {
      "data": {},
      "body": ".ren-scroll-area-sm",
      "path": "components/composites/ren-scroll-area/ren-scroll-area.css",
      "id": "selector:.ren-scroll-area-sm",
      "type": "selector",
      "name": ".ren-scroll-area-sm"
    },
    {
      "data": {},
      "body": ".ren-scroll-area-xl",
      "path": "components/composites/ren-scroll-area/ren-scroll-area.css",
      "id": "selector:.ren-scroll-area-xl",
      "type": "selector",
      "name": ".ren-scroll-area-xl"
    },
    {
      "data": {},
      "body": ".ren-select",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select",
      "type": "selector",
      "name": ".ren-select"
    },
    {
      "data": {},
      "body": ".ren-select-chip",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select-chip",
      "type": "selector",
      "name": ".ren-select-chip"
    },
    {
      "data": {},
      "body": ".ren-select-chip-remove",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select-chip-remove",
      "type": "selector",
      "name": ".ren-select-chip-remove"
    },
    {
      "data": {},
      "body": ".ren-select-chips",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select-chips",
      "type": "selector",
      "name": ".ren-select-chips"
    },
    {
      "data": {},
      "body": ".ren-select-content",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select-content",
      "type": "selector",
      "name": ".ren-select-content"
    },
    {
      "data": {},
      "body": ".ren-select-empty",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select-empty",
      "type": "selector",
      "name": ".ren-select-empty"
    },
    {
      "data": {},
      "body": ".ren-select-group",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select-group",
      "type": "selector",
      "name": ".ren-select-group"
    },
    {
      "data": {},
      "body": ".ren-select-icon",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select-icon",
      "type": "selector",
      "name": ".ren-select-icon"
    },
    {
      "data": {},
      "body": ".ren-select-item",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select-item",
      "type": "selector",
      "name": ".ren-select-item"
    },
    {
      "data": {},
      "body": ".ren-select-label",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select-label",
      "type": "selector",
      "name": ".ren-select-label"
    },
    {
      "data": {},
      "body": ".ren-select-lg",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select-lg",
      "type": "selector",
      "name": ".ren-select-lg"
    },
    {
      "data": {},
      "body": ".ren-select-placeholder",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select-placeholder",
      "type": "selector",
      "name": ".ren-select-placeholder"
    },
    {
      "data": {},
      "body": ".ren-select-separator",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select-separator",
      "type": "selector",
      "name": ".ren-select-separator"
    },
    {
      "data": {},
      "body": ".ren-select-sm",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select-sm",
      "type": "selector",
      "name": ".ren-select-sm"
    },
    {
      "data": {},
      "body": ".ren-select-trigger",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select-trigger",
      "type": "selector",
      "name": ".ren-select-trigger"
    },
    {
      "data": {},
      "body": ".ren-select-value",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "selector:.ren-select-value",
      "type": "selector",
      "name": ".ren-select-value"
    },
    {
      "data": {},
      "body": ".ren-separator",
      "path": "components/primitives/ren-separator/ren-separator.css",
      "id": "selector:.ren-separator",
      "type": "selector",
      "name": ".ren-separator"
    },
    {
      "data": {},
      "body": ".ren-separator-horizontal",
      "path": "components/primitives/ren-separator/ren-separator.css",
      "id": "selector:.ren-separator-horizontal",
      "type": "selector",
      "name": ".ren-separator-horizontal"
    },
    {
      "data": {},
      "body": ".ren-separator-label",
      "path": "components/primitives/ren-separator/ren-separator.css",
      "id": "selector:.ren-separator-label",
      "type": "selector",
      "name": ".ren-separator-label"
    },
    {
      "data": {},
      "body": ".ren-separator-vertical",
      "path": "components/primitives/ren-separator/ren-separator.css",
      "id": "selector:.ren-separator-vertical",
      "type": "selector",
      "name": ".ren-separator-vertical"
    },
    {
      "data": {},
      "body": ".ren-sheet",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "selector:.ren-sheet",
      "type": "selector",
      "name": ".ren-sheet"
    },
    {
      "data": {},
      "body": ".ren-sheet-body",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "selector:.ren-sheet-body",
      "type": "selector",
      "name": ".ren-sheet-body"
    },
    {
      "data": {},
      "body": ".ren-sheet-close",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "selector:.ren-sheet-close",
      "type": "selector",
      "name": ".ren-sheet-close"
    },
    {
      "data": {},
      "body": ".ren-sheet-description",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "selector:.ren-sheet-description",
      "type": "selector",
      "name": ".ren-sheet-description"
    },
    {
      "data": {},
      "body": ".ren-sheet-footer",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "selector:.ren-sheet-footer",
      "type": "selector",
      "name": ".ren-sheet-footer"
    },
    {
      "data": {},
      "body": ".ren-sheet-handle",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "selector:.ren-sheet-handle",
      "type": "selector",
      "name": ".ren-sheet-handle"
    },
    {
      "data": {},
      "body": ".ren-sheet-header",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "selector:.ren-sheet-header",
      "type": "selector",
      "name": ".ren-sheet-header"
    },
    {
      "data": {},
      "body": ".ren-sheet-title",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "selector:.ren-sheet-title",
      "type": "selector",
      "name": ".ren-sheet-title"
    },
    {
      "data": {},
      "body": ".ren-sidebar",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "selector:.ren-sidebar",
      "type": "selector",
      "name": ".ren-sidebar"
    },
    {
      "data": {},
      "body": ".ren-sidebar-content",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "selector:.ren-sidebar-content",
      "type": "selector",
      "name": ".ren-sidebar-content"
    },
    {
      "data": {},
      "body": ".ren-sidebar-divider",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "selector:.ren-sidebar-divider",
      "type": "selector",
      "name": ".ren-sidebar-divider"
    },
    {
      "data": {},
      "body": ".ren-sidebar-footer",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "selector:.ren-sidebar-footer",
      "type": "selector",
      "name": ".ren-sidebar-footer"
    },
    {
      "data": {},
      "body": ".ren-sidebar-header",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "selector:.ren-sidebar-header",
      "type": "selector",
      "name": ".ren-sidebar-header"
    },
    {
      "data": {},
      "body": ".ren-sidebar-item",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "selector:.ren-sidebar-item",
      "type": "selector",
      "name": ".ren-sidebar-item"
    },
    {
      "data": {},
      "body": ".ren-sidebar-item-icon",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "selector:.ren-sidebar-item-icon",
      "type": "selector",
      "name": ".ren-sidebar-item-icon"
    },
    {
      "data": {},
      "body": ".ren-sidebar-item-text",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "selector:.ren-sidebar-item-text",
      "type": "selector",
      "name": ".ren-sidebar-item-text"
    },
    {
      "data": {},
      "body": ".ren-sidebar-layout",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "selector:.ren-sidebar-layout",
      "type": "selector",
      "name": ".ren-sidebar-layout"
    },
    {
      "data": {},
      "body": ".ren-sidebar-main",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "selector:.ren-sidebar-main",
      "type": "selector",
      "name": ".ren-sidebar-main"
    },
    {
      "data": {},
      "body": ".ren-sidebar-nav",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "selector:.ren-sidebar-nav",
      "type": "selector",
      "name": ".ren-sidebar-nav"
    },
    {
      "data": {},
      "body": ".ren-sidebar-overlay",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "selector:.ren-sidebar-overlay",
      "type": "selector",
      "name": ".ren-sidebar-overlay"
    },
    {
      "data": {},
      "body": ".ren-sidebar-section",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "selector:.ren-sidebar-section",
      "type": "selector",
      "name": ".ren-sidebar-section"
    },
    {
      "data": {},
      "body": ".ren-sidebar-section-label",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "selector:.ren-sidebar-section-label",
      "type": "selector",
      "name": ".ren-sidebar-section-label"
    },
    {
      "data": {},
      "body": ".ren-sidebar-toggle",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "selector:.ren-sidebar-toggle",
      "type": "selector",
      "name": ".ren-sidebar-toggle"
    },
    {
      "data": {},
      "body": ".ren-skeleton",
      "path": "components/primitives/ren-skeleton/ren-skeleton.css",
      "id": "selector:.ren-skeleton",
      "type": "selector",
      "name": ".ren-skeleton"
    },
    {
      "data": {},
      "body": ".ren-skeleton-circle",
      "path": "components/primitives/ren-skeleton/ren-skeleton.css",
      "id": "selector:.ren-skeleton-circle",
      "type": "selector",
      "name": ".ren-skeleton-circle"
    },
    {
      "data": {},
      "body": ".ren-skeleton-heading",
      "path": "components/primitives/ren-skeleton/ren-skeleton.css",
      "id": "selector:.ren-skeleton-heading",
      "type": "selector",
      "name": ".ren-skeleton-heading"
    },
    {
      "data": {},
      "body": ".ren-skeleton-rect",
      "path": "components/primitives/ren-skeleton/ren-skeleton.css",
      "id": "selector:.ren-skeleton-rect",
      "type": "selector",
      "name": ".ren-skeleton-rect"
    },
    {
      "data": {},
      "body": ".ren-skeleton-text",
      "path": "components/primitives/ren-skeleton/ren-skeleton.css",
      "id": "selector:.ren-skeleton-text",
      "type": "selector",
      "name": ".ren-skeleton-text"
    },
    {
      "data": {},
      "body": ".ren-slider",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider",
      "type": "selector",
      "name": ".ren-slider"
    },
    {
      "data": {},
      "body": ".ren-slider-danger",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider-danger",
      "type": "selector",
      "name": ".ren-slider-danger"
    },
    {
      "data": {},
      "body": ".ren-slider-label",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider-label",
      "type": "selector",
      "name": ".ren-slider-label"
    },
    {
      "data": {},
      "body": ".ren-slider-lg",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider-lg",
      "type": "selector",
      "name": ".ren-slider-lg"
    },
    {
      "data": {},
      "body": ".ren-slider-mark",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider-mark",
      "type": "selector",
      "name": ".ren-slider-mark"
    },
    {
      "data": {},
      "body": ".ren-slider-mark-label",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider-mark-label",
      "type": "selector",
      "name": ".ren-slider-mark-label"
    },
    {
      "data": {},
      "body": ".ren-slider-mark-tick",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider-mark-tick",
      "type": "selector",
      "name": ".ren-slider-mark-tick"
    },
    {
      "data": {},
      "body": ".ren-slider-marks",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider-marks",
      "type": "selector",
      "name": ".ren-slider-marks"
    },
    {
      "data": {},
      "body": ".ren-slider-range",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider-range",
      "type": "selector",
      "name": ".ren-slider-range"
    },
    {
      "data": {},
      "body": ".ren-slider-sm",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider-sm",
      "type": "selector",
      "name": ".ren-slider-sm"
    },
    {
      "data": {},
      "body": ".ren-slider-success",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider-success",
      "type": "selector",
      "name": ".ren-slider-success"
    },
    {
      "data": {},
      "body": ".ren-slider-track",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider-track",
      "type": "selector",
      "name": ".ren-slider-track"
    },
    {
      "data": {},
      "body": ".ren-slider-track-input",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider-track-input",
      "type": "selector",
      "name": ".ren-slider-track-input"
    },
    {
      "data": {},
      "body": ".ren-slider-value",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider-value",
      "type": "selector",
      "name": ".ren-slider-value"
    },
    {
      "data": {},
      "body": ".ren-slider-vertical",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider-vertical",
      "type": "selector",
      "name": ".ren-slider-vertical"
    },
    {
      "data": {},
      "body": ".ren-slider-warning",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "selector:.ren-slider-warning",
      "type": "selector",
      "name": ".ren-slider-warning"
    },
    {
      "data": {},
      "body": ".ren-spinner",
      "path": "components/primitives/ren-spinner/ren-spinner.css",
      "id": "selector:.ren-spinner",
      "type": "selector",
      "name": ".ren-spinner"
    },
    {
      "data": {},
      "body": ".ren-spinner-lg",
      "path": "components/primitives/ren-spinner/ren-spinner.css",
      "id": "selector:.ren-spinner-lg",
      "type": "selector",
      "name": ".ren-spinner-lg"
    },
    {
      "data": {},
      "body": ".ren-spinner-light",
      "path": "components/primitives/ren-spinner/ren-spinner.css",
      "id": "selector:.ren-spinner-light",
      "type": "selector",
      "name": ".ren-spinner-light"
    },
    {
      "data": {},
      "body": ".ren-spinner-sm",
      "path": "components/primitives/ren-spinner/ren-spinner.css",
      "id": "selector:.ren-spinner-sm",
      "type": "selector",
      "name": ".ren-spinner-sm"
    },
    {
      "data": {},
      "body": ".ren-spinner-xl",
      "path": "components/primitives/ren-spinner/ren-spinner.css",
      "id": "selector:.ren-spinner-xl",
      "type": "selector",
      "name": ".ren-spinner-xl"
    },
    {
      "data": {},
      "body": ".ren-spinner-xs",
      "path": "components/primitives/ren-spinner/ren-spinner.css",
      "id": "selector:.ren-spinner-xs",
      "type": "selector",
      "name": ".ren-spinner-xs"
    },
    {
      "data": {},
      "body": ".ren-switch",
      "path": "components/primitives/ren-switch/ren-switch.css",
      "id": "selector:.ren-switch",
      "type": "selector",
      "name": ".ren-switch"
    },
    {
      "data": {},
      "body": ".ren-switch-track",
      "path": "components/primitives/ren-switch/ren-switch.css",
      "id": "selector:.ren-switch-track",
      "type": "selector",
      "name": ".ren-switch-track"
    },
    {
      "data": {},
      "body": ".ren-tab",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "selector:.ren-tab",
      "type": "selector",
      "name": ".ren-tab"
    },
    {
      "data": {},
      "body": ".ren-tab-list",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "selector:.ren-tab-list",
      "type": "selector",
      "name": ".ren-tab-list"
    },
    {
      "data": {},
      "body": ".ren-tab-list-enclosed",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "selector:.ren-tab-list-enclosed",
      "type": "selector",
      "name": ".ren-tab-list-enclosed"
    },
    {
      "data": {},
      "body": ".ren-tab-list-pills",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "selector:.ren-tab-list-pills",
      "type": "selector",
      "name": ".ren-tab-list-pills"
    },
    {
      "data": {},
      "body": ".ren-tab-list-underline",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "selector:.ren-tab-list-underline",
      "type": "selector",
      "name": ".ren-tab-list-underline"
    },
    {
      "data": {},
      "body": ".ren-tab-panel",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "selector:.ren-tab-panel",
      "type": "selector",
      "name": ".ren-tab-panel"
    },
    {
      "data": {},
      "body": ".ren-table",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table",
      "type": "selector",
      "name": ".ren-table"
    },
    {
      "data": {},
      "body": ".ren-table-body",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-body",
      "type": "selector",
      "name": ".ren-table-body"
    },
    {
      "data": {},
      "body": ".ren-table-comfortable",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-comfortable",
      "type": "selector",
      "name": ".ren-table-comfortable"
    },
    {
      "data": {},
      "body": ".ren-table-compact",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-compact",
      "type": "selector",
      "name": ".ren-table-compact"
    },
    {
      "data": {},
      "body": ".ren-table-empty",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-empty",
      "type": "selector",
      "name": ".ren-table-empty"
    },
    {
      "data": {},
      "body": ".ren-table-empty-description",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-empty-description",
      "type": "selector",
      "name": ".ren-table-empty-description"
    },
    {
      "data": {},
      "body": ".ren-table-empty-icon",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-empty-icon",
      "type": "selector",
      "name": ".ren-table-empty-icon"
    },
    {
      "data": {},
      "body": ".ren-table-empty-title",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-empty-title",
      "type": "selector",
      "name": ".ren-table-empty-title"
    },
    {
      "data": {},
      "body": ".ren-table-header",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-header",
      "type": "selector",
      "name": ".ren-table-header"
    },
    {
      "data": {},
      "body": ".ren-table-loading-overlay",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-loading-overlay",
      "type": "selector",
      "name": ".ren-table-loading-overlay"
    },
    {
      "data": {},
      "body": ".ren-table-pagination",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-pagination",
      "type": "selector",
      "name": ".ren-table-pagination"
    },
    {
      "data": {},
      "body": ".ren-table-pagination-button",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-pagination-button",
      "type": "selector",
      "name": ".ren-table-pagination-button"
    },
    {
      "data": {},
      "body": ".ren-table-pagination-controls",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-pagination-controls",
      "type": "selector",
      "name": ".ren-table-pagination-controls"
    },
    {
      "data": {},
      "body": ".ren-table-pagination-info",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-pagination-info",
      "type": "selector",
      "name": ".ren-table-pagination-info"
    },
    {
      "data": {},
      "body": ".ren-table-select",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-select",
      "type": "selector",
      "name": ".ren-table-select"
    },
    {
      "data": {},
      "body": ".ren-table-toolbar",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-toolbar",
      "type": "selector",
      "name": ".ren-table-toolbar"
    },
    {
      "data": {},
      "body": ".ren-table-toolbar-search",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-toolbar-search",
      "type": "selector",
      "name": ".ren-table-toolbar-search"
    },
    {
      "data": {},
      "body": ".ren-table-wrapper",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-table-wrapper",
      "type": "selector",
      "name": ".ren-table-wrapper"
    },
    {
      "data": {},
      "body": ".ren-tabs",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "selector:.ren-tabs",
      "type": "selector",
      "name": ".ren-tabs"
    },
    {
      "data": {},
      "body": ".ren-tabs-indicator",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "selector:.ren-tabs-indicator",
      "type": "selector",
      "name": ".ren-tabs-indicator"
    },
    {
      "data": {},
      "body": ".ren-tag",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "selector:.ren-tag",
      "type": "selector",
      "name": ".ren-tag"
    },
    {
      "data": {},
      "body": ".ren-tag-clickable",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "selector:.ren-tag-clickable",
      "type": "selector",
      "name": ".ren-tag-clickable"
    },
    {
      "data": {},
      "body": ".ren-tag-danger",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "selector:.ren-tag-danger",
      "type": "selector",
      "name": ".ren-tag-danger"
    },
    {
      "data": {},
      "body": ".ren-tag-dismiss",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "selector:.ren-tag-dismiss",
      "type": "selector",
      "name": ".ren-tag-dismiss"
    },
    {
      "data": {},
      "body": ".ren-tag-group",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "selector:.ren-tag-group",
      "type": "selector",
      "name": ".ren-tag-group"
    },
    {
      "data": {},
      "body": ".ren-tag-lg",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "selector:.ren-tag-lg",
      "type": "selector",
      "name": ".ren-tag-lg"
    },
    {
      "data": {},
      "body": ".ren-tag-primary",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "selector:.ren-tag-primary",
      "type": "selector",
      "name": ".ren-tag-primary"
    },
    {
      "data": {},
      "body": ".ren-tag-sm",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "selector:.ren-tag-sm",
      "type": "selector",
      "name": ".ren-tag-sm"
    },
    {
      "data": {},
      "body": ".ren-tag-success",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "selector:.ren-tag-success",
      "type": "selector",
      "name": ".ren-tag-success"
    },
    {
      "data": {},
      "body": ".ren-tag-warning",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "selector:.ren-tag-warning",
      "type": "selector",
      "name": ".ren-tag-warning"
    },
    {
      "data": {},
      "body": ".ren-td",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-td",
      "type": "selector",
      "name": ".ren-td"
    },
    {
      "data": {},
      "body": ".ren-td-pinned",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-td-pinned",
      "type": "selector",
      "name": ".ren-td-pinned"
    },
    {
      "data": {},
      "body": ".ren-th",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-th",
      "type": "selector",
      "name": ".ren-th"
    },
    {
      "data": {},
      "body": ".ren-th-pinned",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-th-pinned",
      "type": "selector",
      "name": ".ren-th-pinned"
    },
    {
      "data": {},
      "body": ".ren-th-resize",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-th-resize",
      "type": "selector",
      "name": ".ren-th-resize"
    },
    {
      "data": {},
      "body": ".ren-th-sortable",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-th-sortable",
      "type": "selector",
      "name": ".ren-th-sortable"
    },
    {
      "data": {},
      "body": ".ren-toast",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast",
      "type": "selector",
      "name": ".ren-toast"
    },
    {
      "data": {},
      "body": ".ren-toast-action",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast-action",
      "type": "selector",
      "name": ".ren-toast-action"
    },
    {
      "data": {},
      "body": ".ren-toast-actions",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast-actions",
      "type": "selector",
      "name": ".ren-toast-actions"
    },
    {
      "data": {},
      "body": ".ren-toast-body",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast-body",
      "type": "selector",
      "name": ".ren-toast-body"
    },
    {
      "data": {},
      "body": ".ren-toast-close",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast-close",
      "type": "selector",
      "name": ".ren-toast-close"
    },
    {
      "data": {},
      "body": ".ren-toast-danger",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast-danger",
      "type": "selector",
      "name": ".ren-toast-danger"
    },
    {
      "data": {},
      "body": ".ren-toast-description",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast-description",
      "type": "selector",
      "name": ".ren-toast-description"
    },
    {
      "data": {},
      "body": ".ren-toast-error",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast-error",
      "type": "selector",
      "name": ".ren-toast-error"
    },
    {
      "data": {},
      "body": ".ren-toast-icon",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast-icon",
      "type": "selector",
      "name": ".ren-toast-icon"
    },
    {
      "data": {},
      "body": ".ren-toast-info",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast-info",
      "type": "selector",
      "name": ".ren-toast-info"
    },
    {
      "data": {},
      "body": ".ren-toast-loading",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast-loading",
      "type": "selector",
      "name": ".ren-toast-loading"
    },
    {
      "data": {},
      "body": ".ren-toast-progress",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast-progress",
      "type": "selector",
      "name": ".ren-toast-progress"
    },
    {
      "data": {},
      "body": ".ren-toast-success",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast-success",
      "type": "selector",
      "name": ".ren-toast-success"
    },
    {
      "data": {},
      "body": ".ren-toast-title",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast-title",
      "type": "selector",
      "name": ".ren-toast-title"
    },
    {
      "data": {},
      "body": ".ren-toast-viewport",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast-viewport",
      "type": "selector",
      "name": ".ren-toast-viewport"
    },
    {
      "data": {},
      "body": ".ren-toast-warning",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "selector:.ren-toast-warning",
      "type": "selector",
      "name": ".ren-toast-warning"
    },
    {
      "data": {},
      "body": ".ren-toggle-group",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "selector:.ren-toggle-group",
      "type": "selector",
      "name": ".ren-toggle-group"
    },
    {
      "data": {},
      "body": ".ren-toggle-group-full",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "selector:.ren-toggle-group-full",
      "type": "selector",
      "name": ".ren-toggle-group-full"
    },
    {
      "data": {},
      "body": ".ren-toggle-group-item",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "selector:.ren-toggle-group-item",
      "type": "selector",
      "name": ".ren-toggle-group-item"
    },
    {
      "data": {},
      "body": ".ren-toggle-group-lg",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "selector:.ren-toggle-group-lg",
      "type": "selector",
      "name": ".ren-toggle-group-lg"
    },
    {
      "data": {},
      "body": ".ren-toggle-group-sm",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "selector:.ren-toggle-group-sm",
      "type": "selector",
      "name": ".ren-toggle-group-sm"
    },
    {
      "data": {},
      "body": ".ren-toolbar",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "selector:.ren-toolbar",
      "type": "selector",
      "name": ".ren-toolbar"
    },
    {
      "data": {},
      "body": ".ren-toolbar-ghost",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "selector:.ren-toolbar-ghost",
      "type": "selector",
      "name": ".ren-toolbar-ghost"
    },
    {
      "data": {},
      "body": ".ren-toolbar-item",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "selector:.ren-toolbar-item",
      "type": "selector",
      "name": ".ren-toolbar-item"
    },
    {
      "data": {},
      "body": ".ren-toolbar-item-icon",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "selector:.ren-toolbar-item-icon",
      "type": "selector",
      "name": ".ren-toolbar-item-icon"
    },
    {
      "data": {},
      "body": ".ren-toolbar-separator",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "selector:.ren-toolbar-separator",
      "type": "selector",
      "name": ".ren-toolbar-separator"
    },
    {
      "data": {},
      "body": ".ren-toolbar-vertical",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "selector:.ren-toolbar-vertical",
      "type": "selector",
      "name": ".ren-toolbar-vertical"
    },
    {
      "data": {},
      "body": ".ren-tooltip",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "selector:.ren-tooltip",
      "type": "selector",
      "name": ".ren-tooltip"
    },
    {
      "data": {},
      "body": ".ren-tooltip-arrow",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "selector:.ren-tooltip-arrow",
      "type": "selector",
      "name": ".ren-tooltip-arrow"
    },
    {
      "data": {},
      "body": ".ren-tooltip-trigger",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "selector:.ren-tooltip-trigger",
      "type": "selector",
      "name": ".ren-tooltip-trigger"
    },
    {
      "data": {},
      "body": ".ren-tr",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "selector:.ren-tr",
      "type": "selector",
      "name": ".ren-tr"
    },
    {
      "data": {},
      "body": "--_active",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--_active",
      "type": "token",
      "name": "--_active"
    },
    {
      "data": {},
      "body": "--_hover",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--_hover",
      "type": "token",
      "name": "--_hover"
    },
    {
      "data": {},
      "body": "--avatar-2xl",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "token:--avatar-2xl",
      "type": "token",
      "name": "--avatar-2xl"
    },
    {
      "data": {},
      "body": "--avatar-lg",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "token:--avatar-lg",
      "type": "token",
      "name": "--avatar-lg"
    },
    {
      "data": {},
      "body": "--avatar-md",
      "path": "components/primitives/ren-skeleton/ren-skeleton.css",
      "id": "token:--avatar-md",
      "type": "token",
      "name": "--avatar-md"
    },
    {
      "data": {},
      "body": "--avatar-sm",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "token:--avatar-sm",
      "type": "token",
      "name": "--avatar-sm"
    },
    {
      "data": {},
      "body": "--avatar-xl",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "token:--avatar-xl",
      "type": "token",
      "name": "--avatar-xl"
    },
    {
      "data": {},
      "body": "--avatar-xs",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "token:--avatar-xs",
      "type": "token",
      "name": "--avatar-xs"
    },
    {
      "data": {},
      "body": "--blue-100",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "token:--blue-100",
      "type": "token",
      "name": "--blue-100"
    },
    {
      "data": {},
      "body": "--blue-200",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "token:--blue-200",
      "type": "token",
      "name": "--blue-200"
    },
    {
      "data": {},
      "body": "--blue-300",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "token:--blue-300",
      "type": "token",
      "name": "--blue-300"
    },
    {
      "data": {},
      "body": "--blue-50",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "token:--blue-50",
      "type": "token",
      "name": "--blue-50"
    },
    {
      "data": {},
      "body": "--blue-600",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "token:--blue-600",
      "type": "token",
      "name": "--blue-600"
    },
    {
      "data": {},
      "body": "--blue-700",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "token:--blue-700",
      "type": "token",
      "name": "--blue-700"
    },
    {
      "data": {},
      "body": "--blue-800",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "token:--blue-800",
      "type": "token",
      "name": "--blue-800"
    },
    {
      "data": {},
      "body": "--blue-900",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "token:--blue-900",
      "type": "token",
      "name": "--blue-900"
    },
    {
      "data": {},
      "body": "--body-size",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--body-size",
      "type": "token",
      "name": "--body-size"
    },
    {
      "data": {},
      "body": "--body-sm-size",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--body-sm-size",
      "type": "token",
      "name": "--body-sm-size"
    },
    {
      "data": {},
      "body": "--btn-accent",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--btn-accent",
      "type": "token",
      "name": "--btn-accent"
    },
    {
      "data": {},
      "body": "--caption-size",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--caption-size",
      "type": "token",
      "name": "--caption-size"
    },
    {
      "data": {},
      "body": "--caption-sm-size",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "token:--caption-sm-size",
      "type": "token",
      "name": "--caption-sm-size"
    },
    {
      "data": {},
      "body": "--color-accent",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--color-accent",
      "type": "token",
      "name": "--color-accent"
    },
    {
      "data": {},
      "body": "--color-accent-hover",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "token:--color-accent-hover",
      "type": "token",
      "name": "--color-accent-hover"
    },
    {
      "data": {},
      "body": "--color-accent-strong",
      "path": "components/composites/ren-hover-card/ren-hover-card.css",
      "id": "token:--color-accent-strong",
      "type": "token",
      "name": "--color-accent-strong"
    },
    {
      "data": {},
      "body": "--color-accent-subtle",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--color-accent-subtle",
      "type": "token",
      "name": "--color-accent-subtle"
    },
    {
      "data": {},
      "body": "--color-ai",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "token:--color-ai",
      "type": "token",
      "name": "--color-ai"
    },
    {
      "data": {},
      "body": "--color-ai-hover",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "token:--color-ai-hover",
      "type": "token",
      "name": "--color-ai-hover"
    },
    {
      "data": {},
      "body": "--color-ai-subtle",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "token:--color-ai-subtle",
      "type": "token",
      "name": "--color-ai-subtle"
    },
    {
      "data": {},
      "body": "--color-background-active",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "token:--color-background-active",
      "type": "token",
      "name": "--color-background-active"
    },
    {
      "data": {},
      "body": "--color-background-hover",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "token:--color-background-hover",
      "type": "token",
      "name": "--color-background-hover"
    },
    {
      "data": {},
      "body": "--color-border",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--color-border",
      "type": "token",
      "name": "--color-border"
    },
    {
      "data": {},
      "body": "--color-border-hover",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "token:--color-border-hover",
      "type": "token",
      "name": "--color-border-hover"
    },
    {
      "data": {},
      "body": "--color-border-interactive",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--color-border-interactive",
      "type": "token",
      "name": "--color-border-interactive"
    },
    {
      "data": {},
      "body": "--color-border-muted",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--color-border-muted",
      "type": "token",
      "name": "--color-border-muted"
    },
    {
      "data": {},
      "body": "--color-border-strong",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "token:--color-border-strong",
      "type": "token",
      "name": "--color-border-strong"
    },
    {
      "data": {},
      "body": "--color-danger",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--color-danger",
      "type": "token",
      "name": "--color-danger"
    },
    {
      "data": {},
      "body": "--color-danger-strong",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "token:--color-danger-strong",
      "type": "token",
      "name": "--color-danger-strong"
    },
    {
      "data": {},
      "body": "--color-danger-subtle",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--color-danger-subtle",
      "type": "token",
      "name": "--color-danger-subtle"
    },
    {
      "data": {},
      "body": "--color-disabled-bg",
      "path": "components/composites/ren-otp/ren-otp.css",
      "id": "token:--color-disabled-bg",
      "type": "token",
      "name": "--color-disabled-bg"
    },
    {
      "data": {},
      "body": "--color-disabled-text",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "token:--color-disabled-text",
      "type": "token",
      "name": "--color-disabled-text"
    },
    {
      "data": {},
      "body": "--color-fill",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--color-fill",
      "type": "token",
      "name": "--color-fill"
    },
    {
      "data": {},
      "body": "--color-fill-active",
      "path": "components/patterns/ren-menubar/ren-menubar.css",
      "id": "token:--color-fill-active",
      "type": "token",
      "name": "--color-fill-active"
    },
    {
      "data": {},
      "body": "--color-fill-hover",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--color-fill-hover",
      "type": "token",
      "name": "--color-fill-hover"
    },
    {
      "data": {},
      "body": "--color-fill-subtle",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "token:--color-fill-subtle",
      "type": "token",
      "name": "--color-fill-subtle"
    },
    {
      "data": {},
      "body": "--color-focus-ring",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "token:--color-focus-ring",
      "type": "token",
      "name": "--color-focus-ring"
    },
    {
      "data": {},
      "body": "--color-info",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--color-info",
      "type": "token",
      "name": "--color-info"
    },
    {
      "data": {},
      "body": "--color-info-strong",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "token:--color-info-strong",
      "type": "token",
      "name": "--color-info-strong"
    },
    {
      "data": {},
      "body": "--color-info-subtle",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--color-info-subtle",
      "type": "token",
      "name": "--color-info-subtle"
    },
    {
      "data": {},
      "body": "--color-input-bg",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--color-input-bg",
      "type": "token",
      "name": "--color-input-bg"
    },
    {
      "data": {},
      "body": "--color-input-bg-hover",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "token:--color-input-bg-hover",
      "type": "token",
      "name": "--color-input-bg-hover"
    },
    {
      "data": {},
      "body": "--color-input-border",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "token:--color-input-border",
      "type": "token",
      "name": "--color-input-border"
    },
    {
      "data": {},
      "body": "--color-input-border-focus",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--color-input-border-focus",
      "type": "token",
      "name": "--color-input-border-focus"
    },
    {
      "data": {},
      "body": "--color-input-disabled-bg",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "token:--color-input-disabled-bg",
      "type": "token",
      "name": "--color-input-disabled-bg"
    },
    {
      "data": {},
      "body": "--color-input-disabled-text",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "token:--color-input-disabled-text",
      "type": "token",
      "name": "--color-input-disabled-text"
    },
    {
      "data": {},
      "body": "--color-input-error-border",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--color-input-error-border",
      "type": "token",
      "name": "--color-input-error-border"
    },
    {
      "data": {},
      "body": "--color-input-focus-ring",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "token:--color-input-focus-ring",
      "type": "token",
      "name": "--color-input-focus-ring"
    },
    {
      "data": {},
      "body": "--color-input-placeholder",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--color-input-placeholder",
      "type": "token",
      "name": "--color-input-placeholder"
    },
    {
      "data": {},
      "body": "--color-on-accent",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--color-on-accent",
      "type": "token",
      "name": "--color-on-accent"
    },
    {
      "data": {},
      "body": "--color-on-ai",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "token:--color-on-ai",
      "type": "token",
      "name": "--color-on-ai"
    },
    {
      "data": {},
      "body": "--color-on-danger",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--color-on-danger",
      "type": "token",
      "name": "--color-on-danger"
    },
    {
      "data": {},
      "body": "--color-on-success",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--color-on-success",
      "type": "token",
      "name": "--color-on-success"
    },
    {
      "data": {},
      "body": "--color-overlay",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "token:--color-overlay",
      "type": "token",
      "name": "--color-overlay"
    },
    {
      "data": {},
      "body": "--color-separator",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--color-separator",
      "type": "token",
      "name": "--color-separator"
    },
    {
      "data": {},
      "body": "--color-success",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--color-success",
      "type": "token",
      "name": "--color-success"
    },
    {
      "data": {},
      "body": "--color-success-strong",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "token:--color-success-strong",
      "type": "token",
      "name": "--color-success-strong"
    },
    {
      "data": {},
      "body": "--color-success-subtle",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--color-success-subtle",
      "type": "token",
      "name": "--color-success-subtle"
    },
    {
      "data": {},
      "body": "--color-surface",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--color-surface",
      "type": "token",
      "name": "--color-surface"
    },
    {
      "data": {},
      "body": "--color-surface-active",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--color-surface-active",
      "type": "token",
      "name": "--color-surface-active"
    },
    {
      "data": {},
      "body": "--color-surface-hover",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--color-surface-hover",
      "type": "token",
      "name": "--color-surface-hover"
    },
    {
      "data": {},
      "body": "--color-surface-overlay",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--color-surface-overlay",
      "type": "token",
      "name": "--color-surface-overlay"
    },
    {
      "data": {},
      "body": "--color-surface-raised",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--color-surface-raised",
      "type": "token",
      "name": "--color-surface-raised"
    },
    {
      "data": {},
      "body": "--color-surface-sunken",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--color-surface-sunken",
      "type": "token",
      "name": "--color-surface-sunken"
    },
    {
      "data": {},
      "body": "--color-text",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--color-text",
      "type": "token",
      "name": "--color-text"
    },
    {
      "data": {},
      "body": "--color-text-faint",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--color-text-faint",
      "type": "token",
      "name": "--color-text-faint"
    },
    {
      "data": {},
      "body": "--color-text-inverted",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "token:--color-text-inverted",
      "type": "token",
      "name": "--color-text-inverted"
    },
    {
      "data": {},
      "body": "--color-text-link",
      "path": "components/primitives/ren-link/ren-link.css",
      "id": "token:--color-text-link",
      "type": "token",
      "name": "--color-text-link"
    },
    {
      "data": {},
      "body": "--color-text-link-hover",
      "path": "components/primitives/ren-link/ren-link.css",
      "id": "token:--color-text-link-hover",
      "type": "token",
      "name": "--color-text-link-hover"
    },
    {
      "data": {},
      "body": "--color-text-muted",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--color-text-muted",
      "type": "token",
      "name": "--color-text-muted"
    },
    {
      "data": {},
      "body": "--color-text-primary",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "token:--color-text-primary",
      "type": "token",
      "name": "--color-text-primary"
    },
    {
      "data": {},
      "body": "--color-text-secondary",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "token:--color-text-secondary",
      "type": "token",
      "name": "--color-text-secondary"
    },
    {
      "data": {},
      "body": "--color-warning",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "token:--color-warning",
      "type": "token",
      "name": "--color-warning"
    },
    {
      "data": {},
      "body": "--color-warning-strong",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "token:--color-warning-strong",
      "type": "token",
      "name": "--color-warning-strong"
    },
    {
      "data": {},
      "body": "--color-warning-subtle",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--color-warning-subtle",
      "type": "token",
      "name": "--color-warning-subtle"
    },
    {
      "data": {},
      "body": "--dialog-max-width",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "token:--dialog-max-width",
      "type": "token",
      "name": "--dialog-max-width"
    },
    {
      "data": {},
      "body": "--duration-enter",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--duration-enter",
      "type": "token",
      "name": "--duration-enter"
    },
    {
      "data": {},
      "body": "--duration-exit",
      "path": "components/patterns/ren-menubar/ren-menubar.css",
      "id": "token:--duration-exit",
      "type": "token",
      "name": "--duration-exit"
    },
    {
      "data": {},
      "body": "--duration-fast",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "token:--duration-fast",
      "type": "token",
      "name": "--duration-fast"
    },
    {
      "data": {},
      "body": "--duration-loop",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "token:--duration-loop",
      "type": "token",
      "name": "--duration-loop"
    },
    {
      "data": {},
      "body": "--duration-loop-gentle",
      "path": "components/primitives/ren-spinner/ren-spinner.css",
      "id": "token:--duration-loop-gentle",
      "type": "token",
      "name": "--duration-loop-gentle"
    },
    {
      "data": {},
      "body": "--duration-micro",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--duration-micro",
      "type": "token",
      "name": "--duration-micro"
    },
    {
      "data": {},
      "body": "--duration-normal",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--duration-normal",
      "type": "token",
      "name": "--duration-normal"
    },
    {
      "data": {},
      "body": "--duration-overlay",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "token:--duration-overlay",
      "type": "token",
      "name": "--duration-overlay"
    },
    {
      "data": {},
      "body": "--duration-route",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "token:--duration-route",
      "type": "token",
      "name": "--duration-route"
    },
    {
      "data": {},
      "body": "--duration-slow",
      "path": "components/composites/ren-combobox/ren-combobox.css",
      "id": "token:--duration-slow",
      "type": "token",
      "name": "--duration-slow"
    },
    {
      "data": {},
      "body": "--duration-state",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--duration-state",
      "type": "token",
      "name": "--duration-state"
    },
    {
      "data": {},
      "body": "--duration-tactile",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--duration-tactile",
      "type": "token",
      "name": "--duration-tactile"
    },
    {
      "data": {},
      "body": "--ease-enter",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--ease-enter",
      "type": "token",
      "name": "--ease-enter"
    },
    {
      "data": {},
      "body": "--ease-exit",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--ease-exit",
      "type": "token",
      "name": "--ease-exit"
    },
    {
      "data": {},
      "body": "--ease-loop",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "token:--ease-loop",
      "type": "token",
      "name": "--ease-loop"
    },
    {
      "data": {},
      "body": "--ease-loop-pulse",
      "path": "components/primitives/ren-spinner/ren-spinner.css",
      "id": "token:--ease-loop-pulse",
      "type": "token",
      "name": "--ease-loop-pulse"
    },
    {
      "data": {},
      "body": "--ease-loop-smooth",
      "path": "components/primitives/ren-spinner/ren-spinner.css",
      "id": "token:--ease-loop-smooth",
      "type": "token",
      "name": "--ease-loop-smooth"
    },
    {
      "data": {},
      "body": "--ease-out",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ease-out",
      "type": "token",
      "name": "--ease-out"
    },
    {
      "data": {},
      "body": "--ease-playful",
      "path": "components/primitives/ren-radio/ren-radio.css",
      "id": "token:--ease-playful",
      "type": "token",
      "name": "--ease-playful"
    },
    {
      "data": {},
      "body": "--ease-state-change",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ease-state-change",
      "type": "token",
      "name": "--ease-state-change"
    },
    {
      "data": {},
      "body": "--font-mono",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "token:--font-mono",
      "type": "token",
      "name": "--font-mono"
    },
    {
      "data": {},
      "body": "--font-size-body",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "token:--font-size-body",
      "type": "token",
      "name": "--font-size-body"
    },
    {
      "data": {},
      "body": "--font-size-body-sm",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "token:--font-size-body-sm",
      "type": "token",
      "name": "--font-size-body-sm"
    },
    {
      "data": {},
      "body": "--font-size-label",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "token:--font-size-label",
      "type": "token",
      "name": "--font-size-label"
    },
    {
      "data": {},
      "body": "--font-size-xs",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "token:--font-size-xs",
      "type": "token",
      "name": "--font-size-xs"
    },
    {
      "data": {},
      "body": "--font-weight-semibold",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--font-weight-semibold",
      "type": "token",
      "name": "--font-weight-semibold"
    },
    {
      "data": {},
      "body": "--gray-400",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "token:--gray-400",
      "type": "token",
      "name": "--gray-400"
    },
    {
      "data": {},
      "body": "--gray-50",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "token:--gray-50",
      "type": "token",
      "name": "--gray-50"
    },
    {
      "data": {},
      "body": "--gray-500",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "token:--gray-500",
      "type": "token",
      "name": "--gray-500"
    },
    {
      "data": {},
      "body": "--gray-800",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "token:--gray-800",
      "type": "token",
      "name": "--gray-800"
    },
    {
      "data": {},
      "body": "--icon-lg",
      "path": "components/primitives/ren-field/ren-field.css",
      "id": "token:--icon-lg",
      "type": "token",
      "name": "--icon-lg"
    },
    {
      "data": {},
      "body": "--icon-xl",
      "path": "components/composites/ren-alert-dialog/ren-alert-dialog.css",
      "id": "token:--icon-xl",
      "type": "token",
      "name": "--icon-xl"
    },
    {
      "data": {},
      "body": "--label-lg-size",
      "path": "components/composites/ren-collapsible/ren-collapsible.css",
      "id": "token:--label-lg-size",
      "type": "token",
      "name": "--label-lg-size"
    },
    {
      "data": {},
      "body": "--label-lg-weight",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "token:--label-lg-weight",
      "type": "token",
      "name": "--label-lg-weight"
    },
    {
      "data": {},
      "body": "--label-md-size",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "token:--label-md-size",
      "type": "token",
      "name": "--label-md-size"
    },
    {
      "data": {},
      "body": "--label-md-weight",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "token:--label-md-weight",
      "type": "token",
      "name": "--label-md-weight"
    },
    {
      "data": {},
      "body": "--label-size",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--label-size",
      "type": "token",
      "name": "--label-size"
    },
    {
      "data": {},
      "body": "--label-weight",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--label-weight",
      "type": "token",
      "name": "--label-weight"
    },
    {
      "data": {},
      "body": "--leading-5",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--leading-5",
      "type": "token",
      "name": "--leading-5"
    },
    {
      "data": {},
      "body": "--leading-normal",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--leading-normal",
      "type": "token",
      "name": "--leading-normal"
    },
    {
      "data": {},
      "body": "--leading-relaxed",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "token:--leading-relaxed",
      "type": "token",
      "name": "--leading-relaxed"
    },
    {
      "data": {},
      "body": "--leading-snug",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "token:--leading-snug",
      "type": "token",
      "name": "--leading-snug"
    },
    {
      "data": {},
      "body": "--radius-full",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "token:--radius-full",
      "type": "token",
      "name": "--radius-full"
    },
    {
      "data": {},
      "body": "--radius-lg",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "token:--radius-lg",
      "type": "token",
      "name": "--radius-lg"
    },
    {
      "data": {},
      "body": "--radius-md",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--radius-md",
      "type": "token",
      "name": "--radius-md"
    },
    {
      "data": {},
      "body": "--radius-sm",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--radius-sm",
      "type": "token",
      "name": "--radius-sm"
    },
    {
      "data": {},
      "body": "--radius-xl",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--radius-xl",
      "type": "token",
      "name": "--radius-xl"
    },
    {
      "data": {},
      "body": "--red-500",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--red-500",
      "type": "token",
      "name": "--red-500"
    },
    {
      "data": {},
      "body": "--red-600",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--red-600",
      "type": "token",
      "name": "--red-600"
    },
    {
      "data": {},
      "body": "--ren-avatar-bg",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "token:--ren-avatar-bg",
      "type": "token",
      "name": "--ren-avatar-bg"
    },
    {
      "data": {},
      "body": "--ren-avatar-border",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "token:--ren-avatar-border",
      "type": "token",
      "name": "--ren-avatar-border"
    },
    {
      "data": {},
      "body": "--ren-avatar-color",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "token:--ren-avatar-color",
      "type": "token",
      "name": "--ren-avatar-color"
    },
    {
      "data": {},
      "body": "--ren-avatar-font-size",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "token:--ren-avatar-font-size",
      "type": "token",
      "name": "--ren-avatar-font-size"
    },
    {
      "data": {},
      "body": "--ren-avatar-radius",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "token:--ren-avatar-radius",
      "type": "token",
      "name": "--ren-avatar-radius"
    },
    {
      "data": {},
      "body": "--ren-avatar-size",
      "path": "components/primitives/ren-avatar/ren-avatar.css",
      "id": "token:--ren-avatar-size",
      "type": "token",
      "name": "--ren-avatar-size"
    },
    {
      "data": {},
      "body": "--ren-badge-bg",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "token:--ren-badge-bg",
      "type": "token",
      "name": "--ren-badge-bg"
    },
    {
      "data": {},
      "body": "--ren-badge-color",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "token:--ren-badge-color",
      "type": "token",
      "name": "--ren-badge-color"
    },
    {
      "data": {},
      "body": "--ren-badge-font-size",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "token:--ren-badge-font-size",
      "type": "token",
      "name": "--ren-badge-font-size"
    },
    {
      "data": {},
      "body": "--ren-badge-font-weight",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "token:--ren-badge-font-weight",
      "type": "token",
      "name": "--ren-badge-font-weight"
    },
    {
      "data": {},
      "body": "--ren-badge-height",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "token:--ren-badge-height",
      "type": "token",
      "name": "--ren-badge-height"
    },
    {
      "data": {},
      "body": "--ren-badge-padding-x",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "token:--ren-badge-padding-x",
      "type": "token",
      "name": "--ren-badge-padding-x"
    },
    {
      "data": {},
      "body": "--ren-badge-radius",
      "path": "components/primitives/ren-badge/ren-badge.css",
      "id": "token:--ren-badge-radius",
      "type": "token",
      "name": "--ren-badge-radius"
    },
    {
      "data": {},
      "body": "--ren-banner-bg",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "token:--ren-banner-bg",
      "type": "token",
      "name": "--ren-banner-bg"
    },
    {
      "data": {},
      "body": "--ren-banner-border-color",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "token:--ren-banner-border-color",
      "type": "token",
      "name": "--ren-banner-border-color"
    },
    {
      "data": {},
      "body": "--ren-banner-border-width",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "token:--ren-banner-border-width",
      "type": "token",
      "name": "--ren-banner-border-width"
    },
    {
      "data": {},
      "body": "--ren-banner-color",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "token:--ren-banner-color",
      "type": "token",
      "name": "--ren-banner-color"
    },
    {
      "data": {},
      "body": "--ren-banner-gap",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "token:--ren-banner-gap",
      "type": "token",
      "name": "--ren-banner-gap"
    },
    {
      "data": {},
      "body": "--ren-banner-icon-size",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "token:--ren-banner-icon-size",
      "type": "token",
      "name": "--ren-banner-icon-size"
    },
    {
      "data": {},
      "body": "--ren-banner-padding",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "token:--ren-banner-padding",
      "type": "token",
      "name": "--ren-banner-padding"
    },
    {
      "data": {},
      "body": "--ren-banner-radius",
      "path": "components/primitives/ren-banner/ren-banner.css",
      "id": "token:--ren-banner-radius",
      "type": "token",
      "name": "--ren-banner-radius"
    },
    {
      "data": {},
      "body": "--ren-body-size",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--ren-body-size",
      "type": "token",
      "name": "--ren-body-size"
    },
    {
      "data": {},
      "body": "--ren-breadcrumb-active-color",
      "path": "components/primitives/ren-breadcrumb/ren-breadcrumb.css",
      "id": "token:--ren-breadcrumb-active-color",
      "type": "token",
      "name": "--ren-breadcrumb-active-color"
    },
    {
      "data": {},
      "body": "--ren-breadcrumb-color",
      "path": "components/primitives/ren-breadcrumb/ren-breadcrumb.css",
      "id": "token:--ren-breadcrumb-color",
      "type": "token",
      "name": "--ren-breadcrumb-color"
    },
    {
      "data": {},
      "body": "--ren-breadcrumb-font-size",
      "path": "components/primitives/ren-breadcrumb/ren-breadcrumb.css",
      "id": "token:--ren-breadcrumb-font-size",
      "type": "token",
      "name": "--ren-breadcrumb-font-size"
    },
    {
      "data": {},
      "body": "--ren-breadcrumb-gap",
      "path": "components/primitives/ren-breadcrumb/ren-breadcrumb.css",
      "id": "token:--ren-breadcrumb-gap",
      "type": "token",
      "name": "--ren-breadcrumb-gap"
    },
    {
      "data": {},
      "body": "--ren-breadcrumb-separator",
      "path": "components/primitives/ren-breadcrumb/ren-breadcrumb.css",
      "id": "token:--ren-breadcrumb-separator",
      "type": "token",
      "name": "--ren-breadcrumb-separator"
    },
    {
      "data": {},
      "body": "--ren-btn-bg",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-bg",
      "type": "token",
      "name": "--ren-btn-bg"
    },
    {
      "data": {},
      "body": "--ren-btn-bg-active",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-bg-active",
      "type": "token",
      "name": "--ren-btn-bg-active"
    },
    {
      "data": {},
      "body": "--ren-btn-bg-hover",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-bg-hover",
      "type": "token",
      "name": "--ren-btn-bg-hover"
    },
    {
      "data": {},
      "body": "--ren-btn-border-color",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-border-color",
      "type": "token",
      "name": "--ren-btn-border-color"
    },
    {
      "data": {},
      "body": "--ren-btn-border-width",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-border-width",
      "type": "token",
      "name": "--ren-btn-border-width"
    },
    {
      "data": {},
      "body": "--ren-btn-color",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-color",
      "type": "token",
      "name": "--ren-btn-color"
    },
    {
      "data": {},
      "body": "--ren-btn-duration",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-duration",
      "type": "token",
      "name": "--ren-btn-duration"
    },
    {
      "data": {},
      "body": "--ren-btn-easing",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-easing",
      "type": "token",
      "name": "--ren-btn-easing"
    },
    {
      "data": {},
      "body": "--ren-btn-font-size",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-font-size",
      "type": "token",
      "name": "--ren-btn-font-size"
    },
    {
      "data": {},
      "body": "--ren-btn-font-weight",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-font-weight",
      "type": "token",
      "name": "--ren-btn-font-weight"
    },
    {
      "data": {},
      "body": "--ren-btn-gap",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-gap",
      "type": "token",
      "name": "--ren-btn-gap"
    },
    {
      "data": {},
      "body": "--ren-btn-height",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-height",
      "type": "token",
      "name": "--ren-btn-height"
    },
    {
      "data": {},
      "body": "--ren-btn-padding-x",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-padding-x",
      "type": "token",
      "name": "--ren-btn-padding-x"
    },
    {
      "data": {},
      "body": "--ren-btn-padding-y",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-padding-y",
      "type": "token",
      "name": "--ren-btn-padding-y"
    },
    {
      "data": {},
      "body": "--ren-btn-radius",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-radius",
      "type": "token",
      "name": "--ren-btn-radius"
    },
    {
      "data": {},
      "body": "--ren-btn-ring-color",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-ring-color",
      "type": "token",
      "name": "--ren-btn-ring-color"
    },
    {
      "data": {},
      "body": "--ren-btn-ring-offset",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-ring-offset",
      "type": "token",
      "name": "--ren-btn-ring-offset"
    },
    {
      "data": {},
      "body": "--ren-btn-ring-width",
      "path": "components/primitives/ren-button/ren-button.css",
      "id": "token:--ren-btn-ring-width",
      "type": "token",
      "name": "--ren-btn-ring-width"
    },
    {
      "data": {},
      "body": "--ren-calendar-bg",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "token:--ren-calendar-bg",
      "type": "token",
      "name": "--ren-calendar-bg"
    },
    {
      "data": {},
      "body": "--ren-calendar-border",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "token:--ren-calendar-border",
      "type": "token",
      "name": "--ren-calendar-border"
    },
    {
      "data": {},
      "body": "--ren-calendar-day-size",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "token:--ren-calendar-day-size",
      "type": "token",
      "name": "--ren-calendar-day-size"
    },
    {
      "data": {},
      "body": "--ren-calendar-radius",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "token:--ren-calendar-radius",
      "type": "token",
      "name": "--ren-calendar-radius"
    },
    {
      "data": {},
      "body": "--ren-calendar-range-bg",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "token:--ren-calendar-range-bg",
      "type": "token",
      "name": "--ren-calendar-range-bg"
    },
    {
      "data": {},
      "body": "--ren-calendar-selected-bg",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "token:--ren-calendar-selected-bg",
      "type": "token",
      "name": "--ren-calendar-selected-bg"
    },
    {
      "data": {},
      "body": "--ren-calendar-selected-color",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "token:--ren-calendar-selected-color",
      "type": "token",
      "name": "--ren-calendar-selected-color"
    },
    {
      "data": {},
      "body": "--ren-calendar-today-bg",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "token:--ren-calendar-today-bg",
      "type": "token",
      "name": "--ren-calendar-today-bg"
    },
    {
      "data": {},
      "body": "--ren-calendar-width",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "token:--ren-calendar-width",
      "type": "token",
      "name": "--ren-calendar-width"
    },
    {
      "data": {},
      "body": "--ren-caption-size",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-caption-size",
      "type": "token",
      "name": "--ren-caption-size"
    },
    {
      "data": {},
      "body": "--ren-card-bg",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "token:--ren-card-bg",
      "type": "token",
      "name": "--ren-card-bg"
    },
    {
      "data": {},
      "body": "--ren-card-border-color",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "token:--ren-card-border-color",
      "type": "token",
      "name": "--ren-card-border-color"
    },
    {
      "data": {},
      "body": "--ren-card-border-width",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "token:--ren-card-border-width",
      "type": "token",
      "name": "--ren-card-border-width"
    },
    {
      "data": {},
      "body": "--ren-card-gap",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "token:--ren-card-gap",
      "type": "token",
      "name": "--ren-card-gap"
    },
    {
      "data": {},
      "body": "--ren-card-header-gap",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "token:--ren-card-header-gap",
      "type": "token",
      "name": "--ren-card-header-gap"
    },
    {
      "data": {},
      "body": "--ren-card-padding",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "token:--ren-card-padding",
      "type": "token",
      "name": "--ren-card-padding"
    },
    {
      "data": {},
      "body": "--ren-card-radius",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "token:--ren-card-radius",
      "type": "token",
      "name": "--ren-card-radius"
    },
    {
      "data": {},
      "body": "--ren-card-shadow",
      "path": "components/primitives/ren-card/ren-card.css",
      "id": "token:--ren-card-shadow",
      "type": "token",
      "name": "--ren-card-shadow"
    },
    {
      "data": {},
      "body": "--ren-carousel-arrow-size",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "token:--ren-carousel-arrow-size",
      "type": "token",
      "name": "--ren-carousel-arrow-size"
    },
    {
      "data": {},
      "body": "--ren-carousel-dot-active",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "token:--ren-carousel-dot-active",
      "type": "token",
      "name": "--ren-carousel-dot-active"
    },
    {
      "data": {},
      "body": "--ren-carousel-dot-color",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "token:--ren-carousel-dot-color",
      "type": "token",
      "name": "--ren-carousel-dot-color"
    },
    {
      "data": {},
      "body": "--ren-carousel-dot-size",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "token:--ren-carousel-dot-size",
      "type": "token",
      "name": "--ren-carousel-dot-size"
    },
    {
      "data": {},
      "body": "--ren-carousel-duration",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "token:--ren-carousel-duration",
      "type": "token",
      "name": "--ren-carousel-duration"
    },
    {
      "data": {},
      "body": "--ren-carousel-easing",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "token:--ren-carousel-easing",
      "type": "token",
      "name": "--ren-carousel-easing"
    },
    {
      "data": {},
      "body": "--ren-carousel-gap",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "token:--ren-carousel-gap",
      "type": "token",
      "name": "--ren-carousel-gap"
    },
    {
      "data": {},
      "body": "--ren-carousel-radius",
      "path": "components/composites/ren-carousel/ren-carousel.css",
      "id": "token:--ren-carousel-radius",
      "type": "token",
      "name": "--ren-carousel-radius"
    },
    {
      "data": {},
      "body": "--ren-checkbox-border",
      "path": "components/primitives/ren-checkbox/ren-checkbox.css",
      "id": "token:--ren-checkbox-border",
      "type": "token",
      "name": "--ren-checkbox-border"
    },
    {
      "data": {},
      "body": "--ren-checkbox-checked-bg",
      "path": "components/primitives/ren-checkbox/ren-checkbox.css",
      "id": "token:--ren-checkbox-checked-bg",
      "type": "token",
      "name": "--ren-checkbox-checked-bg"
    },
    {
      "data": {},
      "body": "--ren-checkbox-checked-color",
      "path": "components/primitives/ren-checkbox/ren-checkbox.css",
      "id": "token:--ren-checkbox-checked-color",
      "type": "token",
      "name": "--ren-checkbox-checked-color"
    },
    {
      "data": {},
      "body": "--ren-checkbox-duration",
      "path": "components/primitives/ren-checkbox/ren-checkbox.css",
      "id": "token:--ren-checkbox-duration",
      "type": "token",
      "name": "--ren-checkbox-duration"
    },
    {
      "data": {},
      "body": "--ren-checkbox-easing",
      "path": "components/primitives/ren-checkbox/ren-checkbox.css",
      "id": "token:--ren-checkbox-easing",
      "type": "token",
      "name": "--ren-checkbox-easing"
    },
    {
      "data": {},
      "body": "--ren-checkbox-gap",
      "path": "components/primitives/ren-checkbox/ren-checkbox.css",
      "id": "token:--ren-checkbox-gap",
      "type": "token",
      "name": "--ren-checkbox-gap"
    },
    {
      "data": {},
      "body": "--ren-checkbox-radius",
      "path": "components/primitives/ren-checkbox/ren-checkbox.css",
      "id": "token:--ren-checkbox-radius",
      "type": "token",
      "name": "--ren-checkbox-radius"
    },
    {
      "data": {},
      "body": "--ren-checkbox-size",
      "path": "components/primitives/ren-checkbox/ren-checkbox.css",
      "id": "token:--ren-checkbox-size",
      "type": "token",
      "name": "--ren-checkbox-size"
    },
    {
      "data": {},
      "body": "--ren-collapse-bg",
      "path": "components/composites/ren-collapsible/ren-collapsible.css",
      "id": "token:--ren-collapse-bg",
      "type": "token",
      "name": "--ren-collapse-bg"
    },
    {
      "data": {},
      "body": "--ren-collapse-border",
      "path": "components/composites/ren-collapsible/ren-collapsible.css",
      "id": "token:--ren-collapse-border",
      "type": "token",
      "name": "--ren-collapse-border"
    },
    {
      "data": {},
      "body": "--ren-collapse-duration",
      "path": "components/composites/ren-collapsible/ren-collapsible.css",
      "id": "token:--ren-collapse-duration",
      "type": "token",
      "name": "--ren-collapse-duration"
    },
    {
      "data": {},
      "body": "--ren-collapse-easing",
      "path": "components/composites/ren-collapsible/ren-collapsible.css",
      "id": "token:--ren-collapse-easing",
      "type": "token",
      "name": "--ren-collapse-easing"
    },
    {
      "data": {},
      "body": "--ren-collapse-padding",
      "path": "components/composites/ren-collapsible/ren-collapsible.css",
      "id": "token:--ren-collapse-padding",
      "type": "token",
      "name": "--ren-collapse-padding"
    },
    {
      "data": {},
      "body": "--ren-collapse-radius",
      "path": "components/composites/ren-collapsible/ren-collapsible.css",
      "id": "token:--ren-collapse-radius",
      "type": "token",
      "name": "--ren-collapse-radius"
    },
    {
      "data": {},
      "body": "--ren-collapse-trigger-font",
      "path": "components/composites/ren-collapsible/ren-collapsible.css",
      "id": "token:--ren-collapse-trigger-font",
      "type": "token",
      "name": "--ren-collapse-trigger-font"
    },
    {
      "data": {},
      "body": "--ren-collapse-trigger-weight",
      "path": "components/composites/ren-collapsible/ren-collapsible.css",
      "id": "token:--ren-collapse-trigger-weight",
      "type": "token",
      "name": "--ren-collapse-trigger-weight"
    },
    {
      "data": {},
      "body": "--ren-command-bg",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--ren-command-bg",
      "type": "token",
      "name": "--ren-command-bg"
    },
    {
      "data": {},
      "body": "--ren-command-border",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--ren-command-border",
      "type": "token",
      "name": "--ren-command-border"
    },
    {
      "data": {},
      "body": "--ren-command-item-height",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--ren-command-item-height",
      "type": "token",
      "name": "--ren-command-item-height"
    },
    {
      "data": {},
      "body": "--ren-command-item-radius",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--ren-command-item-radius",
      "type": "token",
      "name": "--ren-command-item-radius"
    },
    {
      "data": {},
      "body": "--ren-command-max-height",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--ren-command-max-height",
      "type": "token",
      "name": "--ren-command-max-height"
    },
    {
      "data": {},
      "body": "--ren-command-radius",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--ren-command-radius",
      "type": "token",
      "name": "--ren-command-radius"
    },
    {
      "data": {},
      "body": "--ren-command-shadow",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--ren-command-shadow",
      "type": "token",
      "name": "--ren-command-shadow"
    },
    {
      "data": {},
      "body": "--ren-command-width",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--ren-command-width",
      "type": "token",
      "name": "--ren-command-width"
    },
    {
      "data": {},
      "body": "--ren-cp-bg",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "token:--ren-cp-bg",
      "type": "token",
      "name": "--ren-cp-bg"
    },
    {
      "data": {},
      "body": "--ren-cp-handle-size",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "token:--ren-cp-handle-size",
      "type": "token",
      "name": "--ren-cp-handle-size"
    },
    {
      "data": {},
      "body": "--ren-cp-radius",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "token:--ren-cp-radius",
      "type": "token",
      "name": "--ren-cp-radius"
    },
    {
      "data": {},
      "body": "--ren-cp-shadow",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "token:--ren-cp-shadow",
      "type": "token",
      "name": "--ren-cp-shadow"
    },
    {
      "data": {},
      "body": "--ren-cp-swatch-size",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "token:--ren-cp-swatch-size",
      "type": "token",
      "name": "--ren-cp-swatch-size"
    },
    {
      "data": {},
      "body": "--ren-cp-track-height",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "token:--ren-cp-track-height",
      "type": "token",
      "name": "--ren-cp-track-height"
    },
    {
      "data": {},
      "body": "--ren-cp-width",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "token:--ren-cp-width",
      "type": "token",
      "name": "--ren-cp-width"
    },
    {
      "data": {},
      "body": "--ren-dialog-backdrop",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "token:--ren-dialog-backdrop",
      "type": "token",
      "name": "--ren-dialog-backdrop"
    },
    {
      "data": {},
      "body": "--ren-dialog-bg",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "token:--ren-dialog-bg",
      "type": "token",
      "name": "--ren-dialog-bg"
    },
    {
      "data": {},
      "body": "--ren-dialog-border-color",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "token:--ren-dialog-border-color",
      "type": "token",
      "name": "--ren-dialog-border-color"
    },
    {
      "data": {},
      "body": "--ren-dialog-duration",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "token:--ren-dialog-duration",
      "type": "token",
      "name": "--ren-dialog-duration"
    },
    {
      "data": {},
      "body": "--ren-dialog-easing",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "token:--ren-dialog-easing",
      "type": "token",
      "name": "--ren-dialog-easing"
    },
    {
      "data": {},
      "body": "--ren-dialog-gap",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "token:--ren-dialog-gap",
      "type": "token",
      "name": "--ren-dialog-gap"
    },
    {
      "data": {},
      "body": "--ren-dialog-padding",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "token:--ren-dialog-padding",
      "type": "token",
      "name": "--ren-dialog-padding"
    },
    {
      "data": {},
      "body": "--ren-dialog-radius",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "token:--ren-dialog-radius",
      "type": "token",
      "name": "--ren-dialog-radius"
    },
    {
      "data": {},
      "body": "--ren-dialog-shadow",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "token:--ren-dialog-shadow",
      "type": "token",
      "name": "--ren-dialog-shadow"
    },
    {
      "data": {},
      "body": "--ren-dialog-width",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "token:--ren-dialog-width",
      "type": "token",
      "name": "--ren-dialog-width"
    },
    {
      "data": {},
      "body": "--ren-dropzone-bg",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "token:--ren-dropzone-bg",
      "type": "token",
      "name": "--ren-dropzone-bg"
    },
    {
      "data": {},
      "body": "--ren-dropzone-border-color",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "token:--ren-dropzone-border-color",
      "type": "token",
      "name": "--ren-dropzone-border-color"
    },
    {
      "data": {},
      "body": "--ren-dropzone-border-style",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "token:--ren-dropzone-border-style",
      "type": "token",
      "name": "--ren-dropzone-border-style"
    },
    {
      "data": {},
      "body": "--ren-dropzone-border-width",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "token:--ren-dropzone-border-width",
      "type": "token",
      "name": "--ren-dropzone-border-width"
    },
    {
      "data": {},
      "body": "--ren-dropzone-hover-bg",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "token:--ren-dropzone-hover-bg",
      "type": "token",
      "name": "--ren-dropzone-hover-bg"
    },
    {
      "data": {},
      "body": "--ren-dropzone-hover-border",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "token:--ren-dropzone-hover-border",
      "type": "token",
      "name": "--ren-dropzone-hover-border"
    },
    {
      "data": {},
      "body": "--ren-dropzone-padding",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "token:--ren-dropzone-padding",
      "type": "token",
      "name": "--ren-dropzone-padding"
    },
    {
      "data": {},
      "body": "--ren-dropzone-radius",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "token:--ren-dropzone-radius",
      "type": "token",
      "name": "--ren-dropzone-radius"
    },
    {
      "data": {},
      "body": "--ren-empty-desc-color",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "token:--ren-empty-desc-color",
      "type": "token",
      "name": "--ren-empty-desc-color"
    },
    {
      "data": {},
      "body": "--ren-empty-gap",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "token:--ren-empty-gap",
      "type": "token",
      "name": "--ren-empty-gap"
    },
    {
      "data": {},
      "body": "--ren-empty-icon-size",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "token:--ren-empty-icon-size",
      "type": "token",
      "name": "--ren-empty-icon-size"
    },
    {
      "data": {},
      "body": "--ren-empty-max-width",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "token:--ren-empty-max-width",
      "type": "token",
      "name": "--ren-empty-max-width"
    },
    {
      "data": {},
      "body": "--ren-empty-padding",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "token:--ren-empty-padding",
      "type": "token",
      "name": "--ren-empty-padding"
    },
    {
      "data": {},
      "body": "--ren-empty-title-size",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "token:--ren-empty-title-size",
      "type": "token",
      "name": "--ren-empty-title-size"
    },
    {
      "data": {},
      "body": "--ren-field-bg",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--ren-field-bg",
      "type": "token",
      "name": "--ren-field-bg"
    },
    {
      "data": {},
      "body": "--ren-field-border-color",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--ren-field-border-color",
      "type": "token",
      "name": "--ren-field-border-color"
    },
    {
      "data": {},
      "body": "--ren-field-border-width",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--ren-field-border-width",
      "type": "token",
      "name": "--ren-field-border-width"
    },
    {
      "data": {},
      "body": "--ren-field-color",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--ren-field-color",
      "type": "token",
      "name": "--ren-field-color"
    },
    {
      "data": {},
      "body": "--ren-field-error-color",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--ren-field-error-color",
      "type": "token",
      "name": "--ren-field-error-color"
    },
    {
      "data": {},
      "body": "--ren-field-focus-color",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--ren-field-focus-color",
      "type": "token",
      "name": "--ren-field-focus-color"
    },
    {
      "data": {},
      "body": "--ren-field-font-size",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--ren-field-font-size",
      "type": "token",
      "name": "--ren-field-font-size"
    },
    {
      "data": {},
      "body": "--ren-field-height",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--ren-field-height",
      "type": "token",
      "name": "--ren-field-height"
    },
    {
      "data": {},
      "body": "--ren-field-padding-x",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--ren-field-padding-x",
      "type": "token",
      "name": "--ren-field-padding-x"
    },
    {
      "data": {},
      "body": "--ren-field-padding-y",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--ren-field-padding-y",
      "type": "token",
      "name": "--ren-field-padding-y"
    },
    {
      "data": {},
      "body": "--ren-field-placeholder",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--ren-field-placeholder",
      "type": "token",
      "name": "--ren-field-placeholder"
    },
    {
      "data": {},
      "body": "--ren-field-radius",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--ren-field-radius",
      "type": "token",
      "name": "--ren-field-radius"
    },
    {
      "data": {},
      "body": "--ren-field-success-color",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--ren-field-success-color",
      "type": "token",
      "name": "--ren-field-success-color"
    },
    {
      "data": {},
      "body": "--ren-font-mono",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--ren-font-mono",
      "type": "token",
      "name": "--ren-font-mono"
    },
    {
      "data": {},
      "body": "--ren-icon-color",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "token:--ren-icon-color",
      "type": "token",
      "name": "--ren-icon-color"
    },
    {
      "data": {},
      "body": "--ren-icon-fill",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "token:--ren-icon-fill",
      "type": "token",
      "name": "--ren-icon-fill"
    },
    {
      "data": {},
      "body": "--ren-icon-size",
      "path": "components/primitives/ren-icon/ren-icon.css",
      "id": "token:--ren-icon-size",
      "type": "token",
      "name": "--ren-icon-size"
    },
    {
      "data": {},
      "body": "--ren-kbd-bg",
      "path": "components/primitives/ren-kbd/ren-kbd.css",
      "id": "token:--ren-kbd-bg",
      "type": "token",
      "name": "--ren-kbd-bg"
    },
    {
      "data": {},
      "body": "--ren-kbd-border",
      "path": "components/primitives/ren-kbd/ren-kbd.css",
      "id": "token:--ren-kbd-border",
      "type": "token",
      "name": "--ren-kbd-border"
    },
    {
      "data": {},
      "body": "--ren-kbd-color",
      "path": "components/primitives/ren-kbd/ren-kbd.css",
      "id": "token:--ren-kbd-color",
      "type": "token",
      "name": "--ren-kbd-color"
    },
    {
      "data": {},
      "body": "--ren-kbd-font-family",
      "path": "components/primitives/ren-kbd/ren-kbd.css",
      "id": "token:--ren-kbd-font-family",
      "type": "token",
      "name": "--ren-kbd-font-family"
    },
    {
      "data": {},
      "body": "--ren-kbd-font-size",
      "path": "components/primitives/ren-kbd/ren-kbd.css",
      "id": "token:--ren-kbd-font-size",
      "type": "token",
      "name": "--ren-kbd-font-size"
    },
    {
      "data": {},
      "body": "--ren-kbd-padding",
      "path": "components/primitives/ren-kbd/ren-kbd.css",
      "id": "token:--ren-kbd-padding",
      "type": "token",
      "name": "--ren-kbd-padding"
    },
    {
      "data": {},
      "body": "--ren-kbd-radius",
      "path": "components/primitives/ren-kbd/ren-kbd.css",
      "id": "token:--ren-kbd-radius",
      "type": "token",
      "name": "--ren-kbd-radius"
    },
    {
      "data": {},
      "body": "--ren-label-size",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-label-size",
      "type": "token",
      "name": "--ren-label-size"
    },
    {
      "data": {},
      "body": "--ren-link-color",
      "path": "components/primitives/ren-link/ren-link.css",
      "id": "token:--ren-link-color",
      "type": "token",
      "name": "--ren-link-color"
    },
    {
      "data": {},
      "body": "--ren-link-font-weight",
      "path": "components/primitives/ren-link/ren-link.css",
      "id": "token:--ren-link-font-weight",
      "type": "token",
      "name": "--ren-link-font-weight"
    },
    {
      "data": {},
      "body": "--ren-link-hover-color",
      "path": "components/primitives/ren-link/ren-link.css",
      "id": "token:--ren-link-hover-color",
      "type": "token",
      "name": "--ren-link-hover-color"
    },
    {
      "data": {},
      "body": "--ren-link-underline",
      "path": "components/primitives/ren-link/ren-link.css",
      "id": "token:--ren-link-underline",
      "type": "token",
      "name": "--ren-link-underline"
    },
    {
      "data": {},
      "body": "--ren-menu-bg",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "token:--ren-menu-bg",
      "type": "token",
      "name": "--ren-menu-bg"
    },
    {
      "data": {},
      "body": "--ren-menu-border",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "token:--ren-menu-border",
      "type": "token",
      "name": "--ren-menu-border"
    },
    {
      "data": {},
      "body": "--ren-menu-danger-color",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "token:--ren-menu-danger-color",
      "type": "token",
      "name": "--ren-menu-danger-color"
    },
    {
      "data": {},
      "body": "--ren-menu-duration",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "token:--ren-menu-duration",
      "type": "token",
      "name": "--ren-menu-duration"
    },
    {
      "data": {},
      "body": "--ren-menu-easing",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "token:--ren-menu-easing",
      "type": "token",
      "name": "--ren-menu-easing"
    },
    {
      "data": {},
      "body": "--ren-menu-item-height",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "token:--ren-menu-item-height",
      "type": "token",
      "name": "--ren-menu-item-height"
    },
    {
      "data": {},
      "body": "--ren-menu-item-hover-bg",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "token:--ren-menu-item-hover-bg",
      "type": "token",
      "name": "--ren-menu-item-hover-bg"
    },
    {
      "data": {},
      "body": "--ren-menu-item-padding",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "token:--ren-menu-item-padding",
      "type": "token",
      "name": "--ren-menu-item-padding"
    },
    {
      "data": {},
      "body": "--ren-menu-item-radius",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "token:--ren-menu-item-radius",
      "type": "token",
      "name": "--ren-menu-item-radius"
    },
    {
      "data": {},
      "body": "--ren-menu-padding",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "token:--ren-menu-padding",
      "type": "token",
      "name": "--ren-menu-padding"
    },
    {
      "data": {},
      "body": "--ren-menu-radius",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "token:--ren-menu-radius",
      "type": "token",
      "name": "--ren-menu-radius"
    },
    {
      "data": {},
      "body": "--ren-menu-shadow",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "token:--ren-menu-shadow",
      "type": "token",
      "name": "--ren-menu-shadow"
    },
    {
      "data": {},
      "body": "--ren-menu-width",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "token:--ren-menu-width",
      "type": "token",
      "name": "--ren-menu-width"
    },
    {
      "data": {},
      "body": "--ren-nav-bg",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "token:--ren-nav-bg",
      "type": "token",
      "name": "--ren-nav-bg"
    },
    {
      "data": {},
      "body": "--ren-nav-border",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "token:--ren-nav-border",
      "type": "token",
      "name": "--ren-nav-border"
    },
    {
      "data": {},
      "body": "--ren-nav-gap",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "token:--ren-nav-gap",
      "type": "token",
      "name": "--ren-nav-gap"
    },
    {
      "data": {},
      "body": "--ren-nav-height",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "token:--ren-nav-height",
      "type": "token",
      "name": "--ren-nav-height"
    },
    {
      "data": {},
      "body": "--ren-nav-link-active",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "token:--ren-nav-link-active",
      "type": "token",
      "name": "--ren-nav-link-active"
    },
    {
      "data": {},
      "body": "--ren-nav-link-color",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "token:--ren-nav-link-color",
      "type": "token",
      "name": "--ren-nav-link-color"
    },
    {
      "data": {},
      "body": "--ren-nav-padding-x",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "token:--ren-nav-padding-x",
      "type": "token",
      "name": "--ren-nav-padding-x"
    },
    {
      "data": {},
      "body": "--ren-pagination-active-bg",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "token:--ren-pagination-active-bg",
      "type": "token",
      "name": "--ren-pagination-active-bg"
    },
    {
      "data": {},
      "body": "--ren-pagination-active-color",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "token:--ren-pagination-active-color",
      "type": "token",
      "name": "--ren-pagination-active-color"
    },
    {
      "data": {},
      "body": "--ren-pagination-bg",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "token:--ren-pagination-bg",
      "type": "token",
      "name": "--ren-pagination-bg"
    },
    {
      "data": {},
      "body": "--ren-pagination-font-size",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "token:--ren-pagination-font-size",
      "type": "token",
      "name": "--ren-pagination-font-size"
    },
    {
      "data": {},
      "body": "--ren-pagination-gap",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "token:--ren-pagination-gap",
      "type": "token",
      "name": "--ren-pagination-gap"
    },
    {
      "data": {},
      "body": "--ren-pagination-hover-bg",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "token:--ren-pagination-hover-bg",
      "type": "token",
      "name": "--ren-pagination-hover-bg"
    },
    {
      "data": {},
      "body": "--ren-pagination-radius",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "token:--ren-pagination-radius",
      "type": "token",
      "name": "--ren-pagination-radius"
    },
    {
      "data": {},
      "body": "--ren-pagination-size",
      "path": "components/primitives/ren-pagination/ren-pagination.css",
      "id": "token:--ren-pagination-size",
      "type": "token",
      "name": "--ren-pagination-size"
    },
    {
      "data": {},
      "body": "--ren-popover-bg",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "token:--ren-popover-bg",
      "type": "token",
      "name": "--ren-popover-bg"
    },
    {
      "data": {},
      "body": "--ren-popover-border-color",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "token:--ren-popover-border-color",
      "type": "token",
      "name": "--ren-popover-border-color"
    },
    {
      "data": {},
      "body": "--ren-popover-duration",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "token:--ren-popover-duration",
      "type": "token",
      "name": "--ren-popover-duration"
    },
    {
      "data": {},
      "body": "--ren-popover-easing",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "token:--ren-popover-easing",
      "type": "token",
      "name": "--ren-popover-easing"
    },
    {
      "data": {},
      "body": "--ren-popover-padding",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "token:--ren-popover-padding",
      "type": "token",
      "name": "--ren-popover-padding"
    },
    {
      "data": {},
      "body": "--ren-popover-radius",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "token:--ren-popover-radius",
      "type": "token",
      "name": "--ren-popover-radius"
    },
    {
      "data": {},
      "body": "--ren-popover-shadow",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "token:--ren-popover-shadow",
      "type": "token",
      "name": "--ren-popover-shadow"
    },
    {
      "data": {},
      "body": "--ren-popover-width",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "token:--ren-popover-width",
      "type": "token",
      "name": "--ren-popover-width"
    },
    {
      "data": {},
      "body": "--ren-progress-bg",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "token:--ren-progress-bg",
      "type": "token",
      "name": "--ren-progress-bg"
    },
    {
      "data": {},
      "body": "--ren-progress-duration",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "token:--ren-progress-duration",
      "type": "token",
      "name": "--ren-progress-duration"
    },
    {
      "data": {},
      "body": "--ren-progress-easing",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "token:--ren-progress-easing",
      "type": "token",
      "name": "--ren-progress-easing"
    },
    {
      "data": {},
      "body": "--ren-progress-fill",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "token:--ren-progress-fill",
      "type": "token",
      "name": "--ren-progress-fill"
    },
    {
      "data": {},
      "body": "--ren-progress-height",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "token:--ren-progress-height",
      "type": "token",
      "name": "--ren-progress-height"
    },
    {
      "data": {},
      "body": "--ren-progress-radius",
      "path": "components/primitives/ren-progress/ren-progress.css",
      "id": "token:--ren-progress-radius",
      "type": "token",
      "name": "--ren-progress-radius"
    },
    {
      "data": {},
      "body": "--ren-radius-xl",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--ren-radius-xl",
      "type": "token",
      "name": "--ren-radius-xl"
    },
    {
      "data": {},
      "body": "--ren-separator-color",
      "path": "components/primitives/ren-separator/ren-separator.css",
      "id": "token:--ren-separator-color",
      "type": "token",
      "name": "--ren-separator-color"
    },
    {
      "data": {},
      "body": "--ren-separator-margin",
      "path": "components/primitives/ren-separator/ren-separator.css",
      "id": "token:--ren-separator-margin",
      "type": "token",
      "name": "--ren-separator-margin"
    },
    {
      "data": {},
      "body": "--ren-separator-width",
      "path": "components/primitives/ren-separator/ren-separator.css",
      "id": "token:--ren-separator-width",
      "type": "token",
      "name": "--ren-separator-width"
    },
    {
      "data": {},
      "body": "--ren-sheet-backdrop",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "token:--ren-sheet-backdrop",
      "type": "token",
      "name": "--ren-sheet-backdrop"
    },
    {
      "data": {},
      "body": "--ren-sheet-bg",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "token:--ren-sheet-bg",
      "type": "token",
      "name": "--ren-sheet-bg"
    },
    {
      "data": {},
      "body": "--ren-sheet-border",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "token:--ren-sheet-border",
      "type": "token",
      "name": "--ren-sheet-border"
    },
    {
      "data": {},
      "body": "--ren-sheet-duration",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "token:--ren-sheet-duration",
      "type": "token",
      "name": "--ren-sheet-duration"
    },
    {
      "data": {},
      "body": "--ren-sheet-easing",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "token:--ren-sheet-easing",
      "type": "token",
      "name": "--ren-sheet-easing"
    },
    {
      "data": {},
      "body": "--ren-sheet-padding",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "token:--ren-sheet-padding",
      "type": "token",
      "name": "--ren-sheet-padding"
    },
    {
      "data": {},
      "body": "--ren-sheet-radius",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "token:--ren-sheet-radius",
      "type": "token",
      "name": "--ren-sheet-radius"
    },
    {
      "data": {},
      "body": "--ren-sheet-shadow",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "token:--ren-sheet-shadow",
      "type": "token",
      "name": "--ren-sheet-shadow"
    },
    {
      "data": {},
      "body": "--ren-sheet-width",
      "path": "components/composites/ren-sheet/ren-sheet.css",
      "id": "token:--ren-sheet-width",
      "type": "token",
      "name": "--ren-sheet-width"
    },
    {
      "data": {},
      "body": "--ren-sidebar-active-bg",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-sidebar-active-bg",
      "type": "token",
      "name": "--ren-sidebar-active-bg"
    },
    {
      "data": {},
      "body": "--ren-sidebar-active-color",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-sidebar-active-color",
      "type": "token",
      "name": "--ren-sidebar-active-color"
    },
    {
      "data": {},
      "body": "--ren-sidebar-bg",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-sidebar-bg",
      "type": "token",
      "name": "--ren-sidebar-bg"
    },
    {
      "data": {},
      "body": "--ren-sidebar-border",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-sidebar-border",
      "type": "token",
      "name": "--ren-sidebar-border"
    },
    {
      "data": {},
      "body": "--ren-sidebar-collapsed-width",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-sidebar-collapsed-width",
      "type": "token",
      "name": "--ren-sidebar-collapsed-width"
    },
    {
      "data": {},
      "body": "--ren-sidebar-duration",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-sidebar-duration",
      "type": "token",
      "name": "--ren-sidebar-duration"
    },
    {
      "data": {},
      "body": "--ren-sidebar-easing",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-sidebar-easing",
      "type": "token",
      "name": "--ren-sidebar-easing"
    },
    {
      "data": {},
      "body": "--ren-sidebar-item-height",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-sidebar-item-height",
      "type": "token",
      "name": "--ren-sidebar-item-height"
    },
    {
      "data": {},
      "body": "--ren-sidebar-item-padding",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-sidebar-item-padding",
      "type": "token",
      "name": "--ren-sidebar-item-padding"
    },
    {
      "data": {},
      "body": "--ren-sidebar-item-radius",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-sidebar-item-radius",
      "type": "token",
      "name": "--ren-sidebar-item-radius"
    },
    {
      "data": {},
      "body": "--ren-sidebar-width",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-sidebar-width",
      "type": "token",
      "name": "--ren-sidebar-width"
    },
    {
      "data": {},
      "body": "--ren-skeleton-bg",
      "path": "components/primitives/ren-skeleton/ren-skeleton.css",
      "id": "token:--ren-skeleton-bg",
      "type": "token",
      "name": "--ren-skeleton-bg"
    },
    {
      "data": {},
      "body": "--ren-skeleton-radius",
      "path": "components/primitives/ren-skeleton/ren-skeleton.css",
      "id": "token:--ren-skeleton-radius",
      "type": "token",
      "name": "--ren-skeleton-radius"
    },
    {
      "data": {},
      "body": "--ren-skeleton-shine",
      "path": "components/primitives/ren-skeleton/ren-skeleton.css",
      "id": "token:--ren-skeleton-shine",
      "type": "token",
      "name": "--ren-skeleton-shine"
    },
    {
      "data": {},
      "body": "--ren-skeleton-speed",
      "path": "components/primitives/ren-skeleton/ren-skeleton.css",
      "id": "token:--ren-skeleton-speed",
      "type": "token",
      "name": "--ren-skeleton-speed"
    },
    {
      "data": {},
      "body": "--ren-slider-duration",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "token:--ren-slider-duration",
      "type": "token",
      "name": "--ren-slider-duration"
    },
    {
      "data": {},
      "body": "--ren-slider-easing",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "token:--ren-slider-easing",
      "type": "token",
      "name": "--ren-slider-easing"
    },
    {
      "data": {},
      "body": "--ren-slider-fill-color",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "token:--ren-slider-fill-color",
      "type": "token",
      "name": "--ren-slider-fill-color"
    },
    {
      "data": {},
      "body": "--ren-slider-thumb-bg",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "token:--ren-slider-thumb-bg",
      "type": "token",
      "name": "--ren-slider-thumb-bg"
    },
    {
      "data": {},
      "body": "--ren-slider-thumb-border",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "token:--ren-slider-thumb-border",
      "type": "token",
      "name": "--ren-slider-thumb-border"
    },
    {
      "data": {},
      "body": "--ren-slider-thumb-shadow",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "token:--ren-slider-thumb-shadow",
      "type": "token",
      "name": "--ren-slider-thumb-shadow"
    },
    {
      "data": {},
      "body": "--ren-slider-thumb-size",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "token:--ren-slider-thumb-size",
      "type": "token",
      "name": "--ren-slider-thumb-size"
    },
    {
      "data": {},
      "body": "--ren-slider-track-bg",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "token:--ren-slider-track-bg",
      "type": "token",
      "name": "--ren-slider-track-bg"
    },
    {
      "data": {},
      "body": "--ren-slider-track-height",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "token:--ren-slider-track-height",
      "type": "token",
      "name": "--ren-slider-track-height"
    },
    {
      "data": {},
      "body": "--ren-slider-track-radius",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "token:--ren-slider-track-radius",
      "type": "token",
      "name": "--ren-slider-track-radius"
    },
    {
      "data": {},
      "body": "--ren-space-1",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-space-1",
      "type": "token",
      "name": "--ren-space-1"
    },
    {
      "data": {},
      "body": "--ren-space-2",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-space-2",
      "type": "token",
      "name": "--ren-space-2"
    },
    {
      "data": {},
      "body": "--ren-space-3",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-space-3",
      "type": "token",
      "name": "--ren-space-3"
    },
    {
      "data": {},
      "body": "--ren-space-4",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--ren-space-4",
      "type": "token",
      "name": "--ren-space-4"
    },
    {
      "data": {},
      "body": "--ren-space-8",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--ren-space-8",
      "type": "token",
      "name": "--ren-space-8"
    },
    {
      "data": {},
      "body": "--ren-spinner-color",
      "path": "components/primitives/ren-spinner/ren-spinner.css",
      "id": "token:--ren-spinner-color",
      "type": "token",
      "name": "--ren-spinner-color"
    },
    {
      "data": {},
      "body": "--ren-spinner-size",
      "path": "components/primitives/ren-spinner/ren-spinner.css",
      "id": "token:--ren-spinner-size",
      "type": "token",
      "name": "--ren-spinner-size"
    },
    {
      "data": {},
      "body": "--ren-spinner-speed",
      "path": "components/primitives/ren-spinner/ren-spinner.css",
      "id": "token:--ren-spinner-speed",
      "type": "token",
      "name": "--ren-spinner-speed"
    },
    {
      "data": {},
      "body": "--ren-spinner-width",
      "path": "components/primitives/ren-spinner/ren-spinner.css",
      "id": "token:--ren-spinner-width",
      "type": "token",
      "name": "--ren-spinner-width"
    },
    {
      "data": {},
      "body": "--ren-switch-bg",
      "path": "components/primitives/ren-switch/ren-switch.css",
      "id": "token:--ren-switch-bg",
      "type": "token",
      "name": "--ren-switch-bg"
    },
    {
      "data": {},
      "body": "--ren-switch-checked-bg",
      "path": "components/primitives/ren-switch/ren-switch.css",
      "id": "token:--ren-switch-checked-bg",
      "type": "token",
      "name": "--ren-switch-checked-bg"
    },
    {
      "data": {},
      "body": "--ren-switch-duration",
      "path": "components/primitives/ren-switch/ren-switch.css",
      "id": "token:--ren-switch-duration",
      "type": "token",
      "name": "--ren-switch-duration"
    },
    {
      "data": {},
      "body": "--ren-switch-easing",
      "path": "components/primitives/ren-switch/ren-switch.css",
      "id": "token:--ren-switch-easing",
      "type": "token",
      "name": "--ren-switch-easing"
    },
    {
      "data": {},
      "body": "--ren-switch-height",
      "path": "components/primitives/ren-switch/ren-switch.css",
      "id": "token:--ren-switch-height",
      "type": "token",
      "name": "--ren-switch-height"
    },
    {
      "data": {},
      "body": "--ren-switch-thumb-color",
      "path": "components/primitives/ren-switch/ren-switch.css",
      "id": "token:--ren-switch-thumb-color",
      "type": "token",
      "name": "--ren-switch-thumb-color"
    },
    {
      "data": {},
      "body": "--ren-switch-thumb-size",
      "path": "components/primitives/ren-switch/ren-switch.css",
      "id": "token:--ren-switch-thumb-size",
      "type": "token",
      "name": "--ren-switch-thumb-size"
    },
    {
      "data": {},
      "body": "--ren-switch-width",
      "path": "components/primitives/ren-switch/ren-switch.css",
      "id": "token:--ren-switch-width",
      "type": "token",
      "name": "--ren-switch-width"
    },
    {
      "data": {},
      "body": "--ren-table-border-color",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--ren-table-border-color",
      "type": "token",
      "name": "--ren-table-border-color"
    },
    {
      "data": {},
      "body": "--ren-table-cell-padding",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--ren-table-cell-padding",
      "type": "token",
      "name": "--ren-table-cell-padding"
    },
    {
      "data": {},
      "body": "--ren-table-header-bg",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--ren-table-header-bg",
      "type": "token",
      "name": "--ren-table-header-bg"
    },
    {
      "data": {},
      "body": "--ren-table-header-color",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--ren-table-header-color",
      "type": "token",
      "name": "--ren-table-header-color"
    },
    {
      "data": {},
      "body": "--ren-table-header-size",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--ren-table-header-size",
      "type": "token",
      "name": "--ren-table-header-size"
    },
    {
      "data": {},
      "body": "--ren-table-header-weight",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--ren-table-header-weight",
      "type": "token",
      "name": "--ren-table-header-weight"
    },
    {
      "data": {},
      "body": "--ren-table-hover-bg",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--ren-table-hover-bg",
      "type": "token",
      "name": "--ren-table-hover-bg"
    },
    {
      "data": {},
      "body": "--ren-table-row-height",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--ren-table-row-height",
      "type": "token",
      "name": "--ren-table-row-height"
    },
    {
      "data": {},
      "body": "--ren-table-selected-bg",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--ren-table-selected-bg",
      "type": "token",
      "name": "--ren-table-selected-bg"
    },
    {
      "data": {},
      "body": "--ren-table-stripe-bg",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--ren-table-stripe-bg",
      "type": "token",
      "name": "--ren-table-stripe-bg"
    },
    {
      "data": {},
      "body": "--ren-tabs-active-color",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "token:--ren-tabs-active-color",
      "type": "token",
      "name": "--ren-tabs-active-color"
    },
    {
      "data": {},
      "body": "--ren-tabs-border-color",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "token:--ren-tabs-border-color",
      "type": "token",
      "name": "--ren-tabs-border-color"
    },
    {
      "data": {},
      "body": "--ren-tabs-color",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "token:--ren-tabs-color",
      "type": "token",
      "name": "--ren-tabs-color"
    },
    {
      "data": {},
      "body": "--ren-tabs-duration",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "token:--ren-tabs-duration",
      "type": "token",
      "name": "--ren-tabs-duration"
    },
    {
      "data": {},
      "body": "--ren-tabs-easing",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "token:--ren-tabs-easing",
      "type": "token",
      "name": "--ren-tabs-easing"
    },
    {
      "data": {},
      "body": "--ren-tabs-font-size",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "token:--ren-tabs-font-size",
      "type": "token",
      "name": "--ren-tabs-font-size"
    },
    {
      "data": {},
      "body": "--ren-tabs-font-weight",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "token:--ren-tabs-font-weight",
      "type": "token",
      "name": "--ren-tabs-font-weight"
    },
    {
      "data": {},
      "body": "--ren-tabs-gap",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "token:--ren-tabs-gap",
      "type": "token",
      "name": "--ren-tabs-gap"
    },
    {
      "data": {},
      "body": "--ren-tabs-height",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "token:--ren-tabs-height",
      "type": "token",
      "name": "--ren-tabs-height"
    },
    {
      "data": {},
      "body": "--ren-tabs-indicator-color",
      "path": "components/composites/ren-tabs/ren-tabs.css",
      "id": "token:--ren-tabs-indicator-color",
      "type": "token",
      "name": "--ren-tabs-indicator-color"
    },
    {
      "data": {},
      "body": "--ren-tag-bg",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "token:--ren-tag-bg",
      "type": "token",
      "name": "--ren-tag-bg"
    },
    {
      "data": {},
      "body": "--ren-tag-border-color",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "token:--ren-tag-border-color",
      "type": "token",
      "name": "--ren-tag-border-color"
    },
    {
      "data": {},
      "body": "--ren-tag-color",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "token:--ren-tag-color",
      "type": "token",
      "name": "--ren-tag-color"
    },
    {
      "data": {},
      "body": "--ren-tag-font-size",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "token:--ren-tag-font-size",
      "type": "token",
      "name": "--ren-tag-font-size"
    },
    {
      "data": {},
      "body": "--ren-tag-font-weight",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "token:--ren-tag-font-weight",
      "type": "token",
      "name": "--ren-tag-font-weight"
    },
    {
      "data": {},
      "body": "--ren-tag-gap",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "token:--ren-tag-gap",
      "type": "token",
      "name": "--ren-tag-gap"
    },
    {
      "data": {},
      "body": "--ren-tag-height",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "token:--ren-tag-height",
      "type": "token",
      "name": "--ren-tag-height"
    },
    {
      "data": {},
      "body": "--ren-tag-padding-x",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "token:--ren-tag-padding-x",
      "type": "token",
      "name": "--ren-tag-padding-x"
    },
    {
      "data": {},
      "body": "--ren-tag-radius",
      "path": "components/primitives/ren-tag/ren-tag.css",
      "id": "token:--ren-tag-radius",
      "type": "token",
      "name": "--ren-tag-radius"
    },
    {
      "data": {},
      "body": "--ren-title-sm-size",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "token:--ren-title-sm-size",
      "type": "token",
      "name": "--ren-title-sm-size"
    },
    {
      "data": {},
      "body": "--ren-toast-anim-duration",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--ren-toast-anim-duration",
      "type": "token",
      "name": "--ren-toast-anim-duration"
    },
    {
      "data": {},
      "body": "--ren-toast-bg",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--ren-toast-bg",
      "type": "token",
      "name": "--ren-toast-bg"
    },
    {
      "data": {},
      "body": "--ren-toast-border",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--ren-toast-border",
      "type": "token",
      "name": "--ren-toast-border"
    },
    {
      "data": {},
      "body": "--ren-toast-easing",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--ren-toast-easing",
      "type": "token",
      "name": "--ren-toast-easing"
    },
    {
      "data": {},
      "body": "--ren-toast-gap",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--ren-toast-gap",
      "type": "token",
      "name": "--ren-toast-gap"
    },
    {
      "data": {},
      "body": "--ren-toast-padding",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--ren-toast-padding",
      "type": "token",
      "name": "--ren-toast-padding"
    },
    {
      "data": {},
      "body": "--ren-toast-radius",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--ren-toast-radius",
      "type": "token",
      "name": "--ren-toast-radius"
    },
    {
      "data": {},
      "body": "--ren-toast-shadow",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--ren-toast-shadow",
      "type": "token",
      "name": "--ren-toast-shadow"
    },
    {
      "data": {},
      "body": "--ren-toast-width",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--ren-toast-width",
      "type": "token",
      "name": "--ren-toast-width"
    },
    {
      "data": {},
      "body": "--ren-toggle-active-bg",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "token:--ren-toggle-active-bg",
      "type": "token",
      "name": "--ren-toggle-active-bg"
    },
    {
      "data": {},
      "body": "--ren-toggle-active-color",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "token:--ren-toggle-active-color",
      "type": "token",
      "name": "--ren-toggle-active-color"
    },
    {
      "data": {},
      "body": "--ren-toggle-bg",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "token:--ren-toggle-bg",
      "type": "token",
      "name": "--ren-toggle-bg"
    },
    {
      "data": {},
      "body": "--ren-toggle-color",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "token:--ren-toggle-color",
      "type": "token",
      "name": "--ren-toggle-color"
    },
    {
      "data": {},
      "body": "--ren-toggle-font-size",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "token:--ren-toggle-font-size",
      "type": "token",
      "name": "--ren-toggle-font-size"
    },
    {
      "data": {},
      "body": "--ren-toggle-font-weight",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "token:--ren-toggle-font-weight",
      "type": "token",
      "name": "--ren-toggle-font-weight"
    },
    {
      "data": {},
      "body": "--ren-toggle-height",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "token:--ren-toggle-height",
      "type": "token",
      "name": "--ren-toggle-height"
    },
    {
      "data": {},
      "body": "--ren-toggle-padding-x",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "token:--ren-toggle-padding-x",
      "type": "token",
      "name": "--ren-toggle-padding-x"
    },
    {
      "data": {},
      "body": "--ren-toggle-radius",
      "path": "components/composites/ren-toggle-group/ren-toggle-group.css",
      "id": "token:--ren-toggle-radius",
      "type": "token",
      "name": "--ren-toggle-radius"
    },
    {
      "data": {},
      "body": "--ren-toolbar-active-bg",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "token:--ren-toolbar-active-bg",
      "type": "token",
      "name": "--ren-toolbar-active-bg"
    },
    {
      "data": {},
      "body": "--ren-toolbar-bg",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "token:--ren-toolbar-bg",
      "type": "token",
      "name": "--ren-toolbar-bg"
    },
    {
      "data": {},
      "body": "--ren-toolbar-border",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "token:--ren-toolbar-border",
      "type": "token",
      "name": "--ren-toolbar-border"
    },
    {
      "data": {},
      "body": "--ren-toolbar-height",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "token:--ren-toolbar-height",
      "type": "token",
      "name": "--ren-toolbar-height"
    },
    {
      "data": {},
      "body": "--ren-toolbar-item-hover",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "token:--ren-toolbar-item-hover",
      "type": "token",
      "name": "--ren-toolbar-item-hover"
    },
    {
      "data": {},
      "body": "--ren-toolbar-item-radius",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "token:--ren-toolbar-item-radius",
      "type": "token",
      "name": "--ren-toolbar-item-radius"
    },
    {
      "data": {},
      "body": "--ren-toolbar-item-size",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "token:--ren-toolbar-item-size",
      "type": "token",
      "name": "--ren-toolbar-item-size"
    },
    {
      "data": {},
      "body": "--ren-toolbar-padding",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "token:--ren-toolbar-padding",
      "type": "token",
      "name": "--ren-toolbar-padding"
    },
    {
      "data": {},
      "body": "--ren-toolbar-radius",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "token:--ren-toolbar-radius",
      "type": "token",
      "name": "--ren-toolbar-radius"
    },
    {
      "data": {},
      "body": "--ren-tooltip-bg",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "token:--ren-tooltip-bg",
      "type": "token",
      "name": "--ren-tooltip-bg"
    },
    {
      "data": {},
      "body": "--ren-tooltip-color",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "token:--ren-tooltip-color",
      "type": "token",
      "name": "--ren-tooltip-color"
    },
    {
      "data": {},
      "body": "--ren-tooltip-duration",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "token:--ren-tooltip-duration",
      "type": "token",
      "name": "--ren-tooltip-duration"
    },
    {
      "data": {},
      "body": "--ren-tooltip-easing",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "token:--ren-tooltip-easing",
      "type": "token",
      "name": "--ren-tooltip-easing"
    },
    {
      "data": {},
      "body": "--ren-tooltip-font-size",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "token:--ren-tooltip-font-size",
      "type": "token",
      "name": "--ren-tooltip-font-size"
    },
    {
      "data": {},
      "body": "--ren-tooltip-padding-x",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "token:--ren-tooltip-padding-x",
      "type": "token",
      "name": "--ren-tooltip-padding-x"
    },
    {
      "data": {},
      "body": "--ren-tooltip-padding-y",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "token:--ren-tooltip-padding-y",
      "type": "token",
      "name": "--ren-tooltip-padding-y"
    },
    {
      "data": {},
      "body": "--ren-tooltip-radius",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "token:--ren-tooltip-radius",
      "type": "token",
      "name": "--ren-tooltip-radius"
    },
    {
      "data": {},
      "body": "--ren-tooltip-shadow",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "token:--ren-tooltip-shadow",
      "type": "token",
      "name": "--ren-tooltip-shadow"
    },
    {
      "data": {},
      "body": "--ren-z-modal",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--ren-z-modal",
      "type": "token",
      "name": "--ren-z-modal"
    },
    {
      "data": {},
      "body": "--ren-z-sticky",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "token:--ren-z-sticky",
      "type": "token",
      "name": "--ren-z-sticky"
    },
    {
      "data": {},
      "body": "--ring-offset-width",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "token:--ring-offset-width",
      "type": "token",
      "name": "--ring-offset-width"
    },
    {
      "data": {},
      "body": "--ring-width",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "token:--ring-width",
      "type": "token",
      "name": "--ring-width"
    },
    {
      "data": {},
      "body": "--scroll-max",
      "path": "components/composites/ren-scroll-area/ren-scroll-area.css",
      "id": "token:--scroll-max",
      "type": "token",
      "name": "--scroll-max"
    },
    {
      "data": {},
      "body": "--shadow-lg",
      "path": "components/patterns/ren-menubar/ren-menubar.css",
      "id": "token:--shadow-lg",
      "type": "token",
      "name": "--shadow-lg"
    },
    {
      "data": {},
      "body": "--shadow-md",
      "path": "components/composites/ren-calendar/ren-calendar.css",
      "id": "token:--shadow-md",
      "type": "token",
      "name": "--shadow-md"
    },
    {
      "data": {},
      "body": "--shadow-sm",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "token:--shadow-sm",
      "type": "token",
      "name": "--shadow-sm"
    },
    {
      "data": {},
      "body": "--shadow-xl",
      "path": "components/patterns/ren-command/ren-command.css",
      "id": "token:--shadow-xl",
      "type": "token",
      "name": "--shadow-xl"
    },
    {
      "data": {},
      "body": "--shadow-xs",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "token:--shadow-xs",
      "type": "token",
      "name": "--shadow-xs"
    },
    {
      "data": {},
      "body": "--sidebar-collapsed-width",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--sidebar-collapsed-width",
      "type": "token",
      "name": "--sidebar-collapsed-width"
    },
    {
      "data": {},
      "body": "--sidebar-width",
      "path": "components/patterns/ren-sidebar/ren-sidebar.css",
      "id": "token:--sidebar-width",
      "type": "token",
      "name": "--sidebar-width"
    },
    {
      "data": {},
      "body": "--size-body",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "token:--size-body",
      "type": "token",
      "name": "--size-body"
    },
    {
      "data": {},
      "body": "--size-body-sm",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "token:--size-body-sm",
      "type": "token",
      "name": "--size-body-sm"
    },
    {
      "data": {},
      "body": "--size-caption",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "token:--size-caption",
      "type": "token",
      "name": "--size-caption"
    },
    {
      "data": {},
      "body": "--size-icon-md",
      "path": "components/composites/ren-menu/ren-menu.css",
      "id": "token:--size-icon-md",
      "type": "token",
      "name": "--size-icon-md"
    },
    {
      "data": {},
      "body": "--size-lg",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--size-lg",
      "type": "token",
      "name": "--size-lg"
    },
    {
      "data": {},
      "body": "--size-md",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "token:--size-md",
      "type": "token",
      "name": "--size-md"
    },
    {
      "data": {},
      "body": "--size-sm",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "token:--size-sm",
      "type": "token",
      "name": "--size-sm"
    },
    {
      "data": {},
      "body": "--size-xl",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "token:--size-xl",
      "type": "token",
      "name": "--size-xl"
    },
    {
      "data": {},
      "body": "--space-0-25",
      "path": "components/composites/ren-hover-card/ren-hover-card.css",
      "id": "token:--space-0-25",
      "type": "token",
      "name": "--space-0-25"
    },
    {
      "data": {},
      "body": "--space-0-5",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "token:--space-0-5",
      "type": "token",
      "name": "--space-0-5"
    },
    {
      "data": {},
      "body": "--space-1",
      "path": "components/patterns/ren-menubar/ren-menubar.css",
      "id": "token:--space-1",
      "type": "token",
      "name": "--space-1"
    },
    {
      "data": {},
      "body": "--space-1-5",
      "path": "components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "id": "token:--space-1-5",
      "type": "token",
      "name": "--space-1-5"
    },
    {
      "data": {},
      "body": "--space-10",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--space-10",
      "type": "token",
      "name": "--space-10"
    },
    {
      "data": {},
      "body": "--space-2",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--space-2",
      "type": "token",
      "name": "--space-2"
    },
    {
      "data": {},
      "body": "--space-3",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--space-3",
      "type": "token",
      "name": "--space-3"
    },
    {
      "data": {},
      "body": "--space-4",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--space-4",
      "type": "token",
      "name": "--space-4"
    },
    {
      "data": {},
      "body": "--space-5",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--space-5",
      "type": "token",
      "name": "--space-5"
    },
    {
      "data": {},
      "body": "--space-6",
      "path": "components/patterns/ren-nav/ren-nav.css",
      "id": "token:--space-6",
      "type": "token",
      "name": "--space-6"
    },
    {
      "data": {},
      "body": "--space-8",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "token:--space-8",
      "type": "token",
      "name": "--space-8"
    },
    {
      "data": {},
      "body": "--stroke-1",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--stroke-1",
      "type": "token",
      "name": "--stroke-1"
    },
    {
      "data": {},
      "body": "--text-3xl",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "token:--text-3xl",
      "type": "token",
      "name": "--text-3xl"
    },
    {
      "data": {},
      "body": "--text-base",
      "path": "components/composites/ren-dropzone/ren-dropzone.css",
      "id": "token:--text-base",
      "type": "token",
      "name": "--text-base"
    },
    {
      "data": {},
      "body": "--text-lg",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "token:--text-lg",
      "type": "token",
      "name": "--text-lg"
    },
    {
      "data": {},
      "body": "--text-sm",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "token:--text-sm",
      "type": "token",
      "name": "--text-sm"
    },
    {
      "data": {},
      "body": "--text-xl",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "token:--text-xl",
      "type": "token",
      "name": "--text-xl"
    },
    {
      "data": {},
      "body": "--text-xs",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--text-xs",
      "type": "token",
      "name": "--text-xs"
    },
    {
      "data": {},
      "body": "--title-sm-leading",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--title-sm-leading",
      "type": "token",
      "name": "--title-sm-leading"
    },
    {
      "data": {},
      "body": "--title-sm-size",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--title-sm-size",
      "type": "token",
      "name": "--title-sm-size"
    },
    {
      "data": {},
      "body": "--title-sm-weight",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--title-sm-weight",
      "type": "token",
      "name": "--title-sm-weight"
    },
    {
      "data": {},
      "body": "--touch-min",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--touch-min",
      "type": "token",
      "name": "--touch-min"
    },
    {
      "data": {},
      "body": "--tracking-wide",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "token:--tracking-wide",
      "type": "token",
      "name": "--tracking-wide"
    },
    {
      "data": {},
      "body": "--transition-overlay",
      "path": "components/composites/ren-dialog/ren-dialog.css",
      "id": "token:--transition-overlay",
      "type": "token",
      "name": "--transition-overlay"
    },
    {
      "data": {},
      "body": "--transition-tactile",
      "path": "components/composites/ren-toolbar/ren-toolbar.css",
      "id": "token:--transition-tactile",
      "type": "token",
      "name": "--transition-tactile"
    },
    {
      "data": {},
      "body": "--value",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "token:--value",
      "type": "token",
      "name": "--value"
    },
    {
      "data": {},
      "body": "--value-end",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "token:--value-end",
      "type": "token",
      "name": "--value-end"
    },
    {
      "data": {},
      "body": "--value-start",
      "path": "components/composites/ren-slider/ren-slider.css",
      "id": "token:--value-start",
      "type": "token",
      "name": "--value-start"
    },
    {
      "data": {},
      "body": "--weight-bold",
      "path": "components/patterns/ren-form/ren-form.css",
      "id": "token:--weight-bold",
      "type": "token",
      "name": "--weight-bold"
    },
    {
      "data": {},
      "body": "--weight-medium",
      "path": "components/patterns/ren-ai/ren-ai.css",
      "id": "token:--weight-medium",
      "type": "token",
      "name": "--weight-medium"
    },
    {
      "data": {},
      "body": "--weight-regular",
      "path": "components/primitives/ren-switch/ren-switch.css",
      "id": "token:--weight-regular",
      "type": "token",
      "name": "--weight-regular"
    },
    {
      "data": {},
      "body": "--weight-semibold",
      "path": "components/patterns/ren-table/ren-table.css",
      "id": "token:--weight-semibold",
      "type": "token",
      "name": "--weight-semibold"
    },
    {
      "data": {},
      "body": "--white",
      "path": "components/composites/ren-color-picker/ren-color-picker.css",
      "id": "token:--white",
      "type": "token",
      "name": "--white"
    },
    {
      "data": {},
      "body": "--width-prose",
      "path": "components/patterns/ren-empty-state/ren-empty-state.css",
      "id": "token:--width-prose",
      "type": "token",
      "name": "--width-prose"
    },
    {
      "data": {},
      "body": "--z-dropdown",
      "path": "components/composites/ren-select/ren-select.css",
      "id": "token:--z-dropdown",
      "type": "token",
      "name": "--z-dropdown"
    },
    {
      "data": {},
      "body": "--z-popover",
      "path": "components/composites/ren-popover/ren-popover.css",
      "id": "token:--z-popover",
      "type": "token",
      "name": "--z-popover"
    },
    {
      "data": {},
      "body": "--z-toast",
      "path": "components/composites/ren-toast/ren-toast.css",
      "id": "token:--z-toast",
      "type": "token",
      "name": "--z-toast"
    },
    {
      "data": {},
      "body": "--z-tooltip",
      "path": "components/composites/ren-tooltip/ren-tooltip.css",
      "id": "token:--z-tooltip",
      "type": "token",
      "name": "--z-tooltip"
    },
    {
      "data": {},
      "body": "# RenDS Knowledge Graph\n\nLocal, generated graph for querying RenDS contracts, component source, docs,\nexamples, selectors, and tokens.\n\nThis is both project tooling and a packaged aid for agents that install\n`ren10` from npm.\n\n## Why This Shape\n\n- Keep the source of truth in the existing files: contracts, CSS, JS, docs, and\n  examples.\n- Generate the graph deterministically instead of hand-maintaining a second\n  spec.\n- Ship a prebuilt SQLite database plus FTS5 for search/querying when the\n  `sqlite3` CLI is available.\n- Ship a JSON fallback for agents that can read files but cannot open SQLite.\n- Avoid `node:sqlite` for now because CI runs Node 20 and Node 22 still marks\n  the built-in SQLite API as experimental.\n- Avoid `better-sqlite3` for the first cut because it adds a native dependency.\n\n## Commands\n\n```bash\nnpm run knowledge:build\nnpm run knowledge:check\nnpm run knowledge:query -- \"ren-toast status\"\nnpm run knowledge:query -- \"ren-toast status\" --json\nnpm run agent:manifest\nnpm run agent:doctor\nnpm run agent:smoke\nnpm run agent:skill:check\nnpm run agent:skill:pack\nnpm run agent:check\nnpx ren10 manifest --json\nnpx ren10 search \"dialog workflow\" --json\nnpx ren10 build \"dashboard with sidebar\" --json\nnpx ren10 component button --dense\nnpx ren10 docs layouts --dense\nnpx ren10 doctor\nnpx ren10 knowledge\nnpx ren10 knowledge query \"ren-toast status\"\nnpx ren10 knowledge query \"ren-toast status\" --json\nnpx ren10 knowledge check\n```\n\nGenerated and packaged files live in `knowledge/`:\n\n- `knowledge/ren10-graph.json`\n- `knowledge/ren10-graph.sqlite`\n\nThe generated files are included in the npm package, so an installed consumer\nor agent can inspect `node_modules/ren10/knowledge/`.\n\nAgent-facing JSON commands use a stable envelope:\n\n```json\n{\n  \"apiVersion\": 1,\n  \"type\": \"search\",\n  \"data\": {}\n}\n```\n\nUse `--json` for typed machine output. Use `--dense` when an agent needs a\ncompact Markdown summary that still points back to source contracts.\n\n`knowledge:check` is the package/release gate. It regenerates the graph into a\ntemporary directory, compares the fresh JSON with the committed package JSON,\nvalidates the packaged SQLite database logically, and checks that npm pack\nincludes the graph files.\n\n## Current Schema\n\nNodes:\n\n- `component`\n- `contract`\n- `css`\n- `javascript`\n- `docs_page`\n- `example`\n- `selector`\n- `token`\n- foundation contracts such as `design-contract`, `tokens-contract`, and\n  `component-router`\n\nEdges:\n\n- `has_contract`\n- `has_css`\n- `has_js`\n- `has_docs_page`\n- `exposes_selector`\n- `uses_token`\n- `used_by_example`\n\n## Current Check Layer\n\nThe check currently covers:\n\n- Generated JSON freshness.\n- SQLite integrity, row counts, node IDs, edge rows, and an FTS smoke query.\n- Component coverage: 19 primitives, 26 composites, 8 patterns.\n- Required contract, CSS, and docs edges for every component.\n- Selector/token edge consistency.\n- npm pack inclusion for `knowledge/README.md`, JSON, and SQLite.\n- npm pack inclusion for the agent-ready roadmap, eval README, and versioned\n  RenDS skill.\n- Agent CLI JSON smoke coverage through `npm run agent:smoke`.\n\n## Intended Next Layer\n\nThe next useful step is to make the graph checks more semantic, not only\nstructural:\n\n- Contract/source drift: selector exists in CSS but not in the contract.\n- Token drift: component uses a token not allowed by its contract.\n- Docs drift: docs page references stale class names.\n- Example coverage: public selectors or variants with no runnable example.\n- Agent routing: given a prompt, return the relevant contract chain before\n  generation starts.\n",
      "path": "docs/knowledge-graph.md",
      "id": "tooling:knowledge-graph",
      "type": "tooling-doc",
      "name": "Knowledge graph tooling"
    },
    {
      "data": {},
      "body": "# RenDS Knowledge Graph\n\nPackaged graph for agents and tools that need to understand RenDS from the\ninstalled `ren10` npm package.\n\n## Files\n\n- `ren10-graph.sqlite` — primary SQLite database with FTS5 search.\n- `ren10-graph.json` — fallback graph for agents that can read files but\n  cannot open SQLite.\n\n## CLI\n\n```bash\nnpx ren10 manifest --json\nnpx ren10 build \"dashboard with sidebar\" --json\nnpx ren10 search \"ren-toast status\" --json\nnpx ren10 component toast --dense\nnpx ren10 docs layouts --dense\nnpx ren10 doctor\nnpx ren10 knowledge\nnpx ren10 knowledge query \"ren-toast status\"\nnpx ren10 knowledge query \"ren-toast status\" --json\nnpx ren10 knowledge check\n```\n\nThe query command prefers SQLite when the local `sqlite3` CLI is available and\nfalls back to the packaged JSON graph when it is not.\n\nAll agent-facing JSON responses use this envelope:\n\n```json\n{\n  \"apiVersion\": 1,\n  \"type\": \"knowledge.query\",\n  \"data\": {}\n}\n```\n\nUse `--json` for typed machine output. Use `--source-json` only when you need\nto force the packaged JSON graph instead of SQLite.\n\n## What It Contains\n\nNodes:\n\n- components\n- contracts\n- CSS files\n- JavaScript files\n- docs pages\n- examples\n- selectors\n- tokens\n- foundation contracts\n\nEdges:\n\n- `has_contract`\n- `has_css`\n- `has_js`\n- `has_docs_page`\n- `exposes_selector`\n- `uses_token`\n- `used_by_example`\n\nThis graph is generated from the repository source. Do not edit the generated\nSQLite or JSON files by hand.\n",
      "path": "knowledge/README.md",
      "id": "tooling:knowledge-readme",
      "type": "tooling-doc",
      "name": "Packaged knowledge graph readme"
    }
  ],
  "edges": [
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "selector:.ren-accordion",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "selector:.ren-accordion-bordered",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "selector:.ren-accordion-content",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "selector:.ren-accordion-flush",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "selector:.ren-accordion-item",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "selector:.ren-accordion-trigger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "contract:composite:ren-accordion",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "file:components/composites/ren-accordion/ren-accordion.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "docs:docs/components/ren-accordion.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "file:components/composites/ren-accordion/ren-accordion.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--color-fill-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--duration-exit",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--ease-exit",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--label-lg-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--label-lg-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--size-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-accordion",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "selector:.ren-alert-dialog",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "selector:.ren-alert-dialog-actions",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "selector:.ren-alert-dialog-description",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "selector:.ren-alert-dialog-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "selector:.ren-alert-dialog-icon-danger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "selector:.ren-alert-dialog-icon-warning",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "selector:.ren-alert-dialog-title",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "contract:composite:ren-alert-dialog",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "file:components/composites/ren-alert-dialog/ren-alert-dialog.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "docs:docs/components/ren-alert-dialog.html",
      "type": "has_docs_page"
    },
    {
      "detail": "ren-alert-dialog",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "example:examples/dialog-workflow.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--color-danger-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--color-overlay",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--color-warning",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--color-warning-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--icon-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--leading-normal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--radius-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--shadow-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--space-5",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--space-6",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--text-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--text-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--title-sm-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--transition-overlay",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-alert-dialog",
      "target": "token:--weight-semibold",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar-day",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar-day-in-range",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar-day-range-end",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar-day-range-start",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar-grid",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar-header",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar-nav",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar-next",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar-prev",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar-range",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar-title",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar-weekday",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "selector:.ren-calendar-weekdays",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "contract:composite:ren-calendar",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "file:components/composites/ren-calendar/ren-calendar.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "docs:docs/components/ren-calendar.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "file:components/composites/ren-calendar/ren-calendar.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--color-accent-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--color-fill-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--color-on-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--color-surface-raised",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--color-text-faint",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--label-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--ren-calendar-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--ren-calendar-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--ren-calendar-day-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--ren-calendar-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--ren-calendar-range-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--ren-calendar-selected-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--ren-calendar-selected-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--ren-calendar-today-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--ren-calendar-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--shadow-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--size-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--space-0-5",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--space-6",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-calendar",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-2",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-3",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-4",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-arrow",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-counter",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-counter-current",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-dot",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-dots",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-fade",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-full",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-next",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-no-arrows",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-no-counter",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-no-dots",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-prev",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-slide",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "selector:.ren-carousel-viewport",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "contract:composite:ren-carousel",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "file:components/composites/ren-carousel/ren-carousel.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "docs:docs/components/ren-carousel.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "file:components/composites/ren-carousel/ren-carousel.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--duration-normal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--ease-out",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--ren-carousel-arrow-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--ren-carousel-dot-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--ren-carousel-dot-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--ren-carousel-dot-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--ren-carousel-duration",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--ren-carousel-easing",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--ren-carousel-gap",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--ren-carousel-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--size-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-carousel",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "selector:.ren-accordion",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "selector:.ren-collapsible",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "selector:.ren-collapsible-content",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "selector:.ren-collapsible-ghost",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "contract:composite:ren-collapsible",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "file:components/composites/ren-collapsible/ren-collapsible.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "docs:docs/components/ren-collapsible.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--color-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--color-text-secondary",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--duration-normal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--ease-out",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--label-lg-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--leading-normal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--ren-collapse-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--ren-collapse-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--ren-collapse-duration",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--ren-collapse-easing",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--ren-collapse-padding",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--ren-collapse-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--ren-collapse-trigger-font",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--ren-collapse-trigger-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--ring-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--weight-medium",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-collapsible",
      "target": "token:--weight-semibold",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-alpha",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-alpha-gradient",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-alpha-handle",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-channel",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-channel-input",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-channel-label",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-channels",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-controls",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-controls-left",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-controls-right",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-dropdown",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-eyedropper",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-eyedropper-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-format-toggle",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-handle",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-hex-input",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-hue",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-hue-handle",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-inputs",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-preview",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-preview-info",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-preview-label",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-preview-swatch",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-preview-value",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-saturation",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-saturation-canvas",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-saturation-handle",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-swatch",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-swatch-color",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-swatches",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-track",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-trigger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-trigger-swatch",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "selector:.ren-color-picker-trigger-value",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "contract:composite:ren-color-picker",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "file:components/composites/ren-color-picker/ren-color-picker.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "docs:docs/components/ren-color-picker.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "file:components/composites/ren-color-picker/ren-color-picker.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--color-input-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--color-input-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--color-input-border-focus",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--color-input-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--color-surface-overlay",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--color-surface-raised",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--font-mono",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--label-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--ren-cp-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--ren-cp-handle-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--ren-cp-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--ren-cp-shadow",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--ren-cp-swatch-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--ren-cp-track-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--ren-cp-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--shadow-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-color-picker",
      "target": "token:--white",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "selector:.ren-combobox",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "contract:composite:ren-combobox",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "file:components/composites/ren-combobox/ren-combobox.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "docs:docs/components/ren-combobox.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "file:components/composites/ren-combobox/ren-combobox.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--color-accent-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--color-fill-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--duration-micro",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--duration-slow",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--leading-5",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--shadow-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--space-0-5",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--space-8",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--text-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--text-xs",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-combobox",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-context-menu",
      "target": "selector:.ren-context-menu",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-context-menu",
      "target": "selector:.ren-menu-item",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-context-menu",
      "target": "selector:.ren-menu-separator",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-context-menu",
      "target": "contract:composite:ren-context-menu",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-context-menu",
      "target": "file:components/composites/ren-context-menu/ren-context-menu.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-context-menu",
      "target": "docs:docs/components/ren-context-menu.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-context-menu",
      "target": "file:components/composites/ren-context-menu/ren-context-menu.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-context-menu",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-context-menu",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-context-menu",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-context-menu",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-context-menu",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-context-menu",
      "target": "token:--shadow-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-context-menu",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-context-menu",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "selector:.ren-calendar",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "selector:.ren-date-picker",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "selector:.ren-date-picker-dropdown",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "selector:.ren-date-picker-preset",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "selector:.ren-date-picker-presets",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "selector:.ren-date-picker-trigger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "selector:.ren-date-picker-value",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "contract:composite:ren-date-picker",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "file:components/composites/ren-date-picker/ren-date-picker.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "docs:docs/components/ren-date-picker.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "file:components/composites/ren-date-picker/ren-date-picker.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--color-border-strong",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--color-fill-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--color-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--color-surface-raised",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--label-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--shadow-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--space-1-5",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-picker",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-calendar",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-actions",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-apply",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-calendars",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-cancel",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-divider",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-dropdown",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-end",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-footer",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-inner",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-picker",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-preset",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-presets",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-separator",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-start",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-summary",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-trigger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "selector:.ren-date-range-value",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "contract:composite:ren-date-range-picker",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "file:components/composites/ren-date-range-picker/ren-date-range-picker.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "docs:docs/components/ren-date-range-picker.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "file:components/composites/ren-date-range-picker/ren-date-range-picker.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--color-accent-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--color-accent-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--color-border-strong",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--color-fill-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--color-on-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--color-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--color-surface-raised",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--color-text-secondary",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--label-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--shadow-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--space-1-5",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-date-range-picker",
      "target": "token:--weight-semibold",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "selector:.ren-alert-dialog",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "selector:.ren-dialog",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "selector:.ren-dialog-body",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "selector:.ren-dialog-close",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "selector:.ren-dialog-description",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "selector:.ren-dialog-footer",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "selector:.ren-dialog-full",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "selector:.ren-dialog-header",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "selector:.ren-dialog-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "selector:.ren-dialog-md",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "selector:.ren-dialog-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "selector:.ren-dialog-title",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "selector:.ren-dialog-wrapper",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "selector:.ren-dialog-xl",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "contract:composite:ren-dialog",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "file:components/composites/ren-dialog/ren-dialog.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "docs:docs/components/ren-dialog.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "file:components/composites/ren-dialog/ren-dialog.js",
      "type": "has_js"
    },
    {
      "detail": "ren-dialog",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "example:examples/dialog-workflow.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--color-background-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--color-background-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--color-border-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--color-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--color-overlay",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--color-surface-overlay",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--dialog-max-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--duration-normal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--ease-out",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--radius-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--ren-dialog-backdrop",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--ren-dialog-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--ren-dialog-border-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--ren-dialog-duration",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--ren-dialog-easing",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--ren-dialog-gap",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--ren-dialog-padding",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--ren-dialog-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--ren-dialog-shadow",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--ren-dialog-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--ring-offset-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--ring-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--shadow-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--space-6",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--transition-overlay",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dialog",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "selector:.ren-dropzone",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "selector:.ren-dropzone-compact",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "selector:.ren-dropzone-content",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "selector:.ren-dropzone-description",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "selector:.ren-dropzone-file",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "selector:.ren-dropzone-file-name",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "selector:.ren-dropzone-file-remove",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "selector:.ren-dropzone-file-size",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "selector:.ren-dropzone-files",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "selector:.ren-dropzone-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "selector:.ren-dropzone-input",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "selector:.ren-dropzone-title",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "contract:composite:ren-dropzone",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "file:components/composites/ren-dropzone/ren-dropzone.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "docs:docs/components/ren-dropzone.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "file:components/composites/ren-dropzone/ren-dropzone.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--color-accent-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--color-border-strong",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--color-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--color-surface-raised",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--color-surface-sunken",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--ren-dropzone-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--ren-dropzone-border-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--ren-dropzone-border-style",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--ren-dropzone-border-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--ren-dropzone-hover-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--ren-dropzone-hover-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--ren-dropzone-padding",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--ren-dropzone-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--ring-offset-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--ring-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--space-6",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--space-8",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--text-base",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--text-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--text-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-dropzone",
      "target": "token:--weight-medium",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "selector:.ren-hover-card",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "selector:.ren-hover-card-body",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "selector:.ren-hover-card-footer",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "selector:.ren-hover-card-header",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "selector:.ren-hover-card-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "selector:.ren-hover-card-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "selector:.ren-hover-card-trigger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "contract:composite:ren-hover-card",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "file:components/composites/ren-hover-card/ren-hover-card.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "docs:docs/components/ren-hover-card.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "file:components/composites/ren-hover-card/ren-hover-card.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--color-accent-strong",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--font-size-body",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--font-size-body-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--font-size-xs",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--shadow-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--space-0-25",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--space-0-5",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--space-5",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-hover-card",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "selector:.ren-menu",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "selector:.ren-menu-checkbox-item",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "selector:.ren-menu-group",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "selector:.ren-menu-item",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "selector:.ren-menu-item-danger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "selector:.ren-menu-item-description",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "selector:.ren-menu-item-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "selector:.ren-menu-item-shortcut",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "selector:.ren-menu-label",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "selector:.ren-menu-radio-item",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "selector:.ren-menu-separator",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "selector:.ren-open",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "contract:composite:ren-menu",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "file:components/composites/ren-menu/ren-menu.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "docs:docs/components/ren-menu.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "file:components/composites/ren-menu/ren-menu.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--color-danger-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--color-surface-overlay",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--duration-exit",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--duration-fast",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ease-exit",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ease-out",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ren-menu-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ren-menu-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ren-menu-danger-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ren-menu-duration",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ren-menu-easing",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ren-menu-item-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ren-menu-item-hover-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ren-menu-item-padding",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ren-menu-item-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ren-menu-padding",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ren-menu-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ren-menu-shadow",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--ren-menu-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--shadow-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--size-body",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--size-caption",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--size-icon-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--size-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-menu",
      "target": "token:--z-dropdown",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "selector:.ren-number-field",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "selector:.ren-number-field-decrement",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "selector:.ren-number-field-increment",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "selector:.ren-number-field-input",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "selector:.ren-number-field-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "selector:.ren-number-field-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "contract:composite:ren-number-field",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "file:components/composites/ren-number-field/ren-number-field.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "docs:docs/components/ren-number-field.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "file:components/composites/ren-number-field/ren-number-field.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--color-disabled-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--color-disabled-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--color-fill-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--color-input-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--color-input-bg-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--color-input-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--color-input-border-focus",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--color-input-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--color-input-placeholder",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--color-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--font-mono",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--size-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--size-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--text-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--text-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-number-field",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "selector:.ren-otp",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "selector:.ren-otp-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "selector:.ren-otp-separator",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "selector:.ren-otp-slot",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "selector:.ren-otp-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "contract:composite:ren-otp",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "file:components/composites/ren-otp/ren-otp.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "docs:docs/components/ren-otp.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "file:components/composites/ren-otp/ren-otp.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--color-disabled-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--color-disabled-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--color-input-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--color-input-bg-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--color-input-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--color-input-border-focus",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--color-input-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--color-input-placeholder",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--color-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--font-mono",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--size-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--size-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--space-0-5",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--text-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--text-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-otp",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "selector:.ren-popover",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "selector:.ren-popover-arrow",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "selector:.ren-popover-body",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "selector:.ren-popover-footer",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "selector:.ren-popover-header",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "selector:.ren-popover-trigger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "contract:composite:ren-popover",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "file:components/composites/ren-popover/ren-popover.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "docs:docs/components/ren-popover.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "file:components/composites/ren-popover/ren-popover.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--color-surface-overlay",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--color-text-primary",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--color-text-secondary",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--duration-normal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--ease-out",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--ren-popover-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--ren-popover-border-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--ren-popover-duration",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--ren-popover-easing",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--ren-popover-padding",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--ren-popover-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--ren-popover-shadow",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--ren-popover-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--shadow-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--size-body-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-popover",
      "target": "token:--z-popover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "selector:.ren-scroll-area",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "selector:.ren-scroll-area-full",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "selector:.ren-scroll-area-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "selector:.ren-scroll-area-md",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "selector:.ren-scroll-area-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "selector:.ren-scroll-area-xl",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "contract:composite:ren-scroll-area",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "file:components/composites/ren-scroll-area/ren-scroll-area.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "docs:docs/components/ren-scroll-area.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "token:--color-fill-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "token:--scroll-max",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-scroll-area",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select-chip",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select-chip-remove",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select-chips",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select-content",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select-empty",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select-group",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select-item",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select-label",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select-placeholder",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select-separator",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select-trigger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "selector:.ren-select-value",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "contract:composite:ren-select",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "file:components/composites/ren-select/ren-select.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "docs:docs/components/ren-select.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "file:components/composites/ren-select/ren-select.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--color-accent-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--color-input-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--color-input-bg-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--color-input-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--color-input-border-focus",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--color-input-disabled-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--color-input-disabled-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--color-input-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--color-input-placeholder",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--duration-exit",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--duration-micro",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--ease-exit",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--shadow-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--size-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--size-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--text-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--text-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-select",
      "target": "token:--z-dropdown",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "selector:.ren-sheet",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "selector:.ren-sheet-body",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "selector:.ren-sheet-close",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "selector:.ren-sheet-description",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "selector:.ren-sheet-footer",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "selector:.ren-sheet-handle",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "selector:.ren-sheet-header",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "selector:.ren-sheet-title",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "contract:composite:ren-sheet",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "file:components/composites/ren-sheet/ren-sheet.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "docs:docs/components/ren-sheet.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "file:components/composites/ren-sheet/ren-sheet.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--color-fill-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--color-overlay",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--color-surface-overlay",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--duration-exit",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--duration-normal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--ease-exit",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--ease-out",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--ren-sheet-backdrop",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--ren-sheet-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--ren-sheet-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--ren-sheet-duration",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--ren-sheet-easing",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--ren-sheet-padding",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--ren-sheet-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--ren-sheet-shadow",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--ren-sheet-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--shadow-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--space-6",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--space-8",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--text-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--text-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-sheet",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider-danger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider-label",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider-mark",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider-mark-label",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider-mark-tick",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider-marks",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider-range",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider-success",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider-track",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider-track-input",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider-value",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider-vertical",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "selector:.ren-slider-warning",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "contract:composite:ren-slider",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "file:components/composites/ren-slider/ren-slider.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "docs:docs/components/ren-slider.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "file:components/composites/ren-slider/ren-slider.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--color-fill-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--color-fill-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--color-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--color-warning",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--duration-fast",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--ease-out",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--font-size-body-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--font-size-label",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--font-size-xs",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--ren-slider-duration",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--ren-slider-easing",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--ren-slider-fill-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--ren-slider-thumb-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--ren-slider-thumb-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--ren-slider-thumb-shadow",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--ren-slider-thumb-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--ren-slider-track-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--ren-slider-track-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--ren-slider-track-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--shadow-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--space-0-5",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--value",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--value-end",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-slider",
      "target": "token:--value-start",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "selector:.ren-tab",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "selector:.ren-tab-list",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "selector:.ren-tab-list-enclosed",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "selector:.ren-tab-list-pills",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "selector:.ren-tab-list-underline",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "selector:.ren-tab-panel",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "selector:.ren-tabs",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "selector:.ren-tabs-indicator",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "contract:composite:ren-tabs",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "file:components/composites/ren-tabs/ren-tabs.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "docs:docs/components/ren-tabs.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "file:components/composites/ren-tabs/ren-tabs.js",
      "type": "has_js"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "example:examples/dashboard-shell.html",
      "type": "used_by_example"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "example:examples/data-table.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--duration-fast",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--ease-out",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--label-lg-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--label-md-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--label-md-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--label-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--ren-tabs-active-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--ren-tabs-border-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--ren-tabs-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--ren-tabs-duration",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--ren-tabs-easing",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--ren-tabs-font-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--ren-tabs-font-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--ren-tabs-gap",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--ren-tabs-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--ren-tabs-indicator-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--size-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tabs",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-btn",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast-action",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast-actions",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast-body",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast-close",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast-danger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast-description",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast-error",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast-info",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast-loading",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast-progress",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast-success",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast-title",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast-viewport",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "selector:.ren-toast-warning",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "contract:composite:ren-toast",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "file:components/composites/ren-toast/ren-toast.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "docs:docs/components/ren-toast.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "file:components/composites/ren-toast/ren-toast.js",
      "type": "has_js"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "example:examples/ai-panel.html",
      "type": "used_by_example"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "example:examples/app-sidebar.html",
      "type": "used_by_example"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "example:examples/auth-form.html",
      "type": "used_by_example"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "example:examples/dashboard-shell.html",
      "type": "used_by_example"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "example:examples/data-table.html",
      "type": "used_by_example"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "example:examples/dialog-workflow.html",
      "type": "used_by_example"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "example:examples/settings-form.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--color-danger-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--color-info",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--color-info-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--color-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--color-success-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--color-surface-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--color-surface-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--color-surface-raised",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--color-warning",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--color-warning-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--duration-exit",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--duration-micro",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--duration-normal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--ease-exit",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--ease-out",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--font-weight-semibold",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--leading-5",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--ren-toast-anim-duration",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--ren-toast-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--ren-toast-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--ren-toast-easing",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--ren-toast-gap",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--ren-toast-padding",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--ren-toast-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--ren-toast-shadow",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--ren-toast-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--shadow-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--text-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--text-xs",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toast",
      "target": "token:--z-toast",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "selector:.ren-toggle-group",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "selector:.ren-toggle-group-full",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "selector:.ren-toggle-group-item",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "selector:.ren-toggle-group-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "selector:.ren-toggle-group-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "contract:composite:ren-toggle-group",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "file:components/composites/ren-toggle-group/ren-toggle-group.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "docs:docs/components/ren-toggle-group.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "file:components/composites/ren-toggle-group/ren-toggle-group.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--color-border-strong",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--font-size-body",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--font-size-label",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--font-size-xs",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--label-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--ren-toggle-active-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--ren-toggle-active-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--ren-toggle-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--ren-toggle-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--ren-toggle-font-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--ren-toggle-font-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--ren-toggle-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--ren-toggle-padding-x",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--ren-toggle-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--shadow-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--size-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--space-0-5",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toggle-group",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "selector:.ren-toolbar",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "selector:.ren-toolbar-ghost",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "selector:.ren-toolbar-item",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "selector:.ren-toolbar-item-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "selector:.ren-toolbar-separator",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "selector:.ren-toolbar-vertical",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "contract:composite:ren-toolbar",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "file:components/composites/ren-toolbar/ren-toolbar.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "docs:docs/components/ren-toolbar.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "file:components/composites/ren-toolbar/ren-toolbar.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--color-accent-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--color-disabled-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--color-fill-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--color-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--color-surface-raised",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--color-surface-sunken",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--ren-toolbar-active-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--ren-toolbar-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--ren-toolbar-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--ren-toolbar-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--ren-toolbar-item-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--ren-toolbar-item-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--ren-toolbar-item-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--ren-toolbar-padding",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--ren-toolbar-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--ring-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--shadow-xs",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--size-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--size-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--size-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--text-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--transition-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-toolbar",
      "target": "token:--weight-medium",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "selector:.ren-tooltip",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "selector:.ren-tooltip-arrow",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "selector:.ren-tooltip-trigger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "contract:composite:ren-tooltip",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "file:components/composites/ren-tooltip/ren-tooltip.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "docs:docs/components/ren-tooltip.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "file:components/composites/ren-tooltip/ren-tooltip.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--caption-sm-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--color-text-inverted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--duration-fast",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--ease-out",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--ren-tooltip-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--ren-tooltip-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--ren-tooltip-duration",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--ren-tooltip-easing",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--ren-tooltip-font-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--ren-tooltip-padding-x",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--ren-tooltip-padding-y",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--ren-tooltip-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--ren-tooltip-shadow",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--shadow-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--size-caption",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:composite:ren-tooltip",
      "target": "token:--z-tooltip",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-action",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-action-edit",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-action-regenerate",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-action-thumbs",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-actions",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-citation",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-confidence",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-confidence-bar",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-confidence-fill",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-error",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-error-message",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-file-chip",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-file-chip-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-file-chip-name",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-file-chip-remove",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-file-chip-size",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-message",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-message-content",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-message-footer",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-message-header",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-prompt",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-prompt-input",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-prompt-send",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-skeleton",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-skeleton-line",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-slug",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-source",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-source-number",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-source-title",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-source-url",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-sources",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-streaming",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-tool-call",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-tool-call-args",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-tool-call-header",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-tool-call-name",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-tool-call-result",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-tool-call-status",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-typing",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "selector:.ren-ai-typing-dots",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "contract:pattern:ren-ai",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "file:components/patterns/ren-ai/ren-ai.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "docs:docs/components/ren-ai.html",
      "type": "has_docs_page"
    },
    {
      "detail": "ren-ai",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "example:examples/ai-panel.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--caption-sm-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-ai",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-ai-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-ai-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-border-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-on-ai",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-surface-raised",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-surface-sunken",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-text-secondary",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--color-warning",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--duration-state",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--duration-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--font-mono",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--leading-normal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--leading-relaxed",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--radius-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--size-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--size-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--space-0-5",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--text-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--tracking-wide",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--weight-bold",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--weight-medium",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-ai",
      "target": "token:--weight-semibold",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-empty",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-footer",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-footer-hint",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-group",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-group-heading",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-input",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-input-wrapper",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-item",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-item-content",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-item-description",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-item-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-item-shortcut",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-item-title",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-kbd",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-list",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "selector:.ren-command-separator",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "contract:pattern:ren-command",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "file:components/patterns/ren-command/ren-command.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "docs:docs/components/ren-command.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "file:components/patterns/ren-command/ren-command.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--color-separator",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--color-surface-overlay",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--color-text-faint",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--duration-overlay",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--duration-state",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ease-state-change",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--radius-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-command-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-command-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-command-item-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-command-item-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-command-max-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-command-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-command-shadow",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-command-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-font-mono",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-radius-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-space-8",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--ren-z-modal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--shadow-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-command",
      "target": "token:--size-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "selector:.ren-empty-state",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "selector:.ren-empty-state-actions",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "selector:.ren-empty-state-bordered",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "selector:.ren-empty-state-compact",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "selector:.ren-empty-state-description",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "selector:.ren-empty-state-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "selector:.ren-empty-state-title",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "contract:pattern:ren-empty-state",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "file:components/patterns/ren-empty-state/ren-empty-state.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "docs:docs/components/ren-empty-state.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--leading-normal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--ren-empty-desc-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--ren-empty-gap",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--ren-empty-icon-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--ren-empty-max-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--ren-empty-padding",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--ren-empty-title-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--space-10",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--space-6",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--space-8",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--text-3xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--text-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--text-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--text-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--title-sm-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--weight-semibold",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-empty-state",
      "target": "token:--width-prose",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "selector:.ren-field",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "selector:.ren-form",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "selector:.ren-form-actions",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "selector:.ren-form-error-summary",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "selector:.ren-form-progress",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "selector:.ren-form-row",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "selector:.ren-form-section",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "selector:.ren-form-section-title",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "selector:.ren-form-step",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "selector:.ren-form-step-label",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "selector:.ren-form-success",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "contract:pattern:ren-form",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "file:components/patterns/ren-form/ren-form.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "docs:docs/components/ren-form.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "file:components/patterns/ren-form/ren-form.js",
      "type": "has_js"
    },
    {
      "detail": "ren-form",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "example:examples/auth-form.html",
      "type": "used_by_example"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "example:examples/dashboard-shell.html",
      "type": "used_by_example"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "example:examples/data-table.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-form",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "example:examples/dialog-workflow.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-form",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "example:examples/settings-form.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--body-sm-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-accent-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-border-interactive",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-danger-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-input-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-input-border-focus",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-input-error-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-input-placeholder",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-on-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-on-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-success-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-text-faint",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--duration-state",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--label-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--leading-normal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--ren-field-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--ren-field-border-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--ren-field-border-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--ren-field-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--ren-field-error-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--ren-field-focus-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--ren-field-font-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--ren-field-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--ren-field-padding-x",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--ren-field-padding-y",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--ren-field-placeholder",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--ren-field-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--ren-field-success-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--size-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--space-6",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--title-sm-leading",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--title-sm-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--title-sm-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--weight-bold",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-form",
      "target": "token:--weight-semibold",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "selector:.ren-menubar",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "selector:.ren-menubar-checkbox",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "selector:.ren-menubar-item",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "selector:.ren-menubar-label",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "selector:.ren-menubar-menu",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "selector:.ren-menubar-radio",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "selector:.ren-menubar-separator",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "selector:.ren-menubar-shortcut",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "selector:.ren-menubar-submenu",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "selector:.ren-menubar-trigger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "contract:pattern:ren-menubar",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "file:components/patterns/ren-menubar/ren-menubar.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "docs:docs/components/ren-menubar.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "file:components/patterns/ren-menubar/ren-menubar.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--color-fill-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--color-on-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--color-separator",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--color-surface-raised",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--duration-exit",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--duration-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--label-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--shadow-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-menubar",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "selector:.ren-nav",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "selector:.ren-nav-actions",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "selector:.ren-nav-brand",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "selector:.ren-nav-dropdown",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "selector:.ren-nav-link",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "selector:.ren-nav-links",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "selector:.ren-nav-sticky",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "selector:.ren-nav-toggle",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "selector:.ren-nav-transparent",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "contract:pattern:ren-nav",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "file:components/patterns/ren-nav/ren-nav.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "docs:docs/components/ren-nav.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "file:components/patterns/ren-nav/ren-nav.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--color-accent-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--duration-overlay",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--duration-route",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--duration-state",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ease-state-change",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ren-label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ren-nav-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ren-nav-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ren-nav-gap",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ren-nav-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ren-nav-link-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ren-nav-link-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ren-nav-padding-x",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ren-space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ren-space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ren-space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ren-space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ren-title-sm-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--ren-z-sticky",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-nav",
      "target": "token:--space-6",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "selector:.ren-sidebar",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "selector:.ren-sidebar-content",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "selector:.ren-sidebar-divider",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "selector:.ren-sidebar-footer",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "selector:.ren-sidebar-header",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "selector:.ren-sidebar-item",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "selector:.ren-sidebar-item-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "selector:.ren-sidebar-item-text",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "selector:.ren-sidebar-layout",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "selector:.ren-sidebar-main",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "selector:.ren-sidebar-nav",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "selector:.ren-sidebar-overlay",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "selector:.ren-sidebar-section",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "selector:.ren-sidebar-section-label",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "selector:.ren-sidebar-toggle",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "contract:pattern:ren-sidebar",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "file:components/patterns/ren-sidebar/ren-sidebar.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "docs:docs/components/ren-sidebar.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "file:components/patterns/ren-sidebar/ren-sidebar.js",
      "type": "has_js"
    },
    {
      "detail": "ren-sidebar",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "example:examples/app-sidebar.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-sidebar",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "example:examples/dashboard-shell.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--color-accent-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--color-separator",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--color-surface-sunken",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--color-text-faint",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--duration-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--duration-normal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--duration-state",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ease-out",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ease-state-change",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-sidebar-active-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-sidebar-active-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-sidebar-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-sidebar-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-sidebar-collapsed-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-sidebar-duration",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-sidebar-easing",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-sidebar-item-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-sidebar-item-padding",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-sidebar-item-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-sidebar-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--ren-space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--sidebar-collapsed-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--sidebar-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--size-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-sidebar",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-body",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-comfortable",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-compact",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-empty",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-empty-description",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-empty-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-empty-title",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-header",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-loading-overlay",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-pagination",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-pagination-button",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-pagination-controls",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-pagination-info",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-select",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-toolbar",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-toolbar-search",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-table-wrapper",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-td",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-td-pinned",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-th",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-th-pinned",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-th-resize",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-th-sortable",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "selector:.ren-tr",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "contract:pattern:ren-table",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "file:components/patterns/ren-table/ren-table.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "docs:docs/components/ren-table.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "file:components/patterns/ren-table/ren-table.js",
      "type": "has_js"
    },
    {
      "detail": "ren-table",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "example:examples/dashboard-shell.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-table",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "example:examples/data-table.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--color-accent-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--color-border-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--color-on-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--color-surface-raised",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--color-surface-sunken",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--duration-state",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--duration-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--label-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--ren-table-border-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--ren-table-cell-padding",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--ren-table-header-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--ren-table-header-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--ren-table-header-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--ren-table-header-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--ren-table-hover-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--ren-table-row-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--ren-table-selected-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--ren-table-stripe-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--size-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--space-10",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--space-5",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:pattern:ren-table",
      "target": "token:--weight-semibold",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "selector:.ren-avatar",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "selector:.ren-avatar-2xl",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "selector:.ren-avatar-group",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "selector:.ren-avatar-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "selector:.ren-avatar-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "selector:.ren-avatar-square",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "selector:.ren-avatar-status",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "selector:.ren-avatar-status-away",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "selector:.ren-avatar-status-busy",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "selector:.ren-avatar-status-offline",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "selector:.ren-avatar-wrapper",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "selector:.ren-avatar-xl",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "selector:.ren-avatar-xs",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "contract:primitive:ren-avatar",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "file:components/primitives/ren-avatar/ren-avatar.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "docs:docs/components/ren-avatar.html",
      "type": "has_docs_page"
    },
    {
      "detail": "ren-avatar",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "example:examples/data-table.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--avatar-2xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--avatar-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--avatar-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--avatar-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--avatar-xs",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--color-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--color-surface",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--color-warning",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--gray-400",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--ren-avatar-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--ren-avatar-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--ren-avatar-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--ren-avatar-font-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--ren-avatar-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--ren-avatar-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--text-base",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--text-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--text-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--text-xs",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-avatar",
      "target": "token:--weight-semibold",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "selector:.ren-badge",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "selector:.ren-badge-danger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "selector:.ren-badge-dot",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "selector:.ren-badge-dot-danger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "selector:.ren-badge-dot-success",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "selector:.ren-badge-dot-warning",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "selector:.ren-badge-info",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "selector:.ren-badge-outline",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "selector:.ren-badge-primary",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "selector:.ren-badge-secondary",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "selector:.ren-badge-success",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "selector:.ren-badge-warning",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "contract:primitive:ren-badge",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "file:components/primitives/ren-badge/ren-badge.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "docs:docs/components/ren-badge.html",
      "type": "has_docs_page"
    },
    {
      "detail": "ren-badge",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "example:examples/app-sidebar.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-badge",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "example:examples/dashboard-shell.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-badge",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "example:examples/data-table.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-danger-strong",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-danger-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-info-strong",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-info-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-on-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-success-strong",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-success-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-text-secondary",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-warning",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-warning-strong",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--color-warning-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--ren-badge-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--ren-badge-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--ren-badge-font-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--ren-badge-font-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--ren-badge-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--ren-badge-padding-x",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--ren-badge-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-badge",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "selector:.ren-banner",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "selector:.ren-banner-actions",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "selector:.ren-banner-compact",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "selector:.ren-banner-content",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "selector:.ren-banner-danger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "selector:.ren-banner-dismiss",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "selector:.ren-banner-full",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "selector:.ren-banner-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "selector:.ren-banner-message",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "selector:.ren-banner-neutral",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "selector:.ren-banner-success",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "selector:.ren-banner-title",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "selector:.ren-banner-warning",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "contract:primitive:ren-banner",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "file:components/primitives/ren-banner/ren-banner.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "docs:docs/components/ren-banner.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--color-danger-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--color-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--color-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--color-success-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--color-surface-sunken",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--color-text-secondary",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--color-warning",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--color-warning-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--duration-state",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--duration-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--leading-normal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--ren-banner-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--ren-banner-border-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--ren-banner-border-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--ren-banner-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--ren-banner-gap",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--ren-banner-icon-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--ren-banner-padding",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--ren-banner-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--ring-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--text-base",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--text-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--text-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-banner",
      "target": "token:--weight-semibold",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "selector:.ren-breadcrumb",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "selector:.ren-breadcrumb-truncated",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "contract:primitive:ren-breadcrumb",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "file:components/primitives/ren-breadcrumb/ren-breadcrumb.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "docs:docs/components/ren-breadcrumb.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "token:--color-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "token:--color-text-faint",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "token:--duration-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "token:--ren-breadcrumb-active-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "token:--ren-breadcrumb-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "token:--ren-breadcrumb-font-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "token:--ren-breadcrumb-gap",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "token:--ren-breadcrumb-separator",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "token:--ring-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-breadcrumb",
      "target": "token:--weight-medium",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "selector:.ren-btn",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "selector:.ren-btn-accent",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "selector:.ren-btn-danger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "selector:.ren-btn-full",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "selector:.ren-btn-ghost",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "selector:.ren-btn-group",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "selector:.ren-btn-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "selector:.ren-btn-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "selector:.ren-btn-link",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "selector:.ren-btn-outline",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "selector:.ren-btn-primary",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "selector:.ren-btn-secondary",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "selector:.ren-btn-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "contract:primitive:ren-button",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "file:components/primitives/ren-button/ren-button.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "docs:docs/components/ren-button.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "file:components/primitives/ren-button/ren-button.js",
      "type": "has_js"
    },
    {
      "detail": "ren-button",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "example:examples/ai-panel.html",
      "type": "used_by_example"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "example:examples/app-sidebar.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-button",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "example:examples/auth-form.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-button",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "example:examples/dashboard-shell.html",
      "type": "used_by_example"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "example:examples/data-table.html",
      "type": "used_by_example"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "example:examples/dialog-workflow.html",
      "type": "used_by_example"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "example:examples/settings-form.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--_active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--_hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--btn-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--color-accent-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--color-disabled-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--color-disabled-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--color-fill-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--color-on-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--color-on-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--color-text-link",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--color-text-link-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--duration-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--red-500",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--red-600",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-bg-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-bg-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-border-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-border-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-duration",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-easing",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-font-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-font-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-gap",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-padding-x",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-padding-y",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-ring-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-ring-offset",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--ren-btn-ring-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--size-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--size-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--size-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--space-6",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-button",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card-body",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card-cover",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card-description",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card-elevated",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card-footer",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card-footer-border",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card-ghost",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card-group",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card-header",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card-interactive",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card-outline",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card-selectable",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card-simple",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card-sunken",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "selector:.ren-card-title",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "contract:primitive:ren-card",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "file:components/primitives/ren-card/ren-card.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "docs:docs/components/ren-card.html",
      "type": "has_docs_page"
    },
    {
      "detail": "ren-card",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "example:examples/ai-panel.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-card",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "example:examples/app-sidebar.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-card",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "example:examples/auth-form.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-card",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "example:examples/dashboard-shell.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-card",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "example:examples/data-table.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-card",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "example:examples/dialog-workflow.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-card",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "example:examples/settings-form.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--color-border-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--color-border-strong",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--color-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--color-info",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--color-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--color-surface-sunken",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--color-warning",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--duration-state",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--duration-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--leading-normal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--leading-snug",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--ren-card-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--ren-card-border-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--ren-card-border-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--ren-card-gap",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--ren-card-header-gap",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--ren-card-padding",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--ren-card-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--ren-card-shadow",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--ring-offset-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--ring-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--shadow-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--text-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--text-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--title-sm-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-card",
      "target": "token:--weight-semibold",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "selector:.ren-checkbox",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "selector:.ren-checkbox-control",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "contract:primitive:ren-checkbox",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "file:components/primitives/ren-checkbox/ren-checkbox.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "docs:docs/components/ren-checkbox.html",
      "type": "has_docs_page"
    },
    {
      "detail": "ren-checkbox",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "example:examples/data-table.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-checkbox",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "example:examples/settings-form.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--color-accent-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--color-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--duration-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--ren-checkbox-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--ren-checkbox-checked-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--ren-checkbox-checked-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--ren-checkbox-duration",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--ren-checkbox-easing",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--ren-checkbox-gap",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--ren-checkbox-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--ren-checkbox-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--ring-offset-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--ring-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-checkbox",
      "target": "token:--weight-regular",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "selector:.ren-field",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "selector:.ren-field-control",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "selector:.ren-field-description",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "selector:.ren-field-error",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "selector:.ren-field-label",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "selector:.ren-input",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "selector:.ren-input-error",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "selector:.ren-input-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "selector:.ren-input-icon-end",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "selector:.ren-input-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "selector:.ren-input-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "selector:.ren-input-success",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "selector:.ren-input-wrapper",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "contract:primitive:ren-field",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "file:components/primitives/ren-field/ren-field.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "docs:docs/components/ren-field.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "file:components/primitives/ren-field/ren-field.js",
      "type": "has_js"
    },
    {
      "detail": "selector",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "example:examples/ai-panel.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-field",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "example:examples/auth-form.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-field",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "example:examples/dashboard-shell.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-field",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "example:examples/data-table.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-field",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "example:examples/dialog-workflow.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-field",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "example:examples/settings-form.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--blue-100",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--blue-200",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--blue-300",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--blue-50",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--blue-600",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--blue-700",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--blue-800",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--blue-900",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--color-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--color-input-bg-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--color-input-border-focus",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--color-input-disabled-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--color-input-disabled-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--color-input-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--color-surface-overlay",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--duration-state",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--duration-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--gray-400",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--gray-50",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--gray-500",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--gray-800",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--icon-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--label-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--leading-normal",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--radius-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--ren-field-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--ren-field-border-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--ren-field-border-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--ren-field-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--ren-field-error-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--ren-field-focus-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--ren-field-font-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--ren-field-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--ren-field-padding-x",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--ren-field-padding-y",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--ren-field-placeholder",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--ren-field-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--ren-field-success-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--size-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--size-xl",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--space-10",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--text-lg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--text-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--weight-bold",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-field",
      "target": "token:--weight-medium",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "selector:.ren-icon",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "selector:.ren-icon-2xl",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "selector:.ren-icon-danger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "selector:.ren-icon-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "selector:.ren-icon-md",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "selector:.ren-icon-muted",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "selector:.ren-icon-primary",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "selector:.ren-icon-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "selector:.ren-icon-spin",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "selector:.ren-icon-success",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "selector:.ren-icon-warning",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "selector:.ren-icon-xl",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "selector:.ren-icon-xs",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "contract:primitive:ren-icon",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "file:components/primitives/ren-icon/ren-icon.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "docs:docs/components/ren-icon.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "token:--color-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "token:--color-text-secondary",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "token:--color-warning",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "token:--duration-loop",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "token:--ease-loop",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "token:--ren-icon-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "token:--ren-icon-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-icon",
      "target": "token:--ren-icon-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-kbd",
      "target": "selector:.ren-kbd",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-kbd",
      "target": "contract:primitive:ren-kbd",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-kbd",
      "target": "file:components/primitives/ren-kbd/ren-kbd.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-kbd",
      "target": "docs:docs/components/ren-kbd.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-kbd",
      "target": "token:--ren-kbd-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-kbd",
      "target": "token:--ren-kbd-border",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-kbd",
      "target": "token:--ren-kbd-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-kbd",
      "target": "token:--ren-kbd-font-family",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-kbd",
      "target": "token:--ren-kbd-font-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-kbd",
      "target": "token:--ren-kbd-padding",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-kbd",
      "target": "token:--ren-kbd-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-kbd",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-kbd",
      "target": "token:--weight-medium",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "selector:.ren-link",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "selector:.ren-link-external",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "selector:.ren-link-muted",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "selector:.ren-link-nav",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "selector:.ren-link-plain",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "selector:.ren-link-skip",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "contract:primitive:ren-link",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "file:components/primitives/ren-link/ren-link.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "docs:docs/components/ren-link.html",
      "type": "has_docs_page"
    },
    {
      "detail": "ren-link",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "example:examples/auth-form.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-link",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "example:examples/dashboard-shell.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--color-accent-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--color-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--color-on-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--color-text-link",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--color-text-link-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--duration-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--ren-link-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--ren-link-font-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--ren-link-hover-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--ren-link-underline",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--ring-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--weight-medium",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-link",
      "target": "token:--weight-semibold",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "selector:.ren-pagination",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "selector:.ren-pagination-centered",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "selector:.ren-pagination-compact",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "selector:.ren-pagination-ellipsis",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "selector:.ren-pagination-info",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "selector:.ren-pagination-item",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "selector:.ren-pagination-next",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "selector:.ren-pagination-prev",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "selector:.ren-pagination-simple",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "contract:primitive:ren-pagination",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "file:components/primitives/ren-pagination/ren-pagination.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "docs:docs/components/ren-pagination.html",
      "type": "has_docs_page"
    },
    {
      "detail": "ren-pagination",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "example:examples/data-table.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--color-disabled-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--color-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--duration-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--ren-pagination-active-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--ren-pagination-active-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--ren-pagination-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--ren-pagination-font-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--ren-pagination-gap",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--ren-pagination-hover-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--ren-pagination-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--ren-pagination-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--ring-offset-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--ring-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--size-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--text-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-pagination",
      "target": "token:--weight-medium",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-meter",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-meter-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-meter-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-meter-xl",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-progress",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-progress-bar",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-progress-danger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-progress-indeterminate",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-progress-info",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-progress-label",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-progress-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-progress-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-progress-success",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-progress-value",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-progress-warning",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "selector:.ren-progress-xl",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "contract:primitive:ren-progress",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "file:components/primitives/ren-progress/ren-progress.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "docs:docs/components/ren-progress.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--color-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--color-info",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--color-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--color-text-secondary",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--color-warning",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--label-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--ren-progress-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--ren-progress-duration",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--ren-progress-easing",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--ren-progress-fill",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--ren-progress-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--ren-progress-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--space-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--weight-medium",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-progress",
      "target": "token:--weight-regular",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "selector:.ren-radio",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "selector:.ren-radio-control",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "selector:.ren-radio-group",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "selector:.ren-radio-group-horizontal",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "contract:primitive:ren-radio",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "file:components/primitives/ren-radio/ren-radio.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "docs:docs/components/ren-radio.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "file:components/primitives/ren-radio/ren-radio.js",
      "type": "has_js"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--color-accent-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--color-border-interactive",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--color-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--color-on-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--duration-state",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--duration-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--ease-playful",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--ring-offset-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--ring-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--space-4",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-radio",
      "target": "token:--weight-regular",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-separator",
      "target": "selector:.ren-separator",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-separator",
      "target": "selector:.ren-separator-horizontal",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-separator",
      "target": "selector:.ren-separator-label",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-separator",
      "target": "selector:.ren-separator-vertical",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-separator",
      "target": "contract:primitive:ren-separator",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-separator",
      "target": "file:components/primitives/ren-separator/ren-separator.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-separator",
      "target": "docs:docs/components/ren-separator.html",
      "type": "has_docs_page"
    },
    {
      "detail": "ren-separator",
      "data": {},
      "source": "component:primitive:ren-separator",
      "target": "example:examples/auth-form.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-separator",
      "target": "token:--caption-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-separator",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-separator",
      "target": "token:--ren-separator-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-separator",
      "target": "token:--ren-separator-margin",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-separator",
      "target": "token:--ren-separator-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-separator",
      "target": "token:--space-3",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "selector:.ren-skeleton",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "selector:.ren-skeleton-circle",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "selector:.ren-skeleton-heading",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "selector:.ren-skeleton-rect",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "selector:.ren-skeleton-text",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "contract:primitive:ren-skeleton",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "file:components/primitives/ren-skeleton/ren-skeleton.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "docs:docs/components/ren-skeleton.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "token:--avatar-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "token:--ease-loop-pulse",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "token:--radius-md",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "token:--radius-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "token:--ren-skeleton-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "token:--ren-skeleton-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "token:--ren-skeleton-shine",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-skeleton",
      "target": "token:--ren-skeleton-speed",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "selector:.ren-spinner",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "selector:.ren-spinner-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "selector:.ren-spinner-light",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "selector:.ren-spinner-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "selector:.ren-spinner-xl",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "selector:.ren-spinner-xs",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "contract:primitive:ren-spinner",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "file:components/primitives/ren-spinner/ren-spinner.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "docs:docs/components/ren-spinner.html",
      "type": "has_docs_page"
    },
    {
      "detail": "ren-spinner",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "example:examples/ai-panel.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "token:--color-fill-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "token:--duration-loop-gentle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "token:--ease-loop-pulse",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "token:--ease-loop-smooth",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "token:--ren-spinner-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "token:--ren-spinner-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "token:--ren-spinner-speed",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "token:--ren-spinner-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-spinner",
      "target": "token:--white",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "selector:.ren-switch",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "selector:.ren-switch-track",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "contract:primitive:ren-switch",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "file:components/primitives/ren-switch/ren-switch.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "docs:docs/components/ren-switch.html",
      "type": "has_docs_page"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--color-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--color-success-strong",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--ren-switch-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--ren-switch-checked-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--ren-switch-duration",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--ren-switch-easing",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--ren-switch-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--ren-switch-thumb-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--ren-switch-thumb-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--ren-switch-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--ring-offset-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--ring-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--shadow-sm",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--touch-min",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-switch",
      "target": "token:--weight-regular",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "selector:.ren-tag",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "selector:.ren-tag-clickable",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "selector:.ren-tag-danger",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "selector:.ren-tag-dismiss",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "selector:.ren-tag-group",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "selector:.ren-tag-lg",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "selector:.ren-tag-primary",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "selector:.ren-tag-sm",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "selector:.ren-tag-success",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "selector:.ren-tag-warning",
      "type": "exposes_selector"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "contract:primitive:ren-tag",
      "type": "has_contract"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "file:components/primitives/ren-tag/ren-tag.css",
      "type": "has_css"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "docs:docs/components/ren-tag.html",
      "type": "has_docs_page"
    },
    {
      "detail": "ren-tag",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "example:examples/ai-panel.html",
      "type": "used_by_example"
    },
    {
      "detail": "ren-tag",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "example:examples/data-table.html",
      "type": "used_by_example"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--body-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-accent-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-border-strong",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-danger",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-danger-strong",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-danger-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-fill-active",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-fill-hover",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-focus-ring",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-on-accent",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-success",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-success-strong",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-success-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-text",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-text-muted",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-warning",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-warning-strong",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--color-warning-subtle",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--duration-tactile",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--ease-enter",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--radius-full",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--ren-tag-bg",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--ren-tag-border-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--ren-tag-color",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--ren-tag-font-size",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--ren-tag-font-weight",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--ren-tag-gap",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--ren-tag-height",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--ren-tag-padding-x",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--ren-tag-radius",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--ring-offset-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--ring-width",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--space-2",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--stroke-1",
      "type": "uses_token"
    },
    {
      "detail": "",
      "data": {},
      "source": "component:primitive:ren-tag",
      "target": "token:--text-xs",
      "type": "uses_token"
    }
  ]
}
