Full Code of planetabhi/sargam-icons for AI

main 5ce9f8c56f55 cached
20 files
88.1 KB
27.9k tokens
38 symbols
1 requests
Download .txt
Repository: planetabhi/sargam-icons
Branch: main
Commit: 5ce9f8c56f55
Files: 20
Total size: 88.1 KB

Directory structure:
gitextract_hojju54v/

├── .gitignore
├── .nvmrc
├── LICENSE.txt
├── README.md
├── functions/
│   └── _middleware.ts
├── package.json
├── public/
│   ├── .well-known/
│   │   ├── agent-skills/
│   │   │   ├── index.json
│   │   │   └── sargam-icons/
│   │   │       └── SKILL.md
│   │   ├── api-catalog
│   │   └── mcp/
│   │       └── server-card.json
│   ├── _headers
│   ├── robots.txt
│   └── sitemap.xml
├── rspack.config.ts
├── scripts/
│   └── generate-changelog.ts
├── src/
│   ├── fonts/
│   │   └── JivaMono.otf
│   ├── index.ts
│   ├── sargam.ts
│   └── styles/
│       └── base.scss
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
node_modules
dist
src/assets
src/template.html
src/changelog.html
src/changelog.json
.env
.DS_Store
**/.DS_Store

================================================
FILE: .nvmrc
================================================
22


================================================
FILE: LICENSE.txt
================================================
MIT License

