<!doctype html>
<html lang="en-GB">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="/assets/js/theme.js?v=1791136928"></script>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src 'self' data: blob:; form-action 'none'; base-uri 'self'; object-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; media-src 'self' data: blob:; frame-src 'self' blob: data:">

<title> | ThoughtAsylum Toolbox</title>
<meta name="description" content="Handy tools for handy people: free encoders, formatters, converters, inspectors and generators that run entirely in your browser, online or off. Nothing you paste or choose is uploaded.">
<meta name="author" content="Stephen Millard">
<link rel="canonical" href="https://toolbox.thoughtasylum.com/assets/css/site.css">
<meta property="og:type" content="website">
<meta property="og:site_name" content="ThoughtAsylum Toolbox">
<meta property="og:locale" content="en_GB">
<meta property="og:title" content="ThoughtAsylum Toolbox">
<meta property="og:description" content="Handy tools for handy people: free encoders, formatters, converters, inspectors and generators that run entirely in your browser, online or off. Nothing you paste or choose is uploaded.">
<meta property="og:url" content="https://toolbox.thoughtasylum.com/assets/css/site.css">
<meta property="og:image" content="https://toolbox.thoughtasylum.com/assets/logo/social-card.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="The ThoughtAsylum Toolbox logo">
<meta name="twitter:card" content="summary_large_image">
<script type="application/ld+json">
{"@context":"https://schema.org","@graph":[
{"@type":"Person","@id":"https://toolbox.thoughtasylum.com/about/#author","name":"Stephen Millard","alternateName":"sylumer",
 "url":"https://www.stephenmillard.com","sameAs":["https://www.thoughtasylum.com","https://www.stephenmillard.com"]},
{"@type":"WebSite","@id":"https://toolbox.thoughtasylum.com/#site","name":"ThoughtAsylum Toolbox","url":"https://toolbox.thoughtasylum.com/",
 "description":"Handy tools for handy people: free encoders, formatters, converters, inspectors and generators that run entirely in your browser, online or off. Nothing you paste or choose is uploaded.","inLanguage":"en-GB","publisher":{"@id":"https://toolbox.thoughtasylum.com/about/#author"}}
]}
</script>

<link rel="icon" href="/favicon.ico" sizes="48x48">
<link rel="icon" href="/assets/logo/favicon-192.png" type="image/png" sizes="192x192">
<link rel="apple-touch-icon" href="/assets/logo/apple-touch-icon.png">
<link rel="manifest" href="/manifest.webmanifest">
<meta name="theme-color" content="#0b0d12" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#f4f6fa" media="(prefers-color-scheme: light)">
<link rel="preload" href="/assets/fonts/montserrat.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/assets/css/site.css?v=1791136928">
</head>
<body data-tool="">
<a class="skip" href="#content">Skip to content</a>
<div class="sr-only" id="site-live" role="status"></div>
<header class="site-head">
  <button type="button" class="icon-btn menu-btn" id="site-menu" aria-controls="site-side" aria-expanded="false" aria-label="Menu">
    <svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h16"/></svg></button>
  <a class="brand" href="/" aria-label="ThoughtAsylum Toolbox home">
    <img src="/assets/logo/toolbox-64.webp" srcset="/assets/logo/toolbox-128.webp 2x" width="64" height="52" alt="">
    <span class="wordmark"><b><span>thought</span><em>asylum</em></b><small>TOOLBOX</small></span>
</a><a class="preview-chip" href="/about/#preview" title="What the preview means">Preview</a>
  <button type="button" class="search-btn" data-open-search aria-label="Search tools">
    <svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
    <span>Search 89 tools</span><kbd class="site-key">⌘K</kbd></button>
  <div class="head-theme"><div class="theme-switch" role="group" aria-label="Theme">
  <button type="button" data-set-theme="system" aria-pressed="false" title="Match the system"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8M12 16v4"/></svg><span>System</span></button>
  <button type="button" data-set-theme="light" aria-pressed="false" title="Light"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg><span>Light</span></button>
  <button type="button" data-set-theme="dark" aria-pressed="false" title="Dark"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><span>Dark</span></button>
</div>
</div>
</header>
<div class="site-layout">
  <nav class="site-side" id="site-side" aria-label="Tools">
  <div class="side-theme"><div class="theme-switch" role="group" aria-label="Theme">
  <button type="button" data-set-theme="system" aria-pressed="false" title="Match the system"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8M12 16v4"/></svg><span>System</span></button>
  <button type="button" data-set-theme="light" aria-pressed="false" title="Light"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg><span>Light</span></button>
  <button type="button" data-set-theme="dark" aria-pressed="false" title="Dark"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><span>Dark</span></button>
</div>
</div>
  <details id="site-recent" data-cat="Recent" hidden><summary><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>
Recent<span class="count"></span></summary></details>
  <details data-cat="Encoding">
    <summary><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 8h15l-4-4M20 16H5l4 4"/></svg>
Encoding<span class="count">11</span></summary>
    <a href="/identify/">Identify</a>
    <a href="/text-base64/">Text ⇄ Base64</a>
    <a href="/image-base64/">Image → Base64</a>
    <a href="/base64-image/">Base64 → Image</a>
    <a href="/url/">URL Encode/Decode</a>
    <a href="/jwt/">JWT Decoder & Encoder</a>
    <a href="/cert/">Certificate Decoder</a>
    <a href="/url-parts/">URL Inspector</a>
    <a href="/escape/">String Escaper</a>
    <a href="/morse/">Morse Code</a>
    <a href="/compress/">Compress / Decompress</a>
  </details>
  <details data-cat="Ciphers">
    <summary><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="8" cy="15" r="4"/><path d="m10.8 12.2 9.2-9.2M17 6l3 3M14.5 8.5l2 2"/></svg>
Ciphers<span class="count">17</span></summary>
    <a href="/caesar/">Caesar / ROT13</a>
    <a href="/classic/">Classical Ciphers</a>
    <a href="/encrypt/">Encrypt / Decrypt</a>
    <a href="/stego/">Image Steganography</a>
    <a href="/crack/">Cipher Cracker</a>
    <a href="/enigma/">Enigma Machine</a>
    <a href="/hidden-text/">Hidden Text</a>
    <a href="/picture-alphabets/">Picture Alphabets</a>
    <a href="/visual-crypto/">Visual Cryptography</a>
    <a href="/image-cipher/">Image Encryption</a>
    <a href="/xor/">XOR Cipher</a>
    <a href="/secret-split/">Secret Splitter</a>
    <a href="/bitplane/">Bit-Plane Inspector</a>
    <a href="/spectrogram/">Spectrogram</a>
    <a href="/password/">Password Generator</a>
    <a href="/totp/">TOTP / HOTP Codes</a>
    <a href="/number-codes/">Number Codes</a>
  </details>
  <details data-cat="Data">
    <summary><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14c0 1.7 3.6 3 8 3s8-1.3 8-3V5M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3"/></svg>
Data<span class="count">11</span></summary>
    <a href="/json/">JSON Format</a>
    <a href="/xml/">XML Format</a>
    <a href="/code/">Code Format</a>
    <a href="/yaml/">JSON ⇄ YAML ⇄ TOML</a>
    <a href="/types/">JSON to Types</a>
    <a href="/csv/">CSV ⇄ JSON</a>
    <a href="/plist/">Plist ⇄ JSON</a>
    <a href="/graph/">Chart Maker</a>
    <a href="/pdf/">PDF Pages</a>
    <a href="/validate/">Data Validator</a>
    <a href="/test-data/">Test Data Generator</a>
  </details>
  <details data-cat="Developer">
    <summary><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="m7 9 3 3-3 3M13 15h4"/></svg>
Developer<span class="count">23</span></summary>
    <a href="/hash/">Hash / Checksum</a>
    <a href="/bcrypt/">Bcrypt</a>
    <a href="/uuid/">UUID Generator</a>
    <a href="/calculator/">Calculator</a>
    <a href="/timestamp/">Timestamp Converter</a>
    <a href="/truth-table/">Truth Table</a>
    <a href="/bases/">Number Bases</a>
    <a href="/http-codes/">HTTP Codes & MIME Types</a>
    <a href="/user-agent/">User Agent Parser</a>
    <a href="/keycode/">Key Codes</a>
    <a href="/curl/">cURL to Code</a>
    <a href="/units/">Unit Converter</a>
    <a href="/docker-compose/">docker run to Compose</a>
    <a href="/diff/">Diff Checker</a>
    <a href="/regex/">Regex Tester</a>
    <a href="/cron/">Cron Explainer</a>
    <a href="/chmod/">Permissions</a>
    <a href="/subnet/">Subnet Calculator</a>
    <a href="/dates/">Date Calculator</a>
    <a href="/paths/">Path Converter</a>
    <a href="/world-clock/">World Clock</a>
    <a href="/hex/">Hex Viewer</a>
    <a href="/scratchpad/">Web Scratchpad</a>
  </details>
  <details data-cat="Text">
    <summary><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 7V4h14v3M12 4v16M9 20h6"/></svg>
Text<span class="count">16</span></summary>
    <a href="/clean/">Text Cleaner</a>
    <a href="/preview/">Markdown & HTML Preview</a>
    <a href="/md-table/">Markdown Table</a>
    <a href="/chars/">Character Inspector</a>
    <a href="/case/">Case & Style</a>
    <a href="/phonetic/">Phonetic Spelling</a>
    <a href="/sort/">Sort Lines</a>
    <a href="/freq/">Frequency Analysis</a>
    <a href="/stats/">Text Statistics</a>
    <a href="/list-compare/">List Compare</a>
    <a href="/numerals/">Numerals</a>
    <a href="/random/">Random Picker</a>
    <a href="/anagram/">Anagram Solver</a>
    <a href="/crossword/">Crossword Helper</a>
    <a href="/markup/">Markup Converter</a>
    <a href="/redact/">Redact for AI</a>
  </details>
  <details data-cat="Images">
    <summary><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="2"/><path d="m21 15-5-5L5 20"/></svg>
