|
| 1 | +<!DOCTYPE html> |
| 2 | +<html lang="en"> |
| 3 | +<head> |
| 4 | +<meta charset="utf-8"> |
| 5 | +<meta name="viewport" content="width=device-width, initial-scale=1"> |
| 6 | +<title>Font Ripper — highda archives</title> |
| 7 | +<meta name="description" content="Bookmarklet that finds every font on a page — CSS, JS FontFace, type testers, not-yet-loaded styles — with previews and ZIP download."> |
| 8 | +<meta name="color-scheme" content="dark"> |
| 9 | +<link rel="preconnect" href="https://fonts.googleapis.com"> |
| 10 | +<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| 11 | +<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=VT323&display=swap" rel="stylesheet"> |
| 12 | +<style> |
| 13 | + :root{ |
| 14 | + --bg:#08080f;--bg-2:#0e0e1c;--ink:#d7d7e6;--muted:#7a7a96; |
| 15 | + --cyan:#55ffff;--magenta:#ff55ff;--line:rgba(85,255,255,.16);--card:rgba(255,255,255,.025); |
| 16 | + --radius:14px;--mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; |
| 17 | + } |
| 18 | + *{box-sizing:border-box} |
| 19 | + body{margin:0;min-height:100vh;background:linear-gradient(180deg,var(--bg),var(--bg-2));color:var(--ink); |
| 20 | + font-family:var(--mono);line-height:1.6;-webkit-font-smoothing:antialiased} |
| 21 | + .wrap{max-width:760px;margin:0 auto;padding:clamp(40px,9vw,90px) 16px 60px} |
| 22 | + .back{color:var(--muted);text-decoration:none;font-size:13px} |
| 23 | + .back:hover{color:var(--cyan)} |
| 24 | + h1{font-family:'VT323',var(--mono);font-weight:400;font-size:clamp(48px,11vw,96px);line-height:.95;margin:18px 0 8px;color:#fff} |
| 25 | + .tagline{margin:0 0 30px;max-width:56ch} |
| 26 | + .tagline em{color:var(--magenta);font-style:normal} |
| 27 | + .panel{border:1px solid var(--line);border-radius:var(--radius);background:var(--card);padding:22px} |
| 28 | + .install{display:flex;flex-wrap:wrap;gap:12px;align-items:center} |
| 29 | + .bookmarklet{display:inline-flex;align-items:center;gap:10px;padding:14px 22px;border:1px solid var(--cyan);border-radius:10px; |
| 30 | + color:var(--cyan);background:rgba(85,255,255,.06);text-decoration:none;font-weight:700;cursor:grab; |
| 31 | + box-shadow:0 10px 40px -14px rgba(85,255,255,.5)} |
| 32 | + .bookmarklet:hover{background:rgba(85,255,255,.12)} |
| 33 | + .bookmarklet[aria-disabled="true"]{opacity:.35;pointer-events:none} |
| 34 | + button{padding:14px 18px;border:1px solid var(--line);border-radius:10px;background:none;color:var(--ink);font:inherit;cursor:pointer} |
| 35 | + button:hover:not(:disabled){border-color:var(--magenta);color:var(--magenta)} |
| 36 | + button:disabled{opacity:.35;cursor:default} |
| 37 | + .status{display:block;margin-top:14px;font-size:12.5px;color:var(--muted)} |
| 38 | + h2{font-size:13px;letter-spacing:.22em;text-transform:uppercase;color:var(--cyan);margin:40px 0 12px} |
| 39 | + ol,ul{padding-left:20px;margin:0} |
| 40 | + li{margin-bottom:6px} |
| 41 | + li::marker{color:var(--magenta)} |
| 42 | + kbd,code{font-family:var(--mono);font-size:.9em;color:#fff} |
| 43 | + .muted{color:var(--muted);font-size:13.5px} |
| 44 | + a{color:var(--cyan)} |
| 45 | +</style> |
| 46 | +</head> |
| 47 | +<body> |
| 48 | +<div class="wrap"> |
| 49 | + <a class="back" href="/">← highda archives</a> |
| 50 | + <h1>Font Ripper</h1> |
| 51 | + <p class="tagline">A bookmarklet that finds <em>every</em> font on a page — CSS, JS <code>FontFace</code>, type testers, even styles the tester hasn't loaded yet — with live previews and one-click ZIP download.</p> |
| 52 | + |
| 53 | + <div class="panel"> |
| 54 | + <div class="install"> |
| 55 | + <a class="bookmarklet" id="bookmarklet" href="#" aria-disabled="true">🔤 Font Ripper</a> |
| 56 | + <button id="copy" disabled>Copy code</button> |
| 57 | + </div> |
| 58 | + <span class="status" id="status">Loading latest build…</span> |
| 59 | + </div> |
| 60 | + |
| 61 | + <h2>Install</h2> |
| 62 | + <ol> |
| 63 | + <li>Show the bookmarks bar (<kbd>Ctrl/⌘ + Shift + B</kbd>).</li> |
| 64 | + <li>Drag the <strong>Font Ripper</strong> button onto it.</li> |
| 65 | + <li>Open any page and click the bookmark.</li> |
| 66 | + </ol> |
| 67 | + <p class="muted">Can't drag (mobile, some browsers)? Hit “Copy code”, create a new bookmark and paste it as the URL.</p> |
| 68 | + |
| 69 | + <h2>What it catches</h2> |
| 70 | + <ul class="muted"> |
| 71 | + <li>CSS <code>@font-face</code> incl. <code>@import</code>, shadow DOM, same-origin iframes and CORS-blocked sheets</li> |
| 72 | + <li>Fonts registered from JS via <code>FontFace</code>, matched to their files by glyph metrics + internal names</li> |
| 73 | + <li>Any font file the page downloaded, even from extension-less URLs</li> |
| 74 | + <li>Font files referenced in the page source that haven't been loaded yet</li> |
| 75 | + </ul> |
| 76 | + <p class="muted">Out of reach: cross-origin iframes (listed so you can run it there), fonts decrypted in JS, text rendered into images.</p> |
| 77 | + <p class="muted">Source & details: <a href="https://github.com/highda/vibecode-snippets/tree/main/bookmarklets/font-ripper">github.com/highda/vibecode-snippets</a></p> |
| 78 | +</div> |
| 79 | +<script> |
| 80 | + // Always the latest build from the source repo (raw.githubusercontent.com allows CORS). |
| 81 | + // Percent-encoded because browsers URL-decode javascript: bookmarks before running them. |
| 82 | + (async () => { |
| 83 | + const SRC = "https://raw.githubusercontent.com/highda/vibecode-snippets/main/bookmarklets/font-ripper/minified.js"; |
| 84 | + const link = document.getElementById("bookmarklet"); |
| 85 | + const copy = document.getElementById("copy"); |
| 86 | + const status = document.getElementById("status"); |
| 87 | + try { |
| 88 | + const res = await fetch(SRC, { cache: "no-cache" }); |
| 89 | + if (!res.ok) throw new Error("HTTP " + res.status); |
| 90 | + const code = (await res.text()).trim().replace(/^javascript:/, ""); |
| 91 | + const href = "javascript:" + encodeURIComponent(code); |
| 92 | + link.href = href; |
| 93 | + link.removeAttribute("aria-disabled"); |
| 94 | + link.onclick = e => { e.preventDefault(); status.textContent = "Drag the button to your bookmarks bar instead of clicking it."; }; |
| 95 | + copy.disabled = false; |
| 96 | + copy.onclick = async () => { |
| 97 | + try { await navigator.clipboard.writeText(href); status.textContent = "Copied — paste it as a bookmark URL."; } |
| 98 | + catch { status.textContent = "Clipboard blocked — drag the button instead."; } |
| 99 | + }; |
| 100 | + status.textContent = `Ready — latest build (${Math.round(code.length / 1024)} kB).`; |
| 101 | + } catch (e) { |
| 102 | + status.textContent = "Couldn't load the latest build: " + e.message; |
| 103 | + } |
| 104 | + })(); |
| 105 | +</script> |
| 106 | +</body> |
| 107 | +</html> |
0 commit comments