Copyright (c) 2026 Abhimanyu Rana @planetabhi

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to 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:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
```ascii
   ____       ___        _____       __  ___    
  / __/__ _  / _ \___   / ___/__ _  /  |/  /__ _
 _\ \/ _ `/ / , _/ -_) / (_ / _ `/ / /|_/ / _ `/
/___/\_,_/ /_/|_|\__/  \___/\_,_/ /_/  /_/\_,_/ 
   _______     ___    ________     __  ___      
  / __/ _ |   / _ \  / ___/ _ |   /  |/  /      
 _\ \/ __ |  / , _/ / (_ / __ |  / /|_/ /       
/___/_/ |_| /_/|_|  \___/_/ |_| /_/  /_/        
```

## Sargam Icons
A collection of 1200+ handcrafted open-source icons for your exquisite designs.

[[sargamicons.com]](https://sargamicons.com/) ♪♪♪ ヽ(ˇ∀ˇ )ゞ

- Built using SVG stroke, providing maximum flexibility on styling.
- Optimized vector paths and SVGs for better performance.
- Request a new icon by creating an issue.


[![jsDelivr downloads badge](https://data.jsdelivr.com/v1/package/npm/sargam-icons/badge)](https://www.jsdelivr.com/package/npm/sargam-icons)


================================================
FILE: functions/_middleware.ts
================================================
/**
 * Cloudflare Pages Function middleware for Markdown content negotiation.
 *
 * When a request includes `Accept: text/markdown`, this middleware fetches
 * the HTML response from the origin and converts it to a simplified Markdown
 * representation. Browsers and other clients still receive normal HTML.
 *
 * References:
 *   - RFC 8288 (Web Linking)
 *   - https://developers.cloudflare.com/fundamentals/reference/markdown-for-agents/
 */

// Cloudflare Pages Function types (minimal inline definitions so the file
// works without @cloudflare/workers-types installed locally).
interface EventContext<E = unknown> {
  request: Request;
  next: () => Promise<Response>;
  env: E;
}
type PagesFunction<E = unknown> = (ctx: EventContext<E>) => Promise<Response> | Response;

interface Env {}

function acceptsMarkdown(request: Request): boolean {
  const accept = request.headers.get("Accept") || "";
  return accept.includes("text/markdown");
}

/** Decode common HTML entities. */
function decodeEntities(text: string): string {
  return text
    .replace(/&amp;/g, "&")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">")
    .replace(/&quot;/g, '"')
    .replace(/&#39;/g, "'")
    .replace(/&nbsp;/g, " ")
    .replace(/&#(\d+);/g, (_, n) => String.fromCharCode(Number(n)));
}

/** Decode entities, collapse whitespace, and JSON-quote for safe front-matter. */
function sanitizeMeta(raw: string): string {
  const cleaned = decodeEntities(raw).replace(/\s+/g, " ").trim();
  return JSON.stringify(cleaned);
}

/** Very small HTML-to-Markdown converter for static content pages. */
function htmlToMarkdown(html: string): string {
  let md = html;

  // Extract <title>
  const titleMatch = md.match(/<title[^>]*>(.*?)<\/title>/is);
  const title = titleMatch ? titleMatch[1].trim() : "";

  // Extract <meta name="description">
  const descMatch = md.match(
    /<meta\s+name=["']description["']\s+content=["'](.*?)["']/is,
  );
  const description = descMatch ? descMatch[1].trim() : "";

  // Strip everything outside <body>
  const bodyMatch = md.match(/<body[^>]*>([\s\S]*)<\/body>/i);
  md = bodyMatch ? bodyMatch[1] : md;

  // Remove <script>, <style>, <nav>, <svg>, <noscript> blocks
  md = md.replace(/<(script|style|nav|svg|noscript)\b[\s\S]*?<\/\1>/gi, "");

  // Convert headings
  md = md.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, "\n# $1\n");
  md = md.replace(/<h2[^>]*>([\s\S]*?)<\/h2>/gi, "\n## $1\n");
  md = md.replace(/<h3[^>]*>([\s\S]*?)<\/h3>/gi, "\n### $1\n");
  md = md.replace(/<h4[^>]*>([\s\S]*?)<\/h4>/gi, "\n#### $1\n");

  // Convert links
  md = md.replace(/<a\s+[^>]*href=["']([^"']*)["'][^>]*>([\s\S]*?)<\/a>/gi, "[$2]($1)");

  // Convert paragraphs and divs to line breaks
  md = md.replace(/<\/?(p|div|section|article|main|header|footer)\b[^>]*>/gi, "\n");

  // Convert <br> tags
  md = md.replace(/<br\s*\/?>/gi, "\n");

  // Convert <li>
  md = md.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, "- $1\n");

  // Convert <strong>/<b> and <em>/<i>
  md = md.replace(/<(strong|b)\b[^>]*>([\s\S]*?)<\/\1>/gi, "**$2**");
  md = md.replace(/<(em|i)\b[^>]*>([\s\S]*?)<\/\1>/gi, "*$2*");

  // Convert <code>
  md = md.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, "`$1`");

  // Strip all remaining HTML tags
  md = md.replace(/<[^>]+>/g, "");

  // Decode common HTML entities
  md = decodeEntities(md);

  // Collapse excessive blank lines
  md = md.replace(/\n{3,}/g, "\n\n").trim();

  // Prepend front-matter-style header
  const header = [
    "---",
    title ? `title: ${sanitizeMeta(title)}` : null,
    description ? `description: ${sanitizeMeta(description)}` : null,
    "---",
  ]
    .filter(Boolean)
    .join("\n");

  return `${header}\n\n${md}\n`;
}

/** Rough token estimate: ~1 token per 4 characters for English text. */
function estimateTokens(text: string): number {
  return Math.ceil(text.length / 4);
}

export const onRequest: PagesFunction<Env> = async (context) => {
  if (!acceptsMarkdown(context.request)) {
    return context.next();
  }

  // Fetch the original HTML response from the origin
  const response = await context.next();

  const contentType = response.headers.get("Content-Type") || "";
  if (!contentType.includes("text/html")) {
    return response;
  }

  const html = await response.text();
  const markdown = htmlToMarkdown(html);
  const tokens = estimateTokens(markdown);

  const headers = new Headers(response.headers);
  headers.delete("Content-Length"); // body size changes
  headers.set("Content-Type", "text/markdown; charset=utf-8");
  headers.set("Vary", "Accept");
  headers.set("x-markdown-tokens", String(tokens));

  return new Response(markdown, {
    status: response.status,
    headers,
  });
};


================================================
FILE: package.json
================================================
{
  "name": "sargam-icons",
  "version": "1.6.7",
  "description": "A collection of 1200+ open-source icons.",
  "scripts": {
    "clean": "rimraf package *.tgz",
    "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",
    "generate-icons": "bun run clean && bun run compress",
    "generate-changelog": "bun run scripts/generate-changelog.ts",
    "generate-template": "bun run generate-changelog && bun run src/sargam.ts",
    "build": "bun run generate-template && bunx rspack build",
    "dev": "bun run generate-template && bunx rspack serve",
    "build:dev": "bun run generate-template && bunx rspack build --mode development",
    "preview": "bun run build && bunx serve dist"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/planetabhi/sargam-icons.git"
  },
  "files": [
    "Icons/"
  ],
  "keywords": [
    "icons",
    "line-icons",
    "fill-icons",
    "duotone-icons",
    "sargam-icons",
    "svg",
    "react",
    "optimized",
    "figma",
    "compressed",
    "sargam"
  ],
  "author": "@planetabhi",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/planetabhi/sargam-icons/issues"
  },
  "homepage": "https://sargamicons.com/",
  "devDependencies": {
    "@babel/core": "^7.29.0",
    "@babel/preset-env": "^7.29.2",
    "@babel/preset-typescript": "^7.28.5",
    "@new-ui/colors": "^2.2.5",
    "@new-ui/reset": "^0.1.2",
    "@rspack/cli": "^2.0.1",
    "@rspack/core": "^2.0.1",
    "@rspack/dev-server": "^2.0.1",
    "@sargamdesign/colors": "^3.1.0",
    "@svgr/core": "^8.1.0",
    "@types/node": "^25.6.0",
    "babel-loader": "^10.1.1",
    "css-loader": "^7.1.4",
    "mini-css-extract-plugin": "^2.10.2",

    "rimraf": "^6.1.3",
    "sass-embedded": "^1.99.0",
    "sass-loader": "^16.0.7",
    "spacings": "^0.1.0",

    "svgo": "^4.0.1",
    "typescript": "^6.0.3"
  },
  "main": "index.js"
}

================================================
FILE: public/.well-known/agent-skills/index.json
================================================
{
  "$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
  "skills": [
    {
      "name": "sargam-icons",
      "type": "skill-md",
      "description": "Discover and browse 1,200+ open-source icons in Line, Fill, and Duotone styles.",
      "url": "https://sargamicons.com/.well-known/agent-skills/sargam-icons/SKILL.md",
      "digest": "sha256:6d28bf89e3cfc983abf201ccb656f73efd4557046200c12c5a7896828db70d1d"
    }
  ]
}


================================================
FILE: public/.well-known/agent-skills/sargam-icons/SKILL.md
================================================
# Sargam Icons Discovery

Discover and browse 1,200+ open-source icons in Line, Fill, and Duotone styles from Sargam Icons.

## Usage

- Browse icons at https://sargamicons.com/
- Download SVG icons from `/icons/Line/`, `/icons/Fill/`, `/icons/Duotone/`
- View changelog at https://sargamicons.com/changelog.html
- Source code at https://github.com/planetabhi/sargam-icons

## Icon Styles

- **Line** — Outline/stroke icons
- **Fill** — Solid filled icons
- **Duotone** — Two-tone icons

## License

MIT


================================================
FILE: public/.well-known/api-catalog
================================================
{
  "linkset": [
    {
      "anchor": "https://sargamicons.com/",
      "service-doc": [
        {
          "href": "https://github.com/planetabhi/sargam-icons",
          "type": "text/html"
        }
      ],
      "service-desc": [
        {
          "href": "https://raw.githubusercontent.com/planetabhi/sargam-icons/main/README.md",
          "type": "text/markdown"
        }
      ],
      "describes": [
        {
          "href": "https://sargamicons.com/"
        }
      ]
    }
  ]
}


================================================
FILE: public/.well-known/mcp/server-card.json
================================================
{
  "serverInfo": {
    "name": "sargam-icons",
    "version": "1.6.7",
    "description": "A collection of 1,200+ open-source icons in Line, Fill, and Duotone styles."
  },
  "capabilities": {
    "resources": true,
    "tools": false,
    "prompts": false
  },
  "resources": [
    {
      "name": "icons-line",
      "description": "Line-style SVG icons",
      "uri": "https://sargamicons.com/icons/Line/"
    },
    {
      "name": "icons-fill",
      "description": "Fill-style SVG icons",
      "uri": "https://sargamicons.com/icons/Fill/"
    },
    {
      "name": "icons-duotone",
      "description": "Duotone-style SVG icons",
      "uri": "https://sargamicons.com/icons/Duotone/"
    }
  ]
}


================================================
FILE: public/_headers
================================================
# Link response headers for agent discovery (RFC 8288, RFC 9727)
/
  Link: </sitemap.xml>; rel="sitemap"; type="application/xml"
  Link: </.well-known/api-catalog>; rel="api-catalog"; type="application/linkset+json"
  Link: <https://github.com/planetabhi/sargam-icons>; rel="service-doc"
  Vary: Accept

# Serve api-catalog with correct Content-Type (RFC 9727)
/.well-known/api-catalog
  Content-Type: application/linkset+json

# MCP Server Card (SEP-1649)
/.well-known/mcp/server-card.json
  Content-Type: application/json

# Agent Skills Discovery index
/.well-known/agent-skills/index.json
  Content-Type: application/json


================================================
FILE: public/robots.txt
================================================
# robots.txt for sargam-icons
# https://www.rfc-editor.org/rfc/rfc9309
# Content Signals: https://contentsignals.org/

# Allow all crawlers full access
User-agent: *
Content-Signal: ai-train=yes, search=yes, ai-input=yes
Allow: /icons/Line/
Allow: /icons/Fill/
Allow: /icons/Duotone/
Allow: /
Disallow: /icons/

# AI / LLM crawlers — allow indexing of public pages
User-agent: GPTBot
Allow: /icons/Line/
Allow: /icons/Fill/
Allow: /icons/Duotone/
Allow: /
Disallow: /icons/

User-agent: OAI-SearchBot
Allow: /icons/Line/
Allow: /icons/Fill/
Allow: /icons/Duotone/
Allow: /
Disallow: /icons/

User-agent: ChatGPT-User
Allow: /icons/Line/
Allow: /icons/Fill/
Allow: /icons/Duotone/
Allow: /
Disallow: /icons/

User-agent: ClaudeBot
Allow: /icons/Line/
Allow: /icons/Fill/
Allow: /icons/Duotone/
Allow: /
Disallow: /icons/

User-agent: Claude-Web
Allow: /icons/Line/
Allow: /icons/Fill/
Allow: /icons/Duotone/
Allow: /
Disallow: /icons/

User-agent: anthropic-ai
Allow: /icons/Line/
Allow: /icons/Fill/
Allow: /icons/Duotone/
Allow: /
Disallow: /icons/

User-agent: Google-Extended
Allow: /icons/Line/
Allow: /icons/Fill/
Allow: /icons/Duotone/
Allow: /
Disallow: /icons/

User-agent: Applebot-Extended
Allow: /icons/Line/
Allow: /icons/Fill/
Allow: /icons/Duotone/
Allow: /
Disallow: /icons/

User-agent: PerplexityBot
Allow: /icons/Line/
Allow: /icons/Fill/
Allow: /icons/Duotone/
Allow: /
Disallow: /icons/

User-agent: Amazonbot
Allow: /icons/Line/
Allow: /icons/Fill/
Allow: /icons/Duotone/
Allow: /
Disallow: /icons/

User-agent: Bytespider
Allow: /icons/Line/
Allow: /icons/Fill/
Allow: /icons/Duotone/
Allow: /
Disallow: /icons/

User-agent: CCBot
Allow: /icons/Line/
Allow: /icons/Fill/
Allow: /icons/Duotone/
Allow: /
Disallow: /icons/

Sitemap: https://sargamicons.com/sitemap.xml


================================================
FILE: public/sitemap.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://sargamicons.com/</loc>
    <lastmod>2026-04-18</lastmod>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://sargamicons.com/changelog.html</loc>
    <lastmod>2026-04-18</lastmod>
    <priority>0.5</priority>
  </url>
</urlset>


================================================
FILE: rspack.config.ts
================================================
import path from 'path';
import { rspack, Configuration } from '@rspack/core';
import { fileURLToPath } from 'url';

// Get __dirname equivalent in ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

interface BuildEnv {
    mode?: 'production' | 'development';
}

interface Argv {
    mode?: 'production' | 'development';
}

export default (env: BuildEnv, argv: Argv): Configuration => {
    const isProd = argv && argv.mode === 'production';
    return {
        mode: isProd ? 'production' : 'development',
        entry: {
            bundle: path.resolve(__dirname, 'src/index.ts'),
        },
        output: {
            path: path.resolve(__dirname, 'dist'),
            filename: '[name][contenthash].js',
            clean: true,
            assetModuleFilename: '[name][ext]',
        },
        devtool: isProd ? 'hidden-source-map' : 'eval-source-map',
        devServer: {
            static: {
                directory: path.resolve(__dirname, 'dist'),
            },
            port: 3000,
            open: true,
            hot: true,
            compress: true,
            historyApiFallback: true,
        },
        module: {
            rules: [
                {
                    test: /\.css$/,
                    use: [rspack.CssExtractRspackPlugin.loader, 'css-loader'],
                },
                {
                    test: /\.s[ac]ss$/i,
                    use: [
                        rspack.CssExtractRspackPlugin.loader,
                        'css-loader',
                        {
                            loader: 'sass-loader',
                            options: {
                                // Use package name string — ESM-safe, avoids require() in ESM context
                                implementation: 'sass-embedded',
                            },
                        },
                    ],
                },
                {
                    test: /\.[jt]s$/,
                    exclude: /node_modules/,
                    use: {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-env', '@babel/preset-typescript'],
                        },
                    },
                },
                {
                    test: /\.(png|svg|jpg|jpeg|gif)$/i,
                    type: 'asset/resource',
                },
                {
                    test: /\.(woff|woff2|eot|ttf|otf)$/i,
                    type: 'asset/resource',
                },
            ],
        },
        resolve: {
            extensions: ['.ts', '.js', '.json'],
        },
        plugins: [
            new rspack.CopyRspackPlugin({
                patterns: [
                    { from: path.resolve(__dirname, './Icons/Line'), to: 'icons/Line' },
                    { from: path.resolve(__dirname, './Icons/Duotone'), to: 'icons/Duotone' },
                    { from: path.resolve(__dirname, './Icons/Fill'), to: 'icons/Fill' },
                    { from: path.resolve(__dirname, './public/robots.txt'), to: 'robots.txt' },
                    { from: path.resolve(__dirname, './public/sitemap.xml'), to: 'sitemap.xml' },
                    { from: path.resolve(__dirname, './public/_headers'), to: '_headers' },
                    { from: path.resolve(__dirname, './public/.well-known'), to: '.well-known' },
                ],
            }),
            new rspack.HtmlRspackPlugin({
                title: 'Sargam Icons',
                filename: 'index.html',
                template: path.resolve(__dirname, 'src/template.html'),
                favicon: path.resolve(__dirname, 'src/favicon.ico'),
                meta: {
                    viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no',
                },
            }),
            new rspack.HtmlRspackPlugin({
                title: 'Changelog - Sargam Icons',
                filename: 'changelog.html',
                template: path.resolve(__dirname, 'src/changelog.html'),
                favicon: path.resolve(__dirname, 'src/favicon.ico'),
                meta: {
                    viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no',
                },
            }),
            new rspack.CssExtractRspackPlugin({
                filename: isProd ? '[name][contenthash].css' : '[name].css',
            }),
        ],
    };
};


================================================
FILE: scripts/generate-changelog.ts
================================================
import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const rootDir = path.resolve(__dirname, '..');

interface ChangelogEntry {
    version: string;
    date: string;
    newIcons: string[];
    highlights: string[];
}

interface Changelog {
    generated: string;
    totalIcons: number;
    entries: ChangelogEntry[];
}

function execGit(command: string): string {
    try {
        return execSync(command, { cwd: rootDir, encoding: 'utf-8' }).trim();
    } catch {
        return '';
    }
}

interface VersionCommit {
    hash: string;
    version: string;
    date: string;
}

function getVersionCommits(): VersionCommit[] {
    // Find commits with version patterns like "new:v1.6.7" or "new: v1.6.6" or just "v1.0.0"
    const output = execGit('git log --all --format="%H|%s|%ad" --date=short');
    if (!output) return [];

    // 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"
    // Uses optional colon (new:?) to match both "new:" and "new "
    const versionPattern = /^(?:new:?\s*)?v?(\d+\.\d+\.?\d*)/i;
    const versions: VersionCommit[] = [];
    const seenVersions = new Set<string>();

    for (const line of output.split('\n')) {
        if (!line) continue;
        const [hash, subject, date] = line.split('|');

        // Use single regex for both check and capture
        const match = subject.match(versionPattern);
        if (match) {
            let version = match[1];
            // Normalize version (add .0 if needed)
            if (version.split('.').length === 2) {
                version += '.0';
            }

            // Only take the first (most recent) commit for each version
            if (!seenVersions.has(version)) {
                seenVersions.add(version);
                versions.push({ hash, version, date });
            }
        }
    }

    // Sort by version number descending
    versions.sort((a, b) => {
        const aParts = a.version.split('.').map(Number);
        const bParts = b.version.split('.').map(Number);
        for (let i = 0; i < 3; i++) {
            if ((bParts[i] || 0) !== (aParts[i] || 0)) {
                return (bParts[i] || 0) - (aParts[i] || 0);
            }
        }
        return 0;
    });

    return versions;
}

function getNewIconsBetweenCommits(fromHash: string, toHash: string): string[] {
    // Get icons added between two commits
    const command = fromHash
        ? `git diff --name-status --diff-filter=A ${fromHash}..${toHash} -- "Icons/Line/*.svg"`
        : `git diff --name-status --diff-filter=A $(git rev-list --max-parents=0 HEAD)..${toHash} -- "Icons/Line/*.svg"`;

    const output = execGit(command);
    if (!output) return [];

    return output
        .split('\n')
        .filter(Boolean)
        .map((line) => {
            // Extract icon name from path like "A\tIcons/Line/si_IconName.svg"
            const match = line.match(/si_([^.]+)\.svg$/);
            return match ? match[1] : null;
        })
        .filter((name): name is string => name !== null)
        .sort();
}

function countTotalIcons(): number {
    const iconsDir = path.join(rootDir, 'Icons', 'Line');
    try {
        const files = fs.readdirSync(iconsDir);
        return files.filter((f) => f.endsWith('.svg')).length;
    } catch {
        return 0;
    }
}

function generateChangelog(): Changelog {
    const versionCommits = getVersionCommits();
    const entries: ChangelogEntry[] = [];

    // Limit to 20 most recent versions
    const maxVersions = 20;
    const versionsToProcess = versionCommits.slice(0, maxVersions);

    console.log(`Found ${versionCommits.length} version commits, processing ${versionsToProcess.length}`);

    for (let i = 0; i < versionsToProcess.length; i++) {
        const current = versionsToProcess[i];
        // Look forward: from current version commit to next version commit (or HEAD for latest)
        const next = i > 0 ? versionsToProcess[i - 1] : null;

        // Get icons added AFTER this version commit up to the next version (or HEAD)
        const newIcons = getNewIconsBetweenCommits(
            current.hash,
            next?.hash || 'HEAD'
        );

        console.log(`${current.version}: ${newIcons.length} new icons`);

        // Only add entries with new icons or if it's a major version
        if (newIcons.length > 0 || current.version.endsWith('.0')) {
            entries.push({
                version: current.version,
                date: current.date,
                newIcons,
                highlights: [],
            });
        }
    }

    return {
        generated: new Date().toISOString(),
        totalIcons: countTotalIcons(),
        entries,
    };
}

// Generate and save changelog
const changelog = generateChangelog();
const outputPath = path.join(rootDir, 'src', 'changelog.json');

fs.writeFileSync(outputPath, JSON.stringify(changelog, null, 2));
console.log(`\nChangelog generated successfully!`);
console.log(`Output: ${outputPath}`);
console.log(`Total versions: ${changelog.entries.length}`);
console.log(`Total icons: ${changelog.totalIcons}`);


================================================
FILE: src/index.ts
================================================
import './styles/base.scss';

document.addEventListener('DOMContentLoaded', () => {
    const searchInput = document.getElementById('icon-search') as HTMLInputElement | null;
    const clearBtn = document.getElementById('icon-search-clear') as HTMLButtonElement | null;
    const grid = document.querySelector('#icon-grid .flex-grid') as HTMLElement | null;

    if (!searchInput || !grid) return;

    const items = Array.from(grid.querySelectorAll('.flex-grid-item')) as HTMLElement[];

    function normalize(text: string): string {
        return (text || '').toLowerCase();
    }

    function getItemName(item: HTMLElement): string {
        const name = item.getAttribute('data-icon-name');
        return name || '';
    }

    function filter(query: string): void {
        const q = normalize(query);
        if (!q) {
            items.forEach((el) => {
                el.style.display = '';
            });
            return;
        }

        items.forEach((el) => {
            const name = normalize(getItemName(el));
            el.style.display = name.includes(q) ? '' : 'none';
        });
    }

    if (clearBtn) {
        clearBtn.hidden = (searchInput.value || '').length === 0;
    }
    filter(searchInput.value || '');

    let frameRequested = false;
    function onInputLike(): void {
        const value = searchInput!.value;
        if (clearBtn) {
            clearBtn.hidden = value.length === 0;
        }
        if (frameRequested) return;
        frameRequested = true;
        requestAnimationFrame(() => {
            filter(value);
            frameRequested = false;
        });
    }

    searchInput.addEventListener('input', onInputLike);
    searchInput.addEventListener('change', onInputLike);
    searchInput.addEventListener('search', onInputLike);

    if (clearBtn) {
        clearBtn.addEventListener('click', () => {
            searchInput.value = '';
            clearBtn.hidden = true;
            filter('');
            searchInput.focus();
        });
    }

    searchInput.addEventListener('keydown', (e: KeyboardEvent) => {
        if (e.key === 'Escape') {
            searchInput.value = '';
            if (clearBtn) clearBtn.hidden = true;
            filter('');
        }
    });

    // Cmd/Ctrl+K focuses the search input
    document.addEventListener('keydown', (e: KeyboardEvent) => {
        const isK = e.key === 'k' || e.key === 'K';
        if (isK && (e.metaKey || e.ctrlKey)) {
            e.preventDefault();
            searchInput.focus();
            searchInput.select();
        }
    });
});


================================================
FILE: src/sargam.ts
================================================
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

// Get __dirname equivalent in ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// ─────────────────────────────────────────────
// Types
// ─────────────────────────────────────────────

interface ChangelogEntry {
  version: string;
  date: string;
  newIcons: string[];
  highlights: string[];
}

interface Changelog {
  generated: string;
  totalIcons: number;
  entries: ChangelogEntry[];
}

// ─────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────

function getVersion(): string {
  try {
    const pkgPath = path.join(__dirname, '..', 'package.json');
    const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as { version?: string };
    return pkg.version ?? '1.6.7';
  } catch {
    return '1.6.7';
  }
}

function loadChangelog(): Changelog {
  try {
    const changelogPath = path.join(__dirname, 'changelog.json');
    const data = fs.readFileSync(changelogPath, 'utf-8');
    return JSON.parse(data) as Changelog;
  } catch {
    return { generated: '', totalIcons: 0, entries: [] };
  }
}

function formatDate(dateStr: string): string {
  const date = new Date(dateStr);
  return date.toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'short',
    day: '2-digit',
  });
}

function getIconNames(directory: string): string[] {
  return fs
    .readdirSync(directory)
    .filter((file: string) => file.endsWith('.svg'))
    .map((file: string) => path.basename(file, '.svg'))
    .sort();
}

// ─────────────────────────────────────────────
// Shared HTML generators
// ─────────────────────────────────────────────

/** The brand logo SVG — shared between both pages. */
const 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>`;

/** Theme-toggle button SVGs — shared between both pages. */
const THEME_TOGGLE_SVGS = `
  <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">
    <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"/>
  </svg>
  <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">
    <g clip-path="url(#sun-clip)">
      <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"/>
    </g>
    <defs>
      <clipPath id="sun-clip"><path fill="#fff" d="M0 0h24v24H0z"/></clipPath>
    </defs>
  </svg>`;

/** The icon-popover dialog HTML — identical on both pages. */
function generatePopoverHtml(): string {
  return `
  <div id="icon-popover" class="icon-popover" hidden role="dialog" aria-labelledby="popover-title" aria-modal="true">
    <div class="popover-content">
      <div class="popover-header">
        <h3 id="popover-title" class="popover-icon-name"></h3>
        <button type="button" class="popover-close" aria-label="Close popover">
          <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>
        </button>
      </div>
      <div class="popover-variants">
        <button type="button" class="popover-variant active" data-variant="line">Line</button>
        <button type="button" class="popover-variant" data-variant="duotone">Duotone</button>
        <button type="button" class="popover-variant" data-variant="fill">Fill</button>
      </div>
      <div class="popover-preview">
        <img class="popover-icon" src="" alt="" width="48" height="48">
      </div>
      <div class="popover-menu">
        <button type="button" class="popover-menu-item" id="copy-svg-btn">
          <span>[ Copy SVG ]</span>
        </button>
        <button type="button" class="popover-menu-item" id="copy-cdn-btn">
          <span>[ Copy CDN ]</span>
        </button>
        <button type="button" class="popover-menu-item" id="download-svg-btn">
          <span>[ Download ]</span>
        </button>
      </div>
    </div>
  </div>`;
}

/**
 * The shared initIconPopover() script block.
 *
 * @param cdnBaseUrl - The CDN base URL injected at build time.
 * @param clickSelector - CSS selector for clickable icon elements.
 * @param showRandomOnLoad - Whether to open a random icon popover on page load.
 */
function generatePopoverScript(
  cdnBaseUrl: string,
  clickSelector: string,
  showRandomOnLoad: boolean,
): string {
  // cdnBaseUrl is interpolated as string literals into the generated JS — no runtime const needed
  return `
    function initIconPopover() {
      const popover = document.getElementById('icon-popover');
      const popoverContent = document.querySelector('.popover-content');
      const popoverHeader = document.querySelector('.popover-header');
      const popoverTitle = document.querySelector('.popover-icon-name');
      const popoverIcon = document.querySelector('.popover-icon');
      const popoverClose = document.querySelector('.popover-close');
      const copyBtn = document.getElementById('copy-svg-btn');
      const downloadBtn = document.getElementById('download-svg-btn');
      const copyCdnBtn = document.getElementById('copy-cdn-btn');
      const variantBtns = document.querySelectorAll('.popover-variant');

      /** @type {{ iconName: string, iconType: string, iconUrl: string } | null} */
      let currentIconData = null;
      let isDragging = false;
      let offsetX = 0;
      let offsetY = 0;
      let currentPosX = 0;
      let currentPosY = 0;
      let hasBeenPositioned = false;
      /** @type {Element | null} */
      let previouslyFocusedElement = null;

      function getFocusableElements() {
        return popoverContent.querySelectorAll(
          'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
        );
      }

      function trapFocus(e) {
        if (e.key !== 'Tab') return;
        const focusableElements = getFocusableElements();
        if (focusableElements.length === 0) return;
        const firstElement = focusableElements[0];
        const lastElement = focusableElements[focusableElements.length - 1];
        if (e.shiftKey) {
          if (document.activeElement === firstElement) {
            e.preventDefault();
            lastElement.focus();
          }
        } else {
          if (document.activeElement === lastElement) {
            e.preventDefault();
            firstElement.focus();
          }
        }
      }

      function centerPopover() {
        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;
        const popoverWidth = popoverContent.offsetWidth || 256;
        const popoverHeight = popoverContent.offsetHeight || 400;
        const offsetFromRight = 48;
        const offsetFromTop = 72;
        currentPosX = (viewportWidth / 2) - popoverWidth / 2 - offsetFromRight;
        currentPosY = (offsetFromTop + popoverHeight / 2) - (viewportHeight / 2);
        popoverContent.style.left = '50%';
        popoverContent.style.top = '50%';
        popoverContent.style.transform = 'translate(calc(-50% + ' + currentPosX + 'px), calc(-50% + ' + currentPosY + 'px))';
        offsetX = 0;
        offsetY = 0;
        hasBeenPositioned = true;
      }

      function showPopover(iconElement) {
        const iconName = iconElement.getAttribute('data-name');
        const iconType = iconElement.getAttribute('data-type') || 'line';
        // For <img> elements, use their already-resolved src; for <button> elements (changelog)
        // .src is undefined so we fall back to constructing the URL from CDN + data attributes.
        const iconUrl = iconElement.src || ('${cdnBaseUrl}' + iconType.charAt(0).toUpperCase() + iconType.slice(1) + '/' + iconName + '.svg');

        currentIconData = { iconName, iconType, iconUrl };
        popoverTitle.textContent = iconName;
        popoverIcon.src = iconUrl;
        popoverIcon.alt = iconName + ' ' + iconType + ' icon';

        variantBtns.forEach(function(btn) {
          btn.classList.toggle('active', btn.getAttribute('data-variant') === iconType);
        });

        previouslyFocusedElement = document.activeElement;

        if (!hasBeenPositioned) {
          centerPopover();
        }

        popover.hidden = false;
        popover.setAttribute('aria-hidden', 'false');

        document.removeEventListener('keydown', trapFocus);
        document.addEventListener('keydown', trapFocus);

        setTimeout(function() {
          const focusable = getFocusableElements();
          if (focusable.length > 0) focusable[0].focus();
        }, 100);
      }

      function hidePopover() {
        popover.hidden = true;
        popover.setAttribute('aria-hidden', 'true');
        currentIconData = null;
        isDragging = false;
        document.removeEventListener('keydown', trapFocus);
        if (previouslyFocusedElement && previouslyFocusedElement.focus) {
          previouslyFocusedElement.focus();
        }
        previouslyFocusedElement = null;
      }

      function downloadIcon(iconName, iconType, iconUrl) {
        fetch(iconUrl)
          .then(function(response) { return response.blob(); })
          .then(function(blob) {
            const url = window.URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.style.display = 'none';
            a.href = url;
            a.download = iconName + '-' + iconType + '.svg';
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            window.URL.revokeObjectURL(url);
          })
          .catch(function(error) {
            console.error('Failed to download the icon:', error);
            alert('Failed to download the icon. Please try again.');
          });
      }

      function copyTextToClipboard(text, btn) {
        const spanEl = btn ? btn.querySelector('span') : null;
        const originalText = spanEl ? spanEl.textContent : '';
        function showCopied() {
          if (spanEl) {
            spanEl.textContent = '[ Copied ]';
            setTimeout(function() { spanEl.textContent = originalText; }, 800);
          }
        }
        if (navigator.clipboard && navigator.clipboard.writeText) {
          navigator.clipboard.writeText(text).then(showCopied).catch(function(err) {
            console.error('Failed to copy:', err);
          });
        } else {
          const textarea = document.createElement('textarea');
          textarea.value = text;
          textarea.style.position = 'fixed';
          textarea.style.opacity = '0';
          document.body.appendChild(textarea);
          textarea.select();
          document.execCommand('copy');
          document.body.removeChild(textarea);
          showCopied();
        }
      }

      function copyIconToClipboard(iconUrl) {
        fetch(iconUrl)
          .then(function(response) { return response.text(); })
          .then(function(svgText) { copyTextToClipboard(svgText, copyBtn); })
          .catch(function(error) {
            console.error('Failed to copy SVG:', error);
            alert('Failed to copy SVG. Please try again.');
          });
      }

      // Drag
      function dragStart(e) {
        if (e.target.tagName === 'BUTTON' || e.target.closest('button')) return;
        isDragging = true;
        popoverHeader.style.cursor = 'grabbing';
        if (e.type === 'touchstart') {
          offsetX = e.touches[0].clientX - currentPosX;
          offsetY = e.touches[0].clientY - currentPosY;
        } else {
          offsetX = e.clientX - currentPosX;
          offsetY = e.clientY - currentPosY;
        }
        e.preventDefault();
      }

      function drag(e) {
        if (!isDragging) return;
        e.preventDefault();
        const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
        const clientY = e.type === 'touchmove' ? e.touches[0].clientY : e.clientY;
        currentPosX = clientX - offsetX;
        currentPosY = clientY - offsetY;
        popoverContent.style.left = '50%';
        popoverContent.style.top = '50%';
        popoverContent.style.transform = 'translate(calc(-50% + ' + currentPosX + 'px), calc(-50% + ' + currentPosY + 'px))';
      }

      function dragEnd() {
        if (isDragging) {
          isDragging = false;
          popoverHeader.style.cursor = '';
        }
      }

      popoverHeader.addEventListener('mousedown', dragStart);
      document.addEventListener('mousemove', drag);
      document.addEventListener('mouseup', dragEnd);
      popoverHeader.addEventListener('touchstart', dragStart, { passive: false });
      document.addEventListener('touchmove', drag, { passive: false });
      document.addEventListener('touchend', dragEnd);

      // Click handler
      document.querySelectorAll('${clickSelector}').forEach(function(el) {
        el.addEventListener('click', function(e) {
          e.stopPropagation();
          showPopover(el);
        });
      });

      ${showRandomOnLoad ? `
      // Show random icon on page load
      const allIcons = document.querySelectorAll('.downloadable-icon');
      if (allIcons.length > 0) {
        const randomIcon = allIcons[Math.floor(Math.random() * allIcons.length)];
        showPopover(randomIcon);
      }
      ` : ''}

      // Keyboard: open icon on grid item Enter/Space
      const flexGrid = document.querySelector('.flex-grid');
      if (flexGrid) {
        flexGrid.addEventListener('keydown', function(e) {
          if (e.target.classList.contains('flex-grid-item')) {
            if (e.key === 'Enter' || e.key === ' ') {
              e.preventDefault();
              const firstIcon = e.target.querySelector('.downloadable-icon');
              if (firstIcon) showPopover(firstIcon);
            }
          }
        });
      }

      if (copyBtn) {
        copyBtn.addEventListener('click', function() {
          if (currentIconData) copyIconToClipboard(currentIconData.iconUrl);
        });
      }

      if (downloadBtn) {
        downloadBtn.addEventListener('click', function() {
          if (currentIconData) downloadIcon(currentIconData.iconName, currentIconData.iconType, currentIconData.iconUrl);
        });
      }

      if (popoverClose) popoverClose.addEventListener('click', hidePopover);

      document.addEventListener('keydown', function(e) {
        if (e.key === 'Escape' && !popover.hidden) hidePopover();
      });

      variantBtns.forEach(function(btn) {
        btn.addEventListener('click', function() {
          if (!currentIconData) return;
          const variant = btn.getAttribute('data-variant');
          const newUrl = '${cdnBaseUrl}' + variant.charAt(0).toUpperCase() + variant.slice(1) + '/' + currentIconData.iconName + '.svg';
          currentIconData.iconType = variant;
          currentIconData.iconUrl = newUrl;
          popoverIcon.src = newUrl;
          popoverIcon.alt = currentIconData.iconName + ' ' + variant + ' icon';
          variantBtns.forEach(function(b) { b.classList.remove('active'); });
          btn.classList.add('active');
        });
      });

      if (copyCdnBtn) {
        copyCdnBtn.addEventListener('click', function() {
          if (currentIconData) copyTextToClipboard(currentIconData.iconUrl, copyCdnBtn);
        });
      }
    }

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', initIconPopover);
    } else {
      initIconPopover();
    }`;
}

/** Shared theme-toggle IIFE — used on both pages. */
function generateThemeToggleScript(): string {
  return `
    (function initThemeToggle() {
      var KEY = 'data-new-ui-theme';
      var el = document.documentElement;
      var saved = localStorage.getItem(KEY);

      function apply(theme) {
        el.setAttribute('data-new-ui-theme', theme);
      }

      if (saved === 'dark--warm' || saved === 'light--warm') {
        apply(saved);
      } else {
        apply('dark--warm');
      }

      var btn = document.getElementById('theme-toggle');

      function syncIcons() {
        var isDark = (el.getAttribute('data-new-ui-theme') || 'dark--warm') === 'dark--warm';
        var sun = document.getElementById('icon-sun');
        var moon = document.getElementById('icon-moon');
        if (sun && moon) {
          sun.style.display = isDark ? '' : 'none';
          moon.style.display = isDark ? 'none' : '';
        }
        if (btn) {
          btn.setAttribute('aria-pressed', isDark ? 'true' : 'false');
          btn.setAttribute('aria-label', isDark ? 'Switch to light theme' : 'Switch to dark theme');
          btn.title = isDark ? 'Switch to light theme' : 'Switch to dark theme';
        }
      }
      syncIcons();

      if (btn) {
        btn.addEventListener('click', function() {
          var current = el.getAttribute('data-new-ui-theme') || 'dark--warm';
          var next = current === 'light--warm' ? 'dark--warm' : 'light--warm';
          apply(next);
          try { localStorage.setItem(KEY, next); } catch (e) {}
          syncIcons();
        });
      }
    })();`;
}

/** Progressive image loading with IntersectionObserver. */
function generateProgressiveLoadingScript(): string {
  return `
    function initProgressiveLoading() {
      const images = document.querySelectorAll('.downloadable-icon');

      function applyImage(img) {
        const tempImg = new Image();
        tempImg.onload = function() {
          img.style.opacity = '1';
          img.classList.add('loaded');
        };
        tempImg.onerror = function() {
          img.style.display = 'none';
          const placeholder = img.nextElementSibling;
          if (placeholder && placeholder.classList.contains('icon-placeholder')) {
            placeholder.style.display = 'block';
          }
        };
        tempImg.src = img.src;
      }

      if ('IntersectionObserver' in window) {
        const imageObserver = new IntersectionObserver(function(entries, observer) {
          entries.forEach(function(entry) {
            if (entry.isIntersecting) {
              applyImage(entry.target);
              observer.unobserve(entry.target);
            }
          });
        }, { rootMargin: '50px 0px', threshold: 0.1 });

        images.forEach(function(img) { imageObserver.observe(img); });
      } else {
        images.forEach(applyImage);
      }
    }`;
}

/** Font-loading class management. */
function generateFontLoadingScript(): string {
  return `
    function initFontLoading() {
      if (document.fonts && document.fonts.ready) {
        document.body.classList.add('font-loading');
        document.fonts.ready.then(function() {
          document.body.classList.add('font-loaded');
          document.body.classList.remove('font-loading');
        });
        setTimeout(function() {
          if (document.body.classList.contains('font-loading')) {
            document.body.classList.add('font-loaded');
            document.body.classList.remove('font-loading');
          }
        }, 3000);
      }
    }`;
}

/** Minimal critical CSS inlined in <head> to prevent FOUC before bundle loads. */
function getCriticalCSS(): string {
  return `
html, body { background-color: var(--background); margin: 0; }
body { color: var(--content-primary); }
main { width: 100%; margin: 0; padding: 0; }
.top-nav { position: fixed; top: 0; width: 100%; z-index: 999; }
header { width: 100%; margin: var(--s-112, 7rem) 0 var(--s-56, 3.5rem); text-align: center; }
.flex-grid { display: flex; flex-wrap: wrap; }
.flex-grid-item { flex: 1 0 4rem; display: flex; align-items: center; justify-content: center; position: relative; height: 9.5rem; }
@media screen and (max-width: 639px) {
  header { margin: var(--s-128, 8rem) 0 var(--s-64, 4rem); }
}`;
}

// ─────────────────────────────────────────────
// Build data
// ─────────────────────────────────────────────

const VERSION = getVersion();
const CDN_BASE_URL = `https://cdn.jsdelivr.net/npm/sargam-icons@${VERSION}/Icons/`;
const changelog = loadChangelog();
const iconNames = getIconNames(path.join(__dirname, '..', 'Icons', 'Line'));
const criticalCSS = getCriticalCSS();

// ─────────────────────────────────────────────
// Icon grid HTML
// ─────────────────────────────────────────────

let iconGridContent = '';
iconNames.forEach((iconName: string, index: number) => {
  iconGridContent += `
    <div class="flex-grid-item" data-icon-name="${iconName}" tabindex="0" aria-label="${iconName} icon">
      <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';">
      <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>
      <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';">
      <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>
      <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';">
      <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>
    </div>`;
});

// ─────────────────────────────────────────────
// Main page (index / template.html)
// ─────────────────────────────────────────────

const fullHtmlContent = `<!DOCTYPE html>
<html lang="en" data-new-ui-theme="dark--warm">
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
  <link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
  <link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
  <title>sargam icons</title>
  <meta name="description" content="A collection of 1,200+ handcrafted, and meticulously optimized open-source icons for your exquisite designs.">
  <meta name="author" content="@planetabhi" />
  <meta property="og:title" content="sargam icons" />
  <meta property="og:url" content="https://sargamicons.com/" />
  <meta property="og:type" content="website" />
  <meta property="og:description" content="A collection of 1,200+ handcrafted, and meticulously optimized open-source icons for your exquisite designs." />
  <meta property="og:site_name" content="sargam icons" />
  <link rel="apple-touch-icon" href="icon.png">
  <link rel="canonical" href="https://sargamicons.com/">
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "WebSite",
    "name": "Sargam Icons",
    "url": "https://sargamicons.com/",
    "description": "A collection of 1,200+ handcrafted, and meticulously optimized open-source icons for your exquisite designs.",
    "author": {
      "@type": "Person",
      "name": "Abhimanyu Rana",
      "url": "https://planetabhi.com/"
    }
  }
  </script>
  <style>${criticalCSS}</style>
  <script async src="https://www.googletagmanager.com/gtag/js?id=G-JWMS1KLBBB"></script>
  <script>
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
    gtag('config', 'G-JWMS1KLBBB');
  </script>
</head>
<body>
  <a href="#main-content" class="skip-link">Skip to content</a>
  <nav class="top-nav" aria-label="Primary">
    <div class="top-nav-inner">
      <div class="lhs">
        <a href="/" class="brand" aria-label="sargam icons">
          ${BRAND_SVG}
        </a>
        <span class="version-pill" aria-label="Version">v${VERSION}</span>
        <div class="zoom-separator" role="separator"></div>

        <label for="icon-search" class="sr-only">Search icons</label>
        <div class="nav-search-wrapper">
          <input id="icon-search" type="search" placeholder="Search sargam icons..." aria-label="Search icons" autocomplete="off" inputmode="search" />
          <button id="icon-search-clear" type="button" class="clear-search" aria-label="Clear search" hidden>
            <svg 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-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"/>
            </svg>
          </button>
        </div>
      </div>
      <div class="rhs">
        <a href="changelog.html" aria-label="Changelog">Changelog</a>

        <div class="zoom-separator" role="separator"></div>

        <a href="https://github.com/planetabhi/sargam-icons" target="_blank" rel="noopener noreferrer" aria-label="GitHub">GitHub</a>

        <div class="zoom-separator" role="separator"></div>

        <button type="button" class="zoom-btn" id="zoom-out" aria-label="Zoom out" title="Zoom out">
          <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>
        </button>

        <button type="button" class="zoom-btn" id="zoom-in" aria-label="Zoom in" title="Zoom in">
          <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>
        </button>

        <div class="zoom-separator" role="separator"></div>

        <button type="button" class="theme-toggle" id="theme-toggle" aria-label="Toggle theme" aria-pressed="false">
          ${THEME_TOGGLE_SVGS}
        </button>
      </div>
    </div>
  </nav>

  <header>
    <div class="header-content">
      <h1>Handcrafted. Optimized. Open-source.</h1>
      <div class="CTAs">
        <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>
        <a href="https://registry.npmjs.org/sargam-icons/-/sargam-icons-${VERSION}.tgz" aria-label="Download all icons"><span>Download all</span></a>
      </div>
    </div>
  </header>

  <main id="main-content">
    <section id="icon-grid" aria-labelledby="icon-grid-heading">
      <h2 id="icon-grid-heading" class="sr-only">Sargam Icons</h2>
      <div class="flex-grid" aria-label="Collection of ${iconNames.length} icons">
        ${iconGridContent}
      </div>
    </section>
  </main>

  <footer>
    <div class="footer-content">
      <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>
    </div>
  </footer>

  ${generatePopoverHtml()}

  <script>
    ${generateFontLoadingScript()}
    ${generateProgressiveLoadingScript()}

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', function() {
        initFontLoading();
        initProgressiveLoading();
      });
    } else {
      initFontLoading();
      initProgressiveLoading();
    }

    ${generatePopoverScript(CDN_BASE_URL, '.downloadable-icon', true)}

    ${generateThemeToggleScript()}

    (function initZoomControls() {
      var ZOOM_LEVELS = [1, 1.5, 2, 2.5, 3];
      var currentZoomIndex = 0;
      var iconGrid = document.getElementById('icon-grid');
      var zoomInBtn = document.getElementById('zoom-in');
      var zoomOutBtn = document.getElementById('zoom-out');

      function applyZoom(zoomLevel) {
        if (!iconGrid) return;
        iconGrid.style.transform = '';
        iconGrid.style.zoom = '';
        iconGrid.style.setProperty('--zoom', String(zoomLevel));
      }

      function updateButtons() {
        if (zoomOutBtn) zoomOutBtn.disabled = currentZoomIndex === 0;
        if (zoomInBtn) zoomInBtn.disabled = currentZoomIndex === ZOOM_LEVELS.length - 1;
      }

      if (zoomInBtn) {
        zoomInBtn.addEventListener('click', function() {
          if (currentZoomIndex < ZOOM_LEVELS.length - 1) {
            currentZoomIndex++;
            applyZoom(ZOOM_LEVELS[currentZoomIndex]);
            updateButtons();
          }
        });
      }

      if (zoomOutBtn) {
        zoomOutBtn.addEventListener('click', function() {
          if (currentZoomIndex > 0) {
            currentZoomIndex--;
            applyZoom(ZOOM_LEVELS[currentZoomIndex]);
            updateButtons();
          }
        });
      }

      updateButtons();
    })();
  </script>

</body>
</html>`;

fs.writeFileSync(path.join(__dirname, 'template.html'), fullHtmlContent);

// ─────────────────────────────────────────────
// Changelog page (changelog.html)
// ─────────────────────────────────────────────

const changelogEntriesHtml = changelog.entries
  .map((entry: ChangelogEntry, index: number) => {
    const isFirst = index === 0;
    const iconCount = entry.newIcons.length;

    const iconsHtml = entry.newIcons
      .map(
        (iconName: string) => `
            <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">
              <img src="${CDN_BASE_URL}Line/si_${iconName}.svg" width="20" height="20" alt="" loading="lazy" onerror="this.parentElement.style.display='none'" />
            </button>`,
      )
      .join('');

    return `
            <details class="changelog-entry" ${isFirst ? 'open' : ''}>
              <summary class="changelog-summary" aria-label="Version ${entry.version} — ${iconCount} new icons">
                <span class="changelog-version">v${entry.version}</span>
                <span class="changelog-date">${formatDate(entry.date)}</span>
                <span class="changelog-summary-rhs">
                  <span class="changelog-count">${iconCount}</span>
                  <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>
                </span>
              </summary>
              <div class="changelog-content">
                ${iconCount > 0 ? `<div class="changelog-icons">${iconsHtml}</div>` : '<p class="changelog-empty">No new icons in this version</p>'}
              </div>
            </details>`;
  })
  .join('');

const changelogPageHtml = `<!DOCTYPE html>
<html lang="en" data-new-ui-theme="dark--warm">
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
  <link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
  <link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
  <title>Changelog - Sargam Icons</title>
  <meta name="description" content="Version history and new icons for Sargam Icons.">
  <meta name="author" content="@planetabhi" />
  <meta property="og:title" content="Changelog - Sargam Icons" />
  <meta property="og:url" content="https://sargamicons.com/changelog.html" />
  <meta property="og:type" content="website" />
  <meta property="og:description" content="Version history and new icons for Sargam Icons." />
  <meta property="og:site_name" content="sargam icons" />
  <link rel="apple-touch-icon" href="icon.png">
  <link rel="canonical" href="https://sargamicons.com/changelog.html">
  <meta name="robots" content="index, follow">
  <style>${criticalCSS}</style>
</head>
<body>
  <a href="#main-content" class="skip-link">Skip to content</a>
  <nav class="top-nav" aria-label="Primary">
    <div class="top-nav-inner">
      <div class="lhs">
        <a href="/" class="brand" aria-label="sargam icons">
          ${BRAND_SVG}
        </a>
        <span class="version-pill" aria-label="Version">v${VERSION}</span>
      </div>
      <div class="rhs">
        <a href="https://github.com/planetabhi/sargam-icons" target="_blank" rel="noopener noreferrer" aria-label="GitHub">GitHub</a>

        <div class="zoom-separator" role="separator"></div>

        <button type="button" class="theme-toggle" id="theme-toggle" aria-label="Toggle theme" aria-pressed="false">
          ${THEME_TOGGLE_SVGS}
        </button>
      </div>
    </div>
  </nav>

  <main id="main-content" class="changelog-page">
    <header class="changelog-header">
      <h1>Changelog</h1>
    </header>

    <section id="changelog" class="changelog-section" aria-labelledby="changelog-heading">
      <h2 id="changelog-heading" class="sr-only">Changelog</h2>
      <div class="changelog-container">
        ${changelogEntriesHtml}
      </div>
    </section>
  </main>

  <footer>
    <div class="footer-content">
      <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>
    </div>
  </footer>

  ${generatePopoverHtml()}

  <script>
    ${generatePopoverScript(CDN_BASE_URL, '.changelog-icon-btn', false)}

    ${generateThemeToggleScript()}
  </script>

</body>
</html>`;

fs.writeFileSync(path.join(__dirname, 'changelog.html'), changelogPageHtml);
console.log('generated successfully');


================================================
FILE: src/styles/base.scss
================================================
@use "@new-ui/reset";
@use "@new-ui/colors";

@font-face {
  font-family: "Jiva Mono";
  src: url("../fonts/JivaMono.woff2") format("woff2");
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

:root {
  font-size: 16px;
  --sargam: "Jiva Mono", ui-monospace, Menlo, Consolas, monospace;
  scroll-behavior: smooth;
  scroll-padding-top: 2rem;
  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  --container-max-width: 100%;
  --container-padding: var(--s-16);
  --container-padding-mobile: 1.5rem;
  --shadow-color: rgb(0 0 0 / 0.04);
  --elevated-shadow: 0 0 0 1.5px var(--border-muted),
    0 1px 1px -0.5px var(--shadow-color), 0 3px 3px -1.5px var(--shadow-color);
  --s-00: 0rem;
  --s-01: .0625rem;
  --s-02: .125rem;
  --s-04: .25rem;
  --s-06: .375rem;
  --s-08: .5rem;
  --s-12: .75rem;
  --s-16: 1rem;
  --s-20: 1.25rem;
  --s-24: 1.5rem;
  --s-32: 2rem;
  --s-40: 2.5rem;
  --s-48: 3rem;
  --s-56: 3.5rem;
  --s-64: 4rem;
  --s-72: 4.5rem;
  --s-80: 5rem;
  --s-96: 6rem;
  --s-112: 7rem;
  --s-120: 7.5rem;
  --s-128: 8rem;
  --s-160: 10rem;
  --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"
    ) 16 16, auto;
  --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"
    ) 10 5, pointer;
  --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"
    ) 12 12, text;
  --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;
}

html[data-new-ui-theme*="dark"] {
  --link: #E08742;
  --link-hover: #E9F37E;
  --link-visited: #E08742;
  --border-focus: var(--border);
}

html[data-new-ui-theme*="light"] {
  --link: #2972FF;
  --link-hover: var(--black);
  --link-visited: #2972FF;
  --border-focus: var(--border);
}

@mixin container-base {
  max-width: var(--container-max-width);
  width: 100%;
  margin-inline: auto;
  padding-inline: var(--container-padding);
  box-sizing: border-box;
}

// Shared styles for icon-style nav buttons (zoom-btn, theme-toggle)
@mixin icon-btn {
  width: 32px;
  height: 32px;
  padding: 0;
  cursor: var(--c-pointer);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--content-secondary);
  background: none;
  border-left-color: var(--border-muted);
  border-top-color: var(--border-muted);
  border-right-color: var(--border);
  border-bottom-color: var(--border);
  border-style: solid;
  border-width: 1px;

  &:hover:not(:disabled) {
    color: var(--content-primary);
    background: var(--background-high-contrast);
  }

  &:focus {
    outline: 0;
  }

  svg {
    width: 16px;
    height: 16px;
    display: block;
  }
}

.container {
  @include container-base;
}

html,
body {
  background-color: var(--background);
  cursor: var(--c-arrow);
}

