Adit Rahman commited on
Commit ·
7b33d33
1
Parent(s): 5247451
NanoFable static demo: all 8 packs served same-origin
Browse files- .gitattributes +1 -35
- README.md +10 -4
- assets/bard-still.png +0 -0
- assets/bard-talk.gif +0 -0
- fonts/eb-garamond-italic.woff2 +0 -0
- fonts/eb-garamond.woff2 +0 -0
- fonts/im-fell-english-italic.woff2 +0 -0
- fonts/im-fell-english.woff2 +0 -0
- index.html +94 -18
- js/app.js +397 -0
- js/display.js +109 -0
- js/model.js +239 -0
- js/pack.js +122 -0
- js/tokenizer.js +103 -0
- manifest.json +109 -0
- models/nanofable-16m-fp16.tpack +3 -0
- models/nanofable-16m-ternary.tpack +3 -0
- models/nanofable-1m-fp16.tpack +3 -0
- models/nanofable-1m-ternary.tpack +3 -0
- models/nanofable-28m-fp16.tpack +3 -0
- models/nanofable-28m-ternary.tpack +3 -0
- models/nanofable-6m-fp16.tpack +3 -0
- models/nanofable-6m-ternary.tpack +3 -0
- style.css +451 -18
- tokenizer.json +0 -0
- vendor/obscenity.mjs +10 -0
.gitattributes
CHANGED
|
@@ -1,35 +1 @@
|
|
| 1 |
-
*.
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
+
*.tpack filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,10 +1,16 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
colorTo: gray
|
| 6 |
sdk: static
|
|
|
|
| 7 |
pinned: false
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: NanoFable
|
| 3 |
+
emoji: 🦫
|
| 4 |
+
colorFrom: yellow
|
| 5 |
colorTo: gray
|
| 6 |
sdk: static
|
| 7 |
+
app_file: index.html
|
| 8 |
pinned: false
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# NanoFable — a tiny ternary storyteller, running in your browser
|
| 12 |
+
|
| 13 |
+
Every model runs client-side: the page fetches a `.tpack` (packed ternary / fp16 weights),
|
| 14 |
+
decodes it in JS, and generates tokens on your CPU. Nothing is sent to a server.
|
| 15 |
+
|
| 16 |
+
Weights: https://huggingface.co/collections/adrahmana — code: https://github.com/adit-rah/nanofable
|
assets/bard-still.png
ADDED
|
assets/bard-talk.gif
ADDED
|
fonts/eb-garamond-italic.woff2
ADDED
|
Binary file (25.4 kB). View file
|
|
|
fonts/eb-garamond.woff2
ADDED
|
Binary file (23.8 kB). View file
|
|
|
fonts/im-fell-english-italic.woff2
ADDED
|
Binary file (57.8 kB). View file
|
|
|
fonts/im-fell-english.woff2
ADDED
|
Binary file (59.9 kB). View file
|
|
|
index.html
CHANGED
|
@@ -1,19 +1,95 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 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>nanofable — a pocket storyteller</title>
|
| 7 |
+
<meta name="description" content="A ~1 MB ternary language model that finishes your story, running entirely in your browser." />
|
| 8 |
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🪶</text></svg>" />
|
| 9 |
+
<link rel="preload" href="fonts/im-fell-english.woff2" as="font" type="font/woff2" crossorigin />
|
| 10 |
+
<link rel="preload" href="fonts/im-fell-english-italic.woff2" as="font" type="font/woff2" crossorigin />
|
| 11 |
+
<link rel="preload" href="fonts/eb-garamond.woff2" as="font" type="font/woff2" crossorigin />
|
| 12 |
+
<link rel="preload" href="fonts/eb-garamond-italic.woff2" as="font" type="font/woff2" crossorigin />
|
| 13 |
+
<link rel="stylesheet" href="style.css" />
|
| 14 |
+
</head>
|
| 15 |
+
<body>
|
| 16 |
+
<header class="site-head">
|
| 17 |
+
<div class="mark">nanofable<span class="mark-sub"> · a pocket storyteller</span></div>
|
| 18 |
+
<button id="install-btn" class="icon-btn" aria-label="Install all models" title="Install all models">
|
| 19 |
+
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 20 |
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" />
|
| 21 |
+
</svg>
|
| 22 |
+
</button>
|
| 23 |
+
</header>
|
| 24 |
+
|
| 25 |
+
<main id="main">
|
| 26 |
+
<section id="hero">
|
| 27 |
+
<h1 class="hero-title">Have a story to tell?</h1>
|
| 28 |
+
<p class="hero-sub">Bar'd is a <span id="hero-bytes">1.2 MB</span> storyteller running entirely in your browser.
|
| 29 |
+
Give him an opening line, and he'll <u><em>probably</em></u> tell you a pretty good story.</p>
|
| 30 |
+
<picture>
|
| 31 |
+
<source media="(prefers-reduced-motion: reduce)" srcset="assets/bard-still.png" />
|
| 32 |
+
<img class="hero-bard" src="assets/bard-talk.gif" alt="Bar'd, the nanofable bard" width="465" height="330" />
|
| 33 |
+
</picture>
|
| 34 |
+
</section>
|
| 35 |
+
<section id="transcript" aria-live="polite"></section>
|
| 36 |
+
</main>
|
| 37 |
+
|
| 38 |
+
<div class="composer-wrap">
|
| 39 |
+
<form id="composer" class="composer">
|
| 40 |
+
<textarea id="input" rows="1" placeholder="Once upon a time…" autocomplete="off" spellcheck="true"></textarea>
|
| 41 |
+
<div class="composer-row">
|
| 42 |
+
<div class="composer-tools">
|
| 43 |
+
<div class="menu-anchor">
|
| 44 |
+
<button type="button" id="model-btn" class="chip" aria-haspopup="true" aria-expanded="false">
|
| 45 |
+
<span id="model-btn-label">loading…</span>
|
| 46 |
+
</button>
|
| 47 |
+
<div id="model-menu" class="menu" role="menu" hidden></div>
|
| 48 |
+
</div>
|
| 49 |
+
<div class="menu-anchor">
|
| 50 |
+
<button type="button" id="config-btn" class="chip chip-icon" aria-label="Sampling settings" title="Sampling settings" aria-haspopup="true" aria-expanded="false">
|
| 51 |
+
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true">
|
| 52 |
+
<line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/>
|
| 53 |
+
</svg>
|
| 54 |
+
</button>
|
| 55 |
+
<div id="config-menu" class="menu menu-config" hidden>
|
| 56 |
+
<p class="menu-heading">Sampling</p>
|
| 57 |
+
<label>Max tokens
|
| 58 |
+
<input id="cfg-max-tokens" type="number" min="1" max="400" step="1" value="150" />
|
| 59 |
+
</label>
|
| 60 |
+
<label>Temperature
|
| 61 |
+
<input id="cfg-temperature" type="number" min="0" max="2" step="0.1" value="0.8" />
|
| 62 |
+
</label>
|
| 63 |
+
<label>Top-k <span class="hint">(0 = off)</span>
|
| 64 |
+
<input id="cfg-top-k" type="number" min="0" max="4096" step="1" value="40" />
|
| 65 |
+
</label>
|
| 66 |
+
<label>Seed
|
| 67 |
+
<input id="cfg-seed" type="number" min="0" step="1" value="0" />
|
| 68 |
+
</label>
|
| 69 |
+
</div>
|
| 70 |
+
</div>
|
| 71 |
+
</div>
|
| 72 |
+
<button id="send-btn" type="submit" class="send" aria-label="Continue the story" disabled>
|
| 73 |
+
<svg class="ic-quill" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 74 |
+
<path d="M20.24 12.24a6 6 0 0 0-8.49-8.49L5 10.5V19h8.5z" /><line x1="16" y1="8" x2="2" y2="22" />
|
| 75 |
+
</svg>
|
| 76 |
+
<svg class="ic-stop" viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true">
|
| 77 |
+
<rect x="6" y="6" width="12" height="12" rx="2" />
|
| 78 |
+
</svg>
|
| 79 |
+
</button>
|
| 80 |
+
</div>
|
| 81 |
+
</form>
|
| 82 |
+
<p class="colophon">every word is written on your machine — nothing leaves the page</p>
|
| 83 |
+
</div>
|
| 84 |
+
|
| 85 |
+
<div id="toast-region" aria-live="assertive"></div>
|
| 86 |
+
<aside id="progress-card" hidden>
|
| 87 |
+
<p class="progress-title">Installing models</p>
|
| 88 |
+
<p id="progress-label" class="progress-label"></p>
|
| 89 |
+
<div class="progress-track"><div id="progress-fill" class="progress-fill"></div></div>
|
| 90 |
+
<p id="progress-overall" class="progress-overall"></p>
|
| 91 |
+
</aside>
|
| 92 |
+
|
| 93 |
+
<script type="module" src="js/app.js"></script>
|
| 94 |
+
</body>
|
| 95 |
</html>
|
js/app.js
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// UI state machine: model registry/picker, install-all flow, config popover,
|
| 2 |
+
// validation toasts (bottom-left), download progress (bottom-right), and the
|
| 3 |
+
// token-streaming story renderer.
|
| 4 |
+
|
| 5 |
+
import { EOS_ID, loadTokenizer } from "./tokenizer.js";
|
| 6 |
+
import { fetchModel, isCached, loadManifest, parsePack } from "./pack.js";
|
| 7 |
+
import { Model, greedy, mulberry32, sampleToken } from "./model.js";
|
| 8 |
+
import { fixMojibake, mojibakeFilter } from "./display.js";
|
| 9 |
+
import {
|
| 10 |
+
RegExpMatcher,
|
| 11 |
+
englishDataset,
|
| 12 |
+
englishRecommendedTransformers,
|
| 13 |
+
} from "../vendor/obscenity.mjs";
|
| 14 |
+
|
| 15 |
+
const $ = (id) => document.getElementById(id);
|
| 16 |
+
const el = {
|
| 17 |
+
installBtn: $("install-btn"),
|
| 18 |
+
hero: $("hero"),
|
| 19 |
+
heroBytes: $("hero-bytes"),
|
| 20 |
+
transcript: $("transcript"),
|
| 21 |
+
main: $("main"),
|
| 22 |
+
composer: $("composer"),
|
| 23 |
+
input: $("input"),
|
| 24 |
+
sendBtn: $("send-btn"),
|
| 25 |
+
modelBtn: $("model-btn"),
|
| 26 |
+
modelBtnLabel: $("model-btn-label"),
|
| 27 |
+
modelMenu: $("model-menu"),
|
| 28 |
+
configBtn: $("config-btn"),
|
| 29 |
+
configMenu: $("config-menu"),
|
| 30 |
+
toastRegion: $("toast-region"),
|
| 31 |
+
progressCard: $("progress-card"),
|
| 32 |
+
progressLabel: $("progress-label"),
|
| 33 |
+
progressFill: $("progress-fill"),
|
| 34 |
+
progressOverall: $("progress-overall"),
|
| 35 |
+
};
|
| 36 |
+
|
| 37 |
+
const state = {
|
| 38 |
+
entries: [], // manifest entries + { available }
|
| 39 |
+
currentId: null,
|
| 40 |
+
model: null,
|
| 41 |
+
tokenizer: null,
|
| 42 |
+
matcher: new RegExpMatcher({
|
| 43 |
+
...englishDataset.build(),
|
| 44 |
+
...englishRecommendedTransformers,
|
| 45 |
+
}),
|
| 46 |
+
generating: false,
|
| 47 |
+
installing: false,
|
| 48 |
+
stopRequested: false,
|
| 49 |
+
config: { maxTokens: 150, temperature: 0.8, topK: 40, seed: 0 },
|
| 50 |
+
};
|
| 51 |
+
|
| 52 |
+
const fmtMB = (b) => `${(b / 1e6).toFixed(1)} MB`;
|
| 53 |
+
const tick = () => new Promise((r) => setTimeout(r, 0));
|
| 54 |
+
|
| 55 |
+
// ---- toasts (bottom-left) --------------------------------------------------
|
| 56 |
+
|
| 57 |
+
function toast(message) {
|
| 58 |
+
const t = document.createElement("div");
|
| 59 |
+
t.className = "toast";
|
| 60 |
+
t.textContent = message;
|
| 61 |
+
el.toastRegion.append(t);
|
| 62 |
+
setTimeout(() => t.remove(), 6000);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
// ---- model registry / picker ------------------------------------------------
|
| 66 |
+
|
| 67 |
+
function currentEntry() {
|
| 68 |
+
return state.entries.find((e) => e.id === state.currentId);
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
function renderModelMenu() {
|
| 72 |
+
el.modelMenu.replaceChildren();
|
| 73 |
+
for (const e of state.entries) {
|
| 74 |
+
const item = document.createElement("button");
|
| 75 |
+
item.type = "button";
|
| 76 |
+
item.className = "menu-item" + (e.id === state.currentId ? " selected" : "");
|
| 77 |
+
item.disabled = !e.available;
|
| 78 |
+
const label = document.createElement("span");
|
| 79 |
+
label.textContent = e.label;
|
| 80 |
+
const size = document.createElement("span");
|
| 81 |
+
size.className = "size";
|
| 82 |
+
size.textContent = fmtMB(e.bytes);
|
| 83 |
+
item.append(label, size);
|
| 84 |
+
item.addEventListener("click", () => {
|
| 85 |
+
closeMenus();
|
| 86 |
+
if (e.id !== state.currentId) switchModel(e.id);
|
| 87 |
+
});
|
| 88 |
+
if (e.available) {
|
| 89 |
+
el.modelMenu.append(item);
|
| 90 |
+
} else {
|
| 91 |
+
// A disabled button swallows hover in some browsers, so the tooltip lives
|
| 92 |
+
// on an enabled wrapper around it.
|
| 93 |
+
const row = document.createElement("span");
|
| 94 |
+
row.className = "menu-row locked";
|
| 95 |
+
const tip = document.createElement("span");
|
| 96 |
+
tip.className = "menu-tip";
|
| 97 |
+
tip.textContent = "Not installed yet — press the install button (top left) to download it.";
|
| 98 |
+
row.append(item, tip);
|
| 99 |
+
el.modelMenu.append(row);
|
| 100 |
+
}
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
function updateChrome() {
|
| 105 |
+
const e = currentEntry();
|
| 106 |
+
el.modelBtnLabel.textContent = e
|
| 107 |
+
? `${e.label} · ${fmtMB(e.bytes)}`
|
| 108 |
+
: "loading…";
|
| 109 |
+
if (e) el.heroBytes.textContent = fmtMB(e.bytes);
|
| 110 |
+
const allInstalled = state.entries.every((x) => x.available);
|
| 111 |
+
el.installBtn.disabled = state.installing || allInstalled;
|
| 112 |
+
el.installBtn.classList.toggle("done", allInstalled);
|
| 113 |
+
if (allInstalled) el.installBtn.title = "All models installed";
|
| 114 |
+
el.sendBtn.disabled = !state.model && !state.generating;
|
| 115 |
+
renderModelMenu();
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
async function switchModel(id) {
|
| 119 |
+
const entry = state.entries.find((e) => e.id === id);
|
| 120 |
+
if (!entry || state.generating) return;
|
| 121 |
+
el.modelBtnLabel.textContent = `loading ${entry.label}…`;
|
| 122 |
+
el.sendBtn.disabled = true;
|
| 123 |
+
try {
|
| 124 |
+
const buffer = await fetchModel(entry.url);
|
| 125 |
+
state.model = new Model(parsePack(buffer));
|
| 126 |
+
state.currentId = id;
|
| 127 |
+
} catch (err) {
|
| 128 |
+
toast(`Couldn't load ${entry.label} — ${err.message}`);
|
| 129 |
+
}
|
| 130 |
+
updateChrome();
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
// ---- install-all flow (progress bottom-right) --------------------------------
|
| 134 |
+
|
| 135 |
+
async function installAll() {
|
| 136 |
+
if (state.installing) return;
|
| 137 |
+
const missing = state.entries.filter((e) => !e.available);
|
| 138 |
+
if (!missing.length) return;
|
| 139 |
+
state.installing = true;
|
| 140 |
+
updateChrome();
|
| 141 |
+
el.progressCard.hidden = false;
|
| 142 |
+
|
| 143 |
+
let done = 0;
|
| 144 |
+
for (const entry of missing) {
|
| 145 |
+
el.progressLabel.textContent = `${entry.label} — ${fmtMB(entry.bytes)}`;
|
| 146 |
+
el.progressOverall.textContent = `${done + 1} of ${missing.length}`;
|
| 147 |
+
el.progressFill.style.width = "0";
|
| 148 |
+
try {
|
| 149 |
+
await fetchModel(entry.url, (loaded, total) => {
|
| 150 |
+
const pct = Math.min(100, (loaded / (total ?? entry.bytes)) * 100);
|
| 151 |
+
el.progressFill.style.width = `${pct}%`;
|
| 152 |
+
});
|
| 153 |
+
entry.available = true;
|
| 154 |
+
renderModelMenu();
|
| 155 |
+
} catch {
|
| 156 |
+
toast(`${entry.label} isn't available yet.`);
|
| 157 |
+
}
|
| 158 |
+
done++;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
el.progressCard.hidden = true;
|
| 162 |
+
state.installing = false;
|
| 163 |
+
updateChrome();
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
// ---- config popover -----------------------------------------------------------
|
| 167 |
+
|
| 168 |
+
const cfgInputs = {
|
| 169 |
+
maxTokens: { el: $("cfg-max-tokens"), min: 1, max: 400, int: true },
|
| 170 |
+
temperature: { el: $("cfg-temperature"), min: 0, max: 2, int: false },
|
| 171 |
+
topK: { el: $("cfg-top-k"), min: 0, max: 4096, int: true },
|
| 172 |
+
seed: { el: $("cfg-seed"), min: 0, max: 2 ** 31 - 1, int: true },
|
| 173 |
+
};
|
| 174 |
+
|
| 175 |
+
for (const [key, c] of Object.entries(cfgInputs)) {
|
| 176 |
+
c.el.addEventListener("change", () => {
|
| 177 |
+
let v = Number(c.el.value);
|
| 178 |
+
if (!Number.isFinite(v)) v = state.config[key];
|
| 179 |
+
v = Math.min(c.max, Math.max(c.min, c.int ? Math.round(v) : v));
|
| 180 |
+
c.el.value = v;
|
| 181 |
+
state.config[key] = v;
|
| 182 |
+
});
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
// ---- menus ----------------------------------------------------------------------
|
| 186 |
+
|
| 187 |
+
function closeMenus() {
|
| 188 |
+
for (const [btn, menu] of [
|
| 189 |
+
[el.modelBtn, el.modelMenu],
|
| 190 |
+
[el.configBtn, el.configMenu],
|
| 191 |
+
]) {
|
| 192 |
+
menu.hidden = true;
|
| 193 |
+
btn.setAttribute("aria-expanded", "false");
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
function toggleMenu(btn, menu) {
|
| 198 |
+
const open = menu.hidden;
|
| 199 |
+
closeMenus();
|
| 200 |
+
if (open) {
|
| 201 |
+
menu.hidden = false;
|
| 202 |
+
btn.setAttribute("aria-expanded", "true");
|
| 203 |
+
}
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
el.modelBtn.addEventListener("click", () => toggleMenu(el.modelBtn, el.modelMenu));
|
| 207 |
+
el.configBtn.addEventListener("click", () => toggleMenu(el.configBtn, el.configMenu));
|
| 208 |
+
document.addEventListener("click", (ev) => {
|
| 209 |
+
if (!ev.target.closest(".menu-anchor")) closeMenus();
|
| 210 |
+
});
|
| 211 |
+
document.addEventListener("keydown", (ev) => {
|
| 212 |
+
if (ev.key === "Escape") closeMenus();
|
| 213 |
+
});
|
| 214 |
+
|
| 215 |
+
// ---- transcript rendering --------------------------------------------------------
|
| 216 |
+
|
| 217 |
+
function addUserLine(text) {
|
| 218 |
+
const label = document.createElement("p");
|
| 219 |
+
label.className = "entry-user";
|
| 220 |
+
label.textContent = "You began";
|
| 221 |
+
el.transcript.append(label);
|
| 222 |
+
|
| 223 |
+
const story = document.createElement("p");
|
| 224 |
+
story.className = "story streaming";
|
| 225 |
+
const first = text.match(/\S/);
|
| 226 |
+
if (first) {
|
| 227 |
+
const cap = document.createElement("span");
|
| 228 |
+
cap.className = "dropcap";
|
| 229 |
+
cap.textContent = text.slice(0, first.index + 1).trimStart();
|
| 230 |
+
story.append(cap, document.createTextNode(text.slice(first.index + 1)));
|
| 231 |
+
} else {
|
| 232 |
+
story.append(document.createTextNode(text));
|
| 233 |
+
}
|
| 234 |
+
el.transcript.append(story);
|
| 235 |
+
return story;
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
function appendText(story, text) {
|
| 239 |
+
if (!text) return;
|
| 240 |
+
story.lastChild.nodeType === Node.TEXT_NODE
|
| 241 |
+
? (story.lastChild.data += text)
|
| 242 |
+
: story.append(document.createTextNode(text));
|
| 243 |
+
el.main.scrollTop = el.main.scrollHeight;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
function finishStory(story, note) {
|
| 247 |
+
story.classList.remove("streaming");
|
| 248 |
+
if (note) {
|
| 249 |
+
const n = document.createElement("p");
|
| 250 |
+
n.className = "story-note";
|
| 251 |
+
n.textContent = note;
|
| 252 |
+
story.after(n);
|
| 253 |
+
}
|
| 254 |
+
const fin = document.createElement("p");
|
| 255 |
+
fin.className = "fin";
|
| 256 |
+
fin.textContent = "❦";
|
| 257 |
+
el.transcript.append(fin);
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
// ---- generation -------------------------------------------------------------------
|
| 261 |
+
|
| 262 |
+
function setGenerating(on) {
|
| 263 |
+
state.generating = on;
|
| 264 |
+
document.body.classList.toggle("generating", on);
|
| 265 |
+
el.modelBtn.disabled = on;
|
| 266 |
+
el.configBtn.disabled = on;
|
| 267 |
+
el.input.disabled = on;
|
| 268 |
+
el.sendBtn.setAttribute("aria-label", on ? "Stop writing" : "Continue the story");
|
| 269 |
+
if (on) closeMenus();
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
// Attempts to phish keys/secrets out of the storyteller, or a pasted key itself.
|
| 273 |
+
const KEY_HUNT_RE =
|
| 274 |
+
/\b(?:api|secret|access|private|auth(?:orization)?)[\s_-]*(?:key|token|credential)s?\b|\bsk-[A-Za-z0-9]{8,}\b/i;
|
| 275 |
+
|
| 276 |
+
function validate(text, ids) {
|
| 277 |
+
if (!text.trim()) return "Write an opening line first.";
|
| 278 |
+
if (KEY_HUNT_RE.test(text))
|
| 279 |
+
return "Bar'd has no secrets here to tell :D";
|
| 280 |
+
if (state.matcher.hasMatch(text))
|
| 281 |
+
return "Guys, please don't swear.";
|
| 282 |
+
const ctx = state.model.cfg.ctx;
|
| 283 |
+
if (ids.length + state.config.maxTokens > ctx)
|
| 284 |
+
return `That opening is too long at ${ids.length} tokens of opening in addition to the ${state.config.maxTokens} max tokens exceeding the model's ${ctx}-token memory.`;
|
| 285 |
+
return null;
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
async function runStory(text) {
|
| 289 |
+
const ids = state.tokenizer.encode(text);
|
| 290 |
+
const problem = validate(text, ids);
|
| 291 |
+
if (problem) {
|
| 292 |
+
toast(problem);
|
| 293 |
+
return;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
document.body.classList.add("storytime");
|
| 297 |
+
setGenerating(true);
|
| 298 |
+
state.stopRequested = false;
|
| 299 |
+
const story = addUserLine(fixMojibake(text));
|
| 300 |
+
const model = state.model;
|
| 301 |
+
const { maxTokens, temperature, topK, seed } = state.config;
|
| 302 |
+
|
| 303 |
+
try {
|
| 304 |
+
model.reset();
|
| 305 |
+
let logits = null;
|
| 306 |
+
for (let i = 0; i < ids.length; i++) {
|
| 307 |
+
logits = model.step(ids[i]);
|
| 308 |
+
if (i % 16 === 15) await tick();
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
const rng = mulberry32(seed);
|
| 312 |
+
const decoder = state.tokenizer.streamDecoder();
|
| 313 |
+
const display = mojibakeFilter();
|
| 314 |
+
let note = null;
|
| 315 |
+
for (let n = 0; n < maxTokens; n++) {
|
| 316 |
+
if (state.stopRequested) break;
|
| 317 |
+
const id = sampleToken(logits, { temperature, topK }, rng);
|
| 318 |
+
if (id === EOS_ID) break;
|
| 319 |
+
appendText(story, display.push(decoder.push(id)));
|
| 320 |
+
if (model.pos >= model.cfg.ctx) {
|
| 321 |
+
note = "The model reached its context limit and had to stop.";
|
| 322 |
+
break;
|
| 323 |
+
}
|
| 324 |
+
logits = model.step(id);
|
| 325 |
+
await tick();
|
| 326 |
+
}
|
| 327 |
+
appendText(story, display.push(decoder.flush()));
|
| 328 |
+
appendText(story, display.flush());
|
| 329 |
+
finishStory(story, note);
|
| 330 |
+
} catch (err) {
|
| 331 |
+
finishStory(story, `Something went wrong while writing: ${err.message}`);
|
| 332 |
+
}
|
| 333 |
+
setGenerating(false);
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
// ---- composer events -----------------------------------------------------------
|
| 337 |
+
|
| 338 |
+
el.composer.addEventListener("submit", (ev) => {
|
| 339 |
+
ev.preventDefault();
|
| 340 |
+
if (state.generating) {
|
| 341 |
+
state.stopRequested = true;
|
| 342 |
+
return;
|
| 343 |
+
}
|
| 344 |
+
if (!state.model) return;
|
| 345 |
+
const text = el.input.value;
|
| 346 |
+
el.input.value = "";
|
| 347 |
+
el.input.style.height = "auto";
|
| 348 |
+
runStory(text);
|
| 349 |
+
});
|
| 350 |
+
|
| 351 |
+
el.input.addEventListener("keydown", (ev) => {
|
| 352 |
+
if (ev.key === "Enter" && !ev.shiftKey) {
|
| 353 |
+
ev.preventDefault();
|
| 354 |
+
el.composer.requestSubmit();
|
| 355 |
+
}
|
| 356 |
+
});
|
| 357 |
+
|
| 358 |
+
el.input.addEventListener("input", () => {
|
| 359 |
+
el.input.style.height = "auto";
|
| 360 |
+
el.input.style.height = `${Math.min(el.input.scrollHeight, 144)}px`;
|
| 361 |
+
});
|
| 362 |
+
|
| 363 |
+
el.installBtn.addEventListener("click", installAll);
|
| 364 |
+
|
| 365 |
+
// ---- startup -------------------------------------------------------------------
|
| 366 |
+
|
| 367 |
+
async function main() {
|
| 368 |
+
try {
|
| 369 |
+
const [manifest, tokenizer] = await Promise.all([
|
| 370 |
+
loadManifest(),
|
| 371 |
+
loadTokenizer(),
|
| 372 |
+
]);
|
| 373 |
+
state.tokenizer = tokenizer;
|
| 374 |
+
state.entries = await Promise.all(
|
| 375 |
+
manifest.models.map(async (e) => ({
|
| 376 |
+
...e,
|
| 377 |
+
available: e.baked || (await isCached(e.url)),
|
| 378 |
+
}))
|
| 379 |
+
);
|
| 380 |
+
|
| 381 |
+
// Default: smallest gate-passing model that's available; fallback = baked.
|
| 382 |
+
const candidates = state.entries
|
| 383 |
+
.filter((e) => e.available && e.gate === true)
|
| 384 |
+
.sort((a, b) => a.bytes - b.bytes);
|
| 385 |
+
const first = candidates[0] ?? state.entries.find((e) => e.baked);
|
| 386 |
+
updateChrome();
|
| 387 |
+
await switchModel(first.id);
|
| 388 |
+
} catch (err) {
|
| 389 |
+
toast(`The storyteller couldn't start: ${err.message}`);
|
| 390 |
+
}
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
// Dev/parity hook (docs/demo_site.md): greedy token ids for a prompt.
|
| 394 |
+
window.__greedy = (prompt, n) =>
|
| 395 |
+
greedy(state.model, state.tokenizer.encode(prompt), n);
|
| 396 |
+
|
| 397 |
+
main();
|
js/display.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Display-time repair of the TinyStories corpus's CP1252 mojibake.
|
| 2 |
+
// The dataset stores non-ASCII punctuation double-encoded (each UTF-8 byte re-read
|
| 3 |
+
// as CP1252), so the model legitimately *generates* mojibake — the sequences are
|
| 4 |
+
// merged BPE tokens — and it can also emit *fragments* of them (a continuation
|
| 5 |
+
// token without its lead byte), which no lookup table of known sequences can
|
| 6 |
+
// anticipate. So the encoding is reversed generically instead: map each char back
|
| 7 |
+
// to its CP1252 byte, decode valid UTF-8 runs to the intended character, and treat
|
| 8 |
+
// anything that fails as mojibake residue.
|
| 9 |
+
//
|
| 10 |
+
// Residue policy differs by source:
|
| 11 |
+
// - model output (mojibakeFilter): residue is DROPPED — the corpus's visible text
|
| 12 |
+
// is ASCII + mojibake only, so an undecodable non-ASCII char from the model is a
|
| 13 |
+
// broken fragment, never content. Exception: a bare "â€" whose third byte was
|
| 14 |
+
// lost is the corpus's broken closing quote — rendered ”.
|
| 15 |
+
// - user input (fixMojibake): residue is KEPT — people legitimately type curly
|
| 16 |
+
// quotes, é, emoji.
|
| 17 |
+
// Token ids, the tokenizer, the dataset, and the parity hooks are untouched.
|
| 18 |
+
|
| 19 |
+
// CP1252 codepoint -> byte for the 0x80–0x9F specials. Every other char below
|
| 20 |
+
// 0x100 maps to itself — including the bytes CP1252 leaves *undefined* (e.g.
|
| 21 |
+
// 0x9D), which the dataset pipeline carried through as literal C1 controls.
|
| 22 |
+
const CP1252_HIGH = {
|
| 23 |
+
0x20ac: 0x80, 0x201a: 0x82, 0x0192: 0x83, 0x201e: 0x84, 0x2026: 0x85,
|
| 24 |
+
0x2020: 0x86, 0x2021: 0x87, 0x02c6: 0x88, 0x2030: 0x89, 0x0160: 0x8a,
|
| 25 |
+
0x2039: 0x8b, 0x0152: 0x8c, 0x017d: 0x8e, 0x2018: 0x91, 0x2019: 0x92,
|
| 26 |
+
0x201c: 0x93, 0x201d: 0x94, 0x2022: 0x95, 0x2013: 0x96, 0x2014: 0x97,
|
| 27 |
+
0x02dc: 0x98, 0x2122: 0x99, 0x0161: 0x9a, 0x203a: 0x9b, 0x0153: 0x9c,
|
| 28 |
+
0x017e: 0x9e, 0x0178: 0x9f,
|
| 29 |
+
};
|
| 30 |
+
|
| 31 |
+
// undefined = not CP1252-expressible, so it cannot be part of mojibake.
|
| 32 |
+
const byteOf = (ch) => {
|
| 33 |
+
const cp = ch.codePointAt(0);
|
| 34 |
+
return cp < 0x100 ? cp : CP1252_HIGH[cp];
|
| 35 |
+
};
|
| 36 |
+
|
| 37 |
+
const seqLen = (b) =>
|
| 38 |
+
b >= 0xc2 && b <= 0xdf ? 2 : b >= 0xe0 && b <= 0xef ? 3 : b >= 0xf0 && b <= 0xf4 ? 4 : 0;
|
| 39 |
+
const isCont = (b) => b >= 0x80 && b <= 0xbf;
|
| 40 |
+
|
| 41 |
+
const utf8 = new TextDecoder("utf-8", { fatal: true });
|
| 42 |
+
|
| 43 |
+
// One pass over `s`. When !atEnd, an unfinished candidate sequence at the tail is
|
| 44 |
+
// returned in `hold` for the next chunk to complete (streaming-safe splits).
|
| 45 |
+
function unmojibake(s, atEnd, keepResidue) {
|
| 46 |
+
let out = "";
|
| 47 |
+
let i = 0;
|
| 48 |
+
while (i < s.length) {
|
| 49 |
+
const ch = s[i];
|
| 50 |
+
const b = byteOf(ch);
|
| 51 |
+
if (b === undefined || b < 0x80) {
|
| 52 |
+
out += ch;
|
| 53 |
+
i++;
|
| 54 |
+
continue;
|
| 55 |
+
}
|
| 56 |
+
const need = seqLen(b);
|
| 57 |
+
const bytes = [b];
|
| 58 |
+
let j = i + 1;
|
| 59 |
+
while (j < s.length && bytes.length < need) {
|
| 60 |
+
const cb = byteOf(s[j]);
|
| 61 |
+
if (cb === undefined || !isCont(cb)) break;
|
| 62 |
+
bytes.push(cb);
|
| 63 |
+
j++;
|
| 64 |
+
}
|
| 65 |
+
if (need && bytes.length === need) {
|
| 66 |
+
try {
|
| 67 |
+
const decoded = utf8.decode(new Uint8Array(bytes));
|
| 68 |
+
if (decoded.codePointAt(0) > 0x9f) {
|
| 69 |
+
// (≤ 0x9f would be an invisible C1 control — treat as residue)
|
| 70 |
+
out += decoded;
|
| 71 |
+
i = j;
|
| 72 |
+
continue;
|
| 73 |
+
}
|
| 74 |
+
} catch {
|
| 75 |
+
/* not a real sequence — residue */
|
| 76 |
+
}
|
| 77 |
+
} else if (need && j === s.length && !atEnd) {
|
| 78 |
+
return { out, hold: s.slice(i) };
|
| 79 |
+
}
|
| 80 |
+
if (b === 0xe2 && bytes.length >= 2 && bytes[1] === 0x80) {
|
| 81 |
+
out += "”"; // corpus's broken closing quote (its 0x9d byte lost)
|
| 82 |
+
i += 2;
|
| 83 |
+
continue;
|
| 84 |
+
}
|
| 85 |
+
if (keepResidue) out += ch;
|
| 86 |
+
i++;
|
| 87 |
+
}
|
| 88 |
+
return { out, hold: "" };
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
export function fixMojibake(s) {
|
| 92 |
+
return unmojibake(s, true, true).out;
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
export function mojibakeFilter() {
|
| 96 |
+
let buf = "";
|
| 97 |
+
return {
|
| 98 |
+
push(text) {
|
| 99 |
+
const { out, hold } = unmojibake(buf + text, false, false);
|
| 100 |
+
buf = hold;
|
| 101 |
+
return out;
|
| 102 |
+
},
|
| 103 |
+
flush() {
|
| 104 |
+
const { out } = unmojibake(buf, true, false);
|
| 105 |
+
buf = "";
|
| 106 |
+
return out;
|
| 107 |
+
},
|
| 108 |
+
};
|
| 109 |
+
}
|
js/model.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Client-side forward pass for the nanofable decoder-only transformer, mirroring
|
| 2 |
+
// src/nanofable/model.py exactly: pre-norm RMSNorm (eps 1e-5), NeoX RoPE (base 10000),
|
| 3 |
+
// causal attention (1/sqrt(head_dim)), SwiGLU, tied embedding/LM head.
|
| 4 |
+
// Single-token steps with a KV cache; prefill is the same step run per prompt token.
|
| 5 |
+
|
| 6 |
+
const EPS = 1e-5;
|
| 7 |
+
|
| 8 |
+
// y = W x, W row-major [nOut, nIn]; ternary weights dispatch on .trits.
|
| 9 |
+
function linear(out, x, W, nOut, nIn) {
|
| 10 |
+
const w = W.trits ?? W.f32;
|
| 11 |
+
const scale = W.trits ? W.scale : 1;
|
| 12 |
+
for (let o = 0; o < nOut; o++) {
|
| 13 |
+
let acc = 0;
|
| 14 |
+
const row = o * nIn;
|
| 15 |
+
for (let i = 0; i < nIn; i++) acc += x[i] * w[row + i];
|
| 16 |
+
out[o] = acc * scale;
|
| 17 |
+
}
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
function rmsnorm(out, x, gain, n) {
|
| 21 |
+
let ss = 0;
|
| 22 |
+
for (let i = 0; i < n; i++) ss += x[i] * x[i];
|
| 23 |
+
const inv = 1 / Math.sqrt(ss / n + EPS);
|
| 24 |
+
for (let i = 0; i < n; i++) out[i] = x[i] * inv * gain[i];
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
function silu(v) {
|
| 28 |
+
return v / (1 + Math.exp(-v));
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
export class Model {
|
| 32 |
+
constructor({ header, tensors }) {
|
| 33 |
+
const cfg = header.config;
|
| 34 |
+
this.cfg = cfg;
|
| 35 |
+
this.headDim = cfg.n_embd / cfg.n_head;
|
| 36 |
+
|
| 37 |
+
const get = (name) => {
|
| 38 |
+
const t = tensors.get(name);
|
| 39 |
+
if (!t) throw new Error(`pack missing tensor ${name}`);
|
| 40 |
+
return t;
|
| 41 |
+
};
|
| 42 |
+
this.tokEmb = get("tok_emb.weight").f32; // [vocab, n_embd], also the tied head
|
| 43 |
+
this.finalNorm = get("final_norm.weight").f32;
|
| 44 |
+
this.layers = [];
|
| 45 |
+
for (let i = 0; i < cfg.n_layer; i++) {
|
| 46 |
+
const p = `blocks.${i}.`;
|
| 47 |
+
this.layers.push({
|
| 48 |
+
attnNorm: get(p + "attn_norm.weight").f32,
|
| 49 |
+
q: get(p + "attn.q.weight"), k: get(p + "attn.k.weight"),
|
| 50 |
+
v: get(p + "attn.v.weight"), o: get(p + "attn.o.weight"),
|
| 51 |
+
mlpNorm: get(p + "mlp_norm.weight").f32,
|
| 52 |
+
gate: get(p + "mlp.gate.weight"), up: get(p + "mlp.up.weight"),
|
| 53 |
+
down: get(p + "mlp.down.weight"),
|
| 54 |
+
});
|
| 55 |
+
}
|
| 56 |
+
this.mlpHidden = get("blocks.0.mlp.gate.weight").trits
|
| 57 |
+
? tensors.get("blocks.0.mlp.gate.weight").trits.length / cfg.n_embd
|
| 58 |
+
: tensors.get("blocks.0.mlp.gate.weight").f32.length / cfg.n_embd;
|
| 59 |
+
|
| 60 |
+
// RoPE cache: cos/sin of pos * invFreq[j], j < headDim/2 (NeoX pairs j, j+hd/2).
|
| 61 |
+
const half = this.headDim / 2;
|
| 62 |
+
this.ropeCos = new Float32Array(cfg.ctx * half);
|
| 63 |
+
this.ropeSin = new Float32Array(cfg.ctx * half);
|
| 64 |
+
for (let p = 0; p < cfg.ctx; p++) {
|
| 65 |
+
for (let j = 0; j < half; j++) {
|
| 66 |
+
const angle = p / 10000 ** ((2 * j) / this.headDim);
|
| 67 |
+
this.ropeCos[p * half + j] = Math.cos(angle);
|
| 68 |
+
this.ropeSin[p * half + j] = Math.sin(angle);
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
// KV cache + scratch buffers.
|
| 73 |
+
const E = cfg.n_embd;
|
| 74 |
+
this.kCache = this.layers.map(() => new Float32Array(cfg.ctx * E));
|
| 75 |
+
this.vCache = this.layers.map(() => new Float32Array(cfg.ctx * E));
|
| 76 |
+
this.pos = 0;
|
| 77 |
+
this.x = new Float32Array(E);
|
| 78 |
+
this.xn = new Float32Array(E);
|
| 79 |
+
this.qB = new Float32Array(E);
|
| 80 |
+
this.kB = new Float32Array(E);
|
| 81 |
+
this.vB = new Float32Array(E);
|
| 82 |
+
this.attnB = new Float32Array(E);
|
| 83 |
+
this.projB = new Float32Array(E);
|
| 84 |
+
this.gB = new Float32Array(this.mlpHidden);
|
| 85 |
+
this.uB = new Float32Array(this.mlpHidden);
|
| 86 |
+
this.scores = new Float32Array(cfg.ctx);
|
| 87 |
+
this.logits = new Float32Array(cfg.vocab);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
reset() {
|
| 91 |
+
this.pos = 0;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
applyRope(vec, pos) {
|
| 95 |
+
const { n_head } = this.cfg;
|
| 96 |
+
const hd = this.headDim, half = hd / 2;
|
| 97 |
+
for (let h = 0; h < n_head; h++) {
|
| 98 |
+
const base = h * hd;
|
| 99 |
+
for (let j = 0; j < half; j++) {
|
| 100 |
+
const c = this.ropeCos[pos * half + j];
|
| 101 |
+
const s = this.ropeSin[pos * half + j];
|
| 102 |
+
const a = vec[base + j], b = vec[base + j + half];
|
| 103 |
+
vec[base + j] = a * c - b * s;
|
| 104 |
+
vec[base + j + half] = b * c + a * s;
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
// Feed one token at the current position; returns logits (valid until next step).
|
| 110 |
+
step(tokenId) {
|
| 111 |
+
const cfg = this.cfg, E = cfg.n_embd, hd = this.headDim;
|
| 112 |
+
const pos = this.pos;
|
| 113 |
+
if (pos >= cfg.ctx) throw new Error("context window full");
|
| 114 |
+
this.x.set(this.tokEmb.subarray(tokenId * E, (tokenId + 1) * E));
|
| 115 |
+
|
| 116 |
+
for (let l = 0; l < this.layers.length; l++) {
|
| 117 |
+
const L = this.layers[l];
|
| 118 |
+
// attention
|
| 119 |
+
rmsnorm(this.xn, this.x, L.attnNorm, E);
|
| 120 |
+
linear(this.qB, this.xn, L.q, E, E);
|
| 121 |
+
linear(this.kB, this.xn, L.k, E, E);
|
| 122 |
+
linear(this.vB, this.xn, L.v, E, E);
|
| 123 |
+
this.applyRope(this.qB, pos);
|
| 124 |
+
this.applyRope(this.kB, pos);
|
| 125 |
+
this.kCache[l].set(this.kB, pos * E);
|
| 126 |
+
this.vCache[l].set(this.vB, pos * E);
|
| 127 |
+
|
| 128 |
+
const K = this.kCache[l], V = this.vCache[l];
|
| 129 |
+
const invSqrt = 1 / Math.sqrt(hd);
|
| 130 |
+
for (let h = 0; h < cfg.n_head; h++) {
|
| 131 |
+
const ho = h * hd;
|
| 132 |
+
let max = -Infinity;
|
| 133 |
+
for (let p = 0; p <= pos; p++) {
|
| 134 |
+
let dot = 0;
|
| 135 |
+
const ko = p * E + ho;
|
| 136 |
+
for (let j = 0; j < hd; j++) dot += this.qB[ho + j] * K[ko + j];
|
| 137 |
+
const sc = dot * invSqrt;
|
| 138 |
+
this.scores[p] = sc;
|
| 139 |
+
if (sc > max) max = sc;
|
| 140 |
+
}
|
| 141 |
+
let sum = 0;
|
| 142 |
+
for (let p = 0; p <= pos; p++) {
|
| 143 |
+
const e = Math.exp(this.scores[p] - max);
|
| 144 |
+
this.scores[p] = e;
|
| 145 |
+
sum += e;
|
| 146 |
+
}
|
| 147 |
+
for (let j = 0; j < hd; j++) {
|
| 148 |
+
let acc = 0;
|
| 149 |
+
for (let p = 0; p <= pos; p++) acc += this.scores[p] * V[p * E + ho + j];
|
| 150 |
+
this.attnB[ho + j] = acc / sum;
|
| 151 |
+
}
|
| 152 |
+
}
|
| 153 |
+
linear(this.projB, this.attnB, L.o, E, E);
|
| 154 |
+
for (let i = 0; i < E; i++) this.x[i] += this.projB[i];
|
| 155 |
+
|
| 156 |
+
// mlp
|
| 157 |
+
rmsnorm(this.xn, this.x, L.mlpNorm, E);
|
| 158 |
+
linear(this.gB, this.xn, L.gate, this.mlpHidden, E);
|
| 159 |
+
linear(this.uB, this.xn, L.up, this.mlpHidden, E);
|
| 160 |
+
for (let i = 0; i < this.mlpHidden; i++) this.gB[i] = silu(this.gB[i]) * this.uB[i];
|
| 161 |
+
linear(this.projB, this.gB, L.down, E, this.mlpHidden);
|
| 162 |
+
for (let i = 0; i < E; i++) this.x[i] += this.projB[i];
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
rmsnorm(this.xn, this.x, this.finalNorm, E);
|
| 166 |
+
linear(this.logits, this.xn, { f32: this.tokEmb }, cfg.vocab, E);
|
| 167 |
+
this.pos++;
|
| 168 |
+
return this.logits;
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
// Run all prompt tokens; returns logits after the last one.
|
| 172 |
+
prefill(ids) {
|
| 173 |
+
let logits = null;
|
| 174 |
+
for (const id of ids) logits = this.step(id);
|
| 175 |
+
return logits;
|
| 176 |
+
}
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
// Deterministic PRNG for reproducible sampling (JS-vs-JS; torch RNG differs).
|
| 180 |
+
// The seed is scrambled splitmix-style first: raw small seeds (0, 1, 2 …) give
|
| 181 |
+
// mulberry32 a near-empty state whose first few outputs are biased tiny, which
|
| 182 |
+
// visibly skews the first sampled tokens.
|
| 183 |
+
export function mulberry32(seed) {
|
| 184 |
+
let a = (seed >>> 0) ^ 0x9e3779b9;
|
| 185 |
+
a = Math.imul(a ^ (a >>> 16), 0x21f0aaad);
|
| 186 |
+
a = Math.imul(a ^ (a >>> 15), 0x735a2d97);
|
| 187 |
+
a = (a ^ (a >>> 15)) >>> 0;
|
| 188 |
+
return function () {
|
| 189 |
+
a = (a + 0x6d2b79f5) | 0;
|
| 190 |
+
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
| 191 |
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
| 192 |
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
| 193 |
+
};
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
// Mirrors src/nanofable/generate.py next_token: temperature floor 1e-6, top-k keeps
|
| 197 |
+
// ties at the k-th value, top_k=0 disables the mask, then multinomial via CDF.
|
| 198 |
+
export function sampleToken(logits, { temperature, topK }, rand) {
|
| 199 |
+
const n = logits.length;
|
| 200 |
+
const t = Math.max(temperature, 1e-6);
|
| 201 |
+
const scaled = new Float32Array(n);
|
| 202 |
+
for (let i = 0; i < n; i++) scaled[i] = logits[i] / t;
|
| 203 |
+
|
| 204 |
+
if (topK) {
|
| 205 |
+
const k = Math.min(topK, n);
|
| 206 |
+
const sorted = Float32Array.from(scaled).sort().reverse();
|
| 207 |
+
const threshold = sorted[k - 1];
|
| 208 |
+
for (let i = 0; i < n; i++) if (scaled[i] < threshold) scaled[i] = -Infinity;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
let max = -Infinity;
|
| 212 |
+
for (let i = 0; i < n; i++) if (scaled[i] > max) max = scaled[i];
|
| 213 |
+
let sum = 0;
|
| 214 |
+
for (let i = 0; i < n; i++) {
|
| 215 |
+
scaled[i] = Math.exp(scaled[i] - max);
|
| 216 |
+
sum += scaled[i];
|
| 217 |
+
}
|
| 218 |
+
const r = rand() * sum;
|
| 219 |
+
let cum = 0;
|
| 220 |
+
for (let i = 0; i < n; i++) {
|
| 221 |
+
cum += scaled[i];
|
| 222 |
+
if (r < cum) return i;
|
| 223 |
+
}
|
| 224 |
+
return n - 1;
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
// Dev/parity helper: greedy (argmax) continuation, comparable to a Python argmax loop.
|
| 228 |
+
export function greedy(model, ids, n) {
|
| 229 |
+
model.reset();
|
| 230 |
+
let logits = model.prefill(ids);
|
| 231 |
+
const out = [];
|
| 232 |
+
for (let i = 0; i < n && model.pos < model.cfg.ctx; i++) {
|
| 233 |
+
let best = 0;
|
| 234 |
+
for (let j = 1; j < logits.length; j++) if (logits[j] > logits[best]) best = j;
|
| 235 |
+
out.push(best);
|
| 236 |
+
logits = model.step(best);
|
| 237 |
+
}
|
| 238 |
+
return out;
|
| 239 |
+
}
|
js/pack.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// .tpack (pack format v1 "TCP1") loader + model download/cache helpers.
|
| 2 |
+
// Format is defined normatively in src/nanofable/pack.py.
|
| 3 |
+
|
| 4 |
+
const MAGIC = 0x31504354; // "TCP1" little-endian
|
| 5 |
+
const ALIGN = 8;
|
| 6 |
+
const CACHE_NAME = "nanofable-models";
|
| 7 |
+
|
| 8 |
+
// 243 -> 5 trits lookup, built once.
|
| 9 |
+
const TRIT_LUT = (() => {
|
| 10 |
+
const lut = new Int8Array(243 * 5);
|
| 11 |
+
for (let b = 0; b < 243; b++) {
|
| 12 |
+
let v = b;
|
| 13 |
+
for (let j = 0; j < 5; j++) {
|
| 14 |
+
lut[b * 5 + j] = (v % 3) - 1;
|
| 15 |
+
v = (v / 3) | 0;
|
| 16 |
+
}
|
| 17 |
+
}
|
| 18 |
+
return lut;
|
| 19 |
+
})();
|
| 20 |
+
|
| 21 |
+
function unpackTrits(bytes, numel) {
|
| 22 |
+
const out = new Int8Array(numel);
|
| 23 |
+
const full = Math.floor(numel / 5);
|
| 24 |
+
let o = 0;
|
| 25 |
+
for (let i = 0; i < full; i++) {
|
| 26 |
+
const base = bytes[i] * 5;
|
| 27 |
+
out[o++] = TRIT_LUT[base];
|
| 28 |
+
out[o++] = TRIT_LUT[base + 1];
|
| 29 |
+
out[o++] = TRIT_LUT[base + 2];
|
| 30 |
+
out[o++] = TRIT_LUT[base + 3];
|
| 31 |
+
out[o++] = TRIT_LUT[base + 4];
|
| 32 |
+
}
|
| 33 |
+
for (let j = 0; o < numel; j++) out[o++] = TRIT_LUT[bytes[full] * 5 + j];
|
| 34 |
+
return out;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
function halfToFloat32(u16) {
|
| 38 |
+
const out = new Float32Array(u16.length);
|
| 39 |
+
for (let i = 0; i < u16.length; i++) {
|
| 40 |
+
const h = u16[i];
|
| 41 |
+
const sign = h & 0x8000 ? -1 : 1;
|
| 42 |
+
const exp = (h & 0x7c00) >> 10;
|
| 43 |
+
const frac = h & 0x03ff;
|
| 44 |
+
if (exp === 0) out[i] = sign * 2 ** -14 * (frac / 1024);
|
| 45 |
+
else if (exp === 0x1f) out[i] = frac ? NaN : sign * Infinity;
|
| 46 |
+
else out[i] = sign * 2 ** (exp - 15) * (1 + frac / 1024);
|
| 47 |
+
}
|
| 48 |
+
return out;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
// Parse a pack into { header, tensors }. Tensor values are either
|
| 52 |
+
// { f32: Float32Array } or { trits: Int8Array, scale: number } — matmul-ready.
|
| 53 |
+
export function parsePack(buffer) {
|
| 54 |
+
const view = new DataView(buffer);
|
| 55 |
+
if (view.getUint32(0, true) !== MAGIC) throw new Error("not a TCP1 pack");
|
| 56 |
+
const headerLen = view.getUint32(4, true);
|
| 57 |
+
const header = JSON.parse(
|
| 58 |
+
new TextDecoder().decode(new Uint8Array(buffer, 8, headerLen))
|
| 59 |
+
);
|
| 60 |
+
const payload = Math.ceil((8 + headerLen) / ALIGN) * ALIGN;
|
| 61 |
+
|
| 62 |
+
const tensors = new Map();
|
| 63 |
+
for (const t of header.tensors) {
|
| 64 |
+
const numel = t.shape.reduce((a, b) => a * b, 1);
|
| 65 |
+
if (t.dtype === "trit5") {
|
| 66 |
+
const bytes = new Uint8Array(buffer, payload + t.offset, t.bytes);
|
| 67 |
+
tensors.set(t.name, { trits: unpackTrits(bytes, numel), scale: t.scale });
|
| 68 |
+
} else {
|
| 69 |
+
const u16 = new Uint16Array(buffer.slice(payload + t.offset, payload + t.offset + t.bytes));
|
| 70 |
+
tensors.set(t.name, { f32: halfToFloat32(u16) });
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
return { header, tensors };
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
export async function loadManifest(url = "manifest.json") {
|
| 77 |
+
const res = await fetch(url);
|
| 78 |
+
if (!res.ok) throw new Error(`manifest fetch failed: ${res.status}`);
|
| 79 |
+
return res.json();
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
const cacheAvailable = () => typeof caches !== "undefined";
|
| 83 |
+
|
| 84 |
+
export async function isCached(url) {
|
| 85 |
+
if (!cacheAvailable()) return false;
|
| 86 |
+
const cache = await caches.open(CACHE_NAME);
|
| 87 |
+
return (await cache.match(url)) !== undefined;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
// Fetch a pack with download progress, storing it in the Cache API. Serves from
|
| 91 |
+
// cache when present. onProgress(loadedBytes, totalBytes|null) fires per chunk.
|
| 92 |
+
export async function fetchModel(url, onProgress) {
|
| 93 |
+
const cache = cacheAvailable() ? await caches.open(CACHE_NAME) : null;
|
| 94 |
+
const hit = cache && (await cache.match(url));
|
| 95 |
+
if (hit) return hit.arrayBuffer();
|
| 96 |
+
|
| 97 |
+
const res = await fetch(url);
|
| 98 |
+
if (!res.ok) throw new Error(`download failed (${res.status})`);
|
| 99 |
+
const total = Number(res.headers.get("Content-Length")) || null;
|
| 100 |
+
const reader = res.body.getReader();
|
| 101 |
+
const chunks = [];
|
| 102 |
+
let loaded = 0;
|
| 103 |
+
for (;;) {
|
| 104 |
+
const { done, value } = await reader.read();
|
| 105 |
+
if (done) break;
|
| 106 |
+
chunks.push(value);
|
| 107 |
+
loaded += value.length;
|
| 108 |
+
if (onProgress) onProgress(loaded, total);
|
| 109 |
+
}
|
| 110 |
+
const data = new Uint8Array(loaded);
|
| 111 |
+
let o = 0;
|
| 112 |
+
for (const c of chunks) {
|
| 113 |
+
data.set(c, o);
|
| 114 |
+
o += c.length;
|
| 115 |
+
}
|
| 116 |
+
if (cache) {
|
| 117 |
+
await cache.put(url, new Response(data.buffer.slice(0), {
|
| 118 |
+
headers: { "Content-Type": "application/octet-stream" },
|
| 119 |
+
}));
|
| 120 |
+
}
|
| 121 |
+
return data.buffer;
|
| 122 |
+
}
|
js/tokenizer.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ByteLevel-BPE port of the frozen 4k tokenizer (web/tokenizer.json — byte-identity
|
| 2 |
+
// with artifacts/tokenizer/tokenizer.json is guarded by tests/test_pack.py).
|
| 3 |
+
// Mirrors src/nanofable/generate.py: no BOS prepended; special ids skipped on decode.
|
| 4 |
+
|
| 5 |
+
export const BOS_ID = 0, EOS_ID = 1, PAD_ID = 2;
|
| 6 |
+
|
| 7 |
+
// GPT-2 byte <-> unicode table: printable bytes map to themselves, the rest to 256+n.
|
| 8 |
+
function byteUnicodeTables() {
|
| 9 |
+
const bs = [];
|
| 10 |
+
for (let b = 33; b <= 126; b++) bs.push(b);
|
| 11 |
+
for (let b = 161; b <= 172; b++) bs.push(b);
|
| 12 |
+
for (let b = 174; b <= 255; b++) bs.push(b);
|
| 13 |
+
const byteToChar = new Array(256), charToByte = new Map();
|
| 14 |
+
let n = 0;
|
| 15 |
+
for (let b = 0; b < 256; b++) {
|
| 16 |
+
const cp = bs.includes(b) ? b : 256 + n++;
|
| 17 |
+
byteToChar[b] = String.fromCharCode(cp);
|
| 18 |
+
charToByte.set(byteToChar[b], b);
|
| 19 |
+
}
|
| 20 |
+
return { byteToChar, charToByte };
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
// GPT-2 pre-tokenizer (ByteLevel use_regex:true, add_prefix_space:false).
|
| 24 |
+
const PRETOKEN_RE =
|
| 25 |
+
/'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+/gu;
|
| 26 |
+
|
| 27 |
+
export class Tokenizer {
|
| 28 |
+
constructor(tokenizerJson) {
|
| 29 |
+
const model = tokenizerJson.model;
|
| 30 |
+
this.vocab = new Map(Object.entries(model.vocab));
|
| 31 |
+
this.idToToken = new Array(this.vocab.size);
|
| 32 |
+
for (const [tok, id] of this.vocab) this.idToToken[id] = tok;
|
| 33 |
+
this.ranks = new Map(model.merges.map(([a, b], i) => [a + " " + b, i]));
|
| 34 |
+
const { byteToChar, charToByte } = byteUnicodeTables();
|
| 35 |
+
this.byteToChar = byteToChar;
|
| 36 |
+
this.charToByte = charToByte;
|
| 37 |
+
this.cache = new Map(); // pretoken -> ids
|
| 38 |
+
this.utf8 = new TextEncoder();
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
// Greedy BPE over one pre-token's byte-level chars.
|
| 42 |
+
bpe(chars) {
|
| 43 |
+
let parts = chars;
|
| 44 |
+
for (;;) {
|
| 45 |
+
let best = null, bestRank = Infinity;
|
| 46 |
+
for (let i = 0; i < parts.length - 1; i++) {
|
| 47 |
+
const r = this.ranks.get(parts[i] + " " + parts[i + 1]);
|
| 48 |
+
if (r !== undefined && r < bestRank) { bestRank = r; best = i; }
|
| 49 |
+
}
|
| 50 |
+
if (best === null) return parts;
|
| 51 |
+
parts = parts
|
| 52 |
+
.slice(0, best)
|
| 53 |
+
.concat([parts[best] + parts[best + 1]], parts.slice(best + 2));
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
encode(text) {
|
| 58 |
+
const ids = [];
|
| 59 |
+
for (const m of text.matchAll(PRETOKEN_RE)) {
|
| 60 |
+
const pre = m[0];
|
| 61 |
+
let cached = this.cache.get(pre);
|
| 62 |
+
if (!cached) {
|
| 63 |
+
const bytes = this.utf8.encode(pre);
|
| 64 |
+
const chars = Array.from(bytes, (b) => this.byteToChar[b]);
|
| 65 |
+
cached = this.bpe(chars)
|
| 66 |
+
.map((t) => this.vocab.get(t))
|
| 67 |
+
.filter((id) => id !== undefined);
|
| 68 |
+
this.cache.set(pre, cached);
|
| 69 |
+
}
|
| 70 |
+
ids.push(...cached);
|
| 71 |
+
}
|
| 72 |
+
return ids;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
decode(ids) {
|
| 76 |
+
const d = this.streamDecoder();
|
| 77 |
+
return ids.map((id) => d.push(id)).join("") + d.flush();
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
// Streaming decoder: emits text per token, holding back incomplete UTF-8 chars.
|
| 81 |
+
streamDecoder() {
|
| 82 |
+
const utf8 = new TextDecoder("utf-8", { fatal: false });
|
| 83 |
+
const self = this;
|
| 84 |
+
return {
|
| 85 |
+
push(id) {
|
| 86 |
+
if (id <= PAD_ID) return ""; // specials skipped, as in HF decode
|
| 87 |
+
const tok = self.idToToken[id];
|
| 88 |
+
if (tok === undefined) return "";
|
| 89 |
+
const bytes = Uint8Array.from(tok, (c) => self.charToByte.get(c));
|
| 90 |
+
return utf8.decode(bytes, { stream: true });
|
| 91 |
+
},
|
| 92 |
+
flush() {
|
| 93 |
+
return utf8.decode();
|
| 94 |
+
},
|
| 95 |
+
};
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
export async function loadTokenizer(url = "tokenizer.json") {
|
| 100 |
+
const res = await fetch(url);
|
| 101 |
+
if (!res.ok) throw new Error(`tokenizer fetch failed: ${res.status}`);
|
| 102 |
+
return new Tokenizer(await res.json());
|
| 103 |
+
}
|
manifest.json
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"comment": "Model registry for the HF Space build. All 8 packs are served same-origin from models/ \u2014 no cross-origin fetch, no presigned-CDN redirect. Naming follows the HF release: NanoFable-<params>-<precision>.",
|
| 3 |
+
"models": [
|
| 4 |
+
{
|
| 5 |
+
"id": "nanofable-1m-ternary",
|
| 6 |
+
"tier": "tiny",
|
| 7 |
+
"params": "1M",
|
| 8 |
+
"precision": "ternary",
|
| 9 |
+
"label": "NanoFable-1M-ternary",
|
| 10 |
+
"url": "models/nanofable-1m-ternary.tpack",
|
| 11 |
+
"bytes": 1225968,
|
| 12 |
+
"ppl": null,
|
| 13 |
+
"judge": null,
|
| 14 |
+
"gate": null,
|
| 15 |
+
"baked": false
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"id": "nanofable-1m-fp16",
|
| 19 |
+
"tier": "tiny",
|
| 20 |
+
"params": "1M",
|
| 21 |
+
"precision": "fp16",
|
| 22 |
+
"label": "NanoFable-1M-fp16",
|
| 23 |
+
"url": "models/nanofable-1m-fp16.tpack",
|
| 24 |
+
"bytes": 2758680,
|
| 25 |
+
"ppl": null,
|
| 26 |
+
"judge": null,
|
| 27 |
+
"gate": null,
|
| 28 |
+
"baked": false
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"id": "nanofable-6m-ternary",
|
| 32 |
+
"tier": "small",
|
| 33 |
+
"params": "6M",
|
| 34 |
+
"precision": "ternary",
|
| 35 |
+
"label": "NanoFable-6M-ternary",
|
| 36 |
+
"url": "models/nanofable-6m-ternary.tpack",
|
| 37 |
+
"bytes": 3074144,
|
| 38 |
+
"ppl": null,
|
| 39 |
+
"judge": null,
|
| 40 |
+
"gate": null,
|
| 41 |
+
"baked": false
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"id": "nanofable-6m-fp16",
|
| 45 |
+
"tier": "small",
|
| 46 |
+
"params": "6M",
|
| 47 |
+
"precision": "fp16",
|
| 48 |
+
"label": "NanoFable-6M-fp16",
|
| 49 |
+
"url": "models/nanofable-6m-fp16.tpack",
|
| 50 |
+
"bytes": 11743256,
|
| 51 |
+
"ppl": null,
|
| 52 |
+
"judge": null,
|
| 53 |
+
"gate": null,
|
| 54 |
+
"baked": false
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
"id": "nanofable-16m-ternary",
|
| 58 |
+
"tier": "medium",
|
| 59 |
+
"params": "16M",
|
| 60 |
+
"precision": "ternary",
|
| 61 |
+
"label": "NanoFable-16M-ternary",
|
| 62 |
+
"url": "models/nanofable-16m-ternary.tpack",
|
| 63 |
+
"bytes": 5999152,
|
| 64 |
+
"ppl": null,
|
| 65 |
+
"judge": null,
|
| 66 |
+
"gate": null,
|
| 67 |
+
"baked": false
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"id": "nanofable-16m-fp16",
|
| 71 |
+
"tier": "medium",
|
| 72 |
+
"params": "16M",
|
| 73 |
+
"precision": "fp16",
|
| 74 |
+
"label": "NanoFable-16M-fp16",
|
| 75 |
+
"url": "models/nanofable-16m-fp16.tpack",
|
| 76 |
+
"bytes": 31477816,
|
| 77 |
+
"ppl": null,
|
| 78 |
+
"judge": null,
|
| 79 |
+
"gate": null,
|
| 80 |
+
"baked": true
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"id": "nanofable-28m-ternary",
|
| 84 |
+
"tier": "large",
|
| 85 |
+
"params": "28M",
|
| 86 |
+
"precision": "ternary",
|
| 87 |
+
"label": "NanoFable-28M-ternary",
|
| 88 |
+
"url": "models/nanofable-28m-ternary.tpack",
|
| 89 |
+
"bytes": 9358936,
|
| 90 |
+
"ppl": null,
|
| 91 |
+
"judge": null,
|
| 92 |
+
"gate": null,
|
| 93 |
+
"baked": false
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"id": "nanofable-28m-fp16",
|
| 97 |
+
"tier": "large",
|
| 98 |
+
"params": "28M",
|
| 99 |
+
"precision": "fp16",
|
| 100 |
+
"label": "NanoFable-28M-fp16",
|
| 101 |
+
"url": "models/nanofable-28m-fp16.tpack",
|
| 102 |
+
"bytes": 55599464,
|
| 103 |
+
"ppl": null,
|
| 104 |
+
"judge": null,
|
| 105 |
+
"gate": null,
|
| 106 |
+
"baked": false
|
| 107 |
+
}
|
| 108 |
+
]
|
| 109 |
+
}
|
models/nanofable-16m-fp16.tpack
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6e7712d73dd1863474a7313ba6414c7c30c5e132de641d8eef318a405fa88f8c
|
| 3 |
+
size 31477816
|
models/nanofable-16m-ternary.tpack
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:79116ddc471c2f333960a69a61a6b452b2e92772c375053997eab7442d76775e
|
| 3 |
+
size 5999152
|
models/nanofable-1m-fp16.tpack
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e30a197b4917e0f5e84d76043e1fd60ff70724f360afb04592ea59d1a7d29d38
|
| 3 |
+
size 2758680
|
models/nanofable-1m-ternary.tpack
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4d4a83324c7cac8c83fd5add3fd8575d94119f84ee81217d41b4af24f0dcf4f0
|
| 3 |
+
size 1225968
|
models/nanofable-28m-fp16.tpack
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:605a15ee37f622d3664a2cc7a371a60e7f7abca8724026959e748630d40d26e2
|
| 3 |
+
size 55599464
|
models/nanofable-28m-ternary.tpack
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5718b4e7c7c42f346a5718c5511ce6e7b3560e8a21d0ab460ea6589bd9e42c15
|
| 3 |
+
size 9358936
|
models/nanofable-6m-fp16.tpack
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6a5c86f0add80fdd9a60a03934b564f2cab22660acc23a135b98f2724ec17223
|
| 3 |
+
size 11743256
|
models/nanofable-6m-ternary.tpack
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:845e8bd6deef46fa13d139d977d0374ec91feea939635e56e354d110623a305c
|
| 3 |
+
size 3074144
|
style.css
CHANGED
|
@@ -1,28 +1,461 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
body {
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
}
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
}
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
}
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
}
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
| 28 |
}
|
|
|
|
| 1 |
+
/* nanofable demo — storybook-on-parchment. Tokens from the design plan:
|
| 2 |
+
parchment ground, ink text, quill-blue accent, gilt for the illuminated touches. */
|
| 3 |
+
|
| 4 |
+
/* Self-hosted fonts (latin subsets), preloaded from index.html so they're ready at
|
| 5 |
+
first paint — no swap-reflow on load, and no third-party request. */
|
| 6 |
+
@font-face {
|
| 7 |
+
font-family: "IM Fell English";
|
| 8 |
+
font-style: normal;
|
| 9 |
+
font-weight: 400;
|
| 10 |
+
font-display: swap;
|
| 11 |
+
src: url("fonts/im-fell-english.woff2") format("woff2");
|
| 12 |
+
}
|
| 13 |
+
@font-face {
|
| 14 |
+
font-family: "IM Fell English";
|
| 15 |
+
font-style: italic;
|
| 16 |
+
font-weight: 400;
|
| 17 |
+
font-display: swap;
|
| 18 |
+
src: url("fonts/im-fell-english-italic.woff2") format("woff2");
|
| 19 |
+
}
|
| 20 |
+
@font-face {
|
| 21 |
+
font-family: "EB Garamond";
|
| 22 |
+
font-style: normal;
|
| 23 |
+
font-weight: 400 600;
|
| 24 |
+
font-display: swap;
|
| 25 |
+
src: url("fonts/eb-garamond.woff2") format("woff2");
|
| 26 |
+
}
|
| 27 |
+
@font-face {
|
| 28 |
+
font-family: "EB Garamond";
|
| 29 |
+
font-style: italic;
|
| 30 |
+
font-weight: 400 600;
|
| 31 |
+
font-display: swap;
|
| 32 |
+
src: url("fonts/eb-garamond-italic.woff2") format("woff2");
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
:root {
|
| 36 |
+
--parchment: #f6f1e5;
|
| 37 |
+
--vellum: #fbf8f0;
|
| 38 |
+
--ink: #2e2a24;
|
| 39 |
+
--faded: #857b6b;
|
| 40 |
+
--quill: #3a5169;
|
| 41 |
+
--gilt: #b08d3e;
|
| 42 |
+
--madder: #8c3b2e; /* error accent only */
|
| 43 |
+
--line: rgba(46, 42, 36, 0.14);
|
| 44 |
+
--font-display: "IM Fell English", "Iowan Old Style", serif;
|
| 45 |
+
--font-body: "EB Garamond", Georgia, serif;
|
| 46 |
+
--font-ui: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
| 47 |
+
--col: 44rem;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
* { box-sizing: border-box; margin: 0; }
|
| 51 |
+
|
| 52 |
+
html, body { height: 100%; }
|
| 53 |
+
|
| 54 |
body {
|
| 55 |
+
display: flex;
|
| 56 |
+
flex-direction: column;
|
| 57 |
+
background: var(--parchment);
|
| 58 |
+
color: var(--ink);
|
| 59 |
+
font-family: var(--font-body);
|
| 60 |
+
font-size: 17px;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
/* ---- header ---- */
|
| 64 |
+
|
| 65 |
+
.site-head {
|
| 66 |
+
display: flex;
|
| 67 |
+
align-items: center;
|
| 68 |
+
justify-content: space-between;
|
| 69 |
+
padding: 0.9rem 1.25rem;
|
| 70 |
}
|
| 71 |
|
| 72 |
+
.mark {
|
| 73 |
+
font-family: var(--font-display);
|
| 74 |
+
font-size: 1.15rem;
|
| 75 |
+
letter-spacing: 0.02em;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.mark-sub { color: var(--faded); font-style: italic; font-size: 0.95rem; }
|
| 79 |
+
|
| 80 |
+
.icon-btn {
|
| 81 |
+
display: grid;
|
| 82 |
+
place-items: center;
|
| 83 |
+
width: 2.4rem;
|
| 84 |
+
height: 2.4rem;
|
| 85 |
+
border: 1px solid var(--line);
|
| 86 |
+
border-radius: 50%;
|
| 87 |
+
background: var(--vellum);
|
| 88 |
+
color: var(--quill);
|
| 89 |
+
cursor: pointer;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
.icon-btn:hover { border-color: var(--quill); }
|
| 93 |
+
.icon-btn:disabled { color: var(--faded); cursor: default; border-color: var(--line); }
|
| 94 |
+
.icon-btn.done { color: var(--gilt); }
|
| 95 |
+
|
| 96 |
+
/* ---- main / hero / transcript ---- */
|
| 97 |
+
|
| 98 |
+
#main {
|
| 99 |
+
flex: 1;
|
| 100 |
+
overflow-y: auto;
|
| 101 |
+
padding: 0 1.25rem;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
#hero {
|
| 105 |
+
max-width: var(--col);
|
| 106 |
+
margin: 12vh auto 0;
|
| 107 |
+
text-align: center;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
body.storytime #hero { display: none; }
|
| 111 |
+
|
| 112 |
+
.hero-bard {
|
| 113 |
+
width: 93px; /* the art's native pixel grid (465 / 5) — keeps pixels even */
|
| 114 |
+
height: auto;
|
| 115 |
+
image-rendering: pixelated;
|
| 116 |
+
margin-top: 1.5rem;
|
| 117 |
}
|
| 118 |
|
| 119 |
+
.hero-title {
|
| 120 |
+
font-family: var(--font-display);
|
| 121 |
+
font-weight: 400;
|
| 122 |
+
font-size: clamp(2.1rem, 6vw, 3.3rem);
|
| 123 |
+
line-height: 1.15;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
.hero-sub {
|
| 127 |
+
margin: 1.1rem auto 0;
|
| 128 |
+
max-width: 34rem;
|
| 129 |
+
color: var(--faded);
|
| 130 |
+
font-size: 1.05rem;
|
| 131 |
+
line-height: 1.55;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
#transcript {
|
| 135 |
+
max-width: var(--col);
|
| 136 |
+
margin: 0 auto;
|
| 137 |
+
padding: 1.5rem 0 2rem;
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
.entry-user {
|
| 141 |
+
margin-top: 2.25rem;
|
| 142 |
+
font-family: var(--font-ui);
|
| 143 |
+
font-size: 0.72rem;
|
| 144 |
+
letter-spacing: 0.14em;
|
| 145 |
+
text-transform: uppercase;
|
| 146 |
+
color: var(--faded);
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
.story {
|
| 150 |
+
margin-top: 0.8rem;
|
| 151 |
+
font-size: 1.16rem;
|
| 152 |
+
line-height: 1.72;
|
| 153 |
+
white-space: pre-wrap;
|
| 154 |
+
overflow-wrap: anywhere; /* unbroken token runs (e.g. "NNNN…") wrap, never overflow */
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
/* Raised initial: inline so it shares the first line's baseline — the line sits
|
| 158 |
+
level with the letter's bottom, and the letter towers above it. */
|
| 159 |
+
.story .dropcap {
|
| 160 |
+
font-family: var(--font-display);
|
| 161 |
+
font-size: 2.9em;
|
| 162 |
+
line-height: 1;
|
| 163 |
+
padding-right: 0.06em;
|
| 164 |
+
color: var(--gilt);
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
.story.streaming::after {
|
| 168 |
+
content: "";
|
| 169 |
+
display: inline-block;
|
| 170 |
+
width: 2px;
|
| 171 |
+
height: 1em;
|
| 172 |
+
margin-left: 2px;
|
| 173 |
+
vertical-align: -0.15em;
|
| 174 |
+
background: var(--quill);
|
| 175 |
+
animation: inkblink 1.1s steps(2, start) infinite;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
@keyframes inkblink { to { visibility: hidden; } }
|
| 179 |
+
|
| 180 |
+
.story-note {
|
| 181 |
+
margin-top: 0.6rem;
|
| 182 |
+
font-family: var(--font-ui);
|
| 183 |
+
font-size: 0.75rem;
|
| 184 |
+
color: var(--faded);
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
.fin {
|
| 188 |
+
margin-top: 0.9rem;
|
| 189 |
+
text-align: center;
|
| 190 |
+
color: var(--gilt);
|
| 191 |
+
font-size: 1rem;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
/* ---- composer ---- */
|
| 195 |
+
|
| 196 |
+
.composer-wrap { padding: 0 1.25rem calc(0.75rem + env(safe-area-inset-bottom)); }
|
| 197 |
+
|
| 198 |
+
.composer {
|
| 199 |
+
max-width: var(--col);
|
| 200 |
+
margin: 0 auto;
|
| 201 |
+
background: var(--vellum);
|
| 202 |
+
border: 1px solid var(--line);
|
| 203 |
+
border-radius: 16px;
|
| 204 |
+
box-shadow: 0 2px 14px rgba(46, 42, 36, 0.07);
|
| 205 |
+
padding: 0.85rem 0.9rem 0.65rem;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
.composer:focus-within { border-color: var(--quill); }
|
| 209 |
+
|
| 210 |
+
#input {
|
| 211 |
+
width: 100%;
|
| 212 |
+
border: 0;
|
| 213 |
+
background: transparent;
|
| 214 |
+
resize: none;
|
| 215 |
+
font-family: var(--font-body);
|
| 216 |
+
font-size: 1.08rem;
|
| 217 |
+
line-height: 1.5;
|
| 218 |
+
color: var(--ink);
|
| 219 |
+
max-height: 9rem;
|
| 220 |
}
|
| 221 |
|
| 222 |
+
#input:focus { outline: none; }
|
| 223 |
+
#input::placeholder { color: var(--faded); font-style: italic; }
|
| 224 |
+
|
| 225 |
+
.composer-row {
|
| 226 |
+
display: flex;
|
| 227 |
+
align-items: flex-end;
|
| 228 |
+
justify-content: space-between;
|
| 229 |
+
margin-top: 0.55rem;
|
| 230 |
+
gap: 0.5rem;
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
.composer-tools { display: flex; gap: 0.4rem; }
|
| 234 |
+
|
| 235 |
+
.menu-anchor { position: relative; }
|
| 236 |
+
|
| 237 |
+
.chip {
|
| 238 |
+
display: inline-flex;
|
| 239 |
+
align-items: center;
|
| 240 |
+
justify-content: center;
|
| 241 |
+
gap: 0.35rem;
|
| 242 |
+
font-family: var(--font-ui);
|
| 243 |
+
font-size: 0.78rem;
|
| 244 |
+
color: var(--ink);
|
| 245 |
+
background: transparent;
|
| 246 |
+
border: 1px solid var(--line);
|
| 247 |
+
border-radius: 999px;
|
| 248 |
+
padding: 0.34rem 0.75rem;
|
| 249 |
+
cursor: pointer;
|
| 250 |
+
white-space: nowrap;
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
/* Reserve the chip's width up front so "loading…" -> model name doesn't reflow. */
|
| 254 |
+
#model-btn { min-width: 11.5rem; }
|
| 255 |
+
|
| 256 |
+
.chip:hover:not(:disabled) { border-color: var(--quill); color: var(--quill); }
|
| 257 |
+
.chip:disabled { color: var(--faded); cursor: default; }
|
| 258 |
+
.chip-icon { padding: 0.34rem 0.55rem; }
|
| 259 |
+
|
| 260 |
+
.send {
|
| 261 |
+
display: grid;
|
| 262 |
+
place-items: center;
|
| 263 |
+
width: 2.5rem;
|
| 264 |
+
height: 2.5rem;
|
| 265 |
+
border: 0;
|
| 266 |
+
border-radius: 50%;
|
| 267 |
+
background: var(--quill);
|
| 268 |
+
color: var(--parchment);
|
| 269 |
+
cursor: pointer;
|
| 270 |
+
flex: 0 0 auto;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
.send:hover:not(:disabled) { background: #2e4157; }
|
| 274 |
+
.send:disabled { background: var(--line); color: var(--faded); cursor: default; }
|
| 275 |
+
.send .ic-stop { display: none; }
|
| 276 |
+
body.generating .send .ic-stop { display: block; }
|
| 277 |
+
body.generating .send .ic-quill { display: none; }
|
| 278 |
+
|
| 279 |
+
.colophon {
|
| 280 |
+
max-width: var(--col);
|
| 281 |
+
margin: 0.5rem auto 0;
|
| 282 |
+
text-align: center;
|
| 283 |
+
font-family: var(--font-ui);
|
| 284 |
+
font-size: 0.7rem;
|
| 285 |
+
letter-spacing: 0.04em;
|
| 286 |
+
color: var(--faded);
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
/* ---- menus ---- */
|
| 290 |
+
|
| 291 |
+
.menu {
|
| 292 |
+
position: absolute;
|
| 293 |
+
bottom: calc(100% + 0.5rem);
|
| 294 |
+
left: 0;
|
| 295 |
+
min-width: 15rem;
|
| 296 |
+
background: var(--vellum);
|
| 297 |
+
border: 1px solid var(--line);
|
| 298 |
+
border-radius: 12px;
|
| 299 |
+
box-shadow: 0 6px 24px rgba(46, 42, 36, 0.14);
|
| 300 |
+
padding: 0.4rem;
|
| 301 |
+
z-index: 20;
|
| 302 |
+
font-family: var(--font-ui);
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
.menu-heading {
|
| 306 |
+
font-size: 0.68rem;
|
| 307 |
+
letter-spacing: 0.14em;
|
| 308 |
+
text-transform: uppercase;
|
| 309 |
+
color: var(--faded);
|
| 310 |
+
padding: 0.35rem 0.6rem 0.45rem;
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
.menu-item {
|
| 314 |
+
display: flex;
|
| 315 |
+
align-items: baseline;
|
| 316 |
+
justify-content: space-between;
|
| 317 |
+
gap: 1rem;
|
| 318 |
+
width: 100%;
|
| 319 |
+
border: 0;
|
| 320 |
+
background: transparent;
|
| 321 |
+
border-radius: 8px;
|
| 322 |
+
padding: 0.45rem 0.6rem;
|
| 323 |
+
font-size: 0.82rem;
|
| 324 |
+
color: var(--ink);
|
| 325 |
+
cursor: pointer;
|
| 326 |
+
text-align: left;
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
.menu-item:hover:not(:disabled) { background: rgba(58, 81, 105, 0.08); }
|
| 330 |
+
.menu-item:disabled { color: var(--faded); cursor: not-allowed; }
|
| 331 |
+
.menu-item .size { color: var(--faded); font-size: 0.74rem; }
|
| 332 |
+
.menu-item.selected { color: var(--quill); font-weight: 600; }
|
| 333 |
+
.menu-item.selected::before { content: "✓ "; }
|
| 334 |
+
|
| 335 |
+
.menu-row { display: block; position: relative; }
|
| 336 |
+
|
| 337 |
+
.menu-tip {
|
| 338 |
+
position: absolute;
|
| 339 |
+
left: calc(100% + 0.5rem);
|
| 340 |
+
top: 50%;
|
| 341 |
+
transform: translateY(-50%);
|
| 342 |
+
width: 12rem;
|
| 343 |
+
background: var(--ink);
|
| 344 |
+
color: var(--parchment);
|
| 345 |
+
border-radius: 8px;
|
| 346 |
+
padding: 0.4rem 0.55rem;
|
| 347 |
+
font-size: 0.72rem;
|
| 348 |
+
line-height: 1.35;
|
| 349 |
+
box-shadow: 0 6px 24px rgba(46, 42, 36, 0.18);
|
| 350 |
+
opacity: 0;
|
| 351 |
+
visibility: hidden;
|
| 352 |
+
transition: opacity 0.12s ease;
|
| 353 |
+
pointer-events: none;
|
| 354 |
+
z-index: 30;
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
.menu-row.locked:hover .menu-tip { opacity: 1; visibility: visible; }
|
| 358 |
+
|
| 359 |
+
.menu-config label {
|
| 360 |
+
display: flex;
|
| 361 |
+
align-items: center;
|
| 362 |
+
justify-content: space-between;
|
| 363 |
+
gap: 1rem;
|
| 364 |
+
padding: 0.3rem 0.6rem;
|
| 365 |
+
font-size: 0.82rem;
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
.menu-config .hint { color: var(--faded); font-size: 0.72rem; }
|
| 369 |
+
|
| 370 |
+
.menu-config input {
|
| 371 |
+
width: 4.6rem;
|
| 372 |
+
font-family: var(--font-ui);
|
| 373 |
+
font-size: 0.82rem;
|
| 374 |
+
color: var(--ink);
|
| 375 |
+
background: var(--parchment);
|
| 376 |
+
border: 1px solid var(--line);
|
| 377 |
+
border-radius: 6px;
|
| 378 |
+
padding: 0.25rem 0.4rem;
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
/* ---- toasts (bottom-left) & progress (bottom-right) ---- */
|
| 382 |
+
|
| 383 |
+
#toast-region {
|
| 384 |
+
position: fixed;
|
| 385 |
+
bottom: 1rem;
|
| 386 |
+
left: 1rem;
|
| 387 |
+
display: flex;
|
| 388 |
+
flex-direction: column;
|
| 389 |
+
gap: 0.5rem;
|
| 390 |
+
z-index: 40;
|
| 391 |
+
max-width: min(22rem, calc(100vw - 2rem));
|
| 392 |
+
pointer-events: none; /* toasts are read-only; never block the composer under them */
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
.toast {
|
| 396 |
+
background: var(--vellum);
|
| 397 |
+
border: 1px solid var(--line);
|
| 398 |
+
border-left: 3px solid var(--madder);
|
| 399 |
+
border-radius: 10px;
|
| 400 |
+
box-shadow: 0 4px 18px rgba(46, 42, 36, 0.12);
|
| 401 |
+
padding: 0.6rem 0.85rem;
|
| 402 |
+
font-family: var(--font-ui);
|
| 403 |
+
font-size: 0.82rem;
|
| 404 |
+
line-height: 1.45;
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
#progress-card {
|
| 408 |
+
position: fixed;
|
| 409 |
+
bottom: 1rem;
|
| 410 |
+
right: 1rem;
|
| 411 |
+
width: min(19rem, calc(100vw - 2rem));
|
| 412 |
+
background: var(--vellum);
|
| 413 |
+
border: 1px solid var(--line);
|
| 414 |
+
border-radius: 12px;
|
| 415 |
+
box-shadow: 0 4px 18px rgba(46, 42, 36, 0.12);
|
| 416 |
+
padding: 0.75rem 0.9rem;
|
| 417 |
+
font-family: var(--font-ui);
|
| 418 |
+
z-index: 40;
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
.progress-title {
|
| 422 |
+
font-size: 0.68rem;
|
| 423 |
+
letter-spacing: 0.14em;
|
| 424 |
+
text-transform: uppercase;
|
| 425 |
+
color: var(--faded);
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
.progress-label { font-size: 0.82rem; margin-top: 0.35rem; }
|
| 429 |
+
|
| 430 |
+
.progress-track {
|
| 431 |
+
height: 4px;
|
| 432 |
+
margin-top: 0.5rem;
|
| 433 |
+
border-radius: 2px;
|
| 434 |
+
background: var(--line);
|
| 435 |
+
overflow: hidden;
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
.progress-fill {
|
| 439 |
+
height: 100%;
|
| 440 |
+
width: 0;
|
| 441 |
+
border-radius: 2px;
|
| 442 |
+
background: var(--quill);
|
| 443 |
+
transition: width 0.15s ease-out;
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
.progress-overall { font-size: 0.72rem; color: var(--faded); margin-top: 0.4rem; }
|
| 447 |
+
|
| 448 |
+
/* ---- a11y & responsive ---- */
|
| 449 |
+
|
| 450 |
+
:focus-visible { outline: 2px solid var(--quill); outline-offset: 2px; }
|
| 451 |
+
|
| 452 |
+
@media (prefers-reduced-motion: reduce) {
|
| 453 |
+
.story.streaming::after { animation: none; }
|
| 454 |
+
.progress-fill { transition: none; }
|
| 455 |
}
|
| 456 |
|
| 457 |
+
@media (max-width: 540px) {
|
| 458 |
+
#hero { margin-top: 7vh; }
|
| 459 |
+
.site-head, #main, .composer-wrap { padding-left: 0.8rem; padding-right: 0.8rem; }
|
| 460 |
+
.chip { font-size: 0.72rem; padding: 0.3rem 0.6rem; }
|
| 461 |
}
|
tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
vendor/obscenity.mjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// obscenity@0.4.6 (MIT, https://github.com/jo3-l/obscenity) — self-contained ESM
|
| 2 |
+
// bundle fetched from https://cdn.jsdelivr.net/npm/obscenity@0.4.6/+esm — do not edit.
|
| 3 |
+
/**
|
| 4 |
+
* Bundled by jsDelivr using Rollup v4.62.2 and esbuild v0.28.1.
|
| 5 |
+
* Original file: /npm/obscenity@0.4.6/dist/index.mjs
|
| 6 |
+
*
|
| 7 |
+
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
| 8 |
+
*/
|
| 9 |
+
var C={},T={},P={},ee;function v(){if(ee)return P;ee=1,Object.defineProperty(P,"__esModule",{value:!0}),P.isHighSurrogate=a,P.isLowSurrogate=l,P.convertSurrogatePairToCodePoint=e,P.isWordChar=n,P.isDigit=r,P.isAlphabetic=o,P.isLowerCase=d,P.isUpperCase=t,P.invertCaseOfAlphabeticChar=h,P.getAndAssertSingleCodePoint=i;function a(s){return 55296<=s&&s<=56319}function l(s){return 56320<=s&&s<=57343}function e(s,c){return(s-55296)*1024+c-56320+65536}function n(s){return r(s)||o(s)}function r(s){return 48<=s&&s<=57}function o(s){return d(s)||t(s)}function d(s){return 97<=s&&s<=122}function t(s){return 65<=s&&s<=90}function h(s){return s^32}function i(s){if([...s].length!==1)throw new RangeError("Expected the input string to be one code point in length.");return s.codePointAt(0)}return P}var te;function re(){if(te)return T;te=1,Object.defineProperty(T,"__esModule",{value:!0}),T.keepStartCensorStrategy=l,T.keepEndCensorStrategy=e,T.asteriskCensorStrategy=n,T.grawlixCensorStrategy=r,T.fixedPhraseCensorStrategy=o,T.fixedCharCensorStrategy=d,T.randomCharFromSetCensorStrategy=t;const a=v();function l(h){return i=>i.overlapsAtStart?h(i):String.fromCodePoint(i.input.codePointAt(i.startIndex))+h({...i,matchLength:i.matchLength-1})}function e(h){return i=>{if(i.overlapsAtEnd)return h(i);const s=String.fromCodePoint(i.input.codePointAt(i.endIndex));return h({...i,matchLength:i.matchLength-1})+s}}function n(){return d("*")}function r(){return t("%@$&*")}function o(h){return()=>h}function d(h){return(0,a.getAndAssertSingleCodePoint)(h),i=>h.repeat(i.matchLength)}function t(h){const i=[...h];if(i.length<2)throw new Error("The character set passed must have at least 2 characters.");return s=>{if(s.matchLength===0)return"";let c=Math.floor(Math.random()*i.length),f=i[c];for(let p=1;p<s.matchLength;p++){let m=Math.floor(Math.random()*(i.length-1));m>=c&&m++,c=m,f+=i[m]}return f}}return T}var b={},D={},B={},ae;function De(){if(ae)return B;ae=1,Object.defineProperty(B,"__esModule",{value:!0}),B.compareIntervals=a;function a(l,e,n,r){return l<n?-1:n<l?1:e<r?-1:r<e?1:0}return B}var ne;function V(){if(ne)return D;ne=1,Object.defineProperty(D,"__esModule",{value:!0}),D.compareMatchByPositionAndId=l;const a=De();function l(e,n){const r=(0,a.compareIntervals)(e.startIndex,e.endIndex,n.startIndex,n.endIndex);return r!==0?r:e.termId===n.termId?0:e.termId<n.termId?-1:1}return D}var se;function Be(){if(se)return b;se=1,Object.defineProperty(b,"__esModule",{value:!0}),b.TextCensor=void 0;const a=V(),l=re();class e{strategy=(0,l.grawlixCensorStrategy)();setStrategy(r){return this.strategy=r,this}applyTo(r,o){if(o.length===0)return r;const d=[...o].sort(a.compareMatchByPositionAndId);let t="",h=0;for(let i=0;i<d.length;i++){const s=d[i];if(h>s.endIndex)continue;const c=s.startIndex<h;c||(t+=r.slice(h,s.startIndex));const f=Math.max(h,s.startIndex),p=i<d.length-1&&s.endIndex>=d[i+1].startIndex&&s.endIndex<d[i+1].endIndex;t+=this.strategy({...s,startIndex:f,input:r,overlapsAtStart:c,overlapsAtEnd:p}),h=s.endIndex+1}return t+=r.slice(h),t}}return b.TextCensor=e,b}var W={},N={},ie;function oe(){if(ie)return N;ie=1,Object.defineProperty(N,"__esModule",{value:!0}),N.assignIncrementingIds=a;function a(l){let e=0;return l.map(n=>({id:e++,pattern:n}))}return N}var de;function le(){if(de)return W;de=1,Object.defineProperty(W,"__esModule",{value:!0}),W.PhraseBuilder=W.DataSet=void 0;const a=oe();class l{containers=[];patternCount=0;patternIdToPhraseContainer=new Map;addAll(r){for(const o of r.containers)this.registerContainer(o);return this}removePhrasesIf(r){this.patternCount=0,this.patternIdToPhraseContainer.clear();const o=this.containers.splice(0);for(const d of o)r(d)||this.registerContainer(d);return this}addPhrase(r){const o=r(new e).build();return this.registerContainer(o),this}getPayloadWithPhraseMetadata(r){const o=this.patternIdToPhraseContainer.get(r.termId);if(o===void 0)throw new Error(`The pattern with ID ${r.termId} does not exist in this dataset.`);return{...r,phraseMetadata:this.containers[o].metadata}}build(){return{blacklistedTerms:(0,a.assignIncrementingIds)(this.containers.flatMap(r=>r.patterns)),whitelistedTerms:this.containers.flatMap(r=>r.whitelistedTerms)}}registerContainer(r){const o=this.containers.push(r)-1;for(let d=0,t=this.patternCount;d<r.patterns.length;d++,t++)this.patternIdToPhraseContainer.set(t,o),this.patternCount++}}W.DataSet=l;class e{patterns=[];whitelistedTerms=[];metadata;addPattern(r){return this.patterns.push(r),this}addWhitelistedTerm(r){return this.whitelistedTerms.push(r),this}setMetadata(r){return this.metadata=r,this}build(){return{patterns:this.patterns,whitelistedTerms:this.whitelistedTerms,metadata:this.metadata}}}return W.PhraseBuilder=e,W}var Y={},he;function Ne(){return he||(he=1,Object.defineProperty(Y,"__esModule",{value:!0})),Y}var S={},y={},_={},ce;function G(){if(ce)return _;ce=1,Object.defineProperty(_,"__esModule",{value:!0}),_.SyntaxKind=void 0;var a;return(function(l){l[l.Optional=0]="Optional",l[l.Wildcard=1]="Wildcard",l[l.Literal=2]="Literal",l[l.BoundaryAssertion=3]="BoundaryAssertion"})(a||(_.SyntaxKind=a={})),_}var ue;function $e(){if(ue)return y;ue=1,Object.defineProperty(y,"__esModule",{value:!0}),y.potentiallyMatchesEmptyString=l,y.compilePatternToRegExp=e,y.getRegExpStringForNode=r;const a=G();function l(o){return o.nodes.every(d=>d.kind===a.SyntaxKind.Optional)}function e(o){let d="";o.requireWordBoundaryAtStart&&(d+="\\b");for(const t of o.nodes)d+=r(t);return o.requireWordBoundaryAtEnd&&(d+="\\b"),new RegExp(d,"gs")}const n=["[",".","*","+","?","^","$","{","}","(",")","|","[","\\","]"].map(o=>o.charCodeAt(0));function r(o){switch(o.kind){case a.SyntaxKind.Literal:{let d="";for(const t of o.chars)n.includes(t)&&(d+="\\"),d+=String.fromCodePoint(t);return d}case a.SyntaxKind.Optional:return`(?:${r(o.childNode)})?`;case a.SyntaxKind.Wildcard:return"."}}return y}var k={},fe;function Ue(){if(fe)return k;fe=1,Object.defineProperty(k,"__esModule",{value:!0}),k.TransformerSet=void 0;let a=class{transformers;statefulTransformers;constructor(e){this.transformers=e,this.statefulTransformers=Array.from({length:this.transformers.length});for(let n=0;n<this.transformers.length;n++){const r=this.transformers[n];r.type===1&&(this.statefulTransformers[n]=r.factory())}}applyTo(e){let n=e;for(let r=0;r<this.transformers.length&&n!==void 0;r++){const o=this.transformers[r];o.type===0?n=o.transform(n):n=this.statefulTransformers[r].transform(n)}return n}resetAll(){for(let e=0;e<this.transformers.length;e++)this.transformers[e].type===1&&this.statefulTransformers[e].reset()}};return k.TransformerSet=a,k}var I={},pe;function J(){if(pe)return I;pe=1,Object.defineProperty(I,"__esModule",{value:!0}),I.CharacterIterator=void 0;const a=v();let l=class{_input;lastPosition=-1;currentPosition=0;_lastWidth=0;constructor(n){this._input=n??""}get input(){return this._input}setInput(n){return this._input=n,this.reset(),this}reset(){this.lastPosition=-1,this.currentPosition=0,this._lastWidth=0}next(){if(this.done)return{done:!0,value:void 0};this.lastPosition=this.currentPosition;const n=this._input.charCodeAt(this.currentPosition++);if(this._lastWidth=1,this.done||!(0,a.isHighSurrogate)(n))return{done:!1,value:n};const r=this._input.charCodeAt(this.currentPosition);return(0,a.isLowSurrogate)(r)?(this._lastWidth++,this.currentPosition++,{done:!1,value:(0,a.convertSurrogatePairToCodePoint)(n,r)}):{done:!1,value:n}}get position(){return this.lastPosition}get lastWidth(){return this._lastWidth}get done(){return this.currentPosition>=this._input.length}[Symbol.iterator](){return this}};return I.CharacterIterator=l,I}var w={},me;function Ke(){if(me)return w;me=1,Object.defineProperty(w,"__esModule",{value:!0}),w.IntervalCollection=void 0;let a=class{dirty=!1;intervals=[];insert(e,n){this.intervals.push([e,n]),this.dirty=!0}query(e,n){if(this.intervals.length===0)return!1;this.dirty&&(this.dirty=!1,this.intervals.sort((r,o)=>r[0]<o[0]?-1:o[0]<r[0]?1:0));for(const r of this.intervals){if(r[0]>e)break;if(r[0]<=e&&n<=r[1])return!0}return!1}values(){return this.intervals.values()}[Symbol.iterator](){return this.values()}};return w.IntervalCollection=a,w}var ge;function Fe(){if(ge)return S;ge=1,Object.defineProperty(S,"__esModule",{value:!0}),S.RegExpMatcher=void 0;const a=$e(),l=Ue(),e=v(),n=J(),r=Ke(),o=V();class d{blacklistedTerms;whitelistedTerms;blacklistMatcherTransformers;whitelistMatcherTransformers;constructor({blacklistedTerms:h,whitelistedTerms:i=[],blacklistMatcherTransformers:s=[],whitelistMatcherTransformers:c=[]}){this.blacklistedTerms=this.compileTerms(h),this.validateWhitelistedTerms(i),this.whitelistedTerms=i,this.blacklistMatcherTransformers=new l.TransformerSet(s),this.whitelistMatcherTransformers=new l.TransformerSet(c)}getAllMatches(h,i=!1){const s=this.getWhitelistedIntervals(h),[c,f]=this.applyTransformers(h,this.blacklistMatcherTransformers),p=[];for(const m of this.blacklistedTerms)for(const g of f.matchAll(m.regExp)){const Z=c[g.index];let M=c[g.index+g[0].length-1];M<h.length-1&&(0,e.isHighSurrogate)(h.charCodeAt(M))&&(0,e.isLowSurrogate)(h.charCodeAt(M+1))&&M++,s.query(Z,M)||p.push({termId:m.id,startIndex:Z,endIndex:M,matchLength:[...g[0]].length})}return i&&p.sort(o.compareMatchByPositionAndId),p}hasMatch(h){const i=this.getWhitelistedIntervals(h),[s,c]=this.applyTransformers(h,this.blacklistMatcherTransformers);for(const f of this.blacklistedTerms)for(const p of c.matchAll(f.regExp)){const m=s[p.index];let g=s[p.index+p[0].length-1];if(g<h.length-1&&(0,e.isHighSurrogate)(h.charCodeAt(g))&&(0,e.isLowSurrogate)(h.charCodeAt(g+1))&&g++,!i.query(m,g))return!0}return!1}getWhitelistedIntervals(h){const i=new r.IntervalCollection,[s,c]=this.applyTransformers(h,this.whitelistMatcherTransformers);for(const f of this.whitelistedTerms){let p=0;for(let m=c.indexOf(f,p);m!==-1;m=c.indexOf(f,p)){let g=s[m+f.length-1];g<h.length-1&&(0,e.isHighSurrogate)(h.charCodeAt(g))&&(0,e.isLowSurrogate)(h.charCodeAt(g+1))&&g++,i.insert(s[m],g),p=m+f.length}}return i}applyTransformers(h,i){const s=[];let c="";const f=new n.CharacterIterator(h);for(const p of f){const m=i.applyTo(p);if(m!==void 0)for(c+=String.fromCodePoint(m);s.length<c.length;)s.push(f.position)}return i.resetAll(),[s,c]}compileTerms(h){const i=[],s=new Set;for(const c of h){if(s.has(c.id))throw new Error(`Duplicate blacklisted term ID ${c.id}.`);if((0,a.potentiallyMatchesEmptyString)(c.pattern))throw new Error(`Pattern with ID ${c.id} potentially matches empty string; this is unsupported.`);i.push({id:c.id,regExp:(0,a.compilePatternToRegExp)(c.pattern)}),s.add(c.id)}return i}validateWhitelistedTerms(h){if(h.some(i=>i.length===0))throw new Error("Whitelisted term set contains empty string; this is unsupported.")}}return S.RegExpMatcher=d,S}var x={},Pe;function Te(){if(Pe)return x;Pe=1,Object.defineProperty(x,"__esModule",{value:!0}),x.ParserError=void 0;class a extends Error{name="ParserError";line;column;constructor(e,n,r){super(`${n}:${r}: ${e}`),this.line=n,this.column=r}}return x.ParserError=a,x}var A={},E={},ve;function He(){if(ve)return E;ve=1,Object.defineProperty(E,"__esModule",{value:!0}),E.Parser=void 0;const a=v(),l=J(),e=G(),n=Te(),r=[92,91,93,63,124],o=r.map(h=>`'${String.fromCodePoint(h)}'`).join(", "),d=-1;let t=class{input="";line=1;column=1;position=0;lastColumn=1;lastWidth=0;parse(i){this.setInput(i);const s=[],c=this.nextNode(),f=c?.kind===e.SyntaxKind.BoundaryAssertion;c&&!f&&s.push(c);let p=!1;for(;!this.done;){const m=this.mark(),g=this.nextNode();if(g.kind!==e.SyntaxKind.BoundaryAssertion){s.push(g);continue}this.done||this.reportError("Boundary assertions are not supported in this position; they are only allowed at the start / end of the pattern.",m),p=!0}return{requireWordBoundaryAtStart:f,requireWordBoundaryAtEnd:p,nodes:s}}setInput(i){return this.input=i,this.line=1,this.column=1,this.position=0,this.lastColumn=1,this.lastWidth=0,this}nextNode(){switch(this.peek()){case d:return;case 91:return this.parseOptional();case 93:this.reportError("Unexpected ']' with no corresponding '['.");case 63:return this.parseWildcard();case 124:return this.parseBoundaryAssertion();default:return this.parseLiteral()}}get done(){return this.position>=this.input.length}parseOptional(){const i=this.mark();this.next();const s=this.mark();this.done&&this.reportError("Unexpected unclosed '['.",i),this.accept("[")&&this.reportError("Unexpected nested optional node.",s);const c=this.nextNode();return c.kind===e.SyntaxKind.BoundaryAssertion&&this.reportError("Boundary assertions are not supported in this position; they are only allowed at the start / end of the pattern.",s),this.accept("]")||this.reportError("Unexpected unclosed '['."),{kind:e.SyntaxKind.Optional,childNode:c}}parseWildcard(){return this.next(),{kind:e.SyntaxKind.Wildcard}}parseBoundaryAssertion(){return this.next(),{kind:e.SyntaxKind.BoundaryAssertion}}parseLiteral(){const i=[];for(;!this.done;){if(this.accept("[]?|")){this.backup();break}const s=this.next();if(s===92){this.done&&(this.backup(),this.reportError("Unexpected trailing backslash."));const c=this.next();if(!r.includes(c)){const f=String.fromCodePoint(c);this.backup(),this.reportError(`Cannot escape character '${f}'; the only characters that can be escaped are the following: ${o}.`)}i.push(c)}else i.push(s)}return{kind:e.SyntaxKind.Literal,chars:i}}reportError(i,{line:s=this.line,column:c=this.column}={}){throw new n.ParserError(i,s,c)}mark(){return{line:this.line,column:this.column}}accept(i){const s=this.next(),c=new l.CharacterIterator(i);for(const f of c)if(f===s)return!0;return this.backup(),!1}peek(){const i=this.next();return this.backup(),i}next(){if(this.done)return d;const i=this.input.charCodeAt(this.position++);if(this.lastWidth=1,i===10)return this.lastColumn=this.column,this.column=1,this.line++,i;if(this.lastColumn=this.column++,!(0,a.isHighSurrogate)(i)||this.done)return i;const s=this.input.charCodeAt(this.position);return(0,a.isLowSurrogate)(s)?(this.position++,this.lastWidth++,(0,a.convertSurrogatePairToCodePoint)(i,s)):i}backup(){this.position-=this.lastWidth,this.column=this.lastColumn,this.lastWidth===1&&this.input.charCodeAt(this.position)===10&&this.line--}};return E.Parser=t,E}var Ce;function We(){if(Ce)return A;Ce=1,Object.defineProperty(A,"__esModule",{value:!0}),A.pattern=e,A.parseRawPattern=n;const a=He(),l=new a.Parser;function e(r,...o){let d=r.raw[0];for(const[t,h]of o.entries())d+=String(h),d+=r.raw[t+1];return l.parse(d)}function n(r){return l.parse(r)}return A}var Q={},$={},R={},Me;function q(){if(Me)return R;Me=1,Object.defineProperty(R,"__esModule",{value:!0}),R.createSimpleTransformer=a,R.createStatefulTransformer=l;function a(e){return{type:0,transform:e}}function l(e){return{type:1,factory:e}}return R}var O={},ye;function ze(){if(ye)return O;ye=1,Object.defineProperty(O,"__esModule",{value:!0}),O.CollapseDuplicatesTransformer=void 0;class a{defaultThreshold;customThresholds;remaining=-1;lastChar=-1;constructor({defaultThreshold:e,customThresholds:n}){this.defaultThreshold=e,this.customThresholds=n}transform(e){if(e===this.lastChar)return this.remaining-- >0?e:void 0;const n=this.customThresholds.get(e)??this.defaultThreshold;return this.remaining=n-1,this.lastChar=e,n>0?e:void 0}reset(){this.remaining=-1,this.lastChar=-1}}return O.CollapseDuplicatesTransformer=a,O}var be;function Se(){if(be)return $;be=1,Object.defineProperty($,"__esModule",{value:!0}),$.collapseDuplicatesTransformer=n;const a=v(),l=q(),e=ze();function n({defaultThreshold:o=1,customThresholds:d=new Map}={}){const t=r(d);return(0,l.createStatefulTransformer)(()=>new e.CollapseDuplicatesTransformer({defaultThreshold:o,customThresholds:t}))}function r(o){const d=new Map;for(const[t,h]of o){if(h<0)throw new RangeError("Expected all thresholds to be non-negative.");const i=(0,a.getAndAssertSingleCodePoint)(t);d.set(i,h)}return d}return $}var U={},K={},_e;function X(){if(_e)return K;_e=1,Object.defineProperty(K,"__esModule",{value:!0}),K.remapCharactersTransformer=n;const a=v(),l=J(),e=q();function n(o){const d=r(o);return(0,e.createSimpleTransformer)(t=>d.get(t)??t)}function r(o){const d=new Map,t=o instanceof Map?o.entries():Object.entries(o);for(const[h,i]of t){const s=(0,a.getAndAssertSingleCodePoint)(h),c=new l.CharacterIterator(i);for(const f of c)d.set(f,s)}return d}return K}var j={},ke;function Ve(){return ke||(ke=1,Object.defineProperty(j,"__esModule",{value:!0}),j.confusables=void 0,j.confusables=new Map([[" "," "],["0","\u24FF"],["1","\u24F5\u278A\u2474\xB9\u{1D7CF}\u{1D7D9}\uFF11\u{1D7F7}\u{1D7E3}\u2488\u{1D7ED}1\u2780\u2081\u2460\u2776\u2960"],["2","\u24F6\u2489\u2475\u278B\u01BB\xB2\u14BF\u{1D7DA}\uFF12\u{1D7EE}\u{1D7E4}\u14BE\u{1D7F8}\u01A7\u{1D7D0}\u2461\u1D24\u2082\u2781\u2777\u161D\u01A8"],["3","\xB3\u2CCC\uA7AB\u{1D7D1}\u2128\u{1D7DB}\u{1D7EF}\u{1D7E5}\uA76A\u278C\u0417\u021C\u24F7\u04E0\u01B7\uFF13\u{1D7F9}\u2476\u248A\u0292\u0293\u01EF\u01EE\u01BA\u{1D574}\u1DBE\u0437\u19A1\u2782\u2462\u2083\u1D9A\u1D23\u1D1F\u2778\u0498\u0499\u04EC\u04E1\u04ED\u04DF\u04DE"],["4","\u278D\u04B6\u13CE\u{1D7DC}\u04B7\u24F8\u04B8\u04B9\u04F4\u04F5\u1DA3\uFF14\u0447\u3129\u2074\u2783\u2084\u2463\u2779\u04CB\u2477\u248B"],["5","\u{1D7F1}\u24F9\u278E\u01BC\u{1D7D3}\u{1D7FB}\u{1D7DD}\u{1D7E7}\uFF15\u2784\u2085\u2464\u2075\u277A\u01BD\u2478\u248C"],["6","\u2CD2\u13EE\u{1D7DE}\u{1D7E8}\u{1D7D4}\u278F\u24FA\u03EC\u03ED\u2076\u0431\uFF16\u19C8\u2465\u2785\u2086\u277B\u2479\u248D"],["7","\u24FB\u{104D2}\u2790\uFF17\u2077\u2466\u2087\u277C\u2786\u247A\u248E"],["8","\u{1031A}\u2791\u24FC\uFF18\u{1D7E0}\u{1D7EA}\u09EA\u2078\u2088\u{1D7F4}\u2787\u2467\u277D\u{1D7FE}\u{1D7D6}\u247B\u248F"],["9","\uA76E\u2CCA\u24FD\u2792\u0A67\u09ED\u0B68\uFF19\u{1D7EB}\u{1D7FF}\u{1D7D7}\u2079\u2089\u0533\u2788\u2468\u277E\u247C\u2490"],["A","\u{1F130}\u13AF\u{102A0}\u{1D56C}\u{1D71C}\u{1D434}\uA4EE\u13AA\u{1D6A8}\uAB7A\u{1D756}\u{1F150}\u212B\u2200\u{1F1E6}\u20B3\u{1F170}\u{1D49C}\u{1D608}\u{1D400}\u{1D538}\u0434\u01FA\u15C5\u24B6\uFF21\u0391\u1F8B\u15E9\u0102\xC3\xC5\u01CD\u0200\u0202\u0100\u023A\u0104\u028C\u039B\u03BB\u019B\u1D00\u1D2C\u0414\u0410\u120D\xC4\u2090\u1571\xAA\u01DE\u04D2\u0386\u1EA0\u1EA2\u1EA6\u1EA8\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1FB8\u1FB9\u1FBA\u1FBB\u1FBC\u1F88\u1F89\u1F8A\u1F8C\u1F8D\u1F8E\u1F8F\u1F08\u1F09\u1F0A\u1F0B\u1F0C\u1F0D\u1F0E\u1F0F\u1E00\u0226\u01E0\u04D0\xC0\xC1\xC2\u1EA4\u1EAA\u{1D6E2}\u{1D4D0}\u{1D670}\u{1D63C}"],["a","\u2202\u237A\u24D0\u0571\u01DF\u1D43\u1D8F\u249C\u0430\u0252\uFF41\u03B1\u0203\u0201\u0E04\u01CE\u10DB\xE4\u0251\u0101\u0250\u0105\u1F84\u1E9A\u1EA1\u1EA3\u01E1\u1EA7\u1EB5\u1E01\u0227\u04D1\u04D3\xE3\xE5\u03AC\u1F70\u1F71\u0103\u1EA9\u1EB1\u1EB3\u1EB7\u1F80\u1F81\u1F82\u1F83\u1F85\u1F86\u1FB0\u1FB1\u1FB2\u1FB3\u1FB4\u1D90\u1FB6\u1FB7\u1F00\u1F01\u1F02\u1F03\u1F04\u1F05\u1F06\u1F07\u1F87\u1EAD\u1EAF\xE0\xE1\xE2\u1EA5\u1EAB\u01FB\u2C65\u{1D41A}\u{1D44E}\u{1D482}\u{1D4B6}\u{1D4EA}\u{1D51E}\u{1D552}\u{1D586}\u{1D5BA}\u{1D5EE}\u{1D622}\u{1D656}\u{1D68A}\u{1D6C2}\u{1D6FC}\u{1D736}\u{1D770}\u{1D7AA}\u2376"],["B","\u{10301}\u{1D469}\u{1D56D}\u{1F131}\u{102A1}\u{1D5A1}\u{1D63D}\uA4D0\u{1D5D5}\u{1D609}\u{1D71D}\u{10282}\u{1D6A9}\u{1D401}\u{1D6E3}\u{1D757}\u{1D435}\u{1D671}\u{1D539}\u13F4\u13FC\u{1D791}\uA7B4\u{1D505}\u{1F151}\u0E3F\u{1D4D1}\u15FF\u15FE\u15FD\u{1F171}\u24B7\uFF22\u0432\u03D0\u15F7\u0181\u4E43\xDF\u10EA\u10E9\u0E56\u03B2\u026E\u0411\u0545\u0E52\u1656\u0299\u1D2E\u1D47\u130C\u1E04\u212C\u0392\u0412\u1E9E\u1E02\u1E06\u0243\u0D26\u15F9\u15F8\u1D5D\u165E\u165F\u165D\u16D2\u1657\u1658\u1D03\u{1F1E7}"],["b","\u13CF\u{1D41B}\u{1D623}\u{1D4B7}\u{1D51F}\u{1D4EB}\u{1D587}\u{1D5BB}\u{1D44F}\u{1D657}\u{1D553}\u{1D483}\u{1D5EF}\u{1D68B}\u266D\u1473\u1488\uFF42\u159A\u1579\u157A\u24D1\u1E03\u1E05\u048D\u044A\u1E07\u0183\u0253\u0185\u15AF\u0184\u042C\u1472\xFE\u0182\u249D\u042A\u1D80\u147F\u1480\u1482\u1481\u147E\u044C\u0180\u048C\u0462\u0463\u150E"],["C","\u13DF\u2CA4\u{1F132}\uA4DA\u{102A2}\u{10302}\u{1F172}\u{10415}\u{1F152}\u263E\u010C\xC7\u24B8\uFF23\u2183\u0187\u1455\u3108\xA2\u096E\u21BB\u0108\u03FE\u0547\u023B\u1645\u1D9C\u249E\u0106\u0480\u010A\xA9\u091F\u0186\u2102\u212D\u03F9\u0421\u531A\u1E08\u04AA\u0297\u1456\u1461\u1462\u1463\u1464\u1465\u216D\u{1D402}\u{1D436}\u{1D46A}\u{1D49E}\u{1D4D2}\u{1D56E}\u{1D5A2}\u{1D5D6}\u{1D60A}\u{1D63E}\u150D"],["c","\u2CA5\u{1043D}\uABAF\u0109\uFF43\u24D2\u0107\u010D\u010B\xE7\u0481\u0188\u1E09\u023C\u2184\u0441\u122D\u1D04\u03F2\u04AB\uA49D\u03C2\u027D\u03DB\u{1D672}\u1466\u19DA\u{1D41C}\u{1D450}\u{1D484}\u{1D4B8}\u{1D4EC}\u{1D520}\u{1D554}\u{1D588}\u{1D5BC}\u{1D5F0}\u{1D624}\u{1D658}\u{1D68C}\u20B5\u{1F1E8}\u1974\u14BC\u217D"],["D","\u13A0\u{1F133}\u{1D521}\u{1D589}\u{1D53B}\u{1D5D7}\u{1D60B}\u{1D673}\u{1D437}\u{1D4D3}\u{1D403}\u{1D46B}\u{1D56F}\u{1D5A3}\u{1D507}\u{1D63F}\uAB70\u2145\u{1D49F}\uA4D3\u{1F173}\u{1F153}\u24B9\uFF24\u0189\u15EA\u018A\xD0\u053A\u1D05\u1D30\u2181\u1E0A\u0110\xDE\u216E\u15DE\u146F\u010E\u1E0C\u1E10\u1E12\u1E0E\u15EB\u15EC\u15DF\u15E0\u1D9B\u1D06\u{1F1E9}"],["d","\u13E7\uA4D2\u{1D4ED}\u1D6D\u20AB\u0503\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\u1D48\u249F\u0501\u217E\u1D81\u0500\u147A\u147B\u147C\u147D\u1484\u1470\u1471\u1D91\u{1D555}\u{1D5BD}\u{1D451}\u{1D625}\u{1D485}\u{1D659}\u{1D41D}\u{1D5F1}\u{1D68D}\u2146\u{1D4B9}\u02A0\u056A"],["E","\uAB7C\u{1F134}\u{1D640}\u{1D53C}\u{10286}\u{1D6AC}\uA4F0\u{1D75A}\u{1D794}\u{1D4D4}\u{1D46C}\u{1D5D8}\u{1F174}\u{1F154}\u24BA\u0388\uFF25\u018E\u1F1D\u156E\u0190\u30E2\u0404\u1D07\u1D31\u1D49\xC9\u4E47\u0401\u0246\uA085\u20AC\xC8\u2130\u0395\u0415\u2D39\u13AC\u0112\u0114\u0116\u0118\u011A\xCA\xCB\u0510\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u1E14\u1E16\u1EBA\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u1E18\u1E1A\u1F18\u1F19\u1F1A\u1F1B\u1F1C\u1FC8\u1FC9\u04D6\u1F72\u1F73\u0400\u03F5\u{1F1EA}"],["e","\u{1D452}\u{1D4EE}\u{1D556}\u{1D58A}\u{1D626}\u{1D5F2}\u{1D68E}\u{1D65A}\u{1D486}\u{1D522}\u{1D5BE}\u{1D41E}\u04BE\u04BF\u24D4\uFF45\u24A0\xE8\u19C9\xE9\u1D92\xEA\u0258\u1F14\u1EC1\u1EBF\u1EC5\u0AEF\u01DD\u0454\u03B5\u0113\u04BD\u025B\u1EC3\u1EBD\u1E15\u1E17\u0115\u0117\xEB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u0247\u2091\u0119\u1E1D\u1E19\u1E1B\u212E\u0435\u0511\u0450\u04D7\u1971\u0451\u1F10\u1F11\u1F12\u1F13\u1F15\u212F"],["F","\u{1F135}\u{10287}\u{1D509}\u{1D60D}\u{102A5}\uA4DD\uA798\u{1F175}\u{1F155}\u{1D4D5}\u24BB\uFF26\u0493\u0492\u15B4\u0191\u0532\u03DD\u127B\u1E1E\u2131\u03DC\u20A3\u{1F1EB}\u2132"],["f","\u{1D41F}\u{1D58B}\u24D5\uFF46\u0192\u1E1F\u0283\u0562\u1DA0\u24A1\u017F\uA2B0\u0284\u2231\u1D82\u{1D627}"],["G","\uA4D6\u13F3\u{1F136}\u13C0\u13FB\u{1D53E}\u{1D4D6}\u{1D46E}\u{1D572}\uAB90\u{1D4A2}\u{1D642}\u{1D5A6}\u{1D676}\u{1D50A}\u{1D43A}\u{1D406}\u{1F176}\u{1F156}\u24BC\uFF27\u0262\u0193\u029B\u0122\u161C\u1D33\u01F4\u0120\u050C\u011C\u1E20\u011E\u01E6\u01E4\u050D\u20B2\u{1F1EC}\u2141"],["g","\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u0581\u0AED\u01E5\u0260\uFEED\uFEEE\u1D4D\u24A2\u210A\u0261\u19C1\u{1D420}\u{1D454}\u{1D488}\u{1D4F0}\u{1D524}\u{1D558}\u{1D58C}\u{1D5C0}\u{1D5F4}\u{1D628}\u{1D65C}\u{1D690}"],["H","\u{1F137}\u{1D722}\uA4E7\u{1D60F}\u{1D43B}\u{1D75C}\u{1D5A7}\u{102CF}\u{1D5DB}\uAB8B\u210D\u13BB\u210C\u2C8E\u{1D46F}\u{1D796}\u{1F177}\u{1F157}\u12DE\u01F6\u050B\u24BD\uFF28\u0124\u16BA\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u04A2\u04A3\u04A4\u1FCA\u1FCB\u1FCC\u1F28\u1F29\u1F2A\u1F2B\u1F2D\u1F2E\u1F2F\u1F98\u1F99\u1F9A\u1F9B\u1F9C\u1F9D\u1F9E\u1F9F\u04C9\u04C8\u04A5\u0389\u043D\u5344\u2653\u{1D4D7}\u210B\u041D\u{1D407}\u{1D643}\u{1D677}\u029C\u{1D6E8}\u0397\u{1D6AE}\u157C\u04C7\u1D34\u1D78\u{1F1ED}"],["h","\u04BA\u24A3\u0452\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u0570\u04BB\u12A8\u12A9\u12AA\u12AB\u0266\u210E\u{1D421}\u{1D489}\u{1D4BD}\u{1D4F1}\u{1D525}\u{1D559}\u{1D58D}\u{1D5C1}\u{1D5F5}\u{1D629}\u{1D65D}\u{1D691}\u056B\u02B0\u144B\u15C1\u0267\u3093\u0265"],["I","\u{1F138}\u0407\uA024\u13C6\u{1F178}\u{1F158}\u0625\uFE87\u0673\u0623\uFE83\u0672\u0675\u24BE\uFF29\u17F8\xCC\xCD\xCE\u0128\u012A\u012C\u0130\xCF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197\u30A7\u30A8\u1FD8\u1FD9\u1FDA\u1FDB\u1F38\u1F39\u1F3A\u1F3B\u1F3C\u1F3D\u1F3E\u2160\u03AA\u038A\u026A\u1DA6\u144A\u1963\u{1D6EA}\u{1D408}\u{1D644}\u{1D678}\u{1D4F5}\u{1D661}\u{1D43C}\u1D35\u{1D6B0}\u{1D470}\u{1F1EE}"],["i","\u24D8\uFF49\xEC\xED\xEE\u0129\u012B\u012D\xEF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u1E2D\u1FD0\u1FD1\u1FD2\u1FD3\u1FD6\u1FD7\u1F30\u1F31\u1F32\u2170\u217C\u2223\u2D4F\uFFE8\u05C0\u0627\u0661\u06F1\u07CA\u16C1\u1F33\u1F34\u1F35\u0268\u0456\u1F76\u1F77\u1D96\u{1D526}\u{1D692}\u{1D778}\u{1D5C2}\u{1D422}\u{1D55A}\u{1D58E}\u{1D5F6}\u{1D62A}\u{1D65E}\u03AF\u2071\u1D62\u{1D4F2}\u24A4"],["J","\u{1F139}\u{1F179}\u{1F159}\u24BF\uFF2A\u0408\u029D\u148D\u05E0\uFF8C\u0134\u0286\u0E27\u0644\u0575\u0296\u1D0A\u1D36\uFEDD\u130B\u0248\u2C7C\u0542\u0E45\u10B1\u012F\u13AB\u0237\u4E3F\u2110\u2111\u1498\u1499\u149A\u149B\u14B4\u14B5\u148E\u148F\u{1F1EF}"],["j","\u24D9\uFF4A\u03F3\u02B2\u24A5\u0249\u0135\u01F0\u0458\u06B6\u1DA8\u{1D4BF}\u{1D62B}\u{1D5F7}\u{1D457}\u{1D65F}\u{1D527}\u{1D48B}\u{1D5C3}\u{1D4F3}\u{1D55B}\u{1D693}\u{1D58F}\u{1D423}"],["K","\u{1D5DE}\u{1F13A}\u{1D725}\u{1D612}\uA4D7\u{1D646}\u{1D542}\u2C94\u{1D50E}\u{1D6EB}\u13E6\u{1D799}\u{1D4A6}\u{1F17A}\u{1F15A}\u20AD\u24C0\uFF2B\u0138\u1E30\u045C\u0198\u043A\u04A0\u03BA\u049B\u049F\u04C4\u029E\u049A\u041A\u04A1\u1D0B\u1D37\u1D4F\u24A6\u16D5\u040C\u1315\u1E32\u039A\u212A\u049C\u049D\u049E\u0136\u1E34\u01E8\u2C69\u03D7\u04C3\u{1F1F0}"],["k","\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\u1D84\u{1D424}\u{1D62C}\u{1D5C4}\u{1D55C}\u{1D705}\u{1D718}\u{1D73F}\u{1D752}\u{1D779}\u{1D78C}\u{1D7B3}\u{1D660}\u{1D694}\u{1D458}\u{1D48C}\u03F0\u{1D6CB}\u{1D6DE}\u{1D7C6}\u{1D5F8}\u{1D4F4}\u{1D4C0}"],["L","\u{1F13B}\u{1041B}\u2CD0\u{1D473}\u{1D67B}\u{10443}\u{1D4DB}\u2CD1\uABAE\u13DE\uA4E1\u{1F17B}\u{1F15B}\uFE88\u2514\u24C1\u0582\uFF2C\u013F\u14AA\u4E5A\u0546\u029F\uA4F6\u03B9\u053C\u1D38\u02E1\u0139\u1228\u1E36\u2097\u0393\u056C\u013B\u1102\u216C\u2112\u2C62\u1967\u1968\u14BB\u14B6\u14B7\u1DAB\uFE8E\u14BA\u14B9\u14B8\u14AB\u23B3\u3125\u0141\u2C60\uFE84\u023D\u{1F1F1}"],["l","\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u04C0\u2113\u1E3D\u1E3B\u0142\uFF9A\u026D\u019A\u026B\u2C61|\u0196\u24A7\u0285\u01C0\u05D5\u05DF\u0399\u0406\uFF5C\u1DA9\u04CF\u{1D4D8}\u{1D540}\u{1D5A8}\u{1D5DC}\u{1D610}\u{1D425}\u{1D459}\u{1D48D}\u{1D4C1}\u{1D529}\u{1D55D}\u{1D591}\u{1D5C5}\u{1D5F9}\u{1D62D}\u{1D695}\u{1D724}\u{1D75E}\u0131\u{1D6A4}\u0269\u1FBE\u{1D6CA}\u{1D704}\u{1D73E}\u{1D7B2}"],["M","\u{1F13C}\u{10311}\u{102B0}\uA4DF\u2C98\u13B7\u{1F17C}\u{1F15C}\u24C2\uFF2D\u043C\u1E42\u0D71\u15F0\u5DDE\u163B\u10DD\u0E53\u264F\u028D\u164F\u1D0D\u1D39\u1D50\u24A8\u1E3E\u041C\u1E40\u0BF1\u216F\u2133\u039C\u03FA\u16D6\u04CD\u04CE\u{1D40C}\u{1D440}\u{1D474}\u{1D4DC}\u{1D510}\u{1D544}\u{1D578}\u{1D5AC}\u{1D5E0}\u{1D614}\u{1D648}\u{1D67C}\u{1D6B3}\u{1D6ED}\u{1D727}\u{1D761}\u{1D79B}\u{1F1F2}"],["m","\u20A5\u1D6F\u{1D592}\u{1D426}\u{1D5C6}\u{1D52A}\u{1D55E}\u{1D4C2}\u24DC\uFF4D\u0D28\u1662\u0D69\u1E3F\u1E41\u217F\u03FB\u1E43\u1320\u0271\u17F3\u1D86\u{1D662}\u{1D4F6}\u{1D696}\u{1D45A}\u{1D5FA}\u19D5\u19D7"],["N","\u{1F13D}\u2115\uA4E0\u{1D6EE}\u{1D762}\u{1D67D}\u{1D6B4}\u{1D475}\u{1D441}\u2C9A\u{1D40D}\u{1D4A9}\u{1D79C}\u{1D5E1}\u{1D615}\u{1D728}\u{1D4DD}\u{1D5AD}\u{1F17D}\u20A6\u{1F15D}\u0419\u040D\u24C3\u048B\u17F7\uFF2E\u1D0E\u0274\u019D\u144E\u51E0\u0438\u0548\u057C\u0418\u05D4\u041B\u03C0\u1D3A\u1DB0\u0143\u5200\u12AD\u1E44\u207F\xD1\u041F\u039D\u1D28\u0578\u03D6\u01F8\u0147\u1E46\u0145\u1E4A\u1E48\u0E17\u014A\u04E2\u04E3\u04E4\u04E5\u045B\u045D\u0439\u1962\u048A\u1D3B\u{1F1F3}"],["n","\u05D7\u{1D48F}\u{1D4F7}\u{1D663}\u{1D45B}\u{1D593}\u{1D52B}\u{1D5C7}\u{1D697}\u{1D5FB}\u1952\u24DD\u03AE\uFF4E\u01F9\u1D12\u0144\xF1\u1F97\u03B7\u1E45\u0148\u1E47\u0272\u0146\u1E4B\u1E49\u0572\u0E96\u054C\u019E\u014B\u24A9\u0E20\u0E01\u0273\u043F\u0149\u043B\u0509\u0220\u1F20\u1F21\u1FC3\u0564\u1F90\u1F91\u1F92\u1F93\u1F94\u1F95\u1F96\u1FC4\u1FC6\u1FC7\u1FC2\u1F22\u1F23\u1F24\u1F25\u1F26\u1F27\u1F74\u1F75\u1260\u1261\u1262\u1263\u1264\u1265\u1266\u0235\u{1D6C8}\u{1D702}\u{1D73C}\u{1D776}\u{1D7B0}\u{1D55F}\u{1D62F}\u{1D427}\u{1D4C3}\u1D87\u1D70\u1965\u2229"],["O","\uA132\u{1F13E}\u{10292}\u{1D7EC}\uA4F3\u2C9E\u{10404}\u{102AB}\u{104C2}\u{1D79E}\u{1F15E}\u2365\u25EF\u2D41\u2296\uFF10\u229D\u{1D764}\u0472\u03F4\u{1D6B6}\u{1D72A}\u047A\u04E6\u04E8\u04EA\u038C\u0298\u{1D40E}\u01D1\xD2\u014E\xD3\xD4\xD5\u020C\u020E\u31FF\u274D\u24C4\uFF2F\u1F4B\u30ED\u2764\u0AE6\u2295\xD8\u0424\u053E\u0398\u01A0\u1D3C\u1D52\u24AA\u0150\xD6\u2092\xA4\u25CA\u03A6\u3007\u039F\u041E\u0555\u0B20\u0D20\u0BE6\u05E1\u1ED2\u1ED0\u1ED6\u1ED4\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u022E\u0230\u022A\u1ECE\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u01FE\u019F\u2D54\u07C0\u17F0\u235C\u2394\u2395\u29B0\u29B1\u29B2\u29B3\u29B4\u29B5\u29B6\u29B7\u29B8\u29B9\u29BA\u29BB\u29BC\u29BD\u29BE\u29BF\u29C0\u29C1\u29C2\u29C3\u1F48\u1F49\u1F4A\u1F4C\u1F4D"],["o","\u{1D698}\u{1D6D0}\u{1D5C8}\u{1D7BC}\u101D\u2C9F\u{1D664}\u1040\u{1042C}\u{1D52C}\u{104EA}\u{1D4F8}\u{1F1F4}\u2364\u25CB\u03D9\u{1F17E}\u{1D4AA}\u{1D5AE}\u{1D7E2}\u{1D7F6}\u{1D67E}\u{1D630}\u{1D5FC}\u{1D560}\u{1D70A}\u{1D428}\u{1D77E}\u{1D7B8}\u1424\u24DE\u0473\u19D0\u1972\xF0\uFF4F\u0C20\u199E\u0553\xF2\u04E9\u04E7\xF3\xBA\u014D\xF4\u01D2\u020F\u014F\u1ED3\u1ED1\u020D\u1ED7\u1ED5\xF5\u03C3\u1E4D\u022D\u1E4F\u1F44\u1E51\u1E53\u022F\u022B\u0E4F\u1D0F\u0151\xF6\u047B\u043E\u12D0\u01ED\u0231\u09E6\u0B66\u0665\u0C66\u0CE6\u0D66\u0E50\u0ED0\u03BF\u0585\u1D11\u0966\u0A66\u1ECF\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\xF8\u01FF\u0275\u056E\u1F40\u1F41\u03CC\u1F78\u1F79\u1F42\u1F43\u1F45"],["P","\u{1F13F}\uA4D1\u{1D6B8}\u{1D67F}\u{1D7A0}\u{1D64B}\uABB2\u2CA2\u{1D4AB}\u{1D766}\u{1D443}\u{1D477}\u{1D5E3}\u{1D40F}\u{10295}\u{1D72C}\u{1D617}\u{1D4DF}\u{1D5AF}\u{1D6F2}\u13E2\u{1F15F}\u048E\u{1F17F}\u24C5\uFF30\u01A4\u146D\u5C38\u1E56\u0580\u03C6\u0584\u1D18\u1D3E\u1D56\u24AB\u1E54\uFF71\u05E7\u0420\u12E8\u1D29\u2C63\u2119\u03A1\u1FEC\u1478\u1476\u1477\u1479\u146C\u146E\u{1F1F5}\u20B1"],["p","\u048F\u2117\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\u1FE5\u03C1\u0440\u01BF\u01F7\u1FE4\u2374\u{1D4F9}\u{1D4C5}\u{1D429}\u{1D45D}\u{1D491}\u{1D52D}\u{1D561}\u{1D595}\u{1D5C9}\u{1D5FD}\u{1D631}\u{1D665}\u{1D699}\u{1D6D2}\u{1D746}\u{1D7BA}\u{1D70C}\u{1D780}"],["Q","\u{1F140}\u{1F180}\u{1F160}\u24C6\uFF31\u211A\u2D55\u051A\u{1D410}\u{1D444}\u{1D478}\u{1D4AC}\u{1D4E0}\u{1D680}\u{1D618}\u{1D64C}\u{1D5B0}\u{1D57C}\u{1D514}\u{1D5E4}\u{1F1F6}"],["q","\u24E0\uFF51\u0563\u24AC\u06F9\u0566\u146B\u024B\u024A\u051B\u{1D5CA}\u{1D45E}\u{1D632}\u{1D562}\u{1D69A}\u{1D492}\u{1D596}\u{1D42A}\u{1D52E}\u{1D4FA}\u{1D666}"],["R","\u211E\u211F\uAB71\u13D2\u{104B4}\uABA2\u13A1\uA4E3\u{1F181}\u{1F161}\u24C7\uFF32\u1D19\u0212\u0280\u1587\u044F\u5C3A\u0154\u042F\u0AB0\u01A6\u1D3F\u12EA\u1E5A\u024C\u0281\u211B\u211C\u211D\u1E58\u0158\u0210\u1E5C\u0156\u1E5E\u2C64\u{1D411}\u{1D445}\u{1D479}\u{1D4E1}\u{1D57D}\u{1D5B1}\u{1D5E5}\u{1D619}\u{1D64D}\u{1D681}\u16B1\u{1F1F7}\u1D1A"],["r","\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u0433\u0550\u027E\u196C\u1E5F\u024D\u02B3\u24AD\u027C\u0453\u1D26\u1D89\u{1D42B}\u{1D45F}\u{1D493}\u{1D4C7}\u{1D4FB}\u{1D52F}\u{1D563}\u{1D597}\u{1D5CB}\u{1D5FF}\u{1D633}\u{1D667}\u1D72\u0491\u1D63"],["S","\u{1F142}\uA1D9\u{1D4E2}\u{1D5E6}\u13DA\u{1D4AE}\u13D5\u{1D682}\u{1D412}\uA4E2\u{1D5B2}\u{1D516}\u{1D64E}\u{10296}\u{1D57E}\u{10420}\u{1D61A}\u{1D54A}\u{1D446}\u{1D47A}\u{1F182}\u{1F162}\u24C8\uFF33\u1E68\u015E\u0586\u054F\u0218\u02E2\u24AE\u0405\u1E60\u0160\u015A\u1E64\u015C\u1E66\u1E62\u0D1F\u1515\u1516\u1522\u1521\u1523\u1524"],["s","\u24E2\uA731\u{10448}\uABAA\uFF53\u015B\u1E65\u015D\u1E61\u0161\u1E67\u0282\u1E63\u1E69\u0455\u015F\u0219\u023F\u1D8A\u0C15\u{1D42C}\u{1D460}\u{1D494}\u{1D4C8}\u{1D4FC}\u{1D530}\u{1D564}\u{1D598}\u{1D5CC}\u{1D600}\u{1D634}\u{1D668}\u{1D69C}\u078E\u{1F1F8}"],["T","\u{1F143}\u{1F183}\u{10315}\u{1D6BB}\u{1D6F5}\u{1D54B}\u{1D57F}\u{1D47B}\u{102B1}\u{10297}\u{1D5B3}\u{1D64F}\u{1F768}\u{1D769}\u{1D7A3}\u{1D683}\u{1D61B}\u{1D447}\uA4D4\u27D9\u{1D413}\u2CA6\u{1D5E7}\u22A4\u{1D517}\u13A2\uAB72\u{1D4AF}\u{1F163}\u23C7\u23C9\u24C9\uFF34\u0442\u04AC\u04AD\u01AC\u30A4\u0166\u0535\u03C4\u1D1B\u1D40\uFF72\u1355\u03EE\u0164\u22A5\u01AE\u03A4\u0422\u4E0B\u1E6A\u1E6C\u021A\u0162\u1E70\u1E6E\u4E05\u4E01\u142A\u{1D6D5}\u{1D70F}\u{1D749}\u{1D783}\u{1D7BD}\u{1D4E3}\u3112\u{1F1F9}\u1325"],["t","\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0236\u0A6E\u0567\u0287\u2020\u0163\u1E71\u1E6F\u01AD\u0167\u1D57\u24AF\u0288\u0565\u01AB\u{1D42D}\u{1D461}\u{1D495}\u{1D4C9}\u{1D4FD}\u{1D531}\u{1D565}\u{1D599}\u{1D5CD}\u{1D601}\u{1D635}\u{1D669}\u{1D69D}\u30CA"],["U","\u{1F144}\uA4F4\u{104CE}\uA4A4\u{1F184}\u{1F164}\u0168\u016C\u016E\u1457\u1458\u01D3\u01D5\u01D7\u01D9\u24CA\uFF35\u0216\u144C\u51F5\u01B1\u0574\u0531\uA4F5\u0426\u016A\u0544\u01B2\u1640\u1D41\u1D58\u24B0\u0170\u0AAA\xDC\u054D\xD9\xDA\xDB\u1E78\u1E7A\u01DB\u1EE6\u0214\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244\u1969\u1467\u222A\u162E\u22C3\u{1D414}\u{1D448}\u{1D47C}\u{1D4B0}\u{1D4E4}\u{1D518}\u{1D54C}\u{1D580}\u{1D5B4}\u{1D5E8}\u{1D61C}\u{1D650}\u{1D684}\u{1F1FA}"],["u","\u1F7A\u1F7B\u24E4\uFF55\xF9\u0169\u016B\u1EEB\u1E77\u1E79\u016D\u01D6\u1EEF\u1959\u01DA\u01DC\u1F57\u03C5\u03B0\u0E19\u057D\u028A\u01D8\u01D4\xFA\u016F\u1D1C\u0171\u0173\u0E22\xFB\u1E7B\u0446\u1201\xFC\u1D7E\u1D64\xB5\u028B\u1EE7\u0215\u0217\u01B0\u1EE9\u1EED\u1EF1\u1EE5\u1E73\u1E75\u0289\u1FE0\u1FE1\u1FE2\u1FE3\u1FE6\u1FE7\u1F50\u1F51\u03CB\u03CD\u1F52\u1F53\u1F54\u1F55\u1F56\u1954\u{1D42E}\u{1D462}\u{1D496}\u{1D4CA}\u{1D4FE}\u{1D532}\u{1D566}\u{1D59A}\u{1D5CE}\u1D99"],["V","\u{1F145}\uA4E6\u{1D47D}\u{1D5B5}\u{1D61D}\u13D9\u{1D685}\u{1D651}\u{1D415}\u{1F185}\u{1F165}\u24CB\uFF36\u142F\u0474\u1D5B\u24B1\u06F7\u1E7E\u2174\u2164\u1E7C\u0667\u2D38\u0476\u143A\u143B\u{1F1FB}\u{1D4E5}"],["v","\u1200\u24E5\uFF56\u{1D710}\u{1D74A}\u1E7D\u1E7F\u0C6E\u0E07\u0475\u05E2\u1D20\u03BD\u05D8\u1D65\u0477\u17F4\u1601\u{1D66B}\u{1D69F}\u{1D6CE}\u{1D708}\u{1D742}\u{1D77C}\u{1D7B6}\u{1D637}\u{1D603}\u{1D4FF}"],["W","\u{1F146}\u13D4\u13B3\u{1D47E}\uA4EA\u{1D4B2}\u{1D61E}\u{1F186}\u24CC\u{1F166}\uFF57\uFF37\u1E82\u1FA7\u15EF\u1955\u5C71\u0460\u0E9F\u0C1A\u0561\u0429\u0428\u03CE\u0449\u0E2C\u0448\u164E\u1D42\u02B7\u24B2\u0E1D\u1220\u1E84\u051C\u1E80\u0174\u1E86\u1E88\u0D27\u163A\u047F\u1661\u019C\u20A9\u{1F1FC}"],["w","\u1E81\uAB83\u1E83\u24E6\u2375\u0175\u1E87\u1E85\u1E98\u1E89\u2C73\u1F7C\u1F60\u1F61\u1F62\u1F63\u03C9\u1F64\u1F65\u1F66\u1F67\u1FF2\u1FF3\u1FF4\u1FF6\u1FF7\u2C72\u0461\u051D\u1D21\u1F7D\u1FA0\u1FA1\u1FA2\u1FA3\u1FA4\u1FA5\u1FA6\u026F\u{1D755}\u{1D7C9}\u{1D78F}"],["X","\u{1F7A8}\u{1F7A9}\u{1F7AA}\u{1F147}\u{1F7AB}\u{1F7AC}\u{10317}\u2CAC\uA4EB\u{1D583}\u{1D7A6}\u{1D61F}\u{10290}\u{1D6BE}\u{1D76C}\u{1D732}\uA7B3\u{10322}\u{1D5B7}\u{1D44B}\u{1D54F}\u{1D51B}\u{102B4}\u{1D5EB}\u{1F187}\u{1F167}\u274C\u24CD\u{1D4E7}\uFF38\u1E8A\u166D\u03C7\u3128\u{1D4B3}\u04FE\u10EF\u04FC\u04B3\u0416\u03A7\u04B2\u1D61\u02E3\u24B3\u05D0\u1238\u1E8C\uA2BC\u2169\u0425\u2573\u166E\u1541\u157D\u2179\u16B7\u2D5D\u{1D653}\u{1D687}\u4E42\u{1D417}\u{1F1FD}"],["x","\u24E7\uFF58\u0445\u1E8B\xD7\u2093\u292B\u292C\u2A2F\u1E8D\u1D8D\u{1D66D}\u04FD\u{1D639}\u{1D431}\u{1D6A1}\u2A30\uFF92\u{1D501}"],["Y","\u2CA8\u{1D688}\u{1D44C}\u{1D5EC}\u{1D418}\uA4EC\u{1D480}\u{1D730}\u{102B2}\u{1F188}\u{1F168}\u24CE\uFF39\u1F5B\u01B3\u311A\u028F\u2144\u03D4\uFFE5\xA5\u054E\u03D3\u03B3\u05E5\u04F2\u0427\u040E\u1203\u0178\u024E\u03E4\u03A5\u03D2\u04AE\u1EF2\xDD\u0176\u1EF8\u0232\u1E8E\u1EF6\u1EF4\u1FE8\u1FE9\u1FEA\u1FEB\u1F59\u1F5D\u1F5F\u03AB\u038E\u04EE\u04F0\u04B0\u04B1\u{1D550}\u{1F1FE}"],["y","\u{1F148}\u13BD\u13A9\u24E8\uFF59\u1EF3\xFD\u0177\u1EF9\u0233\u1E8F\xFF\u1EF7\u0443\u10E7\u1E99\u1EF5\u01B4\u024F\u1D5E\u0263\u02B8\u1D8C\u04AF\u24B4\u04F3\u04F1\u04EF\u045E\u0423\u028E"],["Z","\u{1F149}\uA4DC\u{1D5ED}\u{1D419}\u2621\u13C3\u{1D621}\u{1F189}\u{1F169}\u24CF\uFF3A\u1E94\u01B5\u4E59\u1E90\u0224\u1DBB\u24B5\u0179\u2124\u0396\u017B\u017D\u1E92\u2C6B\u{1F1FF}"],["z","\uAB93\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u1D22\u130A\u0290\u2C6C\u1D8E\u0291\u1646"]])),j}var Ie;function we(){if(Ie)return U;Ie=1,Object.defineProperty(U,"__esModule",{value:!0}),U.resolveConfusablesTransformer=e;const a=X(),l=Ve();function e(){return(0,a.remapCharactersTransformer)(l.confusables)}return U}var F={},L={},xe;function Ye(){return xe||(xe=1,Object.defineProperty(L,"__esModule",{value:!0}),L.dictionary=void 0,L.dictionary=new Map([["a","@4"],["c","("],["e","3"],["g","6"],["i","1|!"],["l","/"],["o","0"],["s","$5"],["t","7"],["z","2"]])),L}var Ae;function Ee(){if(Ae)return F;Ae=1,Object.defineProperty(F,"__esModule",{value:!0}),F.resolveLeetSpeakTransformer=e;const a=X(),l=Ye();function e(){return(0,a.remapCharactersTransformer)(l.dictionary)}return F}var H={},Re;function qe(){if(Re)return H;Re=1,Object.defineProperty(H,"__esModule",{value:!0}),H.toAsciiLowerCaseTransformer=e;const a=v(),l=q();function e(){return(0,l.createSimpleTransformer)(n=>(0,a.isUpperCase)(n)?(0,a.invertCaseOfAlphabeticChar)(n):n)}return H}var Oe;function Ge(){return Oe||(Oe=1,(function(a){Object.defineProperty(a,"__esModule",{value:!0}),a.englishDataset=a.englishRecommendedTransformers=a.englishRecommendedWhitelistMatcherTransformers=a.englishRecommendedBlacklistMatcherTransformers=void 0;const l=le(),e=We(),n=Se(),r=we(),o=Ee(),d=qe();a.englishRecommendedBlacklistMatcherTransformers=[(0,r.resolveConfusablesTransformer)(),(0,o.resolveLeetSpeakTransformer)(),(0,d.toAsciiLowerCaseTransformer)(),(0,n.collapseDuplicatesTransformer)({defaultThreshold:1,customThresholds:new Map([["b",2],["e",2],["o",2],["l",2],["s",2],["g",2]])})],a.englishRecommendedWhitelistMatcherTransformers=[(0,d.toAsciiLowerCaseTransformer)(),(0,n.collapseDuplicatesTransformer)({defaultThreshold:Number.POSITIVE_INFINITY,customThresholds:new Map([[" ",1]])})],a.englishRecommendedTransformers={blacklistMatcherTransformers:a.englishRecommendedBlacklistMatcherTransformers,whitelistMatcherTransformers:a.englishRecommendedWhitelistMatcherTransformers},a.englishDataset=new l.DataSet().addPhrase(t=>t.setMetadata({originalWord:"abo"}).addPattern((0,e.pattern)`|ab[b]o[s]|`)).addPhrase(t=>t.setMetadata({originalWord:"abeed"}).addPattern((0,e.pattern)`ab[b]eed`)).addPhrase(t=>t.setMetadata({originalWord:"africoon"}).addPattern((0,e.pattern)`africoon`)).addPhrase(t=>t.setMetadata({originalWord:"anal"}).addPattern((0,e.pattern)`|anal`).addWhitelistedTerm("analabos").addWhitelistedTerm("analagous").addWhitelistedTerm("analav").addWhitelistedTerm("analy").addWhitelistedTerm("analog").addWhitelistedTerm("an al").addPattern((0,e.pattern)`danal`).addPattern((0,e.pattern)`eanal`).addPattern((0,e.pattern)`fanal`).addWhitelistedTerm("fan al").addPattern((0,e.pattern)`ganal`).addWhitelistedTerm("gan al").addPattern((0,e.pattern)`ianal`).addWhitelistedTerm("ian al").addPattern((0,e.pattern)`janal`).addWhitelistedTerm("trojan al").addPattern((0,e.pattern)`kanal`).addPattern((0,e.pattern)`lanal`).addWhitelistedTerm("lan al").addPattern((0,e.pattern)`lanal`).addWhitelistedTerm("lan al").addPattern((0,e.pattern)`oanal|`).addPattern((0,e.pattern)`panal`).addWhitelistedTerm("pan al").addPattern((0,e.pattern)`qanal`).addPattern((0,e.pattern)`ranal`).addPattern((0,e.pattern)`sanal`).addPattern((0,e.pattern)`tanal`).addWhitelistedTerm("tan al").addPattern((0,e.pattern)`uanal`).addWhitelistedTerm("uan al").addPattern((0,e.pattern)`vanal`).addWhitelistedTerm("van al").addPattern((0,e.pattern)`wanal`).addPattern((0,e.pattern)`xanal`).addWhitelistedTerm("texan al").addPattern((0,e.pattern)`yanal`).addPattern((0,e.pattern)`zanal`)).addPhrase(t=>t.setMetadata({originalWord:"anus"}).addPattern((0,e.pattern)`anus`).addWhitelistedTerm("an us").addWhitelistedTerm("tetanus").addWhitelistedTerm("uranus").addWhitelistedTerm("janus").addWhitelistedTerm("manus")).addPhrase(t=>t.setMetadata({originalWord:"arabush"}).addPattern((0,e.pattern)`arab[b]ush`)).addPhrase(t=>t.setMetadata({originalWord:"arse"}).addPattern((0,e.pattern)`|ars[s]e`).addWhitelistedTerm("arsen")).addPhrase(t=>t.setMetadata({originalWord:"ass"}).addPattern((0,e.pattern)`|ass`).addWhitelistedTerm("45s").addWhitelistedTerm("assa").addWhitelistedTerm("assem").addWhitelistedTerm("assen").addWhitelistedTerm("asser").addWhitelistedTerm("assess").addWhitelistedTerm("asset").addWhitelistedTerm("assev").addWhitelistedTerm("assi").addWhitelistedTerm("assoc").addWhitelistedTerm("assoi").addWhitelistedTerm("assu")).addPhrase(t=>t.setMetadata({originalWord:"bastard"}).addPattern((0,e.pattern)`bas[s]tard`)).addPhrase(t=>t.setMetadata({originalWord:"bestiality"}).addPattern((0,e.pattern)`be[e][a]s[s]tial`)).addPhrase(t=>t.setMetadata({originalWord:"bitch"}).addPattern((0,e.pattern)`bitch`).addPattern((0,e.pattern)`bich|`)).addPhrase(t=>t.setMetadata({originalWord:"blowjob"}).addPattern((0,e.pattern)`b[b]l[l][o]wj[o]b`)).addPhrase(t=>t.setMetadata({originalWord:"bollocks"}).addPattern((0,e.pattern)`bol[l]ock`)).addPhrase(t=>t.setMetadata({originalWord:"boob"}).addPattern((0,e.pattern)`boob`)).addPhrase(t=>t.setMetadata({originalWord:"boonga"}).addPattern((0,e.pattern)`boonga`).addWhitelistedTerm("baboon ga")).addPhrase(t=>t.setMetadata({originalWord:"buttplug"}).addPattern((0,e.pattern)`buttplug`)).addPhrase(t=>t.setMetadata({originalWord:"chingchong"}).addPattern((0,e.pattern)`chingchong`)).addPhrase(t=>t.setMetadata({originalWord:"chink"}).addPattern((0,e.pattern)`chink`).addWhitelistedTerm("chin k")).addPhrase(t=>t.setMetadata({originalWord:"cock"}).addPattern((0,e.pattern)`|cock|`).addPattern((0,e.pattern)`|cocks`).addPattern((0,e.pattern)`|cockp`).addPattern((0,e.pattern)`|cocke[e]|`).addWhitelistedTerm("cockney")).addPhrase(t=>t.setMetadata({originalWord:"cuck"}).addPattern((0,e.pattern)`cuck`).addWhitelistedTerm("cuckoo")).addPhrase(t=>t.setMetadata({originalWord:"cum"}).addPattern((0,e.pattern)`|cum`).addWhitelistedTerm("cumu").addWhitelistedTerm("cumb")).addPhrase(t=>t.setMetadata({originalWord:"cunt"}).addPattern((0,e.pattern)`|cunt`).addPattern((0,e.pattern)`cunt|`)).addPhrase(t=>t.setMetadata({originalWord:"deepthroat"}).addPattern((0,e.pattern)`deepthro[o]at`).addPattern((0,e.pattern)`deepthro[o]t`)).addPhrase(t=>t.setMetadata({originalWord:"dick"}).addPattern((0,e.pattern)`|dck|`).addPattern((0,e.pattern)`dick`).addWhitelistedTerm("benedick").addWhitelistedTerm("dickens").addWhitelistedTerm("dickety").addWhitelistedTerm("dickory")).addPhrase(t=>t.setMetadata({originalWord:"dildo"}).addPattern((0,e.pattern)`dildo`)).addPhrase(t=>t.setMetadata({originalWord:"doggystyle"}).addPattern((0,e.pattern)`d[o]g[g]ys[s]t[y]l[l]`)).addPhrase(t=>t.setMetadata({originalWord:"double penetration"}).addPattern((0,e.pattern)`double penetra`)).addPhrase(t=>t.setMetadata({originalWord:"dyke"}).addPattern((0,e.pattern)`dyke`).addWhitelistedTerm("van dyke")).addPhrase(t=>t.setMetadata({originalWord:"ejaculate"}).addPattern((0,e.pattern)`e[e]jacul`).addPattern((0,e.pattern)`e[e]jakul`).addPattern((0,e.pattern)`e[e]acul[l]ate`)).addPhrase(t=>t.setMetadata({originalWord:"fag"}).addPattern((0,e.pattern)`|fag`).addPattern((0,e.pattern)`fggot`)).addPhrase(t=>t.setMetadata({originalWord:"felch"}).addPattern((0,e.pattern)`fe[e]l[l]ch`)).addPhrase(t=>t.setMetadata({originalWord:"fellatio"}).addPattern((0,e.pattern)`f[e][e]llat`)).addPhrase(t=>t.setMetadata({originalWord:"finger bang"}).addPattern((0,e.pattern)`fingerbang`)).addPhrase(t=>t.setMetadata({originalWord:"fisting"}).addPattern((0,e.pattern)`fistin`)).addPhrase(t=>t.setMetadata({originalWord:"fuck"}).addPattern((0,e.pattern)`f[?]ck`).addPattern((0,e.pattern)`|fk`).addPattern((0,e.pattern)`|fu|`).addPattern((0,e.pattern)`|fuk`).addWhitelistedTerm("fick").addWhitelistedTerm("kung-fu").addWhitelistedTerm("kung fu")).addPhrase(t=>t.setMetadata({originalWord:"gangbang"}).addPattern((0,e.pattern)`g[?]ngbang`)).addPhrase(t=>t.setMetadata({originalWord:"handjob"}).addPattern((0,e.pattern)`h[?]ndjob`)).addPhrase(t=>t.setMetadata({originalWord:"hentai"}).addPattern((0,e.pattern)`h[e][e]ntai`)).addPhrase(t=>t.setMetadata({originalWord:"hooker"}).addPattern((0,e.pattern)`hooker`)).addPhrase(t=>t.setMetadata({originalWord:"incest"}).addPattern((0,e.pattern)`incest`)).addPhrase(t=>t.setMetadata({originalWord:"jerk off"}).addPattern((0,e.pattern)`jerkoff`)).addPhrase(t=>t.setMetadata({originalWord:"jizz"}).addPattern((0,e.pattern)`jizz`)).addPhrase(t=>t.setMetadata({originalWord:"kike"}).addPattern((0,e.pattern)`kike`)).addPhrase(t=>t.setMetadata({originalWord:"lubejob"}).addPattern((0,e.pattern)`lubejob`)).addPhrase(t=>t.setMetadata({originalWord:"masturbate"}).addPattern((0,e.pattern)`m[?]sturbate`).addPattern((0,e.pattern)`masterbate`)).addPhrase(t=>t.setMetadata({originalWord:"negro"}).addPattern((0,e.pattern)`negro`).addWhitelistedTerm("montenegro").addWhitelistedTerm("negron").addWhitelistedTerm("stoneground").addWhitelistedTerm("winegrow")).addPhrase(t=>t.setMetadata({originalWord:"nigger"}).addPattern((0,e.pattern)`n[i]gger`).addPattern((0,e.pattern)`n[i]gga`).addPattern((0,e.pattern)`|nig|`).addPattern((0,e.pattern)`|nigs|`).addWhitelistedTerm("snigger")).addPhrase(t=>t.setMetadata({originalWord:"orgasm"}).addPattern((0,e.pattern)`[or]gasm`).addWhitelistedTerm("gasma")).addPhrase(t=>t.setMetadata({originalWord:"orgy"}).addPattern((0,e.pattern)`orgy`).addPattern((0,e.pattern)`orgies`).addWhitelistedTerm("porgy")).addPhrase(t=>t.setMetadata({originalWord:"penis"}).addPattern((0,e.pattern)`pe[e]nis`).addPattern((0,e.pattern)`|pnis`).addWhitelistedTerm("pen is")).addPhrase(t=>t.setMetadata({originalWord:"piss"}).addPattern((0,e.pattern)`|piss`)).addPhrase(t=>t.setMetadata({originalWord:"porn"}).addPattern((0,e.pattern)`|prn|`).addPattern((0,e.pattern)`porn`).addWhitelistedTerm("p orna")).addPhrase(t=>t.setMetadata({originalWord:"prick"}).addPattern((0,e.pattern)`|prick[s]|`)).addPhrase(t=>t.setMetadata({originalWord:"pussy"}).addPattern((0,e.pattern)`p[u]ssy`)).addPhrase(t=>t.setMetadata({originalWord:"rape"}).addPattern((0,e.pattern)`|rape`).addPattern((0,e.pattern)`|rapis[s]t`).addWhitelistedTerm("rapper")).addPhrase(t=>t.setMetadata({originalWord:"retard"}).addPattern((0,e.pattern)`retard`)).addPhrase(t=>t.setMetadata({originalWord:"scat"}).addPattern((0,e.pattern)`|s[s]cat|`)).addPhrase(t=>t.setMetadata({originalWord:"semen"}).addPattern((0,e.pattern)`|s[s]e[e]me[e]n`)).addPhrase(t=>t.setMetadata({originalWord:"sex"}).addPattern((0,e.pattern)`|s[s]e[e]x|`).addPattern((0,e.pattern)`|s[s]e[e]xy|`)).addPhrase(t=>t.setMetadata({originalWord:"shit"}).addPattern((0,e.pattern)`|shit`).addPattern((0,e.pattern)`shit|`).addWhitelistedTerm("s hit").addWhitelistedTerm("sh it").addWhitelistedTerm("shi t").addWhitelistedTerm("shitake")).addPhrase(t=>t.setMetadata({originalWord:"slut"}).addPattern((0,e.pattern)`s[s]lut`)).addPhrase(t=>t.setMetadata({originalWord:"spastic"}).addPattern((0,e.pattern)`|spastic`)).addPhrase(t=>t.setMetadata({originalWord:"tit"}).addPattern((0,e.pattern)`|tit|`).addPattern((0,e.pattern)`|tits|`).addPattern((0,e.pattern)`|titt`).addPattern((0,e.pattern)`|tiddies`).addPattern((0,e.pattern)`|tities`)).addPhrase(t=>t.setMetadata({originalWord:"tranny"}).addPattern((0,e.pattern)`|trany`)).addPhrase(t=>t.setMetadata({originalWord:"turd"}).addPattern((0,e.pattern)`|turd`).addWhitelistedTerm("turducken")).addPhrase(t=>t.setMetadata({originalWord:"twat"}).addPattern((0,e.pattern)`|twat`).addWhitelistedTerm("twattle")).addPhrase(t=>t.setMetadata({originalWord:"vagina"}).addPattern((0,e.pattern)`vagina`).addPattern((0,e.pattern)`|v[?]gina`)).addPhrase(t=>t.setMetadata({originalWord:"wank"}).addPattern((0,e.pattern)`|wank`)).addPhrase(t=>t.setMetadata({originalWord:"whore"}).addPattern((0,e.pattern)`|wh[o]re|`).addPattern((0,e.pattern)`|who[o]res[s]|`).addWhitelistedTerm("who're"))})(Q)),Q}var z={},je;function Je(){if(je)return z;je=1,Object.defineProperty(z,"__esModule",{value:!0}),z.skipNonAlphabeticTransformer=e;const a=v(),l=q();function e(){return(0,l.createSimpleTransformer)(n=>(0,a.isAlphabetic)(n)?n:void 0)}return z}var Le;function Qe(){return Le||(Le=1,(function(a){var l=C&&C.__createBinding||(Object.create?(function(n,r,o,d){d===void 0&&(d=o);var t=Object.getOwnPropertyDescriptor(r,o);(!t||("get"in t?!r.__esModule:t.writable||t.configurable))&&(t={enumerable:!0,get:function(){return r[o]}}),Object.defineProperty(n,d,t)}):(function(n,r,o,d){d===void 0&&(d=o),n[d]=r[o]})),e=C&&C.__exportStar||function(n,r){for(var o in n)o!=="default"&&!Object.prototype.hasOwnProperty.call(r,o)&&l(r,n,o)};Object.defineProperty(a,"__esModule",{value:!0}),e(re(),a),e(Be(),a),e(le(),a),e(oe(),a),e(Ne(),a),e(V(),a),e(Fe(),a),e(G(),a),e(Te(),a),e(We(),a),e(Ge(),a),e(Se(),a),e(X(),a),e(we(),a),e(Ee(),a),e(Je(),a),e(q(),a),e(qe(),a)})(C)),C}var u=Qe();const Xe=u.DataSet,Ze=u.ParserError,et=u.PhraseBuilder,tt=u.RegExpMatcher,rt=u.SyntaxKind,at=u.TextCensor,nt=u.assignIncrementingIds,st=u.asteriskCensorStrategy,it=u.collapseDuplicatesTransformer,ot=u.compareMatchByPositionAndId,dt=u.createSimpleTransformer,lt=u.createStatefulTransformer,ht=u.englishDataset,ct=u.englishRecommendedBlacklistMatcherTransformers,ut=u.englishRecommendedTransformers,ft=u.englishRecommendedWhitelistMatcherTransformers,pt=u.fixedCharCensorStrategy,mt=u.fixedPhraseCensorStrategy,gt=u.grawlixCensorStrategy,Pt=u.keepEndCensorStrategy,Tt=u.keepStartCensorStrategy,vt=u.parseRawPattern,Ct=u.pattern,Wt=u.randomCharFromSetCensorStrategy,Mt=u.remapCharactersTransformer,yt=u.resolveConfusablesTransformer,bt=u.resolveLeetSpeakTransformer,St=u.skipNonAlphabeticTransformer,_t=u.toAsciiLowerCaseTransformer;export{Xe as DataSet,Ze as ParserError,et as PhraseBuilder,tt as RegExpMatcher,rt as SyntaxKind,at as TextCensor,nt as assignIncrementingIds,st as asteriskCensorStrategy,it as collapseDuplicatesTransformer,ot as compareMatchByPositionAndId,dt as createSimpleTransformer,lt as createStatefulTransformer,u as default,ht as englishDataset,ct as englishRecommendedBlacklistMatcherTransformers,ut as englishRecommendedTransformers,ft as englishRecommendedWhitelistMatcherTransformers,pt as fixedCharCensorStrategy,mt as fixedPhraseCensorStrategy,gt as grawlixCensorStrategy,Pt as keepEndCensorStrategy,Tt as keepStartCensorStrategy,vt as parseRawPattern,Ct as pattern,Wt as randomCharFromSetCensorStrategy,Mt as remapCharactersTransformer,yt as resolveConfusablesTransformer,bt as resolveLeetSpeakTransformer,St as skipNonAlphabeticTransformer,_t as toAsciiLowerCaseTransformer};
|
| 10 |
+
//# sourceMappingURL=/sm/b7af592ac93a00f32c7ead0f5ef304fa1fc6ab77d340e750e7f59feb8bbe9488.map
|