Update index.html
Browse files- index.html +890 -17
index.html
CHANGED
|
@@ -1,19 +1,892 @@
|
|
| 1 |
<!doctype html>
|
| 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="vi">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
| 6 |
+
<title>Teencode Tool – Faithful Clone (Offline • 2-way)</title>
|
| 7 |
+
<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." />
|
| 8 |
+
|
| 9 |
+
<style>
|
| 10 |
+
:root{
|
| 11 |
+
--bg:#f3f4f6; --card:#fff; --text:#111827; --muted:#6b7280;
|
| 12 |
+
--line:#e5e7eb; --primary:#2563eb; --primary2:#1d4ed8;
|
| 13 |
+
--warn:#b45309; --ok:#16a34a; --shadow:0 10px 30px rgba(0,0,0,.08);
|
| 14 |
+
--radius:14px;
|
| 15 |
+
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono","Courier New", monospace;
|
| 16 |
+
--sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
|
| 17 |
+
}
|
| 18 |
+
*{box-sizing:border-box}
|
| 19 |
+
body{margin:0; font-family:var(--sans); background:var(--bg); color:var(--text);}
|
| 20 |
+
.wrap{max-width:1200px; margin:0 auto; padding:22px;}
|
| 21 |
+
.top{
|
| 22 |
+
display:flex; justify-content:space-between; gap:16px; align-items:flex-start; margin-bottom:14px;
|
| 23 |
+
}
|
| 24 |
+
.title h1{margin:0; font-size:22px; line-height:1.25}
|
| 25 |
+
.title p{margin:8px 0 0; color:var(--muted); line-height:1.5}
|
| 26 |
+
.badge{
|
| 27 |
+
font-size:12px; padding:6px 10px; border-radius:999px;
|
| 28 |
+
border:1px solid var(--line); background:#fff; color:var(--muted);
|
| 29 |
+
height:fit-content;
|
| 30 |
+
}
|
| 31 |
+
.grid{display:grid; grid-template-columns:1.15fr .85fr; gap:14px;}
|
| 32 |
+
@media (max-width: 980px){ .grid{grid-template-columns:1fr;} }
|
| 33 |
+
.card{
|
| 34 |
+
background:var(--card);
|
| 35 |
+
border:1px solid var(--line);
|
| 36 |
+
border-radius:var(--radius);
|
| 37 |
+
box-shadow:var(--shadow);
|
| 38 |
+
}
|
| 39 |
+
.panel{padding:16px;}
|
| 40 |
+
label.small{display:block; font-size:13px; color:var(--muted); margin-bottom:8px;}
|
| 41 |
+
textarea{
|
| 42 |
+
width:100%; min-height:140px; resize:vertical;
|
| 43 |
+
padding:12px; border-radius:12px; border:1px solid var(--line);
|
| 44 |
+
outline:none; font-size:15px; line-height:1.45; background:#fff;
|
| 45 |
+
}
|
| 46 |
+
textarea:focus{border-color:#93c5fd; box-shadow:0 0 0 4px rgba(59,130,246,.15)}
|
| 47 |
+
.row{display:flex; gap:10px; align-items:center; justify-content:space-between; flex-wrap:wrap; margin-top:10px;}
|
| 48 |
+
.btn{
|
| 49 |
+
border:0; cursor:pointer; padding:10px 14px; border-radius:12px;
|
| 50 |
+
font-weight:700; font-size:14px;
|
| 51 |
+
display:inline-flex; align-items:center; gap:8px;
|
| 52 |
+
transition: transform .05s ease, background .15s ease, opacity .15s ease;
|
| 53 |
+
}
|
| 54 |
+
.btn:active{transform: translateY(1px)}
|
| 55 |
+
.btn.primary{background:var(--primary); color:#fff;}
|
| 56 |
+
.btn.primary:hover{background:var(--primary2)}
|
| 57 |
+
.btn.ghost{background:#f9fafb; border:1px solid var(--line); color:#111827;}
|
| 58 |
+
.btn.ghost:hover{background:#f3f4f6}
|
| 59 |
+
.btn.warn{background:#fff7ed; border:1px solid #fed7aa; color:var(--warn);}
|
| 60 |
+
.btn.warn:hover{background:#ffedd5}
|
| 61 |
+
.btn.small{padding:8px 10px; font-size:13px; border-radius:10px;}
|
| 62 |
+
.hint{color:var(--muted); font-size:13px;}
|
| 63 |
+
.modes h2{margin:0 0 10px; font-size:14px;}
|
| 64 |
+
.mode-list{display:grid; gap:8px; max-height:540px; overflow:auto; padding-right:4px;}
|
| 65 |
+
.mode{
|
| 66 |
+
display:flex; gap:10px; align-items:flex-start;
|
| 67 |
+
border:1px solid var(--line); border-radius:12px;
|
| 68 |
+
padding:10px; background:#fff;
|
| 69 |
+
}
|
| 70 |
+
.mode:hover{border-color:#cbd5e1}
|
| 71 |
+
.mode input{margin-top:3px}
|
| 72 |
+
.mode .ex{font-family:var(--mono); font-size:13px;}
|
| 73 |
+
.mode .tag{font-size:12px; color:var(--muted); margin-top:2px;}
|
| 74 |
+
.toolbar{
|
| 75 |
+
display:flex; gap:8px; align-items:center; flex-wrap:wrap;
|
| 76 |
+
padding:10px 12px; border:1px solid var(--line); border-radius:12px;
|
| 77 |
+
background:#fff; margin-bottom:10px;
|
| 78 |
+
}
|
| 79 |
+
.seg{
|
| 80 |
+
display:flex; border:1px solid var(--line); border-radius:12px; overflow:hidden;
|
| 81 |
+
background:#fff;
|
| 82 |
+
}
|
| 83 |
+
.seg button{
|
| 84 |
+
padding:8px 10px; border:0; cursor:pointer; background:#fff; color:#111827;
|
| 85 |
+
font-weight:700; font-size:13px;
|
| 86 |
+
}
|
| 87 |
+
.seg button.active{background:#111827; color:#fff;}
|
| 88 |
+
.kv{
|
| 89 |
+
display:flex; gap:8px; align-items:center; flex-wrap:wrap;
|
| 90 |
+
font-size:12px; color:var(--muted);
|
| 91 |
+
}
|
| 92 |
+
.kv strong{color:#111827}
|
| 93 |
+
.toast{
|
| 94 |
+
position:fixed; right:16px; bottom:16px;
|
| 95 |
+
background:#0b1220; color:#fff; padding:10px 12px;
|
| 96 |
+
border-radius:12px; box-shadow:0 16px 50px rgba(0,0,0,.25);
|
| 97 |
+
opacity:0; transform: translateY(8px); pointer-events:none;
|
| 98 |
+
transition:opacity .18s ease, transform .18s ease;
|
| 99 |
+
font-size:13px;
|
| 100 |
+
}
|
| 101 |
+
.toast.show{opacity:1; transform: translateY(0)}
|
| 102 |
+
.note{
|
| 103 |
+
margin-top:10px; padding:10px 12px; border-radius:12px;
|
| 104 |
+
border:1px dashed var(--line); background:#fff; color:var(--muted);
|
| 105 |
+
font-size:12px; line-height:1.5;
|
| 106 |
+
}
|
| 107 |
+
.spin{
|
| 108 |
+
width:14px; height:14px; border-radius:999px;
|
| 109 |
+
border:2px solid rgba(255,255,255,.35);
|
| 110 |
+
border-top-color:#fff;
|
| 111 |
+
animation:spin .7s linear infinite;
|
| 112 |
+
}
|
| 113 |
+
@keyframes spin{to{transform:rotate(360deg)}}
|
| 114 |
+
.loading{
|
| 115 |
+
display:none; align-items:center; gap:8px;
|
| 116 |
+
padding:8px 10px; border-radius:12px;
|
| 117 |
+
background:var(--primary); color:#fff; font-weight:800;
|
| 118 |
+
font-size:12px;
|
| 119 |
+
}
|
| 120 |
+
.loading.show{display:inline-flex}
|
| 121 |
+
</style>
|
| 122 |
+
</head>
|
| 123 |
+
|
| 124 |
+
<body>
|
| 125 |
+
<div class="wrap">
|
| 126 |
+
<div class="top">
|
| 127 |
+
<div class="title">
|
| 128 |
+
<h1>Công cụ giúp bạn tạo Teencode tự động online</h1>
|
| 129 |
+
<p>
|
| 130 |
+
Công cụ giúp bạn tạo Teencode tự động tới <b>12 loại</b> khác nhau...<br/>
|
| 131 |
+
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,
|
| 132 |
+
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.
|
| 133 |
+
</p>
|
| 134 |
+
</div>
|
| 135 |
+
<div class="badge">Offline • Faithful Clone • 2-way</div>
|
| 136 |
+
</div>
|
| 137 |
+
|
| 138 |
+
<div class="grid">
|
| 139 |
+
<!-- LEFT: Input/Output -->
|
| 140 |
+
<div class="card panel">
|
| 141 |
+
<div class="toolbar">
|
| 142 |
+
<div class="seg" aria-label="direction">
|
| 143 |
+
<button id="dirForward" type="button" class="active">Normal → Teen</button>
|
| 144 |
+
<button id="dirReverse" type="button">Teen → Normal</button>
|
| 145 |
+
</div>
|
| 146 |
+
|
| 147 |
+
<button id="btnSwap" class="btn ghost small" type="button" title="Đổi chỗ Input/Output">⇄ Swap</button>
|
| 148 |
+
|
| 149 |
+
<div class="loading" id="loadingBox">
|
| 150 |
+
<span class="spin"></span> Converting...
|
| 151 |
+
</div>
|
| 152 |
+
|
| 153 |
+
<div class="kv" style="margin-left:auto">
|
| 154 |
+
<span>Mode: <strong id="modeBadge">1</strong></span>
|
| 155 |
+
<span>•</span>
|
| 156 |
+
<span>Auto-update: <strong>ON</strong></span>
|
| 157 |
+
</div>
|
| 158 |
+
</div>
|
| 159 |
+
|
| 160 |
+
<label class="small" for="inputText">Nhập chữ vào đây nhé</label>
|
| 161 |
+
<textarea id="inputText" placeholder="Nhập chữ vào đây nhé"></textarea>
|
| 162 |
+
|
| 163 |
+
<div class="row">
|
| 164 |
+
<div class="hint">Chọn 1 kiểu teen code bên phải → kết quả cập nhật ngay.</div>
|
| 165 |
+
<div style="display:flex; gap:8px; flex-wrap:wrap;">
|
| 166 |
+
<button id="btnClear" class="btn ghost" type="button">Xoá</button>
|
| 167 |
+
<button id="btnCopy" class="btn primary" type="button">Copy All</button>
|
| 168 |
+
</div>
|
| 169 |
+
</div>
|
| 170 |
+
|
| 171 |
+
<hr style="border:0;border-top:1px solid var(--line); margin:14px 0;">
|
| 172 |
+
|
| 173 |
+
<label class="small" for="outputText">Kết quả</label>
|
| 174 |
+
<textarea id="outputText" readonly placeholder="Kết quả sẽ hiện ở đây..."></textarea>
|
| 175 |
+
|
| 176 |
+
<div class="note">
|
| 177 |
+
<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),
|
| 178 |
+
nên <b>Teen → Normal</b> chỉ khôi phục <b>xấp xỉ</b> (best-effort) — đúng bản chất “teencode”.
|
| 179 |
+
</div>
|
| 180 |
+
</div>
|
| 181 |
+
|
| 182 |
+
<!-- RIGHT: Modes -->
|
| 183 |
+
<div class="card panel modes">
|
| 184 |
+
<h2>Chọn kiểu chữ teen code</h2>
|
| 185 |
+
<div class="mode-list" id="modeList"></div>
|
| 186 |
+
</div>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
|
| 190 |
+
<div class="toast" id="toast">Đã copy!</div>
|
| 191 |
+
|
| 192 |
+
<script>
|
| 193 |
+
/************************************************************
|
| 194 |
+
* Faithful Clone Core (bám sát logic gốc teencode1.js)
|
| 195 |
+
* + Offline UI + 2-way convert (reverse = best-effort)
|
| 196 |
+
************************************************************/
|
| 197 |
+
|
| 198 |
+
// ===== helpers UI =====
|
| 199 |
+
const $ = (sel) => document.querySelector(sel);
|
| 200 |
+
const inputText = $("#inputText");
|
| 201 |
+
const outputText = $("#outputText");
|
| 202 |
+
const modeList = $("#modeList");
|
| 203 |
+
const modeBadge = $("#modeBadge");
|
| 204 |
+
const toast = $("#toast");
|
| 205 |
+
const btnCopy = $("#btnCopy");
|
| 206 |
+
const btnClear = $("#btnClear");
|
| 207 |
+
const btnSwap = $("#btnSwap");
|
| 208 |
+
const loadingBox = $("#loadingBox");
|
| 209 |
+
const dirForward = $("#dirForward");
|
| 210 |
+
const dirReverse = $("#dirReverse");
|
| 211 |
+
|
| 212 |
+
let currentMode = 1;
|
| 213 |
+
let direction = "forward"; // forward | reverse
|
| 214 |
+
|
| 215 |
+
function showToast(msg){
|
| 216 |
+
toast.textContent = msg;
|
| 217 |
+
toast.classList.add("show");
|
| 218 |
+
setTimeout(()=>toast.classList.remove("show"), 900);
|
| 219 |
+
}
|
| 220 |
+
function showLoading(ms=350){
|
| 221 |
+
loadingBox.classList.add("show");
|
| 222 |
+
setTimeout(()=>loadingBox.classList.remove("show"), ms);
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
// ===== faithful-ish normalization (replaces toNormalText in a robust UTF-8 way) =====
|
| 226 |
+
function toNormalText(text){
|
| 227 |
+
// faithful intent: remove Vietnamese diacritics + đ/Đ
|
| 228 |
+
return String(text)
|
| 229 |
+
.normalize("NFD")
|
| 230 |
+
.replace(/[\u0300-\u036f]/g, "")
|
| 231 |
+
.replace(/đ/g,"d")
|
| 232 |
+
.replace(/Đ/g,"D");
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
// =======================
|
| 236 |
+
// FORWARD: 12 modes (match teencode1.js behavior)
|
| 237 |
+
// =======================
|
| 238 |
+
|
| 239 |
+
function toTeenCode1(text){
|
| 240 |
+
text = toNormalText(text);
|
| 241 |
+
text = text.toLowerCase();
|
| 242 |
+
const textArr = text.split(' '); // faithful: split by single space
|
| 243 |
+
for (let i in textArr){
|
| 244 |
+
let word = textArr[i];
|
| 245 |
+
|
| 246 |
+
if(word.indexOf('y')>=0){
|
| 247 |
+
if(word.indexOf('ay')>=0){
|
| 248 |
+
// keep
|
| 249 |
+
}else if(word.indexOf('uy')>=0){
|
| 250 |
+
word = word.replace('uy','y');
|
| 251 |
+
}else{
|
| 252 |
+
word = word.replace('y','i');
|
| 253 |
+
}
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
word = word.replace(/ch$/g,"x");
|
| 257 |
+
word = word.replace(/^ph/g,"f");
|
| 258 |
+
word = word.replace(/^kh/g,"k");
|
| 259 |
+
word = word.replace(/^gi/g,"j");
|
| 260 |
+
word = word.replace(/^gh/g,"g");
|
| 261 |
+
word = word.replace(/^ngh/g,"ng");
|
| 262 |
+
word = word.replace(/^qu/g,"q");
|
| 263 |
+
word = word.replace(/ng$/g,"g");
|
| 264 |
+
word = word.replace(/nh$/g,"h");
|
| 265 |
+
word = word.replace('i','j'); // only first 'i'
|
| 266 |
+
textArr[i] = word;
|
| 267 |
+
}
|
| 268 |
+
return textArr.join(' ');
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
function toTeenCode2(text){
|
| 272 |
+
text = toNormalText(text);
|
| 273 |
+
text = text.toUpperCase();
|
| 274 |
+
const textArr = text.split(' ');
|
| 275 |
+
for (let i in textArr){
|
| 276 |
+
let word = textArr[i];
|
| 277 |
+
word = word.replace(/A/g,"4");
|
| 278 |
+
word = word.replace(/B/g,"13");
|
| 279 |
+
word = word.replace(/C/g,"6");
|
| 280 |
+
word = word.replace(/D/g,"10");
|
| 281 |
+
word = word.replace(/E/g,"3");
|
| 282 |
+
word = word.replace(/G/g,"9");
|
| 283 |
+
word = word.replace(/H/g,"76");
|
| 284 |
+
word = word.replace(/I/g,"1");
|
| 285 |
+
word = word.replace(/K/g,"14");
|
| 286 |
+
word = word.replace(/L/g,"2");
|
| 287 |
+
word = word.replace(/M/g,"111");
|
| 288 |
+
word = word.replace(/N/g,"11");
|
| 289 |
+
word = word.replace(/O/g,"0");
|
| 290 |
+
word = word.replace(/P/g,"19");
|
| 291 |
+
word = word.replace(/Q/g,"62");
|
| 292 |
+
word = word.replace(/R/g,"12");
|
| 293 |
+
word = word.replace(/S/g,"5");
|
| 294 |
+
word = word.replace(/T/g,"7");
|
| 295 |
+
word = word.replace(/U/g,"21");
|
| 296 |
+
word = word.replace(/Ư/g,"22"); // faithful intent of /Ư/
|
| 297 |
+
word = word.replace(/V/g,"17");
|
| 298 |
+
word = word.replace(/X/g,"96");
|
| 299 |
+
word = word.replace(/Y/g,"29");
|
| 300 |
+
word = word.replace(/X/g,"96");
|
| 301 |
+
textArr[i] = word;
|
| 302 |
+
}
|
| 303 |
+
return textArr.join(' ');
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
function toTeenCode3(text){
|
| 307 |
+
text = toNormalText(text);
|
| 308 |
+
text = text.toLowerCase();
|
| 309 |
+
const flipTable = {
|
| 310 |
+
a:'\u0250', b:'q', c:'\u0254', d:'p', e:'\u01DD', f:'\u025F', g:'\u0183',
|
| 311 |
+
h:'\u0265', i:'\u0131', j:'\u027E', k:'\u029E', l:'\u0283', m:'\u026F',
|
| 312 |
+
n:'u', r:'\u0279', t:'\u0287', v:'\u028C', w:'\u028D', y:'\u028E',
|
| 313 |
+
'.':'\u02D9','[':']','(':')','{':'}','?':'\u00BF','!':'\u00A1',
|
| 314 |
+
"'":',','<':'>','_':'\u203E',';':'\u061B','\u203F':'\u2040',
|
| 315 |
+
'\u2045':'\u2046','\u2234':'\u2235','\r':'\n'
|
| 316 |
+
};
|
| 317 |
+
|
| 318 |
+
const last = text.length - 1;
|
| 319 |
+
const result = new Array(text.length);
|
| 320 |
+
for (let i = last; i >= 0; --i){
|
| 321 |
+
const c = text.charAt(i);
|
| 322 |
+
const r = flipTable[c];
|
| 323 |
+
result[last - i] = (r !== undefined) ? r : c;
|
| 324 |
+
}
|
| 325 |
+
return result.join('');
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
function toTeenCode4(text){
|
| 329 |
+
text = toNormalText(text);
|
| 330 |
+
text = text.toUpperCase();
|
| 331 |
+
const textArr = text.split(' ');
|
| 332 |
+
for (let i in textArr){
|
| 333 |
+
let word = textArr[i];
|
| 334 |
+
word = word.replace(/A/g,"4");
|
| 335 |
+
word = word.replace(/E/g,"3");
|
| 336 |
+
word = word.replace(/G/g,"q");
|
| 337 |
+
|
| 338 |
+
// faithful: if(word[0]=="H"){word[0]='h';} else {...}
|
| 339 |
+
// JS strings are immutable, but intention: keep initial H untouched.
|
| 340 |
+
if(word[0] === "H"){
|
| 341 |
+
// do nothing (faithful effect)
|
| 342 |
+
}else{
|
| 343 |
+
word = word.replace(/KH/g,"kh");
|
| 344 |
+
word = word.replace(/H/g,"k");
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
word = word.replace(/I/g,"j");
|
| 348 |
+
word = word.replace(/O/g,"0");
|
| 349 |
+
textArr[i] = word;
|
| 350 |
+
}
|
| 351 |
+
return textArr.join(' ').toLowerCase();
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
function toTeenCode5(text){
|
| 355 |
+
text = text.replace(/l/g,'n');
|
| 356 |
+
text = text.replace(/L/g,'N');
|
| 357 |
+
return text;
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
function toTeenCode6(text){
|
| 361 |
+
text = text.toLowerCase();
|
| 362 |
+
const textArr = text.split(' ');
|
| 363 |
+
|
| 364 |
+
// faithful intent: if starts with vowel => add "ngh"
|
| 365 |
+
const vowelStart = /^(a|ă|â|e|ê|i|o|ô|ơ|u|ư|y|á|à|ả|ã|ạ|ắ|ằ|ẳ|ẵ|ặ|ấ|ầ|ẩ|ẫ|ậ|é|è|ẻ|ẽ|ẹ|ế|ề|ể|ễ|ệ|í|ì|ỉ|ĩ|ị|ó|ò|ỏ|õ|ọ|ố|ồ|ổ|ỗ|ộ|ớ|ờ|ở|ỡ|ợ|ú|ù|ủ|ũ|ụ|ứ|ừ|ử|ữ|ự|ý|ỳ|ỷ|ỹ|ỵ)/i;
|
| 366 |
+
|
| 367 |
+
for (let i in textArr){
|
| 368 |
+
let word = textArr[i];
|
| 369 |
+
if(vowelStart.test(word)){
|
| 370 |
+
word = "ngh" + word;
|
| 371 |
+
}
|
| 372 |
+
// faithful spirit: replace initial cluster with "nh"
|
| 373 |
+
word = word.replace(/^(ngh|ch|gh|gi|kh|ng|nh|ph|qu|th|tr|[bcdđghklmnpqrstvx])/i, "nh");
|
| 374 |
+
textArr[i] = word;
|
| 375 |
+
}
|
| 376 |
+
return textArr.join(' ').toLowerCase();
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
function toTeenCode7(text){
|
| 380 |
+
text = text.toLowerCase();
|
| 381 |
+
const textArr = text.split(' ');
|
| 382 |
+
for (let i in textArr){
|
| 383 |
+
let word = textArr[i];
|
| 384 |
+
|
| 385 |
+
word = word.replace(/b/g,'|3');
|
| 386 |
+
word = word.replace(/đ/g,'+)');
|
| 387 |
+
word = word.replace(/d/g,'])');
|
| 388 |
+
word = word.replace(/l/g,'|_');
|
| 389 |
+
|
| 390 |
+
// a-family
|
| 391 |
+
word = word.replace(/[áắấ]/g,"4'");
|
| 392 |
+
word = word.replace(/[àằầ]/g,"4`");
|
| 393 |
+
word = word.replace(/[aăâ]/g,"4");
|
| 394 |
+
word = word.replace(/[ảẳẩ]/g,"4");
|
| 395 |
+
word = word.replace(/[ạặậ]/g,"4");
|
| 396 |
+
word = word.replace(/[ãẵẫ]/g,"4");
|
| 397 |
+
|
| 398 |
+
// e-family
|
| 399 |
+
word = word.replace(/[éế]/g,"3'");
|
| 400 |
+
word = word.replace(/[ẻể]/g,"3?");
|
| 401 |
+
word = word.replace(/[èề]/g,"3`");
|
| 402 |
+
word = word.replace(/[eê]/g,"3");
|
| 403 |
+
word = word.replace(/[ẽễ]/g,"3");
|
| 404 |
+
word = word.replace(/[ẹệ]/g,"3");
|
| 405 |
+
|
| 406 |
+
// i-family
|
| 407 |
+
word = word.replace(/i/g,"j");
|
| 408 |
+
word = word.replace(/ì/g,"j`");
|
| 409 |
+
word = word.replace(/í/g,"j'");
|
| 410 |
+
word = word.replace(/[ỉĩị]/g,"j");
|
| 411 |
+
|
| 412 |
+
// o-family
|
| 413 |
+
word = word.replace(/[ỏổở]/g,"0");
|
| 414 |
+
word = word.replace(/[oôơ]/g,"0");
|
| 415 |
+
word = word.replace(/[òồờ]/g,"0`");
|
| 416 |
+
word = word.replace(/[óốớ]/g,"0'");
|
| 417 |
+
word = word.replace(/[õỗỡ]/g,"0");
|
| 418 |
+
word = word.replace(/[ọộợ]/g,"0.");
|
| 419 |
+
|
| 420 |
+
// u-family
|
| 421 |
+
word = word.replace(/[úứ]/g,"u'");
|
| 422 |
+
word = word.replace(/[ùừ]/g,"u`");
|
| 423 |
+
word = word.replace(/[ủử]/g,"u");
|
| 424 |
+
word = word.replace(/[ũữ]/g,"u");
|
| 425 |
+
word = word.replace(/[ụự]/g,"u.");
|
| 426 |
+
word = word.replace(/[uư]/g,"u");
|
| 427 |
+
|
| 428 |
+
// y
|
| 429 |
+
word = word.replace(/y/g,"ij");
|
| 430 |
+
|
| 431 |
+
textArr[i] = word;
|
| 432 |
+
}
|
| 433 |
+
return textArr.join(' ').toLowerCase();
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
function toTeenCode8(text){
|
| 437 |
+
text = text.toLowerCase();
|
| 438 |
+
const textArr = text.split(' ');
|
| 439 |
+
for (let i in textArr){
|
| 440 |
+
let word = textArr[i];
|
| 441 |
+
|
| 442 |
+
// vowels first
|
| 443 |
+
word = word.replace(/[áắấ]/g,"Cl'");
|
| 444 |
+
word = word.replace(/[àằầ]/g,"Cl`");
|
| 445 |
+
word = word.replace(/[aăâ]/g,"Cl");
|
| 446 |
+
word = word.replace(/[ảẳẩ]/g,"Cl");
|
| 447 |
+
word = word.replace(/[ạặậ]/g,"Cl");
|
| 448 |
+
word = word.replace(/[ãẵẫ]/g,"Cl");
|
| 449 |
+
|
| 450 |
+
word = word.replace(/[éế]/g,"F_'");
|
| 451 |
+
word = word.replace(/[ẻể]/g,"F_");
|
| 452 |
+
word = word.replace(/[èề]/g,"F_`");
|
| 453 |
+
word = word.replace(/[eê]/g,"F_");
|
| 454 |
+
word = word.replace(/[ẽễ]/g,"F_");
|
| 455 |
+
word = word.replace(/[ẹệ]/g,"F_");
|
| 456 |
+
|
| 457 |
+
word = word.replace(/[ỏổở]/g,"º");
|
| 458 |
+
word = word.replace(/[oôơ]/g,"º");
|
| 459 |
+
word = word.replace(/[òồờ]/g,"º`");
|
| 460 |
+
word = word.replace(/[óốớ]/g,"º'");
|
| 461 |
+
word = word.replace(/[õỗỡ]/g,"º");
|
| 462 |
+
word = word.replace(/[ọộợ]/g,"º");
|
| 463 |
+
|
| 464 |
+
word = word.replace(/i/g,"]");
|
| 465 |
+
word = word.replace(/ì/g,"]`");
|
| 466 |
+
word = word.replace(/í/g,"]'");
|
| 467 |
+
word = word.replace(/[ỉĩị]/g,"]");
|
| 468 |
+
|
| 469 |
+
word = word.replace(/[úứ]/g,"µ'");
|
| 470 |
+
word = word.replace(/[ùừ]/g,"µ`");
|
| 471 |
+
word = word.replace(/[ủử]/g,"µ");
|
| 472 |
+
word = word.replace(/[ũữ]/g,"µ");
|
| 473 |
+
word = word.replace(/[ụự]/g,"µ");
|
| 474 |
+
word = word.replace(/[uư]/g,"µ");
|
| 475 |
+
|
| 476 |
+
// consonants
|
| 477 |
+
word = word.replace(/d/g, "])");
|
| 478 |
+
word = word.replace(/đ/g, "+)");
|
| 479 |
+
word = word.replace(/n/g, "]\\[");
|
| 480 |
+
word = word.replace(/h/g, "¨|");
|
| 481 |
+
word = word.replace(/g/g, "(¬");
|
| 482 |
+
word = word.replace(/k/g, "]<");
|
| 483 |
+
word = word.replace(/t/g, "¨ ");
|
| 484 |
+
word = word.replace(/v/g, "\\/");
|
| 485 |
+
word = word.replace(/m/g, "/v\\");
|
| 486 |
+
word = word.replace(/^qu/g,"v\\/");
|
| 487 |
+
word = word.replace(/r/g, "Pv");
|
| 488 |
+
word = word.replace(/w/g, "v\\/");
|
| 489 |
+
word = word.replace(/y/g, "¥");
|
| 490 |
+
word = word.replace(/s/g, "§");
|
| 491 |
+
word = word.replace(/x/g, "><");
|
| 492 |
+
word = word.replace(/p/g, "]º");
|
| 493 |
+
word = word.replace(/b/g, "]3");
|
| 494 |
+
word = word.replace(/c/g, "(");
|
| 495 |
+
|
| 496 |
+
textArr[i] = word;
|
| 497 |
+
}
|
| 498 |
+
return textArr.join(' ');
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
function toTeenCode9(text, s){
|
| 502 |
+
if(s === undefined) s = "'s";
|
| 503 |
+
const textArr = text.split(' ');
|
| 504 |
+
for (let i in textArr){
|
| 505 |
+
let word = textArr[i];
|
| 506 |
+
const symbol = "~`!@#$%^&*()_-+={}[]:>;'.,</?*-+";
|
| 507 |
+
|
| 508 |
+
if(word.match(/[^A-Za-z0-9]/g) == null){
|
| 509 |
+
word = word + s;
|
| 510 |
+
}else{
|
| 511 |
+
const spArr = word.match(/[^A-Za-z0-9]/g);
|
| 512 |
+
let pos = word.length;
|
| 513 |
+
for (let x in spArr){
|
| 514 |
+
if(symbol.indexOf(spArr[x]) !== -1){
|
| 515 |
+
pos = word.indexOf(spArr[x]);
|
| 516 |
+
break;
|
| 517 |
+
}
|
| 518 |
+
}
|
| 519 |
+
word = [word.slice(0,pos), s, word.slice(pos)].join('');
|
| 520 |
+
}
|
| 521 |
+
textArr[i] = word;
|
| 522 |
+
}
|
| 523 |
+
return textArr.join(' ');
|
| 524 |
+
}
|
| 525 |
+
function toTeenCode10(text){ return toTeenCode9(text,"'ss"); }
|
| 526 |
+
function toTeenCode11(text){ return toTeenCode9(text,"'sss"); }
|
| 527 |
+
|
| 528 |
+
function toTeenCode12(text){
|
| 529 |
+
text = text.toLowerCase();
|
| 530 |
+
const textArr = text.split(' ');
|
| 531 |
+
for (let i in textArr){
|
| 532 |
+
let word = textArr[i].split('');
|
| 533 |
+
const rand = Math.floor((Math.random()*10)+1);
|
| 534 |
+
let j = (rand<=5) ? 0 : 1;
|
| 535 |
+
for(; j<word.length; j+=2){
|
| 536 |
+
word[j] = String(word[j]).toUpperCase();
|
| 537 |
+
}
|
| 538 |
+
textArr[i] = word.join('');
|
| 539 |
+
}
|
| 540 |
+
return textArr.join(' ');
|
| 541 |
+
}
|
| 542 |
+
|
| 543 |
+
// =======================
|
| 544 |
+
// REVERSE: best-effort decode
|
| 545 |
+
// =======================
|
| 546 |
+
|
| 547 |
+
function decodeMode1(text){
|
| 548 |
+
// best-effort: restore some patterns (lossy)
|
| 549 |
+
let t = String(text).toLowerCase();
|
| 550 |
+
const arr = t.split(' ');
|
| 551 |
+
for (let i in arr){
|
| 552 |
+
let w = arr[i];
|
| 553 |
+
|
| 554 |
+
// endings
|
| 555 |
+
w = w.replace(/x$/g, "ch");
|
| 556 |
+
w = w.replace(/g$/g, "ng"); // might be original g
|
| 557 |
+
w = w.replace(/h$/g, "nh"); // might be original h
|
| 558 |
+
|
| 559 |
+
// starts
|
| 560 |
+
w = w.replace(/^f/g, "ph");
|
| 561 |
+
w = w.replace(/^q/g, "qu");
|
| 562 |
+
w = w.replace(/^j/g, "gi");
|
| 563 |
+
w = w.replace(/^k/g, "kh"); // might be original k
|
| 564 |
+
// restore j -> i elsewhere
|
| 565 |
+
w = w.replace(/j/g, "i");
|
| 566 |
+
|
| 567 |
+
arr[i] = w;
|
| 568 |
+
}
|
| 569 |
+
return arr.join(' ');
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
function decodeMode2(text){
|
| 573 |
+
// reverse mapping: greedy longest match
|
| 574 |
+
const codeToChar = {
|
| 575 |
+
"111":"M","96":"X","76":"H","62":"Q","29":"Y","22":"Ư","21":"U",
|
| 576 |
+
"19":"P","17":"V","14":"K","13":"B","12":"R","11":"N","10":"D",
|
| 577 |
+
"9":"G","7":"T","6":"C","5":"S","4":"A","3":"E","2":"L","1":"I","0":"O"
|
| 578 |
+
};
|
| 579 |
+
const codes = Object.keys(codeToChar).sort((a,b)=>b.length-a.length);
|
| 580 |
+
|
| 581 |
+
const words = String(text).trim().split(/\s+/);
|
| 582 |
+
const out = words.map(w=>{
|
| 583 |
+
let s = w;
|
| 584 |
+
let res = "";
|
| 585 |
+
while(s.length){
|
| 586 |
+
let matched = false;
|
| 587 |
+
for(const c of codes){
|
| 588 |
+
if(s.startsWith(c)){
|
| 589 |
+
res += codeToChar[c];
|
| 590 |
+
s = s.slice(c.length);
|
| 591 |
+
matched = true;
|
| 592 |
+
break;
|
| 593 |
+
}
|
| 594 |
+
}
|
| 595 |
+
if(!matched){
|
| 596 |
+
// keep unknown char and move on
|
| 597 |
+
res += s[0];
|
| 598 |
+
s = s.slice(1);
|
| 599 |
+
}
|
| 600 |
+
}
|
| 601 |
+
return res.toLowerCase();
|
| 602 |
+
});
|
| 603 |
+
return out.join(" ");
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
+
function decodeMode3(text){
|
| 607 |
+
// build inverse flip table, then reverse string and map back
|
| 608 |
+
const flipTable = {
|
| 609 |
+
a:'\u0250', b:'q', c:'\u0254', d:'p', e:'\u01DD', f:'\u025F', g:'\u0183',
|
| 610 |
+
h:'\u0265', i:'\u0131', j:'\u027E', k:'\u029E', l:'\u0283', m:'\u026F',
|
| 611 |
+
n:'u', r:'\u0279', t:'\u0287', v:'\u028C', w:'\u028D', y:'\u028E',
|
| 612 |
+
'.':'\u02D9','[':']','(':')','{':'}','?':'\u00BF','!':'\u00A1',
|
| 613 |
+
"'":',','<':'>','_':'\u203E',';':'\u061B','\u203F':'\u2040',
|
| 614 |
+
'\u2045':'\u2046','\u2234':'\u2235','\r':'\n'
|
| 615 |
+
};
|
| 616 |
+
const inv = {};
|
| 617 |
+
for(const k in flipTable){
|
| 618 |
+
inv[flipTable[k]] = k;
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
const s = String(text);
|
| 622 |
+
let out = "";
|
| 623 |
+
for(let i=s.length-1; i>=0; i--){
|
| 624 |
+
const ch = s[i];
|
| 625 |
+
out += (inv[ch] !== undefined) ? inv[ch] : ch;
|
| 626 |
+
}
|
| 627 |
+
return out;
|
| 628 |
+
}
|
| 629 |
+
|
| 630 |
+
function decodeMode4(text){
|
| 631 |
+
// best-effort: reverse leet symbols
|
| 632 |
+
let t = toNormalText(String(text)).toLowerCase();
|
| 633 |
+
t = t.replace(/4/g,"a").replace(/3/g,"e").replace(/0/g,"o").replace(/q/g,"g").replace(/j/g,"i");
|
| 634 |
+
// H/K ambiguity can't be restored
|
| 635 |
+
return t;
|
| 636 |
+
}
|
| 637 |
+
|
| 638 |
+
function decodeMode5(text){
|
| 639 |
+
// lossy reverse (n -> l)
|
| 640 |
+
return String(text).replace(/n/g,"l").replace(/N/g,"L");
|
| 641 |
+
}
|
| 642 |
+
|
| 643 |
+
function decodeMode6(text){
|
| 644 |
+
// cannot restore original clusters reliably
|
| 645 |
+
return String(text);
|
| 646 |
+
}
|
| 647 |
+
|
| 648 |
+
function decodeWithTokens(text, tokenMap){
|
| 649 |
+
// tokenMap: array [token, char], ordered longest-first
|
| 650 |
+
const s = String(text);
|
| 651 |
+
let i = 0;
|
| 652 |
+
let out = "";
|
| 653 |
+
while(i < s.length){
|
| 654 |
+
let matched = false;
|
| 655 |
+
for(const [tok, val] of tokenMap){
|
| 656 |
+
if(s.startsWith(tok, i)){
|
| 657 |
+
out += val;
|
| 658 |
+
i += tok.length;
|
| 659 |
+
matched = true;
|
| 660 |
+
break;
|
| 661 |
+
}
|
| 662 |
+
}
|
| 663 |
+
if(!matched){
|
| 664 |
+
out += s[i];
|
| 665 |
+
i++;
|
| 666 |
+
}
|
| 667 |
+
}
|
| 668 |
+
return out;
|
| 669 |
+
}
|
| 670 |
+
|
| 671 |
+
function decodeMode7(text){
|
| 672 |
+
// IMPORTANT: longest-first
|
| 673 |
+
const tokens = [
|
| 674 |
+
["0.", "o"], ["0`","o"], ["0'","o"],
|
| 675 |
+
["u.","u"], ["u`","u"], ["u'","u"],
|
| 676 |
+
["j`","i"], ["j'","i"], ["ij","y"],
|
| 677 |
+
["|_","l"], ["|3","b"], ["+)", "đ"], ["])", "d"], ["])","d"], // safety
|
| 678 |
+
["4`","a"], ["4'","a"], ["4","a"],
|
| 679 |
+
["3`","e"], ["3'","e"], ["3?","e"], ["3","e"],
|
| 680 |
+
["0","o"], ["u","u"], ["j","i"]
|
| 681 |
+
];
|
| 682 |
+
// unify "])" exact
|
| 683 |
+
const cleaned = String(text).replace(/\]\)/g, "])");
|
| 684 |
+
return decodeWithTokens(cleaned, tokens).toLowerCase();
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
function decodeMode8(text){
|
| 688 |
+
const tokens = [
|
| 689 |
+
["/v\\","m"],
|
| 690 |
+
["v\\/","qu"], // qu + w ambiguity
|
| 691 |
+
["\\/", "v"],
|
| 692 |
+
["Pv","r"],
|
| 693 |
+
["]\\[","n"],
|
| 694 |
+
["(¬","g"],
|
| 695 |
+
["]<","k"],
|
| 696 |
+
["]º","p"],
|
| 697 |
+
["]3","b"],
|
| 698 |
+
["><","x"],
|
| 699 |
+
["+)", "đ"],
|
| 700 |
+
["])", "d"],
|
| 701 |
+
["¨|","h"],
|
| 702 |
+
["¨ ","t"],
|
| 703 |
+
["§","s"],
|
| 704 |
+
["¥","y"],
|
| 705 |
+
["µ`","u"], ["µ'","u"], ["µ","u"],
|
| 706 |
+
["º`","o"], ["º'","o"], ["º","o"],
|
| 707 |
+
["F_`","e"], ["F_'","e"], ["F_","e"],
|
| 708 |
+
["Cl`","a"], ["Cl'","a"], ["Cl","a"],
|
| 709 |
+
["]`","i"], ["]'","i"], ["]","i"],
|
| 710 |
+
["(","c"]
|
| 711 |
+
];
|
| 712 |
+
return decodeWithTokens(String(text), tokens).toLowerCase();
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
function decodeMode9(text){
|
| 716 |
+
return String(text).replace(/'s\b/g,"").replace(/'s(?=[^A-Za-z0-9]|$)/g,"");
|
| 717 |
+
}
|
| 718 |
+
function decodeMode10(text){
|
| 719 |
+
return String(text).replace(/'ss\b/g,"").replace(/'ss(?=[^A-Za-z0-9]|$)/g,"");
|
| 720 |
+
}
|
| 721 |
+
function decodeMode11(text){
|
| 722 |
+
return String(text).replace(/'sss\b/g,"").replace(/'sss(?=[^A-Za-z0-9]|$)/g,"");
|
| 723 |
+
}
|
| 724 |
+
|
| 725 |
+
function decodeMode12(text){
|
| 726 |
+
return String(text).toLowerCase();
|
| 727 |
+
}
|
| 728 |
+
|
| 729 |
+
// ===== apply =====
|
| 730 |
+
function applyForward(mode, text){
|
| 731 |
+
switch(mode){
|
| 732 |
+
case 1: return toTeenCode1(text);
|
| 733 |
+
case 2: return toTeenCode2(text);
|
| 734 |
+
case 3: return toTeenCode3(text);
|
| 735 |
+
case 4: return toTeenCode4(text);
|
| 736 |
+
case 5: return toTeenCode5(text);
|
| 737 |
+
case 6: return toTeenCode6(text);
|
| 738 |
+
case 7: return toTeenCode7(text);
|
| 739 |
+
case 8: return toTeenCode8(text);
|
| 740 |
+
case 9: return toTeenCode9(text);
|
| 741 |
+
case 10: return toTeenCode10(text);
|
| 742 |
+
case 11: return toTeenCode11(text);
|
| 743 |
+
case 12: return toTeenCode12(text);
|
| 744 |
+
default: return text;
|
| 745 |
+
}
|
| 746 |
+
}
|
| 747 |
+
function applyReverse(mode, text){
|
| 748 |
+
switch(mode){
|
| 749 |
+
case 1: return decodeMode1(text);
|
| 750 |
+
case 2: return decodeMode2(text);
|
| 751 |
+
case 3: return decodeMode3(text);
|
| 752 |
+
case 4: return decodeMode4(text);
|
| 753 |
+
case 5: return decodeMode5(text);
|
| 754 |
+
case 6: return decodeMode6(text);
|
| 755 |
+
case 7: return decodeMode7(text);
|
| 756 |
+
case 8: return decodeMode8(text);
|
| 757 |
+
case 9: return decodeMode9(text);
|
| 758 |
+
case 10: return decodeMode10(text);
|
| 759 |
+
case 11: return decodeMode11(text);
|
| 760 |
+
case 12: return decodeMode12(text);
|
| 761 |
+
default: return text;
|
| 762 |
+
}
|
| 763 |
+
}
|
| 764 |
+
|
| 765 |
+
function convertNow(){
|
| 766 |
+
showLoading(450);
|
| 767 |
+
modeBadge.textContent = String(currentMode);
|
| 768 |
+
|
| 769 |
+
const origin = inputText.value ?? "";
|
| 770 |
+
let out = "";
|
| 771 |
+
|
| 772 |
+
if(direction === "forward"){
|
| 773 |
+
out = applyForward(currentMode, origin);
|
| 774 |
+
}else{
|
| 775 |
+
out = applyReverse(currentMode, origin);
|
| 776 |
+
}
|
| 777 |
+
outputText.value = out;
|
| 778 |
+
}
|
| 779 |
+
|
| 780 |
+
// ===== UI modes list (faithful labels) =====
|
| 781 |
+
const MODE_META = [
|
| 782 |
+
{id:1, ex:"day la vj du", tag:"Chuẩn hoá + teen hoá (bỏ dấu, i→j...)"},
|
| 783 |
+
{id:2, ex:"10429 24 171 1021", tag:"Mã hoá số (mapping chữ cái)"},
|
| 784 |
+
{id:3, ex:"up ıʌ ɐʃ ʎɐp", tag:"Lật chữ (upside-down)"},
|
| 785 |
+
{id:4, ex:"d4y l4 vj du", tag:"Leet cơ bản (a→4, e→3, o→0...)"},
|
| 786 |
+
{id:5, ex:"Đây nà ví dụ", tag:"Cute-speak (l→n)"},
|
| 787 |
+
{id:6, ex:"nhây nhà nhí nhụ", tag:"Nh-speak"},
|
| 788 |
+
{id:7, ex:"+)4ij |_4` vj' ])u.", tag:"ASCII/leet nâng cao (kiểu 1)"},
|
| 789 |
+
{id:8, ex:"+)Cl¥ lCl` \\/]' ])µ", tag:"ASCII/leet nâng cao (kiểu 2)"},
|
| 790 |
+
{id:9, ex:"Đây's là's ví's dụ's", tag:"Thêm hậu tố 's"},
|
| 791 |
+
{id:10, ex:"Đây'ss là'ss ví'ss dụ'ss", tag:"Thêm hậu tố 'ss"},
|
| 792 |
+
{id:11, ex:"Đây'sss là'sss ví'sss dụ'sss", tag:"Thêm hậu tố 'sss"},
|
| 793 |
+
{id:12, ex:"ĐâY Là Ví Dụ", tag:"Xen kẽ hoa/thường (ngẫu nhiên)"},
|
| 794 |
+
];
|
| 795 |
+
|
| 796 |
+
function esc(s){
|
| 797 |
+
return String(s).replace(/[&<>"']/g, ch => (
|
| 798 |
+
{ "&":"&","<":"<",">":">",'"':""","'":"'" }[ch]
|
| 799 |
+
));
|
| 800 |
+
}
|
| 801 |
+
|
| 802 |
+
function renderModes(){
|
| 803 |
+
modeList.innerHTML = MODE_META.map(m => `
|
| 804 |
+
<div class="mode" data-mode="${m.id}">
|
| 805 |
+
<input type="checkbox" id="m-${m.id}" ${m.id===currentMode ? "checked":""} />
|
| 806 |
+
<label for="m-${m.id}">
|
| 807 |
+
<div class="ex">${esc(m.ex)}</div>
|
| 808 |
+
<div class="tag">${esc(m.tag)}</div>
|
| 809 |
+
</label>
|
| 810 |
+
</div>
|
| 811 |
+
`).join("");
|
| 812 |
+
|
| 813 |
+
// faithful behavior: only one checkbox checked
|
| 814 |
+
modeList.querySelectorAll(".mode").forEach(box=>{
|
| 815 |
+
box.addEventListener("click", (e)=>{
|
| 816 |
+
const id = Number(box.getAttribute("data-mode"));
|
| 817 |
+
currentMode = id;
|
| 818 |
+
|
| 819 |
+
// uncheck all
|
| 820 |
+
modeList.querySelectorAll("input[type='checkbox']").forEach(cb => cb.checked = false);
|
| 821 |
+
|
| 822 |
+
// check current
|
| 823 |
+
const mine = box.querySelector("input[type='checkbox']");
|
| 824 |
+
mine.checked = true;
|
| 825 |
+
|
| 826 |
+
convertNow();
|
| 827 |
+
});
|
| 828 |
+
});
|
| 829 |
+
}
|
| 830 |
+
|
| 831 |
+
// ===== direction toggle =====
|
| 832 |
+
function setDirection(dir){
|
| 833 |
+
direction = dir;
|
| 834 |
+
if(dir === "forward"){
|
| 835 |
+
dirForward.classList.add("active");
|
| 836 |
+
dirReverse.classList.remove("active");
|
| 837 |
+
}else{
|
| 838 |
+
dirReverse.classList.add("active");
|
| 839 |
+
dirForward.classList.remove("active");
|
| 840 |
+
}
|
| 841 |
+
convertNow();
|
| 842 |
+
}
|
| 843 |
+
|
| 844 |
+
dirForward.addEventListener("click", ()=>setDirection("forward"));
|
| 845 |
+
dirReverse.addEventListener("click", ()=>setDirection("reverse"));
|
| 846 |
+
|
| 847 |
+
// ===== events =====
|
| 848 |
+
inputText.addEventListener("input", convertNow);
|
| 849 |
+
|
| 850 |
+
btnClear.addEventListener("click", ()=>{
|
| 851 |
+
inputText.value = "";
|
| 852 |
+
outputText.value = "";
|
| 853 |
+
inputText.focus();
|
| 854 |
+
showToast("Đã xoá");
|
| 855 |
+
});
|
| 856 |
+
|
| 857 |
+
btnSwap.addEventListener("click", ()=>{
|
| 858 |
+
const a = inputText.value;
|
| 859 |
+
const b = outputText.value;
|
| 860 |
+
inputText.value = b;
|
| 861 |
+
outputText.value = a;
|
| 862 |
+
showToast("Đã swap");
|
| 863 |
+
convertNow();
|
| 864 |
+
});
|
| 865 |
+
|
| 866 |
+
btnCopy.addEventListener("click", async ()=>{
|
| 867 |
+
const text = outputText.value || "";
|
| 868 |
+
if(!text.trim()){ showToast("Chưa có nội dung để copy"); return; }
|
| 869 |
+
|
| 870 |
+
try{
|
| 871 |
+
await navigator.clipboard.writeText(text);
|
| 872 |
+
showToast("Đã copy!");
|
| 873 |
+
}catch(_){
|
| 874 |
+
// fallback
|
| 875 |
+
outputText.focus();
|
| 876 |
+
outputText.select();
|
| 877 |
+
try{
|
| 878 |
+
const ok = document.execCommand("copy");
|
| 879 |
+
showToast(ok ? "Đã copy!" : "Copy bị chặn");
|
| 880 |
+
}catch(e){
|
| 881 |
+
showToast("Copy bị chặn");
|
| 882 |
+
}
|
| 883 |
+
outputText.setSelectionRange(0,0);
|
| 884 |
+
}
|
| 885 |
+
});
|
| 886 |
+
|
| 887 |
+
// ===== init =====
|
| 888 |
+
renderModes();
|
| 889 |
+
convertNow();
|
| 890 |
+
</script>
|
| 891 |
+
</body>
|
| 892 |
</html>
|