body {
  color: var(--content-primary);
  font: 400 1em/1.5 var(--sargam);
  margin: var(--s-00);
  font-kerning: normal;
  overflow-wrap: break-word;
  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;

  &.font-loading {
    font-family: system-ui, sans-serif;
  }

  &.font-loaded {
    font-family: var(--sargam);
  }
}

p,
h1,
h2,
h3,
h4,
h5,
h6,
li,
pre,
code,
strong,
dl,
dt {
  cursor: var(--c-text);
}

a,
a * {
  cursor: var(--c-pointer);
}

main {
  width: 100%;
  margin: 0;
  padding: 0;

  #icon-grid .flex-grid {
    @include container-base;
  }
}

img,
svg {
  max-width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
}

#icon-grid {
  margin-bottom: var(--s-24);
}

:focus-visible {
  outline: 1.5px solid var(--border-focus);
  outline-offset: var(--s-02);
  box-shadow: none;
}

.flex-grid-item:focus,
.flex-grid-item:focus-visible {
  outline: none;
}

.flex-grid-item:focus img,
.flex-grid-item:focus-visible img {
  outline-offset: 2px;
}

.skip-link {
  position: absolute;
  top: -40px;
  left: 6px;
  background: var(--background-primary);
  color: var(--content-secondary);
  padding: var(--s-04);
  text-decoration: none;
  z-index: 1000;
  font-size: 0.875rem;

  &:focus {
    top: 6px;
  }
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

::selection {
  background: var(--content-inked);
  color: var(--background);
  text-shadow: none;
}

a {
  color: var(--link);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  position: relative;
  text-transform: uppercase;

  &:hover {
    color: var(--link-hover);
  }

  &:visited {
    color: var(--link-visited);
  }
}

img,
.flex-grid-item img[data-type="line"],
.flex-grid-item img[data-type="duotone"],
.flex-grid-item img[data-type="fill"] {
  position: relative;
}

header {
  width: 100%;
  margin: var(--s-112) 0 var(--s-56);
  text-align: center;
  padding: 0;

  .header-content {
    @include container-base;
    text-align: center;
    max-inline-size: 40rem;
  }

  h1 {
    font: 400 1.75rem/2.25rem var(--sargam);
    color: var(--content-primary);
    margin-bottom: var(--s-20);
    font-synthesis: none;
    text-rendering: optimizeLegibility;

    span {
      font: italic normal 1em var(--sargam);
    }
  }

  .CTAs {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: var(--s-08);
    min-width: max-content;
    box-sizing: border-box;

    a {
      font: 400 0.875rem/1.563rem var(--sargam);
      padding: var(--s-02) var(--s-12);
      text-transform: uppercase;
      text-decoration-line: none;
      cursor: var(--c-pointer);
      color: var(--link);
      background: var(--background-high-contrast);

      border-left-color: var(--border-muted);
      border-top-color: var(--border-muted);
      border-right-color: var(--border-strong);
      border-bottom-color: var(--border-strong);
      border-style: solid;
      border-width: 1px;

      &:hover {
        color: var(--link-hover);
      }

      &:active {
        border-left-color: var(--border-strong);
        border-top-color: var(--border-strong);
        border-right-color: var(--border-muted);
        border-bottom-color: var(--border-muted);
      }
    }
  }
}

.flex-grid {
  display: flex;
  flex-wrap: wrap;

  &-item {
    flex: 1 0 calc(var(--s-64) * var(--zoom, 1));
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    overflow: hidden;
    margin: 0 calc(var(--s-02) * var(--zoom, 1)) calc(var(--s-04) * var(--zoom, 1));
    height: calc(9.5rem * var(--zoom, 1));
    background: var(--background-high-contrast);
    content-visibility: auto;
    contain-intrinsic-size: 0 calc(9.5rem * var(--zoom, 1));
    border: 1.5px dotted var(--border-muted);

    img {

      &[data-type="line"],
      &[data-type="duotone"],
      &[data-type="fill"] {
        width: calc(24px * var(--zoom, 1));
        height: calc(24px * var(--zoom, 1));
        position: absolute;
        object-fit: contain;
        aspect-ratio: 1 / 1;
        cursor: var(--c-pointer);

        &:hover {
          background: var(--content-secondary-alt);
          color: var(--content-primary);
        }
      }

      &[data-type="line"] {
        top: calc(1.25rem * var(--zoom, 1));
      }

      &[data-type="duotone"] {
        top: calc(4rem * var(--zoom, 1));
      }

      &[data-type="fill"] {
        top: calc(6.75rem * var(--zoom, 1));
      }
    }
  }
}

.top-nav {
  justify-content: flex-end;
  align-items: center;
  width: 100%;
  display: flex;
  z-index: 999;
  position: fixed;
  inset: 0% 0% auto;
  border-bottom: 1px solid var(--border-muted);
  background: var(--background);

  &-inner {
    max-width: var(--container-max-width);
    width: 100%;
    margin: 0 auto;
    padding: var(--s-08);
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-06);
  }

  .lhs,
  .rhs {
    display: flex;
    align-items: center;
    gap: var(--s-06);
  }

  a {
    color: var(--content-secondary);
    text-decoration: none;
    font: 400 14px/1.5 var(--sargam);

    &:hover {
      text-decoration: none;
      color: var(--content-primary);
    }
  }

  .nav-search-wrapper {
    position: relative;
    margin-left: var(--s-00);

    input[type="search"] {
      width: 12rem;
      box-sizing: border-box;
      background-color: var(--background);
      box-shadow: none;
      appearance: none;
      border: 1px solid var(--border-muted);
      text-align: left;
      font: 400 0.875rem/1.5 var(--sargam);
      padding: var(--s-02) var(--s-06);
      color: var(--content-primary);
      caret-color: var(--content-primary);
      text-transform: uppercase;
      cursor: var(--c-text);
      --input-shadow: 2px 2px 0px 0px var(--background-secondary);
      box-shadow: var(--input-shadow) inset;

      &::placeholder {
        color: var(--content-placeholder);
      }

      &:focus {
        border-color: var(--border-focus);
        outline: 0;
        box-shadow: 0 0 0 .5px var(--border-focus);
      }

      &::-webkit-search-cancel-button {
        display: none;
      }
    }

    .clear-search {
      position: absolute;
      right: var(--s-04);
      top: 50%;
      transform: translateY(-50%);
      width: 24px;
      height: 24px;
      border: none;
      background: transparent;
      padding: 0;
      margin: 0;
      cursor: var(--c-pointer);
      color: var(--content-secondary-alt);
      display: inline-flex;
      align-items: center;
      justify-content: center;

      &:focus {
        outline: 0;
        box-shadow: 0 0 0 .5px var(--border-focus);
      }

      &[hidden] {
        display: none;
      }

      svg {
        width: 16px;
        height: 16px;
      }
    }
  }
}