Images<span class="count">11</span></summary>
    <a href="/image/">Image Convert / Resize</a>
    <a href="/svg/">SVG Preview & Export</a>
    <a href="/colour/">Colour Converter</a>
    <a href="/exif/">Photo Metadata</a>
    <a href="/palette/">Image Palette</a>
    <a href="/censor/">Photo Censor</a>
    <a href="/ocr/">Image to Text (OCR)</a>
    <a href="/qr/">QR Code</a>
    <a href="/barcode/">Barcodes</a>
    <a href="/gif/">Animated GIF Maker</a>
    <a href="/gif-frames/">GIF Frame Extractor</a>
  </details>
</nav>

  <div class="site-scrim" id="site-scrim"></div>
  <div class="site-body">
    <main id="content">
/* ThoughtAsylum Toolbox. Dark first; light from the system setting or the theme switch (data-theme on <html>).
   Token names match Concierge's (--bg --fg --card --muted --line --accent --accent2 --glow --ok --warn --err), so the
   tools' own CSS, included at the end, works unchanged. */
@font-face { font-family: Montserrat; src: url(/assets/fonts/montserrat.woff2) format("woff2"); font-weight: 500 600; font-display: swap; }

:root { color-scheme: dark;
  --bg: #0b0d12; --card: #12151c; --surface-2: #181c25; --line: #252a36; --field: #0e1117; --fg: #e9ecf2; --muted: #8b93a7;
  --accent: #0798F5; --accent-fg: #03121f; --accent2: #ad7bff; --glow: #39c6ff; --err: #ff6b7b; --ok: #4fd68b; --warn: #ffb74d;
  --thought: #F2F2F2; --sub: #D0D0D0; --brand-blue: #0798F5;
  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 16px 40px -20px rgba(0,0,0,.8);
  --head-h: 60px; --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --brand-font: Montserrat, "Avenir Next", "Segoe UI", sans-serif; }
@media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) { color-scheme: light;
  --bg: #f4f6fa; --card: #ffffff; --surface-2: #eef1f6; --line: #dde2ea; --field: #f9fafc; --fg: #161a22; --muted: #5d6578;
  --accent: #0574c4; --accent-fg: #ffffff; --accent2: #8a45f6; --glow: #0798F5; --err: #d1243a; --ok: #1d8a4b; --warn: #b36b00;
  --thought: #1B1D24; --sub: #5d6578; --shadow: 0 1px 2px rgba(10,16,70,.05), 0 12px 30px -16px rgba(10,16,70,.25); } }
:root[data-theme="light"] { color-scheme: light;
  --bg: #f4f6fa; --card: #ffffff; --surface-2: #eef1f6; --line: #dde2ea; --field: #f9fafc; --fg: #161a22; --muted: #5d6578;
  --accent: #0574c4; --accent-fg: #ffffff; --accent2: #8a45f6; --glow: #0798F5; --err: #d1243a; --ok: #1d8a4b; --warn: #b36b00;
  --thought: #1B1D24; --sub: #5d6578; --shadow: 0 1px 2px rgba(10,16,70,.05), 0 12px 30px -16px rgba(10,16,70,.25); }

* { box-sizing: border-box; }
html { scroll-padding-top: calc(var(--head-h) + 12px); }
body { margin: 0; min-height: 100vh; background: var(--bg); color: var(--fg); -webkit-font-smoothing: antialiased;
  font: 15px/1.55 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ico { width: 18px; height: 18px; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.skip { position: absolute; left: -999px; top: 8px; z-index: 100; padding: 8px 12px; background: var(--card); border-radius: 8px; }
.skip:focus { left: 8px; }
kbd { font: 11px var(--mono); padding: 0 5px; border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 5px; color: var(--muted); }
.env-banner { position: fixed; right: 12px; bottom: 12px; z-index: 50; padding: 3px 10px; border-radius: 999px; font: 600 11px var(--brand-font);
  letter-spacing: .14em; color: #fff; background: #c2185b; pointer-events: none; }

/* ---------- Header ---------- */
.site-head { position: sticky; top: 0; z-index: 30; display: flex; align-items: center; gap: 14px; height: var(--head-h); padding: 0 18px;
  background: color-mix(in srgb, var(--card) 82%, transparent); backdrop-filter: saturate(1.6) blur(16px); -webkit-backdrop-filter: saturate(1.6) blur(16px);
  border-bottom: 1px solid var(--line); }
.brand { display: flex; align-items: center; gap: 10px; color: inherit; }
.brand:hover { text-decoration: none; }
.brand img { width: auto; height: 38px; filter: drop-shadow(0 0 8px color-mix(in srgb, var(--glow) 35%, transparent)); }
.wordmark { display: grid; justify-items: center; font-family: var(--brand-font); line-height: 1; }
.wordmark b { font-weight: 600; font-size: 21px; letter-spacing: -.02em; }
.wordmark b span { color: var(--thought); } .wordmark b em { font-style: normal; color: var(--brand-blue); }
.preview-chip { padding: 2px 8px; font: 600 10px var(--brand-font); letter-spacing: .08em; text-transform: uppercase; color: var(--accent); border: 1px solid var(--accent); border-radius: 999px; }
.preview-chip:hover { text-decoration: none; background: color-mix(in srgb, var(--accent) 15%, transparent); }
.wordmark small { margin: 5px -.32em 0 0; font-weight: 500; font-size: 7px; letter-spacing: .32em; color: var(--sub); }
.search-btn { display: flex; align-items: center; gap: 8px; width: min(340px, 40vw); margin-left: auto; padding: 8px 10px;
  font: inherit; font-size: 14px; color: var(--muted); background: var(--field); border: 1px solid var(--line); border-radius: 10px; cursor: pointer; }
.search-btn:hover { border-color: color-mix(in srgb, var(--accent) 60%, var(--line)); }
.search-btn kbd { margin-left: auto; }
.theme-switch { display: flex; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.theme-switch button { display: flex; align-items: center; gap: 6px; padding: 7px 9px; font: inherit; font-size: 13px; color: var(--muted);
  background: none; border: 0; cursor: pointer; }
.theme-switch button:hover { color: var(--fg); }
.theme-switch button[aria-pressed="true"] { color: var(--fg); background: var(--surface-2); }
.theme-switch .ico { width: 16px; height: 16px; }
.head-theme .theme-switch span { display: none; }
.icon-btn { display: none; place-items: center; width: 38px; height: 38px; padding: 0; color: var(--fg); background: var(--field);
  border: 1px solid var(--line); border-radius: 10px; cursor: pointer; }

/* ---------- Sidebar + content ---------- */
.site-layout { display: grid; grid-template-columns: 250px minmax(0, 1fr); }
.site-side { position: sticky; top: var(--head-h); height: calc(100vh - var(--head-h)); overflow-y: auto; padding: 14px 10px 30px;
  background: var(--card); border-right: 1px solid var(--line); overscroll-behavior: contain; }
.side-theme { display: none; }
/* Categories are toolbox drawers: one open at a time (site.js slides them), its tools slide out of a recessed tray. */
.site-side details { margin: 0 0 6px; }
.site-side summary { display: flex; align-items: center; gap: 8px; padding: 9px 10px; list-style: none; cursor: pointer;
  font: 600 11px var(--brand-font); letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
  background: linear-gradient(var(--surface-2), var(--card)); border: 1px solid var(--line); border-radius: 8px;
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--fg) 6%, transparent), 0 2px 0 var(--line); transition: color .15s, box-shadow .15s, transform .15s; }
.site-side summary::-webkit-details-marker { display: none; }
.site-side summary:hover { color: var(--fg); }
/* The drawer handle. */
.site-side summary::after { content: ""; width: 22px; height: 6px; border-radius: 3px; background: var(--line);
  box-shadow: inset 0 1px 1px rgba(0,0,0,.35); transition: background .15s; }
.site-side summary:hover::after { background: var(--muted); }
.site-side details[open] summary { color: var(--fg); border-radius: 8px 8px 0 0; box-shadow: inset 0 1px 0 color-mix(in srgb, var(--fg) 6%, transparent); transform: translateY(1px); }
.site-side details[open] summary::after { background: var(--accent); }
.site-side summary .ico { width: 16px; height: 16px; }
.site-side .count { margin-left: auto; font-weight: 500; letter-spacing: 0; }
.site-side details > a:first-of-type { margin-top: 4px; }
.site-side details > a:last-of-type { margin-bottom: 4px; }
.site-side details[open] { background: var(--field); border: 1px solid var(--line); border-top: 0; border-radius: 0 0 8px 8px;
  box-shadow: inset 0 6px 8px -6px rgba(0,0,0,.35); }
.site-side details[open] summary { margin: 0 -1px; }
.site-side a { display: block; margin: 0 4px; padding: 5px 10px; border-radius: 8px; color: var(--fg); font-size: 14px; }
.site-side a:hover { background: var(--surface-2); text-decoration: none; }
.site-side a[aria-current] { font-weight: 600; background: color-mix(in srgb, var(--accent) 16%, transparent); box-shadow: inset 2px 0 0 var(--accent); }
.site-scrim { display: none; }
.site-body { display: flex; flex-direction: column; min-height: calc(100vh - var(--head-h)); }
main { flex: 1; width: 100%; max-width: 980px; padding: 26px 32px 40px; }
.site-foot { position: sticky; bottom: 0; z-index: 10; display: flex; flex-wrap: wrap; gap: 6px 20px; padding: 8px 32px; background: var(--bg); font-size: 13px; color: var(--muted); border-top: 1px solid var(--line); }
.site-foot a { display: inline-flex; align-items: center; gap: 5px; color: var(--muted); }
.site-foot .ico { width: 14px; height: 14px; }

/* ---------- Page content ---------- */
h1, h2, h3 { font-family: var(--brand-font); letter-spacing: -.015em; }
h1 .ico { width: .85em; height: .85em; margin-right: 10px; vertical-align: -.08em; color: var(--accent); }
h1 { margin: 4px 0 6px; font-size: 28px; font-weight: 600; line-height: 1.2; letter-spacing: -.02em; }
main h2 { font-size: 19px; font-weight: 600; margin: 28px 0 8px; }
h3 { font-size: 15px; font-weight: 600; margin: 18px 0 6px; }
.crumb { margin: 0; font-size: 13px; color: var(--muted); }
.lede { margin: 0 0 18px; color: var(--muted); }
.private { display: flex; align-items: flex-start; gap: 8px; margin: 16px 2px 0; font-size: 13px; color: var(--muted); }
.private b { color: var(--ok); font-weight: 600; }
.coffee { display: flex; align-items: flex-start; gap: 8px; margin: 8px 2px 0; font-size: 13px; color: var(--muted); }
.coffee .ico { color: var(--accent); }
.coffee-note { margin-left: 4px; color: var(--muted); font-size: 12px; } .coffee-note::before { content: "("; } .coffee-note::after { content: ")"; }
.data-ctl { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px; margin: 14px 0 6px; } .data-ctl label { margin: 0; }
.data-status { min-height: 1.5em; font-size: 13px; color: var(--ok); }
.prose > .card > :last-child { margin-bottom: 0; }
.help { margin: -6px 0 18px; border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line)); border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 7%, var(--card)); }
.help summary { display: flex; align-items: center; gap: 10px; padding: 10px 14px; cursor: pointer; list-style: none; font-size: 14px; color: var(--muted); }
.help summary::-webkit-details-marker { display: none; }
.help summary > .ico:first-child { color: var(--accent); }
.help summary span { flex: 1; } .help summary b { margin-right: 6px; color: var(--fg); font-weight: 600; }
.help summary:hover b { color: var(--accent); }
.help .chev { color: var(--muted); transition: transform .2s; } .help[open] .chev { transform: rotate(180deg); }
.help-body { padding: 0 16px 14px 42px; }
.help-body h2 { margin: 12px 0 4px; font-size: 13px; text-transform: uppercase; letter-spacing: .08em; color: var(--accent); }
.help-body p, .help-body ul { margin: 0 0 8px; } .help-body ul { padding-left: 20px; } .help-body li p { margin: 0; } .help-body li + li { margin-top: 4px; } .help-body pre { margin: 0 0 8px; white-space: pre-wrap; overflow-wrap: anywhere; } .help-body code { font: 13px var(--mono); }

/* ---------- Controls: the Concierge base styles the tool panels are written for ---------- */
.card { margin-bottom: 16px; padding: 18px; background: var(--card); border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--shadow); }
.card > :first-child { margin-top: 0; }
.row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
label { display: block; margin: 12px 0; font-weight: 500; }
input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=color]), textarea, select { display: block; width: 100%; margin-top: 4px;
  padding: 9px 11px; font: inherit; font-weight: 400; color: inherit; background: var(--field); border: 1px solid var(--line); border-radius: 10px;
  transition: border-color .15s, box-shadow .15s; }
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent); }
/* Safari draws native selects at its own size, ignoring padding and height, so drop the native look and draw the arrow */
select { -webkit-appearance: none; appearance: none; min-height: calc(1lh + 20px); padding-right: 32px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 17px center, right 12px center; background-size: 5px 5px; background-repeat: no-repeat; }
input[type=checkbox], input[type=radio] { accent-color: var(--accent); }
input[type=range] { width: 100%; accent-color: var(--accent); }
textarea { font-family: var(--mono); font-size: 13px; }
pre { overflow: auto; padding: 12px; background: var(--field); border: 1px solid var(--line); border-radius: 10px; font: 13px/1.5 var(--mono); }
code { font-family: var(--mono); font-size: .92em; }
button:where(:not(.search-btn, .icon-btn, .palette-close, .theme-switch button)) { padding: 8px 16px; font: inherit; font-weight: 600; color: var(--accent-fg);
  background: var(--accent); border: 0; border-radius: 10px; cursor: pointer; transition: transform .1s, filter .15s; }
