{
  "settings": {
    "siteName": "HeySwan",
    "adminUser": "somiawan142",
    "adminPass": "somi@#@123"
  },
  "tools": [
    {
      "id": "word-counter-7833",
      "name": "Word & Character Counter (with Keyword Density)",
      "category": "Text",
      "slug": "word-counter",
      "badge": "Popular",
      "iconSvg": "<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n  <defs>\n    <linearGradient id=\"wordPinkGrad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"100%\">\n      <stop offset=\"0%\" stop-color=\"#ec4899\" />\n      <stop offset=\"100%\" stop-color=\"#8b5cf6\" />\n    </linearGradient>\n  </defs>\n  <rect width=\"24\" height=\"24\" rx=\"5\" fill=\"url(#wordPinkGrad)\"/>\n  <line x1=\"6.5\" y1=\"10\" x2=\"17.5\" y2=\"10\" stroke=\"#FFFFFF\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>\n  <line x1=\"6.5\" y1=\"14.5\" x2=\"17.5\" y2=\"14.5\" stroke=\"#FFFFFF\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>\n  <line x1=\"10\" y1=\"6.5\" x2=\"8.5\" y2=\"18\" stroke=\"#FFFFFF\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>\n  <line x1=\"15\" y1=\"6.5\" x2=\"13.5\" y2=\"18\" stroke=\"#FFFFFF\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>\n</svg>",
      "active": true,
      "usageCount": 33,
      "seo": {
        "targetKeyword": "word & character counter with keyword density online",
        "metaTitle": "Word & Character Counter with Keyword Density Online Free",
        "metaDescription": "Count words, characters, sentences, and calculate real-time keyword density online for free. Instant reading time and speaking duration with 100% private tool.",
        "canonical": "https://tools.heyswan.cloud/tool/word-counter",
        "ogImage": "",
        "indexable": true
      },
      "content": {
        "about": "Our Free Online Word & Character Counter with Keyword Density helps writers, SEO professionals, copywriters, and students analyze their written text in real time. Beyond basic word and character counts, our tool features a built-in Keyword Density Calculator that analyzes single-word, two-word, and three-word frequencies to help you prevent keyword stuffing and optimize content for Google search rankings.\n\nEverything runs 100% locally in your browser memory. Your drafts, essays, confidential articles, and client documents are never uploaded to any remote server or stored in any database. Get instant metrics on reading duration, speaking speed, paragraph counts, and character limits for social media platforms (Twitter/X, Instagram, LinkedIn) with zero latency.",
        "howTo": "1. Type or paste your article, essay, or text content into the main editor box.\n2. View real-time live statistics for total words, characters (with and without spaces), sentences, and paragraphs.\n3. Check the keyword density table to analyze your top repeating keywords and word frequency percentages.\n4. Review estimated reading time and speaking duration, then Copy your formatted text or Clear with one click."
      },
      "engine": {
        "html": "<div class=\"omni\">\n\n  <!-- 1. TOP 1-LINE RESPONSIVE ACTION TOOLBAR -->\n  <div class=\"top-action-bar\">\n    <div class=\"top-badge-wrap\">\n      <span class=\"badge bg-primary-lt rounded-pill px-3 py-1 fw-bold small\">Live Text Analysis</span>\n    </div>\n\n    <!-- 1 Single Line Buttons Row -->\n    <div class=\"actions-row\">\n      <button type=\"button\" class=\"icon-btn\" id=\"importBtn\" title=\"Import Text File\" aria-label=\"Import text\">\n        <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 3v12\"/><path d=\"m7 10 5 5 5-5\"/><path d=\"M5 21h14\"/></svg>\n      </button>\n      <button type=\"button\" class=\"icon-btn\" id=\"copyBtn\" title=\"Copy Text\" aria-label=\"Copy text\">\n        <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg>\n      </button>\n      <button type=\"button\" class=\"icon-btn\" id=\"undoBtn\" title=\"Undo (Ctrl+Z)\" aria-label=\"Undo\">\n        <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M9 14 4 9l5-5\"/><path d=\"M4 9h10a6 6 0 0 1 0 12h-1\"/></svg>\n      </button>\n      <button type=\"button\" class=\"icon-btn\" id=\"redoBtn\" title=\"Redo (Ctrl+Y)\" aria-label=\"Redo\">\n        <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"m15 14 5-5-5-5\"/><path d=\"M20 9H10a6 6 0 0 0 0 12h1\"/></svg>\n      </button>\n      <button type=\"button\" class=\"primary-btn\" id=\"analyzeBtn\">Analyze</button>\n      <button type=\"button\" class=\"icon-btn text-danger\" id=\"clearBtn\" title=\"Clear Text\" aria-label=\"Clear\">\n        <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M3 6h18\"/><path d=\"M8 6V4h8v2\"/><path d=\"M19 6l-1 14H6L5 6\"/></svg>\n      </button>\n    </div>\n  </div>\n\n  <!-- 2. MAIN TEXT EDITOR PANEL -->\n  <section class=\"panel mb-3\">\n    <div class=\"editor-top\">\n      <div class=\"chips\">\n        <span class=\"chip\">Live Analysis</span>\n        <span class=\"chip\">100% Client-Side</span>\n        <span class=\"chip\">Heuristic Quality</span>\n      </div>\n      <div class=\"chips d-none d-sm-flex\">\n        <span class=\"chip\">Ctrl + Enter</span>\n      </div>\n    </div>\n\n    <div class=\"editor-wrap\">\n      <textarea id=\"textInput\" spellcheck=\"true\" placeholder=\"Start typing or paste your text here to analyze words, characters, sentences, readability, and writing quality...\"></textarea>\n    </div>\n\n    <div class=\"editor-footer\">\n      <span id=\"status\">Ready</span>\n      <span id=\"cursorInfo\">0 characters</span>\n    </div>\n  </section>\n\n  <!-- 3. MAIN STATS CARDS -->\n  <section class=\"stats mb-3\">\n    <div class=\"stat\">\n      <div class=\"stat-title\"><i class=\"stat-icon\">W</i>Words</div>\n      <div class=\"stat-value\" id=\"words\">0</div>\n    </div>\n    <div class=\"stat\">\n      <div class=\"stat-title\"><i class=\"stat-icon\">C</i>Characters</div>\n      <div class=\"stat-value\" id=\"chars\">0</div>\n    </div>\n    <div class=\"stat\">\n      <div class=\"stat-title\"><i class=\"stat-icon\">C₁</i>No Spaces</div>\n      <div class=\"stat-value\" id=\"charsNoSpace\">0</div>\n    </div>\n    <div class=\"stat\">\n      <div class=\"stat-title\"><i class=\"stat-icon\">S</i>Sentences</div>\n      <div class=\"stat-value\" id=\"sentences\">0</div>\n    </div>\n    <div class=\"stat\">\n      <div class=\"stat-title\"><i class=\"stat-icon\">P</i>Paragraphs</div>\n      <div class=\"stat-value\" id=\"paragraphs\">0</div>\n    </div>\n    <div class=\"stat\">\n      <div class=\"stat-title\"><i class=\"stat-icon\">L</i>Lines</div>\n      <div class=\"stat-value\" id=\"lines\">0</div>\n    </div>\n    <div class=\"stat\">\n      <div class=\"stat-title\"><i class=\"stat-icon\">R</i>Reading Time</div>\n      <div class=\"stat-value\" id=\"reading\">0m</div>\n    </div>\n    <div class=\"stat\">\n      <div class=\"stat-title\"><i class=\"stat-icon\">U</i>Unique Words</div>\n      <div class=\"stat-value\" id=\"unique\">0</div>\n    </div>\n  </section>\n\n  <!-- 4. TEXT TRANSFORM TOOLBAR -->\n  <section class=\"panel tools mb-3\">\n    <span class=\"tool-label\">Transform:</span>\n    <button type=\"button\" class=\"tool\" data-action=\"upper\">UPPERCASE</button>\n    <button type=\"button\" class=\"tool\" data-action=\"lower\">lowercase</button>\n    <button type=\"button\" class=\"tool\" data-action=\"capitalize\">Capitalize</button>\n    <button type=\"button\" class=\"tool\" data-action=\"title\">Title Case</button>\n    <button type=\"button\" class=\"tool\" data-action=\"alternating\">aLtErNaTiNg</button>\n    <button type=\"button\" class=\"tool\" data-action=\"reverse\">Reverse</button>\n    <button type=\"button\" class=\"tool\" data-action=\"reverseWords\">Reverse Words</button>\n    <button type=\"button\" class=\"tool\" data-action=\"trim\">Trim Spaces</button>\n    <button type=\"button\" class=\"tool\" data-action=\"extra\">Remove Extra Spaces</button>\n    <button type=\"button\" class=\"tool\" data-action=\"breaks\">Remove Line Breaks</button>\n    <button type=\"button\" class=\"tool\" data-action=\"blank\">Remove Blank Lines</button>\n    <button type=\"button\" class=\"tool\" data-action=\"duplicate\">Remove Duplicate Lines</button>\n    <button type=\"button\" class=\"tool\" data-action=\"sortAZ\">Sort A–Z</button>\n    <button type=\"button\" class=\"tool\" data-action=\"sortZA\">Sort Z–A</button>\n  </section>\n\n  <!-- 5. FIND / REPLACE BOX -->\n  <section class=\"panel search-box mb-3\">\n    <input class=\"input\" id=\"findInput\" placeholder=\"Find text...\">\n    <input class=\"input\" id=\"replaceInput\" placeholder=\"Replace with...\">\n    <button type=\"button\" class=\"primary-btn\" id=\"replaceBtn\">Replace All</button>\n  </section>\n\n  <!-- 6. ADVANCED ANALYSIS GRID -->\n  <section class=\"main-grid\">\n\n    <!-- Metrics Card -->\n    <div class=\"card\">\n      <div class=\"card-head\">\n        <h2>Advanced Metrics</h2>\n        <span>Live</span>\n      </div>\n      <div class=\"grid4\">\n        <div class=\"metric\"><small>Avg Words / Sentence</small><strong id=\"avgSentence\">0</strong></div>\n        <div class=\"metric\"><small>Avg Chars / Word</small><strong id=\"avgChars\">0</strong></div>\n        <div class=\"metric\"><small>Longest Word</small><strong id=\"longest\">—</strong></div>\n        <div class=\"metric\"><small>Shortest Word</small><strong id=\"shortest\">—</strong></div>\n        <div class=\"metric\"><small>Speaking Time</small><strong id=\"speaking\">0m</strong></div>\n        <div class=\"metric\"><small>Short Words (&le;3)</small><strong id=\"shortWords\">0</strong></div>\n        <div class=\"metric\"><small>Long Words (&ge;8)</small><strong id=\"longWords\">0</strong></div>\n        <div class=\"metric\"><small>Repeated Words</small><strong id=\"repeated\">0</strong></div>\n        <div class=\"metric\"><small>Letters</small><strong id=\"letters\">0</strong></div>\n        <div class=\"metric\"><small>Numbers</small><strong id=\"numbers\">0</strong></div>\n        <div class=\"metric\"><small>Punctuation</small><strong id=\"punctuation\">0</strong></div>\n        <div class=\"metric\"><small>Whitespace</small><strong id=\"whitespace\">0</strong></div>\n      </div>\n    </div>\n\n    <!-- Keyword Analysis Card -->\n    <div class=\"card\">\n      <div class=\"card-head\">\n        <h2>Top 10 Keywords</h2>\n        <span>Density</span>\n      </div>\n      <div class=\"keyword-list\" id=\"keywords\">\n        <div class=\"quality-row\"><span>Start typing to analyze keywords.</span></div>\n      </div>\n    </div>\n\n    <!-- Writing Quality Heuristics -->\n    <div class=\"card\">\n      <div class=\"card-head\">\n        <h2>Writing Quality</h2>\n        <span>Heuristic</span>\n      </div>\n      <div class=\"quality\">\n        <div class=\"quality-row\"><span>Very long sentences (&gt;30 words)</span><b class=\"badge good\" id=\"longSentences\">Good</b></div>\n        <div class=\"quality-row\"><span>Repeated words</span><b class=\"badge good\" id=\"repeatCheck\">Good</b></div>\n        <div class=\"quality-row\"><span>Excessive punctuation</span><b class=\"badge good\" id=\"punctCheck\">Good</b></div>\n        <div class=\"quality-row\"><span>Filler words</span><b class=\"badge good\" id=\"fillerCheck\">Good</b></div>\n        <div class=\"quality-row\"><span>Possible passive wording</span><b class=\"badge good\" id=\"passiveCheck\">Good</b></div>\n      </div>\n    </div>\n\n    <!-- SEO Text Analysis -->\n    <div class=\"card\">\n      <div class=\"card-head\">\n        <h2>SEO Text Checks</h2>\n        <span>On-page</span>\n      </div>\n      <div class=\"seo-grid\">\n        <div class=\"seo-item\"><small>Word Count</small><strong id=\"seoWords\">0</strong></div>\n        <div class=\"seo-item\"><small>Top Keyword Density</small><strong id=\"density\">0%</strong></div>\n        <div class=\"seo-item\"><small>Headings (Markdown)</small><strong id=\"headingCount\">0</strong></div>\n        <div class=\"seo-item\"><small>Links Found</small><strong id=\"linkCount\">0</strong></div>\n        <div class=\"seo-item\"><small>Images Found</small><strong id=\"imageCount\">0</strong></div>\n        <div class=\"seo-item\"><small>Average Sentence</small><strong id=\"seoSentence\">0 words</strong></div>\n      </div>\n    </div>\n\n    <!-- Readability Estimation -->\n    <div class=\"card\">\n      <div class=\"card-head\">\n        <h2>Readability Scores</h2>\n        <span>Flesch-Kincaid</span>\n      </div>\n      <div class=\"grid4\">\n        <div class=\"metric\"><small>Reading Level</small><strong id=\"readingLevel\">—</strong></div>\n        <div class=\"metric\"><small>Grade Estimate</small><strong id=\"grade\">—</strong></div>\n        <div class=\"metric\"><small>Avg Sentence Length</small><strong id=\"readAvgSentence\">0</strong></div>\n        <div class=\"metric\"><small>Complex Words (&ge;3 syl)</small><strong id=\"complexWords\">0</strong></div>\n      </div>\n    </div>\n\n    <!-- Limits Target -->\n    <div class=\"card\">\n      <div class=\"card-head\">\n        <h2>Target Word & Char Limits</h2>\n        <span>Progress</span>\n      </div>\n      <div class=\"limit-box\">\n        <div class=\"limit\">\n          <label>Word Limit Target</label>\n          <input type=\"number\" id=\"wordLimit\" min=\"0\" placeholder=\"e.g. 500\">\n          <div class=\"progress\"><i id=\"wordProgress\"></i></div>\n        </div>\n        <div class=\"limit\">\n          <label>Char Limit Target</label>\n          <input type=\"number\" id=\"charLimit\" min=\"0\" placeholder=\"e.g. 280\">\n          <div class=\"progress\"><i id=\"charProgress\"></i></div>\n        </div>\n      </div>\n    </div>\n\n  </section>\n\n  <input type=\"file\" id=\"fileInput\" accept=\".txt,.md,.text\" hidden>\n</div>",
        "css": "/* ===================================================\n   PERFECT MOBILE 1-LINE TOOLBAR & SPACIOUS LAYOUT\n   =================================================== */\n\n/* Container: Full width on mobile, spacious on desktop */\n.omni {\n  width: 100%;\n  max-width: 1140px;\n  margin: 0 auto;\n  padding: 0 4px;\n  box-sizing: border-box;\n}\n\n.omni button, .omni input, .omni textarea, .omni select { font: inherit; }\n.omni button { cursor: pointer; }\n\n/* 1. TOP ACTION BAR (SEPARATED WITH PROPER GAP) */\n.top-action-bar {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 8px;\n  margin-bottom: 14px; /* Proper gap, no sticking */\n  width: 100%;\n}\n\n.top-badge-wrap {\n  flex-shrink: 0;\n}\n\n/* 1-Line Strict Row for Buttons */\n.actions-row {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  flex-wrap: nowrap; /* Strictly 1 line */\n  justify-content: flex-end;\n}\n\n.icon-btn {\n  width: 35px;\n  height: 35px;\n  border: 1px solid var(--border);\n  border-radius: 8px;\n  background: var(--surface);\n  color: var(--text);\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  transition: all 0.15s ease;\n  flex-shrink: 0;\n}\n.icon-btn:hover {\n  background: var(--surface-hover);\n  border-color: var(--primary);\n  color: var(--primary);\n}\n.icon-btn svg { width: 15px; height: 15px; }\n\n.primary-btn {\n  height: 35px;\n  border: 0;\n  border-radius: 8px;\n  background: var(--primary);\n  color: #ffffff;\n  padding: 0 14px;\n  font-size: 12px;\n  font-weight: 700;\n  transition: all 0.15s ease;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n  white-space: nowrap;\n}\n.primary-btn:hover { background: var(--primary-hover); }\n\n/* 2. EDITOR PANEL */\n.panel {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: var(--radius);\n  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.12);\n  overflow: hidden;\n  margin-bottom: 14px;\n}\n\n.editor-top {\n  min-height: 40px;\n  padding: 8px 12px;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  border-bottom: 1px solid var(--border);\n  background: var(--surface-hover);\n  flex-wrap: wrap;\n  gap: 6px;\n}\n\n.chips { display: flex; gap: 5px; flex-wrap: wrap; }\n.chip {\n  border: 1px solid var(--border);\n  background: var(--surface);\n  color: var(--text-muted);\n  border-radius: 6px;\n  padding: 3px 8px;\n  font-size: 11px;\n  font-weight: 700;\n  white-space: nowrap;\n}\n\n.editor-wrap textarea {\n  width: 100%;\n  min-height: 250px;\n  display: block;\n  resize: vertical;\n  border: 0;\n  outline: 0;\n  padding: 16px;\n  background: var(--surface) !important;\n  color: var(--text) !important;\n  caret-color: var(--primary);\n  line-height: 1.65;\n  font-size: 15px;\n  box-sizing: border-box;\n}\n\n.editor-footer {\n  padding: 8px 14px;\n  min-height: 36px;\n  border-top: 1px solid var(--border);\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  color: var(--text-muted);\n  font-size: 12px;\n  font-weight: 600;\n}\n\n/* 3. STATS CARDS */\n.stats {\n  display: grid;\n  grid-template-columns: repeat(2, 1fr);\n  gap: 10px;\n  margin-bottom: 14px;\n}\n\n@media (min-width: 640px) {\n  .stats {\n    grid-template-columns: repeat(4, 1fr);\n    gap: 12px;\n  }\n}\n\n.stat {\n  padding: 12px 14px;\n  border: 1px solid var(--border);\n  background: var(--surface);\n  border-radius: 12px;\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  transition: transform 0.15s ease, border-color 0.15s ease;\n}\n.stat:hover {\n  transform: translateY(-2px);\n  border-color: var(--primary);\n}\n\n.stat-title {\n  color: var(--text-muted);\n  font-size: 12px;\n  font-weight: 700;\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  margin-bottom: 4px;\n}\n\n.stat-icon {\n  width: 22px;\n  height: 22px;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: 6px;\n  color: var(--badge-text);\n  background: var(--badge-bg);\n  font-size: 11px;\n  font-weight: 900;\n  font-style: normal;\n}\n\n.stat-value {\n  font-size: 22px;\n  line-height: 1.1;\n  font-weight: 800;\n  color: var(--text);\n  font-family: 'SFMono-Regular', Consolas, Menlo, monospace;\n}\n\n/* 4. TRANSFORM TOOLBAR */\n.tools {\n  padding: 8px 12px;\n  display: flex;\n  gap: 6px;\n  align-items: center;\n  overflow-x: auto;\n  scrollbar-width: none;\n  margin-bottom: 14px;\n}\n.tools::-webkit-scrollbar { display: none; }\n\n.tool-label {\n  color: var(--text-muted);\n  font-size: 11px;\n  font-weight: 800;\n  flex: 0 0 auto;\n  margin-right: 4px;\n  text-transform: uppercase;\n  letter-spacing: 0.04em;\n}\n\n.tool {\n  flex: 0 0 auto;\n  padding: 5px 10px;\n  border: 1px solid var(--border);\n  background: var(--surface-hover);\n  color: var(--text);\n  border-radius: 20px;\n  font-size: 11px;\n  font-weight: 700;\n  transition: all 0.15s ease;\n  white-space: nowrap;\n}\n.tool:hover {\n  color: var(--primary);\n  border-color: var(--primary);\n  background: var(--badge-bg);\n}\n\n/* 5. SEARCH & REPLACE BOX */\n.search-box {\n  padding: 10px 14px;\n  display: grid;\n  grid-template-columns: 1fr;\n  gap: 8px;\n  margin-bottom: 14px;\n}\n@media (min-width: 600px) {\n  .search-box {\n    grid-template-columns: 1fr 1fr auto;\n    gap: 10px;\n  }\n}\n\n.input {\n  width: 100%;\n  height: 38px;\n  border: 1px solid var(--border);\n  border-radius: 8px;\n  outline: 0;\n  background: var(--surface-hover);\n  color: var(--text);\n  padding: 0 12px;\n  font-size: 13px;\n}\n.input:focus { border-color: var(--primary); }\n\n/* 6. ADVANCED METRICS GRID */\n.main-grid {\n  display: grid;\n  grid-template-columns: 1fr;\n  gap: 14px;\n}\n@media (min-width: 900px) {\n  .main-grid {\n    grid-template-columns: 1.15fr 0.85fr;\n    gap: 16px;\n  }\n}\n\n.card {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 12px;\n  overflow: hidden;\n}\n\n.card-head {\n  min-height: 40px;\n  padding: 10px 14px;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  border-bottom: 1px solid var(--border);\n  background: var(--surface-hover);\n}\n.card-head h2 { margin: 0; font-size: 13px; font-weight: 800; color: var(--text); }\n.card-head span { color: var(--text-muted); font-size: 11px; font-weight: 600; }\n\n.grid4 {\n  display: grid;\n  grid-template-columns: repeat(2, 1fr);\n}\n@media (min-width: 540px) {\n  .grid4 { grid-template-columns: repeat(4, 1fr); }\n}\n\n.metric {\n  padding: 10px 12px;\n  border-right: 1px solid var(--border);\n  border-bottom: 1px solid var(--border);\n}\n.metric:nth-child(2n) { border-right: 0; }\n@media (min-width: 540px) {\n  .metric:nth-child(2n) { border-right: 1px solid var(--border); }\n  .metric:nth-child(4n) { border-right: 0; }\n}\n\n.metric small { display: block; color: var(--text-muted); font-size: 11px; font-weight: 600; margin-bottom: 3px; }\n.metric strong { font-size: 13px; display: block; color: var(--text); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-family: 'SFMono-Regular', Consolas, monospace; }\n\n/* 7. KEYWORDS, QUALITY, SEO & LIMITS */\n.keyword-list { padding: 10px 12px; display: grid; gap: 6px; max-height: 220px; overflow-y: auto; }\n.keyword {\n  display: grid;\n  grid-template-columns: 1fr auto auto;\n  gap: 10px;\n  align-items: center;\n  padding: 7px 10px;\n  background: var(--surface-hover);\n  border-radius: 8px;\n  font-size: 12px;\n}\n.keyword b { color: var(--text); overflow: hidden; text-overflow: ellipsis; }\n.keyword span { color: var(--text-muted); font-size: 11px; font-weight: 600; }\n.keyword em { color: var(--primary); font-size: 11px; font-style: normal; font-weight: 800; }\n\n.quality { display: grid; gap: 8px; padding: 12px; }\n.quality-row {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  gap: 10px;\n  padding: 7px 10px;\n  background: var(--surface-hover);\n  border-radius: 8px;\n}\n.quality-row span { font-size: 12px; color: var(--text); font-weight: 600; }\n\n.badge { border-radius: 20px; padding: 3px 9px; font-size: 11px; font-weight: 800; }\n.good { color: var(--accent); background: rgba(16, 185, 129, 0.12); }\n.warn { color: #f59e0b; background: rgba(245, 158, 11, 0.12); }\n.bad { color: #ef4444; background: rgba(239, 68, 68, 0.12); }\n\n.seo-grid { padding: 12px; display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }\n.seo-item { padding: 8px 10px; border-radius: 8px; background: var(--surface-hover); }\n.seo-item small { display: block; color: var(--text-muted); font-size: 11px; font-weight: 600; margin-bottom: 2px; }\n.seo-item strong { font-size: 13px; color: var(--text); font-family: 'SFMono-Regular', Consolas, monospace; }\n\n.limit-box { padding: 12px; display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }\n.limit { padding: 8px 10px; background: var(--surface-hover); border-radius: 8px; }\n.limit label { display: block; color: var(--text-muted); font-size: 11px; margin-bottom: 4px; font-weight: 700; }\n.limit input {\n  width: 100%;\n  border: 1px solid var(--border);\n  background: var(--surface);\n  color: var(--text);\n  height: 32px;\n  border-radius: 6px;\n  padding: 0 8px;\n  outline: 0;\n  font-size: 12px;\n}\n.progress { height: 4px; background: var(--border); border-radius: 10px; overflow: hidden; margin-top: 8px; }\n.progress i { display: block; height: 100%; width: 0; background: var(--primary); }\n\n/* ===================================================\n   MOBILE-SPECIFIC SCREEN FIXES (< 580px)\n   =================================================== */\n@media (max-width: 580px) {\n  .omni {\n    padding: 0 2px; /* Maximum usable width on mobile */\n  }\n\n  /* Hide left badge on mobile to let all buttons fit on 1 line! */\n  .top-badge-wrap {\n    display: none;\n  }\n\n  .actions-row {\n    width: 100%;\n    justify-content: space-between; /* Perfect 1-line spread */\n  }\n\n  .icon-btn {\n    width: 32px;\n    height: 32px;\n  }\n  .icon-btn svg {\n    width: 14px;\n    height: 14px;\n  }\n\n  .primary-btn {\n    height: 32px;\n    padding: 0 10px;\n    font-size: 11px;\n  }\n\n  .editor-wrap textarea {\n    min-height: 190px;\n    font-size: 14px;\n    padding: 12px;\n  }\n\n  .stat-value {\n    font-size: 20px;\n  }\n}",
        "js": "(function() {\n  const $ = id => document.getElementById(id);\n  const input = $(\"textInput\");\n  const fileInput = $(\"fileInput\");\n\n  if (!input) return;\n\n  const STOP = new Set([\n    \"the\",\"and\",\"for\",\"are\",\"but\",\"not\",\"you\",\"your\",\"with\",\"this\",\n    \"that\",\"from\",\"have\",\"has\",\"had\",\"was\",\"were\",\"will\",\"would\",\n    \"there\",\"their\",\"they\",\"them\",\"then\",\"than\",\"into\",\"about\",\n    \"what\",\"when\",\"where\",\"which\",\"while\",\"who\",\"how\",\"why\",\n    \"can\",\"could\",\"should\",\"a\",\"an\",\"to\",\"of\",\"in\",\"on\",\"at\",\n    \"is\",\"it\",\"as\",\"be\",\"or\",\"if\",\"by\",\"we\",\"he\",\"she\",\"i\",\n    \"me\",\"my\",\"our\",\"us\",\"do\",\"does\",\"did\",\"so\",\"no\",\"yes\"\n  ]);\n\n  const FILLERS = new Set([\n    \"very\",\"really\",\"actually\",\"basically\",\"literally\",\n    \"just\",\"perhaps\",\"maybe\",\"quite\",\"rather\",\"simply\",\n    \"probably\",\"generally\",\"obviously\",\"certainly\"\n  ]);\n\n  let history = [\"\"];\n  let historyIndex = 0;\n  let changing = false;\n\n  function getWords(text) {\n    if (!text.trim()) return [];\n    if (window.Intl && Intl.Segmenter) {\n      const s = new Intl.Segmenter(undefined, { granularity: \"word\" });\n      return [...s.segment(text)].filter(x => x.isWordLike).map(x => x.segment);\n    }\n    return text.match(/\\b[\\p{L}\\p{N}'’-]+\\b/gu) || [];\n  }\n\n  function time(min) {\n    if (!min) return \"0m\";\n    if (min < 1) return \"<1m\";\n    const h = Math.floor(min / 60);\n    const m = min % 60;\n    return h ? `${h}h ${m}m` : `${m}m`;\n  }\n\n  function syllables(word) {\n    word = word.toLowerCase().replace(/[^a-z]/g, \"\");\n    if (!word) return 0;\n    if (word.length <= 3) return 1;\n    const groups = word.match(/[aeiouy]+/g);\n    let count = groups ? groups.length : 1;\n    if (word.endsWith(\"e\") && count > 1) count--;\n    return Math.max(1, count);\n  }\n\n  function analyze() {\n    const text = input.value;\n    const words = getWords(text);\n    const clean = words.map(x => x.toLowerCase().replace(/[^\\p{L}\\p{N}'’-]/gu, \"\")).filter(Boolean);\n    const count = words.length;\n    const chars = text.length;\n    const noSpace = text.replace(/\\s/g, \"\").length;\n    const sentenceList = text.trim() ? (text.match(/[^.!?]+[.!?]+|[^.!?]+$/g) || []) : [];\n    const sentences = sentenceList.length;\n    const paragraphs = text.trim() ? text.trim().split(/\\n\\s*\\n/).filter(Boolean).length : 0;\n    const lines = text ? text.split(/\\r?\\n/).length : 0;\n    const unique = new Set(clean);\n\n    const frequency = new Map();\n    clean.forEach(w => frequency.set(w, (frequency.get(w) || 0) + 1));\n    const repeated = [...frequency.values()].filter(v => v > 1).length;\n\n    const longest = words.reduce((a, b) => b.length > a.length ? b : a, \"\");\n    const shortest = words.reduce((a, b) => !a || b.length < a.length ? b : a, \"\");\n    const short = words.filter(w => w.length <= 3).length;\n    const long = words.filter(w => w.length >= 8).length;\n\n    const avgChars = count ? noSpace / count : 0;\n    const avgSentence = sentences ? count / sentences : 0;\n\n    const letters = (text.match(/\\p{L}/gu) || []).length;\n    const numbers = (text.match(/\\p{N}/gu) || []).length;\n    const punctuation = (text.match(/[^\\p{L}\\p{N}\\s]/gu) || []).length;\n    const whitespace = (text.match(/\\s/g) || []).length;\n\n    $(\"words\").textContent = count.toLocaleString();\n    $(\"chars\").textContent = chars.toLocaleString();\n    $(\"charsNoSpace\").textContent = noSpace.toLocaleString();\n    $(\"sentences\").textContent = sentences.toLocaleString();\n    $(\"paragraphs\").textContent = paragraphs.toLocaleString();\n    $(\"lines\").textContent = lines.toLocaleString();\n    $(\"reading\").textContent = time(Math.ceil(count / 200));\n    $(\"unique\").textContent = unique.size.toLocaleString();\n\n    $(\"avgSentence\").textContent = avgSentence.toFixed(1);\n    $(\"avgChars\").textContent = avgChars.toFixed(1);\n    $(\"longest\").textContent = longest || \"—\";\n    $(\"shortest\").textContent = shortest || \"—\";\n    $(\"speaking\").textContent = time(Math.ceil(count / 130));\n\n    $(\"shortWords\").textContent = short;\n    $(\"longWords\").textContent = long;\n    $(\"repeated\").textContent = repeated;\n\n    $(\"letters\").textContent = letters;\n    $(\"numbers\").textContent = numbers;\n    $(\"punctuation\").textContent = punctuation;\n    $(\"whitespace\").textContent = whitespace;\n\n    $(\"cursorInfo\").textContent = `${chars.toLocaleString()} characters`;\n\n    analyzeKeywords(clean, frequency);\n    analyzeQuality(text, clean, frequency, sentenceList);\n    analyzeSEO(text, count, avgSentence);\n    analyzeReadability(words, sentences, count);\n    analyzeLimits(count, chars);\n\n    $(\"status\").textContent = text.trim() ? \"Analysis updated\" : \"Ready\";\n  }\n\n  function analyzeKeywords(words, frequency) {\n    const list = [...frequency.entries()]\n      .filter(([word]) => word.length >= 3 && !STOP.has(word))\n      .sort((a, b) => b[1] - a[1])\n      .slice(0, 10);\n\n    const total = words.length;\n    const box = $(\"keywords\");\n    box.innerHTML = \"\";\n\n    if (!list.length) {\n      box.innerHTML = `<div class=\"quality-row\"><span>Start typing to analyze keywords.</span></div>`;\n      return;\n    }\n\n    list.forEach(([word, count]) => {\n      const density = total ? ((count / total) * 100).toFixed(1) : 0;\n      const row = document.createElement(\"div\");\n      row.className = \"keyword\";\n      row.innerHTML = `<b>${escapeHTML(word)}</b><span>${count}×</span><em>${density}%</em>`;\n      box.appendChild(row);\n    });\n  }\n\n  function analyzeQuality(text, words, frequency, sentences) {\n    const longSentences = sentences.filter(s => getWords(s).length > 30).length;\n    const repeated = [...frequency.values()].filter(v => v >= 3).length;\n    const punctuation = (text.match(/[!?]{2,}|\\.{3,}/g) || []).length;\n    const filler = words.filter(w => FILLERS.has(w)).length;\n    const passive = words.filter(w => /^(is|are|was|were|be|been|being)$/.test(w)).length;\n\n    setBadge($(\"longSentences\"), longSentences, \"Long sentences\");\n    setBadge($(\"repeatCheck\"), repeated, \"Repeated words\");\n    setBadge($(\"punctCheck\"), punctuation, \"Punctuation\");\n    setBadge($(\"fillerCheck\"), filler, \"Filler words\");\n    setBadge($(\"passiveCheck\"), passive, \"Possible passive\");\n  }\n\n  function setBadge(el, value, label) {\n    if (!value) {\n      el.textContent = \"Good\";\n      el.className = \"badge good\";\n      return;\n    }\n    el.textContent = `${value} ${label}`;\n    el.className = value >= 3 ? \"badge bad\" : \"badge warn\";\n  }\n\n  function analyzeSEO(text, count, avgSentence) {\n    $(\"seoWords\").textContent = count;\n    const words = getWords(text).map(w => w.toLowerCase());\n    const freq = {};\n    words.forEach(w => { freq[w] = (freq[w] || 0) + 1; });\n\n    const top = Object.entries(freq).filter(([w]) => w.length > 3 && !STOP.has(w)).sort((a, b) => b[1] - a[1])[0];\n    const density = top && count ? ((top[1] / count) * 100).toFixed(1) : 0;\n\n    $(\"density\").textContent = `${density}%`;\n    $(\"headingCount\").textContent = (text.match(/^#{1,6}\\s.+$/gm) || []).length;\n    $(\"linkCount\").textContent = (text.match(/https?:\\/\\/\\S+/gi) || []).length;\n    $(\"imageCount\").textContent = (text.match(/!\\[[^\\]]*\\]\\([^)]+\\)/g) || []).length;\n    $(\"seoSentence\").textContent = `${avgSentence.toFixed(1)} words`;\n  }\n\n  function analyzeReadability(words, sentences, count) {\n    if (!count) {\n      $(\"readingLevel\").textContent = \"—\";\n      $(\"grade\").textContent = \"—\";\n      $(\"readAvgSentence\").textContent = \"0\";\n      $(\"complexWords\").textContent = \"0\";\n      return;\n    }\n\n    const totalSyllables = words.reduce((sum, w) => sum + syllables(w), 0);\n    const complex = words.filter(w => syllables(w) >= 3).length;\n    const grade = 0.39 * (count / Math.max(sentences, 1)) + 11.8 * (totalSyllables / count) - 15.59;\n    const score = 206.835 - 1.015 * (count / Math.max(sentences, 1)) - 84.6 * (totalSyllables / count);\n\n    let level;\n    if (score >= 90) level = \"Very Easy\";\n    else if (score >= 80) level = \"Easy\";\n    else if (score >= 70) level = \"Fairly Easy\";\n    else if (score >= 60) level = \"Standard\";\n    else if (score >= 50) level = \"Fairly Difficult\";\n    else if (score >= 30) level = \"Difficult\";\n    else level = \"Very Difficult\";\n\n    $(\"readingLevel\").textContent = level;\n    $(\"grade\").textContent = Math.max(0, grade).toFixed(1);\n    $(\"readAvgSentence\").textContent = (count / Math.max(sentences, 1)).toFixed(1);\n    $(\"complexWords\").textContent = complex;\n  }\n\n  function analyzeLimits(words, chars) {\n    const wl = parseInt($(\"wordLimit\").value) || 0;\n    const cl = parseInt($(\"charLimit\").value) || 0;\n    const wp = wl ? Math.min(words / wl * 100, 100) : 0;\n    const cp = cl ? Math.min(chars / cl * 100, 100) : 0;\n\n    $(\"wordProgress\").style.width = wp + \"%\";\n    $(\"charProgress\").style.width = cp + \"%\";\n    $(\"wordProgress\").style.background = wp >= 100 ? \"#ef4444\" : \"var(--primary)\";\n    $(\"charProgress\").style.background = cp >= 100 ? \"#ef4444\" : \"var(--primary)\";\n  }\n\n  function escapeHTML(str) {\n    return str.replace(/[&<>\"']/g, m => ({\n      \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#039;\"\n    }[m]));\n  }\n\n  function transform(action) {\n    let text = input.value;\n    const lines = text.split(/\\r?\\n/);\n\n    switch(action) {\n      case \"upper\": text = text.toUpperCase(); break;\n      case \"lower\": text = text.toLowerCase(); break;\n      case \"capitalize\":\n        text = text.replace(/(^|[.!?]\\s+)([a-z])/gi, (_, a, b) => a + b.toUpperCase());\n        break;\n      case \"title\":\n        text = text.toLowerCase().replace(/\\b[\\p{L}\\p{N}]/gu, c => c.toUpperCase());\n        break;\n      case \"alternating\":\n        let i = 0;\n        text = [...text].map(c => {\n          if (/[a-z]/i.test(c)) return (i++ % 2 === 0) ? c.toLowerCase() : c.toUpperCase();\n          return c;\n        }).join(\"\");\n        break;\n      case \"reverse\": text = [...text].reverse().join(\"\"); break;\n      case \"reverseWords\": text = lines.map(line => line.trim().split(/\\s+/).reverse().join(\" \")).join(\"\\n\"); break;\n      case \"trim\": text = lines.map(x => x.trim()).join(\"\\n\").trim(); break;\n      case \"extra\": text = text.replace(/[ \\t]+/g, \" \"); break;\n      case \"breaks\": text = text.replace(/\\s*\\r?\\n\\s*/g, \" \"); break;\n      case \"blank\": text = lines.filter(x => x.trim()).join(\"\\n\"); break;\n      case \"duplicate\": text = [...new Set(lines)].join(\"\\n\"); break;\n      case \"sortAZ\": text = [...lines].sort((a, b) => a.localeCompare(b)).join(\"\\n\"); break;\n      case \"sortZA\": text = [...lines].sort((a, b) => b.localeCompare(a)).join(\"\\n\"); break;\n    }\n\n    saveHistory();\n    input.value = text;\n    analyze();\n    input.focus();\n  }\n\n  function saveHistory() {\n    if (changing) return;\n    const value = input.value;\n    if (history[historyIndex] === value) return;\n    history = history.slice(0, historyIndex + 1);\n    history.push(value);\n    historyIndex = history.length - 1;\n    if (history.length > 50) history.shift();\n  }\n\n  function undo() {\n    if (historyIndex <= 0) return;\n    changing = true;\n    historyIndex--;\n    input.value = history[historyIndex];\n    changing = false;\n    analyze();\n  }\n\n  function redo() {\n    if (historyIndex >= history.length - 1) return;\n    changing = true;\n    historyIndex++;\n    input.value = history[historyIndex];\n    changing = false;\n    analyze();\n  }\n\n  // Event Listeners\n  document.querySelectorAll(\"[data-action]\").forEach(btn => {\n    btn.addEventListener(\"click\", () => transform(btn.dataset.action));\n  });\n\n  input.addEventListener(\"input\", () => {\n    saveHistory();\n    analyze();\n  });\n\n  $(\"analyzeBtn\").onclick = analyze;\n\n  $(\"clearBtn\").onclick = () => {\n    saveHistory();\n    input.value = \"\";\n    analyze();\n    input.focus();\n  };\n\n  $(\"undoBtn\").onclick = undo;\n  $(\"redoBtn\").onclick = redo;\n\n  $(\"copyBtn\").onclick = async () => {\n    if (!input.value) return;\n    try {\n      await navigator.clipboard.writeText(input.value);\n      $(\"status\").textContent = \"Copied to clipboard!\";\n      setTimeout(analyze, 1200);\n    } catch {\n      $(\"status\").textContent = \"Copy failed\";\n    }\n  };\n\n  $(\"importBtn\").onclick = () => fileInput.click();\n\n  fileInput.onchange = () => {\n    const file = fileInput.files[0];\n    if (!file) return;\n    const reader = new FileReader();\n    reader.onload = e => {\n      saveHistory();\n      input.value = e.target.result || \"\";\n      analyze();\n    };\n    reader.readAsText(file);\n  };\n\n  $(\"replaceBtn\").onclick = () => {\n    const find = $(\"findInput\").value;\n    if (!find) return;\n    const replacement = $(\"replaceInput\").value;\n    saveHistory();\n    input.value = input.value.split(find).join(replacement);\n    analyze();\n  };\n\n  $(\"wordLimit\").oninput = analyze;\n  $(\"charLimit\").oninput = analyze;\n\n  input.addEventListener(\"keydown\", e => {\n    if ((e.ctrlKey || e.metaKey) && e.key === \"Enter\") {\n      e.preventDefault();\n      analyze();\n    }\n    if ((e.ctrlKey || e.metaKey) && e.key === \"z\") {\n      e.preventDefault();\n      undo();\n    }\n    if ((e.ctrlKey || e.metaKey) && e.key === \"y\") {\n      e.preventDefault();\n      redo();\n    }\n  });\n\n  analyze();\n})();",
        "cdnScripts": ""
      },
      "faqs": [
        {
          "q": "How to check word count and keyword density online for free?",
          "a": "To check word count and keyword density online for free, paste your text into our editor. The in-browser engine instantly calculates total words, characters, and generates a live keyword density breakdown table in milliseconds."
        },
        {
          "q": "What is keyword density and why is it important for SEO?",
          "a": "Keyword density is the percentage of times a specific keyword appears relative to the total word count of an article. Maintaining a natural density (typically between 1% and 2.5%) helps search engines understand your topic without triggering Google over-optimization penalties."
        },
        {
          "q": "Does this tool calculate reading time and speaking duration?",
          "a": "Yes. The tool automatically estimates reading time based on the standard average reading speed (200 words per minute) and speaking duration for presentations (130 words per minute)."
        },
        {
          "q": "Does the character counter include or exclude spaces?",
          "a": "Our tool provides both metrics simultaneously: total characters including spaces (important for meta tags and essays) and total characters excluding spaces."
        },
        {
          "q": "Is my written content or draft private and secure here?",
          "a": "Yes. All text parsing and keyword analytics run 100% client-side in your web browser. Your content is never saved, uploaded, or tracked on any remote server."
        }
      ]
    },
    {
      "id": "password-generator-0112",
      "name": "Advanced Password Generator & Passphrase Creator",
      "category": "Security",
      "slug": "password-generator",
      "badge": "Popular",
      "iconSvg": "<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n  <defs>\n    <linearGradient id=\"passGreenGrad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"100%\">\n      <stop offset=\"0%\" stop-color=\"#10b981\" />\n      <stop offset=\"100%\" stop-color=\"#34d399\" />\n    </linearGradient>\n  </defs>\n  <rect width=\"24\" height=\"24\" rx=\"5\" fill=\"url(#passGreenGrad)\"/>\n  <path d=\"M12 4L5 7V12C5 16.5 8 19.8 12 21C16 19.8 19 16.5 19 12V7L12 4Z\" stroke=\"#FFFFFF\" stroke-width=\"1.6\" stroke-linejoin=\"round\"/>\n  <rect x=\"9.5\" y=\"11\" width=\"5\" height=\"4\" rx=\"1\" fill=\"#FFFFFF\"/>\n  <path d=\"M10.5 11V9.5C10.5 8.67 11.17 8 12 8C12.83 8 13.5 8.67 13.5 9.5V11\" stroke=\"#FFFFFF\" stroke-width=\"1.2\"/>\n</svg>",
      "active": true,
      "usageCount": 37,
      "seo": {
        "targetKeyword": "advanced password generator & passphrase creator online",
        "metaTitle": "Advanced Password Generator & Passphrase Creator Online Free",
        "metaDescription": "Generate strong, random 16+ character passwords and secure passphrases online for free. Custom symbols, numbers, and entropy metrics with 100% private in-browser tool.",
        "canonical": "https://tools.heyswan.cloud/tool/password-generator",
        "ogImage": "",
        "indexable": true
      },
      "content": {
        "about": "Our Free Online Advanced Password Generator & Passphrase Creator helps individuals, developers, and system administrators generate cryptographically secure credentials in milliseconds. Whether you need an unbreakable 16-character random password with complex symbols for bank accounts or a multi-word memorable passphrase for master vault encryption, our tool gives you complete security customization.\n\nPowered by the native Web Cryptography API (`crypto.getRandomValues`), every password and passphrase is generated 100% locally in your device's browser memory. Your credentials are never transmitted over the internet, logged in any database, or stored on remote servers, guaranteeing total privacy, zero data leaks, and military-grade entropy.",
        "howTo": "1. Select your mode (Random Character Password or Memorable Passphrase).\n2. Choose your length (e.g. 16, 24, or 32 characters) and toggle uppercase, lowercase, numbers, or symbols.\n3. Check the real-time entropy score and crack-time strength meter.\n4. Click 'Copy Password' to transfer your secure credential to your clipboard safely."
      },
      "engine": {
        "html": "<div class=\"pwd-app-wrap\">\n\n  <!-- 1. MODE SELECTOR PILLS -->\n  <div class=\"pwd-modes-bar\">\n    <button type=\"button\" id=\"passwordMode\" class=\"pwd-mode-btn active\">\n      <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path></svg>\n      Password Mode\n    </button>\n    <button type=\"button\" id=\"passphraseMode\" class=\"pwd-mode-btn\">\n      <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M4 19.5A2.5 2.5 0 0 1 6.5 17H20\"></path><path d=\"M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z\"></path></svg>\n      Passphrase Mode\n    </button>\n  </div>\n\n  <!-- 2. OUTPUT DISPLAY BOX -->\n  <div class=\"pwd-output-card mb-3\">\n    <div class=\"pwd-field-wrap\">\n      <input id=\"password\" class=\"pwd-input\" readonly placeholder=\"Generating password...\" aria-label=\"Generated Password\">\n      <div class=\"pwd-field-actions\">\n        <button type=\"button\" class=\"pwd-action-icon\" id=\"copy\" title=\"Copy to clipboard\">\n          <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path></svg>\n        </button>\n        <button type=\"button\" class=\"pwd-action-icon\" id=\"refresh\" title=\"Regenerate Password\">\n          <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2\"/></svg>\n        </button>\n      </div>\n    </div>\n\n    <!-- Strength Meter -->\n    <div class=\"pwd-strength-wrap\">\n      <div class=\"d-flex justify-content-between align-items-center mb-1 small\">\n        <span class=\"text-muted fw-semibold\">Strength: <strong id=\"strengthText\" class=\"text-success\">Strong</strong></span>\n        <span class=\"text-muted fw-semibold\">Entropy: <strong id=\"entropy\" class=\"text-primary\">0 bits</strong></span>\n      </div>\n      <div class=\"pwd-meter-track\">\n        <div class=\"pwd-meter-fill\" id=\"bar\"></div>\n      </div>\n    </div>\n  </div>\n\n  <!-- 3. PASSWORD SETTINGS (TAB 1) -->\n  <div id=\"passwordSettings\">\n    <div class=\"pwd-grid-boxes mb-3\">\n      \n      <!-- Length Box -->\n      <div class=\"pwd-box\">\n        <div class=\"pwd-box-head\">\n          <span>Password Length</span>\n          <span class=\"pwd-pill-badge\" id=\"lengthValue\">20</span>\n        </div>\n        <input id=\"length\" type=\"range\" class=\"pwd-slider\" min=\"4\" max=\"128\" value=\"20\">\n        <div class=\"d-flex justify-content-between text-muted\" style=\"font-size: 0.72rem; margin-top: 4px;\">\n          <span>4 chars</span>\n          <span>128 chars</span>\n        </div>\n      </div>\n\n      <!-- Character Types -->\n      <div class=\"pwd-box\">\n        <div class=\"pwd-box-head\">Character Types</div>\n        <div class=\"pwd-checks-grid\">\n          <label class=\"pwd-check-item\">\n            <input id=\"upper\" type=\"checkbox\" checked>\n            <span>Uppercase (A-Z)</span>\n          </label>\n          <label class=\"pwd-check-item\">\n            <input id=\"lower\" type=\"checkbox\" checked>\n            <span>Lowercase (a-z)</span>\n          </label>\n          <label class=\"pwd-check-item\">\n            <input id=\"number\" type=\"checkbox\" checked>\n            <span>Numbers (0-9)</span>\n          </label>\n          <label class=\"pwd-check-item\">\n            <input id=\"symbol\" type=\"checkbox\" checked>\n            <span>Symbols (!@#$)</span>\n          </label>\n        </div>\n      </div>\n\n      <!-- Advanced Rules -->\n      <div class=\"pwd-box\">\n        <div class=\"pwd-box-head\">Advanced Cryptography Rules</div>\n        <div class=\"pwd-checks-grid\">\n          <label class=\"pwd-check-item\">\n            <input id=\"similar\" type=\"checkbox\">\n            <span>Exclude Similar (i, l, 1, o, 0)</span>\n          </label>\n          <label class=\"pwd-check-item\">\n            <input id=\"duplicate\" type=\"checkbox\">\n            <span>No Duplicate Chars</span>\n          </label>\n          <label class=\"pwd-check-item\">\n            <input id=\"startLetter\" type=\"checkbox\">\n            <span>Start with Letter</span>\n          </label>\n          <label class=\"pwd-check-item\">\n            <input id=\"pronounce\" type=\"checkbox\">\n            <span>Pronounceable</span>\n          </label>\n        </div>\n      </div>\n\n      <!-- Custom Symbols Box -->\n      <div class=\"pwd-box\">\n        <div class=\"pwd-box-head\">Custom Symbols Pool</div>\n        <input id=\"custom\" class=\"pwd-custom-input\" value=\"!@#$%^&*()-_=+[]{};:,.?\">\n      </div>\n\n    </div>\n  </div>\n\n  <!-- 4. PASSPHRASE SETTINGS (TAB 2) -->\n  <div id=\"passSettings\" style=\"display: none;\">\n    <div class=\"pwd-grid-boxes mb-3\">\n      \n      <!-- Word Count Box -->\n      <div class=\"pwd-box\">\n        <div class=\"pwd-box-head\">\n          <span>Number of Words</span>\n          <span class=\"pwd-pill-badge\" id=\"wordsValue\">5</span>\n        </div>\n        <input id=\"words\" type=\"range\" class=\"pwd-slider\" min=\"3\" max=\"12\" value=\"5\">\n        <div class=\"d-flex justify-content-between text-muted\" style=\"font-size: 0.72rem; margin-top: 4px;\">\n          <span>3 words</span>\n          <span>12 words</span>\n        </div>\n      </div>\n\n      <!-- Passphrase Options -->\n      <div class=\"pwd-box\">\n        <div class=\"pwd-box-head\">Passphrase Format</div>\n        <div class=\"pwd-checks-grid\">\n          <label class=\"pwd-check-item\">\n            <input id=\"capitalize\" type=\"checkbox\" checked>\n            <span>Capitalize Words</span>\n          </label>\n          <label class=\"pwd-check-item\">\n            <input id=\"passNumber\" type=\"checkbox\" checked>\n            <span>Append Number</span>\n          </label>\n          <label class=\"pwd-check-item\">\n            <input id=\"passSymbol\" type=\"checkbox\" checked>\n            <span>Append Symbol</span>\n          </label>\n        </div>\n      </div>\n\n    </div>\n  </div>\n\n  <!-- 5. MAIN ACTION BUTTONS ROW -->\n  <div class=\"pwd-actions-bar mb-3\">\n    <button type=\"button\" class=\"btn-pwd-primary\" id=\"generate\">\n      <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2\"/></svg>\n      Generate Password\n    </button>\n    <button type=\"button\" class=\"btn-pwd-secondary\" id=\"bulk\">\n      <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M13 2L3 14h9l-1 8 10-12h-9l1-8z\"></path></svg>\n      Bulk Passwords\n    </button>\n    <button type=\"button\" class=\"btn-pwd-secondary\" id=\"download\">\n      <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 3v12m0 0 5-5m-5 5-5-5M4 21h16\"></path></svg>\n      Download .txt\n    </button>\n  </div>\n\n  <!-- 6. STATS METRICS STRIP -->\n  <div class=\"pwd-stats-strip mb-3\">\n    <div class=\"pwd-stat-card\">\n      <small>Length</small>\n      <strong id=\"statLength\">20</strong>\n    </div>\n    <div class=\"pwd-stat-card\">\n      <small>Entropy</small>\n      <strong id=\"statEntropy\" class=\"text-primary\">0 bits</strong>\n    </div>\n    <div class=\"pwd-stat-card\">\n      <small>Char Pool</small>\n      <strong id=\"statPool\" class=\"text-success\">94</strong>\n    </div>\n  </div>\n\n  <!-- 7. BULK PASSWORDS DRAWER -->\n  <div id=\"bulkBox\" class=\"pwd-bulk-drawer\" style=\"display: none;\">\n    <div class=\"d-flex align-items-center justify-content-between mb-2\">\n      <div class=\"d-flex align-items-center gap-2\">\n        <span class=\"small fw-bold text-muted\">Generate Count:</span>\n        <input id=\"bulkCount\" type=\"number\" class=\"pwd-bulk-count\" min=\"1\" max=\"100\" value=\"10\">\n      </div>\n      <button type=\"button\" class=\"btn-pwd-primary py-1 px-3\" style=\"height: 32px; font-size: 0.8rem;\" id=\"makeBulk\">Generate</button>\n    </div>\n    <textarea id=\"bulkOutput\" class=\"pwd-bulk-output\" readonly placeholder=\"Generated bulk passwords will appear here...\"></textarea>\n  </div>\n\n</div>\n\n<!-- Minimal Notification Toast -->\n<div id=\"toast\" class=\"pwd-toast\"></div>",
        "css": "/* ===================================================\n   PASSWORD GENERATOR CSS (FULL MOBILE WIDTH & CLEAN GAPS)\n   =================================================== */\n\n/* 1. CONTAINER: FULL WIDTH ON MOBILE, 860px ON DESKTOP */\n.pwd-app-wrap {\n  width: 100%;\n  max-width: 860px;\n  margin: 0 auto;\n  padding: 0 2px;\n  box-sizing: border-box;\n}\n\n/* 2. MODE SELECTOR BAR (CLEAN SEPARATION) */\n.pwd-modes-bar {\n  display: flex;\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: var(--radius);\n  padding: 4px;\n  gap: 6px;\n  margin-bottom: 14px; /* Proper bottom gap */\n  width: 100%;\n}\n\n.pwd-mode-btn {\n  flex: 1;\n  padding: 9px 14px;\n  background: transparent;\n  border: none;\n  border-radius: 9px;\n  color: var(--text-muted);\n  font-weight: 700;\n  font-size: 0.86rem;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 6px;\n  cursor: pointer;\n  transition: all 0.2s ease;\n}\n.pwd-mode-btn:hover { color: var(--text); }\n.pwd-mode-btn.active {\n  background: var(--primary);\n  color: #ffffff;\n  box-shadow: 0 2px 8px var(--primary-glow);\n}\n\n/* 3. MAIN PASSWORD OUTPUT BOX (FULL WIDTH ON MOBILE) */\n.pwd-output-card {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: var(--radius);\n  padding: 16px;\n  margin-bottom: 14px; /* Clear separation from next box */\n  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.12);\n  width: 100%;\n  box-sizing: border-box;\n}\n\n.pwd-field-wrap {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 10px;\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  border-radius: 10px;\n  padding: 6px 8px 6px 14px;\n  width: 100%;\n  box-sizing: border-box;\n}\n\n.pwd-input {\n  flex-grow: 1;\n  background: transparent;\n  border: none;\n  font-family: 'SFMono-Regular', Consolas, Menlo, monospace;\n  font-size: 1.1rem; /* Normal & clear readable size */\n  font-weight: 700;\n  color: var(--text);\n  outline: none;\n  letter-spacing: 0.04em;\n  text-overflow: ellipsis;\n  min-width: 0;\n  width: 100%;\n}\n\n.pwd-field-actions {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  flex-shrink: 0;\n}\n\n.pwd-action-icon {\n  width: 38px;\n  height: 38px;\n  border: 1px solid var(--border);\n  background: var(--surface);\n  color: var(--text);\n  border-radius: 8px;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  cursor: pointer;\n  transition: all 0.15s ease;\n  flex-shrink: 0;\n}\n.pwd-action-icon:hover {\n  border-color: var(--primary);\n  color: var(--primary);\n  transform: translateY(-1px);\n}\n\n/* Strength Meter */\n.pwd-strength-wrap {\n  margin-top: 14px;\n  padding-top: 10px;\n  border-top: 1px solid var(--border);\n}\n.pwd-meter-track {\n  width: 100%;\n  height: 6px;\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  border-radius: 10px;\n  overflow: hidden;\n}\n.pwd-meter-fill {\n  height: 100%;\n  width: 0%;\n  border-radius: 10px;\n  transition: width 0.3s ease, background-color 0.3s ease;\n}\n\n/* 4. SETTINGS GRID BOXES (PROPER GAPS, NO COLLIDING) */\n.pwd-grid-boxes {\n  display: grid;\n  grid-template-columns: 1fr;\n  gap: 12px; /* Distinct gap between boxes */\n  margin-bottom: 14px;\n  width: 100%;\n}\n@media (min-width: 680px) {\n  .pwd-grid-boxes {\n    grid-template-columns: 1fr 1fr;\n    gap: 14px;\n  }\n}\n\n.pwd-box {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: var(--radius);\n  padding: 16px;\n  box-sizing: border-box;\n}\n\n.pwd-box-head {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  font-size: 0.85rem;\n  font-weight: 700;\n  color: var(--text);\n  margin-bottom: 12px;\n}\n\n.pwd-pill-badge {\n  font-size: 0.88rem;\n  font-weight: 800;\n  padding: 2px 10px;\n  border-radius: 6px;\n  background: var(--badge-bg);\n  color: var(--badge-text);\n  font-family: 'SFMono-Regular', Consolas, monospace;\n}\n\n.pwd-slider {\n  width: 100%;\n  accent-color: var(--primary);\n  cursor: pointer;\n  height: 5px;\n}\n\n/* Checkboxes Grid */\n.pwd-checks-grid {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 8px;\n}\n\n.pwd-check-item {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  padding: 8px 10px;\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  border-radius: 8px;\n  cursor: pointer;\n  font-size: 0.8rem;\n  font-weight: 600;\n  color: var(--text);\n  user-select: none;\n  transition: all 0.15s ease;\n}\n.pwd-check-item:hover { border-color: var(--primary); }\n.pwd-check-item input[type=\"checkbox\"] {\n  accent-color: var(--primary);\n  width: 15px;\n  height: 15px;\n}\n\n.pwd-custom-input {\n  width: 100%;\n  height: 38px;\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  color: var(--text);\n  border-radius: 8px;\n  padding: 0 12px;\n  font-family: 'SFMono-Regular', Consolas, monospace;\n  font-size: 0.85rem;\n  outline: none;\n  box-sizing: border-box;\n}\n.pwd-custom-input:focus { border-color: var(--primary); }\n\n/* 5. ACTION BUTTONS ROW (SEPARATED WITH PROPER SPACING) */\n.pwd-actions-bar {\n  display: grid;\n  grid-template-columns: 1fr auto auto;\n  gap: 8px;\n  margin-bottom: 14px;\n  width: 100%;\n}\n\n.btn-pwd-primary {\n  height: 42px;\n  background: var(--primary);\n  border: none;\n  color: #ffffff;\n  padding: 0 20px;\n  border-radius: 9px;\n  font-size: 0.9rem;\n  font-weight: 700;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 6px;\n  cursor: pointer;\n  box-shadow: 0 2px 8px var(--primary-glow);\n  transition: all 0.2s ease;\n  white-space: nowrap;\n}\n.btn-pwd-primary:hover { background: var(--primary-hover); }\n\n.btn-pwd-secondary {\n  height: 42px;\n  background: var(--surface);\n  border: 1px solid var(--border);\n  color: var(--text);\n  padding: 0 16px;\n  border-radius: 9px;\n  font-size: 0.88rem;\n  font-weight: 700;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 6px;\n  cursor: pointer;\n  transition: all 0.15s ease;\n  white-space: nowrap;\n}\n.btn-pwd-secondary:hover {\n  background: var(--surface-hover);\n  border-color: var(--primary);\n  color: var(--primary);\n}\n\n/* 6. STATS STRIP */\n.pwd-stats-strip {\n  display: grid;\n  grid-template-columns: repeat(3, 1fr);\n  gap: 8px;\n  margin-bottom: 14px;\n  width: 100%;\n}\n\n.pwd-stat-card {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 10px;\n  padding: 10px 8px;\n  text-align: center;\n}\n.pwd-stat-card small { display: block; color: var(--text-muted); font-size: 0.75rem; margin-bottom: 3px; font-weight: 600; }\n.pwd-stat-card strong { font-size: 0.98rem; font-family: 'SFMono-Regular', Consolas, monospace; color: var(--text); }\n\n/* 7. BULK DRAWER */\n.pwd-bulk-drawer {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: var(--radius);\n  padding: 16px;\n  margin-top: 14px;\n  width: 100%;\n  box-sizing: border-box;\n}\n.pwd-bulk-count {\n  width: 75px;\n  height: 34px;\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  color: var(--text);\n  border-radius: 6px;\n  padding: 0 8px;\n  font-size: 0.84rem;\n  outline: none;\n}\n.pwd-bulk-output {\n  width: 100%;\n  height: 130px;\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  color: var(--badge-text);\n  font-family: 'SFMono-Regular', Consolas, monospace;\n  font-size: 0.84rem;\n  padding: 12px;\n  border-radius: 8px;\n  outline: none;\n  resize: vertical;\n  box-sizing: border-box;\n}\n\n/* 8. TOAST NOTIFICATION */\n.pwd-toast {\n  position: fixed;\n  left: 50%;\n  bottom: 24px;\n  transform: translate(-50%, 20px);\n  background: var(--surface);\n  border: 1px solid var(--border);\n  color: var(--text);\n  padding: 8px 18px;\n  border-radius: 30px;\n  font-size: 0.84rem;\n  font-weight: 700;\n  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);\n  opacity: 0;\n  pointer-events: none;\n  transition: all 0.2s ease;\n  z-index: 9999;\n}\n.pwd-toast.show {\n  opacity: 1;\n  transform: translate(-50%, 0);\n}\n\n/* ===================================================\n   MOBILE-SPECIFIC SCREEN PERFECTION (< 580px)\n   =================================================== */\n@media (max-width: 580px) {\n  .pwd-app-wrap {\n    padding: 0; /* Full phone screen spread */\n  }\n\n  .pwd-output-card {\n    padding: 12px;\n  }\n\n  .pwd-input {\n    font-size: 1rem; /* Clear & readable */\n  }\n\n  .pwd-actions-bar {\n    grid-template-columns: 1fr; /* Full width stacked buttons */\n    gap: 8px;\n  }\n\n  .btn-pwd-primary, .btn-pwd-secondary {\n    width: 100%;\n    height: 40px;\n  }\n\n  .pwd-checks-grid {\n    grid-template-columns: 1fr; /* 1 Column checkboxes on small screens */\n  }\n}",
        "js": "(function() {\n  \"use strict\";\n\n  const LOWER = \"abcdefghijklmnopqrstuvwxyz\";\n  const UPPER = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n  const NUM = \"0123456789\";\n\n  const WORDS = [\n    \"apple\",\"river\",\"stone\",\"cloud\",\"forest\",\"ocean\",\"silver\",\"planet\",\n    \"coffee\",\"rocket\",\"orange\",\"winter\",\"summer\",\"shadow\",\"bright\",\"green\",\n    \"mountain\",\"dragon\",\"tiger\",\"sunset\",\"garden\",\"pixel\",\"thunder\",\"paper\",\n    \"mirror\",\"falcon\",\"yellow\",\"purple\",\"crystal\",\"signal\",\"matrix\",\"secure\",\n    \"future\",\"digital\",\"browser\",\"secret\",\"energy\",\"travel\",\"simple\",\"strong\",\n    \"privacy\",\"network\",\"system\",\"light\",\"water\",\"fire\",\"earth\",\"moon\",\"star\",\n    \"space\",\"dream\",\"happy\",\"smart\",\"quick\",\"silent\",\"gold\",\"storm\",\"flower\"\n  ];\n\n  const $ = id => document.getElementById(id);\n\n  let mode = \"password\";\n\n  // Cryptographically Secure Random Number Generator (CSPRNG)\n  function rand(max) {\n    const a = new Uint32Array(1);\n    const limit = Math.floor(4294967296 / max) * max;\n    do {\n      window.crypto.getRandomValues(a);\n    } while (a[0] >= limit);\n    return a[0] % max;\n  }\n\n  function pick(str) {\n    return str[rand(str.length)];\n  }\n\n  function unique(str) {\n    return [...new Set(str)].join(\"\");\n  }\n\n  function buildSets() {\n    let l = LOWER;\n    let u = UPPER;\n    let n = NUM;\n    let s = $(\"custom\").value || \"!@#$%^&*\";\n\n    if ($(\"similar\").checked) {\n      l = l.replace(/[oO0ilI1sS5bB8]/g, \"\");\n      u = u.replace(/[oO0ilI1sS5bB8]/g, \"\");\n      n = n.replace(/[oO0ilI1sS5bB8]/g, \"\");\n      s = s.replace(/[oO0ilI1sS5bB8]/g, \"\");\n    }\n\n    const sets = [];\n    if ($(\"lower\").checked) sets.push(l);\n    if ($(\"upper\").checked) sets.push(u);\n    if ($(\"number\").checked) sets.push(n);\n    if ($(\"symbol\").checked) sets.push(s);\n\n    return sets.filter(Boolean);\n  }\n\n  function shuffle(arr) {\n    for (let i = arr.length - 1; i > 0; i--) {\n      const j = rand(i + 1);\n      [arr[i], arr[j]] = [arr[j], arr[i]];\n    }\n    return arr;\n  }\n\n  function makePassword() {\n    const sets = buildSets();\n    if (!sets.length) {\n      toast(\"Select at least one character rule.\");\n      return \"\";\n    }\n\n    let len = +$(\"length\").value;\n    const pool = unique(sets.join(\"\"));\n\n    if ($(\"duplicate\").checked && len > pool.length) {\n      len = pool.length;\n      $(\"length\").value = len;\n      $(\"lengthValue\").textContent = len;\n      toast(\"Length adjusted for unique chars.\");\n    }\n\n    let result = [];\n    sets.forEach(set => {\n      if (set.length) result.push(pick(set));\n    });\n\n    while (result.length < len) {\n      const c = pick(pool);\n      if ($(\"duplicate\").checked && result.includes(c)) continue;\n      result.push(c);\n    }\n\n    result = shuffle(result);\n\n    if ($(\"startLetter\").checked) {\n      const letters = ($(\"lower\").checked ? LOWER : \"\") + ($(\"upper\").checked ? UPPER : \"\");\n      if (letters) result[0] = pick(letters);\n    }\n\n    if ($(\"pronounce\").checked) {\n      result = makePronounceable(len);\n    }\n\n    return Array.isArray(result) ? result.join(\"\") : result;\n  }\n\n  function makePronounceable(len) {\n    const c = \"bcdfghjklmnpqrstvwxyz\";\n    const v = \"aeiou\";\n    let out = \"\";\n    for (let i = 0; i < len; i++) {\n      out += pick(i % 2 ? v : c);\n    }\n    if ($(\"number\").checked && len >= 4) {\n      out = out.slice(0, -1) + pick(NUM);\n    }\n    if ($(\"symbol\").checked && len >= 6) {\n      out = out.slice(0, -1) + pick($(\"custom\").value || \"!@#$\");\n    }\n    return out;\n  }\n\n  function makePassphrase() {\n    const count = +$(\"words\").value;\n    const arr = [];\n    for (let i = 0; i < count; i++) {\n      let word = WORDS[rand(WORDS.length)];\n      if ($(\"capitalize\").checked) {\n        word = word[0].toUpperCase() + word.slice(1);\n      }\n      arr.push(word);\n    }\n    let result = arr.join(\"-\");\n    if ($(\"passNumber\").checked) result += \"-\" + rand(1000);\n    if ($(\"passSymbol\").checked) result += pick($(\"custom\").value || \"!@#$\");\n    return result;\n  }\n\n  function getEntropy(str) {\n    let pool = 0;\n    if (/[a-z]/.test(str)) pool += 26;\n    if (/[A-Z]/.test(str)) pool += 26;\n    if (/[0-9]/.test(str)) pool += 10;\n    if (/[^a-zA-Z0-9]/.test(str)) pool += 32;\n    return pool ? str.length * Math.log2(pool) : 0;\n  }\n\n  function update(str) {\n    const e = getEntropy(str);\n    let label = \"Very Weak\";\n    let color = \"#ef4444\";\n\n    if (e >= 100) { label = \"Very Strong\"; color = \"#10b981\"; }\n    else if (e >= 70) { label = \"Strong\"; color = \"#22c55e\"; }\n    else if (e >= 45) { label = \"Good\"; color = \"#f59e0b\"; }\n    else if (e >= 28) { label = \"Weak\"; color = \"#f97316\"; }\n\n    $(\"strengthText\").textContent = label;\n    $(\"strengthText\").style.color = color;\n    $(\"entropy\").textContent = Math.round(e) + \" bits\";\n\n    $(\"bar\").style.width = Math.min(100, e) + \"%\";\n    $(\"bar\").style.backgroundColor = color;\n\n    $(\"statLength\").textContent = str.length;\n    $(\"statEntropy\").textContent = Math.round(e) + \" bits\";\n    $(\"statPool\").textContent = unique(buildSets().join(\"\")).length || 0;\n  }\n\n  function generate() {\n    const value = mode === \"password\" ? makePassword() : makePassphrase();\n    if (!value) return;\n    $(\"password\").value = value;\n    update(value);\n  }\n\n  async function copy() {\n    const value = $(\"password\").value;\n    if (!value) return;\n    try {\n      await navigator.clipboard.writeText(value);\n      toast(\"Password copied to clipboard! ✅\");\n    } catch {\n      $(\"password\").select();\n      document.execCommand(\"copy\");\n      toast(\"Password copied! ✅\");\n    }\n  }\n\n  function download() {\n    const value = $(\"password\").value;\n    if (!value) {\n      toast(\"Generate a password first.\");\n      return;\n    }\n    const blob = new Blob([value + \"\\n\"], { type: \"text/plain\" });\n    const url = URL.createObjectURL(blob);\n    const a = document.createElement(\"a\");\n    a.href = url;\n    a.download = \"heyswan-secure-password.txt\";\n    a.click();\n    URL.revokeObjectURL(url);\n    toast(\"Downloaded password.txt ✅\");\n  }\n\n  function makeBulk() {\n    const count = Math.max(1, Math.min(100, +$(\"bulkCount\").value || 10));\n    const result = [];\n    for (let i = 0; i < count; i++) {\n      result.push(mode === \"password\" ? makePassword() : makePassphrase());\n    }\n    $(\"bulkOutput\").value = result.join(\"\\n\");\n  }\n\n  let toastTimer;\n  function toast(msg) {\n    const t = $(\"toast\");\n    if(!t) return;\n    t.textContent = msg;\n    t.classList.add(\"show\");\n    clearTimeout(toastTimer);\n    toastTimer = setTimeout(() => t.classList.remove(\"show\"), 1800);\n  }\n\n  // Event Listeners\n  $(\"passwordMode\").onclick = () => {\n    mode = \"password\";\n    $(\"passwordMode\").classList.add(\"active\");\n    $(\"passphraseMode\").classList.remove(\"active\");\n    $(\"passwordSettings\").style.display = \"block\";\n    $(\"passSettings\").style.display = \"none\";\n    generate();\n  };\n\n  $(\"passphraseMode\").onclick = () => {\n    mode = \"passphrase\";\n    $(\"passphraseMode\").classList.add(\"active\");\n    $(\"passwordMode\").classList.remove(\"active\");\n    $(\"passwordSettings\").style.display = \"none\";\n    $(\"passSettings\").style.display = \"block\";\n    generate();\n  };\n\n  $(\"generate\").onclick = generate;\n  $(\"refresh\").onclick = generate;\n  $(\"copy\").onclick = copy;\n  $(\"download\").onclick = download;\n  $(\"makeBulk\").onclick = makeBulk;\n\n  $(\"bulk\").onclick = () => {\n    const box = $(\"bulkBox\");\n    box.style.display = box.style.display === \"block\" ? \"none\" : \"block\";\n    if (box.style.display === \"block\") makeBulk();\n  };\n\n  $(\"length\").oninput = () => {\n    $(\"lengthValue\").textContent = $(\"length\").value;\n    generate();\n  };\n\n  $(\"words\").oninput = () => {\n    $(\"wordsValue\").textContent = $(\"words\").value;\n    if (mode === \"passphrase\") generate();\n  };\n\n  [\n    \"upper\",\"lower\",\"number\",\"symbol\",\n    \"similar\",\"duplicate\",\"startLetter\",\n    \"pronounce\",\"custom\",\"capitalize\",\n    \"passNumber\",\"passSymbol\"\n  ].forEach(id => {\n    const el = $(id);\n    if(el) el.addEventListener(id === \"custom\" ? \"input\" : \"change\", generate);\n  });\n\n  // Initial Run\n  generate();\n})();",
        "cdnScripts": ""
      },
      "faqs": [
        {
          "q": "How to generate a strong 16-character password for free?",
          "a": "To generate a strong 16-character password for free, open our tool, set the length slider to 16 (or higher), ensure symbols and numbers are checked, and click Copy. The browser's cryptographic engine creates a high-entropy password instantly."
        },
        {
          "q": "What is the difference between a password and a passphrase?",
          "a": "A password is a random string of mixed characters, numbers, and symbols (e.g., k9#P!x2$L9), while a passphrase consists of multiple random dictionary words chained together (e.g., correct-horse-battery-staple), making it easier to remember while maintaining high security."
        },
        {
          "q": "Are passwords generated here stored on your servers?",
          "a": "No. All password and passphrase generation executes 100% client-side using the browser's native Web Crypto API. We never log, save, or transmit your credentials across the network."
        },
        {
          "q": "What makes a password unbreakable and secure?",
          "a": "A secure password should be at least 16 characters long, contain a mix of uppercase letters, lowercase letters, numbers, and special symbols, and have high cryptographic entropy to resist brute-force dictionary attacks."
        },
        {
          "q": "Can I customize which special characters or symbols to exclude?",
          "a": "Yes. You can toggle specific character sets (numbers, uppercase, lowercase, symbols) and exclude ambiguous characters (like 1, l, 0, O) to prevent typing confusion on mobile keyboards."
        }
      ]
    },
    {
      "id": "image-compressor-8153",
      "name": "Advanced Image Compressor & Converter (Target KB)",
      "category": "Image",
      "slug": "image-compressor",
      "badge": "Fast",
      "iconSvg": "<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n  <defs>\n    <linearGradient id=\"imgOrangeGrad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"100%\">\n      <stop offset=\"0%\" stop-color=\"#f59e0b\" />\n      <stop offset=\"100%\" stop-color=\"#f43f5e\" />\n    </linearGradient>\n  </defs>\n  <rect width=\"24\" height=\"24\" rx=\"5\" fill=\"url(#imgOrangeGrad)\"/>\n  <path d=\"M4 8V5C4 4.44772 4.44772 4 5 4H8\" stroke=\"#FFFFFF\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>\n  <path d=\"M16 4H19C19.5523 4 20 4.44772 20 5V8\" stroke=\"#FFFFFF\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>\n  <path d=\"M20 16V19C20 19.5523 19.5523 20 19 20H16\" stroke=\"#FFFFFF\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>\n  <path d=\"M8 20H5C4.44772 20 4 19.5523 4 19V16\" stroke=\"#FFFFFF\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>\n  <circle cx=\"9\" cy=\"9\" r=\"1.5\" fill=\"#FFFFFF\"/>\n  <path d=\"M7 16L10.5 12.5L13.5 15.5L15 14L17 16\" stroke=\"#FFFFFF\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>",
      "active": true,
      "usageCount": 23,
      "seo": {
        "targetKeyword": "image compressor to 100kb",
        "metaTitle": "Image Compressor to 100KB, 50KB, 20KB - Free Online Tool",
        "metaDescription": "Compress images to 20KB, 50KB, 100KB, 200KB, or 1MB online for free. Reduce JPG, PNG, and WebP file sizes without losing quality with 100% private in-browser tool.",
        "canonical": "https://tools.heyswan.cloud/tool/image-compressor",
        "ogImage": "",
        "indexable": true
      },
      "content": {
        "about": "Our Free Online Image Compressor allows you to compress images to 20KB, 50KB, 100KB, 200KB, and 1MB without losing visual clarity or sharpness. Whether you need to resize images for online job portals, admission forms, government passport uploads, or to optimize website loading speeds, our tool gives you full control over target file sizes.\n\nPowered by native browser Canvas and WebAssembly engines, it supports JPG, JPEG, PNG, and WebP formats directly inside your browser memory. Your personal photos, signatures, and ID cards are never uploaded to any remote server or stored in any database, ensuring 100% privacy, zero watermarks, and unlimited high-speed downloads.",
        "howTo": "1. Upload or drag and drop your JPG, PNG, or WebP image into the compressor box.\n2. Select your target file size (e.g. 20KB, 50KB, 100KB, 200KB, 1MB) or adjust the quality slider.\n3. Preview the compressed image with real-time file size comparison and space saved metrics.\n4. Click 'Download Compressed Image' to save your lightweight, high-quality image instantly."
      },
      "engine": {
        "html": "<div class=\"squoosh-accent-wrap\">\n\n  <!-- =========================================\n       SECTION 1: UPLOAD MEDIA\n       ========================================= -->\n  <div class=\"squoosh-section\">\n    <div class=\"section-title-tag\">\n      <span>Upload Media</span>\n    </div>\n    <span class=\"section-sub-tag\">Drop your image or use the buttons below</span>\n\n    <!-- Dropzone -->\n    <div class=\"squoosh-dropzone\" id=\"dropZone\" onclick=\"document.getElementById('fileInput').click()\">\n      <div class=\"squoosh-drop-icon\">\n        <svg width=\"30\" height=\"30\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path><polyline points=\"17 8 12 3 7 8\"></polyline><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"></line></svg>\n      </div>\n      <div class=\"squoosh-drop-title\">Drop your image here</div>\n      <div class=\"squoosh-drop-sub\">JPG, PNG, WebP &bull; Supports Multiple Files</div>\n    </div>\n\n    <!-- 2-Column Grid: Paste on Left, Browse on Right -->\n    <div class=\"comp-btn-grid-2\">\n      <button type=\"button\" class=\"squoosh-btn-half\" onclick=\"pasteFromClipboard()\">\n        <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path></svg>\n        <span>Paste Image</span>\n      </button>\n\n      <button type=\"button\" class=\"squoosh-btn-half\" onclick=\"document.getElementById('fileInput').click()\">\n        <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"></path></svg>\n        <span>Browse Files</span>\n      </button>\n    </div>\n\n    <input type=\"file\" id=\"fileInput\" multiple accept=\"image/jpeg,image/png,image/webp,image/avif\" style=\"display:none\" onchange=\"handleFiles(this.files)\">\n  </div>\n\n  <!-- =========================================\n       SECTION 2: LIVE COMPARISON PREVIEW\n       ========================================= -->\n  <div class=\"squoosh-section\">\n    <div class=\"d-flex justify-content-between align-items-center\">\n      <div class=\"section-title-tag\">\n        <span>Live Comparison Preview</span>\n      </div>\n      <span id=\"previewSavedBadge\" class=\"squoosh-saved-badge d-none\">-0% Saved</span>\n    </div>\n    <span class=\"section-sub-tag\">Inspect side-by-side sharpness before downloading</span>\n\n    <div class=\"squoosh-viewport\" id=\"previewArea\">\n      <div class=\"squoosh-empty-state\">\n        <svg width=\"34\" height=\"34\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"></rect><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"></circle><polyline points=\"21 15 16 10 5 21\"></polyline></svg>\n        <span class=\"mt-2\">Upload an image to see live comparison</span>\n      </div>\n    </div>\n  </div>\n\n  <!-- =========================================\n       SECTION 3: SETTINGS (ALL BLOCKS UNDERLINED)\n       ========================================= -->\n  <div class=\"squoosh-section\">\n    \n    <!-- Sub-Block 1: Target File Size (Underlined) -->\n    <div class=\"squoosh-sub-block\">\n      <div class=\"d-flex justify-content-between align-items-center mb-1\">\n        <div class=\"sub-accent-title\">\n          <span>Target File Size</span>\n        </div>\n        <span class=\"squoosh-sub-label\" id=\"activeTargetLabel\">Auto (75%)</span>\n      </div>\n      <span class=\"section-sub-tag\">Pick exact KB target for job forms or keep auto</span>\n\n      <div class=\"squoosh-presets-row\">\n        <button type=\"button\" class=\"squoosh-chip active\" data-preset=\"auto\" onclick=\"setPreset('auto', this)\">Auto (75%)</button>\n        <button type=\"button\" class=\"squoosh-chip\" data-preset=\"20\" onclick=\"setPreset(20, this)\">20 KB</button>\n        <button type=\"button\" class=\"squoosh-chip\" data-preset=\"50\" onclick=\"setPreset(50, this)\">50 KB</button>\n        <button type=\"button\" class=\"squoosh-chip\" data-preset=\"100\" onclick=\"setPreset(100, this)\">100 KB</button>\n        <button type=\"button\" class=\"squoosh-chip\" data-preset=\"200\" onclick=\"setPreset(200, this)\">200 KB</button>\n      </div>\n    </div>\n\n    <!-- Sub-Block 2: Compression Quality (Underlined) -->\n    <div class=\"squoosh-sub-block\" id=\"qualitySliderWrap\">\n      <div class=\"d-flex justify-content-between align-items-center mb-1\">\n        <div class=\"sub-accent-title\">\n          <span>Compression Quality</span>\n        </div>\n        <span class=\"squoosh-val-badge\" id=\"qualityVal\">75%</span>\n      </div>\n      <span class=\"section-sub-tag\">Adjust slider to balance file size and visual clarity</span>\n      <input type=\"range\" id=\"qualitySlider\" class=\"squoosh-slider\" min=\"5\" max=\"95\" value=\"75\" step=\"1\" oninput=\"document.getElementById('qualityVal').textContent = this.value + '%'\">\n    </div>\n\n    <!-- Sub-Block 3: Convert Format (Underlined) -->\n    <div class=\"squoosh-sub-block\">\n      <div class=\"sub-accent-title mb-1\">\n        <span>Convert Format</span>\n      </div>\n      <span class=\"section-sub-tag mb-2\">Select output image extension</span>\n      <div class=\"squoosh-format-grid-4\">\n        <button type=\"button\" class=\"squoosh-fmt-chip active\" onclick=\"setFormat(this, 'original')\">Original</button>\n        <button type=\"button\" class=\"squoosh-fmt-chip\" onclick=\"setFormat(this, 'webp')\">WebP</button>\n        <button type=\"button\" class=\"squoosh-fmt-chip\" onclick=\"setFormat(this, 'jpg')\">JPG</button>\n        <button type=\"button\" class=\"squoosh-fmt-chip\" onclick=\"setFormat(this, 'png')\">PNG</button>\n      </div>\n    </div>\n\n    <!-- Sub-Block 4: Resolution Scale (Underlined) -->\n    <div class=\"squoosh-sub-block\" style=\"border-bottom: none; padding-bottom: 0; margin-bottom: 0;\">\n      <div class=\"sub-accent-title mb-1\">\n        <span>Resolution Scale</span>\n      </div>\n      <span class=\"section-sub-tag mb-2\">Resize image dimensions to reduce bulk payload</span>\n      <select id=\"scaleSelect\" class=\"squoosh-select\" onchange=\"updateScale(this.value)\">\n        <option value=\"1\">100% (Original Dimensions)</option>\n        <option value=\"0.75\">75% Resolution</option>\n        <option value=\"0.5\">50% Resolution</option>\n        <option value=\"0.25\">25% Resolution</option>\n      </select>\n    </div>\n\n  </div>\n\n  <!-- =========================================\n       SECTION 4: ACTIONS (CENTERED ICONS & GAP TO STATS)\n       ========================================= -->\n  <div class=\"squoosh-section\" style=\"border-bottom: none; padding-bottom: 0; margin-bottom: 0;\">\n    <div class=\"section-title-tag\">\n      <span>Actions &amp; Export</span>\n    </div>\n    <span class=\"section-sub-tag\">Process your image with 0ms client-side execution</span>\n\n    <!-- Professional Actions Deck -->\n    <div class=\"squoosh-actions-deck\">\n      \n      <!-- Top Row: 2-Grid (Compress + Download) -->\n      <div class=\"comp-btn-grid-top\">\n        <button type=\"button\" class=\"btn-compress-main\" id=\"compressBtn\" onclick=\"compressAll()\">\n          <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M13 2L3 14h9l-1 8 10-12h-9l1-8z\"></path></svg>\n          <span>Compress Image</span>\n        </button>\n\n        <button type=\"button\" class=\"btn-download-main d-none\" id=\"downloadBtn\" onclick=\"downloadResult()\">\n          <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path><polyline points=\"7 10 12 15 17 10\"></polyline><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"></line></svg>\n          <span>Download Optimized</span>\n        </button>\n      </div>\n\n      <!-- Bottom Row: 1 Big Reset Button -->\n      <button type=\"button\" class=\"btn-reset-big\" onclick=\"clearAll()\">\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M3 6h18\"></path><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"></path></svg>\n        <span>Reset All Images</span>\n      </button>\n\n    </div>\n\n    <!-- Bottom Stats Strip (Completely Separated with Generous Gap) -->\n    <div class=\"squoosh-stats-bar\">\n      <div class=\"squoosh-stat-box\">\n        <small>Images</small>\n        <strong id=\"statCount\">0</strong>\n      </div>\n      <div class=\"squoosh-stat-box\">\n        <small>Original</small>\n        <strong id=\"statOrig\">&mdash;</strong>\n      </div>\n      <div class=\"squoosh-stat-box\">\n        <small>Compressed</small>\n        <strong id=\"statComp\">&mdash;</strong>\n      </div>\n      <div class=\"squoosh-stat-box\">\n        <small>Saved</small>\n        <strong id=\"statSaved\" class=\"text-success\">&mdash;</strong>\n      </div>\n    </div>\n  </div>\n\n</div>\n\n<div class=\"squoosh-toast-box\" id=\"toasts\"></div>",
        "css": "/* ===================================================\n   ELEGANT ACCENT-BAR IMAGE COMPRESSOR CSS\n   =================================================== */\n\n.squoosh-accent-wrap {\n  width: 100%;\n  max-width: 920px;\n  margin: 0 auto;\n  box-sizing: border-box;\n}\n\n/* 1. MAIN HEADINGS WITH '|' ACCENT BAR */\n.section-title-tag {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  font-size: 0.88rem;\n  font-weight: 800;\n  text-transform: uppercase;\n  letter-spacing: 0.06em;\n  color: var(--text);\n  margin-bottom: 2px;\n  position: relative;\n  padding-left: 10px;\n}\n.section-title-tag::before {\n  content: '';\n  position: absolute;\n  left: 0;\n  top: 2px;\n  bottom: 2px;\n  width: 3px;\n  background: linear-gradient(to bottom, #3b82f6, #10b981);\n  border-radius: 4px;\n}\n\n/* 2. SUB-LABELS WITH COMPACT '|' ACCENT BAR */\n.sub-accent-title {\n  display: inline-flex;\n  align-items: center;\n  font-size: 0.76rem;\n  font-weight: 800;\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  color: var(--text);\n  position: relative;\n  padding-left: 8px;\n}\n.sub-accent-title::before {\n  content: '';\n  position: absolute;\n  left: 0;\n  top: 2px;\n  bottom: 2px;\n  width: 2.5px;\n  background: var(--primary);\n  border-radius: 3px;\n}\n\n.section-sub-tag {\n  font-size: 0.75rem;\n  color: var(--text-muted);\n  margin-bottom: 12px;\n  display: block;\n}\n\n/* 3. Main Sections Divider */\n.squoosh-section {\n  padding-bottom: 22px;\n  margin-bottom: 22px;\n  border-bottom: 1px solid var(--border);\n}\n\n/* 4. Sub-Block Inner Underlines (After Every Option) */\n.squoosh-sub-block {\n  padding-bottom: 18px;\n  margin-bottom: 18px;\n  border-bottom: 1px solid var(--border);\n}\n\n/* 5. Dropzone */\n.squoosh-dropzone {\n  border: 2px dashed var(--border);\n  border-radius: 10px;\n  background: var(--surface-hover);\n  padding: 24px 16px;\n  text-align: center;\n  cursor: pointer;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  gap: 6px;\n  margin-bottom: 10px;\n  transition: all 0.2s ease;\n}\n.squoosh-dropzone:hover {\n  border-color: var(--primary);\n  background: var(--badge-bg);\n}\n.squoosh-drop-icon { color: var(--primary); }\n.squoosh-drop-title { font-size: 0.92rem; font-weight: 700; color: var(--text); }\n.squoosh-drop-sub { font-size: 0.75rem; color: var(--text-muted); }\n\n/* 6. 2-Column Buttons Grid */\n.comp-btn-grid-2 {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 8px;\n  width: 100%;\n}\n\n.squoosh-btn-half {\n  width: 100%;\n  height: 38px;\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  color: var(--text);\n  border-radius: 7px;\n  font-size: 0.8rem;\n  font-weight: 700;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 6px;\n  cursor: pointer;\n  transition: all 0.15s ease;\n}\n.squoosh-btn-half:hover {\n  border-color: var(--primary);\n  color: var(--primary);\n}\n\n/* 7. Preview Viewport */\n.squoosh-sub-label {\n  font-size: 0.76rem;\n  font-weight: 700;\n  color: var(--badge-text);\n}\n.squoosh-saved-badge {\n  background: rgba(16, 185, 129, 0.15);\n  color: var(--accent);\n  padding: 3px 10px;\n  border-radius: 20px;\n  font-size: 0.76rem;\n  font-weight: 800;\n}\n\n.squoosh-viewport {\n  width: 100%;\n  min-height: 200px;\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  border-radius: 10px;\n  overflow: hidden;\n  position: relative;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.squoosh-empty-state {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  color: var(--text-muted);\n  font-size: 0.8rem;\n  padding: 24px;\n  text-align: center;\n}\n\n.squoosh-split-view {\n  width: 100%;\n  height: 220px;\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  position: relative;\n}\n.squoosh-split-half {\n  width: 100%;\n  height: 100%;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  background: rgba(0, 0, 0, 0.2);\n  padding: 8px;\n  overflow: hidden;\n}\n.squoosh-split-half img {\n  max-width: 100%;\n  max-height: 100%;\n  object-fit: contain;\n}\n.squoosh-divider {\n  position: absolute;\n  left: 50%;\n  top: 0;\n  bottom: 0;\n  width: 2px;\n  background: var(--primary);\n  transform: translateX(-50%);\n  z-index: 2;\n}\n.squoosh-split-tags {\n  position: absolute;\n  bottom: 8px;\n  left: 8px;\n  right: 8px;\n  display: flex;\n  justify-content: space-between;\n  z-index: 3;\n  pointer-events: none;\n}\n.squoosh-tag {\n  background: rgba(0, 0, 0, 0.8);\n  color: #ffffff;\n  font-size: 0.72rem;\n  padding: 3px 8px;\n  border-radius: 4px;\n  font-weight: 700;\n}\n\n/* 8. Presets, Sliders & Formats */\n.squoosh-presets-row {\n  display: grid;\n  grid-template-columns: repeat(5, 1fr);\n  gap: 6px;\n}\n@media (max-width: 540px) {\n  .squoosh-presets-row {\n    grid-template-columns: repeat(3, 1fr);\n  }\n}\n\n.squoosh-chip {\n  padding: 7px 8px;\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  color: var(--text-muted);\n  border-radius: 7px;\n  font-size: 0.76rem;\n  font-weight: 700;\n  cursor: pointer;\n  transition: all 0.15s ease;\n  text-align: center;\n  white-space: nowrap;\n}\n.squoosh-chip:hover {\n  border-color: var(--primary);\n  color: var(--text);\n}\n.squoosh-chip.active {\n  background: var(--primary);\n  border-color: var(--primary);\n  color: #ffffff;\n}\n\n.squoosh-val-badge {\n  font-size: 0.8rem;\n  font-weight: 800;\n  color: var(--primary);\n}\n\n.squoosh-slider {\n  width: 100%;\n  accent-color: var(--primary);\n  cursor: pointer;\n  height: 5px;\n}\n\n.squoosh-format-grid-4 {\n  display: grid;\n  grid-template-columns: repeat(4, 1fr);\n  gap: 6px;\n}\n\n.squoosh-fmt-chip {\n  padding: 7px 4px;\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  color: var(--text-muted);\n  border-radius: 7px;\n  font-size: 0.76rem;\n  font-weight: 700;\n  cursor: pointer;\n  transition: all 0.15s ease;\n  text-align: center;\n}\n.squoosh-fmt-chip:hover {\n  border-color: var(--primary);\n  color: var(--text);\n}\n.squoosh-fmt-chip.active {\n  background: var(--badge-bg);\n  border-color: var(--primary);\n  color: var(--badge-text);\n}\n\n.squoosh-select {\n  width: 100%;\n  height: 35px;\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  color: var(--text);\n  border-radius: 7px;\n  padding: 0 10px;\n  font-size: 0.8rem;\n  font-weight: 600;\n  outline: none;\n}\n.squoosh-select:focus { border-color: var(--primary); }\n\n/* ===================================================\n   9. PERFECT CENTERED BUTTONS & CLEAR STATS GAP\n   =================================================== */\n.squoosh-actions-deck {\n  display: flex;\n  flex-direction: column;\n  gap: 10px;\n  margin-bottom: 24px; /* Generous gap before stats bar! */\n}\n\n.comp-btn-grid-top {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  width: 100%;\n}\n\n.btn-compress-main {\n  flex: 1;\n  height: 44px;\n  background: var(--primary);\n  border: none;\n  color: #ffffff;\n  border-radius: 8px;\n  font-size: 0.88rem;\n  font-weight: 700;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 8px; /* Centered Icon & Text */\n  cursor: pointer;\n  box-shadow: 0 2px 8px var(--primary-glow);\n  transition: all 0.15s ease;\n}\n.btn-compress-main:hover { \n  background: var(--primary-hover); \n}\n\n.btn-download-main {\n  flex: 1;\n  height: 44px;\n  background: var(--accent);\n  border: none;\n  color: #ffffff;\n  border-radius: 8px;\n  font-size: 0.88rem;\n  font-weight: 700;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 8px; /* Centered Icon & Text */\n  cursor: pointer;\n  box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);\n  transition: all 0.15s ease;\n}\n.btn-download-main:hover { \n  background: #059669; \n}\n\n/* Big Full-Width Centered Reset Button */\n.btn-reset-big {\n  width: 100%;\n  height: 40px;\n  background: var(--surface-hover);\n  border: 1px solid rgba(239, 68, 68, 0.35);\n  color: #ef4444;\n  border-radius: 8px;\n  font-size: 0.84rem;\n  font-weight: 700;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 8px; /* Centered Icon & Text */\n  cursor: pointer;\n  transition: all 0.15s ease;\n}\n.btn-reset-big:hover {\n  background: rgba(239, 68, 68, 0.12);\n  border-color: #ef4444;\n}\n\n/* 10. Bottom Stats Strip (Separated from Buttons) */\n.squoosh-stats-bar {\n  display: grid;\n  grid-template-columns: repeat(4, 1fr);\n  gap: 8px;\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  border-radius: 9px;\n  padding: 10px;\n  margin-top: 6px;\n}\n@media (max-width: 500px) {\n  .squoosh-stats-bar {\n    grid-template-columns: repeat(2, 1fr);\n  }\n}\n\n.squoosh-stat-box {\n  text-align: center;\n  padding: 4px;\n}\n.squoosh-stat-box small {\n  display: block;\n  font-size: 0.7rem;\n  text-transform: uppercase;\n  color: var(--text-muted);\n  font-weight: 700;\n}\n.squoosh-stat-box strong {\n  font-size: 0.98rem;\n  font-family: 'SFMono-Regular', Consolas, monospace;\n  color: var(--text);\n}\n\n/* Toasts */\n.squoosh-toast-box {\n  position: fixed;\n  bottom: 20px;\n  left: 50%;\n  transform: translateX(-50%);\n  z-index: 9999;\n}\n.squoosh-toast {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  color: var(--text);\n  padding: 6px 16px;\n  border-radius: 20px;\n  font-size: 0.8rem;\n  font-weight: 700;\n  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);\n}",
        "js": "(function() {\n  let images = [];\n  let selectedFormat = 'original';\n  let targetPreset = 'auto';\n  let currentScale = 1;\n\n  window.setPreset = function(p, el) {\n    targetPreset = p;\n    document.querySelectorAll('.squoosh-presets-row .squoosh-chip').forEach(b => b.classList.remove('active'));\n    el.classList.add('active');\n\n    const sliderWrap = document.getElementById('qualitySliderWrap');\n    const label = document.getElementById('activeTargetLabel');\n\n    if (p === 'auto') {\n      sliderWrap.style.display = 'block';\n      label.textContent = 'Auto (75%)';\n    } else {\n      sliderWrap.style.display = 'none';\n      label.textContent = `Exact ${p} KB Target`;\n    }\n  };\n\n  window.setFormat = function(el, f) {\n    selectedFormat = f;\n    document.querySelectorAll('.squoosh-format-grid-4 .squoosh-fmt-chip').forEach(b => b.classList.remove('active'));\n    el.classList.add('active');\n  };\n\n  window.updateScale = function(val) {\n    currentScale = parseFloat(val) || 1;\n  };\n\n  function showToast(msg) {\n    const c = document.getElementById('toasts');\n    if (!c) return;\n    const t = document.createElement('div');\n    t.className = 'squoosh-toast';\n    t.textContent = msg;\n    c.appendChild(t);\n    setTimeout(() => t.remove(), 2200);\n  }\n\n  window.handleFiles = function(files) {\n    const valid = ['image/jpeg', 'image/png', 'image/webp', 'image/avif'];\n    let count = 0;\n    Array.from(files).forEach(f => {\n      if (!valid.includes(f.type)) return;\n      const id = Date.now() + Math.random();\n      const url = URL.createObjectURL(f);\n      images.push({ id, file: f, url, name: f.name, origSize: f.size, compBlob: null, compSize: null });\n      count++;\n    });\n\n    if (count) {\n      showToast(`${count} image loaded!`);\n      document.getElementById('fileStatusText').textContent = `${images.length} image(s) ready`;\n      renderPreview(images[images.length - 1]);\n      updateStats();\n    }\n  };\n\n  const dropZone = document.getElementById('dropZone');\n  if (dropZone) {\n    ['dragenter', 'dragover'].forEach(n => {\n      dropZone.addEventListener(n, e => { e.preventDefault(); dropZone.classList.add('drag-over'); });\n    });\n    ['dragleave', 'drop'].forEach(n => {\n      dropZone.addEventListener(n, e => { e.preventDefault(); dropZone.classList.remove('drag-over'); });\n    });\n    dropZone.addEventListener('drop', e => {\n      e.preventDefault();\n      if (e.dataTransfer.files.length) window.handleFiles(e.dataTransfer.files);\n    });\n  }\n\n  window.pasteFromClipboard = async function() {\n    try {\n      const items = await navigator.clipboard.read();\n      for (const item of items) {\n        for (const type of item.types) {\n          if (type.startsWith('image/')) {\n            const blob = await item.getType(type);\n            const file = new File([blob], 'clipboard-image.png', { type });\n            window.handleFiles([file]);\n            return;\n          }\n        }\n      }\n      showToast('No image in clipboard');\n    } catch (e) {\n      showToast('Clipboard access denied');\n    }\n  };\n\n  function formatBytes(bytes) {\n    if (bytes == null || isNaN(bytes)) return '&mdash;';\n    if (bytes < 1024) return bytes + ' B';\n    if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';\n    return (bytes / 1024 / 1024).toFixed(2) + ' MB';\n  }\n\n  function renderPreview(img) {\n    const area = document.getElementById('previewArea');\n    const badge = document.getElementById('previewSavedBadge');\n\n    if (img.compBlob) {\n      const compUrl = URL.createObjectURL(img.compBlob);\n      const savedPct = Math.max(0, Math.round((1 - img.compSize / img.origSize) * 100));\n      badge.classList.remove('d-none');\n      badge.textContent = `-${savedPct}% Saved`;\n\n      area.innerHTML = `\n        <div class=\"squoosh-split-view\">\n          <div class=\"squoosh-split-half\"><img src=\"${img.url}\" alt=\"Original\"></div>\n          <div class=\"squoosh-split-half\"><img src=\"${compUrl}\" alt=\"Compressed\"></div>\n          <div class=\"squoosh-divider\"></div>\n          <div class=\"squoosh-split-tags\">\n            <span class=\"squoosh-tag\">Original: ${formatBytes(img.origSize)}</span>\n            <span class=\"squoosh-tag\">Optimized: ${formatBytes(img.compSize)}</span>\n          </div>\n        </div>`;\n    } else {\n      badge.classList.add('d-none');\n      area.innerHTML = `\n        <div style=\"padding:12px; text-align:center; width:100%\">\n          <img src=\"${img.url}\" style=\"max-width:100%; max-height:180px; object-fit:contain; border-radius:4px\" alt=\"Preview\">\n          <div style=\"font-size:0.75rem; color:var(--text-muted); margin-top:4px\">${img.name} &bull; ${formatBytes(img.origSize)}</div>\n        </div>`;\n    }\n  }\n\n  async function compressSingle(img) {\n    return new Promise(resolve => {\n      const canvas = document.createElement('canvas');\n      const iEl = new Image();\n\n      iEl.onload = () => {\n        let w = Math.round(iEl.naturalWidth * currentScale);\n        let h = Math.round(iEl.naturalHeight * currentScale);\n\n        let outMime = selectedFormat === 'original' ? img.file.type :\n          selectedFormat === 'jpg' ? 'image/jpeg' :\n          selectedFormat === 'png' ? 'image/png' :\n          selectedFormat === 'webp' ? 'image/webp' :\n          selectedFormat === 'avif' ? 'image/avif' : 'image/webp';\n\n        if (targetPreset !== 'auto') {\n          const targetBytes = parseInt(targetPreset, 10) * 1024;\n          if (outMime === 'image/png') outMime = 'image/webp';\n          runBinarySearch(iEl, canvas, outMime, targetBytes, w, h).then(blob => resolve(blob));\n          return;\n        }\n\n        canvas.width = Math.max(1, w);\n        canvas.height = Math.max(1, h);\n        const ctx = canvas.getContext('2d');\n        ctx.drawImage(iEl, 0, 0, w, h);\n\n        const quality = parseInt(document.getElementById('qualitySlider').value, 10) / 100;\n        canvas.toBlob(blob => resolve(blob), outMime, quality);\n      };\n\n      iEl.onerror = () => resolve(null);\n      iEl.src = img.url;\n    });\n  }\n\n  async function runBinarySearch(imageEl, canvas, mime, targetBytes, baseW, baseH) {\n    let scale = 1.0;\n    let bestBlob = null;\n\n    for (let pass = 0; pass < 2; pass++) {\n      let w = Math.max(1, Math.round(baseW * scale));\n      let h = Math.max(1, Math.round(baseH * scale));\n      canvas.width = w;\n      canvas.height = h;\n      const ctx = canvas.getContext('2d');\n      ctx.drawImage(imageEl, 0, 0, w, h);\n\n      let minQ = 0.05, maxQ = 0.95;\n      for (let i = 0; i < 8; i++) {\n        let midQ = (minQ + maxQ) / 2;\n        const blob = await new Promise(r => canvas.toBlob(r, mime, midQ));\n        if (!blob) break;\n\n        if (blob.size <= targetBytes) {\n          bestBlob = blob;\n          minQ = midQ;\n        } else {\n          maxQ = midQ;\n        }\n      }\n\n      if (bestBlob && bestBlob.size <= targetBytes) return bestBlob;\n      scale = 0.65;\n    }\n\n    return bestBlob || (await new Promise(r => canvas.toBlob(r, mime, 0.1)));\n  }\n\n  window.compressAll = async function() {\n    if (!images.length) {\n      showToast('Please upload an image first!');\n      return;\n    }\n\n    for (const img of images) {\n      const blob = await compressSingle(img);\n      if (blob) {\n        img.compBlob = blob;\n        img.compSize = blob.size;\n      }\n    }\n\n    renderPreview(images[images.length - 1]);\n    document.getElementById('downloadBtn').classList.remove('d-none');\n    updateStats();\n    showToast('Compressed successfully!');\n  };\n\n  window.downloadResult = function() {\n    if (!images.length || !images[0].compBlob) return;\n    const img = images[0];\n    const ext = img.compBlob.type.split('/')[1].replace('jpeg', 'jpg');\n    const base = img.name.replace(/\\.[^.]+$/, '');\n    const a = document.createElement('a');\n    a.href = URL.createObjectURL(img.compBlob);\n    a.download = `optimized-${base}.${ext}`;\n    a.click();\n  };\n\n  window.clearAll = function() {\n    images = [];\n    document.getElementById('fileStatusText').textContent = 'No image loaded';\n    document.getElementById('previewArea').innerHTML = '<div class=\"squoosh-empty-state\"><svg width=\"34\" height=\"34\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"></rect><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"></circle><polyline points=\"21 15 16 10 5 21\"></polyline></svg><span class=\"mt-2\">Upload an image to see before & after comparison</span></div>';\n    document.getElementById('previewSavedBadge').classList.add('d-none');\n    document.getElementById('downloadBtn').classList.add('d-none');\n    updateStats();\n    showToast('Reset complete');\n  };\n\n  function updateStats() {\n    document.getElementById('statCount').textContent = images.length;\n    const origTotal = images.reduce((sum, i) => sum + i.origSize, 0);\n    document.getElementById('statOrig').innerHTML = origTotal ? formatBytes(origTotal) : '&mdash;';\n\n    const doneImgs = images.filter(i => i.compSize);\n    if (doneImgs.length) {\n      const compTotal = doneImgs.reduce((sum, i) => sum + i.compSize, 0);\n      document.getElementById('statComp').innerHTML = formatBytes(compTotal);\n      const savedBytes = origTotal - compTotal;\n      const pct = Math.max(0, Math.round((savedBytes / origTotal) * 100));\n      document.getElementById('statSaved').innerHTML = `${formatBytes(savedBytes)} (${pct}%)`;\n    } else {\n      document.getElementById('statComp').innerHTML = '&mdash;';\n      document.getElementById('statSaved').innerHTML = '&mdash;';\n    }\n  }\n})();",
        "cdnScripts": "https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"
      },
      "faqs": [
        {
          "q": "How to compress image to 100KB or 20KB online?",
          "a": "To compress an image to 100KB or 20KB, upload your photo, select the 100KB or 20KB preset button (or adjust the slider), and click Download. The browser engine compresses your file to the exact size in milliseconds."
        },
        {
          "q": "Can I compress images to specific sizes like 50KB, 200KB, or 1MB?",
          "a": "Yes. Our tool features one-click presets for 20KB, 50KB, 100KB, 200KB, and 1MB, as well as a precision slider for custom file size requirements."
        },
        {
          "q": "Does compressing reduce image quality or resolution?",
          "a": "No. Our intelligent compression algorithm strips invisible EXIF metadata and optimizes color palettes first, maintaining high visual sharpness while drastically shrinking file size."
        },
        {
          "q": "Which image formats are supported?",
          "a": "You can compress JPG, JPEG, PNG, and modern WebP image formats with instant lossless and lossy optimization options."
        },
        {
          "q": "Is it safe to compress private photos and ID documents here?",
          "a": "Yes. Compression runs 100% client-side in your local browser sandbox. Your photos, signatures, and ID documents are never sent over the internet or saved on remote servers."
        }
      ]
    },
    {
      "id": "qr-code-generator-7873",
      "name": "Custom QR Code Generator & Designer (Wi-Fi, URL, vCard)",
      "category": "Utility",
      "slug": "qr-code-generator",
      "badge": "Popular",
      "iconSvg": "<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n  <defs>\n    <linearGradient id=\"qrGrad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"100%\">\n      <stop offset=\"0%\" stop-color=\"#6366f1\" />\n      <stop offset=\"100%\" stop-color=\"#06b6d4\" />\n    </linearGradient>\n  </defs>\n  <rect width=\"24\" height=\"24\" rx=\"5\" fill=\"url(#qrGrad)\"/>\n  <rect x=\"4.5\" y=\"4.5\" width=\"6\" height=\"6\" rx=\"1.5\" stroke=\"#FFFFFF\" stroke-width=\"1.6\"/>\n  <rect x=\"6.5\" y=\"6.5\" width=\"2\" height=\"2\" fill=\"#FFFFFF\"/>\n  <rect x=\"13.5\" y=\"4.5\" width=\"6\" height=\"6\" rx=\"1.5\" stroke=\"#FFFFFF\" stroke-width=\"1.6\"/>\n  <rect x=\"15.5\" y=\"6.5\" width=\"2\" height=\"2\" fill=\"#FFFFFF\"/>\n  <rect x=\"4.5\" y=\"13.5\" width=\"6\" height=\"6\" rx=\"1.5\" stroke=\"#FFFFFF\" stroke-width=\"1.6\"/>\n  <rect x=\"6.5\" y=\"15.5\" width=\"2\" height=\"2\" fill=\"#FFFFFF\"/>\n  <path d=\"M14 14h2v2h-2zM18 14h1.5v2H18zM14 18h2v1.5h-2zM18 17.5h1.5V19H18z\" fill=\"#FFFFFF\"/>\n</svg>",
      "active": true,
      "usageCount": 50,
      "seo": {
        "targetKeyword": "qr code generator free",
        "metaTitle": "QR Code Generator Free - Create Custom QR Code with Logo Online",
        "metaDescription": "Generate free custom QR codes for URLs, text, Wi-Fi, and contact cards with custom colors and logos. 100% private, no scan limits, instant PNG and SVG download.",
        "canonical": "https://tools.heyswan.cloud/tool/qr-code-generator",
        "ogImage": "",
        "indexable": true
      },
      "content": {
        "about": "Our Free Online QR Code Generator helps businesses, creators, and developers create high-resolution, branded QR codes in seconds. Whether you need a scannable QR code for website URLs, plain text messages, digital business cards (vCard), or seamless Wi-Fi logins, our tool generates clean, static vector codes instantly.\n\nUnlike other platforms that charge subscriptions or expire your codes after a few days, all QR codes generated on HeySwan Cloud are 100% permanent with unlimited scans and zero expiration dates. Everything executes locally inside your browser memory with zero tracking, allowing you to download print-ready PNG and vector SVG graphics for packaging, business cards, and marketing materials safely.",
        "howTo": "1. Select your QR code type (Website URL, plain text, Wi-Fi network, email, or phone number).\n2. Enter your destination link or text message into the input field.\n3. Customize your design with custom foreground/background colors or upload your brand logo.\n4. Click 'Download QR Code' to instantly save your high-resolution PNG or vector SVG file."
      },
      "engine": {
        "html": "<div class=\"qr-unified-app\">\n  \n  <div class=\"qr-layout-grid\">\n    \n    <!-- =========================================\n         LEFT COLUMN: INPUTS & SETTINGS\n         ========================================= -->\n    <div class=\"qr-left-column\">\n      \n      <!-- 1. Type Selector Pills -->\n      <div class=\"qr-block\">\n        <label class=\"qr-block-label\">Select QR Type</label>\n        <div class=\"qr-pills-row\">\n          <button type=\"button\" class=\"qr-type-pill active\" data-type=\"text\">\n            <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"></path><polyline points=\"14 2 14 8 20 8\"></polyline></svg>\n            Text\n          </button>\n          <button type=\"button\" class=\"qr-type-pill\" data-type=\"url\">\n            <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"></path><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"></path></svg>\n            URL\n          </button>\n          <button type=\"button\" class=\"qr-type-pill\" data-type=\"wifi\">\n            <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M5 12.55a11 11 0 0 1 14.08 0\"></path><path d=\"M1.42 9a16 16 0 0 1 21.16 0\"></path><path d=\"M8.53 16.11a6 6 0 0 1 6.95 0\"></path><line x1=\"12\" y1=\"20\" x2=\"12.01\" y2=\"20\"></line></svg>\n            Wi-Fi\n          </button>\n          <button type=\"button\" class=\"qr-type-pill\" data-type=\"contact\">\n            <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"></path><circle cx=\"12\" cy=\"7\" r=\"4\"></circle></svg>\n            vCard\n          </button>\n          <button type=\"button\" class=\"qr-type-pill\" data-type=\"more\">\n            <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"1\"></circle><circle cx=\"19\" cy=\"12\" r=\"1\"></circle><circle cx=\"5\" cy=\"12\" r=\"1\"></circle></svg>\n            More\n          </button>\n        </div>\n      </div>\n\n      <!-- 2. Dynamic Input Content -->\n      <div class=\"qr-block\">\n        <label class=\"qr-block-label\">Enter Content</label>\n\n        <!-- Text -->\n        <div class=\"qr-content-box active\" id=\"text\">\n          <textarea id=\"textValue\" class=\"qr-form-input\" rows=\"3\" placeholder=\"Enter your text here...\"></textarea>\n        </div>\n\n        <!-- URL -->\n        <div class=\"qr-content-box\" id=\"url\">\n          <input id=\"urlValue\" type=\"url\" class=\"qr-form-input\" placeholder=\"https://yourwebsite.com\">\n        </div>\n\n        <!-- Wi-Fi -->\n        <div class=\"qr-content-box\" id=\"wifi\">\n          <div class=\"qr-row-2 mb-2\">\n            <div>\n              <label class=\"qr-micro-label\">Network Name (SSID)</label>\n              <input id=\"wifiSSID\" class=\"qr-form-input\" placeholder=\"e.g. Home_WiFi\">\n            </div>\n            <div>\n              <label class=\"qr-micro-label\">Password</label>\n              <input id=\"wifiPass\" type=\"text\" class=\"qr-form-input\" placeholder=\"Password\">\n            </div>\n          </div>\n          <div class=\"qr-row-2\">\n            <div>\n              <label class=\"qr-micro-label\">Security</label>\n              <select id=\"wifiSecurity\" class=\"qr-form-input\">\n                <option value=\"WPA\" selected>WPA / WPA2 / WPA3</option>\n                <option value=\"WEP\">WEP</option>\n                <option value=\"nopass\">No Password (Open)</option>\n              </select>\n            </div>\n            <div>\n              <label class=\"qr-micro-label\">Network Type</label>\n              <select id=\"wifiHidden\" class=\"qr-form-input\">\n                <option value=\"false\" selected>Visible SSID</option>\n                <option value=\"true\">Hidden SSID</option>\n              </select>\n            </div>\n          </div>\n        </div>\n\n        <!-- vCard -->\n        <div class=\"qr-content-box\" id=\"contact\">\n          <div class=\"qr-row-2 mb-2\">\n            <div>\n              <label class=\"qr-micro-label\">Full Name</label>\n              <input id=\"vName\" class=\"qr-form-input\" placeholder=\"John Doe\">\n            </div>\n            <div>\n              <label class=\"qr-micro-label\">Organization</label>\n              <input id=\"vOrg\" class=\"qr-form-input\" placeholder=\"Company Name\">\n            </div>\n          </div>\n          <div class=\"qr-row-2 mb-2\">\n            <div>\n              <label class=\"qr-micro-label\">Phone Number</label>\n              <input id=\"vPhone\" class=\"qr-form-input\" placeholder=\"+123456789\">\n            </div>\n            <div>\n              <label class=\"qr-micro-label\">Email</label>\n              <input id=\"vEmail\" type=\"email\" class=\"qr-form-input\" placeholder=\"email@example.com\">\n            </div>\n          </div>\n          <div>\n            <label class=\"qr-micro-label\">Website</label>\n            <input id=\"vUrl\" class=\"qr-form-input\" placeholder=\"https://example.com\">\n          </div>\n        </div>\n\n        <!-- More (WhatsApp / Email / Phone / Maps) -->\n        <div class=\"qr-content-box\" id=\"more\">\n          <div class=\"mb-2\">\n            <select id=\"moreType\" class=\"qr-form-input\">\n              <option value=\"whatsapp\" selected>WhatsApp Chat</option>\n              <option value=\"email\">Send Email</option>\n              <option value=\"phone\">Phone Call</option>\n              <option value=\"sms\">SMS Message</option>\n              <option value=\"location\">Google Maps Coordinates</option>\n            </select>\n          </div>\n\n          <div id=\"whatsappFields\">\n            <input id=\"waNumber\" class=\"qr-form-input mb-2\" placeholder=\"WhatsApp Number (e.g. 923001234567)\">\n            <textarea id=\"waMessage\" class=\"qr-form-input\" rows=\"2\" placeholder=\"Pre-filled Message...\"></textarea>\n          </div>\n\n          <div id=\"emailFields\" class=\"hidden\">\n            <input id=\"emailTo\" class=\"qr-form-input mb-2\" placeholder=\"Receiver Email\">\n            <input id=\"emailSubject\" class=\"qr-form-input mb-2\" placeholder=\"Subject\">\n            <textarea id=\"emailBody\" class=\"qr-form-input\" rows=\"2\" placeholder=\"Message Body...\"></textarea>\n          </div>\n\n          <div id=\"phoneFields\" class=\"hidden\">\n            <input id=\"phoneNumber\" class=\"qr-form-input\" placeholder=\"Phone Number (+123456789)\">\n          </div>\n\n          <div id=\"smsFields\" class=\"hidden\">\n            <input id=\"smsNumber\" class=\"qr-form-input mb-2\" placeholder=\"Phone Number\">\n            <textarea id=\"smsMessage\" class=\"qr-form-input\" rows=\"2\" placeholder=\"SMS Text...\"></textarea>\n          </div>\n\n          <div id=\"locationFields\" class=\"hidden\">\n            <div class=\"qr-row-2\">\n              <input id=\"lat\" class=\"qr-form-input\" placeholder=\"Latitude (e.g. 24.8607)\">\n              <input id=\"lng\" class=\"qr-form-input\" placeholder=\"Longitude (e.g. 67.0011)\">\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- 3. Design & Colors (Collapsible Card) -->\n      <div class=\"qr-block\">\n        <label class=\"qr-block-label\">Design &amp; Customization</label>\n        \n        <!-- Color Pickers -->\n        <div class=\"qr-row-2 mb-3\">\n          <div>\n            <label class=\"qr-micro-label\">QR Color</label>\n            <div class=\"qr-color-box\">\n              <input id=\"fgColor\" type=\"color\" value=\"#000000\">\n              <input id=\"fgText\" class=\"qr-hex-text\" value=\"#000000\" maxlength=\"7\">\n            </div>\n          </div>\n          <div>\n            <label class=\"qr-micro-label\">Background Color</label>\n            <div class=\"qr-color-box\">\n              <input id=\"bgColor\" type=\"color\" value=\"#ffffff\">\n              <input id=\"bgText\" class=\"qr-hex-text\" value=\"#ffffff\" maxlength=\"7\">\n            </div>\n          </div>\n        </div>\n\n        <!-- Dot Style & Error Correction -->\n        <div class=\"qr-row-2 mb-3\">\n          <div>\n            <label class=\"qr-micro-label\">Dot Style</label>\n            <select id=\"dotStyle\" class=\"qr-form-input\">\n              <option value=\"square\">Square</option>\n              <option value=\"rounded\" selected>Rounded</option>\n              <option value=\"dots\">Dots</option>\n              <option value=\"extra-rounded\">Extra Rounded</option>\n            </select>\n          </div>\n          <div>\n            <label class=\"qr-micro-label\">Error Correction</label>\n            <select id=\"errorLevel\" class=\"qr-form-input\">\n              <option value=\"L\">Low (7%)</option>\n              <option value=\"M\">Medium (15%)</option>\n              <option value=\"Q\">Quartile (25%)</option>\n              <option value=\"H\" selected>High (30% - Best with Logo)</option>\n            </select>\n          </div>\n        </div>\n\n        <!-- Logo Upload -->\n        <div class=\"mb-3\">\n          <label class=\"qr-micro-label\">Center Logo</label>\n          <div class=\"qr-logo-row\">\n            <input id=\"logoInput\" type=\"file\" accept=\"image/*\" style=\"display: none !important;\">\n            <button type=\"button\" class=\"qr-upload-btn\" onclick=\"document.getElementById('logoInput').click()\">\n              <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path><polyline points=\"17 8 12 3 7 8\"></polyline><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"></line></svg>\n              <span>Upload Logo</span>\n            </button>\n            <div class=\"qr-slider-wrap\">\n              <div class=\"d-flex justify-content-between mb-1\" style=\"font-size: 0.72rem; color: var(--text-muted);\">\n                <span>Scale</span>\n                <span id=\"logoSizeValue\" class=\"text-primary fw-bold\">20%</span>\n              </div>\n              <input id=\"logoSize\" type=\"range\" class=\"qr-range-bar\" min=\"10\" max=\"35\" value=\"20\">\n            </div>\n          </div>\n        </div>\n\n        <!-- Bottom Label -->\n        <div>\n          <label class=\"qr-micro-label\">Bottom Text Label (Optional)</label>\n          <input id=\"qrLabel\" class=\"qr-form-input\" placeholder=\"e.g. Scan to Connect\">\n        </div>\n      </div>\n\n    </div>\n\n    <!-- =========================================\n         RIGHT COLUMN: STICKY LIVE PREVIEW & EXPORT\n         ========================================= -->\n    <div class=\"qr-right-column\">\n      <div class=\"qr-preview-card\">\n        \n        <label class=\"qr-block-label text-center mb-2\">Live Scannable QR</label>\n        \n        <!-- Crisp White QR Box -->\n        <div class=\"qr-canvas-wrapper\">\n          <div id=\"qr\" class=\"qr-canvas-box\">\n            <span class=\"text-muted small\">Generating live QR code...</span>\n          </div>\n          <div id=\"labelPreview\" class=\"qr-label-preview\"></div>\n        </div>\n\n        <!-- Status -->\n        <div class=\"qr-live-status\">\n          <span class=\"status-dot\"></span>\n          <span id=\"statusText\">Vector Ready</span>\n        </div>\n\n        <!-- 4 Action Export Buttons (2x2 Grid) -->\n        <div class=\"qr-buttons-grid\">\n          <button type=\"button\" class=\"qr-btn-action primary\" id=\"pngBtn\">\n            <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path><polyline points=\"7 10 12 15 17 10\"></polyline><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"></line></svg>\n            <span>PNG</span>\n          </button>\n          \n          <button type=\"button\" class=\"qr-btn-action secondary\" id=\"svgBtn\">\n            <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"></path><polyline points=\"14 2 14 8 20 8\"></polyline></svg>\n            <span>SVG</span>\n          </button>\n          \n          <button type=\"button\" class=\"qr-btn-action secondary\" id=\"copyBtn\">\n            <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path></svg>\n            <span>Copy</span>\n          </button>\n          \n          <button type=\"button\" class=\"qr-btn-action secondary\" id=\"printBtn\">\n            <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"6 9 6 2 18 2 18 9\"></polyline><path d=\"M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2\"></path><rect x=\"6\" y=\"14\" width=\"12\" height=\"8\"></rect></svg>\n            <span>Print</span>\n          </button>\n        </div>\n\n      </div>\n    </div>\n\n  </div>\n\n</div>",
        "css": "/* ===================================================\n   STANDALONE CLEAN & RESPONSIVE QR GENERATOR CSS\n   =================================================== */\n\n.qr-unified-app {\n  width: 100%;\n  max-width: 1060px;\n  margin: 0 auto;\n  box-sizing: border-box;\n}\n\n/* 2-Column Desktop Grid / 1-Column Mobile */\n.qr-layout-grid {\n  display: grid;\n  grid-template-columns: minmax(0, 1fr) 320px;\n  gap: 24px;\n  align-items: start;\n}\n\n@media (max-width: 860px) {\n  .qr-layout-grid {\n    grid-template-columns: 1fr;\n    gap: 20px;\n  }\n}\n\n/* Section Blocks */\n.qr-block {\n  margin-bottom: 22px;\n  padding-bottom: 20px;\n  border-bottom: 1px solid var(--border);\n}\n.qr-block:last-child {\n  border-bottom: none;\n  margin-bottom: 0;\n  padding-bottom: 0;\n}\n\n.qr-block-label {\n  display: block;\n  font-size: 0.82rem;\n  font-weight: 800;\n  text-transform: uppercase;\n  letter-spacing: 0.06em;\n  color: var(--text);\n  margin-bottom: 10px;\n}\n\n.qr-micro-label {\n  display: block;\n  font-size: 0.74rem;\n  font-weight: 700;\n  color: var(--text-muted);\n  margin-bottom: 4px;\n  text-transform: uppercase;\n}\n\n/* 1. Type Selector Pills */\n.qr-pills-row {\n  display: flex;\n  gap: 6px;\n  overflow-x: auto;\n  scrollbar-width: none;\n}\n.qr-pills-row::-webkit-scrollbar { display: none; }\n\n.qr-type-pill {\n  flex: 1;\n  min-width: 70px;\n  padding: 8px 12px;\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  color: var(--text-muted);\n  border-radius: 8px;\n  font-size: 0.8rem;\n  font-weight: 700;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 6px;\n  cursor: pointer;\n  white-space: nowrap;\n  transition: all 0.15s ease;\n}\n.qr-type-pill:hover {\n  border-color: var(--primary);\n  color: var(--text);\n}\n.qr-type-pill.active {\n  background: var(--primary);\n  border-color: var(--primary);\n  color: #ffffff;\n  box-shadow: 0 2px 8px var(--primary-glow);\n}\n\n/* 2. Inputs & Textareas */\n.qr-content-box { display: none; }\n.qr-content-box.active { display: block; }\n.hidden { display: none !important; }\n\n.qr-form-input {\n  width: 100%;\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  color: var(--text);\n  border-radius: 8px;\n  padding: 9px 12px;\n  font-size: 0.86rem;\n  outline: none;\n  box-sizing: border-box;\n  transition: border-color 0.15s ease;\n}\n.qr-form-input:focus { border-color: var(--primary); }\n\n.qr-row-2 {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 10px;\n}\n@media (max-width: 500px) {\n  .qr-row-2 { grid-template-columns: 1fr; }\n}\n\n/* 3. Color Boxes */\n.qr-color-box {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  border-radius: 8px;\n  padding: 4px 8px;\n}\n.qr-color-box input[type=color] {\n  width: 32px;\n  height: 32px;\n  border: none;\n  background: transparent;\n  cursor: pointer;\n}\n.qr-hex-text {\n  flex-grow: 1;\n  background: transparent;\n  border: none;\n  color: var(--text);\n  font-family: 'SFMono-Regular', Consolas, monospace;\n  font-size: 0.85rem;\n  font-weight: 700;\n  outline: none;\n  text-transform: uppercase;\n}\n\n/* 4. Logo Row */\n.qr-logo-row {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n}\n\n.qr-upload-btn {\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  color: var(--text);\n  padding: 7px 14px;\n  border-radius: 8px;\n  font-size: 0.78rem;\n  font-weight: 700;\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  cursor: pointer;\n  white-space: nowrap;\n  transition: all 0.15s ease;\n}\n.qr-upload-btn:hover { border-color: var(--primary); color: var(--primary); }\n\n.qr-slider-wrap {\n  flex-grow: 1;\n}\n.qr-range-bar {\n  width: 100%;\n  accent-color: var(--primary);\n  cursor: pointer;\n  height: 4px;\n}\n\n/* =========================================\n   RIGHT COLUMN: STICKY PREVIEW CARD\n   ========================================= */\n.qr-right-column {\n  position: sticky;\n  top: 80px;\n}\n\n.qr-preview-card {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 14px;\n  padding: 16px;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n}\n\n.qr-canvas-wrapper {\n  background: #ffffff; /* Crisp white canvas for scanning */\n  border-radius: 12px;\n  padding: 14px;\n  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);\n  width: 240px;\n  max-width: 100%;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 12px;\n}\n\n.qr-canvas-box {\n  width: 210px;\n  height: 210px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n.qr-canvas-box canvas, .qr-canvas-box svg {\n  max-width: 100%;\n  max-height: 100%;\n}\n\n.qr-label-preview {\n  margin-top: 6px;\n  font-weight: 800;\n  font-size: 0.82rem;\n  color: #0f172a;\n  word-break: break-word;\n  text-align: center;\n}\n\n.qr-live-status {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  font-size: 0.74rem;\n  font-weight: 700;\n  color: var(--accent);\n  margin-bottom: 12px;\n}\n.qr-live-status .status-dot {\n  width: 6px;\n  height: 6px;\n  border-radius: 50%;\n  background: var(--accent);\n  box-shadow: 0 0 6px var(--accent);\n}\n\n/* Action Buttons 2x2 */\n.qr-buttons-grid {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 8px;\n  width: 100%;\n}\n\n.qr-btn-action {\n  height: 38px;\n  border-radius: 8px;\n  font-size: 0.82rem;\n  font-weight: 700;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 6px;\n  cursor: pointer;\n  transition: all 0.15s ease;\n}\n.qr-btn-action.primary {\n  background: var(--primary);\n  border: 1px solid var(--primary);\n  color: #ffffff;\n}\n.qr-btn-action.primary:hover { background: var(--primary-hover); }\n\n.qr-btn-action.secondary {\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  color: var(--text);\n}\n.qr-btn-action.secondary:hover {\n  border-color: var(--primary);\n  color: var(--primary);\n}\n\n@media (max-width: 580px) {\n  .qr-pills-row {\n    display: grid;\n    grid-template-columns: repeat(3, 1fr);\n  }\n}",
        "js": "(function() {\n  let currentType = \"text\";\n  let logoData = null;\n  let qrCode = null;\n\n  const $ = id => document.getElementById(id);\n\n  // 1. DYNAMIC SELF-HEALING LIBRARY AUTO-LOADER\n  function loadQrLibrary(callback) {\n    if (typeof QRCodeStyling !== \"undefined\") {\n      callback();\n      return;\n    }\n\n    const script = document.createElement(\"script\");\n    script.src = \"https://cdn.jsdelivr.net/npm/qr-code-styling@1.8.4/lib/qr-code-styling.js\";\n    script.async = true;\n    script.onload = () => {\n      callback();\n    };\n    script.onerror = () => {\n      if ($(\"statusText\")) $(\"statusText\").textContent = \"Failed to load QR engine. Please refresh.\";\n    };\n    document.head.appendChild(script);\n  }\n\n  // 2. ADVANCE ACCORDION TOGGLE\n  window.toggleAdvanceSettings = function() {\n    const acc = document.querySelector('.qr-advance-accordion');\n    const body = $('advanceSettingsBody');\n    if (acc && body) {\n      acc.classList.toggle('open');\n      body.style.display = body.style.display === 'none' ? 'block' : 'none';\n    }\n  };\n\n  // 3. TYPE SWITCHING TABS\n  const tabs = document.querySelectorAll(\".qr-pill-btn\");\n  const fields = document.querySelectorAll(\".qr-fields\");\n\n  tabs.forEach(tab => {\n    tab.addEventListener(\"click\", () => {\n      tabs.forEach(t => t.classList.remove(\"active\"));\n      fields.forEach(f => f.classList.remove(\"active\"));\n\n      tab.classList.add(\"active\");\n      currentType = tab.dataset.type;\n      const targetBox = $(currentType);\n      if (targetBox) targetBox.classList.add(\"active\");\n\n      updateQR();\n    });\n  });\n\n  function escapeQR(str) {\n    return String(str || \"\")\n      .replace(/\\\\/g, \"\\\\\\\\\")\n      .replace(/;/g, \"\\\\;\")\n      .replace(/,/g, \"\\\\,\")\n      .replace(/:/g, \"\\\\:\");\n  }\n\n  // 4. GET INPUT DATA SAFELY (NEVER EMPTY)\n  function getQRData() {\n    if (currentType === \"text\") {\n      const val = $(\"textValue\") ? $(\"textValue\").value.trim() : \"\";\n      return val || \"https://tools.heyswan.cloud\";\n    }\n\n    if (currentType === \"url\") {\n      const val = $(\"urlValue\") ? $(\"urlValue\").value.trim() : \"\";\n      return val || \"https://tools.heyswan.cloud\";\n    }\n\n    if (currentType === \"wifi\") {\n      const ssid = escapeQR($(\"wifiSSID\") ? $(\"wifiSSID\").value.trim() : \"HeySwan_WiFi\");\n      const pass = escapeQR($(\"wifiPass\") ? $(\"wifiPass\").value.trim() : \"\");\n      const security = $(\"wifiSecurity\") ? $(\"wifiSecurity\").value : \"WPA\";\n      const hidden = $(\"wifiHidden\") ? $(\"wifiHidden\").value : \"false\";\n      return `WIFI:T:${security};S:${ssid || \"HeySwan_WiFi\"};P:${pass};H:${hidden};;`;\n    }\n\n    if (currentType === \"contact\") {\n      const name = $(\"vName\") ? $(\"vName\").value.trim() : \"John Doe\";\n      const org = $(\"vOrg\") ? $(\"vOrg\").value.trim() : \"HeySwan\";\n      const phone = $(\"vPhone\") ? $(\"vPhone\").value.trim() : \"+123456789\";\n      const email = $(\"vEmail\") ? $(\"vEmail\").value.trim() : \"info@heyswan.com\";\n      const url = $(\"vUrl\") ? $(\"vUrl\").value.trim() : \"https://tools.heyswan.cloud\";\n\n      return `BEGIN:VCARD\\nVERSION:3.0\\nFN:${name || \"John Doe\"}\\nORG:${org}\\nTEL:${phone}\\nEMAIL:${email}\\nURL:${url}\\nEND:VCARD`;\n    }\n\n    if (currentType === \"more\") {\n      const type = $(\"moreType\") ? $(\"moreType\").value : \"whatsapp\";\n\n      if (type === \"whatsapp\") {\n        const num = ($(\"waNumber\") ? $(\"waNumber\").value : \"923001234567\").replace(/\\D/g, \"\");\n        const msg = encodeURIComponent($(\"waMessage\") ? $(\"waMessage\").value : \"Hello from HeySwan QR!\");\n        return `https://wa.me/${num || \"923001234567\"}?text=${msg}`;\n      }\n\n      if (type === \"email\") {\n        const to = encodeURIComponent($(\"emailTo\") ? $(\"emailTo\").value : \"info@heyswan.com\");\n        const subject = encodeURIComponent($(\"emailSubject\") ? $(\"emailSubject\").value : \"Inquiry\");\n        const body = encodeURIComponent($(\"emailBody\") ? $(\"emailBody\").value : \"Hello!\");\n        return `mailto:${to}?subject=${subject}&body=${body}`;\n      }\n\n      if (type === \"phone\") {\n        return `tel:${$(\"phoneNumber\") ? $(\"phoneNumber\").value : \"+123456789\"}`;\n      }\n\n      if (type === \"sms\") {\n        return `SMSTO:${$(\"smsNumber\") ? $(\"smsNumber\").value : \"+123456789\"}:${$(\"smsMessage\") ? $(\"smsMessage\").value : \"Hello!\"}`;\n      }\n\n      if (type === \"location\") {\n        return `geo:${$(\"lat\") ? $(\"lat\").value || \"24.8607\" : \"24.8607\"},${$(\"lng\") ? $(\"lng\").value || \"67.0011\" : \"67.0011\"}`;\n      }\n    }\n\n    return \"https://tools.heyswan.cloud\";\n  }\n\n  // 5. RENDER QR CODE ENGINE\n  function createQR() {\n    if (typeof QRCodeStyling === \"undefined\") {\n      loadQrLibrary(createQR);\n      return;\n    }\n\n    const fgColor = $(\"fgColor\") ? $(\"fgColor\").value : \"#000000\";\n    const bgColor = $(\"bgColor\") ? $(\"bgColor\").value : \"#ffffff\";\n    const dotsType = $(\"dotStyle\") ? $(\"dotStyle\").value : \"rounded\";\n    const errorCorrection = $(\"errorLevel\") ? $(\"errorLevel\").value : \"H\";\n    const logoScale = $(\"logoSize\") ? Number($(\"logoSize\").value) / 100 : 0.20;\n\n    qrCode = new QRCodeStyling({\n      width: 220,\n      height: 220,\n      type: \"canvas\",\n      data: getQRData(),\n      margin: 4,\n      qrOptions: {\n        errorCorrectionLevel: errorCorrection\n      },\n      dotsOptions: {\n        color: fgColor,\n        type: dotsType\n      },\n      backgroundOptions: {\n        color: bgColor\n      },\n      cornersSquareOptions: {\n        color: fgColor,\n        type: \"extra-rounded\"\n      },\n      cornersDotOptions: {\n        color: fgColor,\n        type: \"dot\"\n      },\n      image: logoData || undefined,\n      imageOptions: {\n        crossOrigin: \"anonymous\",\n        margin: 4,\n        imageSize: logoScale,\n        hideBackgroundDots: true\n      }\n    });\n\n    const qrContainer = $(\"qr\");\n    if (qrContainer) {\n      qrContainer.innerHTML = \"\";\n      qrCode.append(qrContainer);\n    }\n\n    if ($(\"labelPreview\")) $(\"labelPreview\").textContent = $(\"qrLabel\") ? $(\"qrLabel\").value : \"\";\n    if ($(\"statusText\")) $(\"statusText\").textContent = \"Live Vector Generated\";\n  }\n\n  function updateQR() {\n    createQR();\n  }\n\n  // 6. REAL-TIME EVENT LISTENERS\n  [\n    \"textValue\",\"urlValue\",\"wifiSSID\",\"wifiPass\",\"wifiSecurity\",\n    \"wifiHidden\",\"vName\",\"vOrg\",\"vPhone\",\"vEmail\",\"vUrl\",\n    \"moreType\",\"emailTo\",\"emailSubject\",\"emailBody\",\"phoneNumber\",\n    \"smsNumber\",\"smsMessage\",\"waNumber\",\"waMessage\",\"lat\",\"lng\",\n    \"fgColor\",\"bgColor\",\"fgText\",\"bgText\",\"qrLabel\",\"errorLevel\",\n    \"dotStyle\"\n  ].forEach(id => {\n    const el = $(id);\n    if (el) {\n      el.addEventListener(\"input\", () => {\n        if (id === \"fgText\" && /^#[0-9A-Fa-f]{6}$/.test(el.value)) {\n          if ($(\"fgColor\")) $(\"fgColor\").value = el.value;\n        }\n        if (id === \"bgText\" && /^#[0-9A-Fa-f]{6}$/.test(el.value)) {\n          if ($(\"bgColor\")) $(\"bgColor\").value = el.value;\n        }\n        updateQR();\n      });\n      el.addEventListener(\"change\", updateQR);\n    }\n  });\n\n  if ($(\"fgColor\")) $(\"fgColor\").addEventListener(\"input\", () => { if ($(\"fgText\")) $(\"fgText\").value = $(\"fgColor\").value; });\n  if ($(\"bgColor\")) $(\"bgColor\").addEventListener(\"input\", () => { if ($(\"bgText\")) $(\"bgText\").value = $(\"bgColor\").value; });\n  if ($(\"logoSize\")) $(\"logoSize\").addEventListener(\"input\", () => { if ($(\"logoSizeValue\")) $(\"logoSizeValue\").textContent = $(\"logoSize\").value + \"%\"; updateQR(); });\n\n  // Logo Upload\n  if ($(\"logoInput\")) {\n    $(\"logoInput\").addEventListener(\"change\", e => {\n      const file = e.target.files[0];\n      if (!file) { logoData = null; updateQR(); return; }\n      if (file.size > 3 * 1024 * 1024) { alert(\"Logo must be smaller than 3 MB.\"); e.target.value = \"\"; return; }\n\n      const reader = new FileReader();\n      reader.onload = () => { logoData = reader.result; updateQR(); };\n      reader.readAsDataURL(file);\n    });\n  }\n\n  // More Dropdown Switcher\n  if ($(\"moreType\")) {\n    $(\"moreType\").addEventListener(\"change\", () => {\n      const type = $(\"moreType\").value;\n      [\"emailFields\", \"phoneFields\", \"smsFields\", \"whatsappFields\", \"locationFields\"].forEach(f => {\n        if ($(f)) $(f).classList.add(\"hidden\");\n      });\n\n      if (type === \"email\" && $(\"emailFields\")) $(\"emailFields\").classList.remove(\"hidden\");\n      if (type === \"phone\" && $(\"phoneFields\")) $(\"phoneFields\").classList.remove(\"hidden\");\n      if (type === \"sms\" && $(\"smsFields\")) $(\"smsFields\").classList.remove(\"hidden\");\n      if (type === \"whatsapp\" && $(\"whatsappFields\")) $(\"whatsappFields\").classList.remove(\"hidden\");\n      if (type === \"location\" && $(\"locationFields\")) $(\"locationFields\").classList.remove(\"hidden\");\n\n      updateQR();\n    });\n  }\n\n  // Export Downloads\n  if ($(\"pngBtn\")) $(\"pngBtn\").addEventListener(\"click\", () => { if (qrCode) qrCode.download({ name: \"heyswan-qr-code\", extension: \"png\" }); });\n  if ($(\"svgBtn\")) $(\"svgBtn\").addEventListener(\"click\", () => { if (qrCode) qrCode.download({ name: \"heyswan-qr-code\", extension: \"svg\" }); });\n\n  if ($(\"copyBtn\")) {\n    $(\"copyBtn\").addEventListener(\"click\", async () => {\n      if (!qrCode) return;\n      try {\n        const blob = await qrCode.getRawData(\"png\");\n        await navigator.clipboard.write([new ClipboardItem({ \"image/png\": blob })]);\n        if ($(\"statusText\")) {\n          $(\"statusText\").textContent = \"QR Image Copied! ✅\";\n          setTimeout(() => { if ($(\"statusText\")) $(\"statusText\").textContent = \"Live Vector Generated\"; }, 2000);\n        }\n      } catch(err) {\n        alert(\"Copy not supported directly in this browser. Please click Download PNG.\");\n      }\n    });\n  }\n\n  if ($(\"printBtn\")) {\n    $(\"printBtn\").addEventListener(\"click\", () => {\n      if (!qrCode) return;\n      const canvas = document.querySelector(\"#qr canvas\");\n      if (!canvas) return;\n      const image = canvas.toDataURL(\"image/png\");\n      const label = $(\"qrLabel\") ? $(\"qrLabel\").value : \"\";\n      const win = window.open(\"\", \"_blank\");\n      win.document.write(`<!DOCTYPE html><html><head><title>Print QR</title><style>body{margin:0;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;font-family:system-ui;}img{width:280px;height:280px;}</style></head><body><img src=\"${image}\">${label ? `<h3>${label}</h3>` : \"\"}<script>window.onload=function(){window.print();}<\\/script></body></html>`);\n      win.document.close();\n    });\n  }\n\n  // 7. INITIALIZE ON LOAD WITH AUTO-LOADER\n  loadQrLibrary(createQR);\n})();",
        "cdnScripts": "https://cdn.jsdelivr.net/npm/qr-code-styling@1.8.4/lib/qr-code-styling.js"
      },
      "faqs": [
        {
          "q": "Is this QR code generator completely free?",
          "a": "Yes. Our QR Code Generator is 100% free with unlimited scans, no expiration dates, no hidden subscriptions, and no mandatory account registration."
        },
        {
          "q": "Do these generated QR codes ever expire?",
          "a": "No. The generated QR codes are static, meaning your text or URL is encoded directly into the pixel matrix. They remain functional forever with unlimited scan limits."
        },
        {
          "q": "Can I create a QR code with my custom brand logo?",
          "a": "Yes. You can upload any PNG or JPG logo to place directly in the center of your QR code with high-level error correction to ensure seamless scanner readability."
        },
        {
          "q": "What formats can I download for printing?",
          "a": "You can download high-resolution PNG images for digital displays and vector SVG files for crisp, lossless physical printing on posters and business cards."
        },
        {
          "q": "Is my encoded data or URL stored on your servers?",
          "a": "No. All QR code generation and matrix rendering run 100% client-side in your web browser memory. We never store, log, or track your links or encoded information."
        }
      ]
    },
    {
      "id": "css-cleaner-3997",
      "name": "CSS Cleaner",
      "category": "Developer",
      "slug": "css-cleaner",
      "badge": "Popular",
      "iconSvg": "<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n  <rect width=\"24\" height=\"24\" rx=\"5\" fill=\"#1572B6\"/>\n  <path d=\"M4.5 4.5L5.8 19L12 20.7L18.2 19L19.5 4.5H4.5ZM16.3 8.3H9.2L9.4 10.3H16.1L15.6 15.6L12 16.6L8.4 15.6L8.2 13.4H10.2L10.3 14.3L12 14.8L13.7 14.3L13.9 12.1H8.1L7.7 6.4H16.5L16.3 8.3Z\" fill=\"#FFFFFF\"/>\n</svg>",
      "active": true,
      "usageCount": 46,
      "seo": {
        "targetKeyword": "remove unused css online",
        "metaTitle": "Clean Unused CSS Online - Free PurgeCSS & Unused CSS Remover",
        "metaDescription": "Find and remove unused CSS online instantly. Clean dead stylesheet classes, reduce CSS file size, and boost Google PageSpeed with 100% private in-browser execution.",
        "canonical": "https://tools.heyswan.cloud/tool/css-cleaner",
        "ogImage": "",
        "indexable": true
      },
      "content": {
        "about": "Our Online Unused CSS Remover (PurgeCSS Alternative) helps web developers, SEO specialists, and site owners find and clean dead stylesheet code directly in their web browser. Unused CSS bloats your website, slows down First Contentful Paint (FCP), and causes poor Google PageSpeed Insights and Core Web Vitals scores. \n\nBy analyzing your live HTML DOM tree against your stylesheets, our engine safely strips unused classes, IDs, and redundant media queries while 100% preserving required CSS variables (:root), animations (@keyframes), fonts (@font-face), and pseudo-classes (:hover, :focus). Everything executes locally in your browser memory with zero server latency and absolute data privacy.",
        "howTo": "1. Paste your CSS stylesheet code or upload your .css file into the left editor pane.\n2. Paste your HTML markup, template, or full HTML document with embedded <style> tags into the right editor.\n3. Click 'Clean CSS' to scan your DOM tree and instantly purge all unreferenced, dead selectors.\n4. Review the removed rules list, then Copy or Download your lightweight, optimized stylesheet."
      },
      "engine": {
        "html": "<div class=\"cleaner-wrap\">\n  \n  <!-- Editors Row -->\n  <div class=\"cleaner-editor-row\">\n\n    <!-- CSS Input Pane -->\n    <div class=\"cleaner-pane\">\n      <div class=\"cleaner-pane-top\">\n        <span class=\"cleaner-pane-title\">\n          <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><polyline points=\"16 18 22 12 16 6\"/><polyline points=\"8 6 2 12 8 18\"/></svg>\n          CSS Stylesheet\n        </span>\n        <div class=\"cleaner-pane-actions\">\n          <span class=\"cleaner-count-tag\" id=\"css-count\">0 rules</span>\n          <label class=\"cleaner-ic-btn\" title=\"Upload .css file\">\n            <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"/></svg>\n            <span>Upload</span>\n            <input type=\"file\" id=\"cssFileInput\" accept=\".css\" style=\"display:none\">\n          </label>\n          <button type=\"button\" class=\"cleaner-ic-btn\" id=\"clearCssBtn\" aria-label=\"Clear CSS\">\n            <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>\n          </button>\n        </div>\n      </div>\n      <textarea id=\"css-in\" class=\"cleaner-textarea\" placeholder=\"Paste CSS here, or leave empty if your HTML contains <style> tags...\" spellcheck=\"false\"></textarea>\n    </div>\n\n    <!-- HTML / Mixed File Input Pane -->\n    <div class=\"cleaner-pane\">\n      <div class=\"cleaner-pane-top\">\n        <span class=\"cleaner-pane-title\">\n          <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/></svg>\n          HTML Markup (or Mixed File)\n        </span>\n        <div class=\"cleaner-pane-actions\">\n          <span class=\"cleaner-count-tag\" id=\"html-count\">0 elements</span>\n          <label class=\"cleaner-ic-btn\" title=\"Upload .html file\">\n            <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"/></svg>\n            <span>Upload</span>\n            <input type=\"file\" id=\"htmlFileInput\" accept=\".html,.htm,.php,.vue\" style=\"display:none\">\n          </label>\n          <button type=\"button\" class=\"cleaner-ic-btn\" id=\"clearHtmlBtn\" aria-label=\"Clear HTML\">\n            <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>\n          </button>\n        </div>\n      </div>\n      <textarea id=\"html-in\" class=\"cleaner-textarea\" placeholder=\"Paste HTML or full Single-File HTML with <style>...</style> embedded...\" spellcheck=\"false\"></textarea>\n    </div>\n\n  </div>\n\n  <!-- Action Buttons Row -->\n  <div class=\"cleaner-btn-row\">\n    <button type=\"button\" class=\"btn-action btn-primary\" id=\"cleanActionBtn\">\n      <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg>\n      <span>Clean CSS</span>\n    </button>\n    \n    <!-- Button for Mixed File Sample -->\n    <button type=\"button\" class=\"btn-action\" id=\"demoMixedBtn\" title=\"Load sample Single-File HTML with embedded <style>\">\n      <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/><line x1=\"9\" y1=\"21\" x2=\"9\" y2=\"9\"/></svg>\n      <span>Mixed HTML/CSS Demo</span>\n    </button>\n\n    <button type=\"button\" class=\"btn-action\" id=\"demoActionBtn\">\n      <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M4 19.5A2.5 2.5 0 0 1 6.5 17H20\"/><path d=\"M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z\"/></svg>\n      <span>Split Demo</span>\n    </button>\n    <button type=\"button\" class=\"btn-action\" id=\"resetActionBtn\">\n      <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/></svg>\n      <span>Reset</span>\n    </button>\n  </div>\n\n  <!-- Output & Stats Section -->\n  <div id=\"output-section\" class=\"cleaner-output-section\" style=\"display:none;\">\n\n    <div class=\"cleaner-stats-row\">\n      <div class=\"cleaner-stat\">\n        <div class=\"cleaner-stat-n removed-n\" id=\"s-removed\">0</div>\n        <div class=\"cleaner-stat-l\">rules removed</div>\n      </div>\n      <div class=\"cleaner-stat\">\n        <div class=\"cleaner-stat-n kept-n\" id=\"s-kept\">0</div>\n        <div class=\"cleaner-stat-l\">rules kept</div>\n      </div>\n      <div class=\"cleaner-stat\">\n        <div class=\"cleaner-stat-n saved-n\" id=\"s-saved\">0%</div>\n        <div class=\"cleaner-stat-l\">size reduction</div>\n      </div>\n    </div>\n\n    <div class=\"cleaner-output-pane\">\n      <div class=\"cleaner-output-top\">\n        <div class=\"cleaner-output-label\">\n          <span class=\"cleaner-green-dot\"></span>\n          <span id=\"output-heading-text\">Clean CSS Result</span>\n        </div>\n        <div class=\"cleaner-output-btns\">\n          <button type=\"button\" class=\"cleaner-ic-btn\" id=\"copyCleanCssBtn\">\n            <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg>\n            <span>Copy CSS</span>\n          </button>\n          <button type=\"button\" class=\"cleaner-ic-btn\" id=\"downloadCleanCssBtn\">\n            <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/></svg>\n            <span>Download</span>\n          </button>\n        </div>\n      </div>\n      <textarea class=\"cleaner-clean-textarea\" id=\"clean-out\" readonly spellcheck=\"false\"></textarea>\n      <div class=\"cleaner-removed-list\" id=\"removed-list\"></div>\n    </div>\n\n  </div>\n</div>\n\n<div id=\"cleaner-toast\" class=\"cleaner-toast\">Done</div>",
        "css": ".cleaner-wrap {\n  display: flex;\n  flex-direction: column;\n  gap: 14px;\n  width: 100%;\n}\n\n.cleaner-editor-row {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 14px;\n}\n@media (max-width: 768px) {\n  .cleaner-editor-row { grid-template-columns: 1fr; }\n}\n\n.cleaner-pane {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 10px;\n  overflow: hidden;\n  display: flex;\n  flex-direction: column;\n}\n\n.cleaner-pane-top {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 8px 12px;\n  border-bottom: 1px solid var(--border);\n  background: var(--surface-hover);\n}\n\n.cleaner-pane-title {\n  font-size: 0.8rem;\n  font-weight: 700;\n  color: var(--text);\n  display: flex;\n  align-items: center;\n  gap: 6px;\n}\n.cleaner-pane-title svg { color: var(--primary); }\n\n.cleaner-pane-actions {\n  display: flex;\n  gap: 6px;\n  align-items: center;\n}\n\n.cleaner-count-tag {\n  font-size: 0.72rem;\n  color: var(--text-muted);\n  font-variant-numeric: tabular-nums;\n}\n\n.cleaner-ic-btn {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 6px;\n  padding: 4px 8px;\n  cursor: pointer;\n  font-size: 0.76rem;\n  font-weight: 600;\n  color: var(--text-muted);\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n  transition: all 0.15s ease;\n  font-family: inherit;\n}\n.cleaner-ic-btn:hover {\n  background: var(--surface-hover);\n  color: var(--text);\n  border-color: var(--primary);\n}\n\n.cleaner-textarea {\n  width: 100%;\n  height: 230px;\n  background: transparent;\n  border: none;\n  outline: none;\n  padding: 12px;\n  font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;\n  font-size: 0.85rem;\n  color: var(--text);\n  resize: vertical;\n  line-height: 1.6;\n}\n\n.cleaner-btn-row {\n  display: flex;\n  gap: 8px;\n  flex-wrap: wrap;\n}\n\n.cleaner-output-section {\n  display: flex;\n  flex-direction: column;\n  gap: 12px;\n  margin-top: 4px;\n}\n\n.cleaner-stats-row {\n  display: grid;\n  grid-template-columns: repeat(3, 1fr);\n  gap: 10px;\n}\n\n.cleaner-stat {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 8px;\n  padding: 10px 12px;\n  text-align: center;\n}\n.cleaner-stat-n {\n  font-size: 1.5rem;\n  font-weight: 800;\n  line-height: 1.2;\n}\n.cleaner-stat-l {\n  font-size: 0.72rem;\n  color: var(--text-muted);\n  text-transform: uppercase;\n  font-weight: 700;\n  margin-top: 2px;\n}\n.removed-n { color: #ef4444; }\n.kept-n    { color: #10b981; }\n.saved-n   { color: var(--primary); }\n\n.cleaner-output-pane {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 10px;\n  overflow: hidden;\n}\n\n.cleaner-output-top {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 8px 12px;\n  border-bottom: 1px solid var(--border);\n  background: var(--surface-hover);\n}\n\n.cleaner-output-label {\n  font-size: 0.8rem;\n  font-weight: 700;\n  color: var(--text);\n  display: flex;\n  align-items: center;\n  gap: 7px;\n}\n\n.cleaner-green-dot {\n  width: 8px;\n  height: 8px;\n  border-radius: 50%;\n  background: #10b981;\n  box-shadow: 0 0 8px #10b981;\n  flex-shrink: 0;\n}\n\n.cleaner-output-btns { display: flex; gap: 6px; }\n\n.cleaner-clean-textarea {\n  width: 100%;\n  height: 230px;\n  background: transparent;\n  border: none;\n  outline: none;\n  padding: 12px;\n  font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;\n  font-size: 0.85rem;\n  color: var(--text);\n  resize: vertical;\n  line-height: 1.6;\n}\n\n.cleaner-removed-list {\n  border-top: 1px solid var(--border);\n}\n\n.cleaner-removed-header {\n  padding: 8px 12px;\n  font-size: 0.76rem;\n  font-weight: 700;\n  color: #ef4444;\n  border-bottom: 1px solid var(--border);\n  background: rgba(239, 68, 68, 0.1);\n  display: flex;\n  align-items: center;\n  gap: 6px;\n}\n\n.cleaner-removed-item {\n  padding: 7px 12px;\n  font-size: 0.8rem;\n  font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;\n  border-bottom: 1px solid var(--border);\n  color: var(--text-muted);\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n.cleaner-removed-item:last-child { border-bottom: none; }\n.cleaner-removed-item svg { color: #ef4444; flex-shrink: 0; }\n\n.cleaner-empty-state {\n  padding: 1.25rem;\n  text-align: center;\n  font-size: 0.84rem;\n  color: var(--text-muted);\n}\n\n.cleaner-toast {\n  position: fixed;\n  bottom: 24px;\n  left: 50%;\n  transform: translateX(-50%);\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  border-radius: 8px;\n  font-size: 0.84rem;\n  font-weight: 600;\n  padding: 8px 18px;\n  color: var(--text);\n  white-space: nowrap;\n  opacity: 0;\n  transition: opacity 0.25s ease;\n  pointer-events: none;\n  z-index: 9999;\n  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);\n}",
        "js": "(function () {\n  'use strict';\n\n  const cssIn       = document.getElementById('css-in');\n  const htmlIn      = document.getElementById('html-in');\n  const cleanOut    = document.getElementById('clean-out');\n  const cssCount    = document.getElementById('css-count');\n  const htmlCount   = document.getElementById('html-count');\n  const cssFile     = document.getElementById('cssFileInput');\n  const htmlFile    = document.getElementById('htmlFileInput');\n  const clearCss    = document.getElementById('clearCssBtn');\n  const clearHtml   = document.getElementById('clearHtmlBtn');\n  const cleanBtn    = document.getElementById('cleanActionBtn');\n  const demoBtn     = document.getElementById('demoActionBtn');\n  const demoMixedBtn= document.getElementById('demoMixedBtn');\n  const resetBtn    = document.getElementById('resetActionBtn');\n  const copyBtn     = document.getElementById('copyCleanCssBtn');\n  const downloadBtn = document.getElementById('downloadCleanCssBtn');\n  const outputSec   = document.getElementById('output-section');\n  const toast       = document.getElementById('cleaner-toast');\n\n  let cleanCssText = '';\n\n  function showToast(msg) {\n    if (!toast) return;\n    toast.textContent = msg;\n    toast.style.opacity = '1';\n    setTimeout(function () { toast.style.opacity = '0'; }, 1800);\n  }\n\n  function countRules() {\n    if (!cssIn || !cssCount) return;\n    const m = cssIn.value.match(/[^{}]+(?=\\s*\\{)/g);\n    const n = m ? m.filter(function(s) { return s.trim() && !s.trim().startsWith('@') && !s.trim().startsWith('/'); }).length : 0;\n    cssCount.textContent = n + ' rule' + (n !== 1 ? 's' : '');\n  }\n\n  function countEls() {\n    if (!htmlIn || !htmlCount) return;\n    const m = htmlIn.value.match(/<[a-z][^>]*>/gi);\n    htmlCount.textContent = (m ? m.length : 0) + ' element' + ((m ? m.length : 0) !== 1 ? 's' : '');\n  }\n\n  function handleFileUpload(inputEl, targetTextArea, counterFn) {\n    if (!inputEl) return;\n    inputEl.addEventListener('change', function () {\n      const f = this.files[0];\n      if (!f) return;\n      const r = new FileReader();\n      r.onload = function (e) {\n        targetTextArea.value = e.target.result;\n        counterFn();\n        showToast('Loaded: ' + f.name);\n      };\n      r.readAsText(f);\n      this.value = '';\n    });\n  }\n\n  handleFileUpload(cssFile, cssIn, countRules);\n  handleFileUpload(htmlFile, htmlIn, countEls);\n\n  if (clearCss && cssIn) {\n    clearCss.addEventListener('click', function () { cssIn.value = ''; countRules(); });\n  }\n  if (clearHtml && htmlIn) {\n    clearHtml.addEventListener('click', function () { htmlIn.value = ''; countEls(); });\n  }\n\n  if (cssIn) cssIn.addEventListener('input', countRules);\n  if (htmlIn) htmlIn.addEventListener('input', countEls);\n\n  // ── Auto Extract Embedded <style> ──\n  function extractEmbeddedCss(html) {\n    const styleTags = html.match(/<style[^>]*>([\\s\\S]*?)<\\/style>/gi);\n    if (!styleTags) return '';\n    return styleTags.map(function (tag) {\n      return tag.replace(/<\\/?style[^>]*>/gi, '').trim();\n    }).join('\\n\\n');\n  }\n\n  // ── High Precision Structural CSS Cleaner ──\n  function purgeCssEngine(rawCss, htmlDoc) {\n    function isSelectorUsed(sel) {\n      const s = sel.trim();\n      if (!s || s === '*' || s === ':root' || s.startsWith('html') || s.startsWith('body')) return true;\n      const clean = s\n        .replace(/::?[\\w-]+(\\([^)]*\\))?/g, '')\n        .replace(/\\[.*?\\]/g, '[data-x]')\n        .trim();\n      if (!clean) return true;\n      try {\n        return htmlDoc.querySelectorAll(clean).length > 0;\n      } catch (e) {\n        return true;\n      }\n    }\n\n    function ruleIsUsed(selectorStr) {\n      const parts = selectorStr.split(',').map(function (s) { return s.trim(); }).filter(Boolean);\n      return parts.some(function (p) { return isSelectorUsed(p); });\n    }\n\n    function skipWhitespace(str, idx) {\n      while (idx < str.length) {\n        if (str.startsWith('/*', idx)) {\n          const end = str.indexOf('*/', idx + 2);\n          idx = end === -1 ? str.length : end + 2;\n        } else if (/\\s/.test(str[idx])) {\n          idx++;\n        } else {\n          break;\n        }\n      }\n      return idx;\n    }\n\n    function processInnerRules(innerCss) {\n      let pos = 0;\n      const rules = [];\n      let kept = 0;\n      let removed = 0;\n      const rList = [];\n\n      while (pos < innerCss.length) {\n        pos = skipWhitespace(innerCss, pos);\n        if (pos >= innerCss.length) break;\n\n        const openBrace = innerCss.indexOf('{', pos);\n        if (openBrace === -1) break;\n\n        const selector = innerCss.substring(pos, openBrace).trim();\n        let depth = 1;\n        let cur = openBrace + 1;\n        while (cur < innerCss.length && depth > 0) {\n          if (innerCss.startsWith('/*', cur)) {\n            const cEnd = innerCss.indexOf('*/', cur + 2);\n            cur = cEnd === -1 ? innerCss.length : cEnd + 2;\n            continue;\n          }\n          if (innerCss[cur] === '{') depth++;\n          else if (innerCss[cur] === '}') depth--;\n          cur++;\n        }\n        const body = innerCss.substring(openBrace, cur).trim();\n        pos = cur;\n\n        if (selector) {\n          if (ruleIsUsed(selector)) {\n            rules.push(selector + ' ' + body);\n            kept++;\n          } else {\n            removed++;\n            rList.push(selector);\n          }\n        }\n      }\n      return { rules: rules, kept: kept, removed: removed, removedList: rList };\n    }\n\n    const cleaned = [];\n    const removedList = [];\n    let keptCount = 0;\n    let removedCount = 0;\n    let pos = 0;\n\n    while (pos < rawCss.length) {\n      pos = skipWhitespace(rawCss, pos);\n      if (pos >= rawCss.length) break;\n\n      // Check @-rules\n      if (rawCss[pos] === '@') {\n        const openBrace = rawCss.indexOf('{', pos);\n        const semicolon = rawCss.indexOf(';', pos);\n\n        // Single line @-rule (@import, @charset)\n        if (semicolon !== -1 && (openBrace === -1 || semicolon < openBrace)) {\n          cleaned.push(rawCss.substring(pos, semicolon + 1).trim());\n          pos = semicolon + 1;\n          continue;\n        }\n\n        if (openBrace !== -1) {\n          const header = rawCss.substring(pos, openBrace).trim();\n          let depth = 1;\n          let cur = openBrace + 1;\n          while (cur < rawCss.length && depth > 0) {\n            if (rawCss.startsWith('/*', cur)) {\n              const cEnd = rawCss.indexOf('*/', cur + 2);\n              cur = cEnd === -1 ? rawCss.length : cEnd + 2;\n              continue;\n            }\n            if (rawCss[cur] === '{') depth++;\n            else if (rawCss[cur] === '}') depth--;\n            cur++;\n          }\n          const blockBody = rawCss.substring(openBrace + 1, cur - 1);\n          pos = cur;\n\n          // Preserve @keyframes & @font-face completely\n          if (/^@keyframes|^@-webkit-keyframes|^@font-face|^@counter-style/i.test(header)) {\n            cleaned.push(header + ' {\\n' + blockBody.trim() + '\\n}');\n            keptCount++;\n          }\n          // Filter @media & @supports\n          else if (/^@media|^@supports/i.test(header)) {\n            const inner = processInnerRules(blockBody);\n            if (inner.rules.length > 0) {\n              const formattedInner = inner.rules.map(function (r) {\n                return '  ' + r.replace(/\\n/g, '\\n  ');\n              }).join('\\n\\n');\n              cleaned.push(header + ' {\\n' + formattedInner + '\\n}');\n              keptCount += inner.kept;\n              removedCount += inner.removed;\n              removedList.push.apply(removedList, inner.removedList);\n            } else {\n              removedCount += inner.removed;\n              removedList.push.apply(removedList, inner.removedList);\n            }\n          } else {\n            cleaned.push(header + ' {\\n' + blockBody.trim() + '\\n}');\n          }\n          continue;\n        }\n      }\n\n      // Normal top-level rule\n      const openBrace = rawCss.indexOf('{', pos);\n      if (openBrace === -1) break;\n\n      const selector = rawCss.substring(pos, openBrace).trim();\n      let depth = 1;\n      let cur = openBrace + 1;\n      while (cur < rawCss.length && depth > 0) {\n        if (rawCss.startsWith('/*', cur)) {\n          const cEnd = rawCss.indexOf('*/', cur + 2);\n          cur = cEnd === -1 ? rawCss.length : cEnd + 2;\n          continue;\n        }\n        if (rawCss[cur] === '{') depth++;\n        else if (rawCss[cur] === '}') depth--;\n        cur++;\n      }\n      const body = rawCss.substring(openBrace, cur).trim();\n      pos = cur;\n\n      if (selector) {\n        if (ruleIsUsed(selector)) {\n          cleaned.push(selector + ' ' + body);\n          keptCount++;\n        } else {\n          removedCount++;\n          removedList.push(selector);\n        }\n      }\n    }\n\n    return {\n      cleanCss: cleaned.join('\\n\\n'),\n      kept: keptCount,\n      removed: removedCount,\n      removedList: removedList\n    };\n  }\n\n  // ── Main Clean Execution ──\n  function runClean() {\n    let rawCss  = cssIn.value.trim();\n    const rawHtml = htmlIn.value.trim();\n\n    if (!rawHtml && !rawCss) {\n      showToast('Please enter your code first');\n      return;\n    }\n\n    if (!rawCss && rawHtml) {\n      const extracted = extractEmbeddedCss(rawHtml);\n      if (extracted) {\n        rawCss = extracted;\n        cssIn.value = extracted;\n        countRules();\n        showToast('Extracted embedded <style> CSS!');\n      } else {\n        showToast('No CSS found. Paste CSS or provide HTML with <style> tags.');\n        return;\n      }\n    }\n\n    const doc = new DOMParser().parseFromString('<body>' + rawHtml + '</body>', 'text/html');\n    const result = purgeCssEngine(rawCss, doc);\n\n    cleanCssText = result.cleanCss;\n    if (cleanOut) {\n      cleanOut.value = cleanCssText || '/* All selectors are used — no unused CSS found */';\n    }\n\n    const origLen  = rawCss.length;\n    const cleanLen = cleanCssText.length;\n    const saved    = origLen > 0 ? Math.round((1 - cleanLen / origLen) * 100) : 0;\n\n    const sRem = document.getElementById('s-removed');\n    const sKep = document.getElementById('s-kept');\n    const sSav = document.getElementById('s-saved');\n\n    if (sRem) sRem.textContent = result.removed;\n    if (sKep) sKep.textContent = result.kept;\n    if (sSav) sSav.textContent = saved + '%';\n\n    const rl = document.getElementById('removed-list');\n    if (rl) {\n      if (result.removed === 0) {\n        rl.innerHTML = '<div class=\"cleaner-empty-state\">No unused CSS rules found. All selectors are present in your HTML.</div>';\n      } else {\n        rl.innerHTML =\n          '<div class=\"cleaner-removed-header\"><svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/></svg>' + result.removed + ' unused rules removed</div>' +\n          result.removedList.map(function (r) {\n            return '<div class=\"cleaner-removed-item\"><svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>' + r + '</div>';\n          }).join('');\n      }\n    }\n\n    if (outputSec) outputSec.style.display = 'flex';\n    showToast('CSS Cleaned successfully!');\n  }\n\n  if (cleanBtn) cleanBtn.addEventListener('click', runClean);\n\n  if (copyBtn) {\n    copyBtn.addEventListener('click', function () {\n      if (!cleanCssText) { showToast('Clean CSS first'); return; }\n      navigator.clipboard.writeText(cleanCssText).then(function () {\n        showToast('Clean CSS copied to clipboard!');\n      });\n    });\n  }\n\n  if (downloadBtn) {\n    downloadBtn.addEventListener('click', function () {\n      if (!cleanCssText) { showToast('Clean CSS first'); return; }\n      const a = document.createElement('a');\n      a.href = URL.createObjectURL(new Blob([cleanCssText], { type: 'text/css' }));\n      a.download = 'cleaned-styles.css';\n      a.click();\n      showToast('Downloading cleaned-styles.css...');\n    });\n  }\n\n  if (demoBtn) {\n    demoBtn.addEventListener('click', function () {\n      cssIn.value = `.container { max-width: 800px; margin: 0 auto; }\nh1 { font-size: 2rem; color: #333; }\n.btn { padding: 8px 16px; background: #6366f1; color: white; border-radius: 6px; }\n.ghost-class { display: none; }\n.unused-card { border: 1px solid red; }\n#sidebar { width: 240px; }\np { line-height: 1.6; }\n.old-nav { background: gray; }`;\n\n      htmlIn.value = `<div class=\"container\">\n  <h1>Sample Web Page</h1>\n  <p>Clean dead and unused styles automatically.</p>\n  <button class=\"btn\">Click Me</button>\n</div>`;\n\n      countRules();\n      countEls();\n      runClean();\n    });\n  }\n\n  if (demoMixedBtn) {\n    demoMixedBtn.addEventListener('click', function () {\n      cssIn.value = '';\n      htmlIn.value = `<!DOCTYPE html>\n<html>\n<head>\n  <style>\n    .card { padding: 20px; border: 1px solid #ccc; border-radius: 8px; }\n    .card-title { font-size: 1.5rem; font-weight: bold; }\n    .btn-action { background: green; color: white; padding: 10px; }\n    .unused-modal { position: fixed; top: 0; width: 100%; }\n    .carousel-slider { display: flex; }\n  </style>\n</head>\n<body>\n  <div class=\"card\">\n    <h2 class=\"card-title\">Single File Mixed Demo</h2>\n    <button class=\"btn-action\">Submit</button>\n  </div>\n</body>\n</html>`;\n\n      countRules();\n      countEls();\n      runClean();\n    });\n  }\n\n  if (resetBtn) {\n    resetBtn.addEventListener('click', function () {\n      cssIn.value = '';\n      htmlIn.value = '';\n      cleanCssText = '';\n      countRules();\n      countEls();\n      if (outputSec) outputSec.style.display = 'none';\n      showToast('Reset complete');\n    });\n  }\n\n})();",
        "cdnScripts": ""
      },
      "faqs": [
        {
          "q": "How to clean unused CSS online?",
          "a": "To clean unused CSS online, paste your stylesheet into the CSS editor and your HTML markup into the HTML box. Click 'Clean CSS' — our browser engine scans your DOM tree and strips all dead, unreferenced selectors in milliseconds."
        },
        {
          "q": "Does removing unused CSS improve Google PageSpeed score?",
          "a": "Yes. Eliminating unused CSS reduces render-blocking resources, improves First Contentful Paint (FCP), lowers Total Blocking Time (TBT), and directly boosts your Google Lighthouse Core Web Vitals score."
        },
        {
          "q": "Is this online PurgeCSS tool safe and private?",
          "a": "Yes. All CSS parsing and dead code stripping execute 100% locally in your web browser memory. Your source code and stylesheets are never uploaded or stored on any remote server."
        },
        {
          "q": "Will this tool delete CSS animations (@keyframes) and :hover states?",
          "a": "No. Our intelligent parser safely preserves @keyframes animations, CSS variables (:root), @font-face rules, and pseudo-classes (:hover, :focus) while removing only unrendered, dead selectors."
        },
        {
          "q": "How do I find unused CSS in a single HTML file?",
          "a": "Simply paste your full HTML document containing embedded <style> tags into the HTML box. The tool automatically detects, extracts, and purges the unused CSS without requiring separate files."
        }
      ]
    },
    {
      "id": "unused-js-remover-1606",
      "name": "Unused JS Remover",
      "category": "Developer",
      "slug": "unused-js-remover",
      "badge": "Popular",
      "iconSvg": "<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n  <rect width=\"24\" height=\"24\" rx=\"5\" fill=\"#F7DF1E\"/>\n  <path d=\"M6 16.5C6.7 17.5 7.8 18 9 18C10.7 18 11.5 17 11.5 15.2V7.5H9.5V15.1C9.5 15.9 9.1 16.3 8.5 16.3C8 16.3 7.6 16 7.2 15.5L6 16.5ZM13.8 16.3C14.6 17.4 15.8 18 17.2 18C19.3 18 20.5 16.8 20.5 15C20.5 13.3 19.3 12.5 17.5 11.8C16.3 11.3 15.7 10.9 15.7 10.2C15.7 9.5 16.3 9 17.2 9C18 9 18.7 9.4 19.2 10.1L20.4 8.9C19.6 7.9 18.5 7.4 17.2 7.4C15.2 7.4 14.1 8.6 14.1 10.3C14.1 12 15.3 12.8 17 13.5C18.2 14 18.9 14.5 18.9 15.2C18.9 16 18.1 16.4 17.1 16.4C16.1 16.4 15.2 15.8 14.7 14.9L13.8 16.3Z\" fill=\"#000000\"/>\n</svg>",
      "active": true,
      "usageCount": 16,
      "seo": {
        "targetKeyword": "reduce unused javascript online free",
        "metaTitle": "Reduce Unused JavaScript Online Free - Dead Code Remover & Checker",
        "metaDescription": "Reduce unused JavaScript online for free. Check and remove dead code, uncalled functions, and optimize script evaluation time to boost Google PageSpeed instantly.",
        "canonical": "https://tools.heyswan.cloud/tool/unused-js-remover",
        "ogImage": "",
        "indexable": true
      },
      "content": {
        "about": "Unused JavaScript significantly increases script evaluation time, delays main-thread processing, and causes poor Total Blocking Time (TBT) on Google PageSpeed Insights and Lighthouse reports. \n\nOur Free Online Unused JavaScript Checker and Remover scans your scripts against your HTML DOM tree and event handlers directly in your browser. It automatically detects and strips dead functions, uncalled arrow handlers, orphan variables, and unused ES6 classes. Everything executes 100% locally in your browser memory with zero server latency, reducing your bundle size and accelerating script evaluation time safely.",
        "howTo": "1. Paste your JavaScript code or upload your .js script into the left editor pane.\n2. Paste your HTML markup with event listeners (onclick, onchange) or inline script tags into the right editor.\n3. Click 'Clean JavaScript' to detect unreferenced functions, classes, and dead variables.\n4. Review the list of stripped dead items, then Copy or Download your clean, optimized .js file."
      },
      "engine": {
        "html": "<div class=\"js-cleaner-wrap\">\n  \n  <!-- Editors Grid -->\n  <div class=\"js-cleaner-grid\">\n\n    <!-- JS Input Pane -->\n    <div class=\"js-pane\">\n      <div class=\"js-pane-top\">\n        <span class=\"js-pane-title\">\n          <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><polyline points=\"16 18 22 12 16 6\"/><polyline points=\"8 6 2 12 8 18\"/></svg>\n          JavaScript Code\n        </span>\n        <div class=\"js-pane-actions\">\n          <span class=\"js-count-tag\" id=\"js-count\">0 items</span>\n          <label class=\"js-ic-btn\" title=\"Upload .js file\">\n            <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"/></svg>\n            <span>Upload</span>\n            <input type=\"file\" id=\"jsFileInput\" accept=\".js\" style=\"display:none\">\n          </label>\n          <button type=\"button\" class=\"js-ic-btn\" id=\"clearJsBtn\" aria-label=\"Clear JavaScript\">\n            <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>\n          </button>\n        </div>\n      </div>\n      <textarea id=\"js-in\" class=\"js-textarea\" placeholder=\"Paste JavaScript code here, or leave blank if your HTML contains embedded <script> tags...\" spellcheck=\"false\"></textarea>\n    </div>\n\n    <!-- HTML Input Pane -->\n    <div class=\"js-pane\">\n      <div class=\"js-pane-top\">\n        <span class=\"js-pane-title\">\n          <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/></svg>\n          HTML Markup (or Single-File HTML)\n        </span>\n        <div class=\"js-pane-actions\">\n          <span class=\"js-count-tag\" id=\"html-count\">0 refs</span>\n          <label class=\"js-ic-btn\" title=\"Upload .html file\">\n            <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"/></svg>\n            <span>Upload</span>\n            <input type=\"file\" id=\"htmlFileInput\" accept=\".html,.htm,.php,.vue\" style=\"display:none\">\n          </label>\n          <button type=\"button\" class=\"js-ic-btn\" id=\"clearHtmlBtn\" aria-label=\"Clear HTML\">\n            <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>\n          </button>\n        </div>\n      </div>\n      <textarea id=\"html-in\" class=\"js-textarea\" placeholder=\"Paste HTML markup with event listeners (onclick, onchange) or inline <script> calls...\" spellcheck=\"false\"></textarea>\n    </div>\n\n  </div>\n\n  <!-- Toolbar Controls -->\n  <div class=\"js-btn-row\">\n    <button type=\"button\" class=\"btn-action btn-primary\" id=\"cleanJsBtn\">\n      <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg>\n      <span>Clean JavaScript</span>\n    </button>\n    \n    <button type=\"button\" class=\"btn-action\" id=\"demoSplitBtn\" title=\"Load sample JavaScript & HTML\">\n      <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M4 19.5A2.5 2.5 0 0 1 6.5 17H20\"/><path d=\"M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z\"/></svg>\n      <span>Split Demo</span>\n    </button>\n\n    <button type=\"button\" class=\"btn-action\" id=\"demoMixedJsBtn\" title=\"Load single HTML with embedded <script>\">\n      <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/><line x1=\"9\" y1=\"21\" x2=\"9\" y2=\"9\"/></svg>\n      <span>Single-File HTML Demo</span>\n    </button>\n\n    <button type=\"button\" class=\"btn-action\" id=\"resetJsBtn\">\n      <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/></svg>\n      <span>Reset</span>\n    </button>\n  </div>\n\n  <!-- Output Results Section -->\n  <div id=\"js-output-section\" class=\"js-output-section\" style=\"display:none;\">\n\n    <div class=\"js-stats-row\">\n      <div class=\"js-stat\">\n        <div class=\"js-stat-n removed-n\" id=\"s-removed\">0</div>\n        <div class=\"js-stat-l\">items removed</div>\n      </div>\n      <div class=\"js-stat\">\n        <div class=\"js-stat-n kept-n\" id=\"s-kept\">0</div>\n        <div class=\"js-stat-l\">items kept</div>\n      </div>\n      <div class=\"js-stat\">\n        <div class=\"js-stat-n saved-n\" id=\"s-saved\">0%</div>\n        <div class=\"js-stat-l\">size reduction</div>\n      </div>\n    </div>\n\n    <div class=\"js-output-pane\">\n      <div class=\"js-output-top\">\n        <div class=\"js-output-label\">\n          <span class=\"js-green-dot\"></span>\n          <span>Clean JavaScript Result</span>\n        </div>\n        <div class=\"js-output-btns\">\n          <button type=\"button\" class=\"js-ic-btn\" id=\"copyCleanJsBtn\">\n            <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg>\n            <span>Copy Code</span>\n          </button>\n          <button type=\"button\" class=\"js-ic-btn\" id=\"downloadCleanJsBtn\">\n            <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/></svg>\n            <span>Download .js</span>\n          </button>\n        </div>\n      </div>\n      <textarea id=\"js-out\" class=\"js-clean-textarea\" readonly spellcheck=\"false\"></textarea>\n      <div class=\"js-removed-list\" id=\"js-removed-list\"></div>\n    </div>\n\n  </div>\n\n</div>\n\n<div id=\"js-cleaner-toast\" class=\"js-cleaner-toast\">Done</div>",
        "css": ".js-cleaner-wrap {\n  display: flex;\n  flex-direction: column;\n  gap: 14px;\n  width: 100%;\n}\n\n.js-cleaner-grid {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 14px;\n}\n@media (max-width: 768px) {\n  .js-cleaner-grid { grid-template-columns: 1fr; }\n}\n\n.js-pane {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 10px;\n  overflow: hidden;\n  display: flex;\n  flex-direction: column;\n}\n\n.js-pane-top {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 8px 12px;\n  border-bottom: 1px solid var(--border);\n  background: var(--surface-hover);\n}\n\n.js-pane-title {\n  font-size: 0.8rem;\n  font-weight: 700;\n  color: var(--text);\n  display: flex;\n  align-items: center;\n  gap: 6px;\n}\n.js-pane-title svg { color: var(--primary); }\n\n.js-pane-actions {\n  display: flex;\n  gap: 6px;\n  align-items: center;\n}\n\n.js-count-tag {\n  font-size: 0.72rem;\n  color: var(--text-muted);\n  font-variant-numeric: tabular-nums;\n}\n\n.js-ic-btn {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 6px;\n  padding: 4px 8px;\n  cursor: pointer;\n  font-size: 0.76rem;\n  font-weight: 600;\n  color: var(--text-muted);\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n  transition: all 0.15s ease;\n  font-family: inherit;\n}\n.js-ic-btn:hover {\n  background: var(--surface-hover);\n  color: var(--text);\n  border-color: var(--primary);\n}\n\n.js-textarea {\n  width: 100%;\n  height: 230px;\n  background: transparent;\n  border: none;\n  outline: none;\n  padding: 12px;\n  font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;\n  font-size: 0.85rem;\n  color: var(--text);\n  resize: vertical;\n  line-height: 1.6;\n}\n\n.js-btn-row {\n  display: flex;\n  gap: 8px;\n  flex-wrap: wrap;\n}\n\n/* Stats Section */\n.js-output-section {\n  display: flex;\n  flex-direction: column;\n  gap: 12px;\n  margin-top: 4px;\n}\n\n.js-stats-row {\n  display: grid;\n  grid-template-columns: repeat(3, 1fr);\n  gap: 10px;\n}\n\n.js-stat {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 8px;\n  padding: 10px 12px;\n  text-align: center;\n}\n.js-stat-n {\n  font-size: 1.5rem;\n  font-weight: 800;\n  line-height: 1.2;\n}\n.js-stat-l {\n  font-size: 0.72rem;\n  color: var(--text-muted);\n  text-transform: uppercase;\n  font-weight: 700;\n  margin-top: 2px;\n}\n.removed-n { color: #ef4444; }\n.kept-n    { color: #10b981; }\n.saved-n   { color: var(--primary); }\n\n/* Output Pane */\n.js-output-pane {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 10px;\n  overflow: hidden;\n}\n\n.js-output-top {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 8px 12px;\n  border-bottom: 1px solid var(--border);\n  background: var(--surface-hover);\n}\n\n.js-output-label {\n  font-size: 0.8rem;\n  font-weight: 700;\n  color: var(--text);\n  display: flex;\n  align-items: center;\n  gap: 7px;\n}\n\n.js-green-dot {\n  width: 8px;\n  height: 8px;\n  border-radius: 50%;\n  background: #10b981;\n  box-shadow: 0 0 8px #10b981;\n  flex-shrink: 0;\n}\n\n.js-output-btns { display: flex; gap: 6px; }\n\n.js-clean-textarea {\n  width: 100%;\n  height: 230px;\n  background: transparent;\n  border: none;\n  outline: none;\n  padding: 12px;\n  font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;\n  font-size: 0.85rem;\n  color: var(--text);\n  resize: vertical;\n  line-height: 1.6;\n}\n\n.js-removed-list {\n  border-top: 1px solid var(--border);\n}\n\n.js-removed-header {\n  padding: 8px 12px;\n  font-size: 0.76rem;\n  font-weight: 700;\n  color: #ef4444;\n  border-bottom: 1px solid var(--border);\n  background: rgba(239, 68, 68, 0.1);\n  display: flex;\n  align-items: center;\n  gap: 6px;\n}\n\n.js-removed-item {\n  padding: 7px 12px;\n  font-size: 0.8rem;\n  font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;\n  border-bottom: 1px solid var(--border);\n  color: var(--text-muted);\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n.js-removed-item:last-child { border-bottom: none; }\n.js-removed-item svg { color: #ef4444; flex-shrink: 0; }\n\n.js-type-badge {\n  font-size: 0.7rem;\n  padding: 1px 6px;\n  border-radius: 4px;\n  background: var(--surface-hover);\n  color: var(--text-dim);\n  font-family: system-ui, sans-serif;\n  margin-left: auto;\n}\n\n.js-empty-state {\n  padding: 1.25rem;\n  text-align: center;\n  font-size: 0.84rem;\n  color: var(--text-muted);\n}\n\n.js-cleaner-toast {\n  position: fixed;\n  bottom: 24px;\n  left: 50%;\n  transform: translateX(-50%);\n  background: var(--surface-hover);\n  border: 1px solid var(--border);\n  border-radius: 8px;\n  font-size: 0.84rem;\n  font-weight: 600;\n  padding: 8px 18px;\n  color: var(--text);\n  white-space: nowrap;\n  opacity: 0;\n  transition: opacity 0.25s ease;\n  pointer-events: none;\n  z-index: 9999;\n  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);\n}",
        "js": "(function () {\n  'use strict';\n\n  // DOM Elements\n  const jsIn        = document.getElementById('js-in');\n  const htmlIn      = document.getElementById('html-in');\n  const jsOut       = document.getElementById('js-out');\n  const jsCount     = document.getElementById('js-count');\n  const htmlCount   = document.getElementById('html-count');\n  const jsFile      = document.getElementById('jsFileInput');\n  const htmlFile    = document.getElementById('htmlFileInput');\n  const clearJs     = document.getElementById('clearJsBtn');\n  const clearHtml   = document.getElementById('clearHtmlBtn');\n  const cleanBtn    = document.getElementById('cleanJsBtn');\n  const demoSplitBtn= document.getElementById('demoSplitBtn');\n  const demoMixedBtn= document.getElementById('demoMixedJsBtn');\n  const resetBtn    = document.getElementById('resetJsBtn');\n  const copyBtn     = document.getElementById('copyCleanJsBtn');\n  const downloadBtn = document.getElementById('downloadCleanJsBtn');\n  const outputSec   = document.getElementById('js-output-section');\n  const toast       = document.getElementById('js-cleaner-toast');\n\n  let cleanJsResult = '';\n\n  function showToast(msg) {\n    if (!toast) return;\n    toast.textContent = msg;\n    toast.style.opacity = '1';\n    setTimeout(function () { toast.style.opacity = '0'; }, 1800);\n  }\n\n  // ── 1. Comprehensive Identifier Extractor ──\n  function extractJsItems(js) {\n    const items = [];\n    const seen = new Set();\n    if (!js) return items;\n\n    const patterns = [\n      { re: /(?:async\\s+)?function\\s+([a-zA-Z0-9_$]+)\\s*\\(/g, type: 'function' },\n      { re: /(?:var|let|const)\\s+([a-zA-Z0-9_$]+)\\s*=\\s*(?:async\\s*)?(?:\\([^)]*\\)|[a-zA-Z0-9_$]+)\\s*=>/g, type: 'arrow' },\n      { re: /(?:var|let|const)\\s+([a-zA-Z0-9_$]+)\\s*=\\s*(?:async\\s*)?function/g, type: 'function' },\n      { re: /(?:var|let|const)\\s+([a-zA-Z0-9_$]+)\\s*(?:=|;)/g, type: 'variable' },\n      { re: /class\\s+([a-zA-Z0-9_$]+)/g, type: 'class' }\n    ];\n\n    patterns.forEach(function (p) {\n      let m;\n      while ((m = p.re.exec(js)) !== null) {\n        const name = m[1];\n        if (name && !seen.has(name)) {\n          seen.add(name);\n          items.push({ name: name, type: p.type });\n        }\n      }\n    });\n\n    return items;\n  }\n\n  function countJs() {\n    if (!jsIn || !jsCount) return;\n    const items = extractJsItems(jsIn.value);\n    jsCount.textContent = items.length + ' item' + (items.length !== 1 ? 's' : '');\n  }\n\n  function countHtml() {\n    if (!htmlIn || !htmlCount) return;\n    const m = htmlIn.value.match(/<[a-z][^>]*>/gi);\n    htmlCount.textContent = (m ? m.length : 0) + ' ref' + ((m ? m.length : 0) !== 1 ? 's' : '');\n  }\n\n  // ── File Upload Handlers ──\n  function handleFileUpload(inputEl, targetTextArea, counterFn) {\n    if (!inputEl) return;\n    inputEl.addEventListener('change', function () {\n      const f = this.files[0];\n      if (!f) return;\n      const r = new FileReader();\n      r.onload = function (e) {\n        targetTextArea.value = e.target.result;\n        counterFn();\n        showToast('Loaded: ' + f.name);\n      };\n      r.readAsText(f);\n      this.value = '';\n    });\n  }\n\n  handleFileUpload(jsFile, jsIn, countJs);\n  handleFileUpload(htmlFile, htmlIn, countHtml);\n\n  if (clearJs && jsIn) {\n    clearJs.addEventListener('click', function () { jsIn.value = ''; countJs(); });\n  }\n  if (clearHtml && htmlIn) {\n    clearHtml.addEventListener('click', function () { htmlIn.value = ''; countHtml(); });\n  }\n\n  if (jsIn) jsIn.addEventListener('input', countJs);\n  if (htmlIn) htmlIn.addEventListener('input', countHtml);\n\n  // ── Word Boundary Usage Checkers ──\n  function isUsedInHtml(name, html) {\n    if (!html || !name) return false;\n    const esc = name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n    const regex = new RegExp('\\\\b' + esc + '\\\\b');\n    return regex.test(html);\n  }\n\n  function isUsedElsewhereInJs(name, js) {\n    if (!js || !name) return false;\n    const esc = name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n    const regex = new RegExp('\\\\b' + esc + '\\\\b', 'g');\n    const matches = js.match(regex);\n    return matches && matches.length > 1;\n  }\n\n  // ── 2. Robust Multi-Line Block Stripper ──\n  function removeBlock(code, name) {\n    const esc = name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n    \n    // Check for declarations with opening brace (Functions, Classes, Arrow Functions with body)\n    const blockRegex = new RegExp('(?:(?:async\\\\s+)?function\\\\s+' + esc + '|class\\\\s+' + esc + '|(?:var|let|const)\\\\s+' + esc + '\\\\s*=\\\\s*(?:async\\\\s*)?(?:\\\\([^)]*\\\\)|[a-zA-Z0-9_$]+)?\\\\s*=>|(?:var|let|const)\\\\s+' + esc + '\\\\s*=\\\\s*(?:async\\\\s*)?function)', 'g');\n    \n    let match;\n    while ((match = blockRegex.exec(code)) !== null) {\n      const startIdx = match.index;\n      const rest = code.substring(startIdx);\n      const braceIdx = rest.indexOf('{');\n      const semicolonIdx = rest.indexOf(';');\n      \n      // If it's a block with { ... }\n      if (braceIdx !== -1 && (semicolonIdx === -1 || braceIdx < semicolonIdx)) {\n        let openBraces = 0;\n        let endIdx = -1;\n        const searchFrom = startIdx + braceIdx;\n        \n        for (let i = searchFrom; i < code.length; i++) {\n          if (code[i] === '{') openBraces++;\n          else if (code[i] === '}') {\n            openBraces--;\n            if (openBraces === 0) {\n              endIdx = i + 1;\n              // Check if followed by semicolon\n              if (code[endIdx] === ';') endIdx++;\n              break;\n            }\n          }\n        }\n        \n        if (endIdx !== -1) {\n          code = code.substring(0, startIdx) + code.substring(endIdx);\n          blockRegex.lastIndex = 0; // restart search\n          continue;\n        }\n      }\n    }\n\n    // Single-line variables or flat arrow expressions (e.g. const x = 42; or const x = () => 5;)\n    const flatVarRegex = new RegExp('(?:var|let|const)\\\\s+' + esc + '\\\\s*=[^;\\\\n]*;?', 'g');\n    code = code.replace(flatVarRegex, '');\n\n    return code;\n  }\n\n  // ── Single-File HTML Embedded Script Extractor ──\n  function extractEmbeddedJs(html) {\n    if (!html) return '';\n    const scriptRegex = new RegExp('<script(?![^>]*\\\\bsrc\\\\b)[^>]*>([\\\\s\\\\S]*?)<\\\\/script>', 'gi');\n    const matches = html.match(scriptRegex);\n    if (!matches) return '';\n    return matches.map(function (tag) {\n      return tag.replace(/<script[^>]*>/gi, '').replace(/<\\/script>/gi, '').trim();\n    }).join('\\n\\n');\n  }\n\n  // ── 3. Main Cleaning Logic ──\n  function runClean() {\n    let rawJs = jsIn.value.trim();\n    const rawHtml = htmlIn.value.trim();\n\n    if (!rawJs && !rawHtml) {\n      showToast('Please enter your code first');\n      return;\n    }\n\n    if (!rawJs && rawHtml) {\n      const extracted = extractEmbeddedJs(rawHtml);\n      if (extracted) {\n        rawJs = extracted;\n        jsIn.value = extracted;\n        countJs();\n        showToast('Extracted embedded scripts!');\n      } else {\n        showToast('No JavaScript found in HTML');\n        return;\n      }\n    }\n\n    const items = extractJsItems(rawJs);\n    if (!items.length) {\n      showToast('No functions or variables detected');\n      return;\n    }\n\n    const removed = [];\n    const kept = [];\n    let cleaned = rawJs;\n\n    items.forEach(function (item) {\n      const inHtml = isUsedInHtml(item.name, rawHtml);\n      const inJs = isUsedElsewhereInJs(item.name, rawJs);\n\n      if (!inHtml && !inJs) {\n        removed.push(item);\n      } else {\n        kept.push(item);\n      }\n    });\n\n    removed.forEach(function (item) {\n      cleaned = removeBlock(cleaned, item.name);\n    });\n\n    cleaned = cleaned.replace(/\\n{3,}/g, '\\n\\n').trim();\n    cleanJsResult = cleaned;\n\n    if (jsOut) {\n      jsOut.value = cleanJsResult || '// All items are in use — nothing removed.';\n    }\n\n    const origLen = rawJs.length;\n    const cleanLen = cleanJsResult.length;\n    const saved = origLen > 0 ? Math.round((1 - cleanLen / origLen) * 100) : 0;\n\n    const sRem = document.getElementById('s-removed');\n    const sKep = document.getElementById('s-kept');\n    const sSav = document.getElementById('s-saved');\n\n    if (sRem) sRem.textContent = removed.length;\n    if (sKep) sKep.textContent = kept.length;\n    if (sSav) sSav.textContent = saved + '%';\n\n    const rl = document.getElementById('js-removed-list');\n    if (rl) {\n      if (!removed.length) {\n        rl.innerHTML = '<div class=\"js-empty-state\">No unused items found. All functions and variables are actively referenced.</div>';\n      } else {\n        rl.innerHTML =\n          '<div class=\"js-removed-header\"><svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/></svg>' + removed.length + ' dead items stripped</div>' +\n          removed.map(function (r) {\n            return '<div class=\"js-removed-item\">' +\n              '<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>' +\n              '<span>' + r.name + '</span>' +\n              '<span class=\"js-type-badge\">' + r.type + '</span>' +\n              '</div>';\n          }).join('');\n      }\n    }\n\n    if (outputSec) outputSec.style.display = 'flex';\n    showToast('JavaScript cleaned successfully!');\n  }\n\n  if (cleanBtn) cleanBtn.addEventListener('click', runClean);\n\n  if (copyBtn) {\n    copyBtn.addEventListener('click', function () {\n      if (!cleanJsResult) { showToast('Clean JavaScript first'); return; }\n      navigator.clipboard.writeText(cleanJsResult).then(function () {\n        showToast('Clean JS copied to clipboard!');\n      });\n    });\n  }\n\n  if (downloadBtn) {\n    downloadBtn.addEventListener('click', function () {\n      if (!cleanJsResult) { showToast('Clean JavaScript first'); return; }\n      const a = document.createElement('a');\n      a.href = URL.createObjectURL(new Blob([cleanJsResult], { type: 'text/javascript' }));\n      a.download = 'cleaned-script.js';\n      a.click();\n      showToast('Downloading cleaned-script.js...');\n    });\n  }\n\n  // Split Demo Data\n  if (demoSplitBtn) {\n    demoSplitBtn.addEventListener('click', function () {\n      jsIn.value = 'function showMenu() {\\n  document.getElementById(\"menu\").style.display = \"block\";\\n}\\n\\nfunction oldPopup() {\\n  alert(\"Nobody calls me!\");\\n}\\n\\nfunction deprecatedHelper() {\\n  return \"dead legacy code\";\\n}\\n\\nvar unusedToken = 42;\\nvar clickCount = 0;\\n\\nfunction updateCount(n) {\\n  clickCount = n;\\n  document.getElementById(\"count\").innerText = n;\\n}\\n\\nconst unusedArrowHandler = () => {\\n  console.log(\"Uncalled arrow\");\\n};';\n\n      htmlIn.value = '<button onclick=\"showMenu()\">Open Menu</button>\\n<div id=\"menu\" style=\"display:none\">Navigation Drawer</div>\\n<span id=\"count\">0</span>\\n<' + 'script>\\n  updateCount(5);\\n<' + '/script>';\n\n      countJs();\n      countHtml();\n      runClean();\n    });\n  }\n\n  // Single-File HTML Demo Data\n  if (demoMixedBtn) {\n    demoMixedBtn.addEventListener('click', function () {\n      jsIn.value = '';\n      htmlIn.value = '<!DOCTYPE html>\\n<html>\\n<head>\\n  <title>Single-File Embedded Demo</title>\\n</head>\\n<body>\\n  <h1>Interactive Widget</h1>\\n  <button onclick=\"toggleModal()\">Open Modal</button>\\n\\n  <' + 'script>\\n    function toggleModal() {\\n      console.log(\"Modal opened\");\\n    }\\n\\n    function deadUnusedCalculation(a, b) {\\n      return a * b * 99;\\n    }\\n\\n    var unusedApiSecret = \"12345\";\\n  <' + '/script>\\n</body>\\n</html>';\n\n      countJs();\n      countHtml();\n      runClean();\n    });\n  }\n\n  if (resetBtn) {\n    resetBtn.addEventListener('click', function () {\n      jsIn.value = '';\n      htmlIn.value = '';\n      cleanJsResult = '';\n      countJs();\n      countHtml();\n      if (outputSec) outputSec.style.display = 'none';\n      showToast('Reset done');\n    });\n  }\n\n})();",
        "cdnScripts": ""
      },
      "faqs": [
        {
          "q": "How to reduce unused JavaScript online for free?",
          "a": "To reduce unused JavaScript online for free, paste your script in the left editor and your HTML markup in the right editor. Click 'Clean JavaScript' — our in-browser checker scans for uncalled functions and removes dead code in milliseconds."
        },
        {
          "q": "How does removing unused JavaScript reduce script evaluation time?",
          "a": "Removing unused code reduces total byte size, allowing the browser's V8 engine to parse and compile only necessary instructions, drastically reducing script evaluation time and main-thread execution delays."
        },
        {
          "q": "What is the difference between minifying and removing unused JavaScript?",
          "a": "Minification only compresses whitespace and variable names. Removing unused JavaScript completely strips dead functions and variables that are never executed by your webpage, saving significantly more memory."
        },
        {
          "q": "Will this tool break my active scripts or functions?",
          "a": "No. If a function is called inside your HTML markup, event attributes (onclick, onchange), or referenced within other JavaScript functions, our parser safely preserves it and only strips completely unreferenced code."
        },
        {
          "q": "Can I check and clean a single HTML file with embedded <script> tags?",
          "a": "Yes. Simply paste your complete HTML document containing embedded <script> tags into the HTML box with the JS box left empty. The tool automatically detects, checks, and cleans the embedded scripts."
        }
      ]
    }
  ],
  "landingPage": {
    "heroBadge": "⚡ 100% Free & Client-Side Secure",
    "heroTitle": "Smarter Online Tools for",
    "heroSubtitle": "Blazing-fast utilities built with privacy, no installations, and zero data tracking.",
    "searchPlaceholder": "Search tools...",
    "announcement": {
      "enabled": false,
      "text": "Try Image Compressor tool",
      "link": "/tool/image"
    },
    "seo": {
      "metaTitle": "HeySwan Tools Cloud - 50+ Free Online Web & Developer Tools",
      "metaDescription": "Free, lightning-fast, and secure client-side utility tools for developers, designers, and creators.",
      "keywords": "online tools, developer tools, image compressor, word counter, password generator, web utilities",
      "favicon": "https://tools.heyswan.cloud/public/favicon.ico",
      "appleTouchIcon": "https://tools.heyswan.cloud/public/favicon.ico",
      "gaId": "",
      "gscTag": "<meta name=\"google-site-verification\" content=\"YGxpyV_LYhLyQGpHefIBgsgADU64YeyVkHIF-T7qKr4\" />",
      "ogImage": "https://tools.heyswan.cloud/public/banner.png"
    },
    "ads": {
      "headerAd": "",
      "gridAd": "",
      "footerAd": ""
    },
    "footer": {
      "aboutText": "The fastest utility hub on the web. 100% free with strict zero data tracking.",
      "github": "https://github.com/somi",
      "twitter": "https://twitter.com/pubbidder",
      "discord": "https://discord.com",
      "linkedin": "https://linkedin.com",
      "copyright": "HeySwan Inc. All rights reserved."
    },
    "customCode": {
      "headerScripts": "",
      "footerScripts": ""
    }
  },
  "pages": {
    "privacy": {
      "heading": "Privacy Policy",
      "badge": "Zero Server Logs • 100% Private",
      "content": "<div class=\"highlight-box\">\n  <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z\"/></svg>\n  <div>\n    <strong>100% Client-Side Privacy Guarantee:</strong> All calculations, code cleaning, file processing, and text transformations execute strictly inside your local browser memory using native Web APIs. Your source code, files, and text are <strong>never uploaded, stored, or inspected</strong> on our servers.\n  </div>\n</div>\n\n<h2>1. Introduction</h2>\n<p>\n  Welcome to <strong>HeySwan Cloud</strong> (\"we,\" \"our,\" or \"us\"). We are committed to protecting your personal privacy while providing lightning-fast, secure online utility tools. This Privacy Policy outlines how our website operates, what minimal diagnostic information is collected, and how we safeguard your digital footprint.\n</p>\n\n<h2>2. Information We Do NOT Collect</h2>\n<p>\n  Unlike traditional web applications, our platform operates on a <strong>zero-server-processing</strong> architecture:\n</p>\n<ul>\n  <li><strong>No Source Code or Text Storage:</strong> When you use tools like our CSS Cleaner, JS Cleaner, Formatters, or Generators, your data is processed 100% locally in your browser's V8 engine. It never touches our backend database.</li>\n  <li><strong>No File Retention:</strong> Any file uploaded via our client file-readers is parsed in volatile device memory and discarded immediately upon closing or refreshing the browser tab.</li>\n  <li><strong>No Sensitive Personal Data:</strong> We do not ask for or store passwords, credit card numbers, or proprietary business logic.</li>\n</ul>\n\n<h2>3. Information We Collect Automatically</h2>\n<p>\n  To maintain system stability, optimize page load speeds, and prevent abuse, we collect standard non-personally identifiable diagnostic metrics:\n</p>\n<ul>\n  <li><strong>Device & Browser Information:</strong> Browser version, operating system, device screen resolution, and language settings.</li>\n  <li><strong>Usage Metrics:</strong> Aggregated tool interaction counts (e.g., total hits per tool) to prioritize performance enhancements.</li>\n  <li><strong>Analytics:</strong> We use Google Analytics (GA4) with IP anonymization enabled to analyze general traffic patterns and geographical distribution.</li>\n</ul>\n\n<h2>4. Cookies and Local Storage</h2>\n<p>\n  We use browser <code>localStorage</code> solely for functional user interface preferences:\n</p>\n<ul>\n  <li><strong>Theme Preferences:</strong> Storing your choice of Dark Mode or Light Mode.</li>\n  <li><strong>Draft Auto-Recovery:</strong> Temporarily keeping your active input in local memory to prevent data loss on accidental page reloads.</li>\n</ul>\n<p>\n  We do not use persistent tracking cookies or selling trackers across third-party websites.\n</p>\n\n<h2>5. Google AdSense & Third-Party Advertising</h2>\n<p>\n  We may display third-party advertisements served by Google AdSense to keep our tools 100% free for everyone:\n</p>\n<ul>\n  <li>Google, as a third-party vendor, uses cookies (including the DoubleClick DART cookie) to serve ads based on your visit to this site and other sites on the Internet.</li>\n  <li>Users may opt out of personalized advertising by visiting <a href=\"https://adssettings.google.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Google Ads Settings</a>.</li>\n  <li>You can also opt out of third-party vendor cookies for personalized advertising by visiting <a href=\"https://www.aboutads.info/choices/\" target=\"_blank\" rel=\"noopener noreferrer\">AboutAds.info</a>.</li>\n</ul>\n\n<h2>6. GDPR (General Data Protection Regulation) Rights</h2>\n<p>\n  If you are a resident of the European Economic Area (EEA), you possess full statutory rights under the GDPR. Because our platform does not store personal data, identity profiles, or user inputs, your data remains in your custody on your device at all times.\n</p>\n\n<h2>7. CCPA (California Consumer Privacy Act) Compliance</h2>\n<p>\n  Under the CCPA, California consumers have the right to request disclosure of data collection and prohibit the sale of personal information. <strong>We do not sell, rent, or trade your personal data to any third party.</strong>\n</p>\n\n<h2>8. Children's Online Privacy (COPPA)</h2>\n<p>\n  Our services are designed for general developer and creator audiences. We do not knowingly collect personal identifiable information from children under the age of 13.\n</p>\n\n<h2>9. Changes to This Privacy Policy</h2>\n<p>\n  We reserve the right to update this policy periodically to reflect platform enhancements or regulatory changes. Updates will be published on this page with a revised \"Last Updated\" timestamp.\n</p>\n\n<h2>10. Contact Us</h2>\n<p>\n  If you have any questions, suggestions, or concerns regarding our privacy practices, please contact our support team:\n</p>\n<ul>\n  <li><strong>Email:</strong> support@heyswan.cloud</li>\n  <li><strong>Website:</strong> <a href=\"https://tools.heyswan.cloud/contact\">https://tools.heyswan.cloud/contact</a></li>\n</ul>"
    },
    "terms": {
      "heading": "Terms of Service",
      "badge": "Usage Agreement",
      "content": "<div class=\"highlight-box\">\n  <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/></svg>\n  <div>\n    <strong>Terms Summary:</strong> HeySwan Cloud provides 100% free, client-side online utility tools. You retain full ownership of any code or content processed on our site. Our software is provided \"as is\" without commercial warranty.\n  </div>\n</div>\n\n<h2>1. Agreement to Terms</h2>\n<p>\n  By accessing or using <strong>HeySwan Cloud</strong> (located at <a href=\"https://tools.heyswan.cloud\">https://tools.heyswan.cloud</a>), you acknowledge that you have read, understood, and agreed to be legally bound by these Terms of Service. If you do not agree with any portion of these terms, you must discontinue using our services immediately.\n</p>\n\n<h2>2. Permitted Use and Intellectual Property</h2>\n<p>\n  Our platform provides in-browser developer utilities, formatters, and code cleaners for lawful personal, educational, and commercial purposes:\n</p>\n<ul>\n  <li><strong>Your Content Ownership:</strong> You retain 100% full copyright and intellectual property rights over any source code, text, scripts, or files you process through our tools. We claim zero ownership and do not store copies of your intellectual property.</li>\n  <li><strong>Platform Code:</strong> The visual interface, branding, and proprietary client-side JavaScript algorithms of HeySwan Cloud are protected by international intellectual property laws.</li>\n</ul>\n\n<h2>3. Prohibited Activities</h2>\n<p>When using our website, you agree that you will NOT:</p>\n<ul>\n  <li>Attempt to launch automated Denial-of-Service (DDoS) attacks, flood requests, or compromise the stability of our web servers.</li>\n  <li>Probe, scan, or attempt to bypass the authentication security of our administrative infrastructure.</li>\n  <li>Scrape, redistribute, or replicate our proprietary software tools for unauthorized resale without explicit written permission.</li>\n</ul>\n\n<h2>4. Disclaimer of Warranties</h2>\n<p>\n  All tools, calculators, code cleaners, and generators on this platform are provided on an <strong>\"AS IS\" and \"AS AVAILABLE\"</strong> basis. While our algorithms undergo rigorous accuracy testing, HeySwan Cloud makes no warranties (express or implied) regarding error-free compilation, uninterrupted availability, or specific fitness for a particular commercial outcome.\n</p>\n\n<h2>5. Limitation of Liability</h2>\n<p>\n  To the maximum extent permitted by applicable law, HeySwan Cloud and its creators shall not be liable for any direct, indirect, incidental, or consequential damages resulting from data corruption, system errors, or loss of profits arising out of your use of our tools. We recommend always maintaining local backups of your source code before automated cleaning or minification.\n</p>\n\n<h2>6. Third-Party Advertisements & External Links</h2>\n<p>\n  Our website may display third-party advertisements served by Google AdSense and link to external resources (such as GitHub or documentation). We do not control, endorse, or accept responsibility for the content, privacy practices, or goods offered by third-party advertisers.\n</p>\n\n<h2>7. Modifications to Service</h2>\n<p>\n  We reserve the right to add new tools, update existing software engines, or modify these Terms of Service at any time to reflect technical improvements or legal standards. Continued use of the website following published updates constitutes your acceptance of the revised terms.\n</p>\n\n<h2>8. Contact & Legal Inquiries</h2>\n<p>\n  For any legal clarifications or questions concerning these Terms, please contact our administrative team at:\n  <br><strong>Email:</strong> support@heyswan.cloud\n</p>"
    },
    "about": {
      "heading": "Built for Speed & Privacy",
      "subtitle": "Empowering developers, designers, and creators with instantaneous client-side web utilities.",
      "content": "<div class=\"highlight-box\">\n  <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg>\n  <div>\n    <strong>The HeySwan Mission:</strong> To provide web developers, creators, and digital professionals with the world's fastest, zero-server-latency utility tools that respect your digital privacy.\n  </div>\n</div>\n\n<h2>Why We Built HeySwan Cloud</h2>\n<p>\n  For years, web developers and digital creators have relied on online formatters, minifiers, and text tools that suffer from two major flaws: <strong>annoying server latency</strong> and <strong>serious privacy risks</strong>. Sending your proprietary source code, private configurations, or API payloads across remote backend servers exposes your sensitive data to logging and third-party leaks.\n</p>\n<p>\n  We built <strong>HeySwan Cloud</strong> to change that model forever. By leveraging modern browser V8 JavaScript compilation and WebAssembly standards, our entire tool suite executes <strong>100% locally inside your device memory</strong>. Your code never leaves your computer, and calculations happen in milliseconds.\n</p>\n\n<h2>Our Three Core Pillars</h2>\n<p>Everything we build at HeySwan Cloud is guided by three non-negotiable principles:</p>\n<ul>\n  <li><strong>Instant Speed (0ms Server Latency):</strong> By removing the round-trip server request cycle, our utilities process complex code, multi-line stylesheets, and large text files at native CPU speeds.</li>\n  <li><strong>Absolute Privacy & Sandboxing:</strong> Zero database storage, zero tracking logs, and zero telemetry inspection. Your intellectual property stays in your browser sandbox.</li>\n  <li><strong>Free & Accessible Forever:</strong> No paywalls, no mandatory account registrations, and no artificial character limits. Quality tools should be freely available to developers everywhere.</li>\n</ul>\n\n<h2>Who Is HeySwan Cloud For?</h2>\n<p>Our platform is engineered for a diverse range of modern digital professionals:</p>\n<ul>\n  <li><strong>Frontend & Full-Stack Developers:</strong> Cleaning dead CSS, formatting nested JSON trees, stripping unused JavaScript, and converting API payloads.</li>\n  <li><strong>SEO Specialists & Agencies:</strong> Eliminating render-blocking stylesheet bytes to score 100/100 on Google PageSpeed Insights and Core Web Vitals.</li>\n  <li><strong>Designers & Content Creators:</strong> Optimizing vector SVG graphics, manipulating typography, and generating clean social media formatting.</li>\n  <li><strong>Students & Software Learners:</strong> Experimenting with live code formatters and algorithm converters in a safe local environment.</li>\n</ul>\n\n<h2>Continuous Innovation</h2>\n<p>\n  We are constantly expanding our library with new micro-niche developer utilities, cryptographic tools, and media converters. If you have an idea for a tool that would simplify your daily workflow, we welcome your feedback through our <a href=\"/contact\" style=\"color: var(--primary); text-decoration: underline;\">Contact Support</a> page.\n</p>"
    },
    "contact": {
      "heading": "Get in Touch",
      "subtitle": "Have a suggestion, tool request, or bug report? We would love to hear from you.",
      "supportEmail": "support@heyswan.cloud",
      "content": "<p>\n  Have a question, technical feedback, bug report, or a suggestion for a new utility tool? The <strong>HeySwan Cloud</strong> engineering team is always ready to assist you.\n</p>\n\n<h2>How We Can Help</h2>\n<p>Feel free to reach out to us regarding any of the following:</p>\n<ul>\n  <li><strong>Tool Requests:</strong> Have an idea for a time-saving developer tool? Tell us what you need and we will build it.</li>\n  <li><strong>Bug Reports & Performance Feedback:</strong> Did a parser produce unexpected output or fail on a specific syntax edge-case? Let us know so our team can patch it immediately.</li>\n  <li><strong>Partnership & Advertising:</strong> Inquiries regarding sponsored tool integrations or custom enterprise utility implementations.</li>\n</ul>\n\n<h2>Direct Support Channels</h2>\n<p>\n  You can submit your message directly using the contact form below, or reach out directly to our dedicated support inbox:\n</p>\n<ul>\n  <li><strong>Support Email:</strong> <a href=\"mailto:support@heyswan.cloud\" style=\"color: var(--primary); text-decoration: underline;\">support@heyswan.cloud</a></li>\n  <li><strong>Response Time:</strong> We aim to review and respond to all developer queries within <strong>24 to 48 hours</strong>.</li>\n  <li><strong>Official Website:</strong> <a href=\"https://tools.heyswan.cloud\">https://tools.heyswan.cloud</a></li>\n</ul>"
    }
  }
}