@keyframes rotate {
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;

  svg {
    animation: rotate 4s linear infinite;
    animation-play-state: paused;
  }

  svg:hover,
  &:hover svg {
    animation-play-state: running;
  }
}

.version-pill {
  font: 400 14px/1 var(--sargam);
  padding: var(--s-02) var(--s-04);
  color: var(--content-primary);
  background: var(--background-selected);
}

.zoom-separator {
  width: 1px;
  height: 1rem;
  background: var(--border-muted);
  margin: 0 var(--s-04);
}

.zoom-btn {
  @include icon-btn;

  &:active:not(:disabled) {
    border-left-color: var(--border);
    border-top-color: var(--border);
    border-right-color: var(--border-muted);
    border-bottom-color: var(--border-muted);
  }

  &:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    border: 1px solid transparent;
  }
}

.theme-toggle {
  @include icon-btn;

  &:active {
    border-left-color: var(--border);
    border-top-color: var(--border);
    border-right-color: var(--border-muted);
    border-bottom-color: var(--border-muted);
  }
}

.blank {
  height: 0;
}

footer {
  width: 100%;
  padding: 1rem 0;
  margin: 0;
  text-align: center;
  font: normal 0.875rem/1.5 var(--sargam);
  color: var(--content-secondary);
  border-top: 1px dotted var(--border-muted);

  .footer-content {
    @include container-base;
  }

  a {
    color: var(--link-hover);
  }
}