button:where(:not(.search-btn, .icon-btn, .palette-close, .theme-switch button)):hover { filter: brightness(1.1); }
button:where(:not(.search-btn, .icon-btn, .palette-close, .theme-switch button)):active { transform: scale(.97); }
button.secondary { color: var(--fg); background: var(--surface-2); border: 1px solid var(--line); }
button.small { padding: 4px 11px; font-size: 13px; border-radius: 8px; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 8px; text-align: left; vertical-align: top; border-bottom: 1px solid var(--line); }
thead th { font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.kv th { width: 40%; font-weight: 500; color: var(--muted); }
tr:last-child th, tr:last-child td { border-bottom: 0; }
.scroll { overflow-x: auto; }
.badge { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 12px; font-weight: 600; white-space: nowrap;
  color: var(--muted); background: color-mix(in srgb, var(--muted) 15%, transparent); }
.badge.ok { color: var(--ok); background: color-mix(in srgb, var(--ok) 15%, transparent); }
.badge.warn { color: var(--warn); background: color-mix(in srgb, var(--warn) 16%, transparent); }
.badge.err { color: var(--err); background: color-mix(in srgb, var(--err) 14%, transparent); }
.muted { color: var(--muted); }
.error { color: var(--err); white-space: pre-wrap; }
.ok { color: var(--ok); }
/* The tools' layout wrapper in Concierge is a sidebar grid; here the sidebar is the site's. */
main .tl { display: block; }
.tl-inline select { width: auto; margin: 0; }

/* Tabs over a tool's fields (site.js tabify): the Web Scratchpad's editors, and its Result and Console. */
.tabs { display: flex; gap: 4px; margin: 14px 0 8px; border-bottom: 1px solid var(--line); }
.tab { display: flex; align-items: center; gap: 6px; margin-bottom: -1px; padding: 7px 14px; font: inherit; font-weight: 600; color: var(--muted);
  background: none; border: 0; border-bottom: 2px solid transparent; cursor: pointer; }
.tab:hover { color: var(--fg); }
.tab[aria-selected="true"] { color: var(--fg); border-bottom-color: var(--accent); }
.tab-count { min-width: 18px; padding: 0 6px; border-radius: 999px; font-size: 11px; line-height: 18px; text-align: center;
  color: var(--fg); background: var(--surface-2); }
.tab-count.err { color: #fff; background: var(--err); }
.tabs + label, .tabs ~ label { margin-top: 0; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
main .sc-tabbed .tl-sc { display: block; }
.sc-tabbed .tl-hl textarea { min-height: 380px; }
.sc-tabbed .tl-frame { height: 55vh; }
.sc-tabbed .tl-log { height: 55vh; min-height: 150px; max-height: none; resize: vertical; }

/* ---------- Home ---------- */
.hero { display: grid; grid-template-columns: 240px minmax(0, 1fr); gap: 30px; align-items: center; margin: 4px 0 10px; }
.hero img { width: 100%; height: auto; filter: drop-shadow(0 0 32px color-mix(in srgb, var(--glow) 28%, transparent)); }
.hero h1 { font-size: 32px; }
.hero p { margin: 0; color: var(--muted); }
.hero p + p { margin-top: 10px; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; padding: 0; list-style: none; }
.chips li { padding: 3px 11px; font-size: 13px; color: var(--muted); border: 1px solid var(--line); border-radius: 999px; }
.chips li::before { content: "● "; color: var(--ok); }
.cat-h { margin: 30px 0 10px; font: 600 12px var(--brand-font); letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
.cat-h .ico { width: 15px; height: 15px; margin-right: 8px; vertical-align: -3px; color: var(--accent); }
.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 10px; }
.tile { display: block; padding: 12px 14px; color: var(--fg); background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  transition: border-color .15s, transform .15s; }
.tile:hover { text-decoration: none; border-color: var(--accent); transform: translateY(-1px); }
.tile b { display: block; font-size: 14.5px; font-weight: 600; }
.tile span { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; margin-top: 2px; font-size: 13px; color: var(--muted); }
.mine-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 12px; }
.mine-head span { font-size: 13px; color: var(--muted); }
.mine-foot { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 6px 16px; margin: 10px 2px 0; font-size: 13px; color: var(--muted); }
button.link { padding: 0; font: inherit; color: var(--accent); background: none; border: 0; cursor: pointer; } button.link:hover { text-decoration: underline; filter: none; }

/* ---------- Search palette: centred dialog; a full-screen sheet on phones ---------- */
.palette { width: min(580px, calc(100vw - 24px)); max-height: min(560px, 80vh); margin: 12vh auto auto; padding: 0; overflow: hidden; color: var(--fg);
  background: var(--card); border: 1px solid var(--line); border-radius: 14px; box-shadow: 0 30px 80px -20px rgba(0,0,0,.7); }
.palette[open] { display: flex; flex-direction: column; }
.palette::backdrop { background: rgba(0,0,0,.55); backdrop-filter: blur(2px); }
.palette-in { display: flex; align-items: center; gap: 10px; padding: 10px 12px 10px 14px; border-bottom: 1px solid var(--line); color: var(--muted); }
.palette-in input#site-q { margin: 0; padding: 6px 0; font-size: 16px; color: var(--fg); background: none; border: 0; box-shadow: none; }
.palette-in form { margin: 0; }
.palette-close { padding: 4px; background: none; border: 0; color: var(--accent); font: inherit; font-weight: 600; cursor: pointer; }
.palette-close span { display: none; }
#site-results { flex: 1 1 auto; min-height: 0; margin: 0; padding: 6px 0; overflow-y: auto; list-style: none; }
#site-results a { display: flex; align-items: baseline; gap: 12px; padding: 8px 14px; color: var(--fg); }
#site-results a:hover { text-decoration: none; }
#site-results small { margin-left: auto; font-size: 12px; color: var(--muted); }
#site-results [aria-selected="true"] a { background: color-mix(in srgb, var(--accent) 16%, transparent); box-shadow: inset 2px 0 0 var(--accent); }
#site-results .none { padding: 14px; color: var(--muted); }
.palette-foot { display: flex; gap: 16px; padding: 8px 14px; font-size: 12px; color: var(--muted); border-top: 1px solid var(--line); }

/* ---------- Phones and small tablets ---------- */
@media (max-width: 860px) {
  :root { --head-h: 56px; }
  .site-head { gap: 8px; padding: 0 12px; }
  .icon-btn { display: grid; }
  .brand img { height: 32px; }
  .wordmark b { font-size: 18px; } .wordmark small { font-size: 6px; }
  .search-btn { width: 38px; height: 38px; padding: 0; justify-content: center; }
  .search-btn span, .search-btn kbd, .head-theme { display: none; }
  .site-layout { display: block; }
  .site-side { position: fixed; z-index: 40; top: 0; bottom: 0; left: 0; width: min(300px, 84vw); height: auto; padding-top: 16px;
    transform: translateX(-102%); visibility: hidden; transition: transform .22s ease, visibility .22s; box-shadow: var(--shadow); }
  .menu-open .site-side { transform: none; visibility: visible; }
  .site-scrim { position: fixed; inset: 0; z-index: 35; background: rgba(0,0,0,.5); }
  .menu-open .site-scrim { display: block; }
  .menu-open { overflow: hidden; }
  .side-theme { display: block; margin: 0 8px 12px; }
  .side-theme .theme-switch button { flex: 1; justify-content: center; }
  .kv.tl-cases th { white-space: normal; }  /* the nowrap names push the table past a phone's width */
  main { padding: 18px 16px 32px; }
  .site-foot { padding: 8px 16px; }
  h1 { font-size: 24px; }
  .hero { grid-template-columns: 1fr; gap: 12px; text-align: center; }
  .hero img { width: min(240px, 70%); margin: 0 auto; }
  .hero p { margin: 0 auto; } .chips { justify-content: center; }
  .palette { width: 100vw; max-width: none; height: 100dvh; max-height: none; margin: 0; border: 0; border-radius: 0; }
  .palette-close kbd, .palette-foot { display: none; }
  .palette-close span { display: inline; }
  #site-results a { padding: 13px 16px; border-bottom: 1px solid var(--line); }
}
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }

/* ---------- The tools' own styles (_includes/tools.css) ---------- */
/* The tools' own styles. Copied from Concierge's modules/tools/run STYLE (October 2026) and now the site's own. */
.tl { display: grid; grid-template-columns: 210px minmax(0, 1fr); gap: 16px; align-items: start; }
.tl [hidden] { display: none !important; }
.tl-side { position: sticky; top: 12px; padding: 12px; max-height: calc(100vh - 24px); overflow-y: auto; }
.tl-side input { margin: 0 0 4px; }
.tl-side h2 { margin: 14px 4px 4px; }
.tl-side summary { cursor: pointer; list-style: none; }
.tl-side summary::-webkit-details-marker { display: none; }
.tl-side summary h2::after { content: "›"; display: inline-block; margin-left: 6px; transition: transform .15s; }
.tl-side details[open] summary h2::after { transform: rotate(90deg); }
.tl-side a { display: block; padding: 5px 10px; border-radius: 8px; color: var(--fg); font-size: 14px; }
.tl-side a:hover { background: var(--line); }
.tl-side a.active { background: color-mix(in srgb, var(--accent) 18%, transparent); font-weight: 600; }
.tl-pick { display: none; margin: 0 0 16px; }
.tl-two { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.tl-two label { margin-top: 0; }
.tl-diff { width: 100%; margin-top: 8px; border-collapse: collapse; font: 13px/1.55 ui-monospace, "SF Mono", Menlo, Consolas, monospace; }
.tl-diff td { padding: 0 6px; border: 0; vertical-align: top; }
.tl-diff .tl-dt { white-space: pre-wrap; overflow-wrap: anywhere; width: 100%; }
.tl-diff.tl-dside .tl-dt { width: 50%; }
.tl-diff .tl-dn, .tl-diff .tl-dm { color: var(--muted); text-align: right; user-select: none; white-space: nowrap; }
.tl-diff.tl-dside .tl-dn { border-left: 1px solid var(--line); }
.tl-diff .tl-dnone { background: color-mix(in srgb, var(--line) 40%, transparent); }
.tl-diff .tl-dskip td { padding: 2px 6px; color: var(--muted); background: color-mix(in srgb, var(--line) 40%, transparent); }
.tl-diff ins, .tl-diff del { text-decoration: none; border-radius: 3px; }
.tl-diff ins { background: color-mix(in srgb, var(--ok) 40%, transparent); }
.tl-diff del { background: color-mix(in srgb, var(--err) 40%, transparent); }
#df-out { overflow-x: auto; border: 1px solid var(--line); border-radius: 10px; }
.tl-add { background: color-mix(in srgb, var(--ok) 18%, transparent); }
.tl-del { background: color-mix(in srgb, var(--err) 18%, transparent); }
.tl-tree { font: 13px/1.6 ui-monospace, "SF Mono", Menlo, monospace; overflow-x: auto; }
.tl-tree details > :not(summary) { margin-left: 1.4em; }
.tl-tree summary { cursor: pointer; }
.tl-string { color: var(--ok); } .tl-number { color: var(--accent); } .tl-boolean, .tl-null { color: var(--muted); }
.tl-match th, .tl-match td { color: var(--ok); font-weight: 600; }
.kv code { overflow-wrap: anywhere; }
.tl-perm { width: auto; } .tl-perm td, .tl-perm thead th { text-align: center; }
#up-params td { padding: 4px; } #up-params input { margin: 0; }
.tl-inline { display: inline-flex; align-items: center; gap: 6px; margin: 0 12px 0 0; }
.tl-inline input { width: auto; margin: 0; }
.tl-hex { font-size: 12.5px; white-space: pre; }
.tl-hex .o { color: var(--muted); } .tl-hex .z { color: color-mix(in srgb, var(--muted) 60%, transparent); } .tl-hex .a { color: var(--accent); font-weight: 600; }
/* Highlighted editor: the textarea's text is transparent over a <pre> laid out identically behind it. */
.tl-stage { display: grid; place-items: center; min-height: 160px; max-height: 60vh; padding: 16px; margin-bottom: 12px; overflow: auto;
  border: 1px solid var(--line); border-radius: 12px; resize: vertical; }
.tl-stage.check { background: #fff conic-gradient(#ddd 25%, #fff 0 50%, #ddd 0 75%, #fff 0) 0 0 / 16px 16px; }
.tl-stage img { max-width: 100%; max-height: calc(60vh - 34px); }
.tl-hl { position: relative; margin-top: 4px; }
.tl-hl textarea, .tl-hl pre { margin: 0; padding: 9px 11px; border-width: 1px; border-radius: 10px; font: 13px/1.55 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  white-space: pre-wrap; overflow-wrap: break-word; tab-size: 4; letter-spacing: normal; scrollbar-gutter: stable; }
.tl-hl textarea { position: relative; resize: vertical; background: transparent; color: transparent; caret-color: var(--fg); }
.tl-hl textarea::selection { color: transparent; background: color-mix(in srgb, var(--accent) 35%, transparent); }
.tl-hl pre { position: absolute; inset: 0; overflow: hidden; border-color: transparent; background: var(--bg); color: var(--fg); }
.tl-hl .h { color: var(--accent); font-weight: 700; } .tl-hl .b { font-weight: 700; } .tl-hl .i { font-style: italic; }
.tl-hl .s { text-decoration: line-through; color: var(--muted); } .tl-hl .c { color: var(--ok); } .tl-hl .k { color: var(--accent2); }
.tl-hl .u, .tl-hl .p, .tl-hl .cm { color: var(--muted); } .tl-hl .cm { font-style: italic; } .tl-hl .m { color: var(--glow); }
.tl-hl .t { color: var(--accent2); } .tl-hl .a { color: var(--warn); } .tl-hl .v { color: var(--ok); }
.tl-hl .q0 { color: var(--fg); } .tl-hl .q1 { color: var(--accent); } .tl-hl .q2 { color: var(--ok); } .tl-hl .q3 { color: var(--accent2); }
/* Data Validator: an issue is marked from where it is to the end of its line; xz is a thin bar where a line ends. */
.tl-hl .xe, .tl-hl .xw { text-decoration: wavy underline var(--err); text-decoration-skip-ink: none; background: color-mix(in srgb, var(--err) 14%, transparent); }
.tl-hl .xw { text-decoration-color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, transparent); }
.tl-hl .xz { box-shadow: -2px 0 0 var(--err); } .tl-hl .xw.xz { box-shadow: -2px 0 0 var(--warn); }
/* Line numbers: each line's .ln sits in the widened left padding, sized by --digits. */
.tl-hl.tl-ln textarea, .tl-hl.tl-ln pre { padding-left: calc(var(--digits, 2) * 1ch + 24px); }
.tl-hl .ln { position: absolute; left: 0; width: calc(var(--digits, 2) * 1ch + 11px); text-align: right; color: var(--muted);
  font-weight: normal; font-style: normal; } .tl-hl .ln::before { content: attr(data-n); }
/* Regex Tester: rx-g0–rx-g5 colour a capture group the same in the pattern, the text and the tables; rx-m0 and rx-m1
   alternate between neighbouring matches, rx-z marks an empty match, and rx-sel the part of the pattern being explained. */
.rx-g0 { --g: var(--accent); } .rx-g1 { --g: var(--ok); } .rx-g2 { --g: var(--warn); } .rx-g3 { --g: var(--accent2); } .rx-g4 { --g: var(--glow); } .rx-g5 { --g: #e0569b; }
.rx-m0, .rx-m1 { background: color-mix(in srgb, var(--accent) 14%, transparent); box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--accent) 70%, transparent); border-radius: 2px; }
.rx-m1 { background: color-mix(in srgb, var(--accent2) 16%, transparent); box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--accent2) 70%, transparent); }
:is(.rx-g0, .rx-g1, .rx-g2, .rx-g3, .rx-g4, .rx-g5) { background: color-mix(in srgb, var(--g) 30%, transparent); border-radius: 3px; }
.tl-rx-pat :is(.rx-g0, .rx-g1, .rx-g2, .rx-g3, .rx-g4, .rx-g5), .tl-rx-code :is(.rx-g0, .rx-g1, .rx-g2, .rx-g3, .rx-g4, .rx-g5) { font-weight: 700; }
.rx-n { color: var(--muted); font-weight: 700; background: color-mix(in srgb, var(--muted) 16%, transparent); border-radius: 3px; }
.rx-q { color: var(--warn); font-weight: 700; } .rx-c { color: var(--accent2); } .rx-e { color: var(--ok); } .rx-a { color: var(--glow); font-weight: 700; }
.rx-o { color: var(--err); font-weight: 700; } .rx-br { font-style: italic; text-decoration: underline dotted; }
.rx-sel { text-decoration: underline 2px var(--fg); text-underline-offset: 3px; outline: 1px dashed color-mix(in srgb, var(--fg) 50%, transparent); }
.tl-hl .rx-z { box-shadow: -2px 0 0 var(--accent); }
.tl-rx-mono, .tl-rx-code { font: 13px ui-monospace, "SF Mono", Menlo, Consolas, monospace; }
.tl-rx-flags code { font-weight: 700; }
.tl-rx-tree, .tl-rx-tree ul { margin: 0; padding: 0; list-style: none; }
.tl-rx-tree ul { margin-left: 8px; padding-left: 14px; border-left: 1px solid var(--line); }
.tl-rx-tree li { margin: 4px 0; } .tl-rx-tree li > div { line-height: 1.6; }
button.tl-rx-code, code.tl-rx-code { display: inline; padding: 1px 7px; margin-right: 4px; color: var(--fg); white-space: pre-wrap; overflow-wrap: anywhere;
  background: var(--bg); border: 1px solid var(--line); border-radius: 6px; cursor: pointer; }
button.tl-rx-code:hover, button.tl-rx-code:focus-visible { border-color: var(--accent); filter: none; }
code.tl-rx-code { cursor: default; font-weight: 700; }
.tl-rx-warn { padding: 6px 10px; border-left: 3px solid var(--warn); background: color-mix(in srgb, var(--warn) 10%, transparent); border-radius: 6px; }
.tl-rx-scroll { max-height: 50vh; overflow: auto; } .tl-rx-table td code { white-space: pre-wrap; overflow-wrap: anywhere; padding: 1px 3px; }
.tl-rx-table th, .tl-rx-table td { text-align: left; vertical-align: top; }
.tl-issues { margin: 0; padding-left: 1.4em; } .tl-issues li { margin: 2px 0; } .tl-issues li.error::marker { color: var(--err); } .tl-issues li.warn::marker { color: var(--warn); }
.tl-frame { height: 50vh; min-height: 150px; margin-top: 4px; overflow: hidden; resize: vertical; border: 1px solid var(--line); border-radius: 10px; background: #fff; }
.tl-frame iframe { display: block; width: 100%; height: 100%; border: 0; }
.tl-sc { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 0 12px; }
.tl-log { min-height: 3em; max-height: 30vh; overflow: auto; margin: 4px 0 0; padding: 8px 11px; border: 1px solid var(--line); border-radius: 10px;
  font: 13px/1.5 ui-monospace, "SF Mono", Menlo, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
.tl-log .warn { color: var(--warn); } .tl-log .error { color: var(--err); } .tl-log .info, .tl-log .debug { color: var(--muted); }
.tl-field { display: flex; align-items: center; gap: 8px; } .tl-field input { flex: 1; font: 13px ui-monospace, "SF Mono", Menlo, monospace; }
.tl-hex > div + div { margin-top: 6px; }
.tl-hex .c { display: inline-block; width: 2ch; text-align: center; }
.tl-colour { display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr); gap: 20px; margin-bottom: 12px; }
.tl-sv { position: relative; aspect-ratio: 4 / 3; border-radius: 12px; cursor: crosshair; touch-action: none;
  background: linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, hsl(var(--h, 0) 100% 50%)); }
