{"id":773,"date":"2026-03-20T06:10:12","date_gmt":"2026-03-20T06:10:12","guid":{"rendered":"https:\/\/primetoolhub.com\/?page_id=773"},"modified":"2026-08-08T14:00:52","modified_gmt":"2026-08-08T14:00:52","slug":"universal-url-encoder-decoder","status":"publish","type":"page","link":"https:\/\/schoolict.net\/tools\/universal-url-encoder-decoder\/","title":{"rendered":"Universal URL Encoder &amp; Decoder"},"content":{"rendered":"\n<div class=\"pth-hero-section\">\n    <div class=\"pth-hero-content\">\n        <h2>Make Any String Web-Safe \u2014 Precise URL encoding and decoding for web developers and API integration.<\/h2>\n        <p>Securely encode and decode URLs and text completely offline. Features live text-to-URL conversion, RFC 3986 strict mode, and real-time byte size calculations.\n\nInstallation Guide<\/p>\n\n        <div id=\"pth-toc-placeholder\"><\/div>\n    <\/div>\n\n    <div class=\"pth-hero-image\">\n        <img decoding=\"async\" data-no-lazy=\"1\" width=\"450\" height=\"253\" src=\"https:\/\/schoolict.net\/tools\/wp-content\/uploads\/2026\/03\/url-encoder-decoder-1024x572.jpeg\" alt=\"url encoder decoder\">\n    <\/div>\n\n<\/div>\n\n\n\n<div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><h2>Table of Contents<\/h2><nav><ul><li><a href=\"#\ud83d\udd34-encode-uri-or-encode-uri-component-get-this-wrong-and-your-url-breaks\">\ud83d\udd34\u00a0encodeURI or encodeURIComponent? Get This Wrong and Your URL Breaks<\/a><ul><li><a href=\"#\ud83d\udfe2-and-the-two-nobody-mentions\">\ud83d\udfe2\u00a0And the Two Nobody Mentions<\/a><\/li><\/ul><\/li><li><a href=\"#\ud83d\udfe1-the-double-encoding-trap\">\ud83d\udfe1\u00a0The Double-Encoding Trap<\/a><\/li><li><a href=\"#\ud83d\udd34-the-domain-that-looks-exactly-like-another-domain\">\ud83d\udd34\u00a0The Domain That Looks Exactly Like Another Domain<\/a><ul><li><a href=\"#\ud83d\udfe2-the-other-disguises-it-catches\">\ud83d\udfe2\u00a0The Other Disguises It Catches<\/a><\/li><\/ul><\/li><li><a href=\"#\ud83d\udfe1-utm-tags-and-slugs-the-boring-details-that-matter\">\ud83d\udfe1\u00a0UTM Tags and Slugs: The Boring Details That Matter<\/a><\/li><\/ul><\/nav><\/div>\n\n\n<!-- LSCACHE_DISABLE -->\r\n<style>\r\n#pth-urls-wrapper{font-family:'Inter',system-ui,-apple-system,sans-serif;color:#1e293b;line-height:1.6;max-width:1400px;margin:0 auto;box-sizing:border-box;background:#f8fafc;padding:20px;border-radius:16px}\r\n#pth-urls-wrapper *{box-sizing:border-box;min-width:0}\r\n#pth-urls-wrapper .us-card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:22px;margin-bottom:20px;box-shadow:0 4px 6px -1px rgba(0,0,0,.04)}\r\n#pth-urls-wrapper h2.us-title{font-size:1.5rem;font-weight:800;color:#1d4ed8;margin:0 0 6px}\r\n#pth-urls-wrapper .us-sub{color:#334155;margin:0 0 12px;font-weight:500;font-size:.9rem}\r\n#pth-urls-wrapper .us-meta{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:14px}\r\n#pth-urls-wrapper .us-badge{font-size:.7rem;font-weight:800;padding:3px 10px;border-radius:20px}\r\n#pth-urls-wrapper .us-b-blue{background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe}\r\n#pth-urls-wrapper .us-b-green{background:#f0fdf4;color:#047857;border:1px solid #bbf7d0}\r\n#pth-urls-wrapper .us-toolbar{display:flex;gap:8px;flex-wrap:wrap}\r\n#pth-urls-wrapper .us-btn{border:none;padding:9px 18px;border-radius:8px;font-weight:700;cursor:pointer;font-size:.85rem;display:inline-flex;align-items:center;gap:6px;font-family:inherit;transition:.15s}\r\n#pth-urls-wrapper .us-btn-primary{background:#2563eb;color:#fff}\r\n#pth-urls-wrapper .us-btn-primary:hover{background:#1d4ed8}\r\n#pth-urls-wrapper .us-btn-sec{background:#f1f5f9;color:#334155;border:1px solid #cbd5e1}\r\n#pth-urls-wrapper .us-btn-sec:hover{background:#e2e8f0}\r\n#pth-urls-wrapper .us-btn-purple{background:#f5f3ff;color:#5b21b6;border:1px solid #ddd6fe}\r\n#pth-urls-wrapper .us-btn-danger{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}\r\n#pth-urls-wrapper .us-btn-sm{padding:6px 12px;font-size:.78rem}\r\n#pth-urls-wrapper .us-grid{display:grid;grid-template-columns:1.8fr 1fr;gap:20px;align-items:start}\r\n#pth-urls-wrapper .us-grid.us-fw{grid-template-columns:1fr}\r\n#pth-urls-wrapper .us-grid.us-fw .us-side{display:none}\r\n#pth-urls-wrapper .us-main{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:22px;box-shadow:0 4px 6px -1px rgba(0,0,0,.04)}\r\n#pth-urls-wrapper .us-side{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:22px}\r\n#pth-urls-wrapper .us-tabs{display:flex;flex-wrap:wrap;border-bottom:2px solid #e2e8f0;margin-bottom:20px;gap:2px}\r\n#pth-urls-wrapper .us-tab{background:transparent;border:none;padding:10px 14px;font-size:.85rem;font-weight:700;color:#334155;cursor:pointer;border-bottom:3px solid transparent;margin-bottom:-2px;border-radius:8px 8px 0 0;font-family:inherit;transition:.15s;white-space:nowrap}\r\n#pth-urls-wrapper .us-tab:hover{color:#1d4ed8;background:#f1f5f9}\r\n#pth-urls-wrapper .us-tab.us-active{color:#1d4ed8;border-bottom-color:#2563eb;background:#eff6ff}\r\n#pth-urls-wrapper .us-panel{display:none}\r\n#pth-urls-wrapper .us-panel.us-active{display:block}\r\n#pth-urls-wrapper .us-fld{margin-bottom:14px}\r\n#pth-urls-wrapper .us-fld label{display:block;font-size:.8rem;font-weight:700;margin-bottom:6px;color:#334155}\r\n#pth-urls-wrapper .us-inp{width:100%;padding:10px 13px;font-size:.9rem;border:2px solid #cbd5e1;border-radius:8px;font-family:'Courier New',monospace;font-weight:600;color:#0f172a;outline:none;background:#fff}\r\n#pth-urls-wrapper select.us-inp{font-family:inherit;font-weight:600}\r\n#pth-urls-wrapper .us-inp:focus{border-color:#2563eb;box-shadow:0 0 0 3px rgba(37,99,235,.1)}\r\n#pth-urls-wrapper textarea.us-inp{min-height:110px;resize:vertical;line-height:1.5}\r\n#pth-urls-wrapper textarea.us-out{min-height:110px;resize:vertical;background:#f8fafc;line-height:1.5}\r\n#pth-urls-wrapper textarea.us-out.us-err{background:#fef2f2;border-color:#fecaca;color:#991b1b}\r\n#pth-urls-wrapper .us-row2{display:grid;grid-template-columns:1fr 1fr;gap:14px}\r\n#pth-urls-wrapper .us-row3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px}\r\n#pth-urls-wrapper .us-modes{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:14px}\r\n#pth-urls-wrapper .us-mode{flex:1;min-width:120px;padding:9px 10px;background:#fff;border:2px solid #cbd5e1;border-radius:8px;font-weight:700;font-size:.78rem;color:#334155;cursor:pointer;font-family:inherit;transition:.15s;text-align:center}\r\n#pth-urls-wrapper .us-mode:hover{border-color:#93c5fd;background:#eff6ff}\r\n#pth-urls-wrapper .us-mode.us-on{background:#2563eb;color:#fff;border-color:#2563eb}\r\n#pth-urls-wrapper .us-dir{display:flex;gap:0;border:2px solid #cbd5e1;border-radius:8px;overflow:hidden;margin-bottom:14px}\r\n#pth-urls-wrapper .us-dir-btn{flex:1;padding:10px;background:#fff;border:none;font-weight:800;font-size:.85rem;color:#334155;cursor:pointer;font-family:inherit;transition:.15s}\r\n#pth-urls-wrapper .us-dir-btn.us-on{background:#2563eb;color:#fff}\r\n#pth-urls-wrapper .us-chips{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:14px}\r\n#pth-urls-wrapper .us-chip{background:#f8fafc;border:1px solid #cbd5e1;border-radius:20px;padding:6px 13px;font-size:.78rem;font-weight:700;color:#334155;cursor:pointer;font-family:inherit;transition:.15s}\r\n#pth-urls-wrapper .us-chip:hover{border-color:#2563eb;color:#1d4ed8;background:#eff6ff}\r\n#pth-urls-wrapper .us-stats{display:flex;gap:14px;font-size:.75rem;color:#334155;font-weight:700;margin-bottom:6px;justify-content:flex-end}\r\n#pth-urls-wrapper .us-tbl{width:100%;border-collapse:collapse;background:#fff;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;margin-bottom:14px}\r\n#pth-urls-wrapper .us-tbl th{background:#f8fafc;padding:10px;font-size:.72rem;font-weight:800;color:#0f172a;text-align:left;border-bottom:1px solid #e2e8f0;text-transform:uppercase;letter-spacing:.3px}\r\n#pth-urls-wrapper .us-tbl td{padding:9px 10px;font-size:.85rem;color:#1e293b;border-bottom:1px solid #f1f5f9;font-weight:500;word-break:break-all}\r\n#pth-urls-wrapper .us-tbl tr:last-child td{border-bottom:none}\r\n#pth-urls-wrapper .us-tbl .us-mono{font-family:monospace;font-weight:700;color:#1d4ed8}\r\n#pth-urls-wrapper .us-tbl .us-key{font-family:monospace;font-weight:800;color:#5b21b6}\r\n#pth-urls-wrapper .us-qrow{display:grid;grid-template-columns:1fr 1fr auto;gap:8px;margin-bottom:8px;align-items:center}\r\n#pth-urls-wrapper .us-qrow .us-inp{padding:8px 11px;font-size:.85rem}\r\n#pth-urls-wrapper .us-del{background:#fef2f2;color:#991b1b;border:1px solid #fecaca;border-radius:8px;width:36px;height:36px;cursor:pointer;font-weight:800;font-family:inherit;flex-shrink:0;font-size:1rem;line-height:1}\r\n#pth-urls-wrapper .us-del:hover{background:#fee2e2}\r\n#pth-urls-wrapper .us-result{background:#f0fdf4;border:1px solid #bbf7d0;border-radius:10px;padding:16px;margin-bottom:14px}\r\n#pth-urls-wrapper .us-result-lbl{font-size:.72rem;font-weight:800;color:#047857;text-transform:uppercase;letter-spacing:.4px;margin-bottom:8px}\r\n#pth-urls-wrapper .us-result-val{font-family:monospace;font-size:.9rem;font-weight:700;color:#0f172a;word-break:break-all;line-height:1.6}\r\n#pth-urls-wrapper .us-note{background:#fffbeb;border:1px solid #fde68a;border-radius:8px;padding:12px 14px;font-size:.82rem;color:#92400e;font-weight:500;margin-bottom:14px;line-height:1.5}\r\n#pth-urls-wrapper .us-info{background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;padding:12px 14px;font-size:.82rem;color:#1e293b;font-weight:500;margin-bottom:14px;line-height:1.5}\r\n#pth-urls-wrapper .us-sec{font-size:.75rem;font-weight:800;color:#334155;text-transform:uppercase;letter-spacing:.4px;margin:18px 0 10px;border-bottom:2px solid #f1f5f9;padding-bottom:6px}\r\n#pth-urls-wrapper .us-flag{border-radius:8px;padding:12px 14px;margin-bottom:10px;font-size:.85rem;font-weight:600;line-height:1.5;border:1px solid}\r\n#pth-urls-wrapper .us-flag strong{display:block;margin-bottom:3px;font-weight:800}\r\n#pth-urls-wrapper .us-flag.us-danger{background:#fef2f2;border-color:#fecaca;color:#991b1b}\r\n#pth-urls-wrapper .us-flag.us-warn{background:#fffbeb;border-color:#fde68a;color:#92400e}\r\n#pth-urls-wrapper .us-flag.us-safe{background:#f0fdf4;border-color:#bbf7d0;color:#047857}\r\n#pth-urls-wrapper .us-flag.us-neutral{background:#f8fafc;border-color:#e2e8f0;color:#334155}\r\n#pth-urls-wrapper .us-side h3{font-size:.95rem;font-weight:800;color:#0f172a;margin:0 0 10px}\r\n#pth-urls-wrapper .us-side h3.us-mt{margin-top:22px}\r\n#pth-urls-wrapper .us-side p{font-size:.82rem;color:#1e293b;font-weight:500;margin:0 0 10px}\r\n#pth-urls-wrapper .us-ref{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:12px;font-size:.8rem;line-height:1.9;font-weight:500}\r\n#pth-urls-wrapper .us-ref code{background:#eff6ff;color:#1d4ed8;padding:1px 5px;border-radius:4px;font-weight:800;font-size:.76rem}\r\n#pth-urls-wrapper .us-xlink{background:#f5f3ff;border:1px solid #ddd6fe;border-radius:8px;padding:12px 14px;font-size:.82rem;color:#1e293b;font-weight:500;margin-top:14px}\r\n#pth-urls-wrapper .us-xlink a{color:#5b21b6;font-weight:800;text-decoration:none;display:block;margin-top:4px}\r\n#pth-urls-wrapper .us-xlink a:hover{text-decoration:underline}\r\n#pth-urls-wrapper .us-toast{visibility:hidden;min-width:220px;background:#0f172a;color:#fff;text-align:center;border-radius:8px;padding:12px 18px;position:fixed;z-index:99999;left:50%;bottom:30px;transform:translateX(-50%);font-size:.85rem;font-weight:700;opacity:0;transition:.3s;pointer-events:none}\r\n#pth-urls-wrapper .us-toast.us-show{visibility:visible;opacity:1;bottom:50px}\r\n#pth-urls-wrapper .us-toast.us-ok{background:#047857}\r\n#pth-urls-wrapper .us-toast.us-errt{background:#991b1b}\r\n#pth-urls-wrapper .us-empty{text-align:center;color:#334155;padding:26px;font-weight:600;font-size:.88rem}\r\n@media(max-width:900px){#pth-urls-wrapper .us-grid{grid-template-columns:1fr}#pth-urls-wrapper .us-row2,#pth-urls-wrapper .us-row3{grid-template-columns:1fr}#pth-urls-wrapper .us-qrow{grid-template-columns:1fr 1fr auto}}\r\n<\/style>\r\n\r\n<div id=\"pth-urls-wrapper\">\r\n<div id=\"us-toast\" class=\"us-toast\" role=\"status\" aria-live=\"polite\"><\/div>\r\n\r\n<div class=\"us-card\">\r\n  <h2 class=\"us-title\">URL Studio Pro<\/h2>\r\n  <p class=\"us-sub\">Encode and decode URLs four different ways, parse components, build query strings, tag UTM campaigns, generate slugs, and inspect links for encoding tricks and homograph attacks. 100% offline.<\/p>\r\n  <div class=\"us-meta\">\r\n    <span class=\"us-badge us-b-blue\">v2.0<\/span>\r\n    <span class=\"us-badge us-b-green\">100% Offline<\/span>\r\n  <\/div>\r\n  <div class=\"us-toolbar\">\r\n    <button type=\"button\" class=\"us-btn us-btn-purple\" id=\"us-sample\">Load Sample<\/button>\r\n    <button type=\"button\" class=\"us-btn us-btn-sec\" id=\"us-fw\">Full Width<\/button>\r\n    <button type=\"button\" class=\"us-btn us-btn-danger\" id=\"us-clear\">Clear All<\/button>\r\n  <\/div>\r\n<\/div>\r\n\r\n<div class=\"us-grid\" id=\"us-grid\">\r\n<div class=\"us-main\">\r\n\r\n<div class=\"us-tabs\" role=\"tablist\" aria-label=\"URL Studio sections\">\r\n  <button type=\"button\" class=\"us-tab us-active\" data-tab=\"code\">Encode \/ Decode<\/button>\r\n  <button type=\"button\" class=\"us-tab\" data-tab=\"parse\">URL Parser<\/button>\r\n  <button type=\"button\" class=\"us-tab\" data-tab=\"query\">Query Builder<\/button>\r\n  <button type=\"button\" class=\"us-tab\" data-tab=\"utm\">UTM Builder<\/button>\r\n  <button type=\"button\" class=\"us-tab\" data-tab=\"slug\">Slug Generator<\/button>\r\n  <button type=\"button\" class=\"us-tab\" data-tab=\"inspect\">Link Inspector<\/button>\r\n<\/div>\r\n\r\n<!-- ENCODE \/ DECODE -->\r\n<div id=\"us-p-code\" class=\"us-panel us-active\">\r\n  <div class=\"us-dir\" role=\"group\" aria-label=\"Direction\">\r\n    <button type=\"button\" class=\"us-dir-btn us-on\" id=\"us-dir-enc\">Encode<\/button>\r\n    <button type=\"button\" class=\"us-dir-btn\" id=\"us-dir-dec\">Decode<\/button>\r\n  <\/div>\r\n\r\n  <div id=\"us-enc-modes\">\r\n    <div class=\"us-sec\">Encoding Mode<\/div>\r\n    <div class=\"us-modes\" role=\"group\" aria-label=\"Encoding mode\">\r\n      <button type=\"button\" class=\"us-mode us-on\" data-mode=\"component\">encodeURIComponent<\/button>\r\n      <button type=\"button\" class=\"us-mode\" data-mode=\"uri\">encodeURI<\/button>\r\n      <button type=\"button\" class=\"us-mode\" data-mode=\"rfc3986\">RFC 3986 Strict<\/button>\r\n      <button type=\"button\" class=\"us-mode\" data-mode=\"form\">Form (space to +)<\/button>\r\n    <\/div>\r\n    <div class=\"us-info\" id=\"us-mode-desc\"><\/div>\r\n  <\/div>\r\n\r\n  <div id=\"us-dec-modes\" style=\"display:none;\">\r\n    <div class=\"us-sec\">Decoding Mode<\/div>\r\n    <div class=\"us-modes\" role=\"group\" aria-label=\"Decoding mode\">\r\n      <button type=\"button\" class=\"us-mode us-on\" data-dmode=\"standard\">Standard (%20)<\/button>\r\n      <button type=\"button\" class=\"us-mode\" data-dmode=\"form\">Form (+ to space)<\/button>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <div class=\"us-chips\">\r\n    <span style=\"font-size:.78rem;font-weight:700;color:#334155;align-self:center;\">Try:<\/span>\r\n    <button type=\"button\" class=\"us-chip\" data-sample=\"https:\/\/example.com\/search?q=hello world&amp;lang=en\">URL with spaces<\/button>\r\n    <button type=\"button\" class=\"us-chip\" data-sample=\"name=John Smith&amp;email=john@mail.com\">Form data<\/button>\r\n    <button type=\"button\" class=\"us-chip\" data-sample=\"caf\u00e9s & na\u00efve r\u00e9sum\u00e9\">Accented text<\/button>\r\n    <button type=\"button\" class=\"us-chip\" data-sample=\"a+b=c (100%) [test]!\">Reserved chars<\/button>\r\n  <\/div>\r\n\r\n  <div class=\"us-fld\">\r\n    <label for=\"us-in\">Input<\/label>\r\n    <div class=\"us-stats\"><span id=\"us-in-chars\">0 chars<\/span><span id=\"us-in-bytes\">0 bytes<\/span><\/div>\r\n    <textarea id=\"us-in\" class=\"us-inp\" placeholder=\"Paste text or a URL here...\" spellcheck=\"false\"><\/textarea>\r\n  <\/div>\r\n\r\n  <div class=\"us-fld\">\r\n    <label for=\"us-out\">Output<\/label>\r\n    <div class=\"us-stats\"><span id=\"us-out-chars\">0 chars<\/span><\/div>\r\n    <textarea id=\"us-out\" class=\"us-inp us-out\" readonly spellcheck=\"false\"><\/textarea>\r\n  <\/div>\r\n\r\n  <div class=\"us-toolbar\">\r\n    <button type=\"button\" class=\"us-btn us-btn-primary\" id=\"us-copy\">Copy Output<\/button>\r\n    <button type=\"button\" class=\"us-btn us-btn-sec\" id=\"us-swap\">Send Output to Input<\/button>\r\n  <\/div>\r\n\r\n  <div class=\"us-xlink\">\r\n    Encoding something other than a URL?\r\n    <a href=\"\/free-base64-encoder-decoder-utf-8-supported\/\" target=\"_blank\" rel=\"noopener\">Base64 Encoder \/ Decoder<\/a>\r\n    <a href=\"\/html-entity-encoder-decoder\/\" target=\"_blank\" rel=\"noopener\">HTML Entity Encoder \/ Decoder<\/a>\r\n  <\/div>\r\n<\/div>\r\n\r\n<!-- PARSER -->\r\n<div id=\"us-p-parse\" class=\"us-panel\">\r\n  <div class=\"us-fld\">\r\n    <label for=\"us-parse-in\">URL to Parse<\/label>\r\n    <input type=\"text\" id=\"us-parse-in\" class=\"us-inp\" placeholder=\"https:\/\/user:pass@example.com:8443\/path\/page?a=1&amp;b=2#section\" spellcheck=\"false\">\r\n  <\/div>\r\n  <div id=\"us-parse-err\" class=\"us-note\" style=\"display:none;\"><\/div>\r\n  <div class=\"us-sec\">URL Components<\/div>\r\n  <table class=\"us-tbl\"><tbody id=\"us-parse-tbl\"><tr><td class=\"us-empty\" colspan=\"2\">Enter a full URL, including the protocol.<\/td><\/tr><\/tbody><\/table>\r\n  <div class=\"us-sec\">Query Parameters (values shown decoded)<\/div>\r\n  <table class=\"us-tbl\">\r\n    <thead><tr><th style=\"width:32%;\">Key<\/th><th>Decoded Value<\/th><\/tr><\/thead>\r\n    <tbody id=\"us-param-tbl\"><tr><td colspan=\"2\" class=\"us-empty\">No query parameters found.<\/td><\/tr><\/tbody>\r\n  <\/table>\r\n<\/div>\r\n\r\n<!-- QUERY BUILDER -->\r\n<div id=\"us-p-query\" class=\"us-panel\">\r\n  <div class=\"us-fld\">\r\n    <label for=\"us-qb-base\">Base URL (without query string)<\/label>\r\n    <input type=\"text\" id=\"us-qb-base\" class=\"us-inp\" value=\"https:\/\/example.com\/page\" spellcheck=\"false\">\r\n  <\/div>\r\n  <div class=\"us-sec\">Parameters<\/div>\r\n  <div id=\"us-qb-rows\"><\/div>\r\n  <div class=\"us-toolbar\" style=\"margin-bottom:16px;\">\r\n    <button type=\"button\" class=\"us-btn us-btn-sec us-btn-sm\" id=\"us-qb-add\">+ Add Parameter<\/button>\r\n    <button type=\"button\" class=\"us-btn us-btn-sec us-btn-sm\" id=\"us-qb-import\">Import from Parser Tab<\/button>\r\n  <\/div>\r\n  <div class=\"us-result\">\r\n    <div class=\"us-result-lbl\">Built URL<\/div>\r\n    <div class=\"us-result-val\" id=\"us-qb-out\">https:\/\/example.com\/page<\/div>\r\n  <\/div>\r\n  <button type=\"button\" class=\"us-btn us-btn-primary\" id=\"us-qb-copy\">Copy Built URL<\/button>\r\n  <div class=\"us-info\" style=\"margin-top:14px;\">Keys and values are percent-encoded automatically. Empty rows are skipped. A parameter with a blank value is still included as <code>key=<\/code>, which is a valid and meaningful distinction from omitting it entirely.<\/div>\r\n<\/div>\r\n\r\n<!-- UTM BUILDER -->\r\n<div id=\"us-p-utm\" class=\"us-panel\">\r\n  <div class=\"us-fld\">\r\n    <label for=\"us-utm-base\">Destination URL<\/label>\r\n    <input type=\"text\" id=\"us-utm-base\" class=\"us-inp\" value=\"https:\/\/example.com\/landing\" spellcheck=\"false\">\r\n  <\/div>\r\n  <div class=\"us-sec\">Campaign Presets<\/div>\r\n  <div class=\"us-chips\" id=\"us-utm-presets\"><\/div>\r\n  <div class=\"us-row2\">\r\n    <div class=\"us-fld\"><label for=\"us-utm-source\">utm_source <span style=\"color:#991b1b;\">*required<\/span><\/label>\r\n      <input type=\"text\" id=\"us-utm-source\" class=\"us-inp\" placeholder=\"google, newsletter, facebook\"><\/div>\r\n    <div class=\"us-fld\"><label for=\"us-utm-medium\">utm_medium <span style=\"color:#991b1b;\">*required<\/span><\/label>\r\n      <input type=\"text\" id=\"us-utm-medium\" class=\"us-inp\" placeholder=\"cpc, email, social\"><\/div>\r\n  <\/div>\r\n  <div class=\"us-fld\"><label for=\"us-utm-campaign\">utm_campaign <span style=\"color:#991b1b;\">*required<\/span><\/label>\r\n    <input type=\"text\" id=\"us-utm-campaign\" class=\"us-inp\" placeholder=\"spring_sale_2026\"><\/div>\r\n  <div class=\"us-row2\">\r\n    <div class=\"us-fld\"><label for=\"us-utm-term\">utm_term (optional, paid search keyword)<\/label>\r\n      <input type=\"text\" id=\"us-utm-term\" class=\"us-inp\" placeholder=\"running+shoes\"><\/div>\r\n    <div class=\"us-fld\"><label for=\"us-utm-content\">utm_content (optional, A\/B variant)<\/label>\r\n      <input type=\"text\" id=\"us-utm-content\" class=\"us-inp\" placeholder=\"hero_button_red\"><\/div>\r\n  <\/div>\r\n  <div class=\"us-fld\">\r\n    <label><input type=\"checkbox\" id=\"us-utm-lower\" checked style=\"width:auto;margin-right:6px;\"> Force lowercase (recommended &mdash; analytics tools treat Email and email as two different sources)<\/label>\r\n  <\/div>\r\n  <div id=\"us-utm-warn\"><\/div>\r\n  <div class=\"us-result\">\r\n    <div class=\"us-result-lbl\">Tagged Campaign URL<\/div>\r\n    <div class=\"us-result-val\" id=\"us-utm-out\">Enter a destination URL and the three required fields.<\/div>\r\n  <\/div>\r\n  <button type=\"button\" class=\"us-btn us-btn-primary\" id=\"us-utm-copy\">Copy Campaign URL<\/button>\r\n  <div class=\"us-xlink\">\r\n    Working on the page's meta tags and search snippet instead?\r\n    <a href=\"\/free-seo-meta-tags-generator-preview\/\" target=\"_blank\" rel=\"noopener\">SEO Meta Tags Generator &amp; Preview<\/a>\r\n  <\/div>\r\n<\/div>\r\n\r\n<!-- SLUG -->\r\n<div id=\"us-p-slug\" class=\"us-panel\">\r\n  <div class=\"us-fld\">\r\n    <label for=\"us-slug-in\">Title or Text<\/label>\r\n    <textarea id=\"us-slug-in\" class=\"us-inp\" style=\"min-height:80px;\" placeholder=\"10 Ways to Improve Your Website's Speed in 2026!\"><\/textarea>\r\n  <\/div>\r\n  <div class=\"us-row3\">\r\n    <div class=\"us-fld\"><label for=\"us-slug-sep\">Separator<\/label>\r\n      <select id=\"us-slug-sep\" class=\"us-inp\">\r\n        <option value=\"-\">Hyphen (-) recommended<\/option>\r\n        <option value=\"_\">Underscore (_)<\/option>\r\n      <\/select><\/div>\r\n    <div class=\"us-fld\"><label for=\"us-slug-max\">Max Length (0 = none)<\/label>\r\n      <input type=\"number\" id=\"us-slug-max\" class=\"us-inp\" value=\"60\" min=\"0\"><\/div>\r\n    <div class=\"us-fld\"><label for=\"us-slug-case\">Case<\/label>\r\n      <select id=\"us-slug-case\" class=\"us-inp\">\r\n        <option value=\"lower\">lowercase<\/option>\r\n        <option value=\"keep\">Keep original case<\/option>\r\n      <\/select><\/div>\r\n  <\/div>\r\n  <div class=\"us-fld\">\r\n    <label><input type=\"checkbox\" id=\"us-slug-stop\" style=\"width:auto;margin-right:6px;\"> Remove common stopwords (a, the, of, in, to...)<\/label>\r\n  <\/div>\r\n  <div class=\"us-result\">\r\n    <div class=\"us-result-lbl\">Generated Slug<\/div>\r\n    <div class=\"us-result-val\" id=\"us-slug-out\">&mdash;<\/div>\r\n  <\/div>\r\n  <div class=\"us-toolbar\" style=\"margin-bottom:14px;\">\r\n    <button type=\"button\" class=\"us-btn us-btn-primary\" id=\"us-slug-copy\">Copy Slug<\/button>\r\n  <\/div>\r\n  <table class=\"us-tbl\">\r\n    <tbody>\r\n      <tr><td style=\"width:35%;\"><strong>Length<\/strong><\/td><td class=\"us-mono\" id=\"us-slug-len\">0 chars<\/td><\/tr>\r\n      <tr><td><strong>Words<\/strong><\/td><td class=\"us-mono\" id=\"us-slug-words\">0<\/td><\/tr>\r\n      <tr><td><strong>Accents transliterated<\/strong><\/td><td class=\"us-mono\" id=\"us-slug-accents\">0<\/td><\/tr>\r\n    <\/tbody>\r\n  <\/table>\r\n  <div class=\"us-info\">Accented and non-ASCII characters are transliterated where a sensible Latin equivalent exists (&eacute; becomes e, &uuml; becomes u, &szlig; becomes ss). Characters with no Latin equivalent are dropped rather than guessed at. Keep slugs short and descriptive; length caps break at a word boundary, never mid-word.<\/div>\r\n<\/div>\r\n\r\n<!-- LINK INSPECTOR -->\r\n<div id=\"us-p-inspect\" class=\"us-panel\">\r\n  <div class=\"us-note\">\r\n    <strong>Structural analysis only.<\/strong> This runs entirely in your browser and never contacts the link. It cannot follow redirects, check a blocklist, or tell you whether a site is actually malicious &mdash; doing any of that would require a network request. What it does is decode the URL and flag structural tricks commonly used to disguise a destination. A clean result means no tricks were found in the string, not that the link is safe to visit.\r\n  <\/div>\r\n  <div class=\"us-fld\">\r\n    <label for=\"us-insp-in\">URL to Inspect<\/label>\r\n    <textarea id=\"us-insp-in\" class=\"us-inp\" style=\"min-height:70px;\" placeholder=\"Paste a suspicious link here...\" spellcheck=\"false\"><\/textarea>\r\n  <\/div>\r\n  <div class=\"us-chips\">\r\n    <span style=\"font-size:.78rem;font-weight:700;color:#334155;align-self:center;\">Examples:<\/span>\r\n    <button type=\"button\" class=\"us-chip\" data-insp=\"https:\/\/xn--80ak6aa92e.com\/login\">Punycode homograph<\/button>\r\n    <button type=\"button\" class=\"us-chip\" data-insp=\"https:\/\/google.com@evil-site.com\/verify\">@ credential trick<\/button>\r\n    <button type=\"button\" class=\"us-chip\" data-insp=\"https:\/\/example.com\/redirect?to=https%253A%252F%252Fevil.com\">Double encoded<\/button>\r\n    <button type=\"button\" class=\"us-chip\" data-insp=\"http:\/\/192.168.1.1\/admin\/login.php\">Raw IP host<\/button>\r\n  <\/div>\r\n  <button type=\"button\" class=\"us-btn us-btn-primary\" id=\"us-insp-run\" style=\"width:100%;justify-content:center;margin-bottom:16px;\">Inspect Link<\/button>\r\n  <div class=\"us-sec\">Findings<\/div>\r\n  <div id=\"us-insp-out\"><div class=\"us-empty\">Paste a URL above and click Inspect Link.<\/div><\/div>\r\n  <div id=\"us-insp-detail\"><\/div>\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n<div class=\"us-side\">\r\n  <h3>Which Encoder?<\/h3>\r\n  <div class=\"us-ref\">\r\n    <code>encodeURIComponent<\/code> &mdash; for a single value going <em>inside<\/em> a query string. Escapes <code>\/ ? : @ &amp; =<\/code>.<br><br>\r\n    <code>encodeURI<\/code> &mdash; for a <em>whole<\/em> URL. Leaves <code>\/ ? : # &amp; =<\/code> intact so the URL still works.<br><br>\r\n    <code>RFC 3986<\/code> &mdash; strictest. Also escapes <code>! ' ( ) *<\/code>, which encodeURIComponent leaves alone. Use when a strict parser is on the other end.<br><br>\r\n    <code>Form<\/code> &mdash; space becomes <code>+<\/code>, not <code>%20<\/code>. Only for <code>application\/x-www-form-urlencoded<\/code> bodies.\r\n  <\/div>\r\n\r\n  <h3 class=\"us-mt\">Reserved Characters<\/h3>\r\n  <table class=\"us-tbl\">\r\n    <thead><tr><th>Char<\/th><th>Encoded<\/th><\/tr><\/thead>\r\n    <tbody>\r\n      <tr><td class=\"us-key\">space<\/td><td class=\"us-mono\">%20 or +<\/td><\/tr>\r\n      <tr><td class=\"us-key\">&amp;<\/td><td class=\"us-mono\">%26<\/td><\/tr>\r\n      <tr><td class=\"us-key\">=<\/td><td class=\"us-mono\">%3D<\/td><\/tr>\r\n      <tr><td class=\"us-key\">?<\/td><td class=\"us-mono\">%3F<\/td><\/tr>\r\n      <tr><td class=\"us-key\">#<\/td><td class=\"us-mono\">%23<\/td><\/tr>\r\n      <tr><td class=\"us-key\">\/<\/td><td class=\"us-mono\">%2F<\/td><\/tr>\r\n      <tr><td class=\"us-key\">+<\/td><td class=\"us-mono\">%2B<\/td><\/tr>\r\n      <tr><td class=\"us-key\">%<\/td><td class=\"us-mono\">%25<\/td><\/tr>\r\n    <\/tbody>\r\n  <\/table>\r\n\r\n  <h3 class=\"us-mt\">UTM Conventions<\/h3>\r\n  <p><strong>source<\/strong> = where it came from (google, newsletter).<br><strong>medium<\/strong> = how (cpc, email, social).<br><strong>campaign<\/strong> = which push (spring_sale).<\/p>\r\n  <p>Always lowercase. Analytics treats <code>Email<\/code> and <code>email<\/code> as two separate sources, which silently splits your reporting in half.<\/p>\r\n<\/div>\r\n<\/div>\r\n<\/div>\r\n\r\n<script data-no-optimize=\"1\" data-no-minify=\"1\" data-cfasync=\"false\">\r\n(function(){\r\n'use strict';\r\nif(window.pthUrlStudioApp){return;}\r\n\r\n\/* ===== RFC 3492 PUNYCODE DECODER (ES5, no dependencies) ===== *\/\r\nvar PC_BASE=36,PC_TMIN=1,PC_TMAX=26,PC_SKEW=38,PC_DAMP=700,PC_INITIAL_BIAS=72,PC_INITIAL_N=128,PC_DELIM=45;\r\n\r\nfunction pcBasicToDigit(cp){\r\nif(cp-48<10)return cp-22;\r\nif(cp-65<26)return cp-65;\r\nif(cp-97<26)return cp-97;\r\nreturn PC_BASE;\r\n}\r\nfunction pcAdapt(delta,numPoints,firstTime){\r\nvar k=0;\r\ndelta=firstTime?Math.floor(delta\/PC_DAMP):(delta>>1);\r\ndelta+=Math.floor(delta\/numPoints);\r\nfor(;delta>(((PC_BASE-PC_TMIN)*PC_TMAX)>>1);k+=PC_BASE){\r\ndelta=Math.floor(delta\/(PC_BASE-PC_TMIN));\r\n}\r\nreturn Math.floor(k+((PC_BASE-PC_TMIN+1)*delta)\/(delta+PC_SKEW));\r\n}\r\nfunction pcUcs2Encode(codePoints){\r\nvar out='',i,cp;\r\nfor(i=0;i<codePoints.length;i++){\r\ncp=codePoints[i];\r\nif(cp>0xFFFF){\r\ncp-=0x10000;\r\nout+=String.fromCharCode((cp>>>10)+0xD800,(cp&0x3FF)+0xDC00);\r\n}else{\r\nout+=String.fromCharCode(cp);\r\n}\r\n}\r\nreturn out;\r\n}\r\n\/* decodes a single punycode label (WITHOUT the xn-- prefix) *\/\r\nfunction punycodeDecode(input){\r\nvar output=[],i=0,n=PC_INITIAL_N,bias=PC_INITIAL_BIAS;\r\nvar basic=input.lastIndexOf(String.fromCharCode(PC_DELIM));\r\nif(basic<0)basic=0;\r\nvar j;\r\nfor(j=0;j<basic;j++){\r\nif(input.charCodeAt(j)>=0x80)throw new Error('not-basic');\r\noutput.push(input.charCodeAt(j));\r\n}\r\nvar index=basic>0?basic+1:0;\r\nwhile(index<input.length){\r\nvar oldi=i,w=1,k=PC_BASE;\r\nfor(;;){\r\nif(index>=input.length)throw new Error('invalid-input');\r\nvar digit=pcBasicToDigit(input.charCodeAt(index++));\r\nif(digit>=PC_BASE)throw new Error('invalid-input');\r\nif(digit>Math.floor((0x7FFFFFFF-i)\/w))throw new Error('overflow');\r\ni+=digit*w;\r\nvar t=k<=bias?PC_TMIN:(k>=bias+PC_TMAX?PC_TMAX:k-bias);\r\nif(digit<t)break;\r\nif(w>Math.floor(0x7FFFFFFF\/(PC_BASE-t)))throw new Error('overflow');\r\nw*=(PC_BASE-t);\r\nk+=PC_BASE;\r\n}\r\nvar out=output.length+1;\r\nbias=pcAdapt(i-oldi,out,oldi===0);\r\nif(Math.floor(i\/out)>0x7FFFFFFF-n)throw new Error('overflow');\r\nn+=Math.floor(i\/out);\r\ni%=out;\r\noutput.splice(i,0,n);\r\ni++;\r\n}\r\nreturn pcUcs2Encode(output);\r\n}\r\n\/* decodes a full hostname, handling each xn-- label *\/\r\nfunction punycodeDecodeHost(host){\r\nvar parts=host.split('.'),i,out=[];\r\nfor(i=0;i<parts.length;i++){\r\nvar p=parts[i];\r\nif(p.toLowerCase().indexOf('xn--')===0){\r\ntry{out.push(punycodeDecode(p.slice(4)));}\r\ncatch(e){out.push(p);}\r\n}else{out.push(p);}\r\n}\r\nreturn out.join('.');\r\n}\r\n\r\n\/* ===== SCRIPT DETECTION for homograph check ===== *\/\r\nfunction detectScripts(str){\r\nvar found={},i,cp;\r\nfor(i=0;i<str.length;i++){\r\ncp=str.charCodeAt(i);\r\nif(cp>=0x0041&&cp<=0x007A){found.Latin=1;}\r\nelse if(cp>=0x00C0&&cp<=0x024F){found.Latin=1;}\r\nelse if(cp>=0x0370&&cp<=0x03FF){found.Greek=1;}\r\nelse if(cp>=0x0400&&cp<=0x04FF){found.Cyrillic=1;}\r\nelse if(cp>=0x0530&&cp<=0x058F){found.Armenian=1;}\r\nelse if(cp>=0x0590&&cp<=0x05FF){found.Hebrew=1;}\r\nelse if(cp>=0x0600&&cp<=0x06FF){found.Arabic=1;}\r\nelse if(cp>=0x0E00&&cp<=0x0E7F){found.Thai=1;}\r\nelse if(cp>=0x4E00&&cp<=0x9FFF){found.Han=1;}\r\n}\r\nvar list=[],k;\r\nfor(k in found){if(found.hasOwnProperty(k))list.push(k);}\r\nreturn list;\r\n}\r\n\r\nvar ACCENTS={\r\n'\\u00E0':'a','\\u00E1':'a','\\u00E2':'a','\\u00E3':'a','\\u00E4':'a','\\u00E5':'a','\\u0101':'a','\\u0103':'a','\\u0105':'a',\r\n'\\u00E8':'e','\\u00E9':'e','\\u00EA':'e','\\u00EB':'e','\\u0113':'e','\\u0115':'e','\\u0117':'e','\\u0119':'e','\\u011B':'e',\r\n'\\u00EC':'i','\\u00ED':'i','\\u00EE':'i','\\u00EF':'i','\\u012B':'i','\\u012D':'i','\\u012F':'i','\\u0131':'i',\r\n'\\u00F2':'o','\\u00F3':'o','\\u00F4':'o','\\u00F5':'o','\\u00F6':'o','\\u00F8':'o','\\u014D':'o','\\u014F':'o','\\u0151':'o',\r\n'\\u00F9':'u','\\u00FA':'u','\\u00FB':'u','\\u00FC':'u','\\u016B':'u','\\u016D':'u','\\u016F':'u','\\u0171':'u','\\u0173':'u',\r\n'\\u00E7':'c','\\u0107':'c','\\u010D':'c','\\u00F1':'n','\\u0144':'n','\\u0148':'n',\r\n'\\u00FD':'y','\\u00FF':'y','\\u015B':'s','\\u0161':'s','\\u017A':'z','\\u017C':'z','\\u017E':'z',\r\n'\\u0142':'l','\\u0159':'r','\\u0165':'t','\\u010F':'d','\\u011F':'g',\r\n'\\u00DF':'ss','\\u00E6':'ae','\\u0153':'oe','\\u00F0':'d','\\u00FE':'th'\r\n};\r\n\r\nvar STOPWORDS=['a','an','the','and','or','but','of','in','on','at','to','for','with','is','are','was','were','be','by','from','as','it','its','this','that'];\r\n\r\nvar UTM_PRESETS=[\r\n{label:'Google Ads',s:'google',m:'cpc'},\r\n{label:'Google Organic',s:'google',m:'organic'},\r\n{label:'Facebook',s:'facebook',m:'social'},\r\n{label:'Instagram',s:'instagram',m:'social'},\r\n{label:'Email Newsletter',s:'newsletter',m:'email'},\r\n{label:'LinkedIn',s:'linkedin',m:'social'},\r\n{label:'YouTube',s:'youtube',m:'video'},\r\n{label:'Affiliate',s:'partner',m:'affiliate'}\r\n];\r\n\r\nvar MODE_DESC={\r\ncomponent:'<strong>encodeURIComponent<\/strong> escapes everything that is not safe inside a single query value, including <code>\/ ? : @ &amp; = + $ ,<\/code>. Use this for one value, never for a whole URL, because it will destroy the structure.',\r\nuri:'<strong>encodeURI<\/strong> escapes spaces and non-ASCII but deliberately preserves the characters that give a URL its structure: <code>\/ ? : # &amp; = +<\/code>. Use this on a complete URL you want to remain usable.',\r\nrfc3986:'<strong>RFC 3986 Strict<\/strong> does everything encodeURIComponent does, and additionally escapes <code>! \\' ( ) *<\/code>, which the JavaScript built-in leaves alone. Use this when a strict server-side parser or a signature check is on the receiving end.',\r\nform:'<strong>Form encoding<\/strong> is what an HTML form submits: spaces become <code>+<\/code> rather than <code>%20<\/code>. Correct for an <code>application\/x-www-form-urlencoded<\/code> request body, and wrong almost everywhere else.'\r\n};\r\n\r\nwindow.pthUrlStudioApp={\r\ninitialized:false,\r\ndir:'encode',\r\nencMode:'component',\r\ndecMode:'standard',\r\nqbRows:[],\r\ntoastTimer:null,\r\n\r\nel:function(id){return document.getElementById(id);},\r\n\r\ntoast:function(msg,type){\r\nvar t=this.el('us-toast');\r\nif(!t)return;\r\nt.textContent=msg;\r\nt.className='us-toast us-show'+(type==='err'?' us-errt':' us-ok');\r\nvar self=this;\r\nclearTimeout(self.toastTimer);\r\nself.toastTimer=setTimeout(function(){t.className='us-toast';},3000);\r\n},\r\n\r\nesc:function(s){\r\nreturn String(s).replace(\/&\/g,'&amp;').replace(\/<\/g,'&lt;').replace(\/>\/g,'&gt;').replace(\/\"\/g,'&quot;');\r\n},\r\n\r\ncopy:function(text,label){\r\nif(!text){this.toast('Nothing to copy','err');return;}\r\nvar ta=document.createElement('textarea');\r\nta.value=text;ta.style.position='fixed';ta.style.opacity='0';\r\ndocument.body.appendChild(ta);ta.select();\r\ntry{document.execCommand('copy');this.toast((label||'Copied')+'!','ok');}\r\ncatch(e){this.toast('Copy failed','err');}\r\ndocument.body.removeChild(ta);\r\n},\r\n\r\nswitchTab:function(id){\r\nvar w=this.el('pth-urls-wrapper');\r\nif(!w)return;\r\nvar i,tabs=w.querySelectorAll('.us-tab'),panels=w.querySelectorAll('.us-panel');\r\nfor(i=0;i<tabs.length;i++)tabs[i].classList.remove('us-active');\r\nfor(i=0;i<panels.length;i++)panels[i].classList.remove('us-active');\r\nvar b=w.querySelector('.us-tab[data-tab=\"'+id+'\"]'),p=this.el('us-p-'+id);\r\nif(b)b.classList.add('us-active');\r\nif(p)p.classList.add('us-active');\r\n},\r\n\r\n\/* ---------- TAB 1: ENCODE \/ DECODE ---------- *\/\r\nbyteLen:function(s){\r\ntry{return unescape(encodeURIComponent(s)).length;}catch(e){return s.length;}\r\n},\r\n\r\nencodeRFC3986:function(s){\r\nreturn encodeURIComponent(s).replace(\/[!'()*]\/g,function(c){\r\nreturn '%'+c.charCodeAt(0).toString(16).toUpperCase();\r\n});\r\n},\r\n\r\nencodeForm:function(s){\r\nreturn encodeURIComponent(s).replace(\/%20\/g,'+').replace(\/[!'()*]\/g,function(c){\r\nreturn '%'+c.charCodeAt(0).toString(16).toUpperCase();\r\n});\r\n},\r\n\r\nprocessCode:function(){\r\nvar inp=this.el('us-in').value;\r\nvar out=this.el('us-out');\r\nthis.el('us-in-chars').textContent=inp.length+' chars';\r\nthis.el('us-in-bytes').textContent=this.byteLen(inp)+' bytes';\r\nif(!inp){out.value='';out.classList.remove('us-err');this.el('us-out-chars').textContent='0 chars';return;}\r\nvar res='',ok=true;\r\ntry{\r\nif(this.dir==='encode'){\r\nif(this.encMode==='component')res=encodeURIComponent(inp);\r\nelse if(this.encMode==='uri')res=encodeURI(inp);\r\nelse if(this.encMode==='rfc3986')res=this.encodeRFC3986(inp);\r\nelse res=this.encodeForm(inp);\r\n}else{\r\nvar src=inp;\r\nif(this.decMode==='form')src=src.replace(\/\\+\/g,' ');\r\nres=decodeURIComponent(src);\r\n}\r\n}catch(e){\r\nok=false;\r\nres='Malformed input: this is not a valid percent-encoded string. A stray % that is not followed by two hex digits is the usual cause.';\r\n}\r\nout.value=res;\r\nif(ok)out.classList.remove('us-err');\r\nelse out.classList.add('us-err');\r\nthis.el('us-out-chars').textContent=(ok?res.length:0)+' chars';\r\n},\r\n\r\nsetDir:function(d){\r\nthis.dir=d;\r\nthis.el('us-dir-enc').classList.toggle('us-on',d==='encode');\r\nthis.el('us-dir-dec').classList.toggle('us-on',d==='decode');\r\nthis.el('us-enc-modes').style.display=d==='encode'?'block':'none';\r\nthis.el('us-dec-modes').style.display=d==='decode'?'block':'none';\r\nthis.processCode();\r\n},\r\n\r\nsetEncMode:function(m){\r\nthis.encMode=m;\r\nvar w=this.el('pth-urls-wrapper');\r\nvar btns=w.querySelectorAll('.us-mode[data-mode]'),i;\r\nfor(i=0;i<btns.length;i++){\r\nbtns[i].classList.toggle('us-on',btns[i].getAttribute('data-mode')===m);\r\n}\r\nthis.el('us-mode-desc').innerHTML=MODE_DESC[m]||'';\r\nthis.processCode();\r\n},\r\n\r\nsetDecMode:function(m){\r\nthis.decMode=m;\r\nvar w=this.el('pth-urls-wrapper');\r\nvar btns=w.querySelectorAll('.us-mode[data-dmode]'),i;\r\nfor(i=0;i<btns.length;i++){\r\nbtns[i].classList.toggle('us-on',btns[i].getAttribute('data-dmode')===m);\r\n}\r\nthis.processCode();\r\n},\r\n\r\n\/* ---------- TAB 2: PARSER ---------- *\/\r\nparseURL:function(){\r\nvar raw=this.el('us-parse-in').value.replace(\/^\\s+|\\s+$\/g,'');\r\nvar errBox=this.el('us-parse-err');\r\nvar tbl=this.el('us-parse-tbl');\r\nvar ptbl=this.el('us-param-tbl');\r\nif(!raw){\r\nerrBox.style.display='none';\r\ntbl.innerHTML='<tr><td class=\"us-empty\" colspan=\"2\">Enter a full URL, including the protocol.<\/td><\/tr>';\r\nptbl.innerHTML='<tr><td colspan=\"2\" class=\"us-empty\">No query parameters found.<\/td><\/tr>';\r\nreturn null;\r\n}\r\nvar u;\r\ntry{u=new URL(raw);}\r\ncatch(e){\r\nerrBox.style.display='block';\r\nerrBox.innerHTML='<strong>Could not parse.<\/strong> A full URL needs a protocol &mdash; try adding <code>https:\/\/<\/code> at the start.';\r\ntbl.innerHTML='<tr><td class=\"us-empty\" colspan=\"2\">Waiting for a valid URL.<\/td><\/tr>';\r\nptbl.innerHTML='<tr><td colspan=\"2\" class=\"us-empty\">No query parameters found.<\/td><\/tr>';\r\nreturn null;\r\n}\r\nerrBox.style.display='none';\r\nvar rows=[\r\n['Protocol',u.protocol],\r\n['Origin',u.origin],\r\n['Hostname',u.hostname],\r\n['Port',u.port||'(default)'],\r\n['Path',u.pathname||'\/'],\r\n['Query string',u.search||'(none)'],\r\n['Fragment',u.hash||'(none)']\r\n];\r\nif(u.username)rows.splice(3,0,['Username',u.username]);\r\nif(u.password)rows.splice(4,0,['Password','(present)']);\r\nvar html='',i;\r\nfor(i=0;i<rows.length;i++){\r\nhtml+='<tr><td style=\"width:32%;\"><strong>'+rows[i][0]+'<\/strong><\/td><td class=\"us-mono\">'+this.esc(rows[i][1])+'<\/td><\/tr>';\r\n}\r\ntbl.innerHTML=html;\r\n\r\nvar params=[];\r\nu.searchParams.forEach(function(v,k){params.push([k,v]);});\r\nif(!params.length){\r\nptbl.innerHTML='<tr><td colspan=\"2\" class=\"us-empty\">No query parameters found.<\/td><\/tr>';\r\n}else{\r\nvar ph='';\r\nfor(i=0;i<params.length;i++){\r\nph+='<tr><td class=\"us-key\">'+this.esc(params[i][0])+'<\/td><td>'+this.esc(params[i][1]===''?'(empty)':params[i][1])+'<\/td><\/tr>';\r\n}\r\nptbl.innerHTML=ph;\r\n}\r\nreturn u;\r\n},\r\n\r\n\/* ---------- TAB 3: QUERY BUILDER ---------- *\/\r\nrenderQB:function(){\r\nvar box=this.el('us-qb-rows');\r\nif(!box)return;\r\nvar self=this,html='',i;\r\nif(!this.qbRows.length)this.qbRows=[{k:'',v:''}];\r\nfor(i=0;i<this.qbRows.length;i++){\r\nhtml+='<div class=\"us-qrow\">';\r\nhtml+='<input type=\"text\" class=\"us-inp us-qb-k\" data-i=\"'+i+'\" placeholder=\"key\" value=\"'+this.esc(this.qbRows[i].k)+'\">';\r\nhtml+='<input type=\"text\" class=\"us-inp us-qb-v\" data-i=\"'+i+'\" placeholder=\"value\" value=\"'+this.esc(this.qbRows[i].v)+'\">';\r\nhtml+='<button type=\"button\" class=\"us-del\" data-i=\"'+i+'\" aria-label=\"Delete parameter\">&times;<\/button>';\r\nhtml+='<\/div>';\r\n}\r\nbox.innerHTML=html;\r\nvar ks=box.querySelectorAll('.us-qb-k'),vs=box.querySelectorAll('.us-qb-v'),ds=box.querySelectorAll('.us-del');\r\nfor(i=0;i<ks.length;i++){\r\nks[i].addEventListener('input',function(){\r\nself.qbRows[parseInt(this.getAttribute('data-i'),10)].k=this.value;\r\nself.buildQuery();\r\n});\r\n}\r\nfor(i=0;i<vs.length;i++){\r\nvs[i].addEventListener('input',function(){\r\nself.qbRows[parseInt(this.getAttribute('data-i'),10)].v=this.value;\r\nself.buildQuery();\r\n});\r\n}\r\nfor(i=0;i<ds.length;i++){\r\nds[i].addEventListener('click',function(){\r\nvar idx=parseInt(this.getAttribute('data-i'),10);\r\nself.qbRows.splice(idx,1);\r\nif(!self.qbRows.length)self.qbRows=[{k:'',v:''}];\r\nself.renderQB();\r\nself.buildQuery();\r\n});\r\n}\r\n},\r\n\r\nbuildQuery:function(){\r\nvar base=this.el('us-qb-base').value.replace(\/^\\s+|\\s+$\/g,'');\r\nvar parts=[],i;\r\nfor(i=0;i<this.qbRows.length;i++){\r\nvar k=this.qbRows[i].k.replace(\/^\\s+|\\s+$\/g,'');\r\nif(!k)continue;\r\nparts.push(encodeURIComponent(k)+'='+encodeURIComponent(this.qbRows[i].v));\r\n}\r\nvar out=base;\r\nif(parts.length){\r\nout+=(base.indexOf('?')>=0?'&':'?')+parts.join('&');\r\n}\r\nthis.el('us-qb-out').textContent=out||'(enter a base URL)';\r\nreturn out;\r\n},\r\n\r\nimportParams:function(){\r\nvar u=this.parseURL();\r\nif(!u){this.toast('Enter a valid URL in the Parser tab first','err');return;}\r\nvar rows=[];\r\nu.searchParams.forEach(function(v,k){rows.push({k:k,v:v});});\r\nif(!rows.length){this.toast('That URL has no query parameters','err');return;}\r\nthis.qbRows=rows;\r\nthis.el('us-qb-base').value=u.origin+u.pathname;\r\nthis.renderQB();\r\nthis.buildQuery();\r\nthis.toast('Imported '+rows.length+' parameter(s)','ok');\r\n},\r\n\r\n\/* ---------- TAB 4: UTM ---------- *\/\r\nbuildUTMPresets:function(){\r\nvar box=this.el('us-utm-presets');\r\nif(!box)return;\r\nvar self=this,html='',i;\r\nfor(i=0;i<UTM_PRESETS.length;i++){\r\nhtml+='<button type=\"button\" class=\"us-chip\" data-s=\"'+UTM_PRESETS[i].s+'\" data-m=\"'+UTM_PRESETS[i].m+'\">'+UTM_PRESETS[i].label+'<\/button>';\r\n}\r\nbox.innerHTML=html;\r\nvar chips=box.querySelectorAll('.us-chip');\r\nfor(i=0;i<chips.length;i++){\r\nchips[i].addEventListener('click',function(){\r\nself.el('us-utm-source').value=this.getAttribute('data-s');\r\nself.el('us-utm-medium').value=this.getAttribute('data-m');\r\nself.buildUTM();\r\n});\r\n}\r\n},\r\n\r\nbuildUTM:function(){\r\nvar base=this.el('us-utm-base').value.replace(\/^\\s+|\\s+$\/g,'');\r\nvar lower=this.el('us-utm-lower').checked;\r\nfunction clean(v){\r\nv=v.replace(\/^\\s+|\\s+$\/g,'');\r\nreturn lower?v.toLowerCase():v;\r\n}\r\nvar src=clean(this.el('us-utm-source').value);\r\nvar med=clean(this.el('us-utm-medium').value);\r\nvar cmp=clean(this.el('us-utm-campaign').value);\r\nvar term=clean(this.el('us-utm-term').value);\r\nvar cont=clean(this.el('us-utm-content').value);\r\n\r\nvar warn=this.el('us-utm-warn');\r\nvar missing=[];\r\nif(!src)missing.push('utm_source');\r\nif(!med)missing.push('utm_medium');\r\nif(!cmp)missing.push('utm_campaign');\r\nvar wHtml='';\r\nif(missing.length){\r\nwHtml+='<div class=\"us-flag us-warn\"><strong>Missing required fields<\/strong>'+missing.join(', ')+'. Analytics tools group traffic by these three; leaving any blank sends the visit to an unattributed bucket.<\/div>';\r\n}\r\nif(\/\\s\/.test(this.el('us-utm-campaign').value.replace(\/^\\s+|\\s+$\/g,''))){\r\nwHtml+='<div class=\"us-flag us-neutral\"><strong>Spaces in campaign name<\/strong>They will be encoded as %20, which works but reads badly in reports. Underscores are the usual convention.<\/div>';\r\n}\r\nwarn.innerHTML=wHtml;\r\n\r\nif(!base||!src||!med||!cmp){\r\nthis.el('us-utm-out').textContent='Enter a destination URL and the three required fields.';\r\nreturn '';\r\n}\r\nvar parts=[\r\n'utm_source='+encodeURIComponent(src),\r\n'utm_medium='+encodeURIComponent(med),\r\n'utm_campaign='+encodeURIComponent(cmp)\r\n];\r\nif(term)parts.push('utm_term='+encodeURIComponent(term));\r\nif(cont)parts.push('utm_content='+encodeURIComponent(cont));\r\nvar out=base+(base.indexOf('?')>=0?'&':'?')+parts.join('&');\r\nthis.el('us-utm-out').textContent=out;\r\nreturn out;\r\n},\r\n\r\n\/* ---------- TAB 5: SLUG ---------- *\/\r\nbuildSlug:function(){\r\nvar raw=this.el('us-slug-in').value;\r\nvar sep=this.el('us-slug-sep').value;\r\nvar max=parseInt(this.el('us-slug-max').value,10)||0;\r\nvar useLower=this.el('us-slug-case').value==='lower';\r\nvar dropStop=this.el('us-slug-stop').checked;\r\n\r\nif(!raw.replace(\/^\\s+|\\s+$\/g,'')){\r\nthis.el('us-slug-out').textContent='\\u2014';\r\nthis.el('us-slug-len').textContent='0 chars';\r\nthis.el('us-slug-words').textContent='0';\r\nthis.el('us-slug-accents').textContent='0';\r\nreturn '';\r\n}\r\n\r\nvar s=raw;\r\nvar accentCount=0;\r\nvar i,out='';\r\nfor(i=0;i<s.length;i++){\r\nvar ch=s.charAt(i);\r\nvar lowerCh=ch.toLowerCase();\r\nif(ACCENTS.hasOwnProperty(lowerCh)){\r\naccentCount++;\r\nvar rep=ACCENTS[lowerCh];\r\nout+=(ch===lowerCh)?rep:(rep.charAt(0).toUpperCase()+rep.slice(1));\r\n}else{\r\nout+=ch;\r\n}\r\n}\r\ns=out;\r\nif(useLower)s=s.toLowerCase();\r\n\/* strip apostrophes so \"website's\" becomes \"websites\" not \"website-s\" *\/\r\ns=s.replace(\/['\\u2019]\/g,'');\r\n\/* anything not alphanumeric becomes a separator *\/\r\ns=s.replace(\/[^a-zA-Z0-9]+\/g,' ');\r\ns=s.replace(\/^\\s+|\\s+$\/g,'');\r\n\r\nvar words=s.length?s.split(\/\\s+\/):[];\r\nif(dropStop&&words.length>1){\r\nvar kept=[],j;\r\nfor(j=0;j<words.length;j++){\r\nif(STOPWORDS.indexOf(words[j].toLowerCase())===-1)kept.push(words[j]);\r\n}\r\nif(kept.length)words=kept;\r\n}\r\n\r\nvar slug=words.join(sep);\r\n\/* truncate on a word boundary, never mid-word *\/\r\nif(max>0&&slug.length>max){\r\nvar acc=[],len=0;\r\nfor(i=0;i<words.length;i++){\r\nvar add=(acc.length?1:0)+words[i].length;\r\nif(len+add>max)break;\r\nacc.push(words[i]);\r\nlen+=add;\r\n}\r\nif(!acc.length)acc=[words[0].slice(0,max)];\r\nslug=acc.join(sep);\r\nwords=acc;\r\n}\r\n\r\nthis.el('us-slug-out').textContent=slug||'\\u2014';\r\nthis.el('us-slug-len').textContent=slug.length+' chars';\r\nthis.el('us-slug-words').textContent=words.length;\r\nthis.el('us-slug-accents').textContent=accentCount;\r\nreturn slug;\r\n},\r\n\r\n\/* ---------- TAB 6: LINK INSPECTOR ---------- *\/\r\ninspect:function(){\r\nvar raw=this.el('us-insp-in').value.replace(\/^\\s+|\\s+$\/g,'');\r\nvar box=this.el('us-insp-out');\r\nvar detail=this.el('us-insp-detail');\r\ndetail.innerHTML='';\r\nif(!raw){\r\nbox.innerHTML='<div class=\"us-empty\">Paste a URL above and click Inspect Link.<\/div>';\r\nreturn;\r\n}\r\nvar flags=[];\r\nvar u=null;\r\ntry{u=new URL(raw);}catch(e){u=null;}\r\n\r\n\/* 1. scheme *\/\r\nvar lowered=raw.toLowerCase();\r\nif(lowered.indexOf('javascript:')===0){\r\nflags.push(['danger','Executable scheme','This is a <code>javascript:<\/code> URL. It does not go to a website &mdash; it runs code in whatever page you are currently on. Legitimate links never use this.']);\r\n}else if(lowered.indexOf('data:')===0){\r\nflags.push(['danger','Data URI','This is a <code>data:<\/code> URL. It carries its own embedded content rather than pointing at a server, and is a known way to serve a fake login page without hosting one.']);\r\n}else if(u&&u.protocol==='http:'){\r\nflags.push(['warn','Unencrypted HTTP','Traffic to this link is not encrypted. Anything you type on the page can be read in transit. Not an attack in itself, but no legitimate login page should use it.']);\r\n}\r\n\r\n\/* 2. credential trick *\/\r\nif(u&&u.username){\r\nflags.push(['danger','Credentials in URL (the @ trick)','Everything before the <code>@<\/code> is a username, not the destination. The real host is <strong>'+this.esc(u.hostname)+'<\/strong>. Attackers put a trusted-looking name in front of the @ so the link reads like a familiar site.']);\r\n}\r\n\r\n\/* 3. punycode \/ homograph *\/\r\nif(u){\r\nvar host=u.hostname;\r\nif(host.toLowerCase().indexOf('xn--')>=0){\r\nvar decoded=punycodeDecodeHost(host);\r\nvar scripts=detectScripts(decoded);\r\nvar scriptNote='';\r\nif(scripts.length>1){\r\nscriptNote=' It mixes '+scripts.join(' and ')+' characters in one name, which is the signature of a homograph attack &mdash; a lookalike domain built from characters that render almost identically to Latin ones.';\r\nflags.push(['danger','Punycode homograph domain','The host <code>'+this.esc(host)+'<\/code> actually displays as <strong>'+this.esc(decoded)+'<\/strong>.'+scriptNote]);\r\n}else{\r\nflags.push(['warn','Punycode (internationalised) domain','The host <code>'+this.esc(host)+'<\/code> displays as <strong>'+this.esc(decoded)+'<\/strong>. This is legitimate for non-Latin domain names, but verify it is the site you expect.']);\r\n}\r\n}\r\n}\r\n\r\n\/* 4. raw IP host *\/\r\nif(u&&\/^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$\/.test(u.hostname)){\r\nflags.push(['warn','Raw IP address as host','The link points to an IP address rather than a domain name. Real services almost always use a domain. This is common in phishing and in links to compromised devices.']);\r\n}\r\n\r\n\/* 5. double encoding *\/\r\nvar dblRe=\/%25[0-9A-Fa-f]{2}\/;\r\nif(dblRe.test(raw)){\r\nvar once=raw;\r\ntry{once=decodeURIComponent(raw);}catch(e){}\r\nvar twice=once;\r\ntry{twice=decodeURIComponent(once);}catch(e){}\r\nflags.push(['danger','Double percent-encoding','The URL contains <code>%25<\/code> sequences, meaning a <code>%<\/code> has itself been encoded. Decoding twice reveals: <code>'+this.esc(twice.length>200?twice.slice(0,200)+'...':twice)+'<\/code><br>This is used to slip a payload past a filter that only decodes once.']);\r\n}\r\n\r\n\/* 6. suspicious open redirect params *\/\r\nif(u){\r\nvar redirKeys=['url','redirect','redirect_uri','next','target','dest','destination','continue','returnurl','return_url','to','goto','r'];\r\nvar hits=[];\r\nvar selfRef=this;\r\nu.searchParams.forEach(function(v,k){\r\nif(redirKeys.indexOf(k.toLowerCase())>=0&&v){\r\nvar dv=v;\r\ntry{dv=decodeURIComponent(v);}catch(e){}\r\nif(\/^https?:\/i.test(dv)||\/^%2f%2f\/i.test(v)||dv.indexOf('\/\/')===0){\r\nhits.push(k+' \\u2192 '+dv);\r\n}\r\n}\r\n});\r\nif(hits.length){\r\nflags.push(['warn','Possible open redirect','A parameter carries another URL as its value: <code>'+this.esc(hits.join(', '))+'<\/code>. The link starts at one domain and bounces you to another. Legitimate on login flows, but also the standard way to make a hostile link look like it points somewhere trusted.']);\r\n}\r\n}\r\n\r\n\/* 7. excessive encoding density *\/\r\nvar pctCount=(raw.match(\/%[0-9A-Fa-f]{2}\/g)||[]).length;\r\nif(pctCount>=15){\r\nflags.push(['warn','Heavy percent-encoding','The URL contains '+pctCount+' encoded sequences. Obfuscating an otherwise readable link is a common way to hide its true destination from a casual glance.']);\r\n}\r\n\r\n\/* 8. long subdomain chain *\/\r\nif(u){\r\nvar labels=u.hostname.split('.');\r\nif(labels.length>=5){\r\nflags.push(['warn','Deep subdomain chain','The host has '+labels.length+' labels. Attackers pad a hostname with trusted-looking subdomains (<code>paypal.com.secure-login.example.net<\/code>) so the familiar name appears first and the real domain is pushed out of view. The registrable domain here is the <em>last two<\/em> labels: <strong>'+this.esc(labels.slice(-2).join('.'))+'<\/strong>.']);\r\n}\r\n}\r\n\r\nif(!u){\r\nflags.push(['neutral','Not a parseable URL','This string could not be parsed as a URL. If it should be one, it is probably missing a protocol such as <code>https:\/\/<\/code>.']);\r\n}\r\n\r\nif(!flags.length){\r\nflags.push(['safe','No structural red flags found','Nothing in the structure of this URL looks like a disguise technique. That is not the same as safe &mdash; a plain, honest-looking URL can still host a hostile page. This check reads the string, it does not visit the site.']);\r\n}\r\n\r\nvar html='',i;\r\nfor(i=0;i<flags.length;i++){\r\nhtml+='<div class=\"us-flag us-'+flags[i][0]+'\"><strong>'+flags[i][1]+'<\/strong>'+flags[i][2]+'<\/div>';\r\n}\r\nbox.innerHTML=html;\r\n\r\nif(u){\r\ndetail.innerHTML='<div class=\"us-sec\">Resolved Destination<\/div>'+\r\n'<table class=\"us-tbl\"><tbody>'+\r\n'<tr><td style=\"width:35%;\"><strong>Real host<\/strong><\/td><td class=\"us-mono\">'+this.esc(u.hostname)+'<\/td><\/tr>'+\r\n'<tr><td><strong>Displays as<\/strong><\/td><td class=\"us-mono\">'+this.esc(punycodeDecodeHost(u.hostname))+'<\/td><\/tr>'+\r\n'<tr><td><strong>Protocol<\/strong><\/td><td class=\"us-mono\">'+this.esc(u.protocol)+'<\/td><\/tr>'+\r\n'<tr><td><strong>Path<\/strong><\/td><td class=\"us-mono\">'+this.esc(u.pathname)+'<\/td><\/tr>'+\r\n'<\/tbody><\/table>';\r\n}\r\n},\r\n\r\n\/* ---------- GLOBAL ---------- *\/\r\nloadSample:function(){\r\nthis.el('us-in').value='https:\/\/example.com\/search?q=caf\u00e9 latte&price=100%';\r\nthis.setDir('encode');\r\nthis.setEncMode('component');\r\nthis.el('us-parse-in').value='https:\/\/user@shop.example.com:8443\/products\/list?category=coffee%20beans&sort=price&page=2#reviews';\r\nthis.parseURL();\r\nthis.el('us-qb-base').value='https:\/\/example.com\/products';\r\nthis.qbRows=[{k:'category',v:'coffee beans'},{k:'sort',v:'price'}];\r\nthis.renderQB();this.buildQuery();\r\nthis.el('us-utm-base').value='https:\/\/example.com\/spring-offer';\r\nthis.el('us-utm-source').value='newsletter';\r\nthis.el('us-utm-medium').value='email';\r\nthis.el('us-utm-campaign').value='spring_sale_2026';\r\nthis.buildUTM();\r\nthis.el('us-slug-in').value=\"10 Ways to Improve Your Website's Speed in 2026!\";\r\nthis.buildSlug();\r\nthis.el('us-insp-in').value='https:\/\/google.com@xn--80ak6aa92e.com\/verify';\r\nthis.inspect();\r\nthis.toast('Sample loaded across all tabs','ok');\r\n},\r\n\r\nclearAll:function(){\r\nthis.el('us-in').value='';\r\nthis.el('us-parse-in').value='';\r\nthis.el('us-qb-base').value='https:\/\/example.com\/page';\r\nthis.qbRows=[{k:'',v:''}];\r\nthis.el('us-utm-base').value='';\r\nthis.el('us-utm-source').value='';\r\nthis.el('us-utm-medium').value='';\r\nthis.el('us-utm-campaign').value='';\r\nthis.el('us-utm-term').value='';\r\nthis.el('us-utm-content').value='';\r\nthis.el('us-slug-in').value='';\r\nthis.el('us-insp-in').value='';\r\nthis.el('us-insp-out').innerHTML='<div class=\"us-empty\">Paste a URL above and click Inspect Link.<\/div>';\r\nthis.el('us-insp-detail').innerHTML='';\r\nthis.el('us-utm-warn').innerHTML='';\r\nthis.processCode();\r\nthis.parseURL();\r\nthis.renderQB();\r\nthis.buildQuery();\r\nthis.buildUTM();\r\nthis.buildSlug();\r\nthis.toast('Cleared','ok');\r\n},\r\n\r\nbindEvents:function(){\r\nvar self=this;\r\nvar w=this.el('pth-urls-wrapper');\r\nif(!w||w.getAttribute('data-bound')==='1')return;\r\nw.setAttribute('data-bound','1');\r\n\r\nvar i,tabs=w.querySelectorAll('.us-tab');\r\nfor(i=0;i<tabs.length;i++){\r\ntabs[i].addEventListener('click',function(){self.switchTab(this.getAttribute('data-tab'));});\r\n}\r\nthis.el('us-dir-enc').addEventListener('click',function(){self.setDir('encode');});\r\nthis.el('us-dir-dec').addEventListener('click',function(){self.setDir('decode');});\r\nvar ems=w.querySelectorAll('.us-mode[data-mode]');\r\nfor(i=0;i<ems.length;i++){\r\nems[i].addEventListener('click',function(){self.setEncMode(this.getAttribute('data-mode'));});\r\n}\r\nvar dms=w.querySelectorAll('.us-mode[data-dmode]');\r\nfor(i=0;i<dms.length;i++){\r\ndms[i].addEventListener('click',function(){self.setDecMode(this.getAttribute('data-dmode'));});\r\n}\r\nvar samples=w.querySelectorAll('.us-chip[data-sample]');\r\nfor(i=0;i<samples.length;i++){\r\nsamples[i].addEventListener('click',function(){\r\nself.el('us-in').value=this.getAttribute('data-sample');\r\nself.processCode();\r\n});\r\n}\r\nthis.el('us-in').addEventListener('input',function(){self.processCode();});\r\nthis.el('us-copy').addEventListener('click',function(){self.copy(self.el('us-out').value,'Output copied');});\r\nthis.el('us-swap').addEventListener('click',function(){\r\nvar o=self.el('us-out');\r\nif(o.classList.contains('us-err')||!o.value){self.toast('Nothing to send','err');return;}\r\nself.el('us-in').value=o.value;\r\nself.setDir(self.dir==='encode'?'decode':'encode');\r\n});\r\n\r\nthis.el('us-parse-in').addEventListener('input',function(){self.parseURL();});\r\n\r\nthis.el('us-qb-base').addEventListener('input',function(){self.buildQuery();});\r\nthis.el('us-qb-add').addEventListener('click',function(){\r\nself.qbRows.push({k:'',v:''});\r\nself.renderQB();self.buildQuery();\r\n});\r\nthis.el('us-qb-import').addEventListener('click',function(){self.importParams();});\r\nthis.el('us-qb-copy').addEventListener('click',function(){self.copy(self.buildQuery(),'URL copied');});\r\n\r\nvar utmIds=['us-utm-base','us-utm-source','us-utm-medium','us-utm-campaign','us-utm-term','us-utm-content'];\r\nfor(i=0;i<utmIds.length;i++){\r\nthis.el(utmIds[i]).addEventListener('input',function(){self.buildUTM();});\r\n}\r\nthis.el('us-utm-lower').addEventListener('change',function(){self.buildUTM();});\r\nthis.el('us-utm-copy').addEventListener('click',function(){\r\nvar v=self.buildUTM();\r\nif(!v){self.toast('Fill the required fields first','err');return;}\r\nself.copy(v,'Campaign URL copied');\r\n});\r\n\r\nvar slugIds=['us-slug-in','us-slug-max'];\r\nfor(i=0;i<slugIds.length;i++){\r\nthis.el(slugIds[i]).addEventListener('input',function(){self.buildSlug();});\r\n}\r\nthis.el('us-slug-sep').addEventListener('change',function(){self.buildSlug();});\r\nthis.el('us-slug-case').addEventListener('change',function(){self.buildSlug();});\r\nthis.el('us-slug-stop').addEventListener('change',function(){self.buildSlug();});\r\nthis.el('us-slug-copy').addEventListener('click',function(){self.copy(self.buildSlug(),'Slug copied');});\r\n\r\nthis.el('us-insp-run').addEventListener('click',function(){self.inspect();});\r\nvar insps=w.querySelectorAll('.us-chip[data-insp]');\r\nfor(i=0;i<insps.length;i++){\r\ninsps[i].addEventListener('click',function(){\r\nself.el('us-insp-in').value=this.getAttribute('data-insp');\r\nself.inspect();\r\n});\r\n}\r\n\r\nthis.el('us-sample').addEventListener('click',function(){self.loadSample();});\r\nthis.el('us-clear').addEventListener('click',function(){self.clearAll();});\r\nthis.el('us-fw').addEventListener('click',function(){\r\nvar g=self.el('us-grid');\r\ng.classList.toggle('us-fw');\r\nthis.textContent=g.classList.contains('us-fw')?'Restore Sidebar':'Full Width';\r\n});\r\n},\r\n\r\ninit:function(){\r\nthis.bindEvents();\r\nthis.setEncMode('component');\r\nthis.buildUTMPresets();\r\nthis.renderQB();\r\nthis.buildQuery();\r\nthis.buildSlug();\r\nthis.processCode();\r\n}\r\n};\r\n\r\nfunction pthUSInit(){\r\nif(!window.pthUrlStudioApp.initialized&&document.getElementById('pth-urls-wrapper')){\r\nwindow.pthUrlStudioApp.initialized=true;\r\nwindow.pthUrlStudioApp.init();\r\n}\r\n}\r\npthUSInit();\r\nif(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',pthUSInit);}\r\nwindow.addEventListener('load',function(){setTimeout(pthUSInit,50);});\r\nif(!window.pthUrlStudioApp.initialized&&typeof MutationObserver!=='undefined'){\r\nvar usObs=new MutationObserver(function(){\r\nif(document.getElementById('pth-urls-wrapper')){usObs.disconnect();pthUSInit();}\r\n});\r\nusObs.observe(document.body||document.documentElement,{childList:true,subtree:true});\r\nsetTimeout(function(){usObs.disconnect();},10000);\r\n}\r\n})();\r\n<\/script>\n\n\n\n\n<style>\n.pth-feature-wrap{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px;margin-bottom:40px}\n.pth-feat-box{border:1px solid #e2e8f0;background:#fff;padding:24px;border-radius:12px;box-shadow:0 4px 6px -1px rgba(0,0,0,0.02);transition:all .3s ease}\n.pth-feat-box:hover{border-color:#bfdbfe;transform:translateY(-3px);box-shadow:0 10px 15px -3px rgba(0,0,0,0.05)}\n.pth-feat-title{font-size:1.05rem;font-weight:800;color:#0f172a;margin:0 0 10px;display:flex;align-items:center;gap:10px}\n.pth-feat-text{font-size:.9rem;color:#1e293b;line-height:1.6;margin:0;font-weight:500}\n.pth-steps-wrap{border:1px solid #e2e8f0;background:#fff;padding:40px 30px;border-radius:16px;text-align:center;box-shadow:0 4px 6px -1px rgba(0,0,0,0.02);margin-bottom:40px;width:100%;box-sizing:border-box}\n.pth-steps-title{font-size:1.2rem;font-weight:800;color:#0f172a;text-transform:uppercase;margin:0 0 35px;letter-spacing:1px}\n.pth-steps-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:30px}\n.pth-step-item{display:flex;flex-direction:column;align-items:center}\n.pth-step-num{width:45px;height:45px;background:#2563eb;color:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:1.2rem;margin-bottom:15px;box-shadow:0 4px 10px rgba(37,99,235,.3)}\n.pth-step-name{font-weight:800;color:#0f172a;margin:0 0 8px;font-size:1rem}\n.pth-step-desc{font-size:.9rem;color:#1e293b;line-height:1.5;margin:0;font-weight:500}\n<\/style>\n<div class=\"pth-feature-wrap\">\n  <div class=\"pth-feat-box\">\n    <p class=\"pth-feat-title\"><span aria-hidden=\"true\">\ud83d\udfe2<\/span> Four Encoders, Not One<\/p>\n    <p class=\"pth-feat-text\">Most tools give you a single Encode button and leave you to discover the hard way that it mangled your URL. This one exposes all four: <code>encodeURIComponent<\/code>, <code>encodeURI<\/code>, strict RFC 3986, and form encoding where a space becomes a plus sign. Each has a plain-English note explaining exactly when it is the right choice and when it will break things.<\/p>\n  <\/div>\n  <div class=\"pth-feat-box\">\n    <p class=\"pth-feat-title\"><span aria-hidden=\"true\">\ud83d\udd35<\/span> Build Links, Not Just Read Them<\/p>\n    <p class=\"pth-feat-text\">The Query Builder lets you add, edit and delete parameters in a table and rebuilds the URL as you type, encoding keys and values correctly. The UTM Builder does the same for campaign tags, warns when a required field is missing, and forces lowercase \u2014 because analytics tools treat <code>Email<\/code> and <code>email<\/code> as two different sources and quietly split your reporting in half.<\/p>\n  <\/div>\n  <div class=\"pth-feat-box\">\n    <p class=\"pth-feat-title\"><span aria-hidden=\"true\">\ud83d\udfe3<\/span> A Link Inspector That Decodes Punycode<\/p>\n    <p class=\"pth-feat-text\">Paste a suspicious link and the inspector decodes the hostname, flags the <code>@<\/code> credential trick, spots double percent-encoding, and detects homograph domains \u2014 Cyrillic letters dressed up as Latin ones. It runs entirely in your browser and never contacts the link, so it reads the structure rather than pretending to judge the site.<\/p>\n  <\/div>\n<\/div>\n \n<div class=\"pth-steps-wrap\">\n  <p class=\"pth-steps-title\">How to Use URL Studio Pro<\/p>\n  <div class=\"pth-steps-grid\">\n    <div class=\"pth-step-item\">\n      <div class=\"pth-step-num\" aria-hidden=\"true\">1<\/div>\n      <p class=\"pth-step-name\">Pick the Job<\/p>\n      <p class=\"pth-step-desc\">Six tabs: encode and decode, parse a URL apart, build a query string, tag a campaign, make a slug, or inspect a suspicious link. Load Sample fills every tab with a worked example at once.<\/p>\n    <\/div>\n    <div class=\"pth-step-item\">\n      <div class=\"pth-step-num\" aria-hidden=\"true\">2<\/div>\n      <p class=\"pth-step-name\">Choose the Right Mode<\/p>\n      <p class=\"pth-step-desc\">In Encode, the four mode buttons change the result. Read the note underneath before picking \u2014 using encodeURIComponent on a whole URL is the single most common mistake in this area.<\/p>\n    <\/div>\n    <div class=\"pth-step-item\">\n      <div class=\"pth-step-num\" aria-hidden=\"true\">3<\/div>\n      <p class=\"pth-step-name\">Work Live<\/p>\n      <p class=\"pth-step-desc\">Everything updates as you type. Send Output to Input flips the direction so you can round-trip a value and confirm it survives encoding and decoding unchanged.<\/p>\n    <\/div>\n    <div class=\"pth-step-item\">\n      <div class=\"pth-step-num\" aria-hidden=\"true\">4<\/div>\n      <p class=\"pth-step-name\">Copy and Go<\/p>\n      <p class=\"pth-step-desc\">Every tab has a copy button. In Query Builder you can also import the parameters straight from whatever URL you pasted into the Parser tab, then edit them.<\/p>\n    <\/div>\n  <\/div>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Last updated: August 2026<\/p>\n\n\n\n<h2 id=\"\ud83d\udd34-encode-uri-or-encode-uri-component-get-this-wrong-and-your-url-breaks\" class=\"wp-block-heading\">\ud83d\udd34&nbsp;<strong>encodeURI or encodeURIComponent? Get This Wrong and Your URL Breaks<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This is the mistake that fills Stack Overflow. Both functions percent-encode a string. They escape different characters, and picking the wrong one produces either a broken URL or a security hole.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Take&nbsp;<code>https:\/\/example.com\/search?q=hello world<\/code>. Run it through&nbsp;<strong>encodeURI<\/strong>&nbsp;and you get&nbsp;<code>https:\/\/example.com\/search?q=hello%20world<\/code>&nbsp;\u2014 the space is fixed and the structure survives, because encodeURI deliberately leaves&nbsp;<code>: \/ ? # &amp; =<\/code>&nbsp;alone. Run the same string through&nbsp;<strong>encodeURIComponent<\/strong>&nbsp;and you get&nbsp;<code>https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dhello%20world<\/code>. The colons, slashes and question mark are all escaped, and the result is no longer a URL at all \u2014 it is a single opaque string.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The rule is simple once you see it:&nbsp;<strong>encodeURI is for a whole URL. encodeURIComponent is for one value going inside a URL.<\/strong>&nbsp;If you are building&nbsp;<code>?q=<\/code>&nbsp;plus some user input, that input is a component \u2014 encode it with encodeURIComponent, or a user typing&nbsp;<code>a&amp;b=c<\/code>&nbsp;will inject an extra parameter into your query string. If you have a complete URL that just contains a space or an accented character, encodeURI is what you want.<\/p>\n\n\n\n<h3 id=\"\ud83d\udfe2-and-the-two-nobody-mentions\" class=\"wp-block-heading\">\ud83d\udfe2&nbsp;<strong>And the Two Nobody Mentions<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>RFC 3986 strict<\/strong>&nbsp;exists because JavaScript&#8217;s built-in encodeURIComponent is not actually fully compliant with the spec. It leaves&nbsp;<code>! ' ( ) *<\/code>&nbsp;unencoded. Ninety-nine percent of the time nobody notices. The exception is when a strict server-side parser, an OAuth signature, or an AWS request signature is on the receiving end \u2014 those compute a hash over the encoded string, and a single unescaped bracket produces a signature mismatch and a baffling 403. The strict mode here escapes those five characters too.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Form encoding<\/strong>&nbsp;is the one that turns a space into&nbsp;<code>+<\/code>&nbsp;instead of&nbsp;<code>%20<\/code>. This is what an HTML form actually submits, and it is correct&nbsp;<em>only<\/em>&nbsp;for an&nbsp;<code>application\/x-www-form-urlencoded<\/code>&nbsp;request body. Use it in a normal URL path and the&nbsp;<code>+<\/code>&nbsp;stays a literal plus sign rather than becoming a space, which is exactly the kind of bug that takes an afternoon to find.<\/p>\n\n\n\n<h2 id=\"\ud83d\udfe1-the-double-encoding-trap\" class=\"wp-block-heading\">\ud83d\udfe1&nbsp;<strong>The Double-Encoding Trap<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Encode a string twice and you get nonsense. A space becomes&nbsp;<code>%20<\/code>. Encode that again and the&nbsp;<code>%<\/code>&nbsp;itself gets escaped, giving&nbsp;<code>%2520<\/code>. Now anything that decodes it once sees the literal text&nbsp;<code>%20<\/code>&nbsp;rather than a space, and the value is quietly wrong.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This happens constantly in real systems. A frontend encodes a parameter, an API gateway encodes the whole URL again on the way through, and the backend receives&nbsp;<code>%2520<\/code>. The usual symptom is a search that returns nothing for a query containing a space, or a redirect that lands on a 404 with a strange-looking path. If you ever see&nbsp;<code>%25<\/code>&nbsp;in a URL that you did not deliberately put there, something in the chain has encoded an already-encoded string.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There is a security dimension too, which is why the Link Inspector flags it. A filter that blocks&nbsp;<code>..\/<\/code>&nbsp;will happily pass&nbsp;<code>%252e%252e%252f<\/code>. It decodes once, sees&nbsp;<code>%2e%2e%2f<\/code>, decides that is harmless, and passes it on \u2014 and something downstream decodes it a second time into a directory traversal. Double-decode anything you receive before you validate it, not after.<\/p>\n\n\n\n<h2 id=\"\ud83d\udd34-the-domain-that-looks-exactly-like-another-domain\" class=\"wp-block-heading\">\ud83d\udd34&nbsp;<strong>The Domain That Looks Exactly Like Another Domain<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Domain names can only contain ASCII. To support non-Latin scripts, the internet uses Punycode: an encoding that turns a Unicode name into an ASCII string beginning with&nbsp;<code>xn--<\/code>. Your browser then displays the decoded version. This is genuinely useful \u2014 it is how domains in Sinhala, Arabic and Chinese work at all.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It also enables the homograph attack. Cyrillic&nbsp;<strong>\u0430<\/strong>&nbsp;(U+0430) and Latin&nbsp;<strong>a<\/strong>&nbsp;(U+0061) are different characters that render almost identically in most fonts. Register a domain using the Cyrillic one and you get a name that is visually indistinguishable from the real thing but is a completely different domain, owned by someone else. The classic demonstration is&nbsp;<code>xn--80ak6aa92e.com<\/code>, which browsers once rendered as something a person would read as &#8220;apple.com&#8221; without hesitation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The Link Inspector decodes any&nbsp;<code>xn--<\/code>&nbsp;label and then checks which writing systems the result actually uses. A name mixing Cyrillic and Latin characters in a single label is flagged, because legitimate domains do not do that \u2014 a real German or Spanish domain is entirely Latin, and a real Russian one is entirely Cyrillic. Mixed scripts inside one label is the signature of a lookalike. Browsers now apply similar rules and often display the raw punycode when they see mixed scripts, but not always, and not on every platform.<\/p>\n\n\n\n<h3 id=\"\ud83d\udfe2-the-other-disguises-it-catches\" class=\"wp-block-heading\">\ud83d\udfe2&nbsp;<strong>The Other Disguises It Catches<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83d\udd35\u00a0<strong>The @ trick.<\/strong>\u00a0In\u00a0<code>https:\/\/google.com@evil.com\/login<\/code>, everything before the\u00a0<code>@<\/code>\u00a0is a username, not the destination. You are going to\u00a0<strong>evil.com<\/strong>. The familiar name is decoration.<\/li>\n\n\n\n<li>\ud83d\udfe0\u00a0<strong>Subdomain padding.<\/strong>\u00a0In\u00a0<code>paypal.com.secure-login.example.net<\/code>, the registrable domain is\u00a0<code>example.net<\/code>\u00a0\u2014 the last two labels. Everything before it is a subdomain the attacker controls and can name anything they like.<\/li>\n\n\n\n<li>\ud83d\udfe3\u00a0<strong>Open redirects.<\/strong>\u00a0A parameter like\u00a0<code>?next=https:\/\/evil.com<\/code>\u00a0means the link starts on a domain you trust and bounces you somewhere you do not. The starting domain is real, which is precisely what makes it effective.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">One honest limitation, stated plainly in the tool as well: this is&nbsp;<em>structural<\/em>&nbsp;analysis. It runs in your browser and never contacts the link, because doing so would require a network request and break the offline guarantee. It cannot follow redirects, check a blocklist, or scan a page. A clean result means no disguise tricks were found in the string \u2014 not that the destination is safe.<\/p>\n\n\n\n<h2 id=\"\ud83d\udfe1-utm-tags-and-slugs-the-boring-details-that-matter\" class=\"wp-block-heading\">\ud83d\udfe1&nbsp;<strong>UTM Tags and Slugs: The Boring Details That Matter<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">UTM parameters tell your analytics where a visitor came from. Three are effectively required:&nbsp;<strong>source<\/strong>&nbsp;(where \u2014 google, newsletter),&nbsp;<strong>medium<\/strong>&nbsp;(how \u2014 cpc, email, social), and&nbsp;<strong>campaign<\/strong>&nbsp;(which push \u2014 spring_sale_2026). Two are optional:&nbsp;<strong>term<\/strong>&nbsp;for a paid keyword and&nbsp;<strong>content<\/strong>&nbsp;for distinguishing A\/B variants of the same ad.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The mistake that ruins reporting is capitalisation. Analytics platforms are case-sensitive on these values, so&nbsp;<code>Email<\/code>,&nbsp;<code>email<\/code>&nbsp;and&nbsp;<code>EMAIL<\/code>&nbsp;become three separate sources in your dashboard, each showing a third of the real traffic. Nothing errors, nothing warns you, and the numbers are simply wrong. The builder here forces lowercase by default for that reason, and you can turn it off if you have a specific reason to.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Slugs have their own small traps. Accented characters need transliterating rather than encoding \u2014&nbsp;<code>caf\u00e9<\/code>&nbsp;should become&nbsp;<code>cafe<\/code>, not&nbsp;<code>caf%C3%A9<\/code>, which is technically valid but hideous in a search result. Apostrophes should vanish rather than becoming separators, so&nbsp;<code>website's<\/code>&nbsp;gives&nbsp;<code>websites<\/code>&nbsp;and not&nbsp;<code>website-s<\/code>. And a length cap must break at a word boundary, because truncating mid-word produces a slug ending in a fragment. The generator here handles all three. For the page&#8217;s title tag, description and search snippet, use the&nbsp;<a href=\"https:\/\/schoolict.net\/tools\/free-seo-meta-tags-generator-preview\/\">SEO Meta Tags Generator<\/a>&nbsp;\u2014 that is a different job from tagging the link that points at the page.<\/p>\n\n\n\n\n<style>\n.pth-faq-section{margin-top:50px;margin-bottom:40px;font-family:inherit}\n.pth-faq-header{font-size:1.8rem;font-weight:800;color:#0f172a;margin-bottom:25px;border-bottom:2px solid #e2e8f0;padding-bottom:10px;display:flex;align-items:center;gap:10px}\n.pth-faq-grid{display:grid;grid-template-columns:100%;gap:20px}\n@media(min-width:768px){.pth-faq-grid{grid-template-columns:repeat(2,1fr)}}\n@media(min-width:1024px){.pth-faq-grid{grid-template-columns:repeat(3,1fr)}}\n.pth-faq-card{background:#f8fafc;padding:24px;border-radius:12px;border:1px solid #e2e8f0;transition:transform .2s ease;break-inside:avoid}\n.pth-faq-card:hover{transform:translateY(-3px);box-shadow:0 4px 12px rgba(0,0,0,.05)}\n.pth-faq-q{color:#0f172a;font-size:1rem;font-weight:700;margin:0 0 12px;line-height:1.4}\n.pth-faq-a{margin:0;font-size:.95rem;color:#1e293b;line-height:1.6;font-weight:500}\n<\/style>\n<div class=\"pth-faq-section\">\n  <div class=\"pth-faq-header\"><span aria-hidden=\"true\">\u2753<\/span> Frequently Asked Questions<\/div>\n  <div class=\"pth-faq-grid\">\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Which encoder should I use for a query parameter value?<\/p>\n      <p class=\"pth-faq-a\"><code>encodeURIComponent<\/code>. It escapes <code>&amp;<\/code> and <code>=<\/code>, which is exactly what you need \u2014 otherwise a user typing <code>a&amp;b=c<\/code> injects an extra parameter into your query string. Never use <code>encodeURI<\/code> for a value; it leaves those characters intact.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Why is my space showing as + instead of %20?<\/p>\n      <p class=\"pth-faq-a\">Something used form encoding. The <code>+<\/code> for space convention belongs to <code>application\/x-www-form-urlencoded<\/code> request bodies, not to URLs generally. In a path segment, <code>+<\/code> stays a literal plus sign \u2014 which is a common and confusing bug.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">What does %2520 mean?<\/p>\n      <p class=\"pth-faq-a\">It is a double-encoded space. <code>%20<\/code> got encoded a second time, turning its <code>%<\/code> into <code>%25<\/code>. It usually means two layers of your stack are both encoding the same value. Decode twice to see the original.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Why does RFC 3986 strict mode exist if encodeURIComponent works?<\/p>\n      <p class=\"pth-faq-a\">Because encodeURIComponent leaves <code>! ' ( ) *<\/code> unescaped, which is not fully spec-compliant. This only matters when a strict parser or a request signature (OAuth, AWS) hashes the encoded string \u2014 then one unescaped bracket causes a signature mismatch and a 403 that is very hard to diagnose.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Can the Link Inspector tell me if a site is malicious?<\/p>\n      <p class=\"pth-faq-a\">No, and it says so plainly. It runs offline and never contacts the link, so it analyses the URL string for disguise techniques \u2014 punycode homographs, the @ trick, double encoding, open redirects. A clean result means no tricks were found, not that the destination is safe.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">What is punycode and why does xn-- appear in domains?<\/p>\n      <p class=\"pth-faq-a\">Domain names must be ASCII, so Unicode names are encoded into an ASCII form starting with <code>xn--<\/code>. Your browser decodes and displays the real name. It is legitimate and necessary \u2014 but also how lookalike domains built from Cyrillic characters are registered.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Do UTM tags need to be lowercase?<\/p>\n      <p class=\"pth-faq-a\">Effectively yes. Analytics platforms are case-sensitive on these values, so <code>Email<\/code> and <code>email<\/code> appear as two separate sources and split your traffic across two rows. Nothing warns you \u2014 the numbers just quietly become wrong.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Is my input sent anywhere?<\/p>\n      <p class=\"pth-faq-a\">No. Every tab runs as plain JavaScript in your browser, with no network requests after the page loads. That matters here, because URLs routinely contain API keys, session tokens and internal hostnames you should not paste into a random web form.<\/p>\n    <\/div>\n  <\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Make Any String Web-Safe \u2014 Precise URL encoding and decoding for web developers and API integration. Securely encode and decode URLs and text completely offline. Features live text-to-URL conversion, RFC 3986 strict mode, and real-time byte size calculations. Installation Guide \ud83d\udfe2 Four Encoders, Not One Most tools give you a single Encode button and leave &#8230; <a title=\"Universal URL Encoder &amp; Decoder\" class=\"read-more\" href=\"https:\/\/schoolict.net\/tools\/universal-url-encoder-decoder\/\" aria-label=\"Read more about Universal URL Encoder &amp; Decoder\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":775,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-773","page","type-page","status-publish","has-post-thumbnail"],"_links":{"self":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/773","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/comments?post=773"}],"version-history":[{"count":2,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/773\/revisions"}],"predecessor-version":[{"id":6992,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/773\/revisions\/6992"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/media\/775"}],"wp:attachment":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/media?parent=773"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}