.icon-popover {
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;

  &[hidden] {
    display: none;
  }

  &[hidden] .popover-content {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition-delay: 0s;
  }
}

.popover-content {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
  background: var(--background-high-contrast);
  width: 100%;
  max-width: 16rem;
  pointer-events: auto;
  border: 2px solid var(--black);
  visibility: visible;

  --ease-in: cubic-bezier(0.4, 0, 0.2, 1);
  transition-property: opacity, visibility;
  transition-duration: .2s;
  transition-timing-function: var(--ease-in);
  transition-delay: .2s;
  opacity: 1;

  --popover-shadow: 4px 4px 0px 0px var(--border);
  box-shadow: var(--popover-shadow);
}

.popover-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--s-00) var(--s-00) var(--s-00) var(--s-06);
  border-bottom: 2px solid var(--background-high-contrast);
  cursor: var(--c-grab);
  user-select: none;
  position: relative;
  z-index: 999;

  &:active {
    cursor: grabbing;
  }

  .popover-icon-name {
    font: 400 0.875rem/1.5 var(--sargam);
    color: var(--content-primary);
    margin: 0;
    text-transform: uppercase;
    cursor: var(--c-grab);
    max-width: 16ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .popover-close {
    width: 28px;
    height: 28px;
    padding: 0;
    margin-left: var(--s-08);
    cursor: var(--c-pointer);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--content-secondary);
    background: none;
    transition: transform 0.1s ease;
    border: none;

    &:hover {
      color: var(--content-primary);
      background: var(--background);
    }

    &:focus-visible {
      outline: 0;
      box-shadow: 0 0 0 .5px var(--border-focus);
    }

    &:active {
      transform: scale(0.97);
    }

    svg {
      width: 24px;
      height: 24px;
      display: block;
    }
  }
}

