Created
March 17, 2026 12:30
-
-
Save Taytay/f90391b0ea1bb5a9a16fcd67c7625844 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>URL Cleaner</title> | |
| <style> | |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
| background: #1a1a2e; | |
| color: #e0e0e0; | |
| min-height: 100vh; | |
| display: flex; | |
| justify-content: center; | |
| padding: 2rem 1rem; | |
| } | |
| .container { | |
| width: 100%; | |
| max-width: 720px; | |
| } | |
| h1 { | |
| font-size: 1.4rem; | |
| font-weight: 600; | |
| margin-bottom: 0.25rem; | |
| color: #fff; | |
| } | |
| .subtitle { | |
| font-size: 0.85rem; | |
| color: #888; | |
| margin-bottom: 1.5rem; | |
| } | |
| label { | |
| display: block; | |
| font-size: 0.8rem; | |
| font-weight: 500; | |
| color: #aaa; | |
| margin-bottom: 0.4rem; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| } | |
| textarea { | |
| width: 100%; | |
| padding: 0.75rem; | |
| font-family: "SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace; | |
| font-size: 0.85rem; | |
| line-height: 1.5; | |
| border: 1px solid #333; | |
| border-radius: 6px; | |
| background: #16213e; | |
| color: #e0e0e0; | |
| resize: vertical; | |
| outline: none; | |
| transition: border-color 0.15s; | |
| } | |
| textarea:focus { border-color: #4361ee; } | |
| #input { height: 160px; margin-bottom: 1rem; } | |
| #output { | |
| height: 80px; | |
| background: #0f3460; | |
| border-color: #1a4a7a; | |
| cursor: pointer; | |
| } | |
| .output-group { margin-bottom: 1rem; } | |
| .stats { | |
| font-size: 0.75rem; | |
| color: #666; | |
| margin-top: 0.4rem; | |
| min-height: 1.1em; | |
| } | |
| .stats .removed { color: #e07a5f; } | |
| .btn-row { | |
| display: flex; | |
| gap: 0.5rem; | |
| margin-bottom: 1.25rem; | |
| } | |
| button { | |
| padding: 0.5rem 1rem; | |
| font-size: 0.8rem; | |
| font-weight: 500; | |
| border: 1px solid #333; | |
| border-radius: 6px; | |
| background: #16213e; | |
| color: #e0e0e0; | |
| cursor: pointer; | |
| transition: background 0.15s, border-color 0.15s; | |
| } | |
| button:hover { background: #1a2a4e; border-color: #4361ee; } | |
| button.primary { | |
| background: #4361ee; | |
| border-color: #4361ee; | |
| color: #fff; | |
| } | |
| button.primary:hover { background: #3a56d4; } | |
| .toast { | |
| position: fixed; | |
| bottom: 1.5rem; | |
| left: 50%; | |
| transform: translateX(-50%) translateY(1rem); | |
| background: #4361ee; | |
| color: #fff; | |
| padding: 0.5rem 1.25rem; | |
| border-radius: 6px; | |
| font-size: 0.8rem; | |
| opacity: 0; | |
| transition: opacity 0.2s, transform 0.2s; | |
| pointer-events: none; | |
| } | |
| .toast.show { | |
| opacity: 1; | |
| transform: translateX(-50%) translateY(0); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>URL Cleaner</h1> | |
| <p class="subtitle">Paste a URL copied from Termius (or anywhere) to strip unprintable characters and line-wrapping whitespace.</p> | |
| <label for="input">Dirty URL</label> | |
| <textarea id="input" placeholder="Paste your URL here..." autofocus></textarea> | |
| <div class="btn-row"> | |
| <button class="primary" onclick="copyOutput()">Copy Clean URL</button> | |
| <button onclick="clearAll()">Clear</button> | |
| </div> | |
| <div class="output-group"> | |
| <label for="output">Clean URL</label> | |
| <textarea id="output" readonly onclick="copyOutput()"></textarea> | |
| <div class="stats" id="stats"></div> | |
| </div> | |
| </div> | |
| <div class="toast" id="toast">Copied!</div> | |
| <script> | |
| const inputEl = document.getElementById('input'); | |
| const outputEl = document.getElementById('output'); | |
| const statsEl = document.getElementById('stats'); | |
| const toastEl = document.getElementById('toast'); | |
| function stripUrl(raw) { | |
| let cleaned = ''; | |
| let removed = 0; | |
| for (const ch of raw) { | |
| const code = ch.codePointAt(0); | |
| // Keep: ASCII printable (0x21-0x7E) excluding space, | |
| // plus common URL-safe extended chars | |
| // Drop: whitespace, control chars, zero-width chars, soft hyphens, | |
| // BOM, direction marks, and other non-printable Unicode | |
| if ( | |
| (code >= 0x21 && code <= 0x7E) // ASCII printable (no space) | |
| ) { | |
| cleaned += ch; | |
| } else if ( | |
| code > 0x7E | |
| && !isNonPrintable(code) | |
| ) { | |
| // Non-ASCII printable (e.g. percent-encoded bytes already decoded, | |
| // international domain names). Keep them. | |
| cleaned += ch; | |
| } else { | |
| removed++; | |
| } | |
| } | |
| return { cleaned, removed }; | |
| } | |
| function isNonPrintable(code) { | |
| return ( | |
| code <= 0x1F // C0 controls | |
| || (code >= 0x7F && code <= 0x9F) // C1 controls | |
| || code === 0x00AD // soft hyphen | |
| || code === 0x200B // zero-width space | |
| || code === 0x200C // zero-width non-joiner | |
| || code === 0x200D // zero-width joiner | |
| || code === 0x200E // LTR mark | |
| || code === 0x200F // RTL mark | |
| || code === 0x2028 // line separator | |
| || code === 0x2029 // paragraph separator | |
| || code === 0x202A // LTR embedding | |
| || code === 0x202B // RTL embedding | |
| || code === 0x202C // pop directional | |
| || code === 0x202D // LTR override | |
| || code === 0x202E // RTL override | |
| || code === 0x2060 // word joiner | |
| || code === 0x2061 // function application | |
| || code === 0x2062 // invisible times | |
| || code === 0x2063 // invisible separator | |
| || code === 0x2064 // invisible plus | |
| || code === 0xFEFF // BOM / zero-width no-break space | |
| || (code >= 0xFFF0 && code <= 0xFFFF) // specials block | |
| || (code >= 0xE0000 && code <= 0xE007F) // tags block | |
| ); | |
| } | |
| function update() { | |
| const raw = inputEl.value; | |
| if (!raw.trim()) { | |
| outputEl.value = ''; | |
| statsEl.textContent = ''; | |
| return; | |
| } | |
| const { cleaned, removed } = stripUrl(raw); | |
| outputEl.value = cleaned; | |
| if (removed > 0) { | |
| statsEl.innerHTML = `<span class="removed">${removed} character${removed !== 1 ? 's' : ''} removed</span> · ${cleaned.length} chars`; | |
| } else { | |
| statsEl.textContent = `URL is clean · ${cleaned.length} chars`; | |
| } | |
| } | |
| function copyOutput() { | |
| const text = outputEl.value; | |
| if (!text) return; | |
| navigator.clipboard.writeText(text).then(() => { | |
| toastEl.classList.add('show'); | |
| setTimeout(() => toastEl.classList.remove('show'), 1200); | |
| }); | |
| } | |
| function clearAll() { | |
| inputEl.value = ''; | |
| outputEl.value = ''; | |
| statsEl.textContent = ''; | |
| inputEl.focus(); | |
| } | |
| inputEl.addEventListener('input', update); | |
| // Also clean on paste with a slight delay to catch the pasted value | |
| inputEl.addEventListener('paste', () => setTimeout(update, 0)); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment