Download index.html from CVNSS/congthuc: direct link, hf CLI and curl.
- Browser
- Download file 81 kB
-
https://huggingface.co/spaces/CVNSS/congthuc/resolve/main/index.html
- Command line
-
hf download hf://spaces/CVNSS/congthuc/index.html
-
curl -L -o index.html https://huggingface.co/spaces/CVNSS/congthuc/resolve/main/index.html
81 kB
| <html lang="vi"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>CVNSS 4.0 — Bộ Chuyển Đổi Tốc Ký Chữ Việt</title> | |
| <style> | |
| /* ── OFFLINE SYSTEM FONTS (no internet needed) ── */ | |
| @font-face { | |
| font-family: 'SysMono'; | |
| src: local('Courier New'), local('Courier'), local('Liberation Mono'), local('DejaVu Sans Mono'); | |
| } | |
| @font-face { | |
| font-family: 'SysSans'; | |
| src: local('Segoe UI'), local('Ubuntu'), local('Helvetica Neue'), local('Arial'); | |
| } | |
| :root { | |
| --bg:#f3f5f9;--surface:#fff;--s2:#edf0f6;--s3:#e4e9f2; | |
| --bd:#c8d4e4;--bd2:#aabcd0; | |
| --tx:#14202e;--mu:#5e7288;--fa:#8fa5bc; | |
| --l1:#0058c0;--l1bg:#e3edfb;--l1bd:#7daae8; | |
| --l2:#b03600;--l2bg:#fdeae0;--l2bd:#e8a07a; | |
| --l3:#14683a;--l3bg:#e2f4ea;--l3bd:#7ac898; | |
| --l4:#6418b0;--l4bg:#f1e4fc;--l4bd:#ba88e8; | |
| --ok:#008840;--acc:#004ab0; | |
| /* OUTPUT NODE: red bg gold text */ | |
| --out-bg:#cc1111;--out-txt:#ffd700; | |
| } | |
| *{margin:0;padding:0;box-sizing:border-box} | |
| html{scroll-behavior:smooth} | |
| body{ | |
| background:var(--bg);color:var(--tx); | |
| font-family:'SysSans','Segoe UI',Ubuntu,'Helvetica Neue',Arial,sans-serif; | |
| font-size:14px;min-height:100vh; | |
| background-image:linear-gradient(rgba(140,165,195,.11) 1px,transparent 1px), | |
| linear-gradient(90deg,rgba(140,165,195,.11) 1px,transparent 1px); | |
| background-size:22px 22px; | |
| } | |
| code,pre,.mono{font-family:'SysMono','Courier New',Courier,monospace} | |
| /* ══ HEADER ══ */ | |
| .hdr{background:var(--surface);border-bottom:2px solid var(--bd);padding:13px 26px; | |
| display:flex;align-items:center;justify-content:space-between;position:relative} | |
| .hdr::before{content:'';position:absolute;top:0;left:0;right:0;height:3px; | |
| background:linear-gradient(90deg,var(--l1),var(--l2),var(--l3),var(--l4))} | |
| .hdr-l h1{font-size:18px;font-weight:800;letter-spacing:-.3px} | |
| .hdr-l h1 em{color:var(--acc);font-style:normal} | |
| .hdr-l p{font-size:11px;color:var(--mu);margin-top:2px;font-family:'SysMono',monospace} | |
| .chips{display:flex;gap:4px;flex-wrap:wrap} | |
| .chip{font-family:'SysMono',monospace;font-size:9px;font-weight:700; | |
| padding:3px 8px;border-radius:3px;letter-spacing:1px;text-transform:uppercase;border:1px solid} | |
| .c1{background:var(--l1bg);color:var(--l1);border-color:var(--l1bd)} | |
| .c2{background:var(--l2bg);color:var(--l2);border-color:var(--l2bd)} | |
| .c3{background:var(--l3bg);color:var(--l3);border-color:var(--l3bd)} | |
| .c4{background:var(--l4bg);color:var(--l4);border-color:var(--l4bd)} | |
| /* ══ WRAP ══ */ | |
| .wrap{max-width:1260px;margin:0 auto;padding:18px 18px 0} | |
| /* ══ CONVERTER CARD ══ */ | |
| .cvt-card{background:var(--surface);border:1.5px solid var(--bd2);border-radius:8px; | |
| padding:16px 20px 14px;margin-bottom:20px;box-shadow:0 2px 8px rgba(0,30,90,.06);position:relative} | |
| .cvt-card::after{content:'IC-CONVERTER';position:absolute;top:-10px;right:16px; | |
| font-family:'SysMono',monospace;font-size:9px;color:var(--mu);background:var(--surface); | |
| padding:0 8px;letter-spacing:1.5px;border:1px solid var(--bd);border-radius:3px} | |
| .mode-bar{display:flex;gap:3px;margin-bottom:12px;background:var(--s2); | |
| border:1px solid var(--bd);padding:3px;border-radius:6px;width:fit-content} | |
| .mtab{padding:5px 14px;border:none;border-radius:4px;font-family:'SysMono',monospace; | |
| font-size:11px;font-weight:700;cursor:pointer;transition:all .15s;background:none; | |
| color:var(--mu);letter-spacing:.5px} | |
| .mtab.on{background:var(--surface);color:var(--acc); | |
| box-shadow:0 1px 4px rgba(0,30,90,.12);border:1px solid var(--bd)} | |
| .cvt-row{display:grid;grid-template-columns:1fr 110px 1fr;gap:12px;align-items:end} | |
| .fld label{display:block;font-size:9px;font-weight:700;letter-spacing:1.5px; | |
| text-transform:uppercase;color:var(--mu);margin-bottom:5px;font-family:'SysMono',monospace} | |
| .fld input{width:100%;padding:9px 12px;border:1.5px solid var(--bd);border-radius:5px; | |
| font-family:'SysMono',monospace;font-size:17px;font-weight:600;color:var(--tx); | |
| background:var(--surface);outline:none;transition:border-color .2s,box-shadow .2s} | |
| .fld input:focus{border-color:var(--acc);box-shadow:0 0 0 3px rgba(0,74,176,.1)} | |
| .cvt-mid{display:flex;flex-direction:column;align-items:center;gap:6px;padding-bottom:2px} | |
| .btn{background:var(--acc);color:#fff;border:none;padding:10px 16px; | |
| font-family:'SysMono',monospace;font-weight:700;font-size:11px;letter-spacing:1px; | |
| border-radius:5px;cursor:pointer;transition:background .2s,transform .1s; | |
| text-transform:uppercase;white-space:nowrap} | |
| .btn:hover{background:#003090}.btn:active{transform:scale(.97)} | |
| /* ── OUTPUT BOX: red bg, gold text ── */ | |
| .out-box{ | |
| background:var(--out-bg);border:2px solid #990a0a;border-radius:5px; | |
| padding:9px 13px;font-family:'SysMono',monospace;font-size:22px;font-weight:800; | |
| color:var(--out-txt);min-height:46px;display:flex;align-items:center; | |
| letter-spacing:3px;position:relative;overflow:hidden;transition:all .3s; | |
| box-shadow:0 2px 10px rgba(180,0,0,.2);text-shadow:0 1px 3px rgba(0,0,0,.4) | |
| } | |
| .out-box::before{content:'';position:absolute;left:0;top:0;width:4px;height:100%;background:#ffd700} | |
| .ph{color:rgba(255,215,0,.45);font-size:13px;font-weight:400;letter-spacing:0;text-shadow:none} | |
| /* ══ SIGNAL FLOW ══ */ | |
| .ftop{display:flex;flex-direction:column;align-items:center} | |
| .node-in{background:var(--surface);border:2px solid var(--bd2);border-radius:5px; | |
| padding:8px 34px;font-weight:700;font-size:12px;letter-spacing:2px;text-transform:uppercase; | |
| font-family:'SysMono',monospace;box-shadow:3px 3px 0 var(--bd);transition:all .3s;text-align:center} | |
| .node-in.on{border-color:var(--acc);box-shadow:3px 3px 0 var(--l1bd)} | |
| .node-in small{display:block;font-size:9px;letter-spacing:1.5px;color:var(--mu);font-weight:400;margin-bottom:2px} | |
| .node-val{font-size:20px;color:var(--acc);letter-spacing:3px;min-height:24px} | |
| /* BUS */ | |
| .bus-wrap{height:34px;position:relative;overflow:visible;margin:0} | |
| .bus-h{position:absolute;top:3px;left:12.5%;width:75%;height:1.5px; | |
| background:linear-gradient(90deg,var(--l1),var(--l2),var(--l3),var(--l4));opacity:.4;transition:opacity .3s} | |
| .bus-h.on{opacity:1} | |
| .vias{position:absolute;top:-3px;left:12.5%;width:75%;display:flex;justify-content:space-around} | |
| .via{width:8px;height:8px;border-radius:50%;border:2px solid;background:var(--surface);transition:all .3s} | |
| .v1{border-color:var(--l1bd)}.v2{border-color:var(--l2bd)} | |
| .v3{border-color:var(--l3bd)}.v4{border-color:var(--l4bd)} | |
| .v1.on{background:var(--l1);border-color:var(--l1)} | |
| .v2.on{background:var(--l2);border-color:var(--l2)} | |
| .v3.on{background:var(--l3);border-color:var(--l3)} | |
| .v4.on{background:var(--l4);border-color:var(--l4)} | |
| .vert-wires{position:absolute;top:7px;left:12.5%;width:75%;display:flex;justify-content:space-around;height:28px} | |
| .vw{width:2px;height:100%} | |
| .vw1{background:linear-gradient(180deg,var(--l1),rgba(0,88,192,.15))} | |
| .vw2{background:linear-gradient(180deg,var(--l2),rgba(176,54,0,.15))} | |
| .vw3{background:linear-gradient(180deg,var(--l3),rgba(20,104,58,.15))} | |
| .vw4{background:linear-gradient(180deg,var(--l4),rgba(100,24,176,.15))} | |
| /* ══ LANES ══ */ | |
| .lanes{display:grid;grid-template-columns:repeat(4,1fr);gap:14px} | |
| .lc{border-radius:6px;border:1.5px solid;background:var(--surface); | |
| box-shadow:0 2px 6px rgba(0,0,0,.04);overflow:hidden;transition:box-shadow .2s} | |
| .lc:hover{box-shadow:0 4px 14px rgba(0,0,0,.08)} | |
| .l1c{border-color:var(--l1bd)}.l2c{border-color:var(--l2bd)} | |
| .l3c{border-color:var(--l3bd)}.l4c{border-color:var(--l4bd)} | |
| .lhead{padding:8px 12px;font-family:'SysMono',monospace;font-size:10px;font-weight:700; | |
| letter-spacing:1.5px;text-transform:uppercase;border-bottom:1px solid; | |
| display:flex;align-items:center;justify-content:space-between} | |
| .l1c .lhead{background:var(--l1bg);color:var(--l1);border-color:var(--l1bd)} | |
| .l2c .lhead{background:var(--l2bg);color:var(--l2);border-color:var(--l2bd)} | |
| .l3c .lhead{background:var(--l3bg);color:var(--l3);border-color:var(--l3bd)} | |
| .l4c .lhead{background:var(--l4bg);color:var(--l4);border-color:var(--l4bd)} | |
| .rbadge{font-family:'SysMono',monospace;font-size:12px;font-weight:700; | |
| padding:2px 8px;border-radius:3px;min-width:50px;text-align:center; | |
| border:1px solid;background:var(--surface);letter-spacing:1px;transition:all .3s} | |
| .l1c .rbadge{border-color:var(--l1bd);color:var(--l1)} | |
| .l2c .rbadge{border-color:var(--l2bd);color:var(--l2)} | |
| .l3c .rbadge{border-color:var(--l3bd);color:var(--l3)} | |
| .l4c .rbadge{border-color:var(--l4bd);color:var(--l4)} | |
| .rbadge.lit{color:#fff!important;border-color:transparent!important} | |
| .l1c .rbadge.lit{background:var(--l1)}.l2c .rbadge.lit{background:var(--l2)} | |
| .l3c .rbadge.lit{background:var(--l3)}.l4c .rbadge.lit{background:var(--l4)} | |
| .lproc{margin:7px 11px 0;padding:6px 10px;background:var(--s2);border:1px solid var(--bd); | |
| border-radius:4px;font-family:'SysMono',monospace;font-size:11px; | |
| display:none;align-items:center;gap:7px} | |
| .lproc.show{display:flex} | |
| .lp-in{color:var(--mu)}.lp-arr{color:var(--fa);font-size:10px} | |
| .lp-out{font-weight:700;font-size:13px} | |
| .l1c .lp-out{color:var(--l1)}.l2c .lp-out{color:var(--l2)} | |
| .l3c .lp-out{color:var(--l3)}.l4c .lp-out{color:var(--l4)} | |
| .lbody{padding:11px} | |
| .rsect{margin-bottom:9px}.rsect:last-child{margin-bottom:0} | |
| .rtitle{font-family:'SysMono',monospace;font-size:9px;font-weight:700; | |
| letter-spacing:1.5px;text-transform:uppercase;color:var(--fa); | |
| margin-bottom:4px;padding-bottom:3px;border-bottom:1px dashed var(--bd)} | |
| .mtags{display:flex;flex-wrap:wrap;gap:3px;margin-top:3px} | |
| .mt{font-family:'SysMono',monospace;font-size:11px;padding:2px 6px; | |
| border:1px solid var(--bd);border-radius:3px;background:var(--s2); | |
| white-space:nowrap;transition:all .22s;cursor:default} | |
| .mt .f{color:var(--mu)}.mt .a{color:var(--fa);margin:0 2px}.mt .t{font-weight:700} | |
| .l1c .mt .t{color:var(--l1)}.l2c .mt .t{color:var(--l2)} | |
| .l3c .mt .t{color:var(--l3)}.l4c .mt .t{color:var(--l4)} | |
| .mt.hi{border-color:transparent!important;color:#fff!important} | |
| .l1c .mt.hi{background:var(--l1)}.l2c .mt.hi{background:var(--l2)} | |
| .l3c .mt.hi{background:var(--l3)}.l4c .mt.hi{background:var(--l4)} | |
| .mt.hi .f,.mt.hi .a{color:rgba(255,255,255,.6)!important} | |
| .mt.hi .t{color:#fff!important} | |
| .rlist{list-style:none} | |
| .rlist li{display:flex;gap:5px;align-items:flex-start;padding:2px 0;font-size:12px} | |
| .rlist li .dot{width:5px;height:5px;border-radius:50%;margin-top:5px;flex-shrink:0} | |
| .l1c .dot{background:var(--l1)}.l2c .dot{background:var(--l2)} | |
| .l3c .dot{background:var(--l3)}.l4c .dot{background:var(--l4)} | |
| /* ══ OUTPUT NODE (red/gold) ══ */ | |
| .merge-area{margin-top:0} | |
| .mwires{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;height:28px} | |
| .mw{width:2px;height:100%;margin:0 auto;position:relative} | |
| .mw::after{content:'';position:absolute;bottom:-4px;left:50%;transform:translateX(-50%); | |
| width:8px;height:8px;border-radius:50%;border:2px solid var(--bd2);background:var(--surface)} | |
| .mw1{background:linear-gradient(180deg,var(--l1),#999)}.mw1::after{border-color:var(--l1)} | |
| .mw2{background:linear-gradient(180deg,var(--l2),#999)}.mw2::after{border-color:var(--l2)} | |
| .mw3{background:linear-gradient(180deg,var(--l3),#999)}.mw3::after{border-color:var(--l3)} | |
| .mw4{background:linear-gradient(180deg,var(--l4),#999)}.mw4::after{border-color:var(--l4)} | |
| .mnode{ | |
| background:var(--out-bg);border:2px solid #880808;border-radius:8px; | |
| padding:14px 22px;text-align:center; | |
| box-shadow:0 4px 20px rgba(150,0,0,.25),inset 0 1px 0 rgba(255,215,0,.15); | |
| position:relative | |
| } | |
| .mnode::before{content:'OUTPUT NODE';position:absolute;top:-10px;left:50%;transform:translateX(-50%); | |
| font-family:'SysMono',monospace;font-size:9px;color:var(--out-txt);background:var(--out-bg); | |
| padding:0 10px;letter-spacing:2px;border:1px solid #cc6600;border-radius:3px} | |
| .mformula{font-family:'SysMono',monospace;font-size:11px;color:rgba(255,215,0,.7); | |
| margin-bottom:7px;letter-spacing:.5px;line-height:1.5} | |
| .mformula b.s1{color:#7be0ff}.mformula b.s2{color:#ffaa77} | |
| .mformula b.s3{color:#88ee99}.mformula b.s4{color:#cc99ff} | |
| .mout{font-family:'SysMono',monospace;font-size:30px;font-weight:800; | |
| color:rgba(255,215,0,.4);letter-spacing:5px;min-height:36px;transition:all .3s; | |
| text-shadow:0 0 0 transparent} | |
| .mout.res{color:var(--out-txt);text-shadow:0 2px 8px rgba(255,200,0,.5)} | |
| .msub{font-size:11px;color:rgba(255,215,0,.55);margin-top:5px; | |
| font-family:'SysMono',monospace;letter-spacing:.5px} | |
| /* ══ BOTTOM REFERENCE SECTION ══ */ | |
| .bot{display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px;margin-top:20px} | |
| /* Scrollable reference card */ | |
| .ref-card{background:var(--surface);border:1.5px solid var(--bd); | |
| border-radius:6px;overflow:hidden;display:flex;flex-direction:column} | |
| .ref-head{padding:9px 14px;font-family:'SysMono',monospace;font-size:9px;font-weight:700; | |
| letter-spacing:2px;text-transform:uppercase;background:var(--s2); | |
| border-bottom:1px solid var(--bd);color:var(--mu); | |
| display:flex;align-items:center;justify-content:space-between;gap:8px;flex-shrink:0} | |
| .ref-head .dot{width:6px;height:6px;border-radius:50%;flex-shrink:0} | |
| .ref-head-left{display:flex;align-items:center;gap:8px} | |
| .ref-scroll{overflow-y:auto;max-height:300px;padding:12px 14px;flex:1} | |
| .ref-scroll::-webkit-scrollbar{width:5px} | |
| .ref-scroll::-webkit-scrollbar-track{background:var(--s2)} | |
| .ref-scroll::-webkit-scrollbar-thumb{background:var(--bd2);border-radius:3px} | |
| /* KHD table */ | |
| .khd-g{display:grid;grid-template-columns:repeat(3,1fr);gap:7px} | |
| .khd-grp{padding:7px;border-radius:4px;border:1px solid var(--bd)} | |
| .khd-gt{font-family:'SysMono',monospace;font-size:8px;font-weight:700; | |
| letter-spacing:1px;text-transform:uppercase;color:var(--mu);margin-bottom:4px} | |
| .khd-row{display:flex;justify-content:space-between;align-items:center; | |
| padding:2px 0;font-family:'SysMono',monospace;font-size:11px} | |
| .khd-tn{color:var(--mu)}.khd-sym{font-weight:700;font-size:13px; | |
| padding:1px 5px;border-radius:2px;border:1px solid var(--bd);background:var(--s2)} | |
| .grp-non .khd-sym{color:var(--l1);background:var(--l1bg);border-color:var(--l1bd)} | |
| .grp-trang .khd-sym{color:var(--l3);background:var(--l3bg);border-color:var(--l3bd)} | |
| .grp-plain .khd-sym{color:var(--l4);background:var(--l4bg);border-color:var(--l4bd)} | |
| .grp-ngang .khd-sym{color:var(--l2);background:var(--l2bg);border-color:var(--l2bd)} | |
| /* 52 Rules list */ | |
| .rule-group{margin-bottom:12px} | |
| .rule-group:last-child{margin-bottom:0} | |
| .rg-title{font-family:'SysMono',monospace;font-size:9px;font-weight:700; | |
| letter-spacing:1px;text-transform:uppercase;color:var(--acc); | |
| margin-bottom:6px;padding:3px 6px;background:var(--l1bg); | |
| border-radius:3px;border-left:3px solid var(--l1);display:flex;align-items:center;justify-content:space-between} | |
| .rg-title .cnt{font-size:8px;background:var(--l1);color:#fff;padding:1px 5px;border-radius:10px} | |
| .rule-row{display:flex;align-items:center;gap:5px;padding:3px 4px; | |
| border-bottom:1px solid var(--s3);font-family:'SysMono',monospace;font-size:11px; | |
| cursor:pointer;border-radius:3px;transition:background .15s} | |
| .rule-row:last-child{border-bottom:none} | |
| .rule-row:hover{background:var(--s2)} | |
| .rule-row .rnum{color:var(--fa);font-size:9px;min-width:18px} | |
| .rule-row .rfrom{color:var(--mu)} | |
| .rule-row .rarr{color:var(--fa);margin:0 3px;font-size:10px} | |
| .rule-row .rto{font-weight:700;color:var(--acc)} | |
| .rule-row .rinfo{margin-left:auto;font-size:9px;color:var(--fa)} | |
| .rule-row .popup-btn{ | |
| margin-left:auto;font-size:8px;padding:1px 5px;background:var(--s2); | |
| border:1px solid var(--bd);border-radius:2px;cursor:pointer;color:var(--mu); | |
| transition:all .15s;white-space:nowrap;font-family:'SysMono',monospace} | |
| .rule-row:hover .popup-btn{background:var(--acc);color:#fff;border-color:var(--acc)} | |
| /* Examples grid */ | |
| .ex-g{display:grid;grid-template-columns:repeat(2,1fr);gap:4px} | |
| .ex-i{display:flex;align-items:center;gap:6px;padding:6px 9px;border:1px solid var(--bd); | |
| border-radius:4px;background:var(--s2);font-family:'SysMono',monospace; | |
| cursor:pointer;transition:all .15s} | |
| .ex-i:hover{border-color:var(--acc);background:var(--l1bg)} | |
| .ex-f{font-size:13px;color:var(--tx)}.ex-a{color:var(--bd2);font-size:10px} | |
| .ex-t{font-size:13px;font-weight:700;color:var(--acc)} | |
| /* ══ POPUP MODAL ══ */ | |
| .popup-overlay{position:fixed;inset:0;background:rgba(0,20,60,.45); | |
| display:none;align-items:center;justify-content:center;z-index:1000; | |
| backdrop-filter:blur(3px);padding:20px} | |
| .popup-overlay.open{display:flex} | |
| .popup-box{background:var(--surface);border:1.5px solid var(--bd2);border-radius:10px; | |
| max-width:540px;width:100%;max-height:80vh;overflow:hidden; | |
| box-shadow:0 8px 40px rgba(0,20,80,.2);display:flex;flex-direction:column; | |
| animation:popIn .2s ease} | |
| @keyframes popIn{from{opacity:0;transform:scale(.93) translateY(8px)}to{opacity:1;transform:none}} | |
| .popup-head{padding:14px 18px;border-bottom:1px solid var(--bd);display:flex; | |
| align-items:center;justify-content:space-between;background:var(--s2);flex-shrink:0} | |
| .popup-head h3{font-size:14px;font-weight:700;font-family:'SysMono',monospace; | |
| color:var(--acc);letter-spacing:1px} | |
| .popup-close{background:none;border:1.5px solid var(--bd);border-radius:4px; | |
| width:28px;height:28px;cursor:pointer;font-size:16px;color:var(--mu); | |
| display:flex;align-items:center;justify-content:center;transition:all .15s; | |
| font-family:'SysMono',monospace} | |
| .popup-close:hover{background:var(--l2bg);border-color:var(--l2);color:var(--l2)} | |
| .popup-body{padding:18px;overflow-y:auto;flex:1} | |
| .popup-rule-title{font-size:18px;font-weight:800;font-family:'SysMono',monospace; | |
| color:var(--tx);margin-bottom:6px;letter-spacing:2px} | |
| .popup-rule-title span{color:var(--acc)} | |
| .popup-desc{font-size:13px;color:var(--mu);margin-bottom:14px;line-height:1.6} | |
| .popup-examples{display:flex;flex-direction:column;gap:5px} | |
| .popup-ex-row{display:flex;align-items:center;gap:10px;padding:8px 12px; | |
| background:var(--s2);border-radius:5px;font-family:'SysMono',monospace;font-size:13px; | |
| border:1px solid var(--bd)} | |
| .popup-ex-row .pe-cqn{color:var(--tx);min-width:80px} | |
| .popup-ex-row .pe-arr{color:var(--fa)} | |
| .popup-ex-row .pe-cvn{color:var(--l3);font-weight:700;min-width:80px} | |
| .popup-ex-row .pe-arr2{color:var(--fa)} | |
| .popup-ex-row .pe-cvss{color:var(--acc);font-weight:800;font-size:15px} | |
| .popup-tag{display:inline-block;padding:2px 8px;border-radius:3px;font-size:10px; | |
| font-family:'SysMono',monospace;font-weight:700;letter-spacing:1px;text-transform:uppercase;margin-top:8px} | |
| .pt-l1{background:var(--l1bg);color:var(--l1);border:1px solid var(--l1bd)} | |
| .pt-l2{background:var(--l2bg);color:var(--l2);border:1px solid var(--l2bd)} | |
| .pt-l3{background:var(--l3bg);color:var(--l3);border:1px solid var(--l3bd)} | |
| .pt-l4{background:var(--l4bg);color:var(--l4);border:1px solid var(--l4bd)} | |
| /* ══ FOOTER ══ */ | |
| .footer{text-align:center;padding:16px 20px;font-size:12px;color:var(--mu); | |
| border-top:1px solid var(--bd);margin-top:24px;font-family:'SysMono',monospace; | |
| background:var(--surface);letter-spacing:.5px} | |
| .footer strong{color:var(--acc)} | |
| /* ══ ANIM ══ */ | |
| @keyframes slideIn{from{opacity:0;transform:translateY(-3px)}to{opacity:1;transform:none}} | |
| .anim{animation:slideIn .3s ease} | |
| /* ══ RESPONSIVE ══ */ | |
| @media(max-width:980px){.bot{grid-template-columns:1fr 1fr}} | |
| @media(max-width:920px){.lanes{grid-template-columns:repeat(2,1fr)}} | |
| @media(max-width:640px){ | |
| .bot{grid-template-columns:1fr}.cvt-row{grid-template-columns:1fr} | |
| .khd-g{grid-template-columns:1fr 1fr}.hdr{flex-direction:column;gap:8px} | |
| .lanes{grid-template-columns:1fr} | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- ══ HEADER ══ --> | |
| <div class="hdr"> | |
| <div class="hdr-l"> | |
| <h1>CHỮ <em>VN SONG SONG</em> 4.0</h1> | |
| <p class="mono">Bộ chuyển đổi tốc ký · CQN ⟷ CVN ⟷ CVNSS4.0 · BQ 1850/2020/QTG · Offline Ready</p> | |
| </div> | |
| <div class="chips"> | |
| <div class="chip c1">LÀN 1: VẦN</div> | |
| <div class="chip c2">LÀN 2: DẤU</div> | |
| <div class="chip c3">LÀN 3: CHỮ</div> | |
| <div class="chip c4">LÀN 4: P</div> | |
| </div> | |
| </div> | |
| <div class="wrap"> | |
| <!-- ══ CONVERTER ══ --> | |
| <div class="cvt-card"> | |
| <div class="mode-bar"> | |
| <button class="mtab on" data-mode="cqn" onclick="setMode(this)">CQN → CVNSS</button> | |
| <button class="mtab" data-mode="cvss" onclick="setMode(this)">CVNSS → CQN</button> | |
| <button class="mtab" data-mode="cvn" onclick="setMode(this)">CVN → CVNSS</button> | |
| </div> | |
| <div class="cvt-row"> | |
| <div class="fld"> | |
| <label id="lbl-in">Nhập CQN (Chữ Quốc Ngữ)</label> | |
| <input id="inp" type="text" placeholder="VD: nghiêng, tuyết, đường..." autocomplete="off"> | |
| </div> | |
| <div class="cvt-mid"> | |
| <button class="btn" id="btnConv">▶ CHUYỂN</button> | |
| </div> | |
| <div class="fld"> | |
| <label id="lbl-out">Kết quả CVNSS 4.0</label> | |
| <div class="out-box" id="outBox"><span class="ph">Kết quả hiển thị ở đây...</span></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ══ SIGNAL TOP NODE ══ --> | |
| <div class="ftop"> | |
| <div class="node-in" id="nodeIn"> | |
| <small class="mono">INPUT SIGNAL</small> | |
| <div class="node-val mono" id="nodeVal">—</div> | |
| </div> | |
| </div> | |
| <!-- BUS LINE --> | |
| <div class="bus-wrap"> | |
| <div class="bus-h" id="busH"></div> | |
| <div class="vias"> | |
| <div class="via v1" id="v1"></div><div class="via v2" id="v2"></div> | |
| <div class="via v3" id="v3"></div><div class="via v4" id="v4"></div> | |
| </div> | |
| <div class="vert-wires"> | |
| <div class="vw vw1"></div><div class="vw vw2"></div> | |
| <div class="vw vw3"></div><div class="vw vw4"></div> | |
| </div> | |
| </div> | |
| <!-- ══ 4 LANES ══ --> | |
| <div class="lanes"> | |
| <!-- L1 --> | |
| <div class="lc l1c"> | |
| <div class="lhead"><span>LÀN 1 · VẦN</span><span class="rbadge" id="r1">—</span></div> | |
| <div class="lproc" id="pr1"><span class="lp-in mono" id="p1i"></span><span class="lp-arr">→</span><span class="lp-out mono" id="p1o"></span></div> | |
| <div class="lbody"> | |
| <div class="rsect"> | |
| <div class="rtitle">Rút NAG còn 1 nguyên âm</div> | |
| <div class="mtags"> | |
| <div class="mt"><span class="f">uyê</span><span class="a">→</span><span class="t">Y</span></div> | |
| <div class="mt"><span class="f">iê/yê</span><span class="a">→</span><span class="t">I</span></div> | |
| <div class="mt"><span class="f">uô</span><span class="a">→</span><span class="t">U</span></div> | |
| <div class="mt"><span class="f">ươ</span><span class="a">→</span><span class="t">Ư</span></div> | |
| <div class="mt"><span class="f">uâ</span><span class="a">→</span><span class="t">Â</span></div> | |
| <div class="mt"><span class="f">uơ</span><span class="a">→</span><span class="t">Ơ</span></div> | |
| <div class="mt"><span class="f">oă</span><span class="a">→</span><span class="t">Ă</span></div> | |
| <div class="mt"><span class="f">oe</span><span class="a">→</span><span class="t">E</span></div> | |
| <div class="mt"><span class="f">oa</span><span class="a">→</span><span class="t">O/A</span></div> | |
| </div> | |
| </div> | |
| <div class="rsect"> | |
| <div class="rtitle">Đổi âm cuối (vần ghép)</div> | |
| <div class="mtags"> | |
| <div class="mt"><span class="f">t</span><span class="a">→</span><span class="t">d</span></div> | |
| <div class="mt"><span class="f">p</span><span class="a">→</span><span class="t">f</span></div> | |
| <div class="mt"><span class="f">c</span><span class="a">→</span><span class="t">s</span></div> | |
| <div class="mt"><span class="f">n</span><span class="a">→</span><span class="t">l</span></div> | |
| <div class="mt"><span class="f">m</span><span class="a">→</span><span class="t">v</span></div> | |
| <div class="mt"><span class="f">ng</span><span class="a">→</span><span class="t">z</span></div> | |
| <div class="mt"><span class="f">o/u</span><span class="a">→</span><span class="t">w</span></div> | |
| <div class="mt"><span class="f">i/y</span><span class="a">→</span><span class="t">j</span></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- L2 --> | |
| <div class="lc l2c"> | |
| <div class="lhead"><span>LÀN 2 · DẤU THANH</span><span class="rbadge" id="r2">—</span></div> | |
| <div class="lproc" id="pr2"><span class="lp-in mono" id="p2i"></span><span class="lp-arr">→</span><span class="lp-out mono" id="p2o"></span></div> | |
| <div class="lbody"> | |
| <div class="rsect"> | |
| <div class="rtitle">Nón ^ (â ê ô)</div> | |
| <div class="mtags"> | |
| <div class="mt" id="k-B"><span class="f">sắc+^</span><span class="a">=</span><span class="t">B</span></div> | |
| <div class="mt" id="k-D"><span class="f">huyền+^</span><span class="a">=</span><span class="t">D</span></div> | |
| <div class="mt" id="k-Q"><span class="f">hỏi+^</span><span class="a">=</span><span class="t">Q</span></div> | |
| <div class="mt" id="k-G"><span class="f">ngã+^</span><span class="a">=</span><span class="t">G</span></div> | |
| <div class="mt" id="k-F"><span class="f">nặng+^</span><span class="a">=</span><span class="t">F</span></div> | |
| <div class="mt" id="k-Y"><span class="f">ngang+^</span><span class="a">=</span><span class="t">Y</span></div> | |
| </div> | |
| </div> | |
| <div class="rsect"> | |
| <div class="rtitle">Trăng/Móc (ă ơ ư)</div> | |
| <div class="mtags"> | |
| <div class="mt" id="k-X"><span class="f">sắc+ˀ</span><span class="a">=</span><span class="t">X</span></div> | |
| <div class="mt" id="k-K"><span class="f">huyền+ˀ</span><span class="a">=</span><span class="t">K</span></div> | |
| <div class="mt" id="k-V"><span class="f">hỏi+ˀ</span><span class="a">=</span><span class="t">V</span></div> | |
| <div class="mt" id="k-W"><span class="f">ngã+ˀ</span><span class="a">=</span><span class="t">W</span></div> | |
| <div class="mt" id="k-H"><span class="f">nặng+ˀ</span><span class="a">=</span><span class="t">H</span></div> | |
| <div class="mt" id="k-O"><span class="f">ngang+ˀ</span><span class="a">=</span><span class="t">O</span></div> | |
| </div> | |
| </div> | |
| <div class="rsect"> | |
| <div class="rtitle">Không dấu phụ</div> | |
| <div class="mtags"> | |
| <div class="mt" id="k-J"><span class="f">sắc</span><span class="a">=</span><span class="t">J</span></div> | |
| <div class="mt" id="k-L"><span class="f">huyền</span><span class="a">=</span><span class="t">L</span></div> | |
| <div class="mt" id="k-Z"><span class="f">hỏi</span><span class="a">=</span><span class="t">Z</span></div> | |
| <div class="mt" id="k-S"><span class="f">ngã</span><span class="a">=</span><span class="t">S</span></div> | |
| <div class="mt" id="k-R"><span class="f">nặng</span><span class="a">=</span><span class="t">R</span></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- L3 --> | |
| <div class="lc l3c"> | |
| <div class="lhead"><span>LÀN 3 · PHỤ ÂM</span><span class="rbadge" id="r3">—</span></div> | |
| <div class="lproc" id="pr3"><span class="lp-in mono" id="p3i"></span><span class="lp-arr">→</span><span class="lp-out mono" id="p3o"></span></div> | |
| <div class="lbody"> | |
| <div class="rsect"> | |
| <div class="rtitle">Đổi phụ âm đầu</div> | |
| <div class="mtags" id="tags-dau"> | |
| <div class="mt" data-cqn="ph" data-cvn="f"><span class="f">ph</span><span class="a">→</span><span class="t">F</span></div> | |
| <div class="mt" data-cqn="qu" data-cvn="q"><span class="f">qu</span><span class="a">→</span><span class="t">Q</span></div> | |
| <div class="mt" data-cqn="k" data-cvn="c"><span class="f">k</span><span class="a">→</span><span class="t">C</span></div> | |
| <div class="mt" data-cqn="kh" data-cvn="k"><span class="f">kh</span><span class="a">→</span><span class="t">K</span></div> | |
| <div class="mt" data-cqn="dz" data-cvn="z"><span class="f">d</span><span class="a">→</span><span class="t">Z</span></div> | |
| <div class="mt" data-cqn="đ" data-cvn="d"><span class="f">đ</span><span class="a">→</span><span class="t">D</span></div> | |
| <div class="mt" data-cqn="gi" data-cvn="j"><span class="f">gi</span><span class="a">→</span><span class="t">J</span></div> | |
| <div class="mt" data-cqn="gh" data-cvn="g"><span class="f">gh</span><span class="a">→</span><span class="t">G</span></div> | |
| <div class="mt" data-cqn="ngh" data-cvn="w"><span class="f">ng/ngh</span><span class="a">→</span><span class="t">W</span></div> | |
| </div> | |
| </div> | |
| <div class="rsect"> | |
| <div class="rtitle">Cuối + Y/UY + bỏ sắc c/p/t</div> | |
| <div class="mtags" id="tags-cuoi3"> | |
| <div class="mt" data-cqnc="ng" data-cvnc="g"><span class="f">ng</span><span class="a">→</span><span class="t">G</span></div> | |
| <div class="mt" data-cqnc="nh" data-cvnc="h"><span class="f">nh</span><span class="a">→</span><span class="t">H</span></div> | |
| <div class="mt" data-cqnc="ch" data-cvnc="k"><span class="f">ch</span><span class="a">→</span><span class="t">K</span></div> | |
| <div class="mt"><span class="f">y</span><span class="a">→</span><span class="t">i</span></div> | |
| <div class="mt"><span class="f">uy</span><span class="a">→</span><span class="t">y</span></div> | |
| <div class="mt"><span class="f" style="font-size:10px">c/p/t: -sắc</span></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- L4 --> | |
| <div class="lc l4c"> | |
| <div class="lhead"><span>LÀN 4 · TRƯỜNG HỢP P</span><span class="rbadge" id="r4">—</span></div> | |
| <div class="lproc" id="pr4"><span class="lp-in mono" id="p4i"></span><span class="lp-arr">→</span><span class="lp-out mono" id="p4o"></span></div> | |
| <div class="lbody"> | |
| <div class="rsect"> | |
| <div class="rtitle">Khi nào thêm P cuối?</div> | |
| <ul class="rlist"> | |
| <li><span class="dot"></span>Vần KHÔNG dấu phụ + KHÔNG dấu thanh</li> | |
| <li><span class="dot"></span>Rơi vào 1 trong 18 vần đặc biệt → +P</li> | |
| </ul> | |
| </div> | |
| <div class="rsect"> | |
| <div class="rtitle">18 vần cần thêm P</div> | |
| <div class="mtags" id="tags-p"> | |
| <div class="mt" data-p="ag"><span class="t">ag</span></div> | |
| <div class="mt" data-p="ah"><span class="t">ah</span></div> | |
| <div class="mt" data-p="aj"><span class="t">aj</span></div> | |
| <div class="mt" data-p="eg"><span class="t">eg</span></div> | |
| <div class="mt" data-p="el"><span class="t">el</span></div> | |
| <div class="mt" data-p="ev"><span class="t">ev</span></div> | |
| <div class="mt" data-p="ew"><span class="t">ew</span></div> | |
| <div class="mt" data-p="ez"><span class="t">ez</span></div> | |
| <div class="mt" data-p="ih"><span class="t">ih</span></div> | |
| <div class="mt" data-p="oah"><span class="t">oah</span></div> | |
| <div class="mt" data-p="og"><span class="t">og</span></div> | |
| <div class="mt" data-p="oj"><span class="t">oj</span></div> | |
| <div class="mt" data-p="ol"><span class="t">ol</span></div> | |
| <div class="mt" data-p="ov"><span class="t">ov</span></div> | |
| <div class="mt" data-p="ow"><span class="t">ow</span></div> | |
| <div class="mt" data-p="oz"><span class="t">oz</span></div> | |
| <div class="mt" data-p="ug"><span class="t">ug</span></div> | |
| <div class="mt" data-p="yh"><span class="t">yh</span></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div><!-- end .lanes --> | |
| <!-- ══ MERGE / OUTPUT NODE ══ --> | |
| <div class="merge-area"> | |
| <div class="mwires"> | |
| <div class="mw mw1"></div><div class="mw mw2"></div> | |
| <div class="mw mw3"></div><div class="mw mw4"></div> | |
| </div> | |
| <div class="mnode"> | |
| <div class="mformula mono"> | |
| <b class="s1">CVN(vần)</b> + <b class="s2">KHD(dấu)</b> + <b class="s3">Phụ âm đổi</b> + <b class="s4">P(nếu cần)</b> | |
| </div> | |
| <div class="mout mono" id="mout">—</div> | |
| <div class="msub mono" id="msub">Nhập từ và nhấn ▶ CHUYỂN để thấy 4 làn hoạt động</div> | |
| </div> | |
| </div> | |
| <!-- ══ BOTTOM 3-COLUMN REFERENCE ══ --> | |
| <div class="bot"> | |
| <!-- Col 1: KHD Table --> | |
| <div class="ref-card"> | |
| <div class="ref-head"> | |
| <div class="ref-head-left"><div class="dot" style="background:var(--l2)"></div>BẢNG KHD — 18 KÝ HIỆU DẤU</div> | |
| <span style="font-size:9px;color:var(--fa)">5+5+5+3 = 18</span> | |
| </div> | |
| <div class="ref-scroll"> | |
| <div class="khd-g"> | |
| <div class="khd-grp grp-non"> | |
| <div class="khd-gt">Nón ^ (â ê ô)</div> | |
| <div class="khd-row"><span class="khd-tn">sắc</span><span class="khd-sym">B</span></div> | |
| <div class="khd-row"><span class="khd-tn">huyền</span><span class="khd-sym">D</span></div> | |
| <div class="khd-row"><span class="khd-tn">hỏi</span><span class="khd-sym">Q</span></div> | |
| <div class="khd-row"><span class="khd-tn">ngã</span><span class="khd-sym">G</span></div> | |
| <div class="khd-row"><span class="khd-tn">nặng</span><span class="khd-sym">F</span></div> | |
| </div> | |
| <div class="khd-grp grp-trang"> | |
| <div class="khd-gt">Trăng/Móc (ă ơ ư)</div> | |
| <div class="khd-row"><span class="khd-tn">sắc</span><span class="khd-sym">X</span></div> | |
| <div class="khd-row"><span class="khd-tn">huyền</span><span class="khd-sym">K</span></div> | |
| <div class="khd-row"><span class="khd-tn">hỏi</span><span class="khd-sym">V</span></div> | |
| <div class="khd-row"><span class="khd-tn">ngã</span><span class="khd-sym">W</span></div> | |
| <div class="khd-row"><span class="khd-tn">nặng</span><span class="khd-sym">H</span></div> | |
| </div> | |
| <div class="khd-grp grp-plain"> | |
| <div class="khd-gt">Không dấu phụ</div> | |
| <div class="khd-row"><span class="khd-tn">sắc</span><span class="khd-sym">J</span></div> | |
| <div class="khd-row"><span class="khd-tn">huyền</span><span class="khd-sym">L</span></div> | |
| <div class="khd-row"><span class="khd-tn">hỏi</span><span class="khd-sym">Z</span></div> | |
| <div class="khd-row"><span class="khd-tn">ngã</span><span class="khd-sym">S</span></div> | |
| <div class="khd-row"><span class="khd-tn">nặng</span><span class="khd-sym">R</span></div> | |
| </div> | |
| </div> | |
| <div style="margin-top:10px"> | |
| <div class="khd-grp grp-ngang"> | |
| <div class="khd-gt">Thanh Ngang (không dấu)</div> | |
| <div class="khd-row"><span class="khd-tn">Nón ^</span><span class="khd-sym">Y</span></div> | |
| <div class="khd-row"><span class="khd-tn">Trăng/Móc</span><span class="khd-sym">O</span></div> | |
| <div class="khd-row"><span class="khd-tn">Tránh nhầm</span><span class="khd-sym">P</span></div> | |
| </div> | |
| </div> | |
| <div style="margin-top:10px;padding:8px;background:var(--s2);border-radius:4px;border:1px solid var(--bd);font-size:11px;font-family:'SysMono',monospace;line-height:1.7;color:var(--mu)"> | |
| <b style="color:var(--acc)">Lưu ý:</b> Chữ kết thúc bằng C, P, T <b>không thêm J</b><br> | |
| VD: khác=kac · áp=ap · phút=fut | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Col 2: 52 Vần rút gọn --> | |
| <div class="ref-card"> | |
| <div class="ref-head"> | |
| <div class="ref-head-left"><div class="dot" style="background:var(--l1)"></div>52 VẦN RÚT GỌN (LÀN 1)</div> | |
| <button onclick="openPopup('popup-rules')" style="font-size:8px;padding:2px 7px;background:var(--l1bg);border:1px solid var(--l1bd);border-radius:3px;color:var(--l1);cursor:pointer;font-family:'SysMono',monospace;font-weight:700;letter-spacing:.5px">? CHÚ GIẢI</button> | |
| </div> | |
| <div class="ref-scroll" id="rules-scroll"><!-- built by JS --></div> | |
| </div> | |
| <!-- Col 3: Examples --> | |
| <div class="ref-card"> | |
| <div class="ref-head"> | |
| <div class="ref-head-left"><div class="dot" style="background:var(--acc)"></div>VÍ DỤ · NHẤN ĐỂ THỬ</div> | |
| </div> | |
| <div class="ref-scroll"> | |
| <div class="ex-g" id="exG"></div> | |
| </div> | |
| </div> | |
| </div><!-- end .bot --> | |
| <!-- ══ FOOTER ══ --> | |
| <div class="footer"> | |
| Nguồn: <strong>CVNSS4.0</strong> · Kiều Trường Lâm & Trần Tư Bình · Bản quyền số 1850/2020/QTG · Cục Bản quyền tác giả, Bộ VHTTDL · <strong>2026</strong> | |
| </div> | |
| </div><!-- end .wrap --> | |
| <!-- ══ POPUP: 52 Rules Explanation ══ --> | |
| <div class="popup-overlay" id="popup-rules" onclick="closePopupIfOverlay(event,this)"> | |
| <div class="popup-box"> | |
| <div class="popup-head"> | |
| <h3>52 VẦN RÚT GỌN — CHÚ GIẢI CHI TIẾT</h3> | |
| <button class="popup-close" onclick="closePopup('popup-rules')">✕</button> | |
| </div> | |
| <div class="popup-body"> | |
| <p style="font-size:13px;color:var(--mu);line-height:1.7;margin-bottom:14px"> | |
| CQN có <b>52 vần dài</b> (3–4 chữ cái). CVNSS4.0 rút gọn còn <b>2 chữ cái</b> theo 2 bước <em>cùng lúc</em>: | |
| </p> | |
| <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:14px"> | |
| <div style="padding:10px;background:var(--l1bg);border:1px solid var(--l1bd);border-radius:5px"> | |
| <div style="font-size:10px;font-weight:700;letter-spacing:1px;color:var(--l1);margin-bottom:6px;font-family:'SysMono',monospace">BƯỚC 1: Rút NAG → 1 nguyên âm</div> | |
| <div style="font-family:'SysMono',monospace;font-size:11px;line-height:1.8;color:var(--tx)"> | |
| UYÊ → Y | IÊ/YÊ → I<br> | |
| UÔ → U | ƯƠ → Ư<br> | |
| UÂ → Â | UƠ → Ơ<br> | |
| OĂ → Ă | OE → E<br> | |
| OA → O | OA → A (oay) | |
| </div> | |
| </div> | |
| <div style="padding:10px;background:var(--l3bg);border:1px solid var(--l3bd);border-radius:5px"> | |
| <div style="font-size:10px;font-weight:700;letter-spacing:1px;color:var(--l3);margin-bottom:6px;font-family:'SysMono',monospace">BƯỚC 2: Đổi âm cuối (cùng lúc)</div> | |
| <div style="font-family:'SysMono',monospace;font-size:11px;line-height:1.8;color:var(--tx)"> | |
| T → D | P → F<br> | |
| C → S | N → L<br> | |
| M → V | NG → Z<br> | |
| O/U → W | I/Y → J | |
| </div> | |
| </div> | |
| </div> | |
| <div style="padding:10px;background:var(--s2);border:1px solid var(--bd);border-radius:5px;font-family:'SysMono',monospace;font-size:12px;line-height:2;color:var(--tx)"> | |
| <b style="color:var(--acc)">Ví dụ minh họa:</b><br> | |
| tuyết → <b style="color:var(--l1)">tyd</b> | hiệp → <b style="color:var(--l1)">hịf</b> | thuốc → <b style="color:var(--l1)">thus</b><br> | |
| lượn → <b style="color:var(--l1)">lựl</b> | suất → <b style="color:var(--l1)">sâd</b> | thường → <b style="color:var(--l1)">thừz</b><br> | |
| rượu → <b style="color:var(--l1)">rựw</b> | người → <b style="color:var(--l1)">wừj</b> | xoăn → <b style="color:var(--l1)">xăl</b> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ══ POPUP: Single Rule Detail ══ --> | |
| <div class="popup-overlay" id="popup-rule-detail" onclick="closePopupIfOverlay(event,this)"> | |
| <div class="popup-box"> | |
| <div class="popup-head"> | |
| <h3 id="prd-title">Quy tắc</h3> | |
| <button class="popup-close" onclick="closePopup('popup-rule-detail')">✕</button> | |
| </div> | |
| <div class="popup-body" id="prd-body"></div> | |
| </div> | |
| </div> | |
| <script> | |
| const CVNSSConverter = (function () { | |
| // Bảng ánh xạ ký tự đặc biệt | |
| const specialChars = [ | |
| "`", "“", "”", "<", ">", "@", "-", ";", "=", "…", " ", ",", ".", "?", "!", | |
| '"', "'", "(", ")", "[", "]", "{", "}", "%", "#", "$", "&", "_", "\\", "/", | |
| "*", ":", "+", "~", "^", "|", "\r\n", "\r", "\n" | |
| ]; | |
| // Bảng ánh xạ phụ âm | |
| const consonants = { | |
| cqn: ["ngh", "ng", "ch", "gh", "kh", "nh", "ph", "th", "tr", "gi", "qu", "b", "k", "d", "đ", "g", "h", "c", "l", "m", "n", "r", "s", "t", "v", "x"], | |
| cvn: ["w", "w", "ch", "g", "k", "nh", "f", "th", "tr", "j", "q", "b", "c", "z", "d", "g", "h", "c", "l", "m", "n", "r", "s", "t", "v", "x"] | |
| }; | |
| // Bảng ánh xạ nguyên âm | |
| const vowels = { | |
| cqn: [ | |
| "a", "à", "ả", "ã", "á", "ạ", "oa", "òa", "ỏa", "õa", "óa", "ọa", "oà", "oả", "oã", "oá", "oạ", "oác", "oạc", "oách", "oạch", | |
| "oai", "oài", "oải", "oãi", "oái", "oại", "oao", "oào", "oảo", "oão", "oáo", "oạo", "oáp", "oạp", "oát", "oạt", "oắt", "oặt", "oắc", "oặc", | |
| "oăn", "oằn", "oẳn", "oẵn", "oắn", "oặn", "oăm", "oằm", "oẳm", "oẵm", "oắm", "oặm", "oăng", "oằng", "oẳng", "oẵng", "oắng", "oặng", "oay", "oày", | |
| "oảy", "oãy", "oáy", "oạy", "ác", "ạc", "ách", "ạch", "ai", "ài", "ải", "ãi", "ái", "ại", "am", "àm", "ảm", "ãm", "ám", "ạm", "an", "àn", | |
| "ản", "ãn", "án", "ạn", "oan", "oàn", "oản", "oãn", "oán", "oạn", "oanh", "oành", "oảnh", "oãnh", "oánh", "oạnh", "ang", "àng", "ảng", "ãng", | |
| "áng", "ạng", "oang", "oàng", "oảng", "oãng", "oáng", "oạng", "anh", "ành", "ảnh", "ãnh", "ánh", "ạnh", "ao", "ào", "ảo", "ão", "áo", "ạo", | |
| "áp", "ạp", "át", "ạt", "au", "àu", "ảu", "áu", "ạu", "ay", "ày", "ảy", "ãy", "áy", "ạy", "ắc", "ặc", "ăm", "ằm", "ẳm", "ẵm", "ắm", "ặm", | |
| "ăn", "ằn", "ẳn", "ẵn", "ắn", "ặn", "ăng", "ằng", "ẳng", "ẵng", "ắng", "ặng", "ắp", "ặp", "ắt", "ặt", "ấc", "ậc", "âm", "ầm", "ẩm", "ẫm", | |
| "ấm", "ậm", "ân", "ần", "ẩn", "ẫn", "ấn", "ận", "âng", "ầng", "ẩng", "ẫng", "ấng", "ậng", "uân", "uần", "uẩn", "uẫn", "uấn", "uận", "uâng", | |
| "uầng", "uẩng", "uẫng", "uấng", "uậng", "ấp", "ập", "ất", "ật", "uất", "uật", "âu", "ầu", "ẩu", "ẫu", "ấu", "ậu", "ây", "ầy", "ẩy", "ẫy", | |
| "ấy", "ậy", "uây", "uầy", "uẩy", "uẫy", "uấy", "uậy", "e", "è", "ẻ", "ẽ", "é", "ẹ", "oe", "òe", "ỏe", "õe", "óe", "ọe", "éc", "ẹc", "em", | |
| "èm", "ẻm", "ẽm", "ém", "ẹm", "en", "èn", "ẻn", "ẽn", "én", "ẹn", "oen", "oèn", "oẻn", "oẽn", "oén", "oẹn", "eng", "èng", "ẻng", "ẽng", | |
| "éng", "ẹng", "eo", "èo", "ẻo", "ẽo", "éo", "ẹo", "oeo", "oèo", "oẻo", "oẽo", "oéo", "oẹo", "ép", "ẹp", "ét", "ẹt", "oét", "oẹt", "ê", | |
| "ề", "ể", "ễ", "ế", "ệ", "uê", "uề", "uể", "uễ", "uế", "uệ", "ếch", "ệch", "uếch", "uệch", "êm", "ềm", "ểm", "ễm", "ếm", "ệm", "ên", "ền", | |
| "ển", "ễn", "ến", "ện", "ênh", "ềnh", "ểnh", "ễnh", "ếnh", "ệnh", "uênh", "uềnh", "uểnh", "uễnh", "uếnh", "uệnh", "ếp", "ệp", "ết", "ệt", | |
| "êu", "ều", "ểu", "ễu", "ếu", "ệu", "i", "ì", "ỉ", "ĩ", "í", "ị", "uy", "ùy", "ủy", "ũy", "úy", "ụy", "uỳ", "uỷ", "uỹ", "uý", "uỵ", "ia", | |
| "ìa", "ỉa", "ĩa", "ía", "ịa", "uya", "íc", "ích", "ịch", "uých", "uỵch", "iếc", "iệc", "iêm", "iềm", "iểm", "iễm", "iếm", "iệm", "iên", | |
| "iền", "iển", "iễn", "iến", "iện", "uyên", "uyền", "uyển", "uyễn", "uyến", "uyện", "iêng", "iềng", "iểng", "iễng", "iếng", "iệng", "iếp", | |
| "iệp", "iết", "iệt", "uyết", "uyệt", "iêu", "iều", "iểu", "iễu", "iếu", "iệu", "yêt", "yệt", "yên", "yền", "yển", "yễn", "yến", "yện", | |
| "yêm", "yềm", "yểm", "yễm", "yếm", "yệm", "yêng", "yềng", "yểng", "yễng", "yếng", "yệnh", "yêu", "yều", "yểu", "yễu", "yếu", "yệu", "im", | |
| "ìm", "ỉm", "ĩm", "ím", "ịm", "in", "ìn", "ỉn", "ĩn", "ín", "ịn", "inh", "ình", "ỉnh", "ĩnh", "ính", "ịnh", "uynh", "uỳnh", "uỷnh", "uỹnh", | |
| "uýnh", "uỵnh", "íp", "ịp", "uýp", "uỵp", "ít", "ịt", "uýt", "uỵt", "iu", "ìu", "ỉu", "ĩu", "íu", "ịu", "uyu", "uỳu", "uỷu", "uỹu", "uýu", | |
| "uỵu", "uỳn", "uỷn", "uỹn", "uýn", "uỵn", "o", "ò", "ỏ", "õ", "ó", "ọ", "óc", "ọc", "oi", "òi", "ỏi", "õi", "ói", "ọi", "om", "òm", "ỏm", | |
| "õm", "óm", "ọm", "on", "òn", "ỏn", "õn", "ón", "ọn", "ong", "òng", "ỏng", "õng", "óng", "ọng", "oóc", "oong", "oòng", "oỏng", "oõng", | |
| "oóng", "oòng", "oọng", "óp", "ọp", "ót", "ọt", "ô", "ồ", "ổ", "ỗ", "ố", "ộ", "ốc", "ộc", "ôi", "ồi", "ổi", "ỗi", "ối", "ội", "ôm", "ồm", | |
| "ổm", "ỗm", "ốm", "ộm", "ôn", "ồn", "ổn", "ỗn", "ốn", "ộn", "ông", "ồng", "ổng", "ỗng", "ống", "ộng", "ốp", "ộp", "ốt", "ột", "ơ", "ờ", | |
| "ở", "ỡ", "ớ", "ợ", "ơi", "ời", "ởi", "ỡi", "ới", "ợi", "ơm", "ờm", "ởm", "ỡm", "ớm", "ợm", "ơn", "ờn", "ởn", "ỡn", "ớn", "ợn", "ơng", | |
| "ờng", "ởng", "ỡng", "ớng", "ợng", "ớp", "ợp", "ớt", "ợt", "u", "ù", "ủ", "ũ", "ú", "ụ", "ua", "ùa", "ủa", "ũa", "úa", "ụa", "úc", "ục", | |
| "ui", "ùi", "ủi", "ũi", "úi", "ụi", "um", "ùm", "ủm", "ũm", "úm", "ụm", "un", "ùn", "ủn", "ũn", "ún", "ụn", "ung", "ùng", "ủng", "ũng", | |
| "úng", "ụng", "uơ", "uờ", "uở", "uỡ", "uớ", "uợ", "uơn", "uờn", "uởn", "uỡn", "uớn", "uợn", "uớt", "uợt", "uốc", "uộc", "uôi", "uồi", "uổi", | |
| "uỗi", "uối", "uội", "uôm", "uồm", "uổm", "uỗm", "uốm", "uộm", "uôn", "uồn", "uổn", "uỗn", "uốn", "uộn", "uông", "uồng", "uổng", "uỗng", | |
| "uống", "uộng", "uốt", "uột", "uốp", "uộp", "úp", "ụp", "út", "ụt", "ư", "ừ", "ử", "ữ", "ứ", "ự", "ưa", "ừa", "ửa", "ữa", "ứa", "ựa", | |
| "ức", "ực", "ưi", "ừi", "ửi", "ữi", "ứi", "ựi", "ưm", "ừm", "ửm", "ữm", "ứm", "ựm", "ưn", "ừn", "ửn", "ữn", "ứn", "ựn", "ưng", "ừng", | |
| "ửng", "ững", "ứng", "ựng", "ước", "ược", "ươi", "ười", "ưởi", "ưỡi", "ưới", "ượi", "ươm", "ườm", "ưởm", "ưỡm", "ướm", "ượm", "ươn", | |
| "ườn", "ưởn", "ưỡn", "ướn", "ượn", "ương", "ường", "ưởng", "ưỡng", "ướng", "ượng", "ướp", "ượp", "ướt", "ượt", "ươu", "ườu", "ưởu", | |
| "ưỡu", "ướu", "ượu", "ứt", "ựt", "ưu", "ừu", "ửu", "ữu", "ứu", "ựu", "y", "ỳ", "ỷ", "ỹ", "ý", "ỵ", "ỳa", "ỷa", "ỹa", "ýa", "ỵa" | |
| ], | |
| cvn: [ | |
| "a", "al", "az", "as", "aj", "ar", "oa", "oal", "oaz", "oas", "oaj", "oar", "oal", "oaz", "oas", "oaj", "oar", "osj", "osr", "oakj", "oakr", | |
| "ojp", "ojl", "ojz", "ojs", "ojj", "ojr", "owp", "owl", "owz", "ows", "owj", "owr", "ofj", "ofr", "odj", "odr", "adx", "adh", "asx", "ash", | |
| "alo", "alk", "alv", "alw", "alx", "alh", "avo", "avk", "avv", "avw", "avx", "avh", "azo", "azk", "azv", "azw", "azx", "azh", "ajp", "ajl", | |
| "ajz", "ajs", "ajj", "ajr", "ac", "acr", "akj", "akr", "ai", "ail", "aiz", "ais", "aij", "air", "am", "aml", "amz", "ams", "amj", "amr", | |
| "an", "anl", "anz", "ans", "anj", "anr", "olp", "oll", "olz", "ols", "olj", "olr", "oahp", "oahl", "oahz", "oahs", "oahj", "oahr", "agp", | |
| "agl", "agz", "ags", "agj", "agr", "ozp", "ozl", "ozz", "ozs", "ozj", "ozr", "ahp", "ahl", "ahz", "ahs", "ahj", "ahr", "ao", "aol", "aoz", | |
| "aos", "aoj", "aor", "ap", "apr", "at", "atr", "au", "aul", "auz", "auj", "aur", "ay", "ayl", "ayz", "ays", "ayj", "ayr", "acx", "ach", | |
| "amo", "amk", "amv", "amw", "amx", "amh", "ano", "ank", "anv", "anw", "anx", "anh", "ago", "agk", "agv", "agw", "agx", "agh", "apx", "aph", | |
| "atx", "ath", "acb", "acf", "amy", "amd", "amq", "amg", "amb", "amf", "any", "and", "anq", "ang", "anb", "anf", "agy", "agd", "agq", "agg", | |
| "agb", "agf", "aly", "ald", "alq", "alg", "alb", "alf", "azy", "azd", "azq", "azg", "azb", "azf", "apb", "apf", "atb", "atf", "adb", "adf", | |
| "auy", "aud", "auq", "aug", "aub", "auf", "ayy", "ayd", "ayq", "ayg", "ayb", "ayf", "ajy", "ajd", "ajq", "ajg", "ajb", "ajf", "e", "el", | |
| "ez", "es", "ej", "er", "oe", "oel", "oez", "oes", "oej", "oer", "ec", "ecr", "em", "eml", "emz", "ems", "emj", "emr", "en", "enl", "enz", | |
| "ens", "enj", "enr", "elp", "ell", "elz", "els", "elj", "elr", "egp", "egl", "egz", "egs", "egj", "egr", "eo", "eol", "eoz", "eos", "eoj", | |
| "eor", "ewp", "ewl", "ewz", "ews", "ewj", "ewr", "ep", "epr", "et", "etr", "edj", "edr", "ey", "ed", "eq", "eg", "eb", "ef", "uey", "ued", | |
| "ueq", "ueg", "ueb", "uef", "ekb", "ekf", "uekb", "uekf", "emy", "emd", "emq", "emg", "emb", "emf", "eny", "end", "enq", "eng", "enb", "enf", | |
| "ehy", "ehd", "ehq", "ehg", "ehb", "ehf", "uehy", "uehd", "uehq", "uehg", "uehb", "uehf", "epb", "epf", "etb", "etf", "euy", "eud", "euq", | |
| "eug", "eub", "euf", "i", "il", "iz", "is", "ij", "ir", "y", "yl", "yz", "ys", "yj", "yr", "yl", "yz", "ys", "yj", "yr", "ia", "ial", "iaz", | |
| "ias", "iaj", "iar", "ya", "ic", "ikj", "ikr", "ykj", "ykr", "isb", "isf", "ivy", "ivd", "ivq", "ivg", "ivb", "ivf", "ily", "ild", "ilq", | |
| "ilg", "ilb", "ilf", "yly", "yld", "ylq", "ylg", "ylb", "ylf", "izy", "izd", "izq", "izg", "izb", "izf", "ifb", "iff", "idb", "idf", "ydb", | |
| "ydf", "iwy", "iwd", "iwq", "iwg", "iwb", "iwf", "idb", "idf", "ily", "ild", "ilq", "ilg", "ilb", "ilf", "ivy", "ivd", "ivq", "ivg", "ivb", | |
| "ivf", "izy", "izd", "izq", "izg", "izb", "izf", "iwy", "iwd", "iwq", "iwg", "iwb", "iwf", "im", "iml", "imz", "ims", "imj", "imr", "in", | |
| "inl", "inz", "ins", "inj", "inr", "ihp", "ihl", "ihz", "ihs", "ihj", "ihr", "yhp", "yhl", "yhz", "yhs", "yhj", "yhr", "ip", "ipr", "yp", | |
| "ypr", "it", "itr", "yt", "ytr", "iu", "iul", "iuz", "ius", "iuj", "iur", "yu", "yul", "yuz", "yus", "yuj", "yur", "ynl", "ynz", "yns", | |
| "ynj", "ynr", "o", "ol", "oz", "os", "oj", "or", "oc", "ocr", "oi", "oil", "oiz", "ois", "oij", "oir", "om", "oml", "omz", "oms", "omj", | |
| "omr", "on", "onl", "onz", "ons", "onj", "onr", "ogp", "ogl", "ogz", "ogs", "ogj", "ogr", "ooc", "oog", "oogl", "oogz", "oogs", "oogj", | |
| "oogl", "oogr", "op", "opr", "ot", "otr", "oy", "od", "oq", "og", "ob", "of", "ocb", "ocf", "oiy", "oid", "oiq", "oig", "oib", "oif", | |
| "omy", "omd", "omq", "omg", "omb", "omf", "ony", "ond", "onq", "ong", "onb", "onf", "ogy", "ogd", "ogq", "ogg", "ogb", "ogf", "opb", | |
| "opf", "otb", "otf", "oo", "ok", "ov", "ow", "ox", "oh", "oio", "oik", "oiv", "oiw", "oix", "oih", "omo", "omk", "omv", "omw", "omx", | |
| "omh", "ono", "onk", "onv", "onw", "onx", "onh", "ogo", "ogk", "ogv", "ogw", "ogx", "ogh", "opx", "oph", "otx", "oth", "u", "ul", "uz", | |
| "us", "uj", "ur", "ua", "ual", "uaz", "uas", "uaj", "uar", "uc", "ucr", "ui", "uil", "uiz", "uis", "uij", "uir", "um", "uml", "umz", "ums", | |
| "umj", "umr", "un", "unl", "unz", "uns", "unj", "unr", "ugp", "ugl", "ugz", "ugs", "ugj", "ugr", "uoo", "uok", "uov", "uow", "uox", "uoh", | |
| "olo", "olk", "olv", "olw", "olx", "olh", "odx", "odh", "usb", "usf", "ujy", "ujd", "ujq", "ujg", "ujb", "ujf", "uvy", "uvd", "uvq", "uvg", | |
| "uvb", "uvf", "uly", "uld", "ulq", "ulg", "ulb", "ulf", "uzy", "uzd", "uzq", "uzg", "uzb", "uzf", "udb", "udf", "ufb", "uff", "up", "upr", | |
| "ut", "utr", "uo", "uk", "uv", "uw", "ux", "uh", "uao", "uak", "uav", "uaw", "uax", "uah", "ucx", "uch", "uio", "uik", "uiv", "uiw", "uix", | |
| "uih", "umo", "umk", "umv", "umw", "umx", "umh", "uno", "unk", "unv", "unw", "unx", "unh", "ugo", "ugk", "ugv", "ugw", "ugx", "ugh", "usx", | |
| "ush", "ujo", "ujk", "ujv", "ujw", "ujx", "ujh", "uvo", "uvk", "uvv", "uvw", "uvx", "uvh", "ulo", "ulk", "ulv", "ulw", "ulx", "ulh", "uzo", | |
| "uzk", "uzv", "uzw", "uzx", "uzh", "ufx", "ufh", "udx", "udh", "uwo", "uwk", "uwv", "uww", "uwx", "uwh", "utx", "uth", "uuo", "uuk", "uuv", | |
| "uuw", "uux", "uuh", "i", "il", "iz", "is", "ij", "ir", "ial", "iaz", "ias", "iaj", "iar" | |
| ], | |
| cvss: [ | |
| "a", "al", "az", "as", "aj", "ar", "oa", "oal", "oaz", "oas", "oaj", "oar", "oal", "oaz", "oas", "oaj", "oar", "osj", "osr", "oakj", "oakr", | |
| "ojp", "ojl", "ojz", "ojs", "ojj", "ojr", "owp", "owl", "owz", "ows", "owj", "owr", "ofj", "ofr", "odj", "odr", "adx", "adh", "asx", "ash", | |
| "alo", "alk", "alv", "alw", "alx", "alh", "avo", "avk", "avv", "avw", "avx", "avh", "azo", "azk", "azv", "azw", "azx", "azh", "ajp", "ajl", | |
| "ajz", "ajs", "ajj", "ajr", "ac", "acr", "akj", "akr", "ai", "ail", "aiz", "ais", "aij", "air", "am", "aml", "amz", "ams", "amj", "amr", | |
| "an", "anl", "anz", "ans", "anj", "anr", "olp", "oll", "olz", "ols", "olj", "olr", "oahp", "oahl", "oahz", "oahs", "oahj", "oahr", "agp", | |
| "agl", "agz", "ags", "agj", "agr", "ozp", "ozl", "ozz", "ozs", "ozj", "ozr", "ahp", "ahl", "ahz", "ahs", "ahj", "ahr", "ao", "aol", "aoz", | |
| "aos", "aoj", "aor", "ap", "apr", "at", "atr", "au", "aul", "auz", "auj", "aur", "ay", "ayl", "ayz", "ays", "ayj", "ayr", "acx", "ach", | |
| "amo", "amk", "amv", "amw", "amx", "amh", "ano", "ank", "anv", "anw", "anx", "anh", "ago", "agk", "agv", "agw", "agx", "agh", "apx", "aph", | |
| "atx", "ath", "acb", "acf", "amy", "amd", "amq", "amg", "amb", "amf", "any", "and", "anq", "ang", "anb", "anf", "agy", "agd", "agq", "agg", | |
| "agb", "agf", "aly", "ald", "alq", "alg", "alb", "alf", "azy", "azd", "azq", "azg", "azb", "azf", "apb", "apf", "atb", "atf", "adb", "adf", | |
| "auy", "aud", "auq", "aug", "aub", "auf", "ayy", "ayd", "ayq", "ayg", "ayb", "ayf", "ajy", "ajd", "ajq", "ajg", "ajb", "ajf", "e", "el", | |
| "ez", "es", "ej", "er", "oe", "oel", "oez", "oes", "oej", "oer", "ec", "ecr", "em", "eml", "emz", "ems", "emj", "emr", "en", "enl", "enz", | |
| "ens", "enj", "enr", "elp", "ell", "elz", "els", "elj", "elr", "egp", "egl", "egz", "egs", "egj", "egr", "eo", "eol", "eoz", "eos", "eoj", | |
| "eor", "ewp", "ewl", "ewz", "ews", "ewj", "ewr", "ep", "epr", "et", "etr", "edj", "edr", "ey", "ed", "eq", "eg", "eb", "ef", "uey", "ued", | |
| "ueq", "ueg", "ueb", "uef", "ekb", "ekf", "uekb", "uekf", "emy", "emd", "emq", "emg", "emb", "emf", "eny", "end", "enq", "eng", "enb", "enf", | |
| "ehy", "ehd", "ehq", "ehg", "ehb", "ehf", "uehy", "uehd", "uehq", "uehg", "uehb", "uehf", "epb", "epf", "etb", "etf", "euy", "eud", "euq", | |
| "eug", "eub", "euf", "i", "il", "iz", "is", "ij", "ir", "y", "yl", "yz", "ys", "yj", "yr", "yl", "yz", "ys", "yj", "yr", "ia", "ial", "iaz", | |
| "ias", "iaj", "iar", "ya", "ic", "ikj", "ikr", "ykj", "ykr", "isb", "isf", "ivy", "ivd", "ivq", "ivg", "ivb", "ivf", "ily", "ild", "ilq", | |
| "ilg", "ilb", "ilf", "yly", "yld", "ylq", "ylg", "ylb", "ylf", "izy", "izd", "izq", "izg", "izb", "izf", "ifb", "iff", "idb", "idf", "ydb", | |
| "ydf", "iwy", "iwd", "iwq", "iwg", "iwb", "iwf", "idb", "idf", "ily", "ild", "ilq", "ilg", "ilb", "ilf", "ivy", "ivd", "ivq", "ivg", "ivb", | |
| "ivf", "izy", "izd", "izq", "izg", "izb", "izf", "iwy", "iwd", "iwq", "iwg", "iwb", "iwf", "im", "iml", "imz", "ims", "imj", "imr", "in", | |
| "inl", "inz", "ins", "inj", "inr", "ihp", "ihl", "ihz", "ihs", "ihj", "ihr", "yhp", "yhl", "yhz", "yhs", "yhj", "yhr", "ip", "ipr", "yp", | |
| "ypr", "it", "itr", "yt", "ytr", "iu", "iul", "iuz", "ius", "iuj", "iur", "yu", "yul", "yuz", "yus", "yuj", "yur", "ynl", "ynz", "yns", | |
| "ynj", "ynr", "o", "ol", "oz", "os", "oj", "or", "oc", "ocr", "oi", "oil", "oiz", "ois", "oij", "oir", "om", "oml", "omz", "oms", "omj", | |
| "omr", "on", "onl", "onz", "ons", "onj", "onr", "ogp", "ogl", "ogz", "ogs", "ogj", "ogr", "ooc", "oog", "oogl", "oogz", "oogs", "oogj", | |
| "oogl", "oogr", "op", "opr", "ot", "otr", "oy", "od", "oq", "og", "ob", "of", "ocb", "ocf", "oiy", "oid", "oiq", "oig", "oib", "oif", | |
| "omy", "omd", "omq", "omg", "omb", "omf", "ony", "ond", "onq", "ong", "onb", "onf", "ogy", "ogd", "ogq", "ogg", "ogb", "ogf", "opb", | |
| "opf", "otb", "otf", "oo", "ok", "ov", "ow", "ox", "oh", "oio", "oik", "oiv", "oiw", "oix", "oih", "omo", "omk", "omv", "omw", "omx", | |
| "omh", "ono", "onk", "onv", "onw", "onx", "onh", "ogo", "ogk", "ogv", "ogw", "ogx", "ogh", "opx", "oph", "otx", "oth", "u", "ul", "uz", | |
| "us", "uj", "ur", "ua", "ual", "uaz", "uas", "uaj", "uar", "uc", "ucr", "ui", "uil", "uiz", "uis", "uij", "uir", "um", "uml", "umz", "ums", | |
| "umj", "umr", "un", "unl", "unz", "uns", "unj", "unr", "ugp", "ugl", "ugz", "ugs", "ugj", "ugr", "uoo", "uok", "uov", "uow", "uox", "uoh", | |
| "olo", "olk", "olv", "olw", "olx", "olh", "odx", "odh", "usb", "usf", "ujy", "ujd", "ujq", "ujg", "ujb", "ujf", "uvy", "uvd", "uvq", "uvg", | |
| "uvb", "uvf", "uly", "uld", "ulq", "ulg", "ulb", "ulf", "uzy", "uzd", "uzq", "uzg", "uzb", "uzf", "udb", "udf", "ufb", "uff", "up", "upr", | |
| "ut", "utr", "uo", "uk", "uv", "uw", "ux", "uh", "uao", "uak", "uav", "uaw", "uax", "uah", "ucx", "uch", "uio", "uik", "uiv", "uiw", "uix", | |
| "uih", "umo", "umk", "umv", "umw", "umx", "umh", "uno", "unk", "unv", "unw", "unx", "unh", "ugo", "ugk", "ugv", "ugw", "ugx", "ugh", "usx", | |
| "ush", "ujo", "ujk", "ujv", "ujw", "ujx", "ujh", "uvo", "uvk", "uvv", "uvw", "uvx", "uvh", "ulo", "ulk", "ulv", "ulw", "ulx", "ulh", "uzo", | |
| "uzk", "uzv", "uzw", "uzx", "uzh", "ufx", "ufh", "udx", "udh", "uwo", "uwk", "uwv", "uww", "uwx", "uwh", "utx", "uth", "uuo", "uuk", "uuv", | |
| "uuw", "uux", "uuh", "i", "il", "iz", "is", "ij", "ir", "ial", "iaz", "ias", "iaj", "iar" | |
| ] | |
| }; | |
| // Bảng ánh xạ nguyên âm cơ bản | |
| const baseVowels = [ | |
| "aàảãáạ", "ăằẳẵắặ", "âầẩẫấậ", "eèẻẽéẹ", "êềểễếệ", "iìỉĩíị", | |
| "oòỏõóọ", "ôồổỗốộ", "ơờởỡớợ", "uùủũúụ", "ưừửữứự", "yỳỷỹýỵ" | |
| ]; | |
| // Bảng ánh xạ thay thế đặc biệt | |
| const specialReplacements = { | |
| y: "yỳỷỹýỵ", | |
| i: "iìỉĩíị" | |
| }; | |
| // Quy tắc điều chỉnh phụ âm | |
| const consonantAdjustments = { | |
| phu_am: ["ngh", "gh", "k"], | |
| phu_am_chuyen_doi: ["ng", "g", "c"], | |
| nguyen_am: "ieê" | |
| }; | |
| // Hàm kiểm tra chữ in hoa | |
| function isUpperCase(str) { | |
| return /^[A-ZÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬĐÈÉẺẼẸÊỀẾỂỄỆÌÍỈĨỊÒÓỎÕỌÔỒỐỔỖỘƠỜỚỞỠỢÙÚỦŨỤƯỪỨỬỮỰỲÝỶỸỴ]+$/.test(str); | |
| } | |
| // Hàm lấy nguyên âm cơ bản | |
| function getBaseVowel(char) { | |
| for (const vowelGroup of baseVowels) { | |
| if (vowelGroup.includes(char)) return vowelGroup[0]; | |
| } | |
| return char; | |
| } | |
| // Hàm tách chuỗi thành token | |
| function splitString(str) { | |
| str = str.normalize("NFC"); | |
| const tokens = str.split(/([\s|,|;|`|@|<|>|“|”|.|=|…|?|!|\\|'|"|(|)|[|\]|{|}|%|#|$|&|\-|_|/|*|:|+|~|^|||\r\n|\n|\r])/gm); | |
| return tokens.filter(token => token !== ""); | |
| } | |
| // Hàm điều chỉnh phụ âm và nguyên âm | |
| function adjustConsonantVowel(cqnPad, cqnVan) { | |
| const firstChar = cqnVan[0] || ""; | |
| if (cqnPad === "qu" && getBaseVowel(firstChar) === "u") { | |
| cqnPad = "q"; | |
| } | |
| if (!cqnPad && getBaseVowel(firstChar) === "i") { | |
| cqnVan = cqnVan.replace(firstChar, specialReplacements.y[specialReplacements.i.indexOf(firstChar)]); | |
| } | |
| if (cqnPad === "gi" && getBaseVowel(firstChar) === "i") { | |
| cqnPad = "g"; | |
| } | |
| if (consonantAdjustments.phu_am.includes(cqnPad) && !consonantAdjustments.nguyen_am.includes(getBaseVowel(firstChar))) { | |
| cqnPad = consonantAdjustments.phu_am_chuyen_doi[consonantAdjustments.phu_am.indexOf(cqnPad)]; | |
| } | |
| return { cqnPad, cqnVan }; | |
| } | |
| // Hàm chuyển từ CQN sang CVN và CVSS | |
| function cqnToCvnAndCvss(word) { | |
| const lowerWord = word.toLowerCase(); | |
| let consonant = "", vowelPart = lowerWord, cvnConsonant = "", cqnResult = "", cvnResult = "", cvssResult = ""; | |
| // Tìm phụ âm | |
| for (const c of consonants.cqn) { | |
| if (lowerWord.startsWith(c)) { | |
| consonant = c; | |
| vowelPart = lowerWord.replace(c, ""); | |
| break; | |
| } | |
| } | |
| // Điều chỉnh đặc biệt | |
| if (consonant === "gi" && vowelPart !== "a" && vowels.cqn.includes("i" + vowelPart)) { | |
| vowelPart = "i" + vowelPart; | |
| } | |
| if (consonant === "qu" && getBaseVowel(vowelPart[0]) === "y") { | |
| vowelPart = "u" + vowelPart; | |
| } | |
| if (consonant === "g" && getBaseVowel(vowelPart[0]) === "i") { | |
| consonant = "gi"; | |
| } | |
| // Ánh xạ phụ âm | |
| cvnConsonant = consonants.cqn.includes(consonant) ? consonants.cvn[consonants.cqn.indexOf(consonant)] : consonant; | |
| // Ánh xạ nguyên âm | |
| const vowelIndex = vowels.cqn.indexOf(vowelPart); | |
| if (vowelIndex !== -1) { | |
| cqnResult = vowelPart; | |
| cvnResult = vowels.cvn[vowelIndex]; | |
| cvssResult = vowels.cvss[vowelIndex]; | |
| } else { | |
| cqnResult = vowelPart; | |
| cvnResult = vowelPart; | |
| cvssResult = vowelPart; | |
| } | |
| // Kết hợp kết quả | |
| let cvnOutput = cvnConsonant + cvnResult; | |
| let cvssOutput = cvnConsonant + cvssResult; | |
| // Xử lý chữ hoa | |
| if (word[0] !== word[0].toLowerCase()) { | |
| if (cqnResult.length) cqnResult = cqnResult[0].toUpperCase() + cqnResult.slice(1); | |
| if (cvnOutput.length) cvnOutput = cvnOutput[0].toUpperCase() + cvnOutput.slice(1); | |
| if (cvssOutput.length) cvssOutput = cvssOutput[0].toUpperCase() + cvssOutput.slice(1); | |
| } | |
| if (isUpperCase(word)) { | |
| cqnResult = cqnResult.toUpperCase(); | |
| cvnOutput = cvnOutput.toUpperCase(); | |
| cvssOutput = cvssOutput.toUpperCase(); | |
| } | |
| return { cqn: cqnResult, cvn: cvnOutput, cvss: cvssOutput }; | |
| } | |
| // Hàm chuyển từ CVN sang CQN và CVSS | |
| function cvnToCqnAndCvss(word) { | |
| const lowerWord = word.toLowerCase(); | |
| let consonant = "", vowelPart = lowerWord, cqnConsonant = "", cqnResult = "", cvnResult = "", cvssResult = ""; | |
| // Tìm phụ âm | |
| for (const c of consonants.cvn) { | |
| if (lowerWord.startsWith(c)) { | |
| consonant = c; | |
| cqnConsonant = consonants.cqn[consonants.cvn.indexOf(c)]; | |
| vowelPart = lowerWord.replace(c, ""); | |
| break; | |
| } | |
| } | |
| // Ánh xạ nguyên âm | |
| const vowelIndex = vowels.cvn.indexOf(vowelPart); | |
| if (vowelIndex !== -1) { | |
| cqnResult = vowels.cqn[vowelIndex]; | |
| cvnResult = vowelPart; | |
| cvssResult = vowels.cvss[vowelIndex]; | |
| } else { | |
| cqnResult = vowelPart; | |
| cvnResult = vowelPart; | |
| cvssResult = vowelPart; | |
| } | |
| // Điều chỉnh đặc biệt | |
| if (consonant === "j" && vowelPart === "ịa") { | |
| cqnConsonant = "gi"; | |
| cqnResult = "ỵa"; | |
| } | |
| // Điều chỉnh phụ âm và nguyên âm | |
| const adjusted = adjustConsonantVowel(cqnConsonant, cqnResult); | |
| cqnConsonant = adjusted.cqnPad; | |
| cqnResult = adjusted.cqnVan; | |
| // Kết hợp kết quả | |
| let cqnOutput = cqnConsonant + cqnResult; | |
| let cvssOutput = consonant + cvssResult; | |
| // Xử lý chữ hoa | |
| if (word[0] !== word[0].toLowerCase()) { | |
| if (cqnOutput.length) cqnOutput = cqnOutput[0].toUpperCase() + cqnOutput.slice(1); | |
| if (cvnResult.length) cvnResult = cvnResult[0].toUpperCase() + cvnResult.slice(1); | |
| if (cvssOutput.length) cvssOutput = cvssOutput[0].toUpperCase() + cvssOutput.slice(1); | |
| } | |
| if (isUpperCase(word)) { | |
| cqnOutput = cqnOutput.toUpperCase(); | |
| cvnResult = cvnResult.toUpperCase(); | |
| cvssOutput = cvssOutput.toUpperCase(); | |
| } | |
| return { cqn: cqnOutput, cvn: cvnResult, cvss: cvssOutput }; | |
| } | |
| // Hàm chuyển từ CVSS sang CQN và CVN | |
| function cvssToCqnAndCvn(word) { | |
| const lowerWord = word.toLowerCase(); | |
| let consonant = "", vowelPart = lowerWord, cqnConsonant = "", cqnResult = "", cvnResult = "", cvssResult = ""; | |
| // Tìm phụ âm | |
| for (const c of consonants.cvn) { | |
| if (lowerWord.startsWith(c)) { | |
| consonant = c; | |
| cqnConsonant = consonants.cqn[consonants.cvn.indexOf(c)]; | |
| vowelPart = lowerWord.replace(c, ""); | |
| break; | |
| } | |
| } | |
| // Ánh xạ nguyên âm | |
| const vowelIndex = vowels.cvss.indexOf(vowelPart); | |
| if (vowelIndex !== -1) { | |
| cqnResult = vowels.cqn[vowelIndex]; | |
| cvnResult = vowels.cvn[vowelIndex]; | |
| cvssResult = vowelPart; | |
| } else { | |
| cqnResult = vowelPart; | |
| cvnResult = vowelPart; | |
| cvssResult = vowelPart; | |
| } | |
| // Điều chỉnh đặc biệt | |
| if (consonant === "j" && vowelPart === "iar") { | |
| cqnConsonant = "gi"; | |
| cqnResult = "ỵa"; | |
| } | |
| if (lowerWord === "it") { | |
| cqnResult = "ít"; | |
| } | |
| if (lowerWord === "ikj") { | |
| cqnResult = "ích"; | |
| } | |
| // Điều chỉnh phụ âm và nguyên âm | |
| const adjusted = adjustConsonantVowel(cqnConsonant, cqnResult); | |
| cqnConsonant = adjusted.cqnPad; | |
| cqnResult = adjusted.cqnVan; | |
| // Kết hợp kết quả | |
| let cqnOutput = cqnConsonant + cqnResult; | |
| let cvnOutput = consonant + cvnResult; | |
| // Xử lý chữ hoa | |
| if (word[0] !== word[0].toLowerCase()) { | |
| if (cqnOutput.length) cqnOutput = cqnOutput[0].toUpperCase() + cqnOutput.slice(1); | |
| if (cvnOutput.length) cvnOutput = cvnOutput[0].toUpperCase() + cvnOutput.slice(1); | |
| if (cvssResult.length) cvssResult = cvssResult[0].toUpperCase() + cvssResult.slice(1); | |
| } | |
| if (isUpperCase(word)) { | |
| cqnOutput = cqnOutput.toUpperCase(); | |
| cvnOutput = cvnOutput.toUpperCase(); | |
| cvssResult = cvssResult.toUpperCase(); | |
| } | |
| return { cqn: cqnOutput, cvn: cvnOutput, cvss: cvssResult }; | |
| } | |
| // Hàm chuyển đổi toàn bộ văn bản | |
| function convertText(input, mode) { | |
| const tokens = splitString(input); | |
| const result = { cqn: [], cvn: [], cvss: [] }; | |
| tokens.forEach(token => { | |
| if (specialChars.includes(token)) { | |
| result.cqn.push(token); | |
| result.cvn.push(token); | |
| result.cvss.push(token); | |
| } else { | |
| let converted; | |
| if (mode === "cqn") { | |
| converted = cqnToCvnAndCvss(token); | |
| } else if (mode === "cvn") { | |
| converted = cvnToCqnAndCvss(token); | |
| } else if (mode === "cvss") { | |
| converted = cvssToCqnAndCvn(token); | |
| } | |
| result.cqn.push(converted.cqn); | |
| result.cvn.push(converted.cvn); | |
| result.cvss.push(converted.cvss); | |
| } | |
| }); | |
| return { | |
| cqn: result.cqn.join(""), | |
| cvn: result.cvn.join(""), | |
| cvss: result.cvss.join("") | |
| }; | |
| } | |
| // Xuất module | |
| return { | |
| convert: convertText, | |
| specialChars | |
| }; | |
| })(); | |
| // Xuất module cho môi trường Node.js hoặc trình duyệt | |
| if (typeof module !== "undefined" && typeof module.exports !== "undefined") { | |
| module.exports = CVNSSConverter; | |
| } else { | |
| window.CVNSSConverter = CVNSSConverter; | |
| } | |
| // ══════════════════════════════════════════════════════ | |
| // 52 VẦN DATA | |
| // ══════════════════════════════════════════════════════ | |
| const RULE_GROUPS = [ | |
| { title:"UYÊ → Y", color:"l1", rules:[ | |
| {n:1,from:"uyêt",to:"yd",ex:[["tuyết","tyd"],["khuyết","kyd"]]}, | |
| {n:2,from:"uyên",to:"yl",ex:[["nguyên","wyly"],["huyền","hyld"],["nguyễn","wylg"]]} | |
| ]}, | |
| { title:"IÊ/YÊ → I", color:"l1", rules:[ | |
| {n:3,from:"iêt",to:"id",ex:[["viết","vid"],["thiết","tid"]]}, | |
| {n:4,from:"iêp",to:"if",ex:[["hiệp","hịf"],["nghiệp","wịf"]]}, | |
| {n:5,from:"iêc",to:"is",ex:[["việc","vịs"],["chiếc","cis"]]}, | |
| {n:6,from:"iên",to:"il",ex:[["tiền","tild"],["biên","bily"]]}, | |
| {n:7,from:"iêm",to:"iv",ex:[["hiểm","hỉv"],["tiệm","tịv"]]}, | |
| {n:8,from:"iêng",to:"iz",ex:[["nghiêng","wizy"],["tiếng","tíz"]]}, | |
| {n:9,from:"iêu",to:"iw",ex:[["liệu","lịw"],["điều","diwd"]]}, | |
| {n:10,from:"yêt",to:"id",ex:[["yết","íd"],["tuyết","tyd"]]}, | |
| {n:11,from:"yên",to:"il",ex:[["yên","ily"],["chuyên","cyl"]]}, | |
| {n:12,from:"yêm",to:"iv",ex:[["yểm","ỉv"]]}, | |
| {n:13,from:"yêng",to:"iz",ex:[["yêng","izy"]]}, | |
| {n:14,from:"yêu",to:"iw",ex:[["yếu","íw"],["chiều","ciwd"]]} | |
| ]}, | |
| { title:"UÔ → U", color:"l3", rules:[ | |
| {n:15,from:"uôt",to:"ud",ex:[["nuốt","nud"],["cuốt","cud"]]}, | |
| {n:16,from:"uôc",to:"us",ex:[["cuộc","cụs"],["thuốc","thus"]]}, | |
| {n:17,from:"uôn",to:"ul",ex:[["luôn","luly"],["buồn","bùl"]]}, | |
| {n:18,from:"uôm",to:"uv",ex:[["nhuộm","nhụv"]]}, | |
| {n:19,from:"uông",to:"uz",ex:[["uổng","ủzy"],["chuông","cuzy"]]}, | |
| {n:20,from:"uôi",to:"uj",ex:[["ruồi","rùj"],["tuổi","tủj"]]} | |
| ]}, | |
| { title:"ƯƠ → Ư", color:"l3", rules:[ | |
| {n:21,from:"ươt",to:"ưd",ex:[["lượt","lựd"],["cướt","cứd"]]}, | |
| {n:22,from:"ươp",to:"ưf",ex:[["cướp","cứf"]]}, | |
| {n:23,from:"ươc",to:"ưs",ex:[["bước","bứs"],["được","dựs"]]}, | |
| {n:24,from:"ươn",to:"ưl",ex:[["mướn","mứl"],["lượn","lựl"]]}, | |
| {n:25,from:"ươm",to:"ưv",ex:[["cườm","cừv"]]}, | |
| {n:26,from:"ương",to:"ưz",ex:[["tưởng","tửv"],["đường","duzk"],["thường","thừzy"]]}, | |
| {n:27,from:"ươu",to:"ưw",ex:[["rượu","rựw"]]}, | |
| {n:28,from:"ươi",to:"ưj",ex:[["người","wừj"],["lưỡi","lữj"]]} | |
| ]}, | |
| { title:"UÂ → Â", color:"l2", rules:[ | |
| {n:29,from:"uât",to:"âd",ex:[["luật","lậd"],["suất","sâd"]]}, | |
| {n:30,from:"uân",to:"âl",ex:[["khuẩn","kẩly"],["quân","qâly"]]}, | |
| {n:31,from:"uâng",to:"âz",ex:[["khuâng","kâzy"]]}, | |
| {n:32,from:"uây",to:"âj",ex:[["khuây","kâj"]]} | |
| ]}, | |
| { title:"UƠ → Ơ", color:"l2", rules:[ | |
| {n:33,from:"uơt",to:"ơd",ex:[["hướt","hơd"]]}, | |
| {n:34,from:"uơn",to:"ơl",ex:[["hướn","hơl"]]} | |
| ]}, | |
| { title:"OĂ → Ă", color:"l4", rules:[ | |
| {n:35,from:"oăt",to:"ăd",ex:[["ngoắt","wắd"]]}, | |
| {n:36,from:"oăc",to:"ăs",ex:[["hoặc","hặs"]]}, | |
| {n:37,from:"oăn",to:"ăl",ex:[["xoắn","xăl"],["xoăn","xăly"]]}, | |
| {n:38,from:"oăm",to:"ăv",ex:[["oăm","ăvy"]]}, | |
| {n:39,from:"oăng",to:"ăz",ex:[["hoẵng","hẵz"]]} | |
| ]}, | |
| { title:"OE → E", color:"l4", rules:[ | |
| {n:40,from:"oet",to:"ed",ex:[["toét","ted"]]}, | |
| {n:41,from:"oen",to:"el",ex:[["khoen","kely"]]}, | |
| {n:42,from:"oem",to:"ev",ex:[["ngoém","wévy"]]}, | |
| {n:43,from:"oeo",to:"ew",ex:[["ngoẻo","wẻwy"]]} | |
| ]}, | |
| { title:"OA → O / A", color:"l3", rules:[ | |
| {n:44,from:"oat",to:"od",ex:[["loạt","lọd"]]}, | |
| {n:45,from:"oap",to:"of",ex:[["ngoáp","wóf"]]}, | |
| {n:46,from:"oac",to:"os",ex:[["khoác","kos"]]}, | |
| {n:47,from:"oan",to:"ol",ex:[["toán","tójl"],["loan","lolp"]]}, | |
| {n:48,from:"oam",to:"ov",ex:[["ngoạm","wọv"]]}, | |
| {n:49,from:"oang",to:"oz",ex:[["khoảng","kỏzy"],["hoang","hozy"]]}, | |
| {n:50,from:"oao",to:"ow",ex:[["ngoáo","wów"]]}, | |
| {n:51,from:"oai",to:"oj",ex:[["xoài","xòj"],["ngoại","wọj"]]}, | |
| {n:52,from:"oay",to:"aj",ex:[["xoay","xajp"],["loay hoay","lajp hajp"]]} | |
| ]} | |
| ]; | |
| // Build 52 rules in scroll area | |
| function buildRules() { | |
| const container = document.getElementById('rules-scroll'); | |
| let html = ''; | |
| let num = 0; | |
| RULE_GROUPS.forEach(g => { | |
| html += `<div class="rule-group"> | |
| <div class="rg-title" style="background:var(--${g.color}bg);color:var(--${g.color});border-left-color:var(--${g.color})"> | |
| <span>${g.title}</span><span class="cnt" style="background:var(--${g.color})">${g.rules.length}</span> | |
| </div>`; | |
| g.rules.forEach(r => { | |
| num++; | |
| html += `<div class="rule-row" onclick="showRuleDetail(${r.n})"> | |
| <span class="rnum mono">${r.n}.</span> | |
| <span class="rfrom mono">${r.from}</span> | |
| <span class="rarr">→</span> | |
| <span class="rto mono">${r.to}</span> | |
| <button class="popup-btn" onclick="event.stopPropagation();showRuleDetail(${r.n})">? xem</button> | |
| </div>`; | |
| }); | |
| html += `</div>`; | |
| }); | |
| container.innerHTML = html; | |
| } | |
| // Show detail popup for a rule | |
| function showRuleDetail(num) { | |
| let rule = null, groupTitle = ''; | |
| for(const g of RULE_GROUPS) { | |
| const r = g.rules.find(r => r.n === num); | |
| if(r){ rule = r; groupTitle = g.title; break; } | |
| } | |
| if(!rule) return; | |
| document.getElementById('prd-title').textContent = `Quy tắc ${rule.n}: ${rule.from.toUpperCase()} → ${rule.to.toUpperCase()}`; | |
| let body = ` | |
| <div class="popup-rule-title"><span>${rule.from}</span> → <span>${rule.to}</span></div> | |
| <div class="popup-desc">Nhóm <b>${groupTitle}</b> — Vần CQN <b>${rule.from}</b> rút gọn thành <b>${rule.to}</b> (2 ký tự)</div> | |
| <div class="popup-examples">`; | |
| rule.ex.forEach(([cqn]) => { | |
| const r = CVNSSConverter.convert(cqn, 'cqn'); | |
| body += `<div class="popup-ex-row"> | |
| <span class="pe-cqn">${cqn}</span> | |
| <span class="pe-arr">→</span> | |
| <span class="pe-cvn">${r.cvn}</span> | |
| <span class="pe-arr2">→</span> | |
| <span class="pe-cvss">${r.cvss}</span> | |
| </div>`; | |
| }); | |
| body += `</div>`; | |
| document.getElementById('prd-body').innerHTML = body; | |
| openPopup('popup-rule-detail'); | |
| } | |
| function openPopup(id){ document.getElementById(id).classList.add('open'); } | |
| function closePopup(id){ document.getElementById(id).classList.remove('open'); } | |
| function closePopupIfOverlay(e, el){ if(e.target === el) el.classList.remove('open'); } | |
| document.addEventListener('keydown', e => { if(e.key==='Escape') document.querySelectorAll('.popup-overlay.open').forEach(el=>el.classList.remove('open')); }); | |
| // ══════════════════════════════════════════════════════ | |
| // EXAMPLES | |
| // ══════════════════════════════════════════════════════ | |
| const EXAMPLES = [ | |
| ["nghiêng","wizy"],["tuyết","tyd"],["đường","duzk"],["nguyễn","wylg"], | |
| ["phượng","fuzh"],["tiền","tild"],["khoảng","kỏzy"],["viết","vid"], | |
| ["tưởng","tửvy"],["luật","lậd"],["không","kogb"],["được","dựsy"], | |
| ["xuân","xâly"],["giữ","juw"],["xoài","xòj"],["long","logp"], | |
| ["xoay","xajp"],["reng","regp"],["phở","fov"],["huyền","hyld"], | |
| ]; | |
| function buildExamples() { | |
| const eg = document.getElementById("exG"); | |
| EXAMPLES.forEach(([f,t]) => { | |
| const d = document.createElement("div"); | |
| d.className = "ex-i"; | |
| d.innerHTML = `<span class="ex-f mono">${f}</span><span class="ex-a">→</span><span class="ex-t mono">${t}</span>`; | |
| d.onclick = () => { | |
| document.querySelectorAll(".mtab").forEach(b=>b.classList.remove("on")); | |
| document.querySelector('[data-mode="cqn"]').classList.add("on"); | |
| mode = "cqn"; updateLabels(); | |
| document.getElementById("inp").value = f; | |
| doConvert(); | |
| window.scrollTo({top:0,behavior:'smooth'}); | |
| }; | |
| eg.appendChild(d); | |
| }); | |
| } | |
| // ══════════════════════════════════════════════════════ | |
| // UI CONTROLLER | |
| // ══════════════════════════════════════════════════════ | |
| let mode = "cqn"; | |
| function setMode(btn) { | |
| document.querySelectorAll(".mtab").forEach(b=>b.classList.remove("on")); | |
| btn.classList.add("on"); | |
| mode = btn.dataset.mode; | |
| updateLabels(); resetAll(); | |
| } | |
| function updateLabels() { | |
| const m = { | |
| cqn: ["Nhập CQN (Chữ Quốc Ngữ)", "Kết quả CVNSS 4.0", "VD: nghiêng, tuyết, đường..."], | |
| cvss: ["Nhập CVNSS 4.0", "Kết quả CQN (Chữ Quốc Ngữ)", "VD: wizy, tyd, duzk, kogb..."], | |
| cvn: ["Nhập CVN (còn dấu, ngắn hơn CQN)","Kết quả CVNSS 4.0", "VD: wĩ, lựd, cứf..."], | |
| }; | |
| document.getElementById("lbl-in").textContent = m[mode][0]; | |
| document.getElementById("lbl-out").textContent = m[mode][1]; | |
| document.getElementById("inp").placeholder = m[mode][2]; | |
| } | |
| function doConvert() { | |
| const input = document.getElementById("inp").value.trim(); | |
| if(!input){ resetAll(); return; } | |
| const words = input.split(/\s+/); | |
| const fw = words[0]; | |
| const full = CVNSSConverter.convert(input, mode); | |
| const single = CVNSSConverter.convert(fw, mode); | |
| const target = mode==="cvss" ? full.cqn : full.cvss; | |
| const tSingle = mode==="cvss" ? single.cqn : single.cvss; | |
| // Output box | |
| const ob = document.getElementById("outBox"); | |
| ob.innerHTML = `<span style="font-weight:800;letter-spacing:3px">${target||"—"}</span>`; | |
| ob.classList.add("anim"); | |
| setTimeout(()=>ob.classList.remove("anim"),400); | |
| // Node | |
| document.getElementById("nodeVal").textContent = fw; | |
| document.getElementById("nodeIn").classList.add("on"); | |
| document.getElementById("busH").classList.add("on"); | |
| ["v1","v2","v3","v4"].forEach(id=>document.getElementById(id).classList.add("on")); | |
| // Lanes | |
| animateLanes(fw, single); | |
| // Merge node | |
| const mo = document.getElementById("mout"); | |
| mo.textContent = target||"—"; | |
| mo.classList.toggle("res", !!target && target!=="—"); | |
| const sub = mode==="cvss" | |
| ? `CVSS: ${full.cvss} · CVN: ${full.cvn}` | |
| : `CQN: ${full.cqn} · CVN: ${full.cvn} · CVSS: ${full.cvss}`; | |
| document.getElementById("msub").textContent = sub; | |
| highlightKHD(tSingle); | |
| } | |
| function highlightKHD(cvss) { | |
| document.querySelectorAll('[id^="k-"]').forEach(el=>el.classList.remove("hi")); | |
| if(!cvss||cvss==="—") return; | |
| const s = cvss.toUpperCase(); | |
| for(const k of ["B","D","Q","G","F","Y","X","K","V","W","H","O","J","L","Z","S","R"]) { | |
| if(s.endsWith(k)){ const el=document.getElementById("k-"+k); if(el) el.classList.add("hi"); break; } | |
| } | |
| } | |
| function highlightP(cvss) { | |
| document.querySelectorAll("#tags-p .mt").forEach(el=>el.classList.remove("hi")); | |
| if(!cvss) return; | |
| const s = cvss.toLowerCase().replace(/p$/,""); | |
| document.querySelectorAll("#tags-p .mt").forEach(el=>{ if(el.dataset.p===s) el.classList.add("hi"); }); | |
| } | |
| function animateLanes(word, result) { | |
| const {cqn,cvn,cvss} = result; | |
| setProc(1, word, cvss||"—"); setResult("r1", cvss); | |
| setProc(2, word, cvss||"—"); setResult("r2", cvss); | |
| setProc(3, word, cvn||"—"); setResult("r3", cvn); | |
| const pVans = ["ag","ah","aj","eg","el","ev","ew","ez","ih","oah","og","oj","ol","ov","ow","oz","ug","yh"]; | |
| const base = cvss ? cvss.toLowerCase().replace(/p$/,"") : ""; | |
| const needsP = cvss && pVans.includes(base) && cvss.endsWith("p"); | |
| setProc(4, cvss||"—", needsP ? "→ thêm P ✓" : "không cần P"); | |
| setResult("r4", needsP ? "P ✓" : "OK"); | |
| highlightP(cvss); | |
| highlightConsonant(word, cvn); | |
| } | |
| function highlightConsonant(cqnW, cvnW) { | |
| document.querySelectorAll("#tags-dau .mt, #tags-cuoi3 .mt").forEach(el=>el.classList.remove("hi")); | |
| if(!cqnW||!cvnW) return; | |
| const cl=cqnW.toLowerCase(), cvl=cvnW.toLowerCase(); | |
| document.querySelectorAll("#tags-dau .mt[data-cqn]").forEach(el => { | |
| const cc=el.dataset.cqn, cv=el.dataset.cvn; | |
| if((cc==="ngh" && (cl.startsWith("ng")||cl.startsWith("ngh")) && cvl.startsWith("w")) || | |
| (cl.startsWith(cc) && cvl.startsWith(cv))) el.classList.add("hi"); | |
| }); | |
| } | |
| function setProc(n, inV, outV) { | |
| document.getElementById("p"+n+"i").textContent = inV; | |
| document.getElementById("p"+n+"o").textContent = outV; | |
| document.getElementById("pr"+n).classList.add("show"); | |
| } | |
| function setResult(id, val) { | |
| const el=document.getElementById(id); | |
| el.textContent = val||"—"; | |
| el.classList.toggle("lit", !!val && val!=="—"); | |
| } | |
| function resetAll() { | |
| document.getElementById("nodeVal").textContent="—"; | |
| document.getElementById("nodeIn").classList.remove("on"); | |
| document.getElementById("busH").classList.remove("on"); | |
| ["v1","v2","v3","v4"].forEach(id=>document.getElementById(id).classList.remove("on")); | |
| [1,2,3,4].forEach(n=>{ document.getElementById("pr"+n).classList.remove("show"); setResult("r"+n,""); }); | |
| document.getElementById("mout").textContent="—"; | |
| document.getElementById("mout").classList.remove("res"); | |
| document.getElementById("msub").textContent="Nhập từ và nhấn ▶ CHUYỂN để thấy 4 làn hoạt động"; | |
| document.getElementById("outBox").innerHTML='<span class="ph">Kết quả hiển thị ở đây...</span>'; | |
| document.querySelectorAll('[id^="k-"],.mt').forEach(el=>el.classList.remove("hi")); | |
| } | |
| document.getElementById("btnConv").onclick = doConvert; | |
| document.getElementById("inp").addEventListener("keydown", e=>{ if(e.key==="Enter") doConvert(); }); | |
| // Init | |
| buildRules(); | |
| buildExamples(); | |
| </script> | |
| </body> | |
| </html> | |