| <!DOCTYPE html> |
| <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> |
|
|
| |
| <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"> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <div class="lanes"> |
| |
| <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> |
| |
| <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> |
| |
| <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> |
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <div class="bot"> |
|
|
| |
| <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> |
|
|
| |
| <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"></div> |
| </div> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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 () { |
| |
| const specialChars = [ |
| "`", "“", "”", "<", ">", "@", "-", ";", "=", "…", " ", ",", ".", "?", "!", |
| '"', "'", "(", ")", "[", "]", "{", "}", "%", "#", "$", "&", "_", "\\", "/", |
| "*", ":", "+", "~", "^", "|", "\r\n", "\r", "\n" |
| ]; |
| |
| |
| 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"] |
| }; |
| |
| |
| 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" |
| ] |
| }; |
| |
| |
| const baseVowels = [ |
| "aàảãáạ", "ăằẳẵắặ", "âầẩẫấậ", "eèẻẽéẹ", "êềểễếệ", "iìỉĩíị", |
| "oòỏõóọ", "ôồổỗốộ", "ơờởỡớợ", "uùủũúụ", "ưừửữứự", "yỳỷỹýỵ" |
| ]; |
| |
| |
| const specialReplacements = { |
| y: "yỳỷỹýỵ", |
| i: "iìỉĩíị" |
| }; |
| |
| |
| const consonantAdjustments = { |
| phu_am: ["ngh", "gh", "k"], |
| phu_am_chuyen_doi: ["ng", "g", "c"], |
| nguyen_am: "ieê" |
| }; |
| |
| |
| function isUpperCase(str) { |
| return /^[A-ZÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬĐÈÉẺẼẸÊỀẾỂỄỆÌÍỈĨỊÒÓỎÕỌÔỒỐỔỖỘƠỜỚỞỠỢÙÚỦŨỤƯỪỨỬỮỰỲÝỶỸỴ]+$/.test(str); |
| } |
| |
| |
| function getBaseVowel(char) { |
| for (const vowelGroup of baseVowels) { |
| if (vowelGroup.includes(char)) return vowelGroup[0]; |
| } |
| return char; |
| } |
| |
| |
| function splitString(str) { |
| str = str.normalize("NFC"); |
| const tokens = str.split(/([\s|,|;|`|@|<|>|“|”|.|=|…|?|!|\\|'|"|(|)|[|\]|{|}|%|#|$|&|\-|_|/|*|:|+|~|^|||\r\n|\n|\r])/gm); |
| return tokens.filter(token => token !== ""); |
| } |
| |
| |
| 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 }; |
| } |
| |
| |
| function cqnToCvnAndCvss(word) { |
| const lowerWord = word.toLowerCase(); |
| let consonant = "", vowelPart = lowerWord, cvnConsonant = "", cqnResult = "", cvnResult = "", cvssResult = ""; |
| |
| |
| for (const c of consonants.cqn) { |
| if (lowerWord.startsWith(c)) { |
| consonant = c; |
| vowelPart = lowerWord.replace(c, ""); |
| break; |
| } |
| } |
| |
| |
| 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"; |
| } |
| |
| |
| cvnConsonant = consonants.cqn.includes(consonant) ? consonants.cvn[consonants.cqn.indexOf(consonant)] : consonant; |
| |
| |
| 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; |
| } |
| |
| |
| let cvnOutput = cvnConsonant + cvnResult; |
| let cvssOutput = cvnConsonant + cvssResult; |
| |
| |
| 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 }; |
| } |
| |
| |
| function cvnToCqnAndCvss(word) { |
| const lowerWord = word.toLowerCase(); |
| let consonant = "", vowelPart = lowerWord, cqnConsonant = "", cqnResult = "", cvnResult = "", cvssResult = ""; |
| |
| |
| for (const c of consonants.cvn) { |
| if (lowerWord.startsWith(c)) { |
| consonant = c; |
| cqnConsonant = consonants.cqn[consonants.cvn.indexOf(c)]; |
| vowelPart = lowerWord.replace(c, ""); |
| break; |
| } |
| } |
| |
| |
| 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; |
| } |
| |
| |
| if (consonant === "j" && vowelPart === "ịa") { |
| cqnConsonant = "gi"; |
| cqnResult = "ỵa"; |
| } |
| |
| |
| const adjusted = adjustConsonantVowel(cqnConsonant, cqnResult); |
| cqnConsonant = adjusted.cqnPad; |
| cqnResult = adjusted.cqnVan; |
| |
| |
| let cqnOutput = cqnConsonant + cqnResult; |
| let cvssOutput = consonant + cvssResult; |
| |
| |
| 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 }; |
| } |
| |
| |
| function cvssToCqnAndCvn(word) { |
| const lowerWord = word.toLowerCase(); |
| let consonant = "", vowelPart = lowerWord, cqnConsonant = "", cqnResult = "", cvnResult = "", cvssResult = ""; |
| |
| |
| for (const c of consonants.cvn) { |
| if (lowerWord.startsWith(c)) { |
| consonant = c; |
| cqnConsonant = consonants.cqn[consonants.cvn.indexOf(c)]; |
| vowelPart = lowerWord.replace(c, ""); |
| break; |
| } |
| } |
| |
| |
| 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; |
| } |
| |
| |
| if (consonant === "j" && vowelPart === "iar") { |
| cqnConsonant = "gi"; |
| cqnResult = "ỵa"; |
| } |
| if (lowerWord === "it") { |
| cqnResult = "ít"; |
| } |
| if (lowerWord === "ikj") { |
| cqnResult = "ích"; |
| } |
| |
| |
| const adjusted = adjustConsonantVowel(cqnConsonant, cqnResult); |
| cqnConsonant = adjusted.cqnPad; |
| cqnResult = adjusted.cqnVan; |
| |
| |
| let cqnOutput = cqnConsonant + cqnResult; |
| let cvnOutput = consonant + cvnResult; |
| |
| |
| 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 }; |
| } |
| |
| |
| 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("") |
| }; |
| } |
| |
| |
| return { |
| convert: convertText, |
| specialChars |
| }; |
| })(); |
| |
| |
| if (typeof module !== "undefined" && typeof module.exports !== "undefined") { |
| module.exports = CVNSSConverter; |
| } else { |
| window.CVNSSConverter = CVNSSConverter; |
| } |
| |
| |
| |
| |
| 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"]]} |
| ]} |
| ]; |
| |
| |
| 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; |
| } |
| |
| |
| 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')); }); |
| |
| |
| |
| |
| 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); |
| }); |
| } |
| |
| |
| |
| |
| 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; |
| |
| |
| 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); |
| |
| |
| 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")); |
| |
| |
| animateLanes(fw, single); |
| |
| |
| 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(); }); |
| |
| |
| buildRules(); |
| buildExamples(); |
| </script> |
| </body> |
| </html> |
|
|