Download index.html from CVNSS/Teencode: direct link, hf CLI and curl.
- Browser
- Download file 31.3 kB
-
https://huggingface.co/spaces/CVNSS/Teencode/resolve/main/index.html
- Command line
-
hf download hf://spaces/CVNSS/Teencode/index.html
-
curl -L -o index.html https://huggingface.co/spaces/CVNSS/Teencode/resolve/main/index.html
31.3 kB
| <html lang="vi"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width,initial-scale=1" /> | |
| <title>Teencode Tool – Faithful Clone (Offline • 2-way)</title> | |
| <meta name="description" content="Công cụ giúp bạn tạo Teencode tự động tới 12 loại khác nhau. Chạy offline. Có chuyển đổi 2 chiều." /> | |
| <style> | |
| :root{ | |
| --bg:#f3f4f6; --card:#fff; --text:#111827; --muted:#6b7280; | |
| --line:#e5e7eb; --primary:#2563eb; --primary2:#1d4ed8; | |
| --warn:#b45309; --ok:#16a34a; --shadow:0 10px 30px rgba(0,0,0,.08); | |
| --radius:14px; | |
| --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono","Courier New", monospace; | |
| --sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji"; | |
| } | |
| *{box-sizing:border-box} | |
| body{margin:0; font-family:var(--sans); background:var(--bg); color:var(--text);} | |
| .wrap{max-width:1200px; margin:0 auto; padding:22px;} | |
| .top{ | |
| display:flex; justify-content:space-between; gap:16px; align-items:flex-start; margin-bottom:14px; | |
| } | |
| .title h1{margin:0; font-size:22px; line-height:1.25} | |
| .title p{margin:8px 0 0; color:var(--muted); line-height:1.5} | |
| .badge{ | |
| font-size:12px; padding:6px 10px; border-radius:999px; | |
| border:1px solid var(--line); background:#fff; color:var(--muted); | |
| height:fit-content; | |
| } | |
| .grid{display:grid; grid-template-columns:1.15fr .85fr; gap:14px;} | |
| @media (max-width: 980px){ .grid{grid-template-columns:1fr;} } | |
| .card{ | |
| background:var(--card); | |
| border:1px solid var(--line); | |
| border-radius:var(--radius); | |
| box-shadow:var(--shadow); | |
| } | |
| .panel{padding:16px;} | |
| label.small{display:block; font-size:13px; color:var(--muted); margin-bottom:8px;} | |
| textarea{ | |
| width:100%; min-height:140px; resize:vertical; | |
| padding:12px; border-radius:12px; border:1px solid var(--line); | |
| outline:none; font-size:15px; line-height:1.45; background:#fff; | |
| } | |
| textarea:focus{border-color:#93c5fd; box-shadow:0 0 0 4px rgba(59,130,246,.15)} | |
| .row{display:flex; gap:10px; align-items:center; justify-content:space-between; flex-wrap:wrap; margin-top:10px;} | |
| .btn{ | |
| border:0; cursor:pointer; padding:10px 14px; border-radius:12px; | |
| font-weight:700; font-size:14px; | |
| display:inline-flex; align-items:center; gap:8px; | |
| transition: transform .05s ease, background .15s ease, opacity .15s ease; | |
| } | |
| .btn:active{transform: translateY(1px)} | |
| .btn.primary{background:var(--primary); color:#fff;} | |
| .btn.primary:hover{background:var(--primary2)} | |
| .btn.ghost{background:#f9fafb; border:1px solid var(--line); color:#111827;} | |
| .btn.ghost:hover{background:#f3f4f6} | |
| .btn.warn{background:#fff7ed; border:1px solid #fed7aa; color:var(--warn);} | |
| .btn.warn:hover{background:#ffedd5} | |
| .btn.small{padding:8px 10px; font-size:13px; border-radius:10px;} | |
| .hint{color:var(--muted); font-size:13px;} | |
| .modes h2{margin:0 0 10px; font-size:14px;} | |
| .mode-list{display:grid; gap:8px; max-height:540px; overflow:auto; padding-right:4px;} | |
| .mode{ | |
| display:flex; gap:10px; align-items:flex-start; | |
| border:1px solid var(--line); border-radius:12px; | |
| padding:10px; background:#fff; | |
| } | |
| .mode:hover{border-color:#cbd5e1} | |
| .mode input{margin-top:3px} | |
| .mode .ex{font-family:var(--mono); font-size:13px;} | |
| .mode .tag{font-size:12px; color:var(--muted); margin-top:2px;} | |
| .toolbar{ | |
| display:flex; gap:8px; align-items:center; flex-wrap:wrap; | |
| padding:10px 12px; border:1px solid var(--line); border-radius:12px; | |
| background:#fff; margin-bottom:10px; | |
| } | |
| .seg{ | |
| display:flex; border:1px solid var(--line); border-radius:12px; overflow:hidden; | |
| background:#fff; | |
| } | |
| .seg button{ | |
| padding:8px 10px; border:0; cursor:pointer; background:#fff; color:#111827; | |
| font-weight:700; font-size:13px; | |
| } | |
| .seg button.active{background:#111827; color:#fff;} | |
| .kv{ | |
| display:flex; gap:8px; align-items:center; flex-wrap:wrap; | |
| font-size:12px; color:var(--muted); | |
| } | |
| .kv strong{color:#111827} | |
| .toast{ | |
| position:fixed; right:16px; bottom:16px; | |
| background:#0b1220; color:#fff; padding:10px 12px; | |
| border-radius:12px; box-shadow:0 16px 50px rgba(0,0,0,.25); | |
| opacity:0; transform: translateY(8px); pointer-events:none; | |
| transition:opacity .18s ease, transform .18s ease; | |
| font-size:13px; | |
| } | |
| .toast.show{opacity:1; transform: translateY(0)} | |
| .note{ | |
| margin-top:10px; padding:10px 12px; border-radius:12px; | |
| border:1px dashed var(--line); background:#fff; color:var(--muted); | |
| font-size:12px; line-height:1.5; | |
| } | |
| .spin{ | |
| width:14px; height:14px; border-radius:999px; | |
| border:2px solid rgba(255,255,255,.35); | |
| border-top-color:#fff; | |
| animation:spin .7s linear infinite; | |
| } | |
| @keyframes spin{to{transform:rotate(360deg)}} | |
| .loading{ | |
| display:none; align-items:center; gap:8px; | |
| padding:8px 10px; border-radius:12px; | |
| background:var(--primary); color:#fff; font-weight:800; | |
| font-size:12px; | |
| } | |
| .loading.show{display:inline-flex} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="wrap"> | |
| <div class="top"> | |
| <div class="title"> | |
| <h1>Công cụ giúp bạn tạo Teencode tự động online</h1> | |
| <p> | |
| Công cụ giúp bạn tạo Teencode tự động tới <b>12 loại</b> khác nhau...<br/> | |
| Hướng dẫn tạo: Bạn nhập nội dung cần viết vào ô bên dưới, rồi chọn kiểu chữ, xem qua nội dung được tạo, | |
| bấm nút <b>Copy All</b> để sao chép nội dung và dán vào nơi bạn cần. | |
| </p> | |
| </div> | |
| <div class="badge">Offline • Faithful Clone • 2-way</div> | |
| </div> | |
| <div class="grid"> | |
| <!-- LEFT: Input/Output --> | |
| <div class="card panel"> | |
| <div class="toolbar"> | |
| <div class="seg" aria-label="direction"> | |
| <button id="dirForward" type="button" class="active">Normal → Teen</button> | |
| <button id="dirReverse" type="button">Teen → Normal</button> | |
| </div> | |
| <button id="btnSwap" class="btn ghost small" type="button" title="Đổi chỗ Input/Output">⇄ Swap</button> | |
| <div class="loading" id="loadingBox"> | |
| <span class="spin"></span> Converting... | |
| </div> | |
| <div class="kv" style="margin-left:auto"> | |
| <span>Mode: <strong id="modeBadge">1</strong></span> | |
| <span>•</span> | |
| <span>Auto-update: <strong>ON</strong></span> | |
| </div> | |
| </div> | |
| <label class="small" for="inputText">Nhập chữ vào đây nhé</label> | |
| <textarea id="inputText" placeholder="Nhập chữ vào đây nhé"></textarea> | |
| <div class="row"> | |
| <div class="hint">Chọn 1 kiểu teen code bên phải → kết quả cập nhật ngay.</div> | |
| <div style="display:flex; gap:8px; flex-wrap:wrap;"> | |
| <button id="btnClear" class="btn ghost" type="button">Xoá</button> | |
| <button id="btnCopy" class="btn primary" type="button">Copy All</button> | |
| </div> | |
| </div> | |
| <hr style="border:0;border-top:1px solid var(--line); margin:14px 0;"> | |
| <label class="small" for="outputText">Kết quả</label> | |
| <textarea id="outputText" readonly placeholder="Kết quả sẽ hiện ở đây..."></textarea> | |
| <div class="note"> | |
| <b>Ghi chú 2 chiều:</b> Một số mode (#1/#2/#3/#4/#6/#7/#8/#12) <i>mất thông tin</i> (mã hoá/biến đổi), | |
| nên <b>Teen → Normal</b> chỉ khôi phục <b>xấp xỉ</b> (best-effort) — đúng bản chất “teencode”. | |
| </div> | |
| </div> | |
| <!-- RIGHT: Modes --> | |
| <div class="card panel modes"> | |
| <h2>Chọn kiểu chữ teen code</h2> | |
| <div class="mode-list" id="modeList"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="toast" id="toast">Đã copy!</div> | |
| <script> | |
| /************************************************************ | |
| * Faithful Clone Core (bám sát logic gốc teencode1.js) | |
| * + Offline UI + 2-way convert (reverse = best-effort) | |
| ************************************************************/ | |
| // ===== helpers UI ===== | |
| const $ = (sel) => document.querySelector(sel); | |
| const inputText = $("#inputText"); | |
| const outputText = $("#outputText"); | |
| const modeList = $("#modeList"); | |
| const modeBadge = $("#modeBadge"); | |
| const toast = $("#toast"); | |
| const btnCopy = $("#btnCopy"); | |
| const btnClear = $("#btnClear"); | |
| const btnSwap = $("#btnSwap"); | |
| const loadingBox = $("#loadingBox"); | |
| const dirForward = $("#dirForward"); | |
| const dirReverse = $("#dirReverse"); | |
| let currentMode = 1; | |
| let direction = "forward"; // forward | reverse | |
| function showToast(msg){ | |
| toast.textContent = msg; | |
| toast.classList.add("show"); | |
| setTimeout(()=>toast.classList.remove("show"), 900); | |
| } | |
| function showLoading(ms=350){ | |
| loadingBox.classList.add("show"); | |
| setTimeout(()=>loadingBox.classList.remove("show"), ms); | |
| } | |
| // ===== faithful-ish normalization (replaces toNormalText in a robust UTF-8 way) ===== | |
| function toNormalText(text){ | |
| // faithful intent: remove Vietnamese diacritics + đ/Đ | |
| return String(text) | |
| .normalize("NFD") | |
| .replace(/[\u0300-\u036f]/g, "") | |
| .replace(/đ/g,"d") | |
| .replace(/Đ/g,"D"); | |
| } | |
| // ======================= | |
| // FORWARD: 12 modes (match teencode1.js behavior) | |
| // ======================= | |
| function toTeenCode1(text){ | |
| text = toNormalText(text); | |
| text = text.toLowerCase(); | |
| const textArr = text.split(' '); // faithful: split by single space | |
| for (let i in textArr){ | |
| let word = textArr[i]; | |
| if(word.indexOf('y')>=0){ | |
| if(word.indexOf('ay')>=0){ | |
| // keep | |
| }else if(word.indexOf('uy')>=0){ | |
| word = word.replace('uy','y'); | |
| }else{ | |
| word = word.replace('y','i'); | |
| } | |
| } | |
| word = word.replace(/ch$/g,"x"); | |
| word = word.replace(/^ph/g,"f"); | |
| word = word.replace(/^kh/g,"k"); | |
| word = word.replace(/^gi/g,"j"); | |
| word = word.replace(/^gh/g,"g"); | |
| word = word.replace(/^ngh/g,"ng"); | |
| word = word.replace(/^qu/g,"q"); | |
| word = word.replace(/ng$/g,"g"); | |
| word = word.replace(/nh$/g,"h"); | |
| word = word.replace('i','j'); // only first 'i' | |
| textArr[i] = word; | |
| } | |
| return textArr.join(' '); | |
| } | |
| function toTeenCode2(text){ | |
| text = toNormalText(text); | |
| text = text.toUpperCase(); | |
| const textArr = text.split(' '); | |
| for (let i in textArr){ | |
| let word = textArr[i]; | |
| word = word.replace(/A/g,"4"); | |
| word = word.replace(/B/g,"13"); | |
| word = word.replace(/C/g,"6"); | |
| word = word.replace(/D/g,"10"); | |
| word = word.replace(/E/g,"3"); | |
| word = word.replace(/G/g,"9"); | |
| word = word.replace(/H/g,"76"); | |
| word = word.replace(/I/g,"1"); | |
| word = word.replace(/K/g,"14"); | |
| word = word.replace(/L/g,"2"); | |
| word = word.replace(/M/g,"111"); | |
| word = word.replace(/N/g,"11"); | |
| word = word.replace(/O/g,"0"); | |
| word = word.replace(/P/g,"19"); | |
| word = word.replace(/Q/g,"62"); | |
| word = word.replace(/R/g,"12"); | |
| word = word.replace(/S/g,"5"); | |
| word = word.replace(/T/g,"7"); | |
| word = word.replace(/U/g,"21"); | |
| word = word.replace(/Ư/g,"22"); // faithful intent of /Ư/ | |
| word = word.replace(/V/g,"17"); | |
| word = word.replace(/X/g,"96"); | |
| word = word.replace(/Y/g,"29"); | |
| word = word.replace(/X/g,"96"); | |
| textArr[i] = word; | |
| } | |
| return textArr.join(' '); | |
| } | |
| function toTeenCode3(text){ | |
| text = toNormalText(text); | |
| text = text.toLowerCase(); | |
| const flipTable = { | |
| a:'\u0250', b:'q', c:'\u0254', d:'p', e:'\u01DD', f:'\u025F', g:'\u0183', | |
| h:'\u0265', i:'\u0131', j:'\u027E', k:'\u029E', l:'\u0283', m:'\u026F', | |
| n:'u', r:'\u0279', t:'\u0287', v:'\u028C', w:'\u028D', y:'\u028E', | |
| '.':'\u02D9','[':']','(':')','{':'}','?':'\u00BF','!':'\u00A1', | |
| "'":',','<':'>','_':'\u203E',';':'\u061B','\u203F':'\u2040', | |
| '\u2045':'\u2046','\u2234':'\u2235','\r':'\n' | |
| }; | |
| const last = text.length - 1; | |
| const result = new Array(text.length); | |
| for (let i = last; i >= 0; --i){ | |
| const c = text.charAt(i); | |
| const r = flipTable[c]; | |
| result[last - i] = (r !== undefined) ? r : c; | |
| } | |
| return result.join(''); | |
| } | |
| function toTeenCode4(text){ | |
| text = toNormalText(text); | |
| text = text.toUpperCase(); | |
| const textArr = text.split(' '); | |
| for (let i in textArr){ | |
| let word = textArr[i]; | |
| word = word.replace(/A/g,"4"); | |
| word = word.replace(/E/g,"3"); | |
| word = word.replace(/G/g,"q"); | |
| // faithful: if(word[0]=="H"){word[0]='h';} else {...} | |
| // JS strings are immutable, but intention: keep initial H untouched. | |
| if(word[0] === "H"){ | |
| // do nothing (faithful effect) | |
| }else{ | |
| word = word.replace(/KH/g,"kh"); | |
| word = word.replace(/H/g,"k"); | |
| } | |
| word = word.replace(/I/g,"j"); | |
| word = word.replace(/O/g,"0"); | |
| textArr[i] = word; | |
| } | |
| return textArr.join(' ').toLowerCase(); | |
| } | |
| function toTeenCode5(text){ | |
| text = text.replace(/l/g,'n'); | |
| text = text.replace(/L/g,'N'); | |
| return text; | |
| } | |
| function toTeenCode6(text){ | |
| text = text.toLowerCase(); | |
| const textArr = text.split(' '); | |
| // faithful intent: if starts with vowel => add "ngh" | |
| const vowelStart = /^(a|ă|â|e|ê|i|o|ô|ơ|u|ư|y|á|à|ả|ã|ạ|ắ|ằ|ẳ|ẵ|ặ|ấ|ầ|ẩ|ẫ|ậ|é|è|ẻ|ẽ|ẹ|ế|ề|ể|ễ|ệ|í|ì|ỉ|ĩ|ị|ó|ò|ỏ|õ|ọ|ố|ồ|ổ|ỗ|ộ|ớ|ờ|ở|ỡ|ợ|ú|ù|ủ|ũ|ụ|ứ|ừ|ử|ữ|ự|ý|ỳ|ỷ|ỹ|ỵ)/i; | |
| for (let i in textArr){ | |
| let word = textArr[i]; | |
| if(vowelStart.test(word)){ | |
| word = "ngh" + word; | |
| } | |
| // faithful spirit: replace initial cluster with "nh" | |
| word = word.replace(/^(ngh|ch|gh|gi|kh|ng|nh|ph|qu|th|tr|[bcdđghklmnpqrstvx])/i, "nh"); | |
| textArr[i] = word; | |
| } | |
| return textArr.join(' ').toLowerCase(); | |
| } | |
| function toTeenCode7(text){ | |
| text = text.toLowerCase(); | |
| const textArr = text.split(' '); | |
| for (let i in textArr){ | |
| let word = textArr[i]; | |
| word = word.replace(/b/g,'|3'); | |
| word = word.replace(/đ/g,'+)'); | |
| word = word.replace(/d/g,'])'); | |
| word = word.replace(/l/g,'|_'); | |
| // a-family | |
| word = word.replace(/[áắấ]/g,"4'"); | |
| word = word.replace(/[àằầ]/g,"4`"); | |
| word = word.replace(/[aăâ]/g,"4"); | |
| word = word.replace(/[ảẳẩ]/g,"4"); | |
| word = word.replace(/[ạặậ]/g,"4"); | |
| word = word.replace(/[ãẵẫ]/g,"4"); | |
| // e-family | |
| word = word.replace(/[éế]/g,"3'"); | |
| word = word.replace(/[ẻể]/g,"3?"); | |
| word = word.replace(/[èề]/g,"3`"); | |
| word = word.replace(/[eê]/g,"3"); | |
| word = word.replace(/[ẽễ]/g,"3"); | |
| word = word.replace(/[ẹệ]/g,"3"); | |
| // i-family | |
| word = word.replace(/i/g,"j"); | |
| word = word.replace(/ì/g,"j`"); | |
| word = word.replace(/í/g,"j'"); | |
| word = word.replace(/[ỉĩị]/g,"j"); | |
| // o-family | |
| word = word.replace(/[ỏổở]/g,"0"); | |
| word = word.replace(/[oôơ]/g,"0"); | |
| word = word.replace(/[òồờ]/g,"0`"); | |
| word = word.replace(/[óốớ]/g,"0'"); | |
| word = word.replace(/[õỗỡ]/g,"0"); | |
| word = word.replace(/[ọộợ]/g,"0."); | |
| // u-family | |
| word = word.replace(/[úứ]/g,"u'"); | |
| word = word.replace(/[ùừ]/g,"u`"); | |
| word = word.replace(/[ủử]/g,"u"); | |
| word = word.replace(/[ũữ]/g,"u"); | |
| word = word.replace(/[ụự]/g,"u."); | |
| word = word.replace(/[uư]/g,"u"); | |
| // y | |
| word = word.replace(/y/g,"ij"); | |
| textArr[i] = word; | |
| } | |
| return textArr.join(' ').toLowerCase(); | |
| } | |
| function toTeenCode8(text){ | |
| text = text.toLowerCase(); | |
| const textArr = text.split(' '); | |
| for (let i in textArr){ | |
| let word = textArr[i]; | |
| // vowels first | |
| word = word.replace(/[áắấ]/g,"Cl'"); | |
| word = word.replace(/[àằầ]/g,"Cl`"); | |
| word = word.replace(/[aăâ]/g,"Cl"); | |
| word = word.replace(/[ảẳẩ]/g,"Cl"); | |
| word = word.replace(/[ạặậ]/g,"Cl"); | |
| word = word.replace(/[ãẵẫ]/g,"Cl"); | |
| word = word.replace(/[éế]/g,"F_'"); | |
| word = word.replace(/[ẻể]/g,"F_"); | |
| word = word.replace(/[èề]/g,"F_`"); | |
| word = word.replace(/[eê]/g,"F_"); | |
| word = word.replace(/[ẽễ]/g,"F_"); | |
| word = word.replace(/[ẹệ]/g,"F_"); | |
| word = word.replace(/[ỏổở]/g,"º"); | |
| word = word.replace(/[oôơ]/g,"º"); | |
| word = word.replace(/[òồờ]/g,"º`"); | |
| word = word.replace(/[óốớ]/g,"º'"); | |
| word = word.replace(/[õỗỡ]/g,"º"); | |
| word = word.replace(/[ọộợ]/g,"º"); | |
| word = word.replace(/i/g,"]"); | |
| word = word.replace(/ì/g,"]`"); | |
| word = word.replace(/í/g,"]'"); | |
| word = word.replace(/[ỉĩị]/g,"]"); | |
| word = word.replace(/[úứ]/g,"µ'"); | |
| word = word.replace(/[ùừ]/g,"µ`"); | |
| word = word.replace(/[ủử]/g,"µ"); | |
| word = word.replace(/[ũữ]/g,"µ"); | |
| word = word.replace(/[ụự]/g,"µ"); | |
| word = word.replace(/[uư]/g,"µ"); | |
| // consonants | |
| word = word.replace(/d/g, "])"); | |
| word = word.replace(/đ/g, "+)"); | |
| word = word.replace(/n/g, "]\\["); | |
| word = word.replace(/h/g, "¨|"); | |
| word = word.replace(/g/g, "(¬"); | |
| word = word.replace(/k/g, "]<"); | |
| word = word.replace(/t/g, "¨ "); | |
| word = word.replace(/v/g, "\\/"); | |
| word = word.replace(/m/g, "/v\\"); | |
| word = word.replace(/^qu/g,"v\\/"); | |
| word = word.replace(/r/g, "Pv"); | |
| word = word.replace(/w/g, "v\\/"); | |
| word = word.replace(/y/g, "¥"); | |
| word = word.replace(/s/g, "§"); | |
| word = word.replace(/x/g, "><"); | |
| word = word.replace(/p/g, "]º"); | |
| word = word.replace(/b/g, "]3"); | |
| word = word.replace(/c/g, "("); | |
| textArr[i] = word; | |
| } | |
| return textArr.join(' '); | |
| } | |
| function toTeenCode9(text, s){ | |
| if(s === undefined) s = "'s"; | |
| const textArr = text.split(' '); | |
| for (let i in textArr){ | |
| let word = textArr[i]; | |
| const symbol = "~`!@#$%^&*()_-+={}[]:>;'.,</?*-+"; | |
| if(word.match(/[^A-Za-z0-9]/g) == null){ | |
| word = word + s; | |
| }else{ | |
| const spArr = word.match(/[^A-Za-z0-9]/g); | |
| let pos = word.length; | |
| for (let x in spArr){ | |
| if(symbol.indexOf(spArr[x]) !== -1){ | |
| pos = word.indexOf(spArr[x]); | |
| break; | |
| } | |
| } | |
| word = [word.slice(0,pos), s, word.slice(pos)].join(''); | |
| } | |
| textArr[i] = word; | |
| } | |
| return textArr.join(' '); | |
| } | |
| function toTeenCode10(text){ return toTeenCode9(text,"'ss"); } | |
| function toTeenCode11(text){ return toTeenCode9(text,"'sss"); } | |
| function toTeenCode12(text){ | |
| text = text.toLowerCase(); | |
| const textArr = text.split(' '); | |
| for (let i in textArr){ | |
| let word = textArr[i].split(''); | |
| const rand = Math.floor((Math.random()*10)+1); | |
| let j = (rand<=5) ? 0 : 1; | |
| for(; j<word.length; j+=2){ | |
| word[j] = String(word[j]).toUpperCase(); | |
| } | |
| textArr[i] = word.join(''); | |
| } | |
| return textArr.join(' '); | |
| } | |
| // ======================= | |
| // REVERSE: best-effort decode | |
| // ======================= | |
| function decodeMode1(text){ | |
| // best-effort: restore some patterns (lossy) | |
| let t = String(text).toLowerCase(); | |
| const arr = t.split(' '); | |
| for (let i in arr){ | |
| let w = arr[i]; | |
| // endings | |
| w = w.replace(/x$/g, "ch"); | |
| w = w.replace(/g$/g, "ng"); // might be original g | |
| w = w.replace(/h$/g, "nh"); // might be original h | |
| // starts | |
| w = w.replace(/^f/g, "ph"); | |
| w = w.replace(/^q/g, "qu"); | |
| w = w.replace(/^j/g, "gi"); | |
| w = w.replace(/^k/g, "kh"); // might be original k | |
| // restore j -> i elsewhere | |
| w = w.replace(/j/g, "i"); | |
| arr[i] = w; | |
| } | |
| return arr.join(' '); | |
| } | |
| function decodeMode2(text){ | |
| // reverse mapping: greedy longest match | |
| const codeToChar = { | |
| "111":"M","96":"X","76":"H","62":"Q","29":"Y","22":"Ư","21":"U", | |
| "19":"P","17":"V","14":"K","13":"B","12":"R","11":"N","10":"D", | |
| "9":"G","7":"T","6":"C","5":"S","4":"A","3":"E","2":"L","1":"I","0":"O" | |
| }; | |
| const codes = Object.keys(codeToChar).sort((a,b)=>b.length-a.length); | |
| const words = String(text).trim().split(/\s+/); | |
| const out = words.map(w=>{ | |
| let s = w; | |
| let res = ""; | |
| while(s.length){ | |
| let matched = false; | |
| for(const c of codes){ | |
| if(s.startsWith(c)){ | |
| res += codeToChar[c]; | |
| s = s.slice(c.length); | |
| matched = true; | |
| break; | |
| } | |
| } | |
| if(!matched){ | |
| // keep unknown char and move on | |
| res += s[0]; | |
| s = s.slice(1); | |
| } | |
| } | |
| return res.toLowerCase(); | |
| }); | |
| return out.join(" "); | |
| } | |
| function decodeMode3(text){ | |
| // build inverse flip table, then reverse string and map back | |
| const flipTable = { | |
| a:'\u0250', b:'q', c:'\u0254', d:'p', e:'\u01DD', f:'\u025F', g:'\u0183', | |
| h:'\u0265', i:'\u0131', j:'\u027E', k:'\u029E', l:'\u0283', m:'\u026F', | |
| n:'u', r:'\u0279', t:'\u0287', v:'\u028C', w:'\u028D', y:'\u028E', | |
| '.':'\u02D9','[':']','(':')','{':'}','?':'\u00BF','!':'\u00A1', | |
| "'":',','<':'>','_':'\u203E',';':'\u061B','\u203F':'\u2040', | |
| '\u2045':'\u2046','\u2234':'\u2235','\r':'\n' | |
| }; | |
| const inv = {}; | |
| for(const k in flipTable){ | |
| inv[flipTable[k]] = k; | |
| } | |
| const s = String(text); | |
| let out = ""; | |
| for(let i=s.length-1; i>=0; i--){ | |
| const ch = s[i]; | |
| out += (inv[ch] !== undefined) ? inv[ch] : ch; | |
| } | |
| return out; | |
| } | |
| function decodeMode4(text){ | |
| // best-effort: reverse leet symbols | |
| let t = toNormalText(String(text)).toLowerCase(); | |
| t = t.replace(/4/g,"a").replace(/3/g,"e").replace(/0/g,"o").replace(/q/g,"g").replace(/j/g,"i"); | |
| // H/K ambiguity can't be restored | |
| return t; | |
| } | |
| function decodeMode5(text){ | |
| // lossy reverse (n -> l) | |
| return String(text).replace(/n/g,"l").replace(/N/g,"L"); | |
| } | |
| function decodeMode6(text){ | |
| // cannot restore original clusters reliably | |
| return String(text); | |
| } | |
| function decodeWithTokens(text, tokenMap){ | |
| // tokenMap: array [token, char], ordered longest-first | |
| const s = String(text); | |
| let i = 0; | |
| let out = ""; | |
| while(i < s.length){ | |
| let matched = false; | |
| for(const [tok, val] of tokenMap){ | |
| if(s.startsWith(tok, i)){ | |
| out += val; | |
| i += tok.length; | |
| matched = true; | |
| break; | |
| } | |
| } | |
| if(!matched){ | |
| out += s[i]; | |
| i++; | |
| } | |
| } | |
| return out; | |
| } | |
| function decodeMode7(text){ | |
| // IMPORTANT: longest-first | |
| const tokens = [ | |
| ["0.", "o"], ["0`","o"], ["0'","o"], | |
| ["u.","u"], ["u`","u"], ["u'","u"], | |
| ["j`","i"], ["j'","i"], ["ij","y"], | |
| ["|_","l"], ["|3","b"], ["+)", "đ"], ["])", "d"], ["])","d"], // safety | |
| ["4`","a"], ["4'","a"], ["4","a"], | |
| ["3`","e"], ["3'","e"], ["3?","e"], ["3","e"], | |
| ["0","o"], ["u","u"], ["j","i"] | |
| ]; | |
| // unify "])" exact | |
| const cleaned = String(text).replace(/\]\)/g, "])"); | |
| return decodeWithTokens(cleaned, tokens).toLowerCase(); | |
| } | |
| function decodeMode8(text){ | |
| const tokens = [ | |
| ["/v\\","m"], | |
| ["v\\/","qu"], // qu + w ambiguity | |
| ["\\/", "v"], | |
| ["Pv","r"], | |
| ["]\\[","n"], | |
| ["(¬","g"], | |
| ["]<","k"], | |
| ["]º","p"], | |
| ["]3","b"], | |
| ["><","x"], | |
| ["+)", "đ"], | |
| ["])", "d"], | |
| ["¨|","h"], | |
| ["¨ ","t"], | |
| ["§","s"], | |
| ["¥","y"], | |
| ["µ`","u"], ["µ'","u"], ["µ","u"], | |
| ["º`","o"], ["º'","o"], ["º","o"], | |
| ["F_`","e"], ["F_'","e"], ["F_","e"], | |
| ["Cl`","a"], ["Cl'","a"], ["Cl","a"], | |
| ["]`","i"], ["]'","i"], ["]","i"], | |
| ["(","c"] | |
| ]; | |
| return decodeWithTokens(String(text), tokens).toLowerCase(); | |
| } | |
| function decodeMode9(text){ | |
| return String(text).replace(/'s\b/g,"").replace(/'s(?=[^A-Za-z0-9]|$)/g,""); | |
| } | |
| function decodeMode10(text){ | |
| return String(text).replace(/'ss\b/g,"").replace(/'ss(?=[^A-Za-z0-9]|$)/g,""); | |
| } | |
| function decodeMode11(text){ | |
| return String(text).replace(/'sss\b/g,"").replace(/'sss(?=[^A-Za-z0-9]|$)/g,""); | |
| } | |
| function decodeMode12(text){ | |
| return String(text).toLowerCase(); | |
| } | |
| // ===== apply ===== | |
| function applyForward(mode, text){ | |
| switch(mode){ | |
| case 1: return toTeenCode1(text); | |
| case 2: return toTeenCode2(text); | |
| case 3: return toTeenCode3(text); | |
| case 4: return toTeenCode4(text); | |
| case 5: return toTeenCode5(text); | |
| case 6: return toTeenCode6(text); | |
| case 7: return toTeenCode7(text); | |
| case 8: return toTeenCode8(text); | |
| case 9: return toTeenCode9(text); | |
| case 10: return toTeenCode10(text); | |
| case 11: return toTeenCode11(text); | |
| case 12: return toTeenCode12(text); | |
| default: return text; | |
| } | |
| } | |
| function applyReverse(mode, text){ | |
| switch(mode){ | |
| case 1: return decodeMode1(text); | |
| case 2: return decodeMode2(text); | |
| case 3: return decodeMode3(text); | |
| case 4: return decodeMode4(text); | |
| case 5: return decodeMode5(text); | |
| case 6: return decodeMode6(text); | |
| case 7: return decodeMode7(text); | |
| case 8: return decodeMode8(text); | |
| case 9: return decodeMode9(text); | |
| case 10: return decodeMode10(text); | |
| case 11: return decodeMode11(text); | |
| case 12: return decodeMode12(text); | |
| default: return text; | |
| } | |
| } | |
| function convertNow(){ | |
| showLoading(450); | |
| modeBadge.textContent = String(currentMode); | |
| const origin = inputText.value ?? ""; | |
| let out = ""; | |
| if(direction === "forward"){ | |
| out = applyForward(currentMode, origin); | |
| }else{ | |
| out = applyReverse(currentMode, origin); | |
| } | |
| outputText.value = out; | |
| } | |
| // ===== UI modes list (faithful labels) ===== | |
| const MODE_META = [ | |
| {id:1, ex:"day la vj du", tag:"Chuẩn hoá + teen hoá (bỏ dấu, i→j...)"}, | |
| {id:2, ex:"10429 24 171 1021", tag:"Mã hoá số (mapping chữ cái)"}, | |
| {id:3, ex:"up ıʌ ɐʃ ʎɐp", tag:"Lật chữ (upside-down)"}, | |
| {id:4, ex:"d4y l4 vj du", tag:"Leet cơ bản (a→4, e→3, o→0...)"}, | |
| {id:5, ex:"Đây nà ví dụ", tag:"Cute-speak (l→n)"}, | |
| {id:6, ex:"nhây nhà nhí nhụ", tag:"Nh-speak"}, | |
| {id:7, ex:"+)4ij |_4` vj' ])u.", tag:"ASCII/leet nâng cao (kiểu 1)"}, | |
| {id:8, ex:"+)Cl¥ lCl` \\/]' ])µ", tag:"ASCII/leet nâng cao (kiểu 2)"}, | |
| {id:9, ex:"Đây's là's ví's dụ's", tag:"Thêm hậu tố 's"}, | |
| {id:10, ex:"Đây'ss là'ss ví'ss dụ'ss", tag:"Thêm hậu tố 'ss"}, | |
| {id:11, ex:"Đây'sss là'sss ví'sss dụ'sss", tag:"Thêm hậu tố 'sss"}, | |
| {id:12, ex:"ĐâY Là Ví Dụ", tag:"Xen kẽ hoa/thường (ngẫu nhiên)"}, | |
| ]; | |
| function esc(s){ | |
| return String(s).replace(/[&<>"']/g, ch => ( | |
| { "&":"&","<":"<",">":">",'"':""","'":"'" }[ch] | |
| )); | |
| } | |
| function renderModes(){ | |
| modeList.innerHTML = MODE_META.map(m => ` | |
| <div class="mode" data-mode="${m.id}"> | |
| <input type="checkbox" id="m-${m.id}" ${m.id===currentMode ? "checked":""} /> | |
| <label for="m-${m.id}"> | |
| <div class="ex">${esc(m.ex)}</div> | |
| <div class="tag">${esc(m.tag)}</div> | |
| </label> | |
| </div> | |
| `).join(""); | |
| // faithful behavior: only one checkbox checked | |
| modeList.querySelectorAll(".mode").forEach(box=>{ | |
| box.addEventListener("click", (e)=>{ | |
| const id = Number(box.getAttribute("data-mode")); | |
| currentMode = id; | |
| // uncheck all | |
| modeList.querySelectorAll("input[type='checkbox']").forEach(cb => cb.checked = false); | |
| // check current | |
| const mine = box.querySelector("input[type='checkbox']"); | |
| mine.checked = true; | |
| convertNow(); | |
| }); | |
| }); | |
| } | |
| // ===== direction toggle ===== | |
| function setDirection(dir){ | |
| direction = dir; | |
| if(dir === "forward"){ | |
| dirForward.classList.add("active"); | |
| dirReverse.classList.remove("active"); | |
| }else{ | |
| dirReverse.classList.add("active"); | |
| dirForward.classList.remove("active"); | |
| } | |
| convertNow(); | |
| } | |
| dirForward.addEventListener("click", ()=>setDirection("forward")); | |
| dirReverse.addEventListener("click", ()=>setDirection("reverse")); | |
| // ===== events ===== | |
| inputText.addEventListener("input", convertNow); | |
| btnClear.addEventListener("click", ()=>{ | |
| inputText.value = ""; | |
| outputText.value = ""; | |
| inputText.focus(); | |
| showToast("Đã xoá"); | |
| }); | |
| btnSwap.addEventListener("click", ()=>{ | |
| const a = inputText.value; | |
| const b = outputText.value; | |
| inputText.value = b; | |
| outputText.value = a; | |
| showToast("Đã swap"); | |
| convertNow(); | |
| }); | |
| btnCopy.addEventListener("click", async ()=>{ | |
| const text = outputText.value || ""; | |
| if(!text.trim()){ showToast("Chưa có nội dung để copy"); return; } | |
| try{ | |
| await navigator.clipboard.writeText(text); | |
| showToast("Đã copy!"); | |
| }catch(_){ | |
| // fallback | |
| outputText.focus(); | |
| outputText.select(); | |
| try{ | |
| const ok = document.execCommand("copy"); | |
| showToast(ok ? "Đã copy!" : "Copy bị chặn"); | |
| }catch(e){ | |
| showToast("Copy bị chặn"); | |
| } | |
| outputText.setSelectionRange(0,0); | |
| } | |
| }); | |
| // ===== init ===== | |
| renderModes(); | |
| convertNow(); | |
| </script> | |
| </body> | |
| </html> | |