|
|
<!doctype html> |
|
|
<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"> |
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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"; |
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
function toNormalText(text){ |
|
|
|
|
|
return String(text) |
|
|
.normalize("NFD") |
|
|
.replace(/[\u0300-\u036f]/g, "") |
|
|
.replace(/đ/g,"d") |
|
|
.replace(/Đ/g,"D"); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function toTeenCode1(text){ |
|
|
text = toNormalText(text); |
|
|
text = text.toLowerCase(); |
|
|
const textArr = text.split(' '); |
|
|
for (let i in textArr){ |
|
|
let word = textArr[i]; |
|
|
|
|
|
if(word.indexOf('y')>=0){ |
|
|
if(word.indexOf('ay')>=0){ |
|
|
|
|
|
}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'); |
|
|
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"); |
|
|
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"); |
|
|
|
|
|
|
|
|
|
|
|
if(word[0] === "H"){ |
|
|
|
|
|
}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(' '); |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
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,'|_'); |
|
|
|
|
|
|
|
|
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"); |
|
|
|
|
|
|
|
|
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"); |
|
|
|
|
|
|
|
|
word = word.replace(/i/g,"j"); |
|
|
word = word.replace(/ì/g,"j`"); |
|
|
word = word.replace(/í/g,"j'"); |
|
|
word = word.replace(/[ỉĩị]/g,"j"); |
|
|
|
|
|
|
|
|
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."); |
|
|
|
|
|
|
|
|
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"); |
|
|
|
|
|
|
|
|
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]; |
|
|
|
|
|
|
|
|
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,"µ"); |
|
|
|
|
|
|
|
|
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(' '); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function decodeMode1(text){ |
|
|
|
|
|
let t = String(text).toLowerCase(); |
|
|
const arr = t.split(' '); |
|
|
for (let i in arr){ |
|
|
let w = arr[i]; |
|
|
|
|
|
|
|
|
w = w.replace(/x$/g, "ch"); |
|
|
w = w.replace(/g$/g, "ng"); |
|
|
w = w.replace(/h$/g, "nh"); |
|
|
|
|
|
|
|
|
w = w.replace(/^f/g, "ph"); |
|
|
w = w.replace(/^q/g, "qu"); |
|
|
w = w.replace(/^j/g, "gi"); |
|
|
w = w.replace(/^k/g, "kh"); |
|
|
|
|
|
w = w.replace(/j/g, "i"); |
|
|
|
|
|
arr[i] = w; |
|
|
} |
|
|
return arr.join(' '); |
|
|
} |
|
|
|
|
|
function decodeMode2(text){ |
|
|
|
|
|
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){ |
|
|
|
|
|
res += s[0]; |
|
|
s = s.slice(1); |
|
|
} |
|
|
} |
|
|
return res.toLowerCase(); |
|
|
}); |
|
|
return out.join(" "); |
|
|
} |
|
|
|
|
|
function decodeMode3(text){ |
|
|
|
|
|
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){ |
|
|
|
|
|
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"); |
|
|
|
|
|
return t; |
|
|
} |
|
|
|
|
|
function decodeMode5(text){ |
|
|
|
|
|
return String(text).replace(/n/g,"l").replace(/N/g,"L"); |
|
|
} |
|
|
|
|
|
function decodeMode6(text){ |
|
|
|
|
|
return String(text); |
|
|
} |
|
|
|
|
|
function decodeWithTokens(text, tokenMap){ |
|
|
|
|
|
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){ |
|
|
|
|
|
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"], |
|
|
["4`","a"], ["4'","a"], ["4","a"], |
|
|
["3`","e"], ["3'","e"], ["3?","e"], ["3","e"], |
|
|
["0","o"], ["u","u"], ["j","i"] |
|
|
]; |
|
|
|
|
|
const cleaned = String(text).replace(/\]\)/g, "])"); |
|
|
return decodeWithTokens(cleaned, tokens).toLowerCase(); |
|
|
} |
|
|
|
|
|
function decodeMode8(text){ |
|
|
const tokens = [ |
|
|
["/v\\","m"], |
|
|
["v\\/","qu"], |
|
|
["\\/", "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(); |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
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(""); |
|
|
|
|
|
|
|
|
modeList.querySelectorAll(".mode").forEach(box=>{ |
|
|
box.addEventListener("click", (e)=>{ |
|
|
const id = Number(box.getAttribute("data-mode")); |
|
|
currentMode = id; |
|
|
|
|
|
|
|
|
modeList.querySelectorAll("input[type='checkbox']").forEach(cb => cb.checked = false); |
|
|
|
|
|
|
|
|
const mine = box.querySelector("input[type='checkbox']"); |
|
|
mine.checked = true; |
|
|
|
|
|
convertNow(); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
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")); |
|
|
|
|
|
|
|
|
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(_){ |
|
|
|
|
|
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); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
renderModes(); |
|
|
convertNow(); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|