img.popover-icon {
  box-shadow: 0 0 0 1px var(--link-hover);
}

.popover-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-32);
  background: var(--background);

  .popover-icon {
    width: 96px;
    height: 96px;
    object-fit: contain;
  }
}

.popover-preview:after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 998;
  background: repeating-linear-gradient(0deg, var(--background-secondary) 0, var(--background-secondary) 2px, transparent 2px, transparent 4px);
  animation: lines 3s ease-out infinite;
  opacity: .2;
  mix-blend-mode: color-burn;
  pointer-events: none;
}

@keyframes lines {
  to {
    background-position: 0 25px
  }
}

.popover-menu {
  display: flex;
  flex-direction: column;
  gap: 0;
  border-top: .5px solid var(--border-muted);
  background: var(--background-high-contrast);
  position: relative;
  z-index: 999;
}

.popover-variants {
  display: flex;
  gap: 1px;
  position: relative;
  z-index: 999;
  background: var(--border-muted);
  border-top: 1px solid var(--border-muted);
  border-bottom: 1px solid var(--border-muted);
}

.popover-variant {
  flex: 1;
  padding: var(--s-06) var(--s-08);
  font: 400 0.75rem/1 var(--sargam);
  text-transform: uppercase;
  color: var(--content-secondary);
  background: var(--background);
  border: none;
  cursor: var(--c-pointer);
  transition: background 0.15s ease, color 0.15s ease;

  &:hover {
    background: var(--background-selected);
  }

  &.active {
    color: var(--content-primary);
    background: var(--background-selected);
  }
}

.popover-menu-item {
  display: flex;
  align-items: center;
  gap: var(--s-08);
  padding: var(--s-04);
  font: 400 0.875rem/1.5 var(--sargam);
  color: var(--content-secondary);
  background: none;
  border: none;
  border-bottom: .5px solid var(--border-muted);
  cursor: var(--c-pointer);
  text-align: left;
  text-transform: uppercase;
  transition: transform 0.1s ease;

  &:last-child {
    border-bottom: none;
  }

  &:hover {
    color: var(--link-hover);
  }

  &:focus {
    outline: 0;
    box-shadow: inset 0 0 0 .5px var(--border-focus);
  }

  &:active {
    transform: scale(0.98);
  }

  svg {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
  }

  span {
    flex: 1;
  }
}

// ─────────────────────────────────────────────
// Responsive overrides
// ─────────────────────────────────────────────
@media screen and (max-width: 639px) {
  :root {
    --container-padding: var(--container-padding-mobile);
  }

  header {
    margin: var(--s-128) 0 var(--s-64);
  }


  .top-nav-inner {
    margin: 0 auto 0;
  }

  .popover-content {
    left: 50% !important;
    top: 50% !important;
    transform: translate(-50%, -50%) !important;
    margin: var(--s-12);
    min-width: auto;
  }

  .popover-header {
    cursor: default !important;
    user-select: auto;

    &:active {
      cursor: default !important;
    }
  }

  .nav-search-wrapper {
    display: none;
  }

  .top-nav .lhs {
    gap: var(--s-04);
  }

  // Changelog responsive
  .changelog-summary {
    flex-wrap: wrap;
    gap: var(--s-06);
  }

  .changelog-count {
    width: 100%;
  }

  .changelog-icon-btn {
    width: 32px;
    height: 32px;
  }
}

// Changelog Section Styles
.changelog-section {
  width: 100%;
  margin: var(--s-64) 0;
  border-top: 1px dotted var(--border-muted);
  padding-top: var(--s-48);
}

.changelog-page {
  padding-top: var(--s-64);
}

.changelog-page .changelog-section {
  border-top: none;
  margin-top: 0;
  padding-top: 0;
}

.changelog-header {
  text-align: center;
  margin: var(--s-48) 0 var(--s-32);

  h1 {
    font: 400 1.5rem/1.5 var(--sargam);
    color: var(--content-primary);
    margin: 0 0 var(--s-08);
    text-transform: uppercase;
  }

  p {
    font: 400 0.875rem/1.5 var(--sargam);
    color: var(--content-secondary);
    margin: 0;
  }
}

.changelog-empty {
  font: 400 0.875rem/1.5 var(--sargam);
  color: var(--content-secondary-alt);
  margin: 0;
  padding: var(--s-08);
}

.top-nav a.active {
  color: var(--link);
  text-decoration: underline;
}

.changelog-container {
  max-width: 32rem;
  width: 100%;
  margin: 0 auto;
  padding-inline: var(--container-padding);
  box-sizing: border-box;
  gap: 1px;
  display: flex;
  flex-direction: column;

  h2 {
    font: 400 1.25rem/1.5 var(--sargam);
    color: var(--content-primary);
    margin: 0 0 var(--s-04);
    text-transform: uppercase;
    text-align: center;
  }
}

.changelog-entry {
  background: var(--background-high-contrast);

  &[open] {
    .changelog-summary::after {
      transform: rotate(180deg);
    }
  }
}

.changelog-summary {
  display: flex;
  align-items: center;
  gap: var(--s-12);
  padding: var(--s-04) var(--s-12);
  cursor: var(--c-pointer);
  list-style: none;
  font: 400 14px/1 var(--sargam);
  text-transform: uppercase;

  &::-webkit-details-marker {
    display: none;
  }

  &:hover {
    background: var(--background-selected);
  }
}

