{
  "schemaVersion": 1,
  "generatedAt": "2026-06-08T08:32:51.519Z",
  "sourceRepo": "holmhq/cdn",
  "components": [
    {
      "category": "ui",
      "name": "feed-stars",
      "slug": "feed-stars",
      "tagName": "feed-stars",
      "title": "Feed Stars",
      "summary": "Animated 1–5 star rating control with playful per-star faces.",
      "status": "stable",
      "tags": [
        "rating",
        "stars",
        "feedback",
        "input",
        "form"
      ],
      "latestVersion": "0.0.3",
      "versionFolder": "v-0.0.3",
      "releaseSha": "dc4f2fc748034f056000676f836c727abf47b438",
      "libPath": "libs/ui/feed-stars",
      "moduleFile": "feed-stars.min.mjs",
      "moduleUrl": "https://cdn.jsdelivr.net/gh/holmhq/cdn@dc4f2fc748034f056000676f836c727abf47b438/libs/ui/feed-stars/v-0.0.3/feed-stars.min.mjs",
      "specPath": "libs/ui/feed-stars/v-0.0.3/gallery.json",
      "specUrl": "https://cdn.jsdelivr.net/gh/holmhq/cdn@dc4f2fc748034f056000676f836c727abf47b438/libs/ui/feed-stars/v-0.0.3/gallery.json",
      "stories": [
        {
          "id": "empty",
          "title": "Empty",
          "summary": "Unset rating — all five stars await a selection.",
          "html": "<feed-stars></feed-stars>",
          "height": "120px"
        },
        {
          "id": "preset",
          "title": "Preset value",
          "summary": "Pre-selected to four stars on load via the value attribute.",
          "html": "<feed-stars value=\"4\"></feed-stars>",
          "height": "120px"
        },
        {
          "id": "disabled",
          "title": "Disabled",
          "summary": "Non-interactive and dimmed while still showing the current rating.",
          "html": "<feed-stars value=\"3\" disabled></feed-stars>",
          "height": "120px"
        },
        {
          "id": "themed-amber",
          "title": "Themed (amber on light)",
          "summary": "Warm amber palette tuned for a light background via CSS custom properties.",
          "html": "<feed-stars value=\"5\" style=\"--feed-stars-active:#f59e0b;--feed-stars-active-pale:rgba(245,158,11,0.36);--feed-stars-inactive:#e5e7eb;--feed-stars-face-active:#ffffff;--feed-stars-face-inactive:#fafafa;\"></feed-stars>",
          "height": "120px",
          "theme": "light"
        }
      ],
      "snippets": [
        {
          "id": "change-listener",
          "title": "Listen for rating changes",
          "language": "js",
          "storyId": "empty",
          "code": "const stars = document.querySelector('feed-stars');\nstars.addEventListener('change', (e) => {\n  console.log('New rating:', e.detail.value); // 1–5, or 0 when cleared\n});"
        }
      ],
      "docs": {
        "attributes": [
          {
            "name": "value",
            "type": "number",
            "values": "1, 2, 3, 4, 5",
            "default": "unset (0)",
            "required": false,
            "description": "Initial/current rating. Integers 1–5 select that many stars; any other value leaves the control unset.",
            "example": "value=\"4\""
          },
          {
            "name": "disabled",
            "type": "boolean",
            "default": "false",
            "required": false,
            "description": "When present, makes the control non-interactive and dims it.",
            "example": "disabled"
          }
        ],
        "events": [
          {
            "name": "change",
            "detail": "{ value: number } — the new rating (0 when cleared)",
            "bubbles": true,
            "composed": true,
            "when": "The user selects a new rating, or clears it by clicking the already-selected star.",
            "description": "Notifies listeners of rating changes with the new numeric value."
          }
        ],
        "cssProperties": [
          {
            "name": "--feed-stars-active",
            "default": "#FFED76",
            "description": "Fill color of selected/active stars."
          },
          {
            "name": "--feed-stars-active-pale",
            "default": "rgba(255, 237, 118, 0.36)",
            "description": "Particle/dust color animated around active stars."
          },
          {
            "name": "--feed-stars-inactive",
            "default": "#121621",
            "description": "Outline color of unselected stars."
          },
          {
            "name": "--feed-stars-face-active",
            "default": "#121621",
            "description": "Facial-feature color on selected stars."
          },
          {
            "name": "--feed-stars-face-inactive",
            "default": "#1C212E",
            "description": "Facial-feature color on unselected stars."
          },
          {
            "name": "--feed-stars-gap",
            "default": "16px",
            "description": "Horizontal spacing between stars."
          }
        ],
        "methods": [
          {
            "name": "reset",
            "signature": "reset(): void",
            "description": "Clears the rating back to unset (value = 0)."
          }
        ]
      }
    },
    {
      "category": "ui",
      "name": "flow-globe",
      "slug": "flow-globe",
      "tagName": "flow-globe",
      "title": "Flow Globe",
      "summary": "Animated geospatial flows as glowing arcs on a rotating 3D globe or flat map.",
      "status": "experimental",
      "tags": [
        "map",
        "globe",
        "geo",
        "visualization",
        "data",
        "flows"
      ],
      "latestVersion": "0.1.2",
      "versionFolder": "v-0.1.2",
      "releaseSha": "0245f01cd552fddf8ab31468a5fa62c6dbbdeb08",
      "libPath": "libs/ui/flow-globe",
      "moduleFile": "flow-globe.min.mjs",
      "moduleUrl": "https://cdn.jsdelivr.net/gh/holmhq/cdn@0245f01cd552fddf8ab31468a5fa62c6dbbdeb08/libs/ui/flow-globe/v-0.1.2/flow-globe.min.mjs",
      "specPath": "libs/ui/flow-globe/v-0.1.2/gallery.json",
      "specUrl": "https://cdn.jsdelivr.net/gh/holmhq/cdn@0245f01cd552fddf8ab31468a5fa62c6dbbdeb08/libs/ui/flow-globe/v-0.1.2/gallery.json",
      "stories": [
        {
          "id": "ocean-globe",
          "title": "Ocean globe",
          "summary": "Default ocean theme with demo flows on a rotating globe.",
          "html": "<flow-globe theme=\"ocean\" height=\"520px\"></flow-globe>",
          "height": "560px"
        },
        {
          "id": "custom-flows",
          "title": "Custom flows with roles",
          "summary": "Explicit source/hub/destination roles, custom flow data, and country names.",
          "html": "<flow-globe\n  title=\"Design System Adoption\"\n  subtitle=\"Component imports by region\"\n  theme=\"ocean\"\n  producers=\"US,IN\"\n  hubs=\"DE,SG\"\n  consumers=\"GB,FR,JP,AU\"\n  data='[{\"sourceId\":\"US\",\"targetId\":\"DE\",\"value\":120},{\"sourceId\":\"IN\",\"targetId\":\"SG\",\"value\":90},{\"sourceId\":\"DE\",\"targetId\":\"GB\",\"value\":70},{\"sourceId\":\"SG\",\"targetId\":\"JP\",\"value\":50},{\"sourceId\":\"SG\",\"targetId\":\"AU\",\"value\":40}]'\n  country-names='{\"SG\":\"Singapore\",\"AU\":\"Australia\"}'\n></flow-globe>",
          "height": "580px"
        },
        {
          "id": "slate-map",
          "title": "Slate map projection",
          "summary": "Flat equirectangular map with the slate theme and legend.",
          "html": "<flow-globe title=\"Global Supply Chain\" theme=\"slate\" projection=\"map\" height=\"420px\"></flow-globe>",
          "height": "460px",
          "theme": "dark"
        },
        {
          "id": "coffee-static",
          "title": "Coffee, no controls",
          "summary": "Coffee theme with auto-rotate, controls, and legend all disabled.",
          "html": "<flow-globe theme=\"coffee\" height=\"520px\" auto-rotate=\"false\" controls=\"false\" legend=\"false\"></flow-globe>",
          "height": "560px"
        },
        {
          "id": "ember-arc-override",
          "title": "Ember with arc override",
          "summary": "Compact ember globe with a custom arc color via CSS custom property.",
          "html": "<flow-globe theme=\"ember\" height=\"300px\" style=\"--flow-globe-arc:#ff69b4;\"></flow-globe>",
          "height": "340px"
        }
      ],
      "snippets": [
        {
          "id": "set-data",
          "title": "Replace flow data programmatically",
          "language": "js",
          "storyId": "custom-flows",
          "code": "const globe = document.querySelector('flow-globe');\nglobe.setData([\n  { sourceId: 'US', targetId: 'DE', value: 120 },\n  { sourceId: 'DE', targetId: 'GB', value: 70 }\n]);"
        }
      ],
      "docs": {
        "attributes": [
          {
            "name": "title",
            "type": "string",
            "required": false,
            "description": "Display title shown at the top of the globe.",
            "example": "title=\"Trade Flows\""
          },
          {
            "name": "subtitle",
            "type": "string",
            "required": false,
            "description": "Display subtitle shown below the title."
          },
          {
            "name": "theme",
            "type": "string",
            "values": "ocean, coffee, ember, slate",
            "default": "ocean",
            "required": false,
            "description": "Color palette for the globe, arcs, and nodes."
          },
          {
            "name": "height",
            "type": "string",
            "default": "520px",
            "required": false,
            "description": "Wrapper height as any CSS length.",
            "example": "height=\"70vh\""
          },
          {
            "name": "projection",
            "type": "string",
            "values": "globe, map",
            "default": "globe",
            "required": false,
            "description": "Render mode: orthographic globe or flat equirectangular map."
          },
          {
            "name": "auto-rotate",
            "type": "boolean",
            "values": "true, false",
            "default": "true",
            "required": false,
            "description": "Continuous auto-rotation. Drag-to-rotate still works when false."
          },
          {
            "name": "controls",
            "type": "boolean",
            "values": "true, false",
            "default": "true",
            "required": false,
            "description": "Show the Globe/Map toggle buttons."
          },
          {
            "name": "legend",
            "type": "boolean",
            "values": "true, false",
            "default": "true",
            "required": false,
            "description": "Show the Source/Hub/Destination role legend."
          },
          {
            "name": "data",
            "type": "json",
            "default": "built-in demo flows",
            "required": false,
            "description": "Flow array of { sourceId, targetId, value } using ISO-3166 alpha-2 country codes.",
            "example": "data='[{\"sourceId\":\"US\",\"targetId\":\"DE\",\"value\":120}]'"
          },
          {
            "name": "country-names",
            "type": "json",
            "default": "{}",
            "required": false,
            "description": "Object mapping country IDs to custom display names."
          },
          {
            "name": "producers",
            "type": "csv",
            "required": false,
            "description": "Country codes to color as source nodes.",
            "example": "producers=\"US,IN\""
          },
          {
            "name": "hubs",
            "type": "csv",
            "required": false,
            "description": "Country codes to color as intermediate/hub nodes."
          },
          {
            "name": "consumers",
            "type": "csv",
            "required": false,
            "description": "Country codes to color as destination nodes."
          },
          {
            "name": "value-suffix",
            "type": "string",
            "required": false,
            "description": "Suffix appended to flow values."
          }
        ],
        "events": [],
        "cssProperties": [
          {
            "name": "--flow-globe-height",
            "default": "520px",
            "description": "Wrapper height."
          },
          {
            "name": "--flow-globe-radius",
            "default": "22px",
            "description": "Wrapper border radius."
          },
          {
            "name": "--flow-globe-shadow",
            "default": "0 24px 70px rgba(0,0,0,0.28)",
            "description": "Wrapper box shadow."
          },
          {
            "name": "--flow-globe-arc",
            "default": "theme-dependent",
            "description": "Flow arc line color."
          },
          {
            "name": "--flow-globe-arc-hot",
            "default": "theme-dependent",
            "description": "Particle glow/shadow color."
          },
          {
            "name": "--flow-globe-particle",
            "default": "theme-dependent",
            "description": "Travelling particle fill color."
          },
          {
            "name": "--flow-globe-producer",
            "default": "theme-dependent",
            "description": "Source-country node color."
          },
          {
            "name": "--flow-globe-hub",
            "default": "theme-dependent",
            "description": "Intermediate-country node color."
          },
          {
            "name": "--flow-globe-consumer",
            "default": "theme-dependent",
            "description": "Destination-country node color."
          },
          {
            "name": "--flow-globe-title",
            "default": "theme-dependent",
            "description": "Title text color."
          },
          {
            "name": "--flow-globe-subtitle",
            "default": "theme-dependent",
            "description": "Subtitle text color."
          }
        ],
        "methods": [
          {
            "name": "setData",
            "signature": "setData(flows: Array<{sourceId, targetId, value}>): void",
            "description": "Replace the flow data and re-render."
          },
          {
            "name": "configure",
            "signature": "configure(options: object): FlowGlobe",
            "description": "Apply multiple options (attributes, data, countryNames) at once; returns the element for chaining."
          }
        ]
      }
    },
    {
      "category": "ui",
      "name": "kerala-district-map",
      "slug": "kerala-district-map",
      "tagName": "kerala-district-map",
      "title": "Kerala District Map",
      "summary": "A data-driven SVG map of Kerala districts with choropleth fills, Malayalam labels, and selectable district events.",
      "status": "experimental",
      "tags": [
        "kerala",
        "map",
        "districts",
        "choropleth",
        "visualization",
        "svg",
        "malayalam"
      ],
      "latestVersion": "0.0.2",
      "versionFolder": "v-0.0.2",
      "releaseSha": "63606cb711d0e1697283c7f88db1cce736396c5f",
      "libPath": "libs/ui/kerala-district-map",
      "moduleFile": "kerala-district-map.min.mjs",
      "moduleUrl": "https://cdn.jsdelivr.net/gh/holmhq/cdn@63606cb711d0e1697283c7f88db1cce736396c5f/libs/ui/kerala-district-map/v-0.0.2/kerala-district-map.min.mjs",
      "specPath": "libs/ui/kerala-district-map/v-0.0.2/gallery.json",
      "specUrl": "https://cdn.jsdelivr.net/gh/holmhq/cdn@63606cb711d0e1697283c7f88db1cce736396c5f/libs/ui/kerala-district-map/v-0.0.2/gallery.json",
      "stories": [
        {
          "id": "malayalam-rainfall",
          "title": "Malayalam rainfall",
          "summary": "Malayalam labels, Baloo Chettan 2 opt-in, selected district, and an auto gradient legend.",
          "html": "<kerala-district-map\n  locale=\"ml\"\n  labels=\"id\"\n  size=\"430\"\n  tint-color=\"#0ea5e9\"\n  heading=\"കേരള മഴ\"\n  subheading=\"സാമ്പിൾ മൺസൂൺ സൂചിക\"\n  legend=\"gradient\"\n  legend-title=\"മഴ\"\n  value-suffix=\" mm\"\n  selected-district=\"ernakulam\"\n  load-google-fonts\n  data='{\"kasaragod\":{\"value\":312,\"rank\":\"#8\",\"zone\":\"North\"},\"kannur\":{\"value\":348,\"rank\":\"#5\",\"zone\":\"North\"},\"wayanad\":{\"value\":414,\"rank\":\"#1\",\"zone\":\"High range\"},\"kozhikode\":{\"value\":362,\"rank\":\"#4\",\"zone\":\"North coast\"},\"malappuram\":{\"value\":331,\"rank\":\"#7\",\"zone\":\"Central\"},\"palakkad\":{\"value\":226,\"rank\":\"#14\",\"zone\":\"Gap\"},\"thrissur\":{\"value\":284,\"rank\":\"#11\",\"zone\":\"Central\"},\"ernakulam\":{\"value\":292,\"rank\":\"#10\",\"zone\":\"Metro\"},\"idukki\":{\"value\":405,\"rank\":\"#2\",\"zone\":\"High range\"},\"kottayam\":{\"value\":338,\"rank\":\"#6\",\"zone\":\"Midland\"},\"alappuzha\":{\"value\":276,\"rank\":\"#12\",\"zone\":\"Backwaters\"},\"pathanamthitta\":{\"value\":388,\"rank\":\"#3\",\"zone\":\"Forest edge\"},\"kollam\":{\"value\":302,\"rank\":\"#9\",\"zone\":\"South coast\"},\"thiruvananthapuram\":{\"value\":252,\"rank\":\"#13\",\"zone\":\"Capital\"}}'\n></kerala-district-map>",
          "height": "820px",
          "theme": "both"
        },
        {
          "id": "english-adoption",
          "title": "English adoption map",
          "summary": "English district-name labels, fixed 0-100 domain, and a teal tint.",
          "html": "<kerala-district-map\n  labels=\"name\"\n  size=\"440\"\n  tint-color=\"#14b8a6\"\n  heading=\"Kerala adoption\"\n  subheading=\"Demo district coverage\"\n  legend=\"gradient\"\n  legend-title=\"Adoption\"\n  value-suffix=\"%\"\n  domain-min=\"0\"\n  domain-max=\"100\"\n  selected-district=\"thiruvananthapuram\"\n  data='{\"kasaragod\":{\"value\":42,\"segment\":\"Priority\"},\"kannur\":{\"value\":51,\"segment\":\"Growth\"},\"wayanad\":{\"value\":63,\"segment\":\"Stable\"},\"kozhikode\":{\"value\":68,\"segment\":\"Priority\"},\"malappuram\":{\"value\":57,\"segment\":\"Growth\"},\"palakkad\":{\"value\":39,\"segment\":\"Stable\"},\"thrissur\":{\"value\":72,\"segment\":\"Priority\"},\"ernakulam\":{\"value\":86,\"segment\":\"Growth\"},\"idukki\":{\"value\":58,\"segment\":\"Stable\"},\"kottayam\":{\"value\":74,\"segment\":\"Priority\"},\"alappuzha\":{\"value\":66,\"segment\":\"Growth\"},\"pathanamthitta\":{\"value\":61,\"segment\":\"Stable\"},\"kollam\":{\"value\":69,\"segment\":\"Priority\"},\"thiruvananthapuram\":{\"value\":83,\"segment\":\"Growth\"}}'\n></kerala-district-map>",
          "height": "840px",
          "theme": "both"
        },
        {
          "id": "matched-baloo-fonts",
          "title": "Matched Baloo fonts",
          "summary": "Use Baloo Chettan 2 for English/Latin and Malayalam text when a single visual voice is desired.",
          "html": "<kerala-district-map\n  locale=\"ml\"\n  labels=\"name\"\n  size=\"440\"\n  tint-color=\"#f97316\"\n  heading=\"Kerala rainfall\"\n  subheading=\"English and മലയാളം share Baloo Chettan 2\"\n  legend=\"gradient\"\n  legend-title=\"Rainfall\"\n  value-suffix=\" mm\"\n  font-en=\"baloo-chettan-2\"\n  font-ml=\"baloo-chettan-2\"\n  load-google-fonts\n  data='{\"kasaragod\":{\"value\":312,\"rank\":\"#8\",\"zone\":\"North\"},\"kannur\":{\"value\":348,\"rank\":\"#5\",\"zone\":\"North\"},\"wayanad\":{\"value\":414,\"rank\":\"#1\",\"zone\":\"High range\"},\"kozhikode\":{\"value\":362,\"rank\":\"#4\",\"zone\":\"North coast\"},\"malappuram\":{\"value\":331,\"rank\":\"#7\",\"zone\":\"Central\"},\"palakkad\":{\"value\":226,\"rank\":\"#14\",\"zone\":\"Gap\"},\"thrissur\":{\"value\":284,\"rank\":\"#11\",\"zone\":\"Central\"},\"ernakulam\":{\"value\":292,\"rank\":\"#10\",\"zone\":\"Metro\"},\"idukki\":{\"value\":405,\"rank\":\"#2\",\"zone\":\"High range\"},\"kottayam\":{\"value\":338,\"rank\":\"#6\",\"zone\":\"Midland\"},\"alappuzha\":{\"value\":276,\"rank\":\"#12\",\"zone\":\"Backwaters\"},\"pathanamthitta\":{\"value\":388,\"rank\":\"#3\",\"zone\":\"Forest edge\"},\"kollam\":{\"value\":302,\"rank\":\"#9\",\"zone\":\"South coast\"},\"thiruvananthapuram\":{\"value\":252,\"rank\":\"#13\",\"zone\":\"Capital\"}}'\n></kerala-district-map>",
          "height": "850px",
          "theme": "both"
        },
        {
          "id": "categorical-regions",
          "title": "Categorical regions",
          "summary": "Per-district color overrides with a slotted custom legend.",
          "html": "<kerala-district-map\n  mode=\"dark\"\n  labels=\"id\"\n  size=\"430\"\n  tint-color=\"#a855f7\"\n  empty-color=\"#1f2937\"\n  heading=\"Kerala regions\"\n  subheading=\"Color overrides by region\"\n  legend=\"off\"\n  selected-district=\"idukki\"\n  data='{\"kasaragod\":{\"value\":1,\"region\":\"North\",\"color\":\"#38bdf8\"},\"kannur\":{\"value\":1,\"region\":\"North\",\"color\":\"#38bdf8\"},\"wayanad\":{\"value\":1,\"region\":\"North\",\"color\":\"#38bdf8\"},\"kozhikode\":{\"value\":1,\"region\":\"North\",\"color\":\"#38bdf8\"},\"malappuram\":{\"value\":1,\"region\":\"North\",\"color\":\"#38bdf8\"},\"palakkad\":{\"value\":2,\"region\":\"Central\",\"color\":\"#a78bfa\"},\"thrissur\":{\"value\":2,\"region\":\"Central\",\"color\":\"#a78bfa\"},\"ernakulam\":{\"value\":2,\"region\":\"Central\",\"color\":\"#a78bfa\"},\"idukki\":{\"value\":2,\"region\":\"Central\",\"color\":\"#a78bfa\"},\"kottayam\":{\"value\":3,\"region\":\"South\",\"color\":\"#fb7185\"},\"alappuzha\":{\"value\":3,\"region\":\"South\",\"color\":\"#fb7185\"},\"pathanamthitta\":{\"value\":3,\"region\":\"South\",\"color\":\"#fb7185\"},\"kollam\":{\"value\":3,\"region\":\"South\",\"color\":\"#fb7185\"},\"thiruvananthapuram\":{\"value\":3,\"region\":\"South\",\"color\":\"#fb7185\"}}'\n>\n  <div slot=\"legend\" class=\"region-legend\">\n    <strong>Region</strong>\n    <span><i style=\"background:#38bdf8\"></i>North</span>\n    <span><i style=\"background:#a78bfa\"></i>Central</span>\n    <span><i style=\"background:#fb7185\"></i>South</span>\n  </div>\n</kerala-district-map>",
          "css": ".region-legend{display:grid;gap:6px;min-width:150px;color:inherit;font:inherit}.region-legend strong{font-size:.72rem;letter-spacing:.08em;text-transform:uppercase}.region-legend span{display:flex;align-items:center;gap:7px;font-size:.78rem;font-weight:750}.region-legend i{width:10px;height:10px;border-radius:999px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.18)}",
          "height": "820px",
          "theme": "dark"
        }
      ],
      "snippets": [
        {
          "id": "set-data",
          "title": "Set data programmatically",
          "language": "js",
          "storyId": "english-adoption",
          "code": "const map = document.querySelector('kerala-district-map');\nmap.setData({\n  ernakulam: { value: 86, segment: 'Growth' },\n  thiruvananthapuram: { value: 83, segment: 'Capital' },\n  'ഇടുക്കി': { value: 58, segment: 'High range' }\n});",
          "copyable": true
        },
        {
          "id": "listen-selection",
          "title": "Listen for district selection",
          "language": "js",
          "storyId": "malayalam-rainfall",
          "code": "document.querySelector('kerala-district-map')\n  .addEventListener('district-select', (event) => {\n    const { slug, displayName, value, data } = event.detail;\n    console.log(slug, displayName, value, data);\n  });",
          "copyable": true
        },
        {
          "id": "frosted-surfaces",
          "title": "Tune frosted overlay surfaces",
          "language": "css",
          "storyId": "malayalam-rainfall",
          "code": "kerala-district-map {\n  --kerala-district-map-overlay-background: rgba(255,255,255,.72);\n  --kerala-district-map-overlay-backdrop-filter: blur(18px) saturate(1.35);\n  --kerala-district-map-popup-background: rgba(255,255,255,.82);\n  --kerala-district-map-popup-backdrop-filter: blur(18px) saturate(1.35);\n}",
          "copyable": true
        }
      ],
      "docs": {
        "attributes": [
          {
            "name": "data",
            "type": "json",
            "default": "{}",
            "required": false,
            "description": "District-keyed data object. Keys can be id, slug, English district name, exact Malayalam name, or accepted alias.",
            "example": "data='{\"ernakulam\":{\"value\":86}}'"
          },
          {
            "name": "value-key",
            "type": "string",
            "default": "value",
            "required": false,
            "description": "Object field used as the numeric choropleth value."
          },
          {
            "name": "domain-min",
            "type": "number",
            "default": "auto",
            "required": false,
            "description": "Minimum color-scale value. Omit for automatic domain."
          },
          {
            "name": "domain-max",
            "type": "number",
            "default": "auto",
            "required": false,
            "description": "Maximum color-scale value. Omit for automatic domain."
          },
          {
            "name": "value-label",
            "type": "string",
            "default": "Value",
            "required": false,
            "description": "Label used by default legend/details text."
          },
          {
            "name": "value-suffix",
            "type": "string",
            "default": "",
            "required": false,
            "description": "Suffix appended to values in default details and legend labels.",
            "example": "value-suffix=\" mm\""
          },
          {
            "name": "mode",
            "type": "string",
            "values": "system, light, dark",
            "default": "system",
            "required": false,
            "description": "Light/dark rendering mode. System follows prefers-color-scheme."
          },
          {
            "name": "tint-color",
            "type": "css-color",
            "default": "#14b8a6",
            "required": false,
            "description": "Seed color used to derive choropleth stops and district strokes."
          },
          {
            "name": "empty-color",
            "type": "css-color",
            "default": "#e5e7eb",
            "required": false,
            "description": "Fill for districts with missing or non-numeric values."
          },
          {
            "name": "size",
            "type": "number",
            "default": "480",
            "required": false,
            "description": "Desired map width in pixels; the host still clamps to available width."
          },
          {
            "name": "labels",
            "type": "string",
            "values": "off, id, name",
            "default": "off",
            "required": false,
            "description": "SVG label mode. Name labels follow locale."
          },
          {
            "name": "locale",
            "type": "string",
            "values": "en, ml",
            "default": "en",
            "required": false,
            "description": "Display locale for names, ARIA labels, and default details. lang=\"ml\" also works."
          },
          {
            "name": "font-en",
            "type": "string",
            "values": "inter, system, baloo-chettan-2, noto-sans-malayalam, malayalam-system",
            "default": "inter",
            "required": false,
            "description": "English/Latin/default font preset. Choose baloo-chettan-2 intentionally for a matched Malayalam look."
          },
          {
            "name": "font-ml",
            "type": "string",
            "values": "baloo-chettan-2, noto-sans-malayalam, system",
            "default": "baloo-chettan-2",
            "required": false,
            "description": "Malayalam-script font preset. noto-sans-malayalam is the recommended, fully-shaped Malayalam typeface."
          },
          {
            "name": "load-google-fonts",
            "type": "boolean",
            "default": "false",
            "required": false,
            "description": "Explicitly load selected Google Fonts. Without it, fallback stacks are used."
          },
          {
            "name": "selected-district",
            "type": "string",
            "default": "",
            "required": false,
            "description": "Select/highlight a district by id, slug, English name, alias, or exact Malayalam name."
          },
          {
            "name": "hover-template",
            "type": "trusted-html",
            "default": "built-in template",
            "required": false,
            "description": "Trusted HTML template for tooltip/details. Placeholders include {id}, {code}, {name}, {displayName}, {nameEn}, {nameMl}, {slug}, {value}, {normalized}, {fields}, and data keys."
          },
          {
            "name": "heading",
            "type": "string",
            "default": "",
            "required": false,
            "description": "Built-in header title. Header overlay is hidden when heading and subheading are empty."
          },
          {
            "name": "subheading",
            "type": "string",
            "default": "",
            "required": false,
            "description": "Built-in header subtitle."
          },
          {
            "name": "header-position",
            "type": "string",
            "values": "top-left, top-right, bottom-left, bottom-right",
            "default": "top-right",
            "required": false,
            "description": "Position for built-in/slotted header overlay."
          },
          {
            "name": "legend",
            "type": "string|boolean",
            "values": "off, gradient, true",
            "default": "off",
            "required": false,
            "description": "Show or hide the auto gradient legend."
          },
          {
            "name": "legend-title",
            "type": "string",
            "default": "value-label",
            "required": false,
            "description": "Auto legend title."
          },
          {
            "name": "legend-position",
            "type": "string",
            "values": "top-left, top-right, bottom-left, bottom-right",
            "default": "bottom-left",
            "required": false,
            "description": "Position for built-in/slotted legend overlay."
          },
          {
            "name": "border-color",
            "type": "css-color|auto",
            "default": "auto",
            "required": false,
            "description": "Base district boundary color override."
          },
          {
            "name": "hover-border-color",
            "type": "css-color|auto",
            "default": "auto",
            "required": false,
            "description": "Hover/focus boundary color override."
          },
          {
            "name": "selected-border-color",
            "type": "css-color|auto",
            "default": "auto",
            "required": false,
            "description": "Selected district boundary color override."
          }
        ],
        "events": [
          {
            "name": "district-hover",
            "detail": "{ id, slug, name, displayName, nameEn, nameMl, district, value, data, normalized, source }",
            "bubbles": true,
            "composed": true,
            "when": "Pointer enters a district on hover-capable devices.",
            "description": "Signals that a district is being hovered and includes normalized district/data metadata."
          },
          {
            "name": "district-leave",
            "detail": "{ id, slug, name, displayName, nameEn, nameMl, district, value, data, normalized, source }",
            "bubbles": true,
            "composed": true,
            "when": "Pointer leaves a district.",
            "description": "Signals that district hover ended."
          },
          {
            "name": "district-select",
            "detail": "{ id, slug, name, displayName, nameEn, nameMl, district, value, data, normalized, source }",
            "bubbles": true,
            "composed": true,
            "when": "User clicks, taps, presses Enter, or presses Space on a focused district.",
            "description": "Signals selected district changes from user interaction."
          }
        ],
        "cssProperties": [
          {
            "name": "--kerala-district-map-size",
            "default": "480px",
            "description": "Map width override."
          },
          {
            "name": "--kerala-district-map-background",
            "default": "transparent",
            "description": "Component frame background."
          },
          {
            "name": "--kerala-district-map-stroke",
            "default": "tint/mode derived",
            "description": "Base district boundary stroke."
          },
          {
            "name": "--kerala-district-map-hover-stroke",
            "default": "tint/mode derived",
            "description": "Hover/focus boundary stroke."
          },
          {
            "name": "--kerala-district-map-selected-stroke",
            "default": "tint/mode derived",
            "description": "Selected boundary stroke."
          },
          {
            "name": "--kerala-district-map-empty-fill",
            "default": "#e5e7eb",
            "description": "Missing-data district fill."
          },
          {
            "name": "--kerala-district-map-font-family",
            "default": "unset",
            "description": "Override all component text fonts."
          },
          {
            "name": "--kerala-district-map-font-en-family",
            "default": "Inter/system stack",
            "description": "English/Latin/default font stack override."
          },
          {
            "name": "--kerala-district-map-font-ml-family",
            "default": "Baloo Chettan 2/Malayalam stack",
            "description": "Malayalam-script font stack override."
          },
          {
            "name": "--kerala-district-map-label-font",
            "default": "active script font",
            "description": "SVG label font override."
          },
          {
            "name": "--kerala-district-map-label-color",
            "default": "mode derived",
            "description": "SVG label fill color."
          },
          {
            "name": "--kerala-district-map-popup-background",
            "default": "mode derived translucent",
            "description": "Tooltip and mobile details background."
          },
          {
            "name": "--kerala-district-map-popup-color",
            "default": "mode derived",
            "description": "Tooltip and mobile details text color."
          },
          {
            "name": "--kerala-district-map-popup-backdrop-filter",
            "default": "blur(18px) saturate(1.35)",
            "description": "Tooltip and mobile details backdrop filter."
          },
          {
            "name": "--kerala-district-map-overlay-background",
            "default": "mode derived translucent",
            "description": "Header/legend overlay background."
          },
          {
            "name": "--kerala-district-map-overlay-backdrop-filter",
            "default": "blur(18px) saturate(1.35)",
            "description": "Header/legend overlay backdrop filter."
          },
          {
            "name": "--kerala-district-map-overlay-shadow",
            "default": "mode derived",
            "description": "Header/legend overlay shadow."
          },
          {
            "name": "--kerala-district-map-overlay-inset",
            "default": "8px",
            "description": "Inset for header/legend overlay positions."
          },
          {
            "name": "--kerala-district-map-legend-height",
            "default": "9px",
            "description": "Auto legend gradient swatch height."
          }
        ],
        "methods": [
          {
            "name": "setData",
            "signature": "setData(valuesByDistrict: object): KeralaDistrictMap",
            "description": "Replace district data and rerender."
          },
          {
            "name": "setTintColor",
            "signature": "setTintColor(color: string): KeralaDistrictMap",
            "description": "Update the tint-color attribute and rerender."
          },
          {
            "name": "setHoverTemplate",
            "signature": "setHoverTemplate(html?: string): KeralaDistrictMap",
            "description": "Set or clear the trusted hover/details template."
          },
          {
            "name": "setLabels",
            "signature": "setLabels(mode: \"off\"|\"id\"|\"name\"): KeralaDistrictMap",
            "description": "Update SVG label mode."
          },
          {
            "name": "getDistrict",
            "signature": "getDistrict(idOrSlugOrName: string|number): District|null",
            "description": "Return normalized district metadata."
          },
          {
            "name": "getDistrictData",
            "signature": "getDistrictData(idOrSlugOrName: string|number): object|null",
            "description": "Return normalized data for a district."
          },
          {
            "name": "highlightDistrict",
            "signature": "highlightDistrict(idOrSlugOrName: string|number): KeralaDistrictMap",
            "description": "Select and highlight a district."
          },
          {
            "name": "clearSelection",
            "signature": "clearSelection(): KeralaDistrictMap",
            "description": "Clear the selected district and hide mobile details."
          }
        ]
      }
    },
    {
      "category": "ui",
      "name": "mood-reactions",
      "slug": "mood-reactions",
      "tagName": "mood-reactions",
      "title": "Mood Reactions",
      "summary": "Animated 1–5 reaction picker with expressive faces, labels, and form participation.",
      "status": "experimental",
      "tags": [
        "reactions",
        "feedback",
        "emoji",
        "rating",
        "input",
        "form"
      ],
      "latestVersion": "0.0.3",
      "versionFolder": "v-0.0.3",
      "releaseSha": "e3adefc52555a2084259f7963a40f8b2952778c0",
      "libPath": "libs/ui/mood-reactions",
      "moduleFile": "mood-reactions.min.mjs",
      "moduleUrl": "https://cdn.jsdelivr.net/gh/holmhq/cdn@e3adefc52555a2084259f7963a40f8b2952778c0/libs/ui/mood-reactions/v-0.0.3/mood-reactions.min.mjs",
      "specPath": "libs/ui/mood-reactions/v-0.0.3/gallery.json",
      "specUrl": "https://cdn.jsdelivr.net/gh/holmhq/cdn@e3adefc52555a2084259f7963a40f8b2952778c0/libs/ui/mood-reactions/v-0.0.3/gallery.json",
      "stories": [
        {
          "id": "default",
          "title": "Default",
          "summary": "The standard five-reaction picker with no labels.",
          "html": "<mood-reactions></mood-reactions>",
          "height": "120px"
        },
        {
          "id": "labels",
          "title": "With labels",
          "summary": "Default reactions with captions shown below each face.",
          "html": "<mood-reactions labels></mood-reactions>",
          "height": "150px"
        },
        {
          "id": "preselected-clearable",
          "title": "Preselected & clearable",
          "summary": "Starts on the fourth reaction; clicking it again clears the selection.",
          "html": "<mood-reactions value=\"4\" labels clearable></mood-reactions>",
          "height": "150px"
        },
        {
          "id": "custom-items",
          "title": "Custom items & colors",
          "summary": "Three custom reactions, each with its own active color, via the items attribute.",
          "html": "<mood-reactions labels items='[{\"face\":\"sad\",\"value\":\"bug\",\"label\":\"Bug\",\"active\":\"#fb7185\"},{\"face\":\"ok\",\"value\":\"meh\",\"label\":\"Meh\",\"active\":\"#facc15\"},{\"face\":\"happy\",\"value\":\"ship\",\"label\":\"Ship\",\"active\":\"#34d399\"}]'></mood-reactions>",
          "height": "150px"
        }
      ],
      "snippets": [
        {
          "id": "change-listener",
          "title": "Read the selected reaction",
          "language": "js",
          "storyId": "labels",
          "code": "const reactions = document.querySelector('mood-reactions');\nreactions.addEventListener('change', (e) => {\n  console.log('Selected:', e.detail.value, e.detail.item?.label);\n});"
        },
        {
          "id": "form",
          "title": "Use inside a form",
          "language": "html",
          "code": "<form>\n  <label>How was your experience?</label>\n  <mood-reactions name=\"experience\" required labels></mood-reactions>\n  <button type=\"submit\">Submit</button>\n</form>"
        }
      ],
      "docs": {
        "attributes": [
          {
            "name": "value",
            "type": "string",
            "default": "\"\" (unset)",
            "required": false,
            "description": "Selected reaction value. Default reactions use values 1 through 5.",
            "example": "value=\"4\""
          },
          {
            "name": "items",
            "type": "json",
            "default": "default 5-item set",
            "required": false,
            "description": "Custom reaction list. JSON array of { face, value, label, ... } or CSV `face:value:label`. Faces: angry, sad, ok, good, happy."
          },
          {
            "name": "clearable",
            "type": "boolean",
            "default": "false",
            "required": false,
            "description": "When set, clicking the current selection clears the value."
          },
          {
            "name": "labels",
            "type": "boolean",
            "default": "false",
            "required": false,
            "description": "Show captions for each reaction."
          },
          {
            "name": "label-position",
            "type": "string",
            "values": "bottom, top, none",
            "default": "bottom",
            "required": false,
            "description": "Where labels appear relative to the faces."
          },
          {
            "name": "size",
            "type": "string",
            "default": "40",
            "required": false,
            "description": "Face size in pixels. Overridden by scale when both are present."
          },
          {
            "name": "scale",
            "type": "number",
            "default": "1",
            "required": false,
            "description": "Scale multiplier for face size (0.4–4)."
          },
          {
            "name": "gap",
            "type": "string",
            "default": "20px",
            "required": false,
            "description": "Space between reactions; bare numbers are treated as pixels."
          },
          {
            "name": "legend",
            "type": "string",
            "default": "Feedback reaction",
            "required": false,
            "description": "Accessible label announced for the radiogroup."
          },
          {
            "name": "name",
            "type": "string",
            "default": "auto-generated",
            "required": false,
            "description": "Form field name; enables form participation."
          },
          {
            "name": "required",
            "type": "boolean",
            "default": "false",
            "required": false,
            "description": "Requires a selection before the form can submit."
          },
          {
            "name": "disabled",
            "type": "boolean",
            "default": "false",
            "required": false,
            "description": "Disables interaction and dims the control."
          },
          {
            "name": "readonly",
            "type": "boolean",
            "default": "false",
            "required": false,
            "description": "Prevents user selection but still submits with the form."
          }
        ],
        "events": [
          {
            "name": "input",
            "detail": "{ value: string, index: number, item: object | null }",
            "bubbles": true,
            "composed": true,
            "when": "On user selection or interactive change.",
            "description": "Fires during interaction for real-time feedback."
          },
          {
            "name": "change",
            "detail": "{ value: string, index: number, item: object | null }",
            "bubbles": true,
            "composed": true,
            "when": "On user selection or interactive change.",
            "description": "Fires when the user completes a selection."
          }
        ],
        "cssProperties": [
          {
            "name": "--mood-reactions-normal",
            "default": "#414052",
            "description": "Face color when not selected."
          },
          {
            "name": "--mood-reactions-active",
            "default": "#f8da69",
            "description": "Face color when selected."
          },
          {
            "name": "--mood-reactions-active-mouth",
            "default": "#f05136",
            "description": "Mouth color on the selected face."
          },
          {
            "name": "--mood-reactions-active-tear",
            "default": "#76b5e7",
            "description": "Tear-drop color on the selected sad face."
          },
          {
            "name": "--mood-reactions-focus",
            "default": "#8ab4ff",
            "description": "Focus-ring outline color."
          },
          {
            "name": "--mood-reactions-caption",
            "default": "currentColor",
            "description": "Label/caption text color."
          },
          {
            "name": "--mood-reactions-caption-size",
            "default": "0.72rem",
            "description": "Label font size."
          },
          {
            "name": "--mood-reactions-gap",
            "default": "20px",
            "description": "Space between reactions (set via the gap attribute)."
          },
          {
            "name": "--mood-reactions-opacity-disabled",
            "default": "0.52",
            "description": "Opacity when disabled."
          }
        ],
        "methods": [
          {
            "name": "select",
            "signature": "select(valueOrIndex: string | number, options?: { emit?: boolean }): void",
            "description": "Select a reaction by value or index; emit dispatches input/change."
          },
          {
            "name": "reset",
            "signature": "reset(options?: { emit?: boolean }): void",
            "description": "Clear the current selection."
          },
          {
            "name": "configure",
            "signature": "configure(options?: object): MoodReactions",
            "description": "Apply options programmatically; returns the element for chaining."
          }
        ]
      }
    },
    {
      "category": "ui",
      "name": "poster-charts",
      "slug": "poster-charts",
      "tagName": "poster-chart",
      "title": "Poster Charts",
      "summary": "Bold data-poster SVG charts — eight infographic archetypes for chart-first storytelling.",
      "status": "stable",
      "tags": [
        "charts",
        "data",
        "visualization",
        "infographic",
        "svg"
      ],
      "latestVersion": "0.0.4",
      "versionFolder": "v-0.0.4",
      "releaseSha": "cb97a86fb6c0bdc3e7b56684a913041246c7972e",
      "libPath": "libs/ui/poster-charts",
      "moduleFile": "poster-charts.min.mjs",
      "moduleUrl": "https://cdn.jsdelivr.net/gh/holmhq/cdn@cb97a86fb6c0bdc3e7b56684a913041246c7972e/libs/ui/poster-charts/v-0.0.4/poster-charts.min.mjs",
      "specPath": "libs/ui/poster-charts/v-0.0.4/gallery.json",
      "specUrl": "https://cdn.jsdelivr.net/gh/holmhq/cdn@cb97a86fb6c0bdc3e7b56684a913041246c7972e/libs/ui/poster-charts/v-0.0.4/gallery.json",
      "stories": [
        {
          "id": "stat",
          "title": "Stat with delta",
          "summary": "Big-number callout with a directional delta pill.",
          "html": "<poster-chart type=\"stat\" label=\"Inherited arrears\" accent=\"#E24536\" source=\"Table 2.7\" data='{\"prefix\":\"₹\",\"value\":48733,\"suffix\":\" cr\",\"sub\":\"Inherited arrears stock\",\"context\":\"as on 31 Mar 2026\",\"delta\":\"+12%\",\"deltaDir\":\"up\"}'></poster-chart>",
          "height": "360px"
        },
        {
          "id": "gauge",
          "title": "Gauge",
          "summary": "Single value rendered as an arc ring.",
          "html": "<poster-chart type=\"gauge\" label=\"Own-tax realization\" accent=\"#0F5132\" source=\"Table 1.1\" data='{\"value\":92,\"max\":100,\"unit\":\"%\",\"label\":\"of target\",\"target\":100}'></poster-chart>",
          "height": "360px"
        },
        {
          "id": "bars",
          "title": "Grouped bars",
          "summary": "Paired vertical bars with a legend across two groups.",
          "html": "<poster-chart type=\"bars\" label=\"Estimates keep missing\" accent=\"#F2A73B\" source=\"Table 3.1\" data='{\"unit\":\"%\",\"max\":4.4,\"legend\":[{\"label\":\"BE\"},{\"label\":\"Actual\"}],\"groups\":[{\"label\":\"Rev deficit\",\"bars\":[{\"label\":\"BE\",\"value\":1.90},{\"label\":\"Act\",\"value\":2.58}]},{\"label\":\"Fis deficit\",\"bars\":[{\"label\":\"BE\",\"value\":3.16},{\"label\":\"Act\",\"value\":3.78}]}]}'></poster-chart>",
          "height": "380px"
        },
        {
          "id": "slope",
          "title": "Slope lines",
          "summary": "Before/after slope lines across multiple series.",
          "html": "<poster-chart type=\"slope\" label=\"What gets squeezed\" accent=\"#E24536\" source=\"Tables 6.2/6.3\" data='{\"unit\":\"%\",\"fromLabel\":\"2017-18\",\"toLabel\":\"2025-26\",\"lines\":[{\"label\":\"Plan share\",\"from\":23.18,\"to\":17.55},{\"label\":\"Social services\",\"from\":53.78,\"to\":30.68},{\"label\":\"SC/ST/OBC\",\"from\":9.24,\"to\":3.85}]}'></poster-chart>",
          "height": "380px"
        },
        {
          "id": "donut",
          "title": "Donut with center stat",
          "summary": "Share-of-whole donut with a center value and legend.",
          "html": "<poster-chart type=\"donut\" label=\"Arrears concentration\" accent=\"#3E55C6\" source=\"Table 5.10\" data='{\"unit\":\"%\",\"centerValue\":69,\"centerUnit\":\"%\",\"centerLabel\":\"top 3 utilities\",\"segments\":[{\"label\":\"KSRTC+KWA+KSEBL\",\"value\":69},{\"label\":\"All others\",\"value\":31,\"color\":\"#E3DAC6\"}]}'></poster-chart>",
          "height": "380px"
        },
        {
          "id": "heatstrip",
          "title": "Heatstrip",
          "summary": "Twelve state-colored cells with a legend.",
          "html": "<poster-chart type=\"heatstrip\" label=\"Treasury closed negative 10 of 12 months\" accent=\"#E24536\" source=\"Table 2.2\" data='{\"columns\":12,\"legend\":[{\"state\":\"neg\",\"label\":\"Negative close\",\"color\":\"#E24536\"},{\"state\":\"pos\",\"label\":\"Positive\",\"color\":\"#1FA463\"}],\"cells\":[{\"state\":\"neg\",\"label\":\"A\"},{\"state\":\"neg\",\"label\":\"M\"},{\"state\":\"neg\",\"label\":\"J\"},{\"state\":\"neg\",\"label\":\"J\"},{\"state\":\"pos\",\"label\":\"A\"},{\"state\":\"neg\",\"label\":\"S\"},{\"state\":\"neg\",\"label\":\"O\"},{\"state\":\"neg\",\"label\":\"N\"},{\"state\":\"neg\",\"label\":\"D\"},{\"state\":\"neg\",\"label\":\"J\"},{\"state\":\"pos\",\"label\":\"F\"},{\"state\":\"neg\",\"label\":\"M\"}]}'></poster-chart>",
          "height": "360px"
        },
        {
          "id": "dark-theme",
          "title": "Slate (dark) theme",
          "summary": "Gauge rendered with the dark slate palette.",
          "html": "<poster-chart type=\"gauge\" theme=\"slate\" label=\"Committed exp\" source=\"Table 1.1\" data='{\"value\":77.6,\"unit\":\"%\",\"label\":\"of revenue\"}'></poster-chart>",
          "height": "360px",
          "theme": "dark"
        },
        {
          "id": "bare",
          "title": "Bare mode",
          "summary": "Stacked composition without the plate frame or header strip.",
          "html": "<poster-chart type=\"stacked\" bare label=\"₹100 of revenue\" data='{\"unit\":\"\",\"segments\":[{\"label\":\"Committed (non-interest)\",\"value\":56.7},{\"label\":\"Interest\",\"value\":20.9},{\"label\":\"Left for development\",\"value\":22.4,\"color\":\"#1FA463\"}]}'></poster-chart>",
          "height": "240px"
        }
      ],
      "snippets": [
        {
          "id": "set-data-property",
          "title": "Set data via the property",
          "language": "js",
          "storyId": "stat",
          "code": "const chart = document.querySelector('poster-chart');\nchart.data = { prefix: '₹', value: 48733, suffix: ' cr', delta: '+12%', deltaDir: 'up' };"
        }
      ],
      "docs": {
        "attributes": [
          {
            "name": "type",
            "type": "string",
            "values": "stat, gauge, bars, slope, heatstrip, donut, stacked, capacity",
            "default": "stat",
            "required": false,
            "description": "Chart archetype to render."
          },
          {
            "name": "theme",
            "type": "string",
            "values": "kerala, slate, ink",
            "default": "kerala",
            "required": false,
            "description": "Color palette preset — kerala (light), slate (dark), ink (monochrome)."
          },
          {
            "name": "accent",
            "type": "string",
            "default": "from theme",
            "required": false,
            "description": "Override accent color for the header strip and default series fills."
          },
          {
            "name": "label",
            "type": "string",
            "required": false,
            "description": "Header strip title (rendered uppercase)."
          },
          {
            "name": "source",
            "type": "string",
            "required": false,
            "description": "Small source-attribution chip in the header strip."
          },
          {
            "name": "caption",
            "type": "string",
            "required": false,
            "description": "Supporting text rendered below the chart."
          },
          {
            "name": "data",
            "type": "json",
            "default": "{}",
            "required": false,
            "description": "Chart configuration and data; shape depends on type. Can also be set via the .data property."
          },
          {
            "name": "bare",
            "type": "boolean",
            "default": "false",
            "required": false,
            "description": "Render without the plate frame and header strip."
          },
          {
            "name": "animate",
            "type": "string",
            "values": "off",
            "default": "on",
            "required": false,
            "description": "Set to off to disable motion; otherwise respects prefers-reduced-motion."
          }
        ],
        "events": [],
        "cssProperties": [
          {
            "name": "--poster-ink",
            "default": "#15211B",
            "description": "Primary text color."
          },
          {
            "name": "--poster-ink-soft",
            "default": "#5A6B61",
            "description": "Secondary/muted text color."
          },
          {
            "name": "--poster-surface",
            "default": "#FFFFFF",
            "description": "Main background color."
          },
          {
            "name": "--poster-surface-sunk",
            "default": "#F1EADA",
            "description": "Sunken background for legend and source chip."
          },
          {
            "name": "--poster-line",
            "default": "#E3DAC6",
            "description": "Border and divider color."
          },
          {
            "name": "--poster-good",
            "default": "#1FA463",
            "description": "Positive/success state color."
          },
          {
            "name": "--poster-bad",
            "default": "#E24536",
            "description": "Negative/error state color."
          },
          {
            "name": "--poster-warn",
            "default": "#F2A73B",
            "description": "Warning state color."
          },
          {
            "name": "--poster-accent",
            "default": "#0F5132",
            "description": "Header strip accent and default fill color."
          },
          {
            "name": "--poster-display-font",
            "default": "inherits --poster-font",
            "description": "Font family for the large numerals."
          }
        ]
      }
    },
    {
      "category": "ui",
      "name": "pulse-faces",
      "slug": "pulse-faces",
      "tagName": "pulse-faces",
      "title": "Pulse Faces",
      "summary": "Animated SVG sentiment faces with selection, labels, sizing, and theming.",
      "status": "experimental",
      "tags": [
        "sentiment",
        "faces",
        "feedback",
        "rating",
        "input",
        "form"
      ],
      "latestVersion": "0.0.4",
      "versionFolder": "v-0.0.4",
      "releaseSha": "eae0dfaae86fdc051a2fd886e01c45a9aa9550bd",
      "libPath": "libs/ui/pulse-faces",
      "moduleFile": "pulse-faces.min.mjs",
      "moduleUrl": "https://cdn.jsdelivr.net/gh/holmhq/cdn@eae0dfaae86fdc051a2fd886e01c45a9aa9550bd/libs/ui/pulse-faces/v-0.0.4/pulse-faces.min.mjs",
      "specPath": "libs/ui/pulse-faces/v-0.0.4/gallery.json",
      "specUrl": "https://cdn.jsdelivr.net/gh/holmhq/cdn@eae0dfaae86fdc051a2fd886e01c45a9aa9550bd/libs/ui/pulse-faces/v-0.0.4/gallery.json",
      "stories": [
        {
          "id": "default",
          "title": "Default display",
          "summary": "The default four-face set rendered as a non-interactive display.",
          "html": "<pulse-faces></pulse-faces>",
          "height": "140px"
        },
        {
          "id": "single-face",
          "title": "Single face",
          "summary": "Display one face on its own with the face attribute, tinted via a custom background color.",
          "html": "<pulse-faces face=\"happy\" color=\"#a855f7\" size=\"96\"></pulse-faces>",
          "height": "150px"
        },
        {
          "id": "labels",
          "title": "With labels",
          "summary": "Faces with captions shown below, at a larger size.",
          "html": "<pulse-faces labels size=\"80\"></pulse-faces>",
          "height": "170px"
        },
        {
          "id": "selectable",
          "title": "Selectable picker",
          "summary": "Clickable, keyboard-navigable picker with custom faces and a preselected value.",
          "html": "<pulse-faces selectable labels value=\"fine\" faces=\"sad:1:Rough,neutral:2:Okay,fine:3:Fine,happy:4:Great\"></pulse-faces>",
          "height": "170px"
        },
        {
          "id": "vertical",
          "title": "Vertical with custom colors",
          "summary": "Vertical orientation, labels above, with overridden face colors.",
          "html": "<pulse-faces selectable labels label-position=\"top\" orientation=\"vertical\" size=\"64px\" gap=\"12px\" style=\"--pulse-faces-happy:#34d399;--pulse-faces-sad:#fb7185;\"></pulse-faces>",
          "height": "440px"
        }
      ],
      "snippets": [
        {
          "id": "change-listener",
          "title": "Read the selected face",
          "language": "js",
          "storyId": "selectable",
          "code": "const faces = document.querySelector('pulse-faces[selectable]');\nfaces.addEventListener('change', (e) => {\n  console.log('Selected:', e.detail.value, e.detail.item);\n});"
        },
        {
          "id": "form",
          "title": "Required form field",
          "language": "html",
          "code": "<form>\n  <label>How are you feeling?</label>\n  <pulse-faces selectable labels name=\"feeling\" required></pulse-faces>\n  <button type=\"submit\">Submit</button>\n</form>"
        }
      ],
      "docs": {
        "attributes": [
          {
            "name": "faces",
            "type": "json",
            "default": "default 4-face set",
            "required": false,
            "description": "Faces to render. JSON array or CSV `face:value:label:color`. Faces: sad, neutral, fine, happy.",
            "example": "faces=\"sad:1:Rough,happy:4:Great\""
          },
          {
            "name": "face",
            "type": "string",
            "values": "sad, neutral, fine, happy",
            "required": false,
            "description": "Display a single face. Shorthand for a one-item face set; ignored when faces is also set.",
            "example": "face=\"happy\""
          },
          {
            "name": "color",
            "type": "string",
            "required": false,
            "description": "Background (disc) color for the single face set via the face attribute.",
            "example": "color=\"#a855f7\""
          },
          {
            "name": "value",
            "type": "string",
            "default": "\"\"",
            "required": false,
            "description": "Selected value when selectable/interactive is set."
          },
          {
            "name": "selectable",
            "type": "boolean",
            "default": "false",
            "required": false,
            "description": "Enable click and keyboard selection with input/change events."
          },
          {
            "name": "interactive",
            "type": "boolean",
            "default": "false",
            "required": false,
            "description": "Alias for selectable."
          },
          {
            "name": "labels",
            "type": "boolean",
            "default": "false",
            "required": false,
            "description": "Show caption text for each face."
          },
          {
            "name": "label-position",
            "type": "string",
            "values": "bottom, top, none",
            "default": "bottom",
            "required": false,
            "description": "Where captions appear relative to faces."
          },
          {
            "name": "orientation",
            "type": "string",
            "values": "horizontal, vertical",
            "default": "horizontal",
            "required": false,
            "description": "Layout direction of the faces."
          },
          {
            "name": "size",
            "type": "string",
            "default": "72px",
            "required": false,
            "description": "Icon size per face; bare numbers are treated as pixels."
          },
          {
            "name": "gap",
            "type": "string",
            "default": "16px",
            "required": false,
            "description": "Space between adjacent faces."
          },
          {
            "name": "paused",
            "type": "boolean",
            "default": "false",
            "required": false,
            "description": "Freeze all face animations."
          },
          {
            "name": "animated",
            "type": "string",
            "values": "false",
            "default": "on",
            "required": false,
            "description": "Set to \"false\" to disable animations (alternative to paused)."
          },
          {
            "name": "disabled",
            "type": "boolean",
            "default": "false",
            "required": false,
            "description": "Disable interaction and dim the control."
          },
          {
            "name": "required",
            "type": "boolean",
            "default": "false",
            "required": false,
            "description": "Mark as required for form validation."
          },
          {
            "name": "name",
            "type": "string",
            "default": "Emotion faces",
            "required": false,
            "description": "Form field name and accessible label."
          }
        ],
        "events": [
          {
            "name": "input",
            "detail": "{ value: string, index: number, item: object | null }",
            "bubbles": true,
            "composed": true,
            "when": "On click or keyboard selection while selectable.",
            "description": "Real-time user-selection feedback."
          },
          {
            "name": "change",
            "detail": "{ value: string, index: number, item: object | null }",
            "bubbles": true,
            "composed": true,
            "when": "On click or completed keyboard selection while selectable.",
            "description": "Selection confirmation. Fires only when the value actually changes."
          }
        ],
        "cssProperties": [
          {
            "name": "--pulse-faces-size",
            "default": "72px",
            "description": "Icon size per face."
          },
          {
            "name": "--pulse-faces-gap",
            "default": "16px",
            "description": "Space between faces."
          },
          {
            "name": "--pulse-faces-ink",
            "default": "#2C0E0F",
            "description": "Color of eyes, mouth, and features."
          },
          {
            "name": "--pulse-faces-sad",
            "default": "#E23D18",
            "description": "Sad face circle color."
          },
          {
            "name": "--pulse-faces-neutral",
            "default": "#F9AC1B",
            "description": "Neutral face circle color."
          },
          {
            "name": "--pulse-faces-fine",
            "default": "#1988E3",
            "description": "Fine face circle color."
          },
          {
            "name": "--pulse-faces-happy",
            "default": "#248C37",
            "description": "Happy face circle color."
          },
          {
            "name": "--pulse-faces-focus",
            "default": "#8ab4ff",
            "description": "Keyboard focus-ring color."
          },
          {
            "name": "--pulse-faces-caption",
            "default": "currentColor",
            "description": "Label text color."
          },
          {
            "name": "--pulse-faces-caption-font",
            "default": "Inter, sans-serif",
            "description": "Font family for caption labels."
          },
          {
            "name": "--pulse-faces-disabled-opacity",
            "default": "0.55",
            "description": "Opacity when disabled."
          }
        ],
        "methods": [
          {
            "name": "select",
            "signature": "select(valueOrIndex: string | number, options?: { emit?: boolean }): void",
            "description": "Select a face by value or index; emit dispatches input/change."
          },
          {
            "name": "reset",
            "signature": "reset(options?: { emit?: boolean }): void",
            "description": "Clear the current selection."
          },
          {
            "name": "configure",
            "signature": "configure(options: object): PulseFaces",
            "description": "Apply options programmatically; returns the element for chaining."
          }
        ]
      }
    },
    {
      "category": "ui",
      "name": "spin-maze",
      "slug": "spin-maze",
      "tagName": "spin-maze",
      "title": "Spin Maze",
      "summary": "Rotating maze puzzle game as an iframe-isolated custom element.",
      "status": "stable",
      "tags": [
        "game",
        "puzzle",
        "maze",
        "interactive"
      ],
      "latestVersion": "0.0.3",
      "versionFolder": "v-0.0.3",
      "releaseSha": "691f71a1760e5a18bf9ff0f10f902703055c1ec7",
      "libPath": "libs/ui/spin-maze",
      "moduleFile": "spin-maze.min.mjs",
      "moduleUrl": "https://cdn.jsdelivr.net/gh/holmhq/cdn@691f71a1760e5a18bf9ff0f10f902703055c1ec7/libs/ui/spin-maze/v-0.0.3/spin-maze.min.mjs",
      "specPath": "libs/ui/spin-maze/v-0.0.3/gallery.json",
      "specUrl": "https://cdn.jsdelivr.net/gh/holmhq/cdn@691f71a1760e5a18bf9ff0f10f902703055c1ec7/libs/ui/spin-maze/v-0.0.3/gallery.json",
      "stories": [
        {
          "id": "classic",
          "title": "Classic",
          "summary": "Default classic theme with the built-in level set.",
          "html": "<spin-maze height=\"560px\"></spin-maze>",
          "height": "600px"
        },
        {
          "id": "night",
          "title": "Night theme",
          "summary": "Dark night palette.",
          "html": "<spin-maze theme=\"night\" height=\"560px\"></spin-maze>",
          "height": "600px",
          "theme": "dark"
        },
        {
          "id": "custom-levels",
          "title": "Custom levels",
          "summary": "Inject a custom level via the levels JSON attribute on the candy theme.",
          "html": "<spin-maze theme=\"candy\" height=\"560px\" levels='[{\"id\":\"tiny\",\"blurb\":\"Demo\",\"instruction\":\"Drag the cub to the star\",\"map\":\"*=.=.\\n    !\\n. . .\\n    !\\n@=.=.\"}]'></spin-maze>",
          "height": "600px"
        },
        {
          "id": "no-picker",
          "title": "Hidden level picker",
          "summary": "Forest theme with the level picker button hidden.",
          "html": "<spin-maze theme=\"forest\" height=\"560px\" show-levels=\"false\"></spin-maze>",
          "height": "600px"
        }
      ],
      "snippets": [
        {
          "id": "complete-listener",
          "title": "React to level completion",
          "language": "js",
          "storyId": "classic",
          "code": "const maze = document.querySelector('spin-maze');\nmaze.addEventListener('complete', (e) => {\n  console.log('Completed level:', e.detail.level);\n  console.log('All completed:', e.detail.completedLevels);\n});"
        }
      ],
      "docs": {
        "attributes": [
          {
            "name": "height",
            "type": "string",
            "default": "720px",
            "required": false,
            "description": "Game/iframe height as any CSS length.",
            "example": "height=\"80vh\""
          },
          {
            "name": "theme",
            "type": "string",
            "values": "classic, night, candy, forest",
            "default": "classic",
            "required": false,
            "description": "Color theme palette."
          },
          {
            "name": "level",
            "type": "string",
            "default": "first level",
            "required": false,
            "description": "Initial level id to load."
          },
          {
            "name": "storage-key",
            "type": "string",
            "default": "spin-maze:{uid}",
            "required": false,
            "description": "localStorage namespace for current and completed levels."
          },
          {
            "name": "show-levels",
            "type": "boolean",
            "values": "true, false",
            "default": "true",
            "required": false,
            "description": "Show or hide the level picker button."
          },
          {
            "name": "button-label",
            "type": "string",
            "default": "Levels",
            "required": false,
            "description": "Level picker button text."
          },
          {
            "name": "next-label",
            "type": "string",
            "default": "Next level",
            "required": false,
            "description": "Next-level button text."
          },
          {
            "name": "levels",
            "type": "json",
            "default": "built-in set",
            "required": false,
            "description": "Custom level array of { id, blurb, instruction, map } objects."
          }
        ],
        "events": [
          {
            "name": "complete",
            "detail": "{ level: string, completedLevels: string[] }",
            "bubbles": true,
            "composed": true,
            "when": "The player reaches the goal/star position.",
            "description": "Signals level completion with the current level id and all completed level ids."
          }
        ],
        "cssProperties": [
          {
            "name": "--spin-maze-height",
            "default": "720px",
            "description": "Game height."
          },
          {
            "name": "--spin-maze-radius",
            "default": "18px",
            "description": "Wrapper border radius."
          },
          {
            "name": "--spin-maze-shadow",
            "default": "0 18px 60px rgba(0,0,0,0.16)",
            "description": "Wrapper box shadow."
          },
          {
            "name": "--spin-maze-background",
            "default": "#ffffff",
            "description": "Game page background."
          },
          {
            "name": "--spin-maze-text",
            "default": "#555555",
            "description": "UI text color."
          },
          {
            "name": "--spin-maze-button-bg",
            "default": "#bbbbbb",
            "description": "Button background."
          },
          {
            "name": "--spin-maze-button-hover",
            "default": "#0099ff",
            "description": "Active/hover button background."
          },
          {
            "name": "--spin-maze-cub",
            "default": "hsla(330, 100%, 40%, 1)",
            "description": "Player (cub) color."
          },
          {
            "name": "--spin-maze-star",
            "default": "hsla(50, 100%, 50%, 1)",
            "description": "Goal/star color."
          },
          {
            "name": "--spin-maze-free",
            "default": "hsla(200, 80%, 50%, 0.7)",
            "description": "Rotating/free maze segment color."
          },
          {
            "name": "--spin-maze-fixed",
            "default": "hsla(30, 100%, 40%, 0.6)",
            "description": "Fixed maze segment color."
          }
        ],
        "methods": [
          {
            "name": "configure",
            "signature": "configure(options?: object): SpinMaze",
            "description": "Apply options (theme, height, level, levels, labels, showLevels) programmatically; returns the element for chaining."
          },
          {
            "name": "reload",
            "signature": "reload(): void",
            "description": "Rebuild the iframe game."
          }
        ]
      }
    }
  ]
}