.tl-sv i { position: absolute; width: 18px; height: 18px; margin: -9px; border: 2px solid #fff; border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(0,0,0,.4), 0 2px 6px rgba(0,0,0,.4); pointer-events: none; }
.tl-colour input[type=range] { -webkit-appearance: none; appearance: none; width: 100%; height: 14px; margin: 14px 0 0; padding: 0;
  border-radius: 999px; border: 1px solid var(--line); }
.tl-colour input.tl-hue { background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); }
.tl-swatch, .tl-chips button { background-color: #fff; background-clip: padding-box;
  background-image: conic-gradient(#ccc 25%, #fff 0 50%, #ccc 0 75%, #fff 0); background-size: 12px 12px; }
.tl-colour input.tl-alpha { background-color: #fff; background-clip: padding-box; background-image: linear-gradient(to right, transparent, var(--c, #000)), conic-gradient(#ccc 25%, #fff 0 50%, #ccc 0 75%, #fff 0);
  background-size: 100% 100%, 12px 12px; }
.tl-colour input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; border-radius: 50%;
  background: #fff; border: 1px solid rgba(0,0,0,.3); box-shadow: 0 1px 4px rgba(0,0,0,.4); cursor: pointer; }
.tl-colour input[type=range]::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: #fff;
  border: 1px solid rgba(0,0,0,.3); box-shadow: 0 1px 4px rgba(0,0,0,.4); cursor: pointer; }
.tl-swatch { height: 96px; border-radius: 12px; border: 1px solid var(--line); overflow: hidden; }
.tl-swatch span { display: block; height: 100%; background: var(--c); }
.tl input[type=color] { width: 44px; height: 32px; padding: 2px; margin: 0; }
.tl-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
.tl-chips button { width: 44px; height: 44px; padding: 0; border-radius: 10px; border: 1px solid var(--line); box-shadow: none; overflow: hidden; }
.tl-chips button::before { content: ""; display: block; height: 100%; background: var(--c); }
.tl-sample { display: grid; gap: 4px; padding: 16px; border-radius: 12px; border: 1px solid var(--line); }
.tl-sample b { font-size: 24px; font-weight: 600; } .tl-sample span { font-size: 15px; }
.tl-four { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 0 12px; }
.tl-cases code { white-space: pre-wrap; font-size: 14px; }
.tl-cases th { cursor: pointer; white-space: nowrap; } .tl-cases th:hover { color: var(--accent); }
.tl-wordlist { columns: 9rem; list-style: none; padding: 0; margin: 0; } .tl-anwords th { white-space: nowrap; vertical-align: top; }
.tl-morse { display: flex; flex-wrap: wrap; gap: 4px 2px; margin: 12px 0; }
.tl-morse span { display: inline-grid; justify-items: center; min-width: 1.6em; padding: 2px 4px; border-radius: 6px; }
.tl-morse span.w { min-width: 1em; }
.tl-morse b { font-size: 15px; } .tl-morse i { font: 13px ui-monospace, Menlo, monospace; font-style: normal; color: var(--muted); letter-spacing: 1px; }
.tl-morse span.on { background: color-mix(in srgb, var(--glow) 35%, transparent); }
.tl-lamp { width: 18px; height: 18px; border-radius: 50%; background: var(--line); transition: background .03s; }
.tl-lamp.on { background: var(--glow); box-shadow: 0 0 12px var(--glow); }
.tl-key { width: 100%; padding: 22px; font-size: 16px; touch-action: none; user-select: none; -webkit-user-select: none; }
.tl-key.on { background: color-mix(in srgb, var(--glow) 35%, transparent); }
.tl-chart { margin: 12px 0; border: 1px solid var(--line); border-radius: 12px; overflow: hidden; }
.tl-chart.none { background-image: conic-gradient(var(--line) 25%, transparent 0 50%, var(--line) 0 75%, transparent 0); background-size: 16px 16px; }
.tl-chart svg { display: block; width: 100%; height: auto; }
.tl-chart svg .mark:hover { opacity: .8; }
.tl-freq td:last-child { width: 40%; }
.tl-freq i { display: block; height: 8px; border-radius: 4px; background: var(--accent); }
.tl-freq td:nth-child(2) { font-family: ui-monospace, Menlo, monospace; white-space: pre; }
.tl-stats { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; margin-bottom: 8px; }
.tl-stats div { padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px; }
.tl-stats b { display: block; font-size: 20px; font-weight: 650; overflow-wrap: anywhere; }
.tl-stats span { font-size: 12.5px; color: var(--muted); }
@media (max-width: 720px) {
  .tl { grid-template-columns: minmax(0, 1fr); }
  .tl-side { display: none; }
  .tl-pick { display: block; }
  .tl-two, .tl-colour { grid-template-columns: 1fr; }
}
/* Ciphers */
.tl-grid { margin: 12px 0 0; padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px; font: 14px/1.5 ui-monospace, "SF Mono", Menlo, monospace; overflow-x: auto; }
.tl-pig { display: flex; flex-wrap: wrap; gap: 6px 22px; margin-top: 12px; }
.tl-pig span { display: inline-flex; gap: 4px; }
.tl-shot { display: block; max-width: 100%; max-height: 50vh; margin-top: 8px; border: 1px solid var(--line); border-radius: 8px; image-rendering: pixelated; }
.tl-lamps { display: grid; justify-items: center; gap: 8px; margin-top: 16px; }
.tl-lamps div { display: flex; gap: clamp(3px, 1.5vw, 8px); }
.tl-lamps span { display: grid; place-items: center; width: clamp(24px, 7.5vw, 34px); aspect-ratio: 1; border-radius: 50%; border: 1px solid var(--line);
  background: var(--surface-2); color: var(--muted); font: 600 14px ui-monospace, Menlo, monospace; }
.tl-lamps span.on { background: var(--glow); color: #111; box-shadow: 0 0 14px var(--glow); }
.tl-vc { position: relative; margin-top: 12px; overflow: hidden; background: #fff; border-radius: 8px; border: 1px solid var(--line); }
.tl-vc img { display: block; width: 100%; image-rendering: pixelated; }
.tl-vc img + img { position: absolute; inset: 0; }
.tl-qrs { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 8px; }
.tl-qrs figure { margin: 0; width: 150px; padding: 6px; background: #fff; color: #111; border-radius: 8px; text-align: center; font-size: 13px; }
.tl-wrap { white-space: pre-wrap; overflow-wrap: anywhere; }
.tl-spec { display: block; width: 100%; height: 320px; margin-top: 8px; border-radius: 8px; image-rendering: auto; }
.tl-frames { list-style: none; padding: 0; margin: 8px 0; display: grid; gap: 6px; }
.tl-frames li { display: flex; align-items: center; gap: 6px; }
.tl-frames canvas { width: 44px; height: 44px; object-fit: contain; border: 1px solid var(--line); border-radius: 6px; flex: none; }
.tl-frames span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#gf-list input { width: 6em; flex: none; margin: 0; }  /* an id, to beat site.css's input:not(…) rule */
#gx-out img { display: block; width: 100%; height: auto; }
/* Text Cleaner recipe steps */
.tl-recipe label { display: flex; align-items: center; gap: 8px; margin: 0; flex: 1 1 340px; } .tl-recipe select { margin: 0; }
.tl-steps { list-style: none; counter-reset: step; padding: 0; margin: 12px 0; display: grid; gap: 6px; }
.tl-step { counter-increment: step; padding: 6px 10px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface-2); }
.tl-step.off > :not(.tl-step-head), .tl-step.off b { opacity: .5; }
.tl-step.drag { position: relative; z-index: 1; box-shadow: 0 6px 18px rgba(0,0,0,.25); border-color: var(--accent); }
.tl-step-head { display: flex; align-items: center; gap: 8px; }
.tl-step-head b::before { content: counter(step) ". "; color: var(--muted); font-weight: 400; }
.tl-step-head input { width: auto; margin: 0; }
.tl-step-head b { flex: 1; min-width: 0; }
.tl-grip { padding: 2px 4px !important; background: none !important; color: var(--muted) !important; border: 0; font-size: 18px; line-height: 1;
  cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none; }
.tl-step.drag .tl-grip { cursor: grabbing; }
.tl-step-info { font-size: 12.5px; color: var(--muted); } .tl-step-info.ok { color: var(--ok); } .tl-step-info.error { color: var(--err); }
.tl-step-opts { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 0; margin: 4px 0 2px 30px; font-size: 14px; }
.tl-step-opts label { margin: 0 12px 0 0; } .tl-step-opts select, .tl-step-opts input[type=text] { width: auto; max-width: 100%; margin: 0; }
.tl-step-opts .tl-num { width: 5em; margin: 0; }
/* Dropping files (tools.js): the card shows it will take them; the hint sits after each file input. */
.card.dropping { outline: 2px dashed var(--accent); outline-offset: 4px; }
.drop-hint { display: block; margin-top: 4px; font-size: 12.5px; font-weight: 400; color: var(--muted); }
@media (hover: none) { .drop-hint { display: none; } }
/* "Copy a link to this input" (tools.js), above tools that offer it. */
.share-line { margin: -4px 0 10px; text-align: right; font-size: 13px; }
/* Phonetic Spelling, and the "Spell it out" boxes on other tools. */
.tl-phon td:first-child { width: 2.5em; } .tl-phon td:nth-child(2) { width: 5em; } .tl-phon td:last-child { width: 40%; } .tl-phon .tl-alike td { background: color-mix(in srgb, var(--warn) 14%, transparent); }
.tl-phon .tl-gstart td { border-top: 2px solid var(--line); }
.tl-spell { margin: 6px 0 2px; padding: 6px 10px; border-left: 3px solid var(--accent); white-space: pre-wrap; font-size: 14px; }
/* World Clock: a row per place, a column per hour of the first place's day. */
.tl-wc { width: auto; border-collapse: separate; border-spacing: 0; user-select: none; touch-action: pan-y; }
.tl-wc th { position: sticky; left: 0; z-index: 1; min-width: 170px; background: var(--card); font-weight: 400; font-size: 13px; }
.tl-wc td { min-width: 42px; padding: 6px 2px; text-align: center; font-size: 13px; cursor: pointer; border-bottom: 1px solid var(--card); }
.tl-wc td small { color: var(--muted); font-size: 10px; white-space: nowrap; }
.wc-work { background: color-mix(in srgb, var(--ok) 22%, transparent); }
.wc-awake { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.wc-night { background: color-mix(in srgb, var(--muted) 18%, transparent); color: var(--muted); }
.tl-wc .wc-midnight { border-left: 2px solid var(--fg); }
.tl-wc .wc-now { box-shadow: inset 0 3px 0 var(--warn); }
.tl-wc .wc-sel { outline: 2px solid var(--accent); outline-offset: -2px; font-weight: 700; }
.tl-wc:focus-visible { outline: 2px solid var(--accent); }
.tl-wc-key span { display: inline-block; padding: 2px 8px; margin-right: 6px; border-radius: 6px; }
.tl-wc-key .wc-now { box-shadow: inset 0 3px 0 var(--warn); }
/* Identify */
.tl-id { padding-left: 1.4em; } .tl-id li { margin: 0 0 12px; } .tl-id code { overflow-wrap: anywhere; }
/* "Open in…" menus beside results */
select.tl-open { display: inline-block; width: auto; margin: 0; min-height: 0; padding: 4px 30px 4px 11px; font-size: 13px; border-radius: 8px; vertical-align: middle; }
.share-line .link + .link { margin-left: 18px; }
.guide-line { margin: -4px 0 10px; font-size: 13px; color: var(--muted); }
.tl-table th button { font: inherit; color: inherit; text-transform: inherit; letter-spacing: inherit; } .tl-table td { white-space: pre-wrap; }
/* Cron Explainer's builder */
.tl-cronb { margin: 4px 0 12px; } .tl-cronb summary { cursor: pointer; font-weight: 600; }
.tl-cronb fieldset { border: 0; padding: 0; margin: 10px 0 0; } .tl-cronb legend { padding: 0; font-size: 14px; }
.tl-weak li { color: var(--warn); }
.tl-mixout { display: inline-block; width: 34px; height: 24px; border-radius: 6px; background: var(--c); border: 1px solid var(--line); vertical-align: middle; }
/* Image Palette */
.tl-pal-img { max-width: 100%; max-height: 240px; border-radius: 10px; margin: 6px 0; }
.tl-pals { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 12px; }
.tl-pal { display: flex; flex-direction: column; gap: 4px; width: 110px; font: 12px ui-monospace, Menlo, monospace; color: var(--fg); }
.tl-pal span { height: 64px; border-radius: 10px; border: 1px solid var(--line); }
.tl-yellow { border: 0; padding: 0; margin: 8px 0 0; } .tl-yellow legend { padding: 0; font-size: 14px; } .tl-yellow input { display: inline-block; width: 4.2em; margin-right: 6px; }
.tl-codeimg { display: block; max-width: 100%; margin-top: 10px; border-radius: 12px; }
.tl-crop { border: 0; padding: 0; margin: 8px 0 0; } .tl-crop legend { padding: 0; font-size: 14px; }
.tl-otqr { width: 200px; max-width: 100%; margin: 8px 0; background: #fff; border-radius: 8px; } .tl-otqr svg { display: block; width: 100%; height: auto; }
.tl-ref { margin-top: 12px; } .tl-ref caption { text-align: left; font-weight: 600; padding: 4px 0; }
.tl-ref tbody th { white-space: nowrap; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-weight: 600; }
.tl-keypad { display: grid; place-items: center; min-height: 140px; border: 2px dashed var(--line); border-radius: 12px; cursor: text; }
.tl-keypad:focus { border-color: var(--accent); outline: none; }
#kc-big { font: 600 40px/1.2 ui-monospace, "SF Mono", Menlo, monospace; overflow-wrap: anywhere; text-align: center; padding: 8px; }
.tl-truthbox { overflow-x: auto; max-height: 70vh; overflow-y: auto; margin-top: 12px; }
.tl-truth { width: auto; font-family: ui-monospace, "SF Mono", Menlo, monospace; } .tl-truth td, .tl-truth th { text-align: center; padding: 2px 10px; }
.tl-truth thead th { position: sticky; top: 0; background: var(--card, var(--bg)); }
.tl-truth .tl-on { color: var(--ok); font-weight: 600; }
.tl-barcode { margin: 8px 0 12px; overflow-x: auto; } .tl-barcode svg { display: block; max-width: 100%; height: auto; }
.tl-censor { display: block; max-width: 100%; height: auto; margin: 8px 0; cursor: crosshair; touch-action: none; border-radius: 6px; }
.tl-censor:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
.tl-pa-out { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-height: 52px; margin: 8px 0; padding: 8px; border: 1px solid var(--line); border-radius: 10px; }
.tl-pa-out svg { flex: none; } .tl-pa-gap { width: 24px; } .tl-pa-other { font: 600 20px var(--mono); }
.tl-pa-break { flex-basis: 100%; height: 0; }
.tl-pa-keys { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
.tl-pa-key { padding: 4px; min-width: 50px; min-height: 50px; display: grid; place-items: center; color: var(--fg); background: var(--field); border: 1px solid var(--line); }
.tl-pa-char { font-size: 26px; line-height: 1; }
.tl-pdfs { padding-left: 1.4em; } .tl-pdfs li { display: flex; align-items: center; gap: 6px; margin: 4px 0; }
.tl-pdfs li span { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.tl-ocr { display: block; max-width: 100%; height: auto; margin-top: 8px; border-radius: 6px; border: 1px solid var(--line); }

/* A tool's heading, with the pin button beside it. */
.tool-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 12px; }
.pin-btn { display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px; font-size: 13px; color: var(--muted); background: transparent; border: 1px solid var(--line); border-radius: 999px; }
.pin-btn .ico { width: 15px; height: 15px; }
.pin-btn[aria-pressed=true] { color: var(--accent); border-color: var(--accent); }
.pin-btn[aria-pressed=true] .ico { fill: currentColor; }

    </main>
    <footer class="site-foot">
      <span>© 2026 <a href="https://www.thoughtasylum.com">ThoughtAsylum</a></span>
      <a href="/about/"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 11v5M12 8h.01"/></svg>
About</a>
      <a href="/privacy/"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3 4 6v6c0 4.5 3.4 8.2 8 9 4.6-.8 8-4.5 8-9V6z"/><path d="m9 12 2 2 4-4"/></svg>
Privacy</a>
      <a href="/offline/"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 16V6a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v10M2 19h20"/><path d="M12 7.5v5.5M9.5 10.5 12 13l2.5-2.5"/></svg>
Use offline</a>
      <a href="/about/#feedback"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5h16v11H9l-5 4z"/><path d="M8 9h8M8 12h5"/></svg>
Feedback</a>
    </footer>
  </div>
</div>
<dialog class="palette" id="site-palette" aria-label="Search tools" data-coffee="https://www.buymeacoffee.com/sylumer">
  <div class="palette-in">
    <svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
    <input id="site-q" type="search" placeholder="Search tools" autocomplete="off" autocapitalize="off" spellcheck="false"
      role="combobox" aria-expanded="true" aria-controls="site-results" aria-label="Search tools">
    <form method="dialog"><button class="palette-close" aria-label="Close"><kbd>esc</kbd><span>Cancel</span></button></form>
  </div>
  <ul id="site-results" role="listbox" aria-label="Tools"></ul>
  <div class="palette-foot"><span><kbd>↑</kbd> <kbd>↓</kbd> move</span><span><kbd>↵</kbd> open</span><span><kbd>esc</kbd> close</span></div>
</dialog>
<script type="application/json" id="site-index">[{"id":"identify","title":"Identify","cat":"Encoding","keys":"identify detect recognise recognize what is this string decoder magic cipher identifier hash identifier encoding detector base64 hex jwt uuid timestamp morse caesar vigenere ctf puzzle unknown format analyser analyzer"},{"id":"text-base64","title":"Text ⇄ Base64","cat":"Encoding","keys":"encode decode base32 base58 base85 ascii85 base45 base100 emoji big integer long to bytes file to base64 base64 file encoder decoder"},{"id":"image-base64","title":"Image → Base64","cat":"Encoding","keys":"data uri encode picture"},{"id":"base64-image","title":"Base64 → Image","cat":"Encoding","keys":"data uri decode picture"},{"id":"url","title":"URL Encode/Decode","cat":"Encoding","keys":"percent encodeuri encodeuricomponent query"},{"id":"jwt","title":"JWT Decoder & Encoder","cat":"Encoding","keys":"jwk jwks pem convert jwk to pem pem to jwk json web token bearer auth claims jwt.io verify signature sign encode create hs256 rs256 es256 ps256 hmac public key pem"},{"id":"cert","title":"Certificate Decoder","cat":"Encoding","keys":"pem der crt cer x509 x.509 ssl tls https expiry san fingerprint chain verify chain csr certificate signing request decoder ssh public key fingerprint ssh-keygen ed25519 public key pem"},{"id":"url-parts","title":"URL Inspector","cat":"Encoding","keys":"parse query parameters x-callback-url scheme shortcuts drafts edit build unwrap safe links outlook safelinks proofpoint urldefense google redirect decode defang refang ioc phishing security"},{"id":"escape","title":"String Escaper","cat":"Encoding","keys":"quote json shell zsh bash applescript html entities regex backslash"},{"id":"morse","title":"Morse Code","cat":"Encoding","keys":"cw telegraph dit dah dot dash audio sound beep play wav ham radio sos key decode audio morse decoder from sound file microphone listen recording wav mp3"},{"id":"compress","title":"Compress / Decompress","cat":"Encoding","keys":"gzip gunzip zlib deflate inflate compress decompress uncompress decode base64 gzip saml request payload .gz file compression ratio"},{"id":"json","title":"JSON Format","cat":"Data","keys":"prettify beautify minify tree viewer validate repair fix loose json5 trailing comma comments single quotes sort keys jsonpath query filter jq"},{"id":"xml","title":"XML Format","cat":"Data","keys":"prettify beautify minify validate xpath query xml to json json to xml convert"},{"id":"code","title":"Code Format","cat":"Data","keys":"minify minifier uglify compress compressor beautify beautifier prettify pretty print formatter unminify html css js javascript sql svg optimise optimize svgo terser code to image code screenshot carbon snippet picture png syntax highlight"},{"id":"yaml","title":"JSON ⇄ YAML ⇄ TOML","cat":"Data","keys":"yml convert config json to yaml yaml to json toml to json json to toml yaml to toml toml to yaml cargo pyproject"},{"id":"types","title":"JSON to Types","cat":"Data","keys":"json schema generator json to json schema infer schema from sample json to typescript interface json to go struct json to rust serde struct json to python typeddict json to swift codable json to java record json to php class quicktype struct converter yaml toml sql create table to typescript generate types model classes"},{"id":"csv","title":"CSV ⇄ JSON","cat":"Data","keys":"spreadsheet tsv table convert csv viewer sort filter preview"},{"id":"plist","title":"Plist ⇄ JSON","cat":"Data","keys":"property list xml launchd info.plist apple convert"},{"id":"graph","title":"Chart Maker","cat":"Data","keys":"graph plot bar column line area pie doughnut donut radar treemap visualise visualize key value svg png export"},{"id":"pdf","title":"PDF Pages","cat":"Data","keys":"merge pdf combine pdf files split pdf extract pages from pdf delete pages remove pages reorder pdf pages rotate pdf page join pdfs pdf tools offline private no upload"},{"id":"hash","title":"Hash / Checksum","cat":"Developer","keys":"md5 sha1 sha224 sha256 sha512 sha3 sha-3 keccak blake2 blake2b blake2s blake3 ripemd160 crc32 crc adler32 checksum digest verify download hmac signature mac webhook hash identifier what hash is this"},{"id":"validate","title":"Data Validator","cat":"Data","keys":"lint linter syntax checker check validate json yaml yml toml csv tsv config error duplicate keys"},{"id":"bcrypt","title":"Bcrypt","cat":"Developer","keys":"bcrypt hash generator verify check compare password hash cost factor rounds salt 2a 2b 2y php password_hash laravel django node spring"},{"id":"uuid","title":"UUID Generator","cat":"Developer","keys":"guid random v4 v7 v1 v5 uuidv7 time-based namespace name-based sha-1 rfc 9562 identifier decode parse timestamp ulid nanoid nano id objectid object id mongodb bson unique id generator"},{"id":"calculator","title":"Calculator","cat":"Developer","keys":"calculator big integer bigint arbitrary precision modular inverse modpow gcd lcm prime test factorise factor rsa ctf math evaluator expression"},{"id":"timestamp","title":"Timestamp Converter","cat":"Developer","keys":"unix epoch date time iso 8601 utc windows filetime ldap ntfs apple cocoa core data nsdate excel serial date ulid objectid mongodb snowflake twitter discord id timestamp"},{"id":"truth-table","title":"Truth Table","cat":"Developer","keys":"truth table generator boolean algebra logic expression simplify minimise quine mccluskey karnaugh map sum of products minterms tautology contradiction equivalence de morgan and or not xor nand nor implies"},{"id":"bases","title":"Number Bases","cat":"Developer","keys":"binary octal decimal hex hexadecimal base62 base 62 base36 base 36 radix convert twos complement signed unsigned endian endianness little big byte order ascii codes text to binary text to hex char codes"},{"id":"http-codes","title":"HTTP Codes & MIME Types","cat":"Developer","keys":"http status codes list 404 500 502 503 401 403 418 429 meaning response codes reference mime types content type media type file extension lookup application/json image/webp"},{"id":"user-agent","title":"User Agent Parser","cat":"Developer","keys":"user agent parser ua string what is my user agent browser detection operating system device detect bot crawler googlebot client hints sec-ch-ua navigator.userAgent"},{"id":"keycode","title":"Key Codes","cat":"Developer","keys":"javascript keycode event.key event.code keycode which key press keyboard event tester keydown shortcut modifier location numpad what key did i press"},{"id":"curl","title":"cURL to Code","cat":"Developer","keys":"har to curl raw http request to curl convert har burp request to curl curl to code curl converter curl to python requests curl to fetch javascript curl to go net/http curl to php copy as curl devtools convert curl command http request"},{"id":"units","title":"Unit Converter","cat":"Developer","keys":"unit converter bytes kb mb gb kib mib gib kibibyte mebibyte gibibyte megabit bits to bytes data size time length weight mass temperature celsius fahrenheit kelvin area volume metric imperial speed mph kmh knots angle degrees radians pressure psi bar atm hpa energy joules calories kwh btu percentage percent change"},{"id":"docker-compose","title":"docker run to Compose","cat":"Developer","keys":"docker run to docker compose converter composerize compose.yaml docker-compose.yml generate convert docker command ports volumes environment restart network services yaml"},{"id":"diff","title":"Diff Checker","cat":"Developer","keys":"compare difference changes side by side unified patch git json diff yaml diff compare json compare yaml ignore key order"},{"id":"regex","title":"Regex Tester","cat":"Developer","keys":"regular expression regexp regex101 javascript pattern match test explain debug capture groups named groups lookahead lookbehind replace backreference flags"},{"id":"cron","title":"Cron Explainer","cat":"Developer","keys":"crontab schedule launchd startcalendarinterval next run cron generator cron builder cron maker"},{"id":"chmod","title":"Permissions","cat":"Developer","keys":"chmod octal rwx unix file mode setuid sticky ls"},{"id":"subnet","title":"Subnet Calculator","cat":"Developer","keys":"subnet calculator ipv4 ipv6 cidr netmask subnet mask wildcard mask broadcast network address host range prefix split subnetting vlsm ip network ip to decimal ip to hex ip to binary list all addresses expand range group ips by subnet aggregate ula unique local address generator random mac address generator"},{"id":"dates","title":"Date Calculator","cat":"Developer","keys":"days between difference add subtract business working weekdays week number"},{"id":"paths","title":"Path Converter","cat":"Developer","keys":"posix hfs applescript file url smb unc windows network share volume tilde finder"},{"id":"world-clock","title":"World Clock","cat":"Developer","keys":"time zone converter world time buddy meeting planner timezone dst daylight saving summer time bst gmt utc est pst cet ist jst offset clock change city time difference schedule call"},{"id":"hex","title":"Hex Viewer","cat":"Developer","keys":"hexdump xxd bytes binary file type magic number signature strings entropy embedded file binwalk forensics ctf"},{"id":"scratchpad","title":"Web Scratchpad","cat":"Developer","keys":"javascript js css html playground sandbox snippet try run console code editor"},{"id":"clean","title":"Text Cleaner","cat":"Text","keys":"text cleanup recipe multi-step textsoap remove duplicates blank lines trim whitespace invisible zero-width unwrap pdf line breaks strip markdown asciidoc html tags find replace regex sort lines change case smart quotes extract emails extract urls extract ip addresses extract hashes ioc line numbers word wrap pad lines"},{"id":"preview","title":"Markdown & HTML Preview","cat":"Text","keys":"md render gfm github html page view"},{"id":"md-table","title":"Markdown Table","cat":"Text","keys":"csv tsv spreadsheet numbers excel align columns pipe"},{"id":"chars","title":"Character Inspector","cat":"Text","keys":"unicode code point utf-8 bytes nfc nfd normalize emoji invisible mojibake garbled text encoding fix windows-1252 latin-1 utf-16 punycode idn international domain name xn-- homograph"},{"id":"case","title":"Case & Style","cat":"Text","keys":"uppercase lowercase sentence title capitalise capitalize snake camel pascal kebab slug diacritics accents symbols punctuation bubble circled bold italic script fraktur gothic fancy unicode font upside down small caps strikethrough zalgo leet rot13 nato reverse numeronym i18n a11y braille"},{"id":"phonetic","title":"Phonetic Spelling","cat":"Text","keys":"nato alphabet spelling alphabet icao alfa bravo charlie military radio phonetic spell out password over the phone read aloud dictate symbols case sensitive translator decode reverse german din 5009 buchstabiertafel aachen berlin western union adams boston lapd police adam boy charles"},{"id":"sort","title":"Sort Lines","cat":"Text","keys":"order alphabetical reverse random shuffle numeric number absolute length natural date words list"},{"id":"freq","title":"Frequency Analysis","cat":"Text","keys":"word count character letter frequency occurrences common popular cipher ngram bigram index of coincidence ioc chi squared entropy cryptanalysis"},{"id":"stats","title":"Text Statistics","cat":"Text","keys":"word count character count sentences paragraphs reading time age grade level readability flesch kincaid fog smog syllables speaking pangram tweet sms"},{"id":"image","title":"Image Convert / Resize","cat":"Images","keys":"png jpeg jpg webp gif compress shrink scale exif metadata gps strip url link download from web crop rotate flip mirror turn"},{"id":"list-compare","title":"List Compare","cat":"Text","keys":"compare lists diff two lists difference intersection union set operations find duplicates missing items in both only in one merge dedupe emails ids sql in excel vlookup"},{"id":"numerals","title":"Numerals","cat":"Text","keys":"roman numerals converter number to words spell out numbers cheque writing words to number mayan numerals babylonian cuneiform arabic indic devanagari thai chinese digits numeral systems"},{"id":"svg","title":"SVG Preview & Export","cat":"Images","keys":"vector icon render png jpeg webp rasterise rasterize export resolution scale retina"},{"id":"colour","title":"Colour Converter","cat":"Images","keys":"color picker hex rgb hsl hsb hsv oklch swiftui contrast wcag accessibility eyedropper palette shades"},{"id":"random","title":"Random Picker","cat":"Text","keys":"random name picker pick a winner raffle draw shuffle list random order random team generator split into groups random number generator dice roller d20 2d6 coin flip heads or tails"},{"id":"exif","title":"Photo Metadata","cat":"Images","keys":"exif viewer exif remover remove metadata from photo strip exif gps location photo metadata viewer camera settings xmp iptc c2pa content credentials privacy before sharing jpeg png webp"},{"id":"palette","title":"Image Palette","cat":"Images","keys":"color palette generator from image extract colours colors from photo dominant colour main colors logo brand colours hex css variables picker"},{"id":"censor","title":"Photo Censor","cat":"Images","keys":"censor photo blur image online pixelate face hide number plate redact screenshot black box blur part of picture anonymise image hide personal information before sharing"},{"id":"ocr","title":"Image to Text (OCR)","cat":"Images","keys":"ocr image to text multilingual french german spanish japanese chinese arabic russian extract text from image picture to text screenshot to text copy text from photo scan to text tesseract optical character recognition offline private"},{"id":"qr","title":"QR Code","cat":"Images","keys":"qrcode barcode link phone scan read decode reader scanner wifi wi-fi qr code generator guest network password vcard contact qr code business card mecard"},{"id":"barcode","title":"Barcodes","cat":"Images","keys":"barcode generator barcode maker code 128 ean-13 ean 13 upc-a upc ean-8 code 39 check digit calculator svg png print labels barcode reader scanner scan barcode from image"},{"id":"anagram","title":"Anagram Solver","cat":"Text","keys":"anagram finder maker generator unscramble jumbled scrambled letters word unscrambler name anagram phrase multi-word scrabble countdown wordle word games puzzle"},{"id":"crossword","title":"Crossword Helper","cat":"Text","keys":"crossword solver helper clue answer missing letters pattern match wildcard blanks fill in word finder wordle puzzle cryptic wordle solver wordle helper words starting with words ending with words containing letters regex word search scrabble"},{"id":"caesar","title":"Caesar / ROT13","cat":"Ciphers","keys":"caesar shift rot13 rot47 rot5 rot18 rotate cipher decode brute force crack spoiler"},{"id":"markup","title":"Markup Converter","cat":"Text","keys":"html to markdown markdown to html asciidoc to markdown markdown to asciidoc html to asciidoc asciidoc to html adoc md gfm plain text to markdown markdown to plain text html to text text to html strip formatting asciidoctor turndown pandoc convert docs documentation"},{"id":"classic","title":"Classical Ciphers","cat":"Ciphers","keys":"atbash affine keyword substitution vigenere vigenère beaufort autokey playfair rail fence zigzag columnar transposition polybius square bacon baconian adfgvx adfgx pigpen masonic cipher puzzle geocache bifid trifid four-square foursquare porta gronsfeld nihilist amsco double transposition route cipher spiral scytale keyed caesar one-time pad otp"},{"id":"encrypt","title":"Encrypt / Decrypt","cat":"Ciphers","keys":"aes aes-256 password encrypt decrypt file openssl enc pbkdf2 salted protect secret"},{"id":"stego","title":"Image Steganography","cat":"Ciphers","keys":"steganography stegano hide hidden message secret lsb least significant bit png picture conceal watermark"},{"id":"crack","title":"Cipher Cracker","cat":"Ciphers","keys":"substitution cipher solver cryptogram solver monoalphabetic keyword cipher columnar transposition unknown key hill climbing crack break solve decode unknown cipher brute force caesar affine atbash vigenere beaufort rail fence key length kasiski index of coincidence puzzle"},{"id":"enigma","title":"Enigma Machine","cat":"Ciphers","keys":"enigma machine m3 m4 wehrmacht kriegsmarine naval rotor walzen reflector umkehrwalze ukw ring stellung plugboard steckerbrett bletchley turing ww2 simulator"},{"id":"redact","title":"Redact for AI","cat":"Text","keys":"redact mask anonymise anonymize sanitise sanitize pii personal data api key token secret password email ip address chatgpt claude gemini copilot llm ai prompt privacy placeholder restore unmask data loss prevention"},{"id":"hidden-text","title":"Hidden Text","cat":"Ciphers","keys":"zero width space invisible characters hidden message steganography unicode tags ascii smuggling prompt injection variation selectors emoji watermark detect strip clean"},{"id":"picture-alphabets","title":"Picture Alphabets","cat":"Ciphers","keys":"hexahue colour cipher color code pigpen cipher masonic cipher semaphore flags translator maritime signal flags nautical flag alphabet braille translator elder futhark runes rune translator greek letters symbol cipher decoder geocaching escape room puzzle"},{"id":"visual-crypto","title":"Visual Cryptography","cat":"Ciphers","keys":"visual cryptography secret sharing naor shamir shares overlay transparency noise reveal print puzzle"},{"id":"image-cipher","title":"Image Encryption","cat":"Ciphers","keys":"encrypt image picture photo pixels scramble shuffle noise aes ctr ecb penguin password protect visual"},{"id":"xor","title":"XOR Cipher","cat":"Ciphers","keys":"xor exclusive or key repeating single byte brute force break crack ctf hex base64 one time pad"},{"id":"secret-split","title":"Secret Splitter","cat":"Ciphers","keys":"shamir secret sharing split shares threshold recovery key backup seed phrase password combine qr"},{"id":"bitplane","title":"Bit-Plane Inspector","cat":"Ciphers","keys":"bit plane lsb least significant bit steganalysis stegsolve ctf hidden image channel extract compare difference"},{"id":"spectrogram","title":"Spectrogram","cat":"Ciphers","keys":"spectrogram audio sound hidden image picture message steganography frequency fft wav aphex twin ctf"},{"id":"password","title":"Password Generator","cat":"Ciphers","keys":"password strength checker password strength test how strong is my password weak password common password password passphrase api key token generator hex base62 base64url slug random string xkcd correct horse battery staple diceware eff word list random strong secure pin haystack grc entropy strength brute force"},{"id":"gif","title":"Animated GIF Maker","cat":"Images","keys":"gif animation animate frames slideshow loop delay timing make create combine images jpg png webp to gif"},{"id":"totp","title":"TOTP / HOTP Codes","cat":"Ciphers","keys":"totp hotp 2fa mfa two-factor authenticator one-time password otp code generator google authenticator authy otpauth secret base32 qr code verify rfc 6238 4226"},{"id":"gif-frames","title":"GIF Frame Extractor","cat":"Images","keys":"gif split frames extract explode separate animated webp apng png images zip delay timing still"},{"id":"number-codes","title":"Number Codes","cat":"Ciphers","keys":"a1z26 letters to numbers numbers to letters alphabet position tap code prisoner knock code baudot ita2 murray teletype phone keypad multitap t9 old phone text bcd binary coded decimal gray code puzzle geocache escape room"},{"id":"test-data","title":"Test Data Generator","cat":"Data","keys":"fake mock dummy sample random data generator seed seeded repeatable names emails addresses phone numbers dates uuid lorem ipsum csv tsv json jsonl ndjson sql insert fixtures faker mockaroo database seeding"}]</script>
<script src="/assets/js/site.js?v=1791136928"></script>
</body>
</html>
