CVNSS commited on
Commit
d232838
·
verified ·
1 Parent(s): 1573acd

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +890 -17
index.html CHANGED
@@ -1,19 +1,892 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ { "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;" }[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>