[
  {
    "path": ".gitignore",
    "content": "node_modules\ndist\nsrc/assets\nsrc/template.html\nsrc/changelog.html\nsrc/changelog.json\n.env\n.DS_Store\n**/.DS_Store"
  },
  {
    "path": ".nvmrc",
    "content": "22\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) 2026 Abhimanyu Rana @planetabhi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, and/or publish copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "```ascii\n   ____       ___        _____       __  ___    \n  / __/__ _  / _ \\___   / ___/__ _  /  |/  /__ _\n _\\ \\/ _ `/ / , _/ -_) / (_ / _ `/ / /|_/ / _ `/\n/___/\\_,_/ /_/|_|\\__/  \\___/\\_,_/ /_/  /_/\\_,_/ \n   _______     ___    ________     __  ___      \n  / __/ _ |   / _ \\  / ___/ _ |   /  |/  /      \n _\\ \\/ __ |  / , _/ / (_ / __ |  / /|_/ /       \n/___/_/ |_| /_/|_|  \\___/_/ |_| /_/  /_/        \n```\n\n## Sargam Icons\nA collection of 1200+ handcrafted open-source icons for your exquisite designs.\n\n[[sargamicons.com]](https://sargamicons.com/) ♪♪♪ ヽ(ˇ∀ˇ )ゞ\n\n- Built using SVG stroke, providing maximum flexibility on styling.\n- Optimized vector paths and SVGs for better performance.\n- Request a new icon by creating an issue.\n\n\n[![jsDelivr downloads badge](https://data.jsdelivr.com/v1/package/npm/sargam-icons/badge)](https://www.jsdelivr.com/package/npm/sargam-icons)\n"
  },
  {
    "path": "functions/_middleware.ts",
    "content": "/**\n * Cloudflare Pages Function middleware for Markdown content negotiation.\n *\n * When a request includes `Accept: text/markdown`, this middleware fetches\n * the HTML response from the origin and converts it to a simplified Markdown\n * representation. Browsers and other clients still receive normal HTML.\n *\n * References:\n *   - RFC 8288 (Web Linking)\n *   - https://developers.cloudflare.com/fundamentals/reference/markdown-for-agents/\n */\n\n// Cloudflare Pages Function types (minimal inline definitions so the file\n// works without @cloudflare/workers-types installed locally).\ninterface EventContext<E = unknown> {\n  request: Request;\n  next: () => Promise<Response>;\n  env: E;\n}\ntype PagesFunction<E = unknown> = (ctx: EventContext<E>) => Promise<Response> | Response;\n\ninterface Env {}\n\nfunction acceptsMarkdown(request: Request): boolean {\n  const accept = request.headers.get(\"Accept\") || \"\";\n  return accept.includes(\"text/markdown\");\n}\n\n/** Decode common HTML entities. */\nfunction decodeEntities(text: string): string {\n  return text\n    .replace(/&amp;/g, \"&\")\n    .replace(/&lt;/g, \"<\")\n    .replace(/&gt;/g, \">\")\n    .replace(/&quot;/g, '\"')\n    .replace(/&#39;/g, \"'\")\n    .replace(/&nbsp;/g, \" \")\n    .replace(/&#(\\d+);/g, (_, n) => String.fromCharCode(Number(n)));\n}\n\n/** Decode entities, collapse whitespace, and JSON-quote for safe front-matter. */\nfunction sanitizeMeta(raw: string): string {\n  const cleaned = decodeEntities(raw).replace(/\\s+/g, \" \").trim();\n  return JSON.stringify(cleaned);\n}\n\n/** Very small HTML-to-Markdown converter for static content pages. */\nfunction htmlToMarkdown(html: string): string {\n  let md = html;\n\n  // Extract <title>\n  const titleMatch = md.match(/<title[^>]*>(.*?)<\\/title>/is);\n  const title = titleMatch ? titleMatch[1].trim() : \"\";\n\n  // Extract <meta name=\"description\">\n  const descMatch = md.match(\n    /<meta\\s+name=[\"']description[\"']\\s+content=[\"'](.*?)[\"']/is,\n  );\n  const description = descMatch ? descMatch[1].trim() : \"\";\n\n  // Strip everything outside <body>\n  const bodyMatch = md.match(/<body[^>]*>([\\s\\S]*)<\\/body>/i);\n  md = bodyMatch ? bodyMatch[1] : md;\n\n  // Remove <script>, <style>, <nav>, <svg>, <noscript> blocks\n  md = md.replace(/<(script|style|nav|svg|noscript)\\b[\\s\\S]*?<\\/\\1>/gi, \"\");\n\n  // Convert headings\n  md = md.replace(/<h1[^>]*>([\\s\\S]*?)<\\/h1>/gi, \"\\n# $1\\n\");\n  md = md.replace(/<h2[^>]*>([\\s\\S]*?)<\\/h2>/gi, \"\\n## $1\\n\");\n  md = md.replace(/<h3[^>]*>([\\s\\S]*?)<\\/h3>/gi, \"\\n### $1\\n\");\n  md = md.replace(/<h4[^>]*>([\\s\\S]*?)<\\/h4>/gi, \"\\n#### $1\\n\");\n\n  // Convert links\n  md = md.replace(/<a\\s+[^>]*href=[\"']([^\"']*)[\"'][^>]*>([\\s\\S]*?)<\\/a>/gi, \"[$2]($1)\");\n\n  // Convert paragraphs and divs to line breaks\n  md = md.replace(/<\\/?(p|div|section|article|main|header|footer)\\b[^>]*>/gi, \"\\n\");\n\n  // Convert <br> tags\n  md = md.replace(/<br\\s*\\/?>/gi, \"\\n\");\n\n  // Convert <li>\n  md = md.replace(/<li[^>]*>([\\s\\S]*?)<\\/li>/gi, \"- $1\\n\");\n\n  // Convert <strong>/<b> and <em>/<i>\n  md = md.replace(/<(strong|b)\\b[^>]*>([\\s\\S]*?)<\\/\\1>/gi, \"**$2**\");\n  md = md.replace(/<(em|i)\\b[^>]*>([\\s\\S]*?)<\\/\\1>/gi, \"*$2*\");\n\n  // Convert <code>\n  md = md.replace(/<code[^>]*>([\\s\\S]*?)<\\/code>/gi, \"`$1`\");\n\n  // Strip all remaining HTML tags\n  md = md.replace(/<[^>]+>/g, \"\");\n\n  // Decode common HTML entities\n  md = decodeEntities(md);\n\n  // Collapse excessive blank lines\n  md = md.replace(/\\n{3,}/g, \"\\n\\n\").trim();\n\n  // Prepend front-matter-style header\n  const header = [\n    \"---\",\n    title ? `title: ${sanitizeMeta(title)}` : null,\n    description ? `description: ${sanitizeMeta(description)}` : null,\n    \"---\",\n  ]\n    .filter(Boolean)\n    .join(\"\\n\");\n\n  return `${header}\\n\\n${md}\\n`;\n}\n\n/** Rough token estimate: ~1 token per 4 characters for English text. */\nfunction estimateTokens(text: string): number {\n  return Math.ceil(text.length / 4);\n}\n\nexport const onRequest: PagesFunction<Env> = async (context) => {\n  if (!acceptsMarkdown(context.request)) {\n    return context.next();\n  }\n\n  // Fetch the original HTML response from the origin\n  const response = await context.next();\n\n  const contentType = response.headers.get(\"Content-Type\") || \"\";\n  if (!contentType.includes(\"text/html\")) {\n    return response;\n  }\n\n  const html = await response.text();\n  const markdown = htmlToMarkdown(html);\n  const tokens = estimateTokens(markdown);\n\n  const headers = new Headers(response.headers);\n  headers.delete(\"Content-Length\"); // body size changes\n  headers.set(\"Content-Type\", \"text/markdown; charset=utf-8\");\n  headers.set(\"Vary\", \"Accept\");\n  headers.set(\"x-markdown-tokens\", String(tokens));\n\n  return new Response(markdown, {\n    status: response.status,\n    headers,\n  });\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"sargam-icons\",\n  \"version\": \"1.6.7\",\n  \"description\": \"A collection of 1200+ open-source icons.\",\n  \"scripts\": {\n    \"clean\": \"rimraf package *.tgz\",\n    \"compress\": \"svgo -f ./src/assets/Duotone -o ./icons/Duotone && svgo -f ./src/assets/Fill -o ./icons/Fill && svgo -f ./src/assets/Line -o ./icons/Line\",\n    \"generate-icons\": \"bun run clean && bun run compress\",\n    \"generate-changelog\": \"bun run scripts/generate-changelog.ts\",\n    \"generate-template\": \"bun run generate-changelog && bun run src/sargam.ts\",\n    \"build\": \"bun run generate-template && bunx rspack build\",\n    \"dev\": \"bun run generate-template && bunx rspack serve\",\n    \"build:dev\": \"bun run generate-template && bunx rspack build --mode development\",\n    \"preview\": \"bun run build && bunx serve dist\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/planetabhi/sargam-icons.git\"\n  },\n  \"files\": [\n    \"Icons/\"\n  ],\n  \"keywords\": [\n    \"icons\",\n    \"line-icons\",\n    \"fill-icons\",\n    \"duotone-icons\",\n    \"sargam-icons\",\n    \"svg\",\n    \"react\",\n    \"optimized\",\n    \"figma\",\n    \"compressed\",\n    \"sargam\"\n  ],\n  \"author\": \"@planetabhi\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/planetabhi/sargam-icons/issues\"\n  },\n  \"homepage\": \"https://sargamicons.com/\",\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.29.0\",\n    \"@babel/preset-env\": \"^7.29.2\",\n    \"@babel/preset-typescript\": \"^7.28.5\",\n    \"@new-ui/colors\": \"^2.2.5\",\n    \"@new-ui/reset\": \"^0.1.2\",\n    \"@rspack/cli\": \"^2.0.1\",\n    \"@rspack/core\": \"^2.0.1\",\n    \"@rspack/dev-server\": \"^2.0.1\",\n    \"@sargamdesign/colors\": \"^3.1.0\",\n    \"@svgr/core\": \"^8.1.0\",\n    \"@types/node\": \"^25.6.0\",\n    \"babel-loader\": \"^10.1.1\",\n    \"css-loader\": \"^7.1.4\",\n    \"mini-css-extract-plugin\": \"^2.10.2\",\n\n    \"rimraf\": \"^6.1.3\",\n    \"sass-embedded\": \"^1.99.0\",\n    \"sass-loader\": \"^16.0.7\",\n    \"spacings\": \"^0.1.0\",\n\n    \"svgo\": \"^4.0.1\",\n    \"typescript\": \"^6.0.3\"\n  },\n  \"main\": \"index.js\"\n}"
  },
  {
    "path": "public/.well-known/agent-skills/index.json",
    "content": "{\n  \"$schema\": \"https://schemas.agentskills.io/discovery/0.2.0/schema.json\",\n  \"skills\": [\n    {\n      \"name\": \"sargam-icons\",\n      \"type\": \"skill-md\",\n      \"description\": \"Discover and browse 1,200+ open-source icons in Line, Fill, and Duotone styles.\",\n      \"url\": \"https://sargamicons.com/.well-known/agent-skills/sargam-icons/SKILL.md\",\n      \"digest\": \"sha256:6d28bf89e3cfc983abf201ccb656f73efd4557046200c12c5a7896828db70d1d\"\n    }\n  ]\n}\n"
  },
  {
    "path": "public/.well-known/agent-skills/sargam-icons/SKILL.md",
    "content": "# Sargam Icons Discovery\n\nDiscover and browse 1,200+ open-source icons in Line, Fill, and Duotone styles from Sargam Icons.\n\n## Usage\n\n- Browse icons at https://sargamicons.com/\n- Download SVG icons from `/icons/Line/`, `/icons/Fill/`, `/icons/Duotone/`\n- View changelog at https://sargamicons.com/changelog.html\n- Source code at https://github.com/planetabhi/sargam-icons\n\n## Icon Styles\n\n- **Line** — Outline/stroke icons\n- **Fill** — Solid filled icons\n- **Duotone** — Two-tone icons\n\n## License\n\nMIT\n"
  },
  {
    "path": "public/.well-known/api-catalog",
    "content": "{\n  \"linkset\": [\n    {\n      \"anchor\": \"https://sargamicons.com/\",\n      \"service-doc\": [\n        {\n          \"href\": \"https://github.com/planetabhi/sargam-icons\",\n          \"type\": \"text/html\"\n        }\n      ],\n      \"service-desc\": [\n        {\n          \"href\": \"https://raw.githubusercontent.com/planetabhi/sargam-icons/main/README.md\",\n          \"type\": \"text/markdown\"\n        }\n      ],\n      \"describes\": [\n        {\n          \"href\": \"https://sargamicons.com/\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "public/.well-known/mcp/server-card.json",
    "content": "{\n  \"serverInfo\": {\n    \"name\": \"sargam-icons\",\n    \"version\": \"1.6.7\",\n    \"description\": \"A collection of 1,200+ open-source icons in Line, Fill, and Duotone styles.\"\n  },\n  \"capabilities\": {\n    \"resources\": true,\n    \"tools\": false,\n    \"prompts\": false\n  },\n  \"resources\": [\n    {\n      \"name\": \"icons-line\",\n      \"description\": \"Line-style SVG icons\",\n      \"uri\": \"https://sargamicons.com/icons/Line/\"\n    },\n    {\n      \"name\": \"icons-fill\",\n      \"description\": \"Fill-style SVG icons\",\n      \"uri\": \"https://sargamicons.com/icons/Fill/\"\n    },\n    {\n      \"name\": \"icons-duotone\",\n      \"description\": \"Duotone-style SVG icons\",\n      \"uri\": \"https://sargamicons.com/icons/Duotone/\"\n    }\n  ]\n}\n"
  },
  {
    "path": "public/_headers",
    "content": "# Link response headers for agent discovery (RFC 8288, RFC 9727)\n/\n  Link: </sitemap.xml>; rel=\"sitemap\"; type=\"application/xml\"\n  Link: </.well-known/api-catalog>; rel=\"api-catalog\"; type=\"application/linkset+json\"\n  Link: <https://github.com/planetabhi/sargam-icons>; rel=\"service-doc\"\n  Vary: Accept\n\n# Serve api-catalog with correct Content-Type (RFC 9727)\n/.well-known/api-catalog\n  Content-Type: application/linkset+json\n\n# MCP Server Card (SEP-1649)\n/.well-known/mcp/server-card.json\n  Content-Type: application/json\n\n# Agent Skills Discovery index\n/.well-known/agent-skills/index.json\n  Content-Type: application/json\n"
  },
  {
    "path": "public/robots.txt",
    "content": "# robots.txt for sargam-icons\n# https://www.rfc-editor.org/rfc/rfc9309\n# Content Signals: https://contentsignals.org/\n\n# Allow all crawlers full access\nUser-agent: *\nContent-Signal: ai-train=yes, search=yes, ai-input=yes\nAllow: /icons/Line/\nAllow: /icons/Fill/\nAllow: /icons/Duotone/\nAllow: /\nDisallow: /icons/\n\n# AI / LLM crawlers — allow indexing of public pages\nUser-agent: GPTBot\nAllow: /icons/Line/\nAllow: /icons/Fill/\nAllow: /icons/Duotone/\nAllow: /\nDisallow: /icons/\n\nUser-agent: OAI-SearchBot\nAllow: /icons/Line/\nAllow: /icons/Fill/\nAllow: /icons/Duotone/\nAllow: /\nDisallow: /icons/\n\nUser-agent: ChatGPT-User\nAllow: /icons/Line/\nAllow: /icons/Fill/\nAllow: /icons/Duotone/\nAllow: /\nDisallow: /icons/\n\nUser-agent: ClaudeBot\nAllow: /icons/Line/\nAllow: /icons/Fill/\nAllow: /icons/Duotone/\nAllow: /\nDisallow: /icons/\n\nUser-agent: Claude-Web\nAllow: /icons/Line/\nAllow: /icons/Fill/\nAllow: /icons/Duotone/\nAllow: /\nDisallow: /icons/\n\nUser-agent: anthropic-ai\nAllow: /icons/Line/\nAllow: /icons/Fill/\nAllow: /icons/Duotone/\nAllow: /\nDisallow: /icons/\n\nUser-agent: Google-Extended\nAllow: /icons/Line/\nAllow: /icons/Fill/\nAllow: /icons/Duotone/\nAllow: /\nDisallow: /icons/\n\nUser-agent: Applebot-Extended\nAllow: /icons/Line/\nAllow: /icons/Fill/\nAllow: /icons/Duotone/\nAllow: /\nDisallow: /icons/\n\nUser-agent: PerplexityBot\nAllow: /icons/Line/\nAllow: /icons/Fill/\nAllow: /icons/Duotone/\nAllow: /\nDisallow: /icons/\n\nUser-agent: Amazonbot\nAllow: /icons/Line/\nAllow: /icons/Fill/\nAllow: /icons/Duotone/\nAllow: /\nDisallow: /icons/\n\nUser-agent: Bytespider\nAllow: /icons/Line/\nAllow: /icons/Fill/\nAllow: /icons/Duotone/\nAllow: /\nDisallow: /icons/\n\nUser-agent: CCBot\nAllow: /icons/Line/\nAllow: /icons/Fill/\nAllow: /icons/Duotone/\nAllow: /\nDisallow: /icons/\n\nSitemap: https://sargamicons.com/sitemap.xml\n"
  },
  {
    "path": "public/sitemap.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n  <url>\n    <loc>https://sargamicons.com/</loc>\n    <lastmod>2026-04-18</lastmod>\n    <priority>1.0</priority>\n  </url>\n  <url>\n    <loc>https://sargamicons.com/changelog.html</loc>\n    <lastmod>2026-04-18</lastmod>\n    <priority>0.5</priority>\n  </url>\n</urlset>\n"
  },
  {
    "path": "rspack.config.ts",
    "content": "import path from 'path';\nimport { rspack, Configuration } from '@rspack/core';\nimport { fileURLToPath } from 'url';\n\n// Get __dirname equivalent in ES modules\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\ninterface BuildEnv {\n    mode?: 'production' | 'development';\n}\n\ninterface Argv {\n    mode?: 'production' | 'development';\n}\n\nexport default (env: BuildEnv, argv: Argv): Configuration => {\n    const isProd = argv && argv.mode === 'production';\n    return {\n        mode: isProd ? 'production' : 'development',\n        entry: {\n            bundle: path.resolve(__dirname, 'src/index.ts'),\n        },\n        output: {\n            path: path.resolve(__dirname, 'dist'),\n            filename: '[name][contenthash].js',\n            clean: true,\n            assetModuleFilename: '[name][ext]',\n        },\n        devtool: isProd ? 'hidden-source-map' : 'eval-source-map',\n        devServer: {\n            static: {\n                directory: path.resolve(__dirname, 'dist'),\n            },\n            port: 3000,\n            open: true,\n            hot: true,\n            compress: true,\n            historyApiFallback: true,\n        },\n        module: {\n            rules: [\n                {\n                    test: /\\.css$/,\n                    use: [rspack.CssExtractRspackPlugin.loader, 'css-loader'],\n                },\n                {\n                    test: /\\.s[ac]ss$/i,\n                    use: [\n                        rspack.CssExtractRspackPlugin.loader,\n                        'css-loader',\n                        {\n                            loader: 'sass-loader',\n                            options: {\n                                // Use package name string — ESM-safe, avoids require() in ESM context\n                                implementation: 'sass-embedded',\n                            },\n                        },\n                    ],\n                },\n                {\n                    test: /\\.[jt]s$/,\n                    exclude: /node_modules/,\n                    use: {\n                        loader: 'babel-loader',\n                        options: {\n                            presets: ['@babel/preset-env', '@babel/preset-typescript'],\n                        },\n                    },\n                },\n                {\n                    test: /\\.(png|svg|jpg|jpeg|gif)$/i,\n                    type: 'asset/resource',\n                },\n                {\n                    test: /\\.(woff|woff2|eot|ttf|otf)$/i,\n                    type: 'asset/resource',\n                },\n            ],\n        },\n        resolve: {\n            extensions: ['.ts', '.js', '.json'],\n        },\n        plugins: [\n            new rspack.CopyRspackPlugin({\n                patterns: [\n                    { from: path.resolve(__dirname, './Icons/Line'), to: 'icons/Line' },\n                    { from: path.resolve(__dirname, './Icons/Duotone'), to: 'icons/Duotone' },\n                    { from: path.resolve(__dirname, './Icons/Fill'), to: 'icons/Fill' },\n                    { from: path.resolve(__dirname, './public/robots.txt'), to: 'robots.txt' },\n                    { from: path.resolve(__dirname, './public/sitemap.xml'), to: 'sitemap.xml' },\n                    { from: path.resolve(__dirname, './public/_headers'), to: '_headers' },\n                    { from: path.resolve(__dirname, './public/.well-known'), to: '.well-known' },\n                ],\n            }),\n            new rspack.HtmlRspackPlugin({\n                title: 'Sargam Icons',\n                filename: 'index.html',\n                template: path.resolve(__dirname, 'src/template.html'),\n                favicon: path.resolve(__dirname, 'src/favicon.ico'),\n                meta: {\n                    viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no',\n                },\n            }),\n            new rspack.HtmlRspackPlugin({\n                title: 'Changelog - Sargam Icons',\n                filename: 'changelog.html',\n                template: path.resolve(__dirname, 'src/changelog.html'),\n                favicon: path.resolve(__dirname, 'src/favicon.ico'),\n                meta: {\n                    viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no',\n                },\n            }),\n            new rspack.CssExtractRspackPlugin({\n                filename: isProd ? '[name][contenthash].css' : '[name].css',\n            }),\n        ],\n    };\n};\n"
  },
  {
    "path": "scripts/generate-changelog.ts",
    "content": "import { execSync } from 'child_process';\nimport fs from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst rootDir = path.resolve(__dirname, '..');\n\ninterface ChangelogEntry {\n    version: string;\n    date: string;\n    newIcons: string[];\n    highlights: string[];\n}\n\ninterface Changelog {\n    generated: string;\n    totalIcons: number;\n    entries: ChangelogEntry[];\n}\n\nfunction execGit(command: string): string {\n    try {\n        return execSync(command, { cwd: rootDir, encoding: 'utf-8' }).trim();\n    } catch {\n        return '';\n    }\n}\n\ninterface VersionCommit {\n    hash: string;\n    version: string;\n    date: string;\n}\n\nfunction getVersionCommits(): VersionCommit[] {\n    // Find commits with version patterns like \"new:v1.6.7\" or \"new: v1.6.6\" or just \"v1.0.0\"\n    const output = execGit('git log --all --format=\"%H|%s|%ad\" --date=short');\n    if (!output) return [];\n\n    // Pattern to detect and capture version from commits like \"new:v1.6.7\", \"new v1.6.6\", \"new: v1.0.0\", or just \"v1.0.0\"\n    // Uses optional colon (new:?) to match both \"new:\" and \"new \"\n    const versionPattern = /^(?:new:?\\s*)?v?(\\d+\\.\\d+\\.?\\d*)/i;\n    const versions: VersionCommit[] = [];\n    const seenVersions = new Set<string>();\n\n    for (const line of output.split('\\n')) {\n        if (!line) continue;\n        const [hash, subject, date] = line.split('|');\n\n        // Use single regex for both check and capture\n        const match = subject.match(versionPattern);\n        if (match) {\n            let version = match[1];\n            // Normalize version (add .0 if needed)\n            if (version.split('.').length === 2) {\n                version += '.0';\n            }\n\n            // Only take the first (most recent) commit for each version\n            if (!seenVersions.has(version)) {\n                seenVersions.add(version);\n                versions.push({ hash, version, date });\n            }\n        }\n    }\n\n    // Sort by version number descending\n    versions.sort((a, b) => {\n        const aParts = a.version.split('.').map(Number);\n        const bParts = b.version.split('.').map(Number);\n        for (let i = 0; i < 3; i++) {\n            if ((bParts[i] || 0) !== (aParts[i] || 0)) {\n                return (bParts[i] || 0) - (aParts[i] || 0);\n            }\n        }\n        return 0;\n    });\n\n    return versions;\n}\n\nfunction getNewIconsBetweenCommits(fromHash: string, toHash: string): string[] {\n    // Get icons added between two commits\n    const command = fromHash\n        ? `git diff --name-status --diff-filter=A ${fromHash}..${toHash} -- \"Icons/Line/*.svg\"`\n        : `git diff --name-status --diff-filter=A $(git rev-list --max-parents=0 HEAD)..${toHash} -- \"Icons/Line/*.svg\"`;\n\n    const output = execGit(command);\n    if (!output) return [];\n\n    return output\n        .split('\\n')\n        .filter(Boolean)\n        .map((line) => {\n            // Extract icon name from path like \"A\\tIcons/Line/si_IconName.svg\"\n            const match = line.match(/si_([^.]+)\\.svg$/);\n            return match ? match[1] : null;\n        })\n        .filter((name): name is string => name !== null)\n        .sort();\n}\n\nfunction countTotalIcons(): number {\n    const iconsDir = path.join(rootDir, 'Icons', 'Line');\n    try {\n        const files = fs.readdirSync(iconsDir);\n        return files.filter((f) => f.endsWith('.svg')).length;\n    } catch {\n        return 0;\n    }\n}\n\nfunction generateChangelog(): Changelog {\n    const versionCommits = getVersionCommits();\n    const entries: ChangelogEntry[] = [];\n\n    // Limit to 20 most recent versions\n    const maxVersions = 20;\n    const versionsToProcess = versionCommits.slice(0, maxVersions);\n\n    console.log(`Found ${versionCommits.length} version commits, processing ${versionsToProcess.length}`);\n\n    for (let i = 0; i < versionsToProcess.length; i++) {\n        const current = versionsToProcess[i];\n        // Look forward: from current version commit to next version commit (or HEAD for latest)\n        const next = i > 0 ? versionsToProcess[i - 1] : null;\n\n        // Get icons added AFTER this version commit up to the next version (or HEAD)\n        const newIcons = getNewIconsBetweenCommits(\n            current.hash,\n            next?.hash || 'HEAD'\n        );\n\n        console.log(`${current.version}: ${newIcons.length} new icons`);\n\n        // Only add entries with new icons or if it's a major version\n        if (newIcons.length > 0 || current.version.endsWith('.0')) {\n            entries.push({\n                version: current.version,\n                date: current.date,\n                newIcons,\n                highlights: [],\n            });\n        }\n    }\n\n    return {\n        generated: new Date().toISOString(),\n        totalIcons: countTotalIcons(),\n        entries,\n    };\n}\n\n// Generate and save changelog\nconst changelog = generateChangelog();\nconst outputPath = path.join(rootDir, 'src', 'changelog.json');\n\nfs.writeFileSync(outputPath, JSON.stringify(changelog, null, 2));\nconsole.log(`\\nChangelog generated successfully!`);\nconsole.log(`Output: ${outputPath}`);\nconsole.log(`Total versions: ${changelog.entries.length}`);\nconsole.log(`Total icons: ${changelog.totalIcons}`);\n"
  },
  {
    "path": "src/index.ts",
    "content": "import './styles/base.scss';\n\ndocument.addEventListener('DOMContentLoaded', () => {\n    const searchInput = document.getElementById('icon-search') as HTMLInputElement | null;\n    const clearBtn = document.getElementById('icon-search-clear') as HTMLButtonElement | null;\n    const grid = document.querySelector('#icon-grid .flex-grid') as HTMLElement | null;\n\n    if (!searchInput || !grid) return;\n\n    const items = Array.from(grid.querySelectorAll('.flex-grid-item')) as HTMLElement[];\n\n    function normalize(text: string): string {\n        return (text || '').toLowerCase();\n    }\n\n    function getItemName(item: HTMLElement): string {\n        const name = item.getAttribute('data-icon-name');\n        return name || '';\n    }\n\n    function filter(query: string): void {\n        const q = normalize(query);\n        if (!q) {\n            items.forEach((el) => {\n                el.style.display = '';\n            });\n            return;\n        }\n\n        items.forEach((el) => {\n            const name = normalize(getItemName(el));\n            el.style.display = name.includes(q) ? '' : 'none';\n        });\n    }\n\n    if (clearBtn) {\n        clearBtn.hidden = (searchInput.value || '').length === 0;\n    }\n    filter(searchInput.value || '');\n\n    let frameRequested = false;\n    function onInputLike(): void {\n        const value = searchInput!.value;\n        if (clearBtn) {\n            clearBtn.hidden = value.length === 0;\n        }\n        if (frameRequested) return;\n        frameRequested = true;\n        requestAnimationFrame(() => {\n            filter(value);\n            frameRequested = false;\n        });\n    }\n\n    searchInput.addEventListener('input', onInputLike);\n    searchInput.addEventListener('change', onInputLike);\n    searchInput.addEventListener('search', onInputLike);\n\n    if (clearBtn) {\n        clearBtn.addEventListener('click', () => {\n            searchInput.value = '';\n            clearBtn.hidden = true;\n            filter('');\n            searchInput.focus();\n        });\n    }\n\n    searchInput.addEventListener('keydown', (e: KeyboardEvent) => {\n        if (e.key === 'Escape') {\n            searchInput.value = '';\n            if (clearBtn) clearBtn.hidden = true;\n            filter('');\n        }\n    });\n\n    // Cmd/Ctrl+K focuses the search input\n    document.addEventListener('keydown', (e: KeyboardEvent) => {\n        const isK = e.key === 'k' || e.key === 'K';\n        if (isK && (e.metaKey || e.ctrlKey)) {\n            e.preventDefault();\n            searchInput.focus();\n            searchInput.select();\n        }\n    });\n});\n"
  },
  {
    "path": "src/sargam.ts",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\n// Get __dirname equivalent in ES modules\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// ─────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────\n\ninterface ChangelogEntry {\n  version: string;\n  date: string;\n  newIcons: string[];\n  highlights: string[];\n}\n\ninterface Changelog {\n  generated: string;\n  totalIcons: number;\n  entries: ChangelogEntry[];\n}\n\n// ─────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────\n\nfunction getVersion(): string {\n  try {\n    const pkgPath = path.join(__dirname, '..', 'package.json');\n    const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as { version?: string };\n    return pkg.version ?? '1.6.7';\n  } catch {\n    return '1.6.7';\n  }\n}\n\nfunction loadChangelog(): Changelog {\n  try {\n    const changelogPath = path.join(__dirname, 'changelog.json');\n    const data = fs.readFileSync(changelogPath, 'utf-8');\n    return JSON.parse(data) as Changelog;\n  } catch {\n    return { generated: '', totalIcons: 0, entries: [] };\n  }\n}\n\nfunction formatDate(dateStr: string): string {\n  const date = new Date(dateStr);\n  return date.toLocaleDateString('en-US', {\n    year: 'numeric',\n    month: 'short',\n    day: '2-digit',\n  });\n}\n\nfunction getIconNames(directory: string): string[] {\n  return fs\n    .readdirSync(directory)\n    .filter((file: string) => file.endsWith('.svg'))\n    .map((file: string) => path.basename(file, '.svg'))\n    .sort();\n}\n\n// ─────────────────────────────────────────────\n// Shared HTML generators\n// ─────────────────────────────────────────────\n\n/** The brand logo SVG — shared between both pages. */\nconst BRAND_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"none\"><g fill=\"var(--content-primary)\" clip-path=\"url(#a)\"><path fill-rule=\"evenodd\" d=\"M29.163 16.038a5.965 5.965 0 0 0-3.234-3.972 4.934 4.934 0 0 0-2.13-.429.134.134 0 0 1-.12-.206c.38-.625.614-1.329.686-2.057a5.97 5.97 0 0 0-1.821-4.802 5.487 5.487 0 0 0-2.247-1.348c-1.474-.443-2.637-.216-3.268.635-.35.504-.543 1.1-.552 1.714a2.86 2.86 0 0 0 .453 1.846 2.174 2.174 0 0 0 1.509.881 2.134 2.134 0 0 0 1.642-.531 2.077 2.077 0 0 0 .687-1.499.593.593 0 0 0-.498-.607.566.566 0 0 0-.635.562.932.932 0 0 1-.318.686.99.99 0 0 1-.762.25 1.027 1.027 0 0 1-.71-.414 1.77 1.77 0 0 1-.246-1.129c.004-.393.123-.775.342-1.1.443-.594 1.465-.399 2.034-.227a4.36 4.36 0 0 1 1.78 1.073 4.886 4.886 0 0 1 1.486 3.865 4.189 4.189 0 0 1-2.172 3.156l-.027.02-.147.086a.343.343 0 0 1-.447-.089 5.691 5.691 0 0 0-8.917 0 .343.343 0 0 1-.442.09l-.148-.087-.027-.02A4.194 4.194 0 0 1 8.743 9.23a4.908 4.908 0 0 1 1.484-3.865 4.37 4.37 0 0 1 1.784-1.073c.57-.172 1.592-.371 2.034.226.22.325.34.708.343 1.101.046.393-.04.79-.243 1.129a1.03 1.03 0 0 1-.71.414.98.98 0 0 1-.765-.25.933.933 0 0 1-.32-.686.565.565 0 0 0-.634-.562.593.593 0 0 0-.497.606 2.081 2.081 0 0 0 1.451 1.94 2.168 2.168 0 0 0 2.397-.773c.358-.544.52-1.194.456-1.842a3.13 3.13 0 0 0-.552-1.715c-.634-.85-1.794-1.077-3.268-.634a5.466 5.466 0 0 0-2.247 1.348 5.96 5.96 0 0 0-1.821 4.801c.071.729.306 1.432.686 2.058a.134.134 0 0 1-.12.206 4.929 4.929 0 0 0-2.127.429 5.978 5.978 0 0 0-3.237 3.971 5.519 5.519 0 0 0-.045 2.62c.343 1.5 1.132 2.401 2.185 2.511.092.007.185.007.278 0 .52-.012 1.03-.149 1.488-.398a2.83 2.83 0 0 0 1.372-1.313 2.164 2.164 0 0 0 0-1.746 2.123 2.123 0 0 0-2.12-1.254 2.093 2.093 0 0 0-.806.242.593.593 0 0 0-.278.738.566.566 0 0 0 .803.267.929.929 0 0 1 .765-.075.988.988 0 0 1 .682.948c0 .141-.029.28-.085.41-.19.347-.49.62-.854.775-.338.198-.728.291-1.119.267-.737-.085-1.076-1.07-1.213-1.65a4.36 4.36 0 0 1 .04-2.077 4.877 4.877 0 0 1 2.607-3.221 4.191 4.191 0 0 1 3.814.302l.178.103a.343.343 0 0 1 .144.428 5.703 5.703 0 0 0 4.761 7.752v.398a.517.517 0 0 0 0 .102 4.181 4.181 0 0 1-1.647 3.458 4.901 4.901 0 0 1-4.094.655 4.384 4.384 0 0 1-1.828-1.03c-.436-.407-1.118-1.196-.824-1.875.175-.351.45-.642.789-.837a1.773 1.773 0 0 1 1.098-.343 1.028 1.028 0 0 1 .717.404.993.993 0 0 1 .008 1.147.944.944 0 0 1-.29.267.562.562 0 0 0-.144.86.593.593 0 0 0 .752.094 2.077 2.077 0 0 0 .946-1.349 2.122 2.122 0 0 0-.36-1.687 2.178 2.178 0 0 0-1.52-.864 2.822 2.822 0 0 0-1.82.528c-.53.313-.954.78-1.215 1.337-.422.974-.034 2.093 1.084 3.149a5.463 5.463 0 0 0 2.291 1.269 6.198 6.198 0 0 0 1.687.233 5.785 5.785 0 0 0 3.372-1.05 4.859 4.859 0 0 0 1.433-1.632.14.14 0 0 1 .193-.052c.021.012.04.03.051.052a4.87 4.87 0 0 0 1.437 1.633 5.769 5.769 0 0 0 3.369 1.049c.57 0 1.138-.078 1.686-.234a5.463 5.463 0 0 0 2.292-1.268c1.122-1.056 1.506-2.175 1.084-3.149a3.086 3.086 0 0 0-1.211-1.337 2.84 2.84 0 0 0-1.825-.528 2.163 2.163 0 0 0-1.516.864 2.11 2.11 0 0 0-.36 1.687 2.059 2.059 0 0 0 .947 1.348.593.593 0 0 0 .751-.093.568.568 0 0 0-.144-.86.92.92 0 0 1-.446-.628.982.982 0 0 1 .165-.786 1.027 1.027 0 0 1 .713-.404c.394-.012.78.109 1.098.343.34.195.616.486.792.837.292.686-.391 1.468-.823 1.876a4.396 4.396 0 0 1-1.821 1.005 4.902 4.902 0 0 1-4.092-.649 4.198 4.198 0 0 1-1.647-3.453v-.207a.343.343 0 0 1 .299-.342 5.713 5.713 0 0 0 4.85-5.615 5.652 5.652 0 0 0-.392-2.057.344.344 0 0 1 .145-.429l.181-.107a4.199 4.199 0 0 1 3.818-.302 4.892 4.892 0 0 1 2.603 3.221c.178.679.19 1.39.035 2.075-.138.58-.478 1.564-1.212 1.65a1.979 1.979 0 0 1-1.121-.268 1.755 1.755 0 0 1-.853-.775 1.029 1.029 0 0 1 0-.82.987.987 0 0 1 .596-.538.944.944 0 0 1 .717.048.617.617 0 0 0 .761-.096.562.562 0 0 0-.148-.857 2.084 2.084 0 0 0-1.68-.172 2.115 2.115 0 0 0-1.28 1.142 2.164 2.164 0 0 0 0 1.746 2.84 2.84 0 0 0 1.372 1.313 3.06 3.06 0 0 0 1.767.381c1.05-.12 1.828-1.029 2.181-2.51a5.487 5.487 0 0 0-.038-2.617ZM16 11.421a4.57 4.57 0 0 1 3.379 1.496.342.342 0 0 1-.086.531l-2.95 1.715a.685.685 0 0 1-.686 0l-2.95-1.715a.342.342 0 0 1-.082-.531A4.562 4.562 0 0 1 16 11.42Zm-4.579 4.589c.002-.465.074-.928.213-1.371a.342.342 0 0 1 .5-.192l2.957 1.714a.686.686 0 0 1 .343.594v3.409a.342.342 0 0 1-.419.343 4.585 4.585 0 0 1-3.594-4.497Zm9.158 0a4.582 4.582 0 0 1-3.591 4.459.343.343 0 0 1-.415-.343V16.72a.686.686 0 0 1 .343-.593l2.953-1.715a.344.344 0 0 1 .5.195c.143.454.214.928.21 1.403Z\" clip-rule=\"evenodd\"/><path fill-rule=\"evenodd\" d=\"M16 0C7.163 0 0 7.163 0 16s7.163 16 16 16 16-7.163 16-16S24.837 0 16 0ZM1.132 16C1.132 7.789 7.789 1.132 16 1.132S30.868 7.789 30.868 16 24.211 30.868 16 30.868 1.132 24.211 1.132 16Z\" clip-rule=\"evenodd\"/><path d=\"M4.727 8.981a1.029 1.029 0 1 1 1.144 1.711 1.029 1.029 0 0 1-1.144-1.71Zm21.974-.173a1.029 1.029 0 1 0 0 2.057 1.029 1.029 0 0 0 0-2.057ZM16 27.328a1.029 1.029 0 1 0 0 2.059 1.029 1.029 0 0 0 0-2.058Z\"/></g><defs><clipPath id=\"a\"><path fill=\"#fff\" d=\"M0 0h32v32H0z\"/></clipPath></defs></svg>`;\n\n/** Theme-toggle button SVGs — shared between both pages. */\nconst THEME_TOGGLE_SVGS = `\n  <svg id=\"icon-moon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n    <path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"M10.41 13.28C7.332 10.205 6.716 5.693 8.357 2c-1.23.41-2.256 1.23-3.281 2.256a10.4 10.4 0 0 0 0 14.768c4.102 4.102 10.46 3.897 14.562-.205 1.026-1.026 1.846-2.051 2.256-3.282-3.896 1.436-8.409.82-11.486-2.256\"/>\n  </svg>\n  <svg id=\"icon-sun\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\" aria-hidden=\"true\" style=\"display:none\">\n    <g clip-path=\"url(#sun-clip)\">\n      <path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-miterlimit=\"10\" stroke-width=\"1.5\" d=\"M5 12H1m22 0h-4M7.05 7.05 4.222 4.222m15.556 15.556L16.95 16.95m-9.9 0-2.828 2.828M19.778 4.222 16.95 7.05M12 19v4m0-22v4m4 7a4 4 0 1 1-8 0 4 4 0 0 1 8 0\"/>\n    </g>\n    <defs>\n      <clipPath id=\"sun-clip\"><path fill=\"#fff\" d=\"M0 0h24v24H0z\"/></clipPath>\n    </defs>\n  </svg>`;\n\n/** The icon-popover dialog HTML — identical on both pages. */\nfunction generatePopoverHtml(): string {\n  return `\n  <div id=\"icon-popover\" class=\"icon-popover\" hidden role=\"dialog\" aria-labelledby=\"popover-title\" aria-modal=\"true\">\n    <div class=\"popover-content\">\n      <div class=\"popover-header\">\n        <h3 id=\"popover-title\" class=\"popover-icon-name\"></h3>\n        <button type=\"button\" class=\"popover-close\" aria-label=\"Close popover\">\n          <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-miterlimit=\"10\" stroke-width=\"1.5\" d=\"m7.757 16.243 8.486-8.486m0 8.486L7.757 7.757\"/></svg>\n        </button>\n      </div>\n      <div class=\"popover-variants\">\n        <button type=\"button\" class=\"popover-variant active\" data-variant=\"line\">Line</button>\n        <button type=\"button\" class=\"popover-variant\" data-variant=\"duotone\">Duotone</button>\n        <button type=\"button\" class=\"popover-variant\" data-variant=\"fill\">Fill</button>\n      </div>\n      <div class=\"popover-preview\">\n        <img class=\"popover-icon\" src=\"\" alt=\"\" width=\"48\" height=\"48\">\n      </div>\n      <div class=\"popover-menu\">\n        <button type=\"button\" class=\"popover-menu-item\" id=\"copy-svg-btn\">\n          <span>[ Copy SVG ]</span>\n        </button>\n        <button type=\"button\" class=\"popover-menu-item\" id=\"copy-cdn-btn\">\n          <span>[ Copy CDN ]</span>\n        </button>\n        <button type=\"button\" class=\"popover-menu-item\" id=\"download-svg-btn\">\n          <span>[ Download ]</span>\n        </button>\n      </div>\n    </div>\n  </div>`;\n}\n\n/**\n * The shared initIconPopover() script block.\n *\n * @param cdnBaseUrl - The CDN base URL injected at build time.\n * @param clickSelector - CSS selector for clickable icon elements.\n * @param showRandomOnLoad - Whether to open a random icon popover on page load.\n */\nfunction generatePopoverScript(\n  cdnBaseUrl: string,\n  clickSelector: string,\n  showRandomOnLoad: boolean,\n): string {\n  // cdnBaseUrl is interpolated as string literals into the generated JS — no runtime const needed\n  return `\n    function initIconPopover() {\n      const popover = document.getElementById('icon-popover');\n      const popoverContent = document.querySelector('.popover-content');\n      const popoverHeader = document.querySelector('.popover-header');\n      const popoverTitle = document.querySelector('.popover-icon-name');\n      const popoverIcon = document.querySelector('.popover-icon');\n      const popoverClose = document.querySelector('.popover-close');\n      const copyBtn = document.getElementById('copy-svg-btn');\n      const downloadBtn = document.getElementById('download-svg-btn');\n      const copyCdnBtn = document.getElementById('copy-cdn-btn');\n      const variantBtns = document.querySelectorAll('.popover-variant');\n\n      /** @type {{ iconName: string, iconType: string, iconUrl: string } | null} */\n      let currentIconData = null;\n      let isDragging = false;\n      let offsetX = 0;\n      let offsetY = 0;\n      let currentPosX = 0;\n      let currentPosY = 0;\n      let hasBeenPositioned = false;\n      /** @type {Element | null} */\n      let previouslyFocusedElement = null;\n\n      function getFocusableElements() {\n        return popoverContent.querySelectorAll(\n          'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n        );\n      }\n\n      function trapFocus(e) {\n        if (e.key !== 'Tab') return;\n        const focusableElements = getFocusableElements();\n        if (focusableElements.length === 0) return;\n        const firstElement = focusableElements[0];\n        const lastElement = focusableElements[focusableElements.length - 1];\n        if (e.shiftKey) {\n          if (document.activeElement === firstElement) {\n            e.preventDefault();\n            lastElement.focus();\n          }\n        } else {\n          if (document.activeElement === lastElement) {\n            e.preventDefault();\n            firstElement.focus();\n          }\n        }\n      }\n\n      function centerPopover() {\n        const viewportWidth = window.innerWidth;\n        const viewportHeight = window.innerHeight;\n        const popoverWidth = popoverContent.offsetWidth || 256;\n        const popoverHeight = popoverContent.offsetHeight || 400;\n        const offsetFromRight = 48;\n        const offsetFromTop = 72;\n        currentPosX = (viewportWidth / 2) - popoverWidth / 2 - offsetFromRight;\n        currentPosY = (offsetFromTop + popoverHeight / 2) - (viewportHeight / 2);\n        popoverContent.style.left = '50%';\n        popoverContent.style.top = '50%';\n        popoverContent.style.transform = 'translate(calc(-50% + ' + currentPosX + 'px), calc(-50% + ' + currentPosY + 'px))';\n        offsetX = 0;\n        offsetY = 0;\n        hasBeenPositioned = true;\n      }\n\n      function showPopover(iconElement) {\n        const iconName = iconElement.getAttribute('data-name');\n        const iconType = iconElement.getAttribute('data-type') || 'line';\n        // For <img> elements, use their already-resolved src; for <button> elements (changelog)\n        // .src is undefined so we fall back to constructing the URL from CDN + data attributes.\n        const iconUrl = iconElement.src || ('${cdnBaseUrl}' + iconType.charAt(0).toUpperCase() + iconType.slice(1) + '/' + iconName + '.svg');\n\n        currentIconData = { iconName, iconType, iconUrl };\n        popoverTitle.textContent = iconName;\n        popoverIcon.src = iconUrl;\n        popoverIcon.alt = iconName + ' ' + iconType + ' icon';\n\n        variantBtns.forEach(function(btn) {\n          btn.classList.toggle('active', btn.getAttribute('data-variant') === iconType);\n        });\n\n        previouslyFocusedElement = document.activeElement;\n\n        if (!hasBeenPositioned) {\n          centerPopover();\n        }\n\n        popover.hidden = false;\n        popover.setAttribute('aria-hidden', 'false');\n\n        document.removeEventListener('keydown', trapFocus);\n        document.addEventListener('keydown', trapFocus);\n\n        setTimeout(function() {\n          const focusable = getFocusableElements();\n          if (focusable.length > 0) focusable[0].focus();\n        }, 100);\n      }\n\n      function hidePopover() {\n        popover.hidden = true;\n        popover.setAttribute('aria-hidden', 'true');\n        currentIconData = null;\n        isDragging = false;\n        document.removeEventListener('keydown', trapFocus);\n        if (previouslyFocusedElement && previouslyFocusedElement.focus) {\n          previouslyFocusedElement.focus();\n        }\n        previouslyFocusedElement = null;\n      }\n\n      function downloadIcon(iconName, iconType, iconUrl) {\n        fetch(iconUrl)\n          .then(function(response) { return response.blob(); })\n          .then(function(blob) {\n            const url = window.URL.createObjectURL(blob);\n            const a = document.createElement('a');\n            a.style.display = 'none';\n            a.href = url;\n            a.download = iconName + '-' + iconType + '.svg';\n            document.body.appendChild(a);\n            a.click();\n            document.body.removeChild(a);\n            window.URL.revokeObjectURL(url);\n          })\n          .catch(function(error) {\n            console.error('Failed to download the icon:', error);\n            alert('Failed to download the icon. Please try again.');\n          });\n      }\n\n      function copyTextToClipboard(text, btn) {\n        const spanEl = btn ? btn.querySelector('span') : null;\n        const originalText = spanEl ? spanEl.textContent : '';\n        function showCopied() {\n          if (spanEl) {\n            spanEl.textContent = '[ Copied ]';\n            setTimeout(function() { spanEl.textContent = originalText; }, 800);\n          }\n        }\n        if (navigator.clipboard && navigator.clipboard.writeText) {\n          navigator.clipboard.writeText(text).then(showCopied).catch(function(err) {\n            console.error('Failed to copy:', err);\n          });\n        } else {\n          const textarea = document.createElement('textarea');\n          textarea.value = text;\n          textarea.style.position = 'fixed';\n          textarea.style.opacity = '0';\n          document.body.appendChild(textarea);\n          textarea.select();\n          document.execCommand('copy');\n          document.body.removeChild(textarea);\n          showCopied();\n        }\n      }\n\n      function copyIconToClipboard(iconUrl) {\n        fetch(iconUrl)\n          .then(function(response) { return response.text(); })\n          .then(function(svgText) { copyTextToClipboard(svgText, copyBtn); })\n          .catch(function(error) {\n            console.error('Failed to copy SVG:', error);\n            alert('Failed to copy SVG. Please try again.');\n          });\n      }\n\n      // Drag\n      function dragStart(e) {\n        if (e.target.tagName === 'BUTTON' || e.target.closest('button')) return;\n        isDragging = true;\n        popoverHeader.style.cursor = 'grabbing';\n        if (e.type === 'touchstart') {\n          offsetX = e.touches[0].clientX - currentPosX;\n          offsetY = e.touches[0].clientY - currentPosY;\n        } else {\n          offsetX = e.clientX - currentPosX;\n          offsetY = e.clientY - currentPosY;\n        }\n        e.preventDefault();\n      }\n\n      function drag(e) {\n        if (!isDragging) return;\n        e.preventDefault();\n        const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;\n        const clientY = e.type === 'touchmove' ? e.touches[0].clientY : e.clientY;\n        currentPosX = clientX - offsetX;\n        currentPosY = clientY - offsetY;\n        popoverContent.style.left = '50%';\n        popoverContent.style.top = '50%';\n        popoverContent.style.transform = 'translate(calc(-50% + ' + currentPosX + 'px), calc(-50% + ' + currentPosY + 'px))';\n      }\n\n      function dragEnd() {\n        if (isDragging) {\n          isDragging = false;\n          popoverHeader.style.cursor = '';\n        }\n      }\n\n      popoverHeader.addEventListener('mousedown', dragStart);\n      document.addEventListener('mousemove', drag);\n      document.addEventListener('mouseup', dragEnd);\n      popoverHeader.addEventListener('touchstart', dragStart, { passive: false });\n      document.addEventListener('touchmove', drag, { passive: false });\n      document.addEventListener('touchend', dragEnd);\n\n      // Click handler\n      document.querySelectorAll('${clickSelector}').forEach(function(el) {\n        el.addEventListener('click', function(e) {\n          e.stopPropagation();\n          showPopover(el);\n        });\n      });\n\n      ${showRandomOnLoad ? `\n      // Show random icon on page load\n      const allIcons = document.querySelectorAll('.downloadable-icon');\n      if (allIcons.length > 0) {\n        const randomIcon = allIcons[Math.floor(Math.random() * allIcons.length)];\n        showPopover(randomIcon);\n      }\n      ` : ''}\n\n      // Keyboard: open icon on grid item Enter/Space\n      const flexGrid = document.querySelector('.flex-grid');\n      if (flexGrid) {\n        flexGrid.addEventListener('keydown', function(e) {\n          if (e.target.classList.contains('flex-grid-item')) {\n            if (e.key === 'Enter' || e.key === ' ') {\n              e.preventDefault();\n              const firstIcon = e.target.querySelector('.downloadable-icon');\n              if (firstIcon) showPopover(firstIcon);\n            }\n          }\n        });\n      }\n\n      if (copyBtn) {\n        copyBtn.addEventListener('click', function() {\n          if (currentIconData) copyIconToClipboard(currentIconData.iconUrl);\n        });\n      }\n\n      if (downloadBtn) {\n        downloadBtn.addEventListener('click', function() {\n          if (currentIconData) downloadIcon(currentIconData.iconName, currentIconData.iconType, currentIconData.iconUrl);\n        });\n      }\n\n      if (popoverClose) popoverClose.addEventListener('click', hidePopover);\n\n      document.addEventListener('keydown', function(e) {\n        if (e.key === 'Escape' && !popover.hidden) hidePopover();\n      });\n\n      variantBtns.forEach(function(btn) {\n        btn.addEventListener('click', function() {\n          if (!currentIconData) return;\n          const variant = btn.getAttribute('data-variant');\n          const newUrl = '${cdnBaseUrl}' + variant.charAt(0).toUpperCase() + variant.slice(1) + '/' + currentIconData.iconName + '.svg';\n          currentIconData.iconType = variant;\n          currentIconData.iconUrl = newUrl;\n          popoverIcon.src = newUrl;\n          popoverIcon.alt = currentIconData.iconName + ' ' + variant + ' icon';\n          variantBtns.forEach(function(b) { b.classList.remove('active'); });\n          btn.classList.add('active');\n        });\n      });\n\n      if (copyCdnBtn) {\n        copyCdnBtn.addEventListener('click', function() {\n          if (currentIconData) copyTextToClipboard(currentIconData.iconUrl, copyCdnBtn);\n        });\n      }\n    }\n\n    if (document.readyState === 'loading') {\n      document.addEventListener('DOMContentLoaded', initIconPopover);\n    } else {\n      initIconPopover();\n    }`;\n}\n\n/** Shared theme-toggle IIFE — used on both pages. */\nfunction generateThemeToggleScript(): string {\n  return `\n    (function initThemeToggle() {\n      var KEY = 'data-new-ui-theme';\n      var el = document.documentElement;\n      var saved = localStorage.getItem(KEY);\n\n      function apply(theme) {\n        el.setAttribute('data-new-ui-theme', theme);\n      }\n\n      if (saved === 'dark--warm' || saved === 'light--warm') {\n        apply(saved);\n      } else {\n        apply('dark--warm');\n      }\n\n      var btn = document.getElementById('theme-toggle');\n\n      function syncIcons() {\n        var isDark = (el.getAttribute('data-new-ui-theme') || 'dark--warm') === 'dark--warm';\n        var sun = document.getElementById('icon-sun');\n        var moon = document.getElementById('icon-moon');\n        if (sun && moon) {\n          sun.style.display = isDark ? '' : 'none';\n          moon.style.display = isDark ? 'none' : '';\n        }\n        if (btn) {\n          btn.setAttribute('aria-pressed', isDark ? 'true' : 'false');\n          btn.setAttribute('aria-label', isDark ? 'Switch to light theme' : 'Switch to dark theme');\n          btn.title = isDark ? 'Switch to light theme' : 'Switch to dark theme';\n        }\n      }\n      syncIcons();\n\n      if (btn) {\n        btn.addEventListener('click', function() {\n          var current = el.getAttribute('data-new-ui-theme') || 'dark--warm';\n          var next = current === 'light--warm' ? 'dark--warm' : 'light--warm';\n          apply(next);\n          try { localStorage.setItem(KEY, next); } catch (e) {}\n          syncIcons();\n        });\n      }\n    })();`;\n}\n\n/** Progressive image loading with IntersectionObserver. */\nfunction generateProgressiveLoadingScript(): string {\n  return `\n    function initProgressiveLoading() {\n      const images = document.querySelectorAll('.downloadable-icon');\n\n      function applyImage(img) {\n        const tempImg = new Image();\n        tempImg.onload = function() {\n          img.style.opacity = '1';\n          img.classList.add('loaded');\n        };\n        tempImg.onerror = function() {\n          img.style.display = 'none';\n          const placeholder = img.nextElementSibling;\n          if (placeholder && placeholder.classList.contains('icon-placeholder')) {\n            placeholder.style.display = 'block';\n          }\n        };\n        tempImg.src = img.src;\n      }\n\n      if ('IntersectionObserver' in window) {\n        const imageObserver = new IntersectionObserver(function(entries, observer) {\n          entries.forEach(function(entry) {\n            if (entry.isIntersecting) {\n              applyImage(entry.target);\n              observer.unobserve(entry.target);\n            }\n          });\n        }, { rootMargin: '50px 0px', threshold: 0.1 });\n\n        images.forEach(function(img) { imageObserver.observe(img); });\n      } else {\n        images.forEach(applyImage);\n      }\n    }`;\n}\n\n/** Font-loading class management. */\nfunction generateFontLoadingScript(): string {\n  return `\n    function initFontLoading() {\n      if (document.fonts && document.fonts.ready) {\n        document.body.classList.add('font-loading');\n        document.fonts.ready.then(function() {\n          document.body.classList.add('font-loaded');\n          document.body.classList.remove('font-loading');\n        });\n        setTimeout(function() {\n          if (document.body.classList.contains('font-loading')) {\n            document.body.classList.add('font-loaded');\n            document.body.classList.remove('font-loading');\n          }\n        }, 3000);\n      }\n    }`;\n}\n\n/** Minimal critical CSS inlined in <head> to prevent FOUC before bundle loads. */\nfunction getCriticalCSS(): string {\n  return `\nhtml, body { background-color: var(--background); margin: 0; }\nbody { color: var(--content-primary); }\nmain { width: 100%; margin: 0; padding: 0; }\n.top-nav { position: fixed; top: 0; width: 100%; z-index: 999; }\nheader { width: 100%; margin: var(--s-112, 7rem) 0 var(--s-56, 3.5rem); text-align: center; }\n.flex-grid { display: flex; flex-wrap: wrap; }\n.flex-grid-item { flex: 1 0 4rem; display: flex; align-items: center; justify-content: center; position: relative; height: 9.5rem; }\n@media screen and (max-width: 639px) {\n  header { margin: var(--s-128, 8rem) 0 var(--s-64, 4rem); }\n}`;\n}\n\n// ─────────────────────────────────────────────\n// Build data\n// ─────────────────────────────────────────────\n\nconst VERSION = getVersion();\nconst CDN_BASE_URL = `https://cdn.jsdelivr.net/npm/sargam-icons@${VERSION}/Icons/`;\nconst changelog = loadChangelog();\nconst iconNames = getIconNames(path.join(__dirname, '..', 'Icons', 'Line'));\nconst criticalCSS = getCriticalCSS();\n\n// ─────────────────────────────────────────────\n// Icon grid HTML\n// ─────────────────────────────────────────────\n\nlet iconGridContent = '';\niconNames.forEach((iconName: string, index: number) => {\n  iconGridContent += `\n    <div class=\"flex-grid-item\" data-icon-name=\"${iconName}\" tabindex=\"0\" aria-label=\"${iconName} icon\">\n      <img class=\"downloadable-icon\" data-type=\"line\" data-name=\"${iconName}\" src=\"${CDN_BASE_URL}Line/${iconName}.svg\" width=\"24\" height=\"24\" alt=\"${iconName} line style\" loading=\"lazy\" decoding=\"async\" onerror=\"this.style.display='none'; this.nextElementSibling.style.display='block';\">\n      <div class=\"icon-placeholder\" style=\"display:none; width:24px; height:24px; background:var(--border-muted); border-radius:2px; position:absolute; top:1.25rem; left:50%; transform:translateX(-50%);\"></div>\n      <img class=\"downloadable-icon\" data-type=\"duotone\" data-name=\"${iconName}\" src=\"${CDN_BASE_URL}Duotone/${iconName}.svg\" width=\"24\" height=\"24\" alt=\"${iconName} duotone style\" loading=\"lazy\" decoding=\"async\" onerror=\"this.style.display='none'; this.nextElementSibling.style.display='block';\">\n      <div class=\"icon-placeholder\" style=\"display:none; width:24px; height:24px; background:var(--border-muted); border-radius:2px; position:absolute; top:4rem; left:50%; transform:translateX(-50%);\"></div>\n      <img class=\"downloadable-icon\" data-type=\"fill\" data-name=\"${iconName}\" src=\"${CDN_BASE_URL}Fill/${iconName}.svg\" width=\"24\" height=\"24\" alt=\"${iconName} fill style\" loading=\"lazy\" decoding=\"async\" onerror=\"this.style.display='none'; this.nextElementSibling.style.display='block';\">\n      <div class=\"icon-placeholder\" style=\"display:none; width:24px; height:24px; background:var(--border-muted); border-radius:2px; position:absolute; top:6.75rem; left:50%; transform:translateX(-50%);\"></div>\n    </div>`;\n});\n\n// ─────────────────────────────────────────────\n// Main page (index / template.html)\n// ─────────────────────────────────────────────\n\nconst fullHtmlContent = `<!DOCTYPE html>\n<html lang=\"en\" data-new-ui-theme=\"dark--warm\">\n<head>\n  <meta charset=\"UTF-8\" />\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <link rel=\"preconnect\" href=\"https://cdn.jsdelivr.net\" crossorigin>\n  <link rel=\"dns-prefetch\" href=\"https://cdn.jsdelivr.net\">\n  <link rel=\"shortcut icon\" href=\"favicon.ico\" type=\"image/x-icon\">\n  <title>sargam icons</title>\n  <meta name=\"description\" content=\"A collection of 1,200+ handcrafted, and meticulously optimized open-source icons for your exquisite designs.\">\n  <meta name=\"author\" content=\"@planetabhi\" />\n  <meta property=\"og:title\" content=\"sargam icons\" />\n  <meta property=\"og:url\" content=\"https://sargamicons.com/\" />\n  <meta property=\"og:type\" content=\"website\" />\n  <meta property=\"og:description\" content=\"A collection of 1,200+ handcrafted, and meticulously optimized open-source icons for your exquisite designs.\" />\n  <meta property=\"og:site_name\" content=\"sargam icons\" />\n  <link rel=\"apple-touch-icon\" href=\"icon.png\">\n  <link rel=\"canonical\" href=\"https://sargamicons.com/\">\n  <script type=\"application/ld+json\">\n  {\n    \"@context\": \"https://schema.org\",\n    \"@type\": \"WebSite\",\n    \"name\": \"Sargam Icons\",\n    \"url\": \"https://sargamicons.com/\",\n    \"description\": \"A collection of 1,200+ handcrafted, and meticulously optimized open-source icons for your exquisite designs.\",\n    \"author\": {\n      \"@type\": \"Person\",\n      \"name\": \"Abhimanyu Rana\",\n      \"url\": \"https://planetabhi.com/\"\n    }\n  }\n  </script>\n  <style>${criticalCSS}</style>\n  <script async src=\"https://www.googletagmanager.com/gtag/js?id=G-JWMS1KLBBB\"></script>\n  <script>\n    window.dataLayer = window.dataLayer || [];\n    function gtag(){dataLayer.push(arguments);}\n    gtag('js', new Date());\n    gtag('config', 'G-JWMS1KLBBB');\n  </script>\n</head>\n<body>\n  <a href=\"#main-content\" class=\"skip-link\">Skip to content</a>\n  <nav class=\"top-nav\" aria-label=\"Primary\">\n    <div class=\"top-nav-inner\">\n      <div class=\"lhs\">\n        <a href=\"/\" class=\"brand\" aria-label=\"sargam icons\">\n          ${BRAND_SVG}\n        </a>\n        <span class=\"version-pill\" aria-label=\"Version\">v${VERSION}</span>\n        <div class=\"zoom-separator\" role=\"separator\"></div>\n\n        <label for=\"icon-search\" class=\"sr-only\">Search icons</label>\n        <div class=\"nav-search-wrapper\">\n          <input id=\"icon-search\" type=\"search\" placeholder=\"Search sargam icons...\" aria-label=\"Search icons\" autocomplete=\"off\" inputmode=\"search\" />\n          <button id=\"icon-search-clear\" type=\"button\" class=\"clear-search\" aria-label=\"Clear search\" hidden>\n            <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" fill=\"none\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n              <path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-miterlimit=\"10\" stroke-width=\"1.5\" d=\"m7.757 16.243 8.486-8.486m0 8.486L7.757 7.757M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10\"/>\n            </svg>\n          </button>\n        </div>\n      </div>\n      <div class=\"rhs\">\n        <a href=\"changelog.html\" aria-label=\"Changelog\">Changelog</a>\n\n        <div class=\"zoom-separator\" role=\"separator\"></div>\n\n        <a href=\"https://github.com/planetabhi/sargam-icons\" target=\"_blank\" rel=\"noopener noreferrer\" aria-label=\"GitHub\">GitHub</a>\n\n        <div class=\"zoom-separator\" role=\"separator\"></div>\n\n        <button type=\"button\" class=\"zoom-btn\" id=\"zoom-out\" aria-label=\"Zoom out\" title=\"Zoom out\">\n          <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-miterlimit=\"10\" stroke-width=\"1.5\" d=\"m21 21-4-4m-9-6h6m5 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0\"/></svg>\n        </button>\n\n        <button type=\"button\" class=\"zoom-btn\" id=\"zoom-in\" aria-label=\"Zoom in\" title=\"Zoom in\">\n          <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-miterlimit=\"10\" stroke-width=\"1.5\" d=\"m21 21-4-4m-9-6h6m-3 3V8m8 3a8 8 0 1 1-16 0 8 8 0 0 1 16 0\"/></svg>\n        </button>\n\n        <div class=\"zoom-separator\" role=\"separator\"></div>\n\n        <button type=\"button\" class=\"theme-toggle\" id=\"theme-toggle\" aria-label=\"Toggle theme\" aria-pressed=\"false\">\n          ${THEME_TOGGLE_SVGS}\n        </button>\n      </div>\n    </div>\n  </nav>\n\n  <header>\n    <div class=\"header-content\">\n      <h1>Handcrafted. Optimized. Open-source.</h1>\n      <div class=\"CTAs\">\n        <a href=\"https://www.figma.com/community/file/1152296792728333709/sargam-icons\" aria-label=\"Open in Figma\" rel=\"noopener noreferrer\"><span>Open in Figma</span></a>\n        <a href=\"https://registry.npmjs.org/sargam-icons/-/sargam-icons-${VERSION}.tgz\" aria-label=\"Download all icons\"><span>Download all</span></a>\n      </div>\n    </div>\n  </header>\n\n  <main id=\"main-content\">\n    <section id=\"icon-grid\" aria-labelledby=\"icon-grid-heading\">\n      <h2 id=\"icon-grid-heading\" class=\"sr-only\">Sargam Icons</h2>\n      <div class=\"flex-grid\" aria-label=\"Collection of ${iconNames.length} icons\">\n        ${iconGridContent}\n      </div>\n    </section>\n  </main>\n\n  <footer>\n    <div class=\"footer-content\">\n      <a href=\"/changelog.html\">Changelog</a> &middot; <a href=\"https://github.com/SargamDesign/sargam-icons-react\" target=\"_blank\" rel=\"noopener noreferrer\">React</a> &middot; <a href=\"https://github.com/planetabhi/sargam-icons/blob/main/LICENSE.txt\" target=\"_blank\" rel=\"noopener noreferrer\">License</a> <br /> By <a href=\"https://x.com/planetabhi\" target=\"_blank\" rel=\"noopener noreferrer\">@PLANETABHI</a> &middot; ABHIMANYU RANA 2026 © <br /> <a href=\"https://www.jsdelivr.com/package/npm/sargam-icons\"><img src=\"https://data.jsdelivr.com/v1/package/npm/sargam-icons/badge\" alt=\"jsdelivr package download stats\" style=\"margin: 0 auto; padding-top: 0.5rem; aspect-ratio: auto;\"></a>\n    </div>\n  </footer>\n\n  ${generatePopoverHtml()}\n\n  <script>\n    ${generateFontLoadingScript()}\n    ${generateProgressiveLoadingScript()}\n\n    if (document.readyState === 'loading') {\n      document.addEventListener('DOMContentLoaded', function() {\n        initFontLoading();\n        initProgressiveLoading();\n      });\n    } else {\n      initFontLoading();\n      initProgressiveLoading();\n    }\n\n    ${generatePopoverScript(CDN_BASE_URL, '.downloadable-icon', true)}\n\n    ${generateThemeToggleScript()}\n\n    (function initZoomControls() {\n      var ZOOM_LEVELS = [1, 1.5, 2, 2.5, 3];\n      var currentZoomIndex = 0;\n      var iconGrid = document.getElementById('icon-grid');\n      var zoomInBtn = document.getElementById('zoom-in');\n      var zoomOutBtn = document.getElementById('zoom-out');\n\n      function applyZoom(zoomLevel) {\n        if (!iconGrid) return;\n        iconGrid.style.transform = '';\n        iconGrid.style.zoom = '';\n        iconGrid.style.setProperty('--zoom', String(zoomLevel));\n      }\n\n      function updateButtons() {\n        if (zoomOutBtn) zoomOutBtn.disabled = currentZoomIndex === 0;\n        if (zoomInBtn) zoomInBtn.disabled = currentZoomIndex === ZOOM_LEVELS.length - 1;\n      }\n\n      if (zoomInBtn) {\n        zoomInBtn.addEventListener('click', function() {\n          if (currentZoomIndex < ZOOM_LEVELS.length - 1) {\n            currentZoomIndex++;\n            applyZoom(ZOOM_LEVELS[currentZoomIndex]);\n            updateButtons();\n          }\n        });\n      }\n\n      if (zoomOutBtn) {\n        zoomOutBtn.addEventListener('click', function() {\n          if (currentZoomIndex > 0) {\n            currentZoomIndex--;\n            applyZoom(ZOOM_LEVELS[currentZoomIndex]);\n            updateButtons();\n          }\n        });\n      }\n\n      updateButtons();\n    })();\n  </script>\n\n</body>\n</html>`;\n\nfs.writeFileSync(path.join(__dirname, 'template.html'), fullHtmlContent);\n\n// ─────────────────────────────────────────────\n// Changelog page (changelog.html)\n// ─────────────────────────────────────────────\n\nconst changelogEntriesHtml = changelog.entries\n  .map((entry: ChangelogEntry, index: number) => {\n    const isFirst = index === 0;\n    const iconCount = entry.newIcons.length;\n\n    const iconsHtml = entry.newIcons\n      .map(\n        (iconName: string) => `\n            <button type=\"button\" class=\"changelog-icon-btn downloadable-icon\" data-icon-name=\"si_${iconName}\" data-name=\"si_${iconName}\" data-type=\"line\" title=\"${iconName}\" aria-label=\"${iconName} icon\">\n              <img src=\"${CDN_BASE_URL}Line/si_${iconName}.svg\" width=\"20\" height=\"20\" alt=\"\" loading=\"lazy\" onerror=\"this.parentElement.style.display='none'\" />\n            </button>`,\n      )\n      .join('');\n\n    return `\n            <details class=\"changelog-entry\" ${isFirst ? 'open' : ''}>\n              <summary class=\"changelog-summary\" aria-label=\"Version ${entry.version} — ${iconCount} new icons\">\n                <span class=\"changelog-version\">v${entry.version}</span>\n                <span class=\"changelog-date\">${formatDate(entry.date)}</span>\n                <span class=\"changelog-summary-rhs\">\n                  <span class=\"changelog-count\">${iconCount}</span>\n                  <svg class=\"changelog-chevron\" xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" fill=\"none\" viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"m10 16 4-4-4-4\"/></svg>\n                </span>\n              </summary>\n              <div class=\"changelog-content\">\n                ${iconCount > 0 ? `<div class=\"changelog-icons\">${iconsHtml}</div>` : '<p class=\"changelog-empty\">No new icons in this version</p>'}\n              </div>\n            </details>`;\n  })\n  .join('');\n\nconst changelogPageHtml = `<!DOCTYPE html>\n<html lang=\"en\" data-new-ui-theme=\"dark--warm\">\n<head>\n  <meta charset=\"UTF-8\" />\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <link rel=\"preconnect\" href=\"https://cdn.jsdelivr.net\" crossorigin>\n  <link rel=\"dns-prefetch\" href=\"https://cdn.jsdelivr.net\">\n  <link rel=\"shortcut icon\" href=\"favicon.ico\" type=\"image/x-icon\">\n  <title>Changelog - Sargam Icons</title>\n  <meta name=\"description\" content=\"Version history and new icons for Sargam Icons.\">\n  <meta name=\"author\" content=\"@planetabhi\" />\n  <meta property=\"og:title\" content=\"Changelog - Sargam Icons\" />\n  <meta property=\"og:url\" content=\"https://sargamicons.com/changelog.html\" />\n  <meta property=\"og:type\" content=\"website\" />\n  <meta property=\"og:description\" content=\"Version history and new icons for Sargam Icons.\" />\n  <meta property=\"og:site_name\" content=\"sargam icons\" />\n  <link rel=\"apple-touch-icon\" href=\"icon.png\">\n  <link rel=\"canonical\" href=\"https://sargamicons.com/changelog.html\">\n  <meta name=\"robots\" content=\"index, follow\">\n  <style>${criticalCSS}</style>\n</head>\n<body>\n  <a href=\"#main-content\" class=\"skip-link\">Skip to content</a>\n  <nav class=\"top-nav\" aria-label=\"Primary\">\n    <div class=\"top-nav-inner\">\n      <div class=\"lhs\">\n        <a href=\"/\" class=\"brand\" aria-label=\"sargam icons\">\n          ${BRAND_SVG}\n        </a>\n        <span class=\"version-pill\" aria-label=\"Version\">v${VERSION}</span>\n      </div>\n      <div class=\"rhs\">\n        <a href=\"https://github.com/planetabhi/sargam-icons\" target=\"_blank\" rel=\"noopener noreferrer\" aria-label=\"GitHub\">GitHub</a>\n\n        <div class=\"zoom-separator\" role=\"separator\"></div>\n\n        <button type=\"button\" class=\"theme-toggle\" id=\"theme-toggle\" aria-label=\"Toggle theme\" aria-pressed=\"false\">\n          ${THEME_TOGGLE_SVGS}\n        </button>\n      </div>\n    </div>\n  </nav>\n\n  <main id=\"main-content\" class=\"changelog-page\">\n    <header class=\"changelog-header\">\n      <h1>Changelog</h1>\n    </header>\n\n    <section id=\"changelog\" class=\"changelog-section\" aria-labelledby=\"changelog-heading\">\n      <h2 id=\"changelog-heading\" class=\"sr-only\">Changelog</h2>\n      <div class=\"changelog-container\">\n        ${changelogEntriesHtml}\n      </div>\n    </section>\n  </main>\n\n  <footer>\n    <div class=\"footer-content\">\n      <a href=\"https://www.jsdelivr.com/package/npm/sargam-icons\"><img src=\"https://data.jsdelivr.com/v1/package/npm/sargam-icons/badge\" alt=\"jsdelivr package download stats\" style=\"margin: 0 auto; aspect-ratio: auto;\"></a>\n    </div>\n  </footer>\n\n  ${generatePopoverHtml()}\n\n  <script>\n    ${generatePopoverScript(CDN_BASE_URL, '.changelog-icon-btn', false)}\n\n    ${generateThemeToggleScript()}\n  </script>\n\n</body>\n</html>`;\n\nfs.writeFileSync(path.join(__dirname, 'changelog.html'), changelogPageHtml);\nconsole.log('generated successfully');\n"
  },
  {
    "path": "src/styles/base.scss",
    "content": "@use \"@new-ui/reset\";\n@use \"@new-ui/colors\";\n\n@font-face {\n  font-family: \"Jiva Mono\";\n  src: url(\"../fonts/JivaMono.woff2\") format(\"woff2\");\n  font-weight: normal;\n  font-style: normal;\n  font-display: swap;\n}\n\n:root {\n  font-size: 16px;\n  --sargam: \"Jiva Mono\", ui-monospace, Menlo, Consolas, monospace;\n  scroll-behavior: smooth;\n  scroll-padding-top: 2rem;\n  font-synthesis: none;\n  text-rendering: optimizeLegibility;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  --container-max-width: 100%;\n  --container-padding: var(--s-16);\n  --container-padding-mobile: 1.5rem;\n  --shadow-color: rgb(0 0 0 / 0.04);\n  --elevated-shadow: 0 0 0 1.5px var(--border-muted),\n    0 1px 1px -0.5px var(--shadow-color), 0 3px 3px -1.5px var(--shadow-color);\n  --s-00: 0rem;\n  --s-01: .0625rem;\n  --s-02: .125rem;\n  --s-04: .25rem;\n  --s-06: .375rem;\n  --s-08: .5rem;\n  --s-12: .75rem;\n  --s-16: 1rem;\n  --s-20: 1.25rem;\n  --s-24: 1.5rem;\n  --s-32: 2rem;\n  --s-40: 2.5rem;\n  --s-48: 3rem;\n  --s-56: 3.5rem;\n  --s-64: 4rem;\n  --s-72: 4.5rem;\n  --s-80: 5rem;\n  --s-96: 6rem;\n  --s-112: 7rem;\n  --s-120: 7.5rem;\n  --s-128: 8rem;\n  --s-160: 10rem;\n  --c-text: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' height='24' width='24'%3E%3Cpath fill='black' d='M9 4H11V5H9V4ZM12 6H11V5H12V6ZM13 6H12V18H11V19H9V20H11V19H12V18H13V19H14V20H16V19H14V18H13V6ZM14 5V6H13V5H14ZM14 5V4H16V5H14Z' clip-rule='evenodd' fill-rule='evenodd'/%3E%3Cpath fill='white' d='M15 3H16V4H15H14V3H15ZM14 5V4H13V5H12V4H11V3H10H9V4H8V5H9V6H10H11V18H10H9V19H8V20H9V21H10H11V20H12V19H13V20H14V21H15H16V20H17V19H16V18H15H14V6H15H16V5H17V4H16V5H15H14ZM13 6V5H14V6H13ZM13 18V6H12V5H11V4H10H9V5H10H11V6H12V18H11V19H10H9V20H10H11V19H12V18H13ZM13 18V19H14V20H15H16V19H15H14V18H13Z' clip-rule='evenodd' fill-rule='evenodd'/%3E%3C/svg%3E\"\n    ) 16 16, auto;\n  --c-pointer: url(\"data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M9 4H11V5H9V4ZM9 10V8V5H8V8V10H6V11H5V13H6V14H7V16H8V17H9V18H10V20H11H12H13H14V19H15H16V20H17V19H18V17H19V15H20V10H19V9H18H17V8H15H14V7H12V5H11V8V10H12V8H14V10H15V9H17V11H18V10H19V15H18V17H17V13H16V17H17V18V19H16V18H14V19H13H12H11V18H10V17H9V16H8V13H7H6V11H8V12H9V13H10V12V10H9ZM9 10H8V11H9V10ZM14 13H15V17H14V13ZM13 13H12V17H13V13Z' fill='black'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M9 5H11V8V10H12V8H14V10H15V9H17V10V11H18V10H19V15H18V17H17V13H16V17H17V18V19H16V18H14V19H13H12H11V18H10V17H9V16H8V13H6V11H8V12H9V13H10V12V10H9V8V5ZM12 13V17H13V13H12ZM14 13V17H15V13H14Z' fill='white'/%3E%3C/svg%3E\"\n    ) 10 5, pointer;\n  --c-arrow: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' height='24' width='24'%3E%3Cpath fill='black' d='M9 5H8V16H9V15H10V14H11V15H12V17H13V19H14H15V17H14V15H13V13H14H15H16V12H15V11H14V10H13V9H12V8H11V7H10V6H9V5Z' clip-rule='evenodd' fill-rule='evenodd'/%3E%3Cpath fill='white' d='M8 4H9V5H8V4ZM8 16H7V5H8V16ZM9 16V17H8V16H9ZM10 15V16H9V15H10ZM11 15H10V14H11V15ZM12 17H11V16V15H12V16V17ZM13 19H12V18V17H13V18V19ZM15 19V20H14H13V19H14H15ZM15 17H16V18V19H15V18V17ZM14 15H15V16V17H14V16V15ZM13 14V15H14V14H16V13H17V12H16V11H15V10H14V9H13V8H12V7H11V6H10V5H9V6H10V7H11V8H12V9H13V10H14V11H15V12H16V13H13V14Z' clip-rule='evenodd' fill-rule='evenodd'/%3E%3C/svg%3E\"\n    ) 12 12, text;\n  --c-grab: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' height='24' width='24'%3E%3Cpath fill='black' d='M13 4H11V5H10V6H9V5H7V6H6V8H7V10H5V11H4V13H5V14H6V16H7V17H8V18H9V20H10H11H12H13V19H14H15V20H16V19H17V17H18V15H19V12H20V8H19V7H18V8H17V6H16V5H14H13V4ZM13 5V10H14V6H16V11H17V9H18V8H19V12H18V15H17V17H16V13H15V17H16V19H15V18H14H13V19H12H11H10V18H9V17H8V16H7V14H6V13H5V11H7V12H8V13H9V10H8V8H7V6H9V8H10V10H11V5H13ZM8 10V11H7V10H8ZM14 13H13V17H14V13ZM11 13H12V17H11V13Z' clip-rule='evenodd' fill-rule='evenodd'/%3E%3Cpath fill='white' d='M13 5H11V10H10V8H9V6H7V8H8V10H9V12V13H8V12H7V11H5V13H6V14H7V16H8V17H9V18H10V19H11H12H13V18H15V19H16V18V17H17V15H18V12H19V8H18V9H17V11H16V10V6H14V10H13V5ZM14 13H13V17H14V13ZM12 13H11V17H12V13ZM16 13H15V17H16V13Z' clip-rule='evenodd' fill-rule='evenodd'/%3E%3C/svg%3E\") 12 12, grab;\n}\n\nhtml[data-new-ui-theme*=\"dark\"] {\n  --link: #E08742;\n  --link-hover: #E9F37E;\n  --link-visited: #E08742;\n  --border-focus: var(--border);\n}\n\nhtml[data-new-ui-theme*=\"light\"] {\n  --link: #2972FF;\n  --link-hover: var(--black);\n  --link-visited: #2972FF;\n  --border-focus: var(--border);\n}\n\n@mixin container-base {\n  max-width: var(--container-max-width);\n  width: 100%;\n  margin-inline: auto;\n  padding-inline: var(--container-padding);\n  box-sizing: border-box;\n}\n\n// Shared styles for icon-style nav buttons (zoom-btn, theme-toggle)\n@mixin icon-btn {\n  width: 32px;\n  height: 32px;\n  padding: 0;\n  cursor: var(--c-pointer);\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  color: var(--content-secondary);\n  background: none;\n  border-left-color: var(--border-muted);\n  border-top-color: var(--border-muted);\n  border-right-color: var(--border);\n  border-bottom-color: var(--border);\n  border-style: solid;\n  border-width: 1px;\n\n  &:hover:not(:disabled) {\n    color: var(--content-primary);\n    background: var(--background-high-contrast);\n  }\n\n  &:focus {\n    outline: 0;\n  }\n\n  svg {\n    width: 16px;\n    height: 16px;\n    display: block;\n  }\n}\n\n.container {\n  @include container-base;\n}\n\nhtml,\nbody {\n  background-color: var(--background);\n  cursor: var(--c-arrow);\n}\n\nbody {\n  color: var(--content-primary);\n  font: 400 1em/1.5 var(--sargam);\n  margin: var(--s-00);\n  font-kerning: normal;\n  overflow-wrap: break-word;\n  font-synthesis: none;\n  text-rendering: optimizeLegibility;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n\n  &.font-loading {\n    font-family: system-ui, sans-serif;\n  }\n\n  &.font-loaded {\n    font-family: var(--sargam);\n  }\n}\n\np,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\nli,\npre,\ncode,\nstrong,\ndl,\ndt {\n  cursor: var(--c-text);\n}\n\na,\na * {\n  cursor: var(--c-pointer);\n}\n\nmain {\n  width: 100%;\n  margin: 0;\n  padding: 0;\n\n  #icon-grid .flex-grid {\n    @include container-base;\n  }\n}\n\nimg,\nsvg {\n  max-width: 100%;\n  height: auto;\n  aspect-ratio: 1 / 1;\n}\n\n#icon-grid {\n  margin-bottom: var(--s-24);\n}\n\n:focus-visible {\n  outline: 1.5px solid var(--border-focus);\n  outline-offset: var(--s-02);\n  box-shadow: none;\n}\n\n.flex-grid-item:focus,\n.flex-grid-item:focus-visible {\n  outline: none;\n}\n\n.flex-grid-item:focus img,\n.flex-grid-item:focus-visible img {\n  outline-offset: 2px;\n}\n\n.skip-link {\n  position: absolute;\n  top: -40px;\n  left: 6px;\n  background: var(--background-primary);\n  color: var(--content-secondary);\n  padding: var(--s-04);\n  text-decoration: none;\n  z-index: 1000;\n  font-size: 0.875rem;\n\n  &:focus {\n    top: 6px;\n  }\n}\n\n.sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip: rect(0, 0, 0, 0);\n  white-space: nowrap;\n  border: 0;\n}\n\n::selection {\n  background: var(--content-inked);\n  color: var(--background);\n  text-shadow: none;\n}\n\na {\n  color: var(--link);\n  text-decoration: underline;\n  text-decoration-thickness: 1px;\n  text-underline-offset: 3px;\n  position: relative;\n  text-transform: uppercase;\n\n  &:hover {\n    color: var(--link-hover);\n  }\n\n  &:visited {\n    color: var(--link-visited);\n  }\n}\n\nimg,\n.flex-grid-item img[data-type=\"line\"],\n.flex-grid-item img[data-type=\"duotone\"],\n.flex-grid-item img[data-type=\"fill\"] {\n  position: relative;\n}\n\nheader {\n  width: 100%;\n  margin: var(--s-112) 0 var(--s-56);\n  text-align: center;\n  padding: 0;\n\n  .header-content {\n    @include container-base;\n    text-align: center;\n    max-inline-size: 40rem;\n  }\n\n  h1 {\n    font: 400 1.75rem/2.25rem var(--sargam);\n    color: var(--content-primary);\n    margin-bottom: var(--s-20);\n    font-synthesis: none;\n    text-rendering: optimizeLegibility;\n\n    span {\n      font: italic normal 1em var(--sargam);\n    }\n  }\n\n  .CTAs {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    gap: var(--s-08);\n    min-width: max-content;\n    box-sizing: border-box;\n\n    a {\n      font: 400 0.875rem/1.563rem var(--sargam);\n      padding: var(--s-02) var(--s-12);\n      text-transform: uppercase;\n      text-decoration-line: none;\n      cursor: var(--c-pointer);\n      color: var(--link);\n      background: var(--background-high-contrast);\n\n      border-left-color: var(--border-muted);\n      border-top-color: var(--border-muted);\n      border-right-color: var(--border-strong);\n      border-bottom-color: var(--border-strong);\n      border-style: solid;\n      border-width: 1px;\n\n      &:hover {\n        color: var(--link-hover);\n      }\n\n      &:active {\n        border-left-color: var(--border-strong);\n        border-top-color: var(--border-strong);\n        border-right-color: var(--border-muted);\n        border-bottom-color: var(--border-muted);\n      }\n    }\n  }\n}\n\n.flex-grid {\n  display: flex;\n  flex-wrap: wrap;\n\n  &-item {\n    flex: 1 0 calc(var(--s-64) * var(--zoom, 1));\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    position: relative;\n    overflow: hidden;\n    margin: 0 calc(var(--s-02) * var(--zoom, 1)) calc(var(--s-04) * var(--zoom, 1));\n    height: calc(9.5rem * var(--zoom, 1));\n    background: var(--background-high-contrast);\n    content-visibility: auto;\n    contain-intrinsic-size: 0 calc(9.5rem * var(--zoom, 1));\n    border: 1.5px dotted var(--border-muted);\n\n    img {\n\n      &[data-type=\"line\"],\n      &[data-type=\"duotone\"],\n      &[data-type=\"fill\"] {\n        width: calc(24px * var(--zoom, 1));\n        height: calc(24px * var(--zoom, 1));\n        position: absolute;\n        object-fit: contain;\n        aspect-ratio: 1 / 1;\n        cursor: var(--c-pointer);\n\n        &:hover {\n          background: var(--content-secondary-alt);\n          color: var(--content-primary);\n        }\n      }\n\n      &[data-type=\"line\"] {\n        top: calc(1.25rem * var(--zoom, 1));\n      }\n\n      &[data-type=\"duotone\"] {\n        top: calc(4rem * var(--zoom, 1));\n      }\n\n      &[data-type=\"fill\"] {\n        top: calc(6.75rem * var(--zoom, 1));\n      }\n    }\n  }\n}\n\n.top-nav {\n  justify-content: flex-end;\n  align-items: center;\n  width: 100%;\n  display: flex;\n  z-index: 999;\n  position: fixed;\n  inset: 0% 0% auto;\n  border-bottom: 1px solid var(--border-muted);\n  background: var(--background);\n\n  &-inner {\n    max-width: var(--container-max-width);\n    width: 100%;\n    margin: 0 auto;\n    padding: var(--s-08);\n    box-sizing: border-box;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    gap: var(--s-06);\n  }\n\n  .lhs,\n  .rhs {\n    display: flex;\n    align-items: center;\n    gap: var(--s-06);\n  }\n\n  a {\n    color: var(--content-secondary);\n    text-decoration: none;\n    font: 400 14px/1.5 var(--sargam);\n\n    &:hover {\n      text-decoration: none;\n      color: var(--content-primary);\n    }\n  }\n\n  .nav-search-wrapper {\n    position: relative;\n    margin-left: var(--s-00);\n\n    input[type=\"search\"] {\n      width: 12rem;\n      box-sizing: border-box;\n      background-color: var(--background);\n      box-shadow: none;\n      appearance: none;\n      border: 1px solid var(--border-muted);\n      text-align: left;\n      font: 400 0.875rem/1.5 var(--sargam);\n      padding: var(--s-02) var(--s-06);\n      color: var(--content-primary);\n      caret-color: var(--content-primary);\n      text-transform: uppercase;\n      cursor: var(--c-text);\n      --input-shadow: 2px 2px 0px 0px var(--background-secondary);\n      box-shadow: var(--input-shadow) inset;\n\n      &::placeholder {\n        color: var(--content-placeholder);\n      }\n\n      &:focus {\n        border-color: var(--border-focus);\n        outline: 0;\n        box-shadow: 0 0 0 .5px var(--border-focus);\n      }\n\n      &::-webkit-search-cancel-button {\n        display: none;\n      }\n    }\n\n    .clear-search {\n      position: absolute;\n      right: var(--s-04);\n      top: 50%;\n      transform: translateY(-50%);\n      width: 24px;\n      height: 24px;\n      border: none;\n      background: transparent;\n      padding: 0;\n      margin: 0;\n      cursor: var(--c-pointer);\n      color: var(--content-secondary-alt);\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n\n      &:focus {\n        outline: 0;\n        box-shadow: 0 0 0 .5px var(--border-focus);\n      }\n\n      &[hidden] {\n        display: none;\n      }\n\n      svg {\n        width: 16px;\n        height: 16px;\n      }\n    }\n  }\n}\n\n@keyframes rotate {\n  from {\n    transform: rotate(0deg);\n  }\n\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n.brand {\n  display: inline-flex;\n  align-items: center;\n  gap: 8px;\n\n  svg {\n    animation: rotate 4s linear infinite;\n    animation-play-state: paused;\n  }\n\n  svg:hover,\n  &:hover svg {\n    animation-play-state: running;\n  }\n}\n\n.version-pill {\n  font: 400 14px/1 var(--sargam);\n  padding: var(--s-02) var(--s-04);\n  color: var(--content-primary);\n  background: var(--background-selected);\n}\n\n.zoom-separator {\n  width: 1px;\n  height: 1rem;\n  background: var(--border-muted);\n  margin: 0 var(--s-04);\n}\n\n.zoom-btn {\n  @include icon-btn;\n\n  &:active:not(:disabled) {\n    border-left-color: var(--border);\n    border-top-color: var(--border);\n    border-right-color: var(--border-muted);\n    border-bottom-color: var(--border-muted);\n  }\n\n  &:disabled {\n    opacity: 0.4;\n    cursor: not-allowed;\n    border: 1px solid transparent;\n  }\n}\n\n.theme-toggle {\n  @include icon-btn;\n\n  &:active {\n    border-left-color: var(--border);\n    border-top-color: var(--border);\n    border-right-color: var(--border-muted);\n    border-bottom-color: var(--border-muted);\n  }\n}\n\n.blank {\n  height: 0;\n}\n\nfooter {\n  width: 100%;\n  padding: 1rem 0;\n  margin: 0;\n  text-align: center;\n  font: normal 0.875rem/1.5 var(--sargam);\n  color: var(--content-secondary);\n  border-top: 1px dotted var(--border-muted);\n\n  .footer-content {\n    @include container-base;\n  }\n\n  a {\n    color: var(--link-hover);\n  }\n}\n\n.icon-popover {\n  position: fixed;\n  inset: 0;\n  z-index: 9999;\n  pointer-events: none;\n\n  &[hidden] {\n    display: none;\n  }\n\n  &[hidden] .popover-content {\n    opacity: 0;\n    visibility: hidden;\n    pointer-events: none;\n    transition-delay: 0s;\n  }\n}\n\n.popover-content {\n  position: absolute;\n  left: 50%;\n  top: 50%;\n  transform: translate(-50%, -50%);\n  z-index: 1;\n  background: var(--background-high-contrast);\n  width: 100%;\n  max-width: 16rem;\n  pointer-events: auto;\n  border: 2px solid var(--black);\n  visibility: visible;\n\n  --ease-in: cubic-bezier(0.4, 0, 0.2, 1);\n  transition-property: opacity, visibility;\n  transition-duration: .2s;\n  transition-timing-function: var(--ease-in);\n  transition-delay: .2s;\n  opacity: 1;\n\n  --popover-shadow: 4px 4px 0px 0px var(--border);\n  box-shadow: var(--popover-shadow);\n}\n\n.popover-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: var(--s-00) var(--s-00) var(--s-00) var(--s-06);\n  border-bottom: 2px solid var(--background-high-contrast);\n  cursor: var(--c-grab);\n  user-select: none;\n  position: relative;\n  z-index: 999;\n\n  &:active {\n    cursor: grabbing;\n  }\n\n  .popover-icon-name {\n    font: 400 0.875rem/1.5 var(--sargam);\n    color: var(--content-primary);\n    margin: 0;\n    text-transform: uppercase;\n    cursor: var(--c-grab);\n    max-width: 16ch;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n\n  .popover-close {\n    width: 28px;\n    height: 28px;\n    padding: 0;\n    margin-left: var(--s-08);\n    cursor: var(--c-pointer);\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    color: var(--content-secondary);\n    background: none;\n    transition: transform 0.1s ease;\n    border: none;\n\n    &:hover {\n      color: var(--content-primary);\n      background: var(--background);\n    }\n\n    &:focus-visible {\n      outline: 0;\n      box-shadow: 0 0 0 .5px var(--border-focus);\n    }\n\n    &:active {\n      transform: scale(0.97);\n    }\n\n    svg {\n      width: 24px;\n      height: 24px;\n      display: block;\n    }\n  }\n}\n\nimg.popover-icon {\n  box-shadow: 0 0 0 1px var(--link-hover);\n}\n\n.popover-preview {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: var(--s-32);\n  background: var(--background);\n\n  .popover-icon {\n    width: 96px;\n    height: 96px;\n    object-fit: contain;\n  }\n}\n\n.popover-preview:after {\n  content: \"\";\n  position: absolute;\n  inset: 0;\n  z-index: 998;\n  background: repeating-linear-gradient(0deg, var(--background-secondary) 0, var(--background-secondary) 2px, transparent 2px, transparent 4px);\n  animation: lines 3s ease-out infinite;\n  opacity: .2;\n  mix-blend-mode: color-burn;\n  pointer-events: none;\n}\n\n@keyframes lines {\n  to {\n    background-position: 0 25px\n  }\n}\n\n.popover-menu {\n  display: flex;\n  flex-direction: column;\n  gap: 0;\n  border-top: .5px solid var(--border-muted);\n  background: var(--background-high-contrast);\n  position: relative;\n  z-index: 999;\n}\n\n.popover-variants {\n  display: flex;\n  gap: 1px;\n  position: relative;\n  z-index: 999;\n  background: var(--border-muted);\n  border-top: 1px solid var(--border-muted);\n  border-bottom: 1px solid var(--border-muted);\n}\n\n.popover-variant {\n  flex: 1;\n  padding: var(--s-06) var(--s-08);\n  font: 400 0.75rem/1 var(--sargam);\n  text-transform: uppercase;\n  color: var(--content-secondary);\n  background: var(--background);\n  border: none;\n  cursor: var(--c-pointer);\n  transition: background 0.15s ease, color 0.15s ease;\n\n  &:hover {\n    background: var(--background-selected);\n  }\n\n  &.active {\n    color: var(--content-primary);\n    background: var(--background-selected);\n  }\n}\n\n.popover-menu-item {\n  display: flex;\n  align-items: center;\n  gap: var(--s-08);\n  padding: var(--s-04);\n  font: 400 0.875rem/1.5 var(--sargam);\n  color: var(--content-secondary);\n  background: none;\n  border: none;\n  border-bottom: .5px solid var(--border-muted);\n  cursor: var(--c-pointer);\n  text-align: left;\n  text-transform: uppercase;\n  transition: transform 0.1s ease;\n\n  &:last-child {\n    border-bottom: none;\n  }\n\n  &:hover {\n    color: var(--link-hover);\n  }\n\n  &:focus {\n    outline: 0;\n    box-shadow: inset 0 0 0 .5px var(--border-focus);\n  }\n\n  &:active {\n    transform: scale(0.98);\n  }\n\n  svg {\n    width: 16px;\n    height: 16px;\n    flex-shrink: 0;\n  }\n\n  span {\n    flex: 1;\n  }\n}\n\n// ─────────────────────────────────────────────\n// Responsive overrides\n// ─────────────────────────────────────────────\n@media screen and (max-width: 639px) {\n  :root {\n    --container-padding: var(--container-padding-mobile);\n  }\n\n  header {\n    margin: var(--s-128) 0 var(--s-64);\n  }\n\n\n  .top-nav-inner {\n    margin: 0 auto 0;\n  }\n\n  .popover-content {\n    left: 50% !important;\n    top: 50% !important;\n    transform: translate(-50%, -50%) !important;\n    margin: var(--s-12);\n    min-width: auto;\n  }\n\n  .popover-header {\n    cursor: default !important;\n    user-select: auto;\n\n    &:active {\n      cursor: default !important;\n    }\n  }\n\n  .nav-search-wrapper {\n    display: none;\n  }\n\n  .top-nav .lhs {\n    gap: var(--s-04);\n  }\n\n  // Changelog responsive\n  .changelog-summary {\n    flex-wrap: wrap;\n    gap: var(--s-06);\n  }\n\n  .changelog-count {\n    width: 100%;\n  }\n\n  .changelog-icon-btn {\n    width: 32px;\n    height: 32px;\n  }\n}\n\n// Changelog Section Styles\n.changelog-section {\n  width: 100%;\n  margin: var(--s-64) 0;\n  border-top: 1px dotted var(--border-muted);\n  padding-top: var(--s-48);\n}\n\n.changelog-page {\n  padding-top: var(--s-64);\n}\n\n.changelog-page .changelog-section {\n  border-top: none;\n  margin-top: 0;\n  padding-top: 0;\n}\n\n.changelog-header {\n  text-align: center;\n  margin: var(--s-48) 0 var(--s-32);\n\n  h1 {\n    font: 400 1.5rem/1.5 var(--sargam);\n    color: var(--content-primary);\n    margin: 0 0 var(--s-08);\n    text-transform: uppercase;\n  }\n\n  p {\n    font: 400 0.875rem/1.5 var(--sargam);\n    color: var(--content-secondary);\n    margin: 0;\n  }\n}\n\n.changelog-empty {\n  font: 400 0.875rem/1.5 var(--sargam);\n  color: var(--content-secondary-alt);\n  margin: 0;\n  padding: var(--s-08);\n}\n\n.top-nav a.active {\n  color: var(--link);\n  text-decoration: underline;\n}\n\n.changelog-container {\n  max-width: 32rem;\n  width: 100%;\n  margin: 0 auto;\n  padding-inline: var(--container-padding);\n  box-sizing: border-box;\n  gap: 1px;\n  display: flex;\n  flex-direction: column;\n\n  h2 {\n    font: 400 1.25rem/1.5 var(--sargam);\n    color: var(--content-primary);\n    margin: 0 0 var(--s-04);\n    text-transform: uppercase;\n    text-align: center;\n  }\n}\n\n.changelog-entry {\n  background: var(--background-high-contrast);\n\n  &[open] {\n    .changelog-summary::after {\n      transform: rotate(180deg);\n    }\n  }\n}\n\n.changelog-summary {\n  display: flex;\n  align-items: center;\n  gap: var(--s-12);\n  padding: var(--s-04) var(--s-12);\n  cursor: var(--c-pointer);\n  list-style: none;\n  font: 400 14px/1 var(--sargam);\n  text-transform: uppercase;\n\n  &::-webkit-details-marker {\n    display: none;\n  }\n\n  &:hover {\n    background: var(--background-selected);\n  }\n}\n\n.changelog-summary-rhs {\n  display: flex;\n  align-items: center;\n  gap: var(--s-08);\n  margin-left: auto;\n}\n\n.changelog-chevron {\n  width: 20px;\n  height: 20px;\n  transition: transform 0.2s ease;\n  color: var(--content-secondary);\n\n  .changelog-entry[open] & {\n    transform: rotate(90deg);\n  }\n}\n\n.changelog-version {\n  color: var(--link);\n  font-weight: 400;\n  min-width: 4rem;\n}\n\n.changelog-date {\n  color: var(--content-secondary);\n}\n\n.changelog-count {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  min-width: 1.5rem;\n  padding: 0 var(--s-06);\n  font: 400 14px/1 var(--sargam);\n  color: var(--content-secondary);\n  background: var(--background);\n  border: 1px dotted var(--border-muted);\n}\n\n.changelog-content {\n  padding: var(--s-16);\n  border-top: 1px dotted var(--border-muted);\n  background: var(--background);\n}\n\n.changelog-icons {\n  display: flex;\n  flex-wrap: wrap;\n  gap: var(--s-06);\n  align-items: center;\n}\n\n.changelog-icon-btn {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 36px;\n  height: 36px;\n  padding: var(--s-06);\n  background: var(--background-high-contrast);\n  border: 1px solid var(--border-muted);\n  cursor: var(--c-pointer);\n  transition: background 0.15s ease;\n  overflow: hidden;\n\n  &:hover {\n    background: var(--background-selected);\n    border-color: var(--border);\n  }\n\n  &:focus-visible {\n    outline: 1.5px solid var(--border-focus);\n    outline-offset: 1px;\n  }\n\n  img {\n    width: 20px;\n    height: 20px;\n    object-fit: contain;\n\n    &[alt] {\n      font-size: 0;\n      color: transparent;\n    }\n  }\n}\n\n\n\n[data-new-ui-theme=\"dark--warm\"] {\n\n  img[data-type=\"line\"],\n  img[data-type=\"duotone\"],\n  img[data-type=\"fill\"],\n  .popover-icon,\n  .changelog-icon-btn img {\n    filter: invert(1) sepia(100%) hue-rotate(180deg);\n  }\n\n}"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"ESNext\",\n    \"lib\": [\n      \"ES2022\",\n      \"DOM\",\n      \"DOM.Iterable\"\n    ],\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"allowJs\": true,\n    \"checkJs\": false,\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"declaration\": false,\n    \"declarationMap\": false,\n    \"sourceMap\": false,\n    \"noEmit\": true,\n    \"types\": [\n      \"node\"\n    ]\n  },\n  \"include\": [\n    \"src/**/*\",\n    \"*.ts\"\n  ],\n  \"exclude\": [\n    \"node_modules\",\n    \"dist\",\n    \"Icons\"\n  ]\n}"
  }
]