.changelog-summary-rhs {
  display: flex;
  align-items: center;
  gap: var(--s-08);
  margin-left: auto;
}

.changelog-chevron {
  width: 20px;
  height: 20px;
  transition: transform 0.2s ease;
  color: var(--content-secondary);

  .changelog-entry[open] & {
    transform: rotate(90deg);
  }
}

.changelog-version {
  color: var(--link);
  font-weight: 400;
  min-width: 4rem;
}

.changelog-date {
  color: var(--content-secondary);
}

.changelog-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.5rem;
  padding: 0 var(--s-06);
  font: 400 14px/1 var(--sargam);
  color: var(--content-secondary);
  background: var(--background);
  border: 1px dotted var(--border-muted);
}

.changelog-content {
  padding: var(--s-16);
  border-top: 1px dotted var(--border-muted);
  background: var(--background);
}

.changelog-icons {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-06);
  align-items: center;
}

.changelog-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: var(--s-06);
  background: var(--background-high-contrast);
  border: 1px solid var(--border-muted);
  cursor: var(--c-pointer);
  transition: background 0.15s ease;
  overflow: hidden;

  &:hover {
    background: var(--background-selected);
    border-color: var(--border);
  }

  &:focus-visible {
    outline: 1.5px solid var(--border-focus);
    outline-offset: 1px;
  }

  img {
    width: 20px;
    height: 20px;
    object-fit: contain;

    &[alt] {
      font-size: 0;
      color: transparent;
    }
  }
}



[data-new-ui-theme="dark--warm"] {

  img[data-type="line"],
  img[data-type="duotone"],
  img[data-type="fill"],
  .popover-icon,
  .changelog-icon-btn img {
    filter: invert(1) sepia(100%) hue-rotate(180deg);
  }

}

================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "lib": [
      "ES2022",
      "DOM",
      "DOM.Iterable"
    ],
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "allowJs": true,
    "checkJs": false,
    "outDir": "./dist",
    "rootDir": "./",
    "strict": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": false,
    "declarationMap": false,
    "sourceMap": false,
    "noEmit": true,
    "types": [
      "node"
    ]
  },
  "include": [
    "src/**/*",
    "*.ts"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "Icons"
  ]
}
Download .txt
gitextract_hojju54v/

├── .gitignore
├── .nvmrc
├── LICENSE.txt
├── README.md
├── functions/
│   └── _middleware.ts
├── package.json
├── public/
│   ├── .well-known/
│   │   ├── agent-skills/
│   │   │   ├── index.json
│   │   │   └── sargam-icons/
│   │   │       └── SKILL.md
│   │   ├── api-catalog
│   │   └── mcp/
│   │       └── server-card.json
│   ├── _headers
│   ├── robots.txt
│   └── sitemap.xml
├── rspack.config.ts
├── scripts/
│   └── generate-changelog.ts
├── src/
│   ├── fonts/
│   │   └── JivaMono.otf
│   ├── index.ts
│   ├── sargam.ts
│   └── styles/
│       └── base.scss
└── tsconfig.json
Download .txt
SYMBOL INDEX (38 symbols across 5 files)

FILE: functions/_middleware.ts
  type EventContext (line 15) | interface EventContext<E = unknown> {
  type PagesFunction (line 20) | type PagesFunction<E = unknown> = (ctx: EventContext<E>) => Promise<Resp...
  type Env (line 22) | interface Env {}
  function acceptsMarkdown (line 24) | function acceptsMarkdown(request: Request): boolean {
  function decodeEntities (line 30) | function decodeEntities(text: string): string {
  function sanitizeMeta (line 42) | function sanitizeMeta(raw: string): string {
  function htmlToMarkdown (line 48) | function htmlToMarkdown(html: string): string {
  function estimateTokens (line 116) | function estimateTokens(text: string): number {

FILE: rspack.config.ts
  type BuildEnv (line 9) | interface BuildEnv {
  type Argv (line 13) | interface Argv {

FILE: scripts/generate-changelog.ts
  type ChangelogEntry (line 10) | interface ChangelogEntry {
  type Changelog (line 17) | interface Changelog {
  function execGit (line 23) | function execGit(command: string): string {
  type VersionCommit (line 31) | interface VersionCommit {
  function getVersionCommits (line 37) | function getVersionCommits(): VersionCommit[] {
  function getNewIconsBetweenCommits (line 84) | function getNewIconsBetweenCommits(fromHash: string, toHash: string): st...
  function countTotalIcons (line 105) | function countTotalIcons(): number {
  function generateChangelog (line 115) | function generateChangelog(): Changelog {

FILE: src/index.ts
  function normalize (line 12) | function normalize(text: string): string {
  function getItemName (line 16) | function getItemName(item: HTMLElement): string {
  function filter (line 21) | function filter(query: string): void {
  function onInputLike (line 42) | function onInputLike(): void {

FILE: src/sargam.ts
  type ChangelogEntry (line 13) | interface ChangelogEntry {
  type Changelog (line 20) | interface Changelog {
  function getVersion (line 30) | function getVersion(): string {
  function loadChangelog (line 40) | function loadChangelog(): Changelog {
  function formatDate (line 50) | function formatDate(dateStr: string): string {
  function getIconNames (line 59) | function getIconNames(directory: string): string[] {
  constant BRAND_SVG (line 72) | const BRAND_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="32" he...
  constant THEME_TOGGLE_SVGS (line 75) | const THEME_TOGGLE_SVGS = `
  function generatePopoverHtml (line 89) | function generatePopoverHtml(): string {
  function generatePopoverScript (line 129) | function generatePopoverScript(
  function generateThemeToggleScript (line 422) | function generateThemeToggleScript(): string {
  function generateProgressiveLoadingScript (line 470) | function generateProgressiveLoadingScript(): string {
  function generateFontLoadingScript (line 509) | function generateFontLoadingScript(): string {
  function getCriticalCSS (line 529) | function getCriticalCSS(): string {
  constant VERSION (line 547) | const VERSION = getVersion();
  constant CDN_BASE_URL (line 548) | const CDN_BASE_URL = `https://cdn.jsdelivr.net/npm/sargam-icons@${VERSIO...
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (97K chars).
[
  {
    "path": ".gitignore",
    "chars": 112,
    "preview": "node_modules\ndist\nsrc/assets\nsrc/template.html\nsrc/changelog.html\nsrc/changelog.json\n.env\n.DS_Store\n**/.DS_Store"
  },
  {
    "path": ".nvmrc",
    "chars": 3,
    "preview": "22\n"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1053,
    "preview": "MIT License\n\nCopyright (c) 2026 Abhimanyu Rana @planetabhi\n\nPermission is hereby granted, free of charge, to any person "
  },
  {
    "path": "README.md",
    "chars": 876,
    "preview": "```ascii\n   ____       ___        _____       __  ___    \n  / __/__ _  / _ \\___   / ___/__ _  /  |/  /__ _\n _\\ \\/ _ `/ /"
  },
  {
    "path": "functions/_middleware.ts",
    "chars": 4692,
    "preview": "/**\n * Cloudflare Pages Function middleware for Markdown content negotiation.\n *\n * When a request includes `Accept: tex"
  },
  {
    "path": "package.json",
    "chars": 1970,
    "preview": "{\n  \"name\": \"sargam-icons\",\n  \"version\": \"1.6.7\",\n  \"description\": \"A collection of 1200+ open-source icons.\",\n  \"script"
  },
  {
    "path": "public/.well-known/agent-skills/index.json",
    "chars": 446,
    "preview": "{\n  \"$schema\": \"https://schemas.agentskills.io/discovery/0.2.0/schema.json\",\n  \"skills\": [\n    {\n      \"name\": \"sargam-i"
  },
  {
    "path": "public/.well-known/agent-skills/sargam-icons/SKILL.md",
    "chars": 504,
    "preview": "# Sargam Icons Discovery\n\nDiscover and browse 1,200+ open-source icons in Line, Fill, and Duotone styles from Sargam Ico"
  },
  {
    "path": "public/.well-known/api-catalog",
    "chars": 500,
    "preview": "{\n  \"linkset\": [\n    {\n      \"anchor\": \"https://sargamicons.com/\",\n      \"service-doc\": [\n        {\n          \"href\": \"h"
  },
  {
    "path": "public/.well-known/mcp/server-card.json",
    "chars": 705,
    "preview": "{\n  \"serverInfo\": {\n    \"name\": \"sargam-icons\",\n    \"version\": \"1.6.7\",\n    \"description\": \"A collection of 1,200+ open-"
  },
  {
    "path": "public/_headers",
    "chars": 626,
    "preview": "# Link response headers for agent discovery (RFC 8288, RFC 9727)\n/\n  Link: </sitemap.xml>; rel=\"sitemap\"; type=\"applicat"
  },
  {
    "path": "public/robots.txt",
    "chars": 1789,
    "preview": "# robots.txt for sargam-icons\n# https://www.rfc-editor.org/rfc/rfc9309\n# Content Signals: https://contentsignals.org/\n\n#"
  },
  {
    "path": "public/sitemap.xml",
    "chars": 364,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n  <url>\n    <loc>htt"
  },
  {
    "path": "rspack.config.ts",
    "chars": 4474,
    "preview": "import path from 'path';\nimport { rspack, Configuration } from '@rspack/core';\nimport { fileURLToPath } from 'url';\n\n// "
  },
  {
    "path": "scripts/generate-changelog.ts",
    "chars": 5293,
    "preview": "import { execSync } from 'child_process';\nimport fs from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'u"
  },
  {
    "path": "src/index.ts",
    "chars": 2577,
    "preview": "import './styles/base.scss';\n\ndocument.addEventListener('DOMContentLoaded', () => {\n    const searchInput = document.get"
  },
  {
    "path": "src/sargam.ts",
    "chars": 40532,
    "preview": "import fs from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\n// Get __dirname equivalent in ES mo"
  },
  {
    "path": "src/styles/base.scss",
    "chars": 23037,
    "preview": "@use \"@new-ui/reset\";\n@use \"@new-ui/colors\";\n\n@font-face {\n  font-family: \"Jiva Mono\";\n  src: url(\"../fonts/JivaMono.wof"
  },
  {
    "path": "tsconfig.json",
    "chars": 709,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"ESNext\",\n    \"lib\": [\n      \"ES2022\",\n      \"DOM\",\n     "
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the planetabhi/sargam-icons GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (88.1 KB), approximately 27.9k tokens, and a symbol index with 38 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!