Advanced Web Server Manager
Complete File Manager & Terminal - Standalone Version
By Sid Gifari | Gifari Industries
Current path:
/
/
home
/
qtdcvxyp
/
public_html
/
english-test
✏️
Editing: index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="theme-color" content="#080F1A"> <title>STAR4Hire — English Level Test</title> <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"> <style> *{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent} html,body{height:100%;font-family:'Plus Jakarta Sans',sans-serif;background:#080F1A;color:#EEF2F7;overflow-x:hidden} :root{ --bg:#080F1A;--bg2:#0D1828;--bg3:#112035; --blue:#3B82F6;--blue2:#2563EB;--teal:#06B6D4; --gold:#F59E0B;--green:#10B981;--red:#EF4444; --purple:#8B5CF6;--pink:#EC4899; --muted:#64748B;--white:#EEF2F7; --border:rgba(255,255,255,0.08);--card:rgba(255,255,255,0.04); --safe-b:env(safe-area-inset-bottom,0px); } /* ── SCREENS ── */ .screen{display:none;min-height:100dvh;flex-direction:column} .screen.active{display:flex} /* ══ INTRO ══ */ #s-intro{ background:linear-gradient(160deg,#080F1A 0%,#0D1A30 50%,#080F1A 100%); justify-content:space-between;align-items:center; padding:0;text-align:center;overflow:hidden;position:relative; } #s-intro::before{ content:'';position:absolute;inset:0;pointer-events:none; background: radial-gradient(ellipse 80% 60% at 20% 10%,rgba(59,130,246,.1) 0%,transparent 60%), radial-gradient(ellipse 60% 50% at 80% 90%,rgba(139,92,246,.08) 0%,transparent 55%), radial-gradient(ellipse 40% 40% at 90% 20%,rgba(6,182,212,.07) 0%,transparent 50%); } .intro-inner{ position:relative;z-index:1;width:100%;max-width:440px; min-height:100dvh;display:flex;flex-direction:column; justify-content:space-between; padding:48px 24px calc(36px + var(--safe-b));margin:0 auto; } .brand-pill{ display:inline-flex;align-items:center;gap:7px; background:rgba(59,130,246,.12);border:1px solid rgba(59,130,246,.3); border-radius:100px;padding:6px 16px;font-size:11px; color:var(--blue);letter-spacing:.08em;font-weight:700;margin-bottom:24px; } .intro-title{font-size:clamp(30px,8vw,48px);font-weight:900;line-height:1.05;margin-bottom:10px} .intro-title em{ background:linear-gradient(135deg,var(--blue),var(--teal)); -webkit-background-clip:text;-webkit-text-fill-color:transparent; background-clip:text;font-style:normal; } .intro-sub{font-size:14px;color:var(--muted);line-height:1.7;margin-bottom:24px;max-width:340px;margin-left:auto;margin-right:auto} /* LEVEL BADGES ROW */ .levels-row{display:flex;justify-content:center;gap:6px;margin-bottom:24px;flex-wrap:wrap} .lv-badge{ padding:5px 12px;border-radius:100px;font-size:11px;font-weight:800; letter-spacing:.06em;border:1px solid; } .lv-a0{background:rgba(100,116,139,.15);border-color:rgba(100,116,139,.3);color:#94A3B8} .lv-a1{background:rgba(16,185,129,.1);border-color:rgba(16,185,129,.3);color:#10B981} .lv-a2{background:rgba(6,182,212,.1);border-color:rgba(6,182,212,.3);color:#06B6D4} .lv-b1{background:rgba(59,130,246,.1);border-color:rgba(59,130,246,.3);color:#3B82F6} .lv-b2{background:rgba(139,92,246,.1);border-color:rgba(139,92,246,.3);color:#8B5CF6} .lv-c1{background:rgba(245,158,11,.1);border-color:rgba(245,158,11,.3);color:#F59E0B} .skills-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:24px} .skill-chip{ background:var(--card);border:1px solid var(--border);border-radius:12px; padding:10px 12px;font-size:12px;color:var(--muted); display:flex;align-items:center;gap:8px; } .skill-chip-icon{font-size:16px} .stats-row{display:flex;justify-content:center;gap:28px;margin-bottom:0} .stat{text-align:center} .stat-num{font-size:22px;font-weight:900;color:var(--blue)} .stat-lbl{font-size:11px;color:var(--muted);margin-top:2px} .btn-start{ width:100%;background:linear-gradient(135deg,var(--blue),var(--blue2)); color:#fff;font-weight:800;font-size:17px;border:none; border-radius:16px;padding:18px;cursor:pointer; box-shadow:0 8px 32px rgba(59,130,246,.4);font-family:inherit; transition:transform .2s; } .btn-start:active{transform:scale(.97)} .intro-est{color:var(--muted);font-size:12px;margin-top:10px} /* ══ TOPBAR ══ */ #topbar{ position:fixed;top:0;left:0;right:0;z-index:100; background:rgba(8,15,26,.93);backdrop-filter:blur(16px); padding:10px 20px 8px;display:none; } #topbar.show{display:block} .tb-row{display:flex;align-items:center;gap:12px;margin-bottom:7px} .tb-logo{font-size:13px;font-weight:800;color:var(--blue)} .tb-skill{ font-size:10px;font-weight:700;letter-spacing:.06em; border-radius:100px;padding:3px 10px; } .tb-right{margin-left:auto;display:flex;align-items:center;gap:10px} .tb-lvl{font-size:11px;font-weight:700;color:var(--gold)} .tb-count{font-size:11px;color:var(--white);font-weight:700} .prog-track{height:3px;background:rgba(255,255,255,.08);border-radius:100px;overflow:hidden} .prog-fill{height:100%;border-radius:100px;transition:width .5s ease} /* ══ QUESTIONS ══ */ #s-q{padding:66px 0 110px;align-items:center} .q-page{width:100%;max-width:560px;padding:0 20px;animation:fadeUp .3s ease} @keyframes fadeUp{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:translateY(0)}} .q-skill-tag{ display:inline-flex;align-items:center;gap:6px; border-radius:100px;padding:5px 14px;font-size:11px; font-weight:700;letter-spacing:.06em;margin-bottom:14px; } .q-meta{font-size:12px;color:var(--muted);margin-bottom:8px} .q-text{font-size:clamp(16px,4vw,21px);font-weight:700;line-height:1.5;margin-bottom:6px} .q-context{ background:rgba(59,130,246,.07);border:1px solid rgba(59,130,246,.15); border-radius:12px;padding:14px;margin-bottom:18px; font-size:13px;color:rgba(238,242,247,.8);line-height:1.7; font-style:italic; } .q-context-label{font-size:10px;font-weight:700;color:var(--blue);letter-spacing:.08em;margin-bottom:6px;font-style:normal} /* CHOICES */ .opts{display:flex;flex-direction:column;gap:9px} .opt{ background:var(--card);border:1.5px solid var(--border); border-radius:14px;padding:14px 16px;cursor:pointer; display:flex;align-items:center;gap:12px; transition:border-color .2s,background .2s,transform .15s; } .opt:active{transform:scale(.98)} .opt.sel{border-color:var(--blue);background:rgba(59,130,246,.1)} .opt.correct{border-color:var(--green);background:rgba(16,185,129,.1);pointer-events:none} .opt.wrong{border-color:var(--red);background:rgba(239,68,68,.1);pointer-events:none} .opt.reveal{border-color:var(--green);background:rgba(16,185,129,.07);pointer-events:none} .opt-letter{ width:32px;height:32px;border-radius:50%;flex-shrink:0; background:rgba(255,255,255,.07);border:1.5px solid rgba(255,255,255,.12); display:flex;align-items:center;justify-content:center; font-size:12px;font-weight:800;color:var(--muted);transition:all .2s; } .opt.sel .opt-letter{background:var(--blue);border-color:var(--blue);color:#fff} .opt.correct .opt-letter{background:var(--green);border-color:var(--green);color:#fff} .opt.wrong .opt-letter{background:var(--red);border-color:var(--red);color:#fff} .opt.reveal .opt-letter{background:var(--green);border-color:var(--green);color:#fff} .opt-text{font-size:14px;font-weight:500;flex:1;line-height:1.4} /* FILL IN BLANK */ .fill-wrap{margin-bottom:8px} .fill-input{ width:100%;background:rgba(255,255,255,.06);border:1.5px solid var(--border); border-radius:12px;padding:14px 16px;color:var(--white);font-size:15px; font-family:inherit;outline:none;transition:border-color .2s; -webkit-appearance:none; } .fill-input:focus{border-color:var(--blue)} .fill-input.correct{border-color:var(--green);background:rgba(16,185,129,.08)} .fill-input.wrong{border-color:var(--red);background:rgba(239,68,68,.08)} .fill-hint{font-size:12px;margin-top:6px;padding:8px 12px;border-radius:8px;line-height:1.5} .fill-hint.correct{background:rgba(16,185,129,.1);color:#10B981} .fill-hint.wrong{background:rgba(239,68,68,.1);color:#EF4444} /* WRITING */ .write-wrap{margin-bottom:8px} .write-area{ width:100%;background:rgba(255,255,255,.06);border:1.5px solid var(--border); border-radius:12px;padding:14px 16px;color:var(--white);font-size:15px; font-family:inherit;outline:none;resize:none;min-height:120px; transition:border-color .2s;-webkit-appearance:none;line-height:1.6; } .write-area:focus{border-color:var(--blue)} .write-counter{font-size:11px;color:var(--muted);text-align:right;margin-top:4px} .write-guide{ background:rgba(59,130,246,.07);border:1px solid rgba(59,130,246,.15); border-radius:10px;padding:10px 14px;font-size:12px; color:rgba(238,242,247,.7);margin-bottom:12px;line-height:1.6; } /* FEEDBACK TOAST */ .feedback-bar{ position:fixed;bottom:90px;left:50%;transform:translateX(-50%) translateY(60px); border-radius:12px;padding:10px 20px;font-size:13px;font-weight:700; display:flex;align-items:center;gap:8px;z-index:200; transition:transform .3s cubic-bezier(.34,1.56,.64,1);white-space:nowrap; font-family:inherit; } .feedback-bar.show{transform:translateX(-50%) translateY(0)} .feedback-bar.correct{background:rgba(16,185,129,.15);border:1px solid rgba(16,185,129,.3);color:#10B981} .feedback-bar.wrong{background:rgba(239,68,68,.15);border:1px solid rgba(239,68,68,.3);color:#EF4444} /* BOTTOM NAV */ .q-bottom{ position:fixed;bottom:0;left:0;right:0;z-index:90; background:rgba(8,15,26,.95);backdrop-filter:blur(16px); padding:12px 20px calc(12px + var(--safe-b));border-top:1px solid var(--border); display:none; } .q-bottom.show{display:block} .nav-row{display:flex;gap:10px} .btn-back{ flex:0 0 52px;height:52px;border-radius:14px; background:var(--card);border:1.5px solid var(--border); color:var(--muted);font-size:20px;cursor:pointer; display:flex;align-items:center;justify-content:center; } .btn-next{ flex:1;height:52px;border-radius:14px; background:linear-gradient(135deg,var(--blue),var(--blue2)); color:#fff;font-weight:800;font-size:16px;border:none; cursor:pointer;font-family:inherit; box-shadow:0 4px 20px rgba(59,130,246,.3);transition:opacity .2s; } .btn-next:disabled{opacity:.3;cursor:not-allowed} /* ══ LOADING ══ */ #s-load{justify-content:center;align-items:center;text-align:center;padding:40px 24px} .load-ring{ width:72px;height:72px;border-radius:50%;margin:0 auto 24px; border:3px solid rgba(59,130,246,.2);border-top:3px solid var(--blue); animation:spin 1s linear infinite; } @keyframes spin{to{transform:rotate(360deg)}} .load-title{font-size:22px;font-weight:800;margin-bottom:8px} .load-sub{font-size:14px;color:var(--muted);margin-bottom:20px} .load-steps{display:flex;flex-direction:column;gap:7px;max-width:300px;margin:0 auto} .ls{ display:flex;align-items:center;gap:10px;padding:9px 14px; background:var(--card);border:1px solid var(--border);border-radius:9px; font-size:13px;color:var(--muted); opacity:0;transform:translateY(6px);transition:all .4s; } .ls.show{opacity:1;transform:translateY(0)} .ls-dot{width:5px;height:5px;border-radius:50%;background:var(--blue);flex-shrink:0} /* ══ CAPTURE ══ */ #s-cap{padding:40px 20px calc(40px + var(--safe-b));align-items:center;overflow-y:auto} .cap-wrap{width:100%;max-width:460px} .cap-icon{font-size:36px;text-align:center;margin-bottom:16px} .cap-title{font-size:26px;font-weight:900;text-align:center;margin-bottom:8px;line-height:1.2} .cap-title em{ background:linear-gradient(135deg,var(--blue),var(--teal)); -webkit-background-clip:text;-webkit-text-fill-color:transparent; background-clip:text;font-style:normal; } .cap-sub{font-size:14px;color:var(--muted);text-align:center;line-height:1.7;margin-bottom:24px} .form-field{margin-bottom:12px} .field-label{font-size:12px;font-weight:700;color:var(--muted);letter-spacing:.04em;margin-bottom:5px;display:block} .field-label span{color:var(--blue)} .field-input{ width:100%;background:rgba(255,255,255,.06);border:1.5px solid var(--border); border-radius:12px;padding:13px 15px;color:var(--white);font-size:15px; font-family:inherit;outline:none;transition:border-color .2s;-webkit-appearance:none; } .field-input:focus{border-color:var(--blue)} .field-input.err{border-color:var(--red)} .form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px} .btn-submit{ width:100%;background:linear-gradient(135deg,var(--blue),var(--blue2)); color:#fff;font-weight:800;font-size:16px;border:none; border-radius:16px;padding:17px;cursor:pointer;font-family:inherit; box-shadow:0 6px 24px rgba(59,130,246,.35);margin-top:8px; transition:transform .2s; } .btn-submit:active{transform:scale(.97)} .btn-submit:disabled{opacity:.4;cursor:not-allowed} .cap-privacy{font-size:11px;color:var(--muted);text-align:center;margin-top:12px;line-height:1.6} /* ══ RESULT ══ */ #s-res{padding:0 0 60px;align-items:center;overflow-y:auto} .res-wrap{width:100%;max-width:580px;padding:0 16px} /* LEVEL HERO */ .res-hero{ background:linear-gradient(160deg,var(--bg2),var(--bg3)); padding:36px 20px 28px;text-align:center; border-bottom:1px solid var(--border);margin-bottom:14px; } .level-ring{ width:110px;height:110px;border-radius:50%;margin:0 auto 16px; display:flex;align-items:center;justify-content:center; font-size:36px;font-weight:900; border:4px solid;position:relative; } .level-ring::before{ content:'';position:absolute;inset:-8px;border-radius:50%; border:2px solid;opacity:.2; } .level-label{font-size:13px;font-weight:700;letter-spacing:.1em;margin-bottom:6px;text-transform:uppercase} .level-name{font-size:clamp(26px,6vw,38px);font-weight:900;margin-bottom:6px;line-height:1.1} .level-desc{font-size:14px;color:rgba(238,242,247,.75);line-height:1.7;max-width:380px;margin:0 auto 16px} .score-summary{ display:inline-flex;align-items:center;gap:6px; border-radius:100px;padding:8px 20px;font-size:13px;font-weight:700; } /* SKILL BREAKDOWN */ .res-sec{ background:var(--card);border:1px solid var(--border); border-radius:16px;padding:18px;margin-bottom:12px; } .res-sec-hd{font-size:15px;font-weight:800;margin-bottom:14px;display:flex;align-items:center;gap:8px} .sec-ico{width:30px;height:30px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:15px;flex-shrink:0} .skill-rows{display:flex;flex-direction:column;gap:10px} .skill-row{display:flex;align-items:center;gap:10px} .skill-name{font-size:13px;font-weight:600;width:80px;flex-shrink:0} .skill-bar-wrap{flex:1;height:8px;background:rgba(255,255,255,.08);border-radius:100px;overflow:hidden} .skill-bar-fill{height:100%;border-radius:100px;transition:width 1s ease} .skill-pct{font-size:12px;font-weight:700;width:36px;text-align:right;flex-shrink:0} .skill-lv{font-size:10px;font-weight:700;padding:2px 7px;border-radius:100px;flex-shrink:0} /* ADAPTIVE PATH */ .path-items{display:flex;flex-direction:column;gap:0} .path-item{display:flex;gap:12px;padding-bottom:14px;position:relative} .path-item:not(:last-child)::after{content:'';position:absolute;left:11px;top:24px;bottom:0;width:1px;background:var(--border)} .path-dot{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;flex-shrink:0;margin-top:2px;border:2px solid} .path-body{flex:1} .path-week{font-size:10px;font-weight:700;letter-spacing:.06em;margin-bottom:3px} .path-title{font-size:14px;font-weight:700;margin-bottom:4px} .path-desc{font-size:12px;color:var(--muted);line-height:1.6} /* PROGRAM RECS */ .prog-list{display:flex;flex-direction:column;gap:8px} .prog-item{ display:flex;align-items:center;gap:12px;padding:12px 14px; background:rgba(255,255,255,.03);border:1px solid var(--border);border-radius:12px; } .prog-item.ready{border-color:rgba(16,185,129,.3);background:rgba(16,185,129,.05)} .prog-item.soon{border-color:rgba(245,158,11,.3);background:rgba(245,158,11,.05)} .prog-item.later{opacity:.55} .prog-flag{font-size:22px;flex-shrink:0} .prog-info{flex:1} .prog-name{font-size:14px;font-weight:700} .prog-req{font-size:11px;color:var(--muted);margin-top:2px} .prog-status{font-size:10px;font-weight:700;padding:3px 9px;border-radius:100px;flex-shrink:0} .prog-status.ready{background:rgba(16,185,129,.15);color:#10B981} .prog-status.soon{background:rgba(245,158,11,.15);color:#F59E0B} .prog-status.later{background:rgba(100,116,139,.15);color:#94A3B8} /* WA GATE */ #s-wa{ justify-content:center;align-items:center; padding:40px 24px calc(40px + var(--safe-b)); background:linear-gradient(160deg,#080F1A 0%,#0D1A30 60%,#080F1A 100%); } .wa-wrap{width:100%;max-width:420px;text-align:center} .wa-icon{ width:72px;height:72px;border-radius:50%; background:linear-gradient(135deg,#25D366,#128C7E); display:flex;align-items:center;justify-content:center; font-size:32px;margin:0 auto 18px; box-shadow:0 8px 28px rgba(37,211,102,.4); } .wa-title{font-size:24px;font-weight:900;margin-bottom:8px;line-height:1.2} .wa-title em{color:#25D366;font-style:normal} .wa-sub{font-size:14px;color:var(--muted);line-height:1.7;margin-bottom:22px} .wa-preview{ background:rgba(37,211,102,.07);border:1px solid rgba(37,211,102,.2); border-radius:14px;padding:14px;margin-bottom:20px;text-align:left; } .wa-preview-title{font-size:10px;font-weight:700;color:#25D366;letter-spacing:.07em;margin-bottom:8px} .wa-preview-row{display:flex;align-items:center;gap:7px;margin-bottom:5px;font-size:12px;color:rgba(238,242,247,.8)} .wa-preview-row:last-child{margin-bottom:0} .wa-dot{width:4px;height:4px;border-radius:50%;background:#25D366;flex-shrink:0} .btn-wa-send{ width:100%;background:linear-gradient(135deg,#25D366,#128C7E); color:#fff;font-weight:900;font-size:17px;border:none;border-radius:16px; padding:18px;cursor:pointer;font-family:inherit;margin-bottom:10px; box-shadow:0 6px 24px rgba(37,211,102,.4); display:flex;align-items:center;justify-content:center;gap:8px; transition:transform .2s; animation:pulse-wa 2.5s ease-in-out infinite; } @keyframes pulse-wa{0%,100%{box-shadow:0 6px 24px rgba(37,211,102,.4)}50%{box-shadow:0 10px 40px rgba(37,211,102,.65)}} .btn-wa-send:active{transform:scale(.97);animation:none} .btn-skip{width:100%;background:transparent;border:none;color:var(--muted);font-size:13px;cursor:pointer;font-family:inherit;padding:8px;text-decoration:underline;text-underline-offset:3px} /* TOAST */ .toast{ position:fixed;bottom:20px;left:50%;transform:translateX(-50%) translateY(80px); background:linear-gradient(135deg,#1A4A2A,#128C7E);color:#fff; border-radius:100px;padding:11px 22px;font-size:13px;font-weight:700; display:flex;align-items:center;gap:7px;z-index:999; box-shadow:0 6px 24px rgba(37,211,102,.4);white-space:nowrap; transition:transform .4s cubic-bezier(.34,1.56,.64,1);font-family:inherit; } .toast.show{transform:translateX(-50%) translateY(0)} /* ALERTS */ .alert{border-radius:12px;padding:12px 14px;margin-bottom:8px;display:flex;gap:10px} .alert.info{background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.2)} .alert.warn{background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.2)} .alert.good{background:rgba(16,185,129,.08);border:1px solid rgba(16,185,129,.2)} .alert-ico{font-size:15px;flex-shrink:0} .alert-txt{font-size:13px;color:rgba(238,242,247,.85);line-height:1.6} /* CTA */ .res-cta{ background:var(--bg2);border:1px solid var(--border); border-radius:16px;padding:22px;text-align:center;margin:0 0 12px; } .cta-title{font-size:20px;font-weight:900;margin-bottom:8px} .cta-sub{font-size:13px;color:var(--muted);line-height:1.7;margin-bottom:18px} .btn-wa-res{ width:100%;background:linear-gradient(135deg,#25D366,#128C7E); color:#fff;font-weight:800;font-size:15px;border:none; border-radius:14px;padding:15px;cursor:pointer;font-family:inherit; display:flex;align-items:center;justify-content:center;gap:8px;margin-bottom:10px; transition:transform .2s; } .btn-wa-res:active{transform:scale(.97)} .btn-restart{ width:100%;background:transparent;border:1.5px solid var(--border); color:var(--muted);border-radius:14px;padding:13px; font-size:14px;cursor:pointer;font-family:inherit; } @media(max-width:360px){.scale-btn{font-size:13px}.intro-title{font-size:28px}} @media(min-width:481px){.form-row{grid-template-columns:1fr 1fr}} </style> </head> <body> <!-- ══ INTRO ══ --> <div class="screen active" id="s-intro"> <div class="intro-inner"> <div> <div class="brand-pill">✦ STAR4HIRE ENGLISH ASSESSMENT</div> <h1 class="intro-title">Test Your<br><em>English Level</em></h1> <p class="intro-sub">Tes adaptif berbasis CEFR — makin banyak jawaban benar, makin sulit soalnya. Cocok untuk semua peserta program STAR4Hire.</p> <div class="levels-row"> <div class="lv-badge lv-a0">A0</div> <div class="lv-badge lv-a1">A1</div> <div class="lv-badge lv-a2">A2</div> <div class="lv-badge lv-b1">B1</div> <div class="lv-badge lv-b2">B2</div> <div class="lv-badge lv-c1">C1+</div> </div> <div class="skills-grid"> <div class="skill-chip"><span class="skill-chip-icon">📖</span>Reading</div> <div class="skill-chip"><span class="skill-chip-icon">📚</span>Vocabulary</div> <div class="skill-chip"><span class="skill-chip-icon">✏️</span>Grammar</div> <div class="skill-chip"><span class="skill-chip-icon">💬</span>Writing</div> </div> <div class="stats-row"> <div class="stat"><div class="stat-num">40</div><div class="stat-lbl">Soal</div></div> <div class="stat"><div class="stat-num">Adaptive</div><div class="stat-lbl">Engine</div></div> <div class="stat"><div class="stat-num">15'</div><div class="stat-lbl">Estimasi</div></div> </div> </div> <div> <button class="btn-start" onclick="startTest()">Mulai Test →</button> <p class="intro-est">Hospitality context · CEFR aligned · A0 sampai C1</p> </div> </div> </div> <!-- ══ TOPBAR ══ --> <div id="topbar"> <div class="tb-row"> <span class="tb-logo">STAR4Hire</span> <span class="tb-skill" id="tbSkill"></span> <div class="tb-right"> <span class="tb-lvl" id="tbLvl"></span> <span class="tb-count" id="tbCount">0/40</span> </div> </div> <div class="prog-track"><div class="prog-fill" id="progFill" style="width:0%"></div></div> </div> <!-- ══ QUESTIONS ══ --> <div class="screen" id="s-q"> <div class="q-page" id="qPage"></div> </div> <div class="q-bottom" id="qBottom"> <div class="nav-row"> <button class="btn-back" id="btnBack" onclick="nav(-1)" style="visibility:hidden">←</button> <button class="btn-next" id="btnNext" disabled onclick="nav(1)">Lanjut →</button> </div> </div> <!-- ══ LOADING ══ --> <div class="screen" id="s-load"> <div class="load-ring"></div> <div class="load-title">Analysing Your English…</div> <div class="load-sub">Memproses semua jawaban kamu</div> <div class="load-steps"> <div class="ls" id="ls0"><div class="ls-dot"></div>Evaluating Reading comprehension</div> <div class="ls" id="ls1"><div class="ls-dot"></div>Scoring Vocabulary range</div> <div class="ls" id="ls2"><div class="ls-dot"></div>Checking Grammar accuracy</div> <div class="ls" id="ls3"><div class="ls-dot"></div>Assessing Writing ability</div> <div class="ls" id="ls4"><div class="ls-dot"></div>Mapping CEFR level & study plan</div> </div> </div> <!-- ══ CAPTURE ══ --> <div class="screen" id="s-cap"> <div class="cap-wrap"> <div class="cap-icon">🎯</div> <h2 class="cap-title">Result<br><em>Siap!</em></h2> <p class="cap-sub">Masukkan data diri untuk melihat level dan study plan lengkap kamu.</p> <div class="form-row"> <div class="form-field"> <label class="field-label">Nama <span>*</span></label> <input type="text" id="f-name" class="field-input" placeholder="Nama kamu" autocomplete="name"> </div> <div class="form-field"> <label class="field-label">Usia</label> <input type="number" id="f-age" class="field-input" placeholder="Usia" min="15" max="40" inputmode="numeric"> </div> </div> <div class="form-field"> <label class="field-label">Nomor WhatsApp <span>*</span></label> <input type="tel" id="f-wa" class="field-input" placeholder="08xx-xxxx-xxxx" inputmode="tel" autocomplete="tel"> </div> <div class="form-field"> <label class="field-label">Email <span>*</span></label> <input type="email" id="f-email" class="field-input" placeholder="email@kamu.com" inputmode="email" autocomplete="email"> </div> <div class="form-field"> <label class="field-label">Program yang diminati</label> <select id="f-prog" class="field-input" style="-webkit-appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%2364748B' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 14px center;padding-right:36px;cursor:pointer"> <option value="">-- Pilih program --</option> <option value="J1 USA">J1 USA Internship</option> <option value="F1 USA">F1 USA Study/MBA</option> <option value="Taiwan">Taiwan Internship</option> <option value="Hong Kong">Hong Kong Internship</option> <option value="Malaysia">Malaysia Internship</option> <option value="TND">TND — The New DreamLeap</option> </select> </div> <button class="btn-submit" id="btnSubmit" onclick="doSubmit()">Lihat Level & Study Plan →</button> <p class="cap-privacy">🔒 Data hanya digunakan tim STAR4Hire untuk konsultasi karier.</p> </div> </div> <!-- ══ WA GATE ══ --> <div class="screen" id="s-wa"> <div class="wa-wrap"> <div class="wa-icon">💬</div> <h2 class="wa-title">Hasil<br><em>Sudah Siap!</em></h2> <div class="wa-sub">Kirim hasil test ke Career Consultant STAR4Hire untuk mendapatkan panduan belajar yang dipersonalisasi.</div> <div class="wa-preview"> <div class="wa-preview-title">YANG AKAN DIKIRIM:</div> <div class="wa-preview-row"><div class="wa-dot"></div>Nama, WA, email, program target</div> <div class="wa-preview-row"><div class="wa-dot"></div>Level CEFR (A0–C1)</div> <div class="wa-preview-row"><div class="wa-dot"></div>Skor per skill (Reading, Vocab, Grammar, Writing)</div> <div class="wa-preview-row"><div class="wa-dot"></div>Program yang bisa dilamar sekarang</div> <div class="wa-preview-row"><div class="wa-dot"></div>90-day study plan rekomendasi</div> </div> <button class="btn-wa-send" onclick="sendWAandContinue()"> <span style="font-size:20px">💬</span> Kirim ke Career Consultant & Lihat Hasil </button> <button class="btn-skip" onclick="skipAndContinue()">Lewati, langsung lihat hasil →</button> </div> </div> <!-- ══ RESULT ══ --> <div class="screen" id="s-res"> <div class="res-wrap" id="resWrap"></div> </div> <!-- FEEDBACK & TOAST --> <div class="feedback-bar" id="feedbackBar"></div> <div class="toast" id="toast">✅ Hasil terkirim ke Career Consultant!</div> <script> // ═══════════════════════════════════════════════════════════ // QUESTION BANK — 60 questions across 4 skills, 6 levels // ═══════════════════════════════════════════════════════════ const QB = { reading: [ // A0-A1 {id:'r1',level:0,type:'mc', context:'Sign at a hotel: "BREAKFAST: 7:00 AM – 10:00 AM"', text:'What time does breakfast start?', opts:['7:00 AM','10:00 AM','7:00 PM','12:00 PM'],correct:0}, {id:'r2',level:0,type:'mc', context:'Menu: "Nasi Goreng — Fried rice with egg and vegetables — Rp 35,000"', text:'What is in Nasi Goreng?', opts:['Only rice','Rice, egg and vegetables','Rice and chicken','Egg and chicken'],correct:1}, {id:'r3',level:1,type:'mc', context:'Hotel notice: "The swimming pool is closed for maintenance on Monday and Tuesday. It opens again on Wednesday at 8 AM."', text:'When can guests use the pool again?', opts:['Monday morning','Tuesday afternoon','Wednesday at 8 AM','Thursday'],correct:2}, {id:'r4',level:1,type:'mc', context:'Room service menu: "All orders placed after 11 PM will have an additional late-night surcharge of 15%."', text:'What happens if you order at midnight?', opts:['Free delivery','15% extra charge','Order is not accepted','Faster delivery'],correct:1}, // A2 {id:'r5',level:2,type:'mc', context:'Email from manager: "Please ensure that all guest rooms are checked by 2 PM. If any room has not been serviced, report immediately to the front office so we can inform the guests."', text:'What should staff do if a room is not ready by 2 PM?', opts:['Wait until 3 PM','Ask the guest to wait','Report to the front office','Clean the room after 2 PM'],correct:2}, {id:'r6',level:2,type:'mc', context:'"Guests who wish to extend their stay must notify the front desk before 12 noon on the day of departure. Late notification may result in additional charges."', text:'What is the consequence of late notification for stay extension?', opts:['Free extension','Room upgrade','Additional charges','Automatic extension'],correct:2}, // B1 {id:'r7',level:3,type:'mc', context:'"A recent survey showed that 78% of hotel guests consider staff attitude more important than room quality when rating their overall experience. However, physical comfort ranked highest among business travellers specifically."', text:'According to the survey, what did business travellers value most?', opts:['Staff attitude','Room quality','Physical comfort','Overall experience'],correct:2}, {id:'r8',level:3,type:'mc', context:'"The concierge service at luxury hotels is evolving. Rather than simply booking restaurants, modern concierges are expected to anticipate guest needs before they are expressed — a skill that requires both intuition and deep local knowledge."', text:'What distinguishes a modern concierge from a traditional one?', opts:['Booking restaurants faster','Anticipating needs before guests express them','Having more staff','Working longer hours'],correct:1}, // B2 {id:'r9',level:4,type:'mc', context:'"The concept of \'bleisure\' — blending business with leisure travel — has fundamentally altered hotel revenue strategies. Properties that once catered exclusively to corporate clients are now redesigning spaces to accommodate the spontaneous extension of business trips into personal holidays, creating hybrid offerings that blur traditional room categories."', text:'What has the "bleisure" trend caused hotels to do?', opts:['Focus only on business clients','Reduce room prices','Redesign spaces for hybrid business-leisure use','Create separate hotels for tourists'],correct:2}, {id:'r10',level:4,type:'mc', context:'"Employee retention in the hospitality sector remains disproportionately low compared to other service industries, with turnover rates exceeding 70% annually in some markets. Scholars attribute this not merely to compensation disparities but to a fundamental misalignment between the emotional labour demands of the role and the psychological support structures offered by employers."', text:'According to the text, why is staff turnover high in hospitality?', opts:['Only because of low pay','Poor working conditions alone','Mismatch between emotional demands and employer support','Long working hours'],correct:2}, // C1 {id:'r11',level:5,type:'mc', context:'"The paradox of luxury hospitality lies in its simultaneous pursuit of exclusivity and scale. As global hotel chains acquire independent boutique properties, there is an inherent tension between maintaining the bespoke character that defined the original offering and achieving the operational efficiencies that justify the acquisition premium."', text:'What central tension does the passage describe?', opts:['The cost of luxury vs affordability','Exclusivity vs operational efficiency in hotel acquisitions','Staff training vs guest satisfaction','Technology vs personal service'],correct:1}, {id:'r12',level:5,type:'mc', context:'"Neurological research increasingly informs hospitality design. Studies on ambient scent, acoustic engineering, and biophilic elements suggest that environmental subconscious cues influence guest satisfaction scores more significantly than many consciously evaluated metrics, challenging the primacy of rational service quality assessment."', text:'What does this research challenge?', opts:['The importance of staff training','That rational service quality is the primary driver of satisfaction','Hotel design standards','Guest review systems'],correct:1}, ], vocabulary: [ // A0-A1 {id:'v1',level:0,type:'mc', text:'A person who stays at a hotel is called a ___.', opts:['worker','guest','chef','cleaner'],correct:1}, {id:'v2',level:0,type:'mc', text:'The place in a hotel where you pay and get your room key is called the ___.', opts:['restaurant','swimming pool','front desk','elevator'],correct:2}, {id:'v3',level:1,type:'mc', text:'When you arrive at a hotel and register, you ___.', opts:['check out','check in','book out','sign off'],correct:1}, {id:'v4',level:1,type:'mc', text:'The hotel staff who carry guests\' bags to their room are called ___.', opts:['concierge','bellboy','receptionist','housekeeper'],correct:1}, // A2 {id:'v5',level:2,type:'mc', text:'A room with two separate beds is called a ___ room.', opts:['suite','twin','double','single'],correct:1}, {id:'v6',level:2,type:'fill', text:'The person who manages and plans what food is cooked in a restaurant kitchen is called an _____ chef.', answer:'executive',hints:['e','x']}, // B1 {id:'v7',level:3,type:'mc', text:'The term "concierge" refers to a hotel employee who ___.', opts:['cleans the rooms','helps guests with reservations and recommendations','cooks the food','manages hotel finances'],correct:1}, {id:'v8',level:3,type:'mc', text:'"Mise en place" in culinary context means ___.', opts:['a French dessert','preparing and organizing ingredients before cooking','a type of knife cut','a formal dining style'],correct:1}, {id:'v9',level:3,type:'fill', text:'When a hotel is fully booked with no available rooms, it is said to be at full ______.', answer:'occupancy',hints:['o','c']}, // B2 {id:'v10',level:4,type:'mc', text:'The word "bespoke" in hospitality means ___.', opts:['standard','budget-friendly','custom-made for a specific guest','temporarily unavailable'],correct:2}, {id:'v11',level:4,type:'mc', text:'"RevPAR" is a hospitality metric that stands for ___.', opts:['Revenue Per Available Room','Reservation Per Available Rate','Revenue Per Accommodated Room','Room Performance Average Report'],correct:0}, {id:'v12',level:4,type:'fill', text:'The art and science of selling the right room to the right guest at the right price is called revenue ________.', answer:'management',hints:['m','a']}, // C1 {id:'v13',level:5,type:'mc', text:'The term "bleisure" is a portmanteau combining ___.', opts:['breakfast and leisure','business and leisure','blend and pleasure','brief and leisure'],correct:1}, {id:'v14',level:5,type:'mc', text:'In hospitality, "yield management" is most closely associated with ___.', opts:['staff scheduling','dynamic pricing to maximize revenue','food waste reduction','guest loyalty programs'],correct:1}, ], grammar: [ // A0-A1 {id:'g1',level:0,type:'mc', text:'Choose the correct sentence:', opts:['I works at a hotel.','I work at a hotel.','I working at a hotel.','I am work at a hotel.'],correct:1}, {id:'g2',level:0,type:'mc', text:'___ is your name?', opts:['Who','What','Where','Which'],correct:1}, {id:'g3',level:1,type:'mc', text:'The guest ___ in the restaurant right now.', opts:['eat','eats','is eating','are eat'],correct:2}, {id:'g4',level:1,type:'mc', text:'Choose the correct question: "You want coffee or tea?" — "___ coffee, please."', opts:['I like','I would like','I will like','I am like'],correct:1}, // A2 {id:'g5',level:2,type:'mc', text:'The room ___ cleaned before the guests arrived.', opts:['was','were','is','has'],correct:0}, {id:'g6',level:2,type:'mc', text:'I have worked at this hotel ___ three years.', opts:['since','for','during','while'],correct:1}, {id:'g7',level:2,type:'fill', text:'If the guest is not satisfied, we ______ offer a complimentary upgrade. (obligation)', answer:'should',hints:['s','h']}, // B1 {id:'g8',level:3,type:'mc', text:'The manager asked the staff ___ be more careful with guest requests.', opts:['to','that','for','if'],correct:0}, {id:'g9',level:3,type:'mc', text:'"___ you please ensure the meeting room is set up by 2 PM?" — Select the most professional option:', opts:['Can','Could','Will','Do'],correct:1}, {id:'g10',level:3,type:'fill', text:'The new policy, _______ was introduced last month, requires all staff to wear name badges.', answer:'which',hints:['w','h']}, // B2 {id:'g11',level:4,type:'mc', text:'"Had I known about the complaint earlier, I ___ addressed it immediately."', opts:['will have','would have','would','should'],correct:1}, {id:'g12',level:4,type:'mc', text:'Select the sentence with correct use of the passive voice in a formal report:', opts:[ 'The management team decided to revise the policy.', 'A decision to revise the policy was made by the management team.', 'They have revised the policy already.', 'The policy is revising now.' ],correct:1}, {id:'g13',level:4,type:'fill', text:'Despite _______ busy, the front desk manager always greets guests with a smile.', answer:'being',hints:['b','e']}, // C1 {id:'g14',level:5,type:'mc', text:'Which sentence uses a nominalization correctly in a formal business context?', opts:[ 'We need to decide quickly.', 'A swift decision is required.', 'We must be deciding now.', 'Deciding is what we should do.' ],correct:1}, {id:'g15',level:5,type:'fill', text:'The extent ______ employee satisfaction influences guest experience cannot be overstated.', answer:'to which',hints:['t','w']}, ], writing: [ // A1 {id:'w1',level:1,type:'write', text:'Write 2-3 sentences about your work experience in hospitality.', guide:'Include: where you worked, what you did, how long you worked there.', minWords:15, scoreKey:'experience', rubric:{keywords:['worked','job','hotel','restaurant','months','years','staff','service','position'],grammar_weight:0.4}}, // A2 {id:'w2',level:2,type:'write', text:'A guest calls and says their room is too cold. Write your response to the guest.', guide:'Be polite and professional. Offer a solution. Use "I" statements.', minWords:25, scoreKey:'guest_response', rubric:{keywords:['sorry','apologize','help','fix','send','maintenance','comfortable','immediately','inconvenience'],grammar_weight:0.5}}, // B1 {id:'w3',level:3,type:'write', text:'Why do you want to work abroad in hospitality? Write a short paragraph.', guide:'Give 2-3 specific reasons. Mention what you hope to learn or achieve.', minWords:40, scoreKey:'motivation', rubric:{keywords:['experience','international','develop','skill','culture','career','opportunity','grow','professional'],grammar_weight:0.5}}, // B2 {id:'w4',level:4,type:'write', text:'Describe a challenging situation you faced at work and how you handled it. What did you learn?', guide:'Use past tense. Be specific about the challenge, your actions, and the outcome.', minWords:60, scoreKey:'situation', rubric:{keywords:['challenge','difficult','problem','solved','learned','result','team','communication','experience','outcome'],grammar_weight:0.6}}, // C1 {id:'w5',level:5,type:'write', text:'In your opinion, how is technology changing the hospitality industry? Discuss both benefits and challenges.', guide:'Present a balanced argument. Use linking words (however, on the other hand, furthermore).', minWords:80, scoreKey:'technology', rubric:{keywords:['technology','digital','automation','personalization','efficiency','human','experience','data','challenge','benefit','however','furthermore','although'],grammar_weight:0.7}}, ], }; // ═══════════════════════════════════════════════════════════ // ADAPTIVE ENGINE CONFIG // ═══════════════════════════════════════════════════════════ const SKILL_CONFIG = { reading: {color:'#3B82F6',bg:'rgba(59,130,246,.15)',icon:'📖',label:'Reading', questions:12}, vocabulary: {color:'#8B5CF6',bg:'rgba(139,92,246,.15)',icon:'📚',label:'Vocabulary',questions:14}, grammar: {color:'#06B6D4',bg:'rgba(6,182,212,.15)', icon:'✏️',label:'Grammar', questions:15}, writing: {color:'#F59E0B',bg:'rgba(245,158,11,.15)',icon:'💬',label:'Writing', questions:5}, }; const LEVELS = [ {id:'A0',name:'Beginner', color:'#94A3B8',min:0, max:15, prog:'none'}, {id:'A1',name:'Elementary', color:'#10B981',min:16, max:30, prog:'Malaysia only (basic)'}, {id:'A2',name:'Pre-Intermediate',color:'#06B6D4',min:31,max:45,prog:'Malaysia, Taiwan (dengan persiapan)'}, {id:'B1',name:'Intermediate', color:'#3B82F6',min:46, max:60, prog:'Taiwan, Hong Kong'}, {id:'B2',name:'Upper-Intermediate',color:'#8B5CF6',min:61,max:75,prog:'Hong Kong, J1 USA'}, {id:'C1',name:'Advanced', color:'#F59E0B',min:76, max:100,prog:'J1 USA, F1 MBA — fully ready'}, ]; // ═══════════════════════════════════════════════════════════ // STATE // ═══════════════════════════════════════════════════════════ let state = { currentSkill: 'reading', skillOrder: ['reading','vocabulary','grammar','writing'], skillIdx: 0, qIdx: 0, adaptiveLevel: 2, // start at A2 (index 2) answers: {}, // {qid: {answer, correct, score, time}} skillScores: {reading:[], vocabulary:[], grammar:[], writing:[]}, questionQueue: [], // current skill's question queue totalAnswered: 0, startTime: 0, uData: {}, }; const TOTAL_Q = 40; // ═══════════════════════════════════════════════════════════ // QUESTION SELECTION (Adaptive) // ═══════════════════════════════════════════════════════════ function buildQueue(skill) { const pool = QB[skill]; const cfg = SKILL_CONFIG[skill]; const queue = []; let lvl = state.adaptiveLevel; for (let i = 0; i < cfg.questions; i++) { // Find questions at current level const candidates = pool.filter(q => q.level === lvl && !queue.find(qq => qq.id === q.id) ); if (candidates.length > 0) { const q = candidates[Math.floor(Math.random() * candidates.length)]; queue.push({...q, _targetLevel: lvl}); } else { // No more at this level — try adjacent levels const adj = [lvl-1, lvl+1, lvl-2, lvl+2].filter(l => l >= 0 && l <= 5); for (const al of adj) { const ac = pool.filter(q => q.level === al && !queue.find(qq => qq.id === q.id)); if (ac.length > 0) { const q = ac[Math.floor(Math.random() * ac.length)]; queue.push({...q, _targetLevel: al}); break; } } } } return queue; } function adjustLevel(correct) { if (correct && state.adaptiveLevel < 5) { // 2 correct in a row → go up state._correctStreak = (state._correctStreak || 0) + 1; if (state._correctStreak >= 2) { state.adaptiveLevel = Math.min(5, state.adaptiveLevel + 1); state._correctStreak = 0; } } else if (!correct) { state._correctStreak = 0; state.adaptiveLevel = Math.max(0, state.adaptiveLevel - 1); } } // ═══════════════════════════════════════════════════════════ // UI HELPERS // ═══════════════════════════════════════════════════════════ function show(id) { document.querySelectorAll('.screen').forEach(s => s.classList.remove('active')); document.getElementById('s-'+id).classList.add('active'); window.scrollTo(0,0); } function updateTopbar() { const skill = state.currentSkill; const cfg = SKILL_CONFIG[skill]; const lv = LEVELS[state.adaptiveLevel]; const pct = Math.round((state.totalAnswered / TOTAL_Q) * 100); document.getElementById('tbSkill').textContent = cfg.icon + ' ' + cfg.label; document.getElementById('tbSkill').style.cssText = `background:${cfg.bg};color:${cfg.color};border:1px solid ${cfg.color}44`; document.getElementById('tbLvl').textContent = lv.id; document.getElementById('tbCount').textContent = state.totalAnswered + '/' + TOTAL_Q; document.getElementById('progFill').style.width = pct + '%'; document.getElementById('progFill').style.background = `linear-gradient(90deg,${cfg.color},${LEVELS[Math.min(5,state.adaptiveLevel+1)].color})`; } function showFeedback(correct, correctText) { const bar = document.getElementById('feedbackBar'); bar.className = 'feedback-bar ' + (correct ? 'correct' : 'wrong'); bar.innerHTML = correct ? '✓ Correct!' : `✗ Answer: <strong>${correctText}</strong>`; bar.classList.add('show'); setTimeout(() => bar.classList.remove('show'), 1800); } // ═══════════════════════════════════════════════════════════ // START TEST // ═══════════════════════════════════════════════════════════ function startTest() { state.startTime = Date.now(); state.currentSkill = 'reading'; state.skillIdx = 0; state.questionQueue = buildQueue('reading'); state.qIdx = 0; document.getElementById('topbar').classList.add('show'); document.getElementById('qBottom').classList.add('show'); show('q'); renderQ(); } // ═══════════════════════════════════════════════════════════ // RENDER QUESTION // ═══════════════════════════════════════════════════════════ function renderQ() { const skill = state.currentSkill; const cfg = SKILL_CONFIG[skill]; const q = state.questionQueue[state.qIdx]; updateTopbar(); if (!q) { nextSkill(); return; } const answered = state.answers[q.id]; let html = ''; // Skill badge html += `<div class="q-skill-tag" style="background:${cfg.bg};border:1px solid ${cfg.color}44;color:${cfg.color}">${cfg.icon} ${cfg.label}</div>`; // Level indicator const lv = LEVELS[q.level || state.adaptiveLevel]; html += `<div class="q-meta">Soal ${state.qIdx + 1} dari ${SKILL_CONFIG[skill].questions} · <span style="color:${lv.color};font-weight:700">${lv.id}</span></div>`; // Context if (q.context) { html += `<div class="q-context"><div class="q-context-label">📄 READ THIS FIRST</div>${q.context}</div>`; } // Question html += `<div class="q-text">${q.text}</div>`; if (q.type === 'mc') { html += '<div class="opts">'; q.opts.forEach((opt, i) => { let cls = ''; if (answered) { if (i === q.correct) cls = 'correct'; else if (i === answered.answer && i !== q.correct) cls = 'wrong'; } else if (answered === undefined) { // not answered yet } html += `<div class="opt ${cls}" onclick="${answered ? '' : `pickMC('${q.id}',${i},${q.correct},this)`}"> <div class="opt-letter">${String.fromCharCode(65+i)}</div> <div class="opt-text">${opt}</div> </div>`; }); html += '</div>'; } else if (q.type === 'fill') { const val = answered ? answered.answer : ''; const cls = answered ? (answered.correct ? 'correct' : 'wrong') : ''; html += `<div class="fill-wrap"> <input type="text" class="fill-input ${cls}" id="fill-${q.id}" value="${val}" placeholder="Type your answer here…" ${answered ? 'disabled' : `oninput="enableNext()" onkeydown="if(event.key==='Enter')submitFill('${q.id}','${q.answer}')"`}> </div>`; if (answered) { html += `<div class="fill-hint ${answered.correct ? 'correct' : 'wrong'}"> ${answered.correct ? '✓ Correct!' : `✗ Answer: <strong>${q.answer}</strong>`} </div>`; } } else if (q.type === 'write') { const val = answered ? answered.answer : ''; html += `<div class="write-guide">📝 ${q.guide}</div> <div class="write-wrap"> <textarea class="write-area" id="write-${q.id}" placeholder="Write your answer here…" oninput="countWords(this,'wc-${q.id}')" ${answered ? 'disabled' : ''}>${val}</textarea> <div class="write-counter" id="wc-${q.id}">Minimum ${q.minWords} words</div> </div>`; if (!answered) { html += `<div style="text-align:right;margin-top:8px"> <button onclick="submitWrite('${q.id}',${q.minWords})" style="background:linear-gradient(135deg,var(--gold),#D97706);color:#fff;border:none;border-radius:10px;padding:10px 20px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit">Submit Writing</button> </div>`; } } const page = document.getElementById('qPage'); page.style.opacity = '0'; page.style.transform = 'translateY(10px)'; page.innerHTML = html; requestAnimationFrame(() => { page.style.transition = 'opacity .25s,transform .25s'; page.style.opacity = '1'; page.style.transform = 'translateY(0)'; }); // Nav buttons document.getElementById('btnBack').style.visibility = (state.skillIdx === 0 && state.qIdx === 0) ? 'hidden' : 'visible'; const isAnswered = !!state.answers[q.id]; document.getElementById('btnNext').disabled = !isAnswered; document.getElementById('btnNext').textContent = isLastQuestion() ? 'Selesai ✓' : 'Lanjut →'; } function isLastQuestion() { return state.skillIdx === state.skillOrder.length - 1 && state.qIdx === state.questionQueue.length - 1; } function enableNext() { document.getElementById('btnNext').disabled = false; } function countWords(el, counterId) { const words = el.value.trim().split(/\s+/).filter(w => w.length > 0).length; const counter = document.getElementById(counterId); if (counter) { const q = state.questionQueue[state.qIdx]; counter.textContent = words + ' words' + (words < q.minWords ? ` (need ${q.minWords - words} more)` : ' ✓'); counter.style.color = words >= q.minWords ? 'var(--green)' : 'var(--muted)'; if (words >= q.minWords) enableNext(); else document.getElementById('btnNext').disabled = true; } } // ═══════════════════════════════════════════════════════════ // ANSWER HANDLERS // ═══════════════════════════════════════════════════════════ function pickMC(qid, chosen, correct, el) { const isCorrect = chosen === correct; const q = state.questionQueue[state.qIdx]; // Record answer state.answers[qid] = { answer: chosen, correct: isCorrect, type:'mc', level: q.level }; state.skillScores[state.currentSkill].push(isCorrect ? 1 : 0); state.totalAnswered++; // Visual feedback document.querySelectorAll('.opt').forEach((o, i) => { if (i === correct) o.classList.add('correct'); else if (i === chosen && !isCorrect) o.classList.add('wrong'); o.style.pointerEvents = 'none'; }); showFeedback(isCorrect, q.opts[correct]); adjustLevel(isCorrect); document.getElementById('btnNext').disabled = false; // Auto-advance after short delay on mobile setTimeout(() => { if (!isLastQuestion()) nav(1); else document.getElementById('btnNext').disabled = false; }, 900); } function submitFill(qid, correctAnswer) { const input = document.getElementById('fill-' + qid); if (!input) return; const userAnswer = input.value.trim().toLowerCase(); const isCorrect = userAnswer === correctAnswer.toLowerCase() || userAnswer.replace(/\s+/g,' ') === correctAnswer.toLowerCase(); const q = state.questionQueue[state.qIdx]; state.answers[qid] = { answer: input.value.trim(), correct: isCorrect, type:'fill', level: q.level }; state.skillScores[state.currentSkill].push(isCorrect ? 1 : 0); state.totalAnswered++; adjustLevel(isCorrect); input.disabled = true; input.classList.add(isCorrect ? 'correct' : 'wrong'); // Add hint const hint = document.createElement('div'); hint.className = 'fill-hint ' + (isCorrect ? 'correct' : 'wrong'); hint.innerHTML = isCorrect ? '✓ Correct!' : `✗ Answer: <strong>${correctAnswer}</strong>`; input.parentNode.insertBefore(hint, input.nextSibling); showFeedback(isCorrect, correctAnswer); document.getElementById('btnNext').disabled = false; setTimeout(() => { if (!isLastQuestion()) nav(1); }, 900); } function submitWrite(qid, minWords) { const area = document.getElementById('write-' + qid); if (!area) return; const text = area.value.trim(); const words = text.split(/\s+/).filter(w => w.length > 0).length; if (words < minWords) { return; } const q = state.questionQueue[state.qIdx]; // Score writing based on word count, keywords, and length const keywords = q.rubric.keywords; const textLower = text.toLowerCase(); const keywordHits = keywords.filter(k => textLower.includes(k)).length; const keywordScore = Math.min(1, keywordHits / (keywords.length * 0.5)); const lengthScore = Math.min(1, words / (minWords * 1.5)); const score = (keywordScore * 0.6 + lengthScore * 0.4); state.answers[qid] = { answer: text, correct: score > 0.5, score: score, words, type:'write', level: q.level }; state.skillScores[state.currentSkill].push(score); state.totalAnswered++; area.disabled = true; document.getElementById('btnNext').disabled = false; document.getElementById('btnNext').textContent = isLastQuestion() ? 'Selesai ✓' : 'Lanjut →'; // Show score feedback const fb = document.createElement('div'); fb.className = `alert ${score > 0.7 ? 'good' : score > 0.4 ? 'info' : 'warn'}`; fb.style.marginTop = '10px'; fb.innerHTML = `<span class="alert-ico">${score > 0.7 ? '✅' : score > 0.4 ? '💡' : '📝'}</span> <span class="alert-txt"><strong>${words} words written.</strong> ${score > 0.7 ? 'Good use of relevant vocabulary!' : score > 0.4 ? 'Good effort! Try to include more specific hospitality terms.' : 'Try to write more and use specific vocabulary related to the topic.'}</span>`; area.parentNode.parentNode.appendChild(fb); } // ═══════════════════════════════════════════════════════════ // NAVIGATION // ═══════════════════════════════════════════════════════════ function nav(dir) { if (dir > 0) { // Check if fill input needs to be submitted const q = state.questionQueue[state.qIdx]; if (q && q.type === 'fill' && !state.answers[q.id]) { const input = document.getElementById('fill-' + q.id); if (input && input.value.trim()) { submitFill(q.id, q.answer); return; } } if (isLastQuestion()) { showLoad(); return; } // Move to next question in skill or next skill if (state.qIdx < state.questionQueue.length - 1) { state.qIdx++; } else { nextSkill(); return; } } else { if (state.qIdx > 0) state.qIdx--; else if (state.skillIdx > 0) { state.skillIdx--; state.currentSkill = state.skillOrder[state.skillIdx]; state.qIdx = state.questionQueue.length - 1; } } renderQ(); window.scrollTo(0,0); } function nextSkill() { state.skillIdx++; if (state.skillIdx >= state.skillOrder.length) { showLoad(); return; } state.currentSkill = state.skillOrder[state.skillIdx]; state.questionQueue = buildQueue(state.currentSkill); state.qIdx = 0; renderQ(); } // ═══════════════════════════════════════════════════════════ // LOADING // ═══════════════════════════════════════════════════════════ function showLoad() { document.getElementById('topbar').style.display = 'none'; document.getElementById('qBottom').style.display = 'none'; show('load'); [0,1,2,3,4].forEach(i => { setTimeout(() => { const el = document.getElementById('ls'+i); if (el) el.classList.add('show'); }, (i+1) * 700); }); setTimeout(() => show('cap'), 4200); } // ═══════════════════════════════════════════════════════════ // SCORE CALCULATION // ═══════════════════════════════════════════════════════════ function calculateResults() { const skillResults = {}; let totalScore = 0; let totalWeight = 0; const weights = { reading: 0.3, vocabulary: 0.25, grammar: 0.25, writing: 0.2 }; for (const [skill, scores] of Object.entries(state.skillScores)) { if (scores.length === 0) { skillResults[skill] = { pct: 0, level: 'A0' }; continue; } const avg = scores.reduce((a,b) => a+b, 0) / scores.length; const pct = Math.round(avg * 100); const lv = getLevelFromPct(pct); skillResults[skill] = { pct, level: lv.id, color: lv.color }; totalScore += avg * weights[skill]; totalWeight += weights[skill]; } const overallPct = totalWeight > 0 ? Math.round((totalScore / totalWeight) * 100) : 0; const overallLevel = getLevelFromPct(overallPct); return { skillResults, overallPct, overallLevel }; } function getLevelFromPct(pct) { if (pct >= 90) return LEVELS[5]; if (pct >= 75) return LEVELS[4]; if (pct >= 60) return LEVELS[3]; if (pct >= 45) return LEVELS[2]; if (pct >= 28) return LEVELS[1]; return LEVELS[0]; } // ═══════════════════════════════════════════════════════════ // SUBMIT & WA // ═══════════════════════════════════════════════════════════ const CC_WA = '6287725650958'; let cachedResults = null; function doSubmit() { const name = document.getElementById('f-name').value.trim(); const wa = document.getElementById('f-wa').value.trim(); const email = document.getElementById('f-email').value.trim(); const age = document.getElementById('f-age').value.trim(); const prog = document.getElementById('f-prog').value; let ok = true; ['f-name','f-wa','f-email'].forEach(id => { const el = document.getElementById(id); if (!el.value.trim()) { el.classList.add('err'); el.addEventListener('input', () => el.classList.remove('err'), {once:true}); ok = false; } }); if (!ok) return; state.uData = { name, wa, email, age, prog }; document.getElementById('btnSubmit').disabled = true; document.getElementById('btnSubmit').textContent = 'Memproses…'; cachedResults = calculateResults(); setTimeout(() => show('wa'), 500); } function buildWAMsg() { const r = cachedResults; const u = state.uData; const lvl = r.overallLevel; const sr = r.skillResults; return ( `🎓 *HASIL ENGLISH LEVEL TEST*\n` + `━━━━━━━━━━━━━━━━━━━━━━\n` + `👤 *Nama:* ${u.name}\n` + `📱 *WA:* ${u.wa}\n` + `📧 *Email:* ${u.email}\n` + `🎂 *Usia:* ${u.age || '-'}\n` + `🎯 *Program:* ${u.prog || '-'}\n` + `━━━━━━━━━━━━━━━━━━━━━━\n` + `📊 *LEVEL CEFR: ${lvl.id} — ${lvl.name}*\n` + `Overall Score: ${r.overallPct}%\n` + `━━━━━━━━━━━━━━━━━━━━━━\n` + `📖 Reading: ${sr.reading?.pct || 0}% (${sr.reading?.level || 'A0'})\n` + `📚 Vocabulary: ${sr.vocabulary?.pct || 0}% (${sr.vocabulary?.level || 'A0'})\n` + `✏️ Grammar: ${sr.grammar?.pct || 0}% (${sr.grammar?.level || 'A0'})\n` + `💬 Writing: ${sr.writing?.pct || 0}% (${sr.writing?.level || 'A0'})\n` + `━━━━━━━━━━━━━━━━━━━━━━\n` + `✅ *Program Eligible:* ${lvl.prog}\n` + `━━━━━━━━━━━━━━━━━━━━━━\n` + `📅 ${new Date().toLocaleString('id-ID',{timeZone:'Asia/Jakarta'})}\n` + `_STAR4Hire English Level Test_` ); } function sendWAandContinue() { try { window.open(`https://wa.me/${CC_WA}?text=${encodeURIComponent(buildWAMsg())}`, '_blank'); setTimeout(() => { const t = document.getElementById('toast'); t.classList.add('show'); setTimeout(() => t.classList.remove('show'), 3000); }, 500); } catch(e){} setTimeout(() => renderResult(), 400); } function skipAndContinue() { renderResult(); } function manualSendWA() { window.open(`https://wa.me/${CC_WA}?text=${encodeURIComponent(buildWAMsg())}`, '_blank'); } // ═══════════════════════════════════════════════════════════ // RENDER RESULT // ═══════════════════════════════════════════════════════════ function renderResult() { const r = cachedResults || calculateResults(); const lvl = r.overallLevel; const sr = r.skillResults; const u = state.uData; // Study plan based on level const studyPlans = { A0: [ {week:'Minggu 1-2',title:'Master the Basics',desc:'Pelajari 200 kata benda hospitality paling umum. Gunakan aplikasi Duolingo + BBC Learning English Level 1.',color:'#94A3B8'}, {week:'Minggu 3-4',title:'Simple Sentences',desc:'Latihan membuat kalimat Subject-Verb-Object. Fokus: "I work at...", "The guest needs...", "Please...", "Can I help?"',color:'#94A3B8'}, {week:'Bulan 2',title:'Basic Conversations',desc:'Mulai kelas GEG (Global English Gateway) STAR4Hire. Practice greetings, numbers, room types, dan basic requests.',color:'#10B981'}, {week:'Bulan 3',title:'Target Level: A1',desc:'Test ulang. Target naik ke A1 sebelum mendaftar program apapun.',color:'#10B981'}, ], A1: [ {week:'Minggu 1-2',title:'Expand Vocabulary to 500 Words',desc:'Fokus pada departemen hospitality: Front Office, Housekeeping, F&B, Culinary. Flashcard harian minimal 20 kata.',color:'#10B981'}, {week:'Minggu 3-4',title:'Present & Past Tense Mastery',desc:'Latihan menceritakan pengalaman kerja dalam English. "I worked at...", "One day a guest...", "I helped them by..."',color:'#10B981'}, {week:'Bulan 2',title:'Listening Practice',desc:'BBC Learning English Everyday. TED-Ed videos dengan subtitle. 30 menit per hari.',color:'#06B6D4'}, {week:'Bulan 3',title:'Target Level: A2 + Program Malaysia',desc:'A2 sudah eligible untuk program Malaysia. Test ulang dan daftar GEG STAR4Hire.',color:'#06B6D4'}, ], A2: [ {week:'Minggu 1-2',title:'Intermediate Grammar',desc:'Past perfect, passive voice, conditional sentences (if/unless). Fokus pada konteks kerja di hotel.',color:'#06B6D4'}, {week:'Minggu 3-4',title:'Writing Practice',desc:'Latihan menulis email profesional, laporan singkat, dan response to complaints. Minta feedback dari Career Consultant.',color:'#06B6D4'}, {week:'Bulan 2',title:'Speaking Confidence',desc:'Join English conversation club online. Practice STAR method untuk interview: Situation, Task, Action, Result.',color:'#3B82F6'}, {week:'Bulan 3',title:'Target Level: B1 + TOEIC Prep',desc:'B1 membuka pintu ke Taiwan dan Hong Kong. Mulai persiapan TOEIC (target 500+).',color:'#3B82F6'}, ], B1: [ {week:'Minggu 1-2',title:'Advanced Vocabulary',desc:'Pelajari hospitality industry terms: RevPAR, yield management, concierge services, SOPs. Business English vocabulary.',color:'#3B82F6'}, {week:'Minggu 3-4',title:'Interview Skills',desc:'Mock interview dalam English: behavioral questions, STAR method, situational questions. Record dan review sendiri.',color:'#3B82F6'}, {week:'Bulan 2',title:'TOEIC Preparation',desc:'Target TOEIC 600+. Latihan listening comprehension dan reading passages. Gunakan official TOEIC practice materials.',color:'#8B5CF6'}, {week:'Bulan 3',title:'Target Level: B2 + Taiwan/HK Ready',desc:'B2 = fully ready for Taiwan and Hong Kong. Practice industry-specific presentations.',color:'#8B5CF6'}, ], B2: [ {week:'Minggu 1-2',title:'Academic & Business Writing',desc:'Latihan menulis formal reports, cover letters yang kuat, dan personal statements untuk F1/J1 applications.',color:'#8B5CF6'}, {week:'Minggu 3-4',title:'Advanced Speaking',desc:'Debate-style discussions, presentations on hospitality topics. Join Toastmasters or similar speaking club.',color:'#8B5CF6'}, {week:'Bulan 2',title:'IELTS/TOEFL Preparation',desc:'Target IELTS 6.5 / TOEFL iBT 87. Focus on academic reading and writing sections.',color:'#F59E0B'}, {week:'Bulan 3',title:'Target Level: C1 + USA Ready',desc:'C1 = ready for J1 USA and F1 MBA applications. Kamu hampir di sana!',color:'#F59E0B'}, ], C1: [ {week:'Ongoing',title:'Maintain & Specialize',desc:'Your English is advanced. Focus on industry-specific mastery: hospitality management vocabulary, financial literacy in English.',color:'#F59E0B'}, {week:'Ongoing',title:'Leadership Communication',desc:'Practice presenting data, facilitating meetings, and writing strategic documents in English. You are ready for global leadership.',color:'#F59E0B'}, {week:'Target',title:'J1 USA / F1 MBA — Fully Ready',desc:'Apply now. Your English will not hold you back. Focus on program-specific documents: Grant Application, Motivational Letter.',color:'#10B981'}, ], }; // Program eligibility const progElig = [ {flag:'🇲🇾', name:'Malaysia Internship', req:'A2+', status: r.overallPct >= 31 ? 'ready' : r.overallPct >= 16 ? 'soon' : 'later'}, {flag:'🇹🇼', name:'Taiwan Internship', req:'B1+', status: r.overallPct >= 46 ? 'ready' : r.overallPct >= 31 ? 'soon' : 'later'}, {flag:'🇭🇰', name:'Hong Kong Internship', req:'B1+', status: r.overallPct >= 46 ? 'ready' : r.overallPct >= 31 ? 'soon' : 'later'}, {flag:'🇺🇸', name:'J1 USA Internship', req:'B2+', status: r.overallPct >= 61 ? 'ready' : r.overallPct >= 46 ? 'soon' : 'later'}, {flag:'🎓', name:'F1 USA Study/MBA', req:'C1 / IELTS 6.5',status: r.overallPct >= 76 ? 'ready' : r.overallPct >= 61 ? 'soon' : 'later'}, {flag:'🇹🇷', name:'Turkey Internship', req:'A2+', status: r.overallPct >= 31 ? 'ready' : r.overallPct >= 16 ? 'soon' : 'later'}, ]; const plan = studyPlans[lvl.id] || studyPlans['A2']; let html = ''; // HERO html += `<div class="res-hero"> <div class="level-ring" style="color:${lvl.color};border-color:${lvl.color}"> <div style="position:absolute;inset:-8px;border-radius:50%;border:2px solid ${lvl.color};opacity:.2"></div> ${lvl.id} </div> <div class="level-label" style="color:${lvl.color}">${lvl.id} — ${lvl.name}</div> <div class="level-name">${u.name ? u.name + '\'s Level' : 'Your Level'}</div> <div class="level-desc">${getLevelDescription(lvl.id)}</div> <div class="score-summary" style="background:${lvl.color}18;border:1px solid ${lvl.color}44;color:${lvl.color}"> Overall Score: ${r.overallPct}% </div> </div>`; // SKILL BREAKDOWN html += `<div class="res-sec"> <div class="res-sec-hd"><div class="sec-ico" style="background:rgba(59,130,246,.15);color:#3B82F6">📊</div>Skill Breakdown</div> <div class="skill-rows">`; const skillOrder = ['reading','vocabulary','grammar','writing']; skillOrder.forEach(skill => { const s = sr[skill] || {pct:0,level:'A0',color:'#94A3B8'}; const cfg = SKILL_CONFIG[skill]; html += `<div class="skill-row"> <div class="skill-name" style="color:${cfg.color}">${cfg.icon} ${cfg.label}</div> <div class="skill-bar-wrap"><div class="skill-bar-fill" style="width:${s.pct}%;background:${cfg.color}"></div></div> <div class="skill-pct" style="color:${cfg.color}">${s.pct}%</div> <div class="skill-lv" style="background:${s.color}20;color:${s.color};border:1px solid ${s.color}44">${s.level}</div> </div>`; }); html += '</div></div>'; // PROGRAM ELIGIBILITY html += `<div class="res-sec"> <div class="res-sec-hd"><div class="sec-ico" style="background:rgba(16,185,129,.15);color:#10B981">🌏</div>Program Eligibility</div> <div class="prog-list">`; progElig.forEach(p => { const statusLabel = p.status === 'ready' ? 'SIAP' : p.status === 'soon' ? 'HAMPIR' : 'BUTUH PREP'; html += `<div class="prog-item ${p.status}"> <div class="prog-flag">${p.flag}</div> <div class="prog-info"> <div class="prog-name">${p.name}</div> <div class="prog-req">Minimum: ${p.req}</div> </div> <div class="prog-status ${p.status}">${statusLabel}</div> </div>`; }); html += '</div></div>'; // 90-DAY STUDY PLAN html += `<div class="res-sec"> <div class="res-sec-hd"><div class="sec-ico" style="background:rgba(245,158,11,.15);color:#F59E0B">📅</div>90-Day Study Plan</div> <div class="path-items">`; plan.forEach(p => { html += `<div class="path-item"> <div class="path-dot" style="background:${p.color}22;border-color:${p.color};color:${p.color}">●</div> <div class="path-body"> <div class="path-week" style="color:${p.color}">${p.week}</div> <div class="path-title">${p.title}</div> <div class="path-desc">${p.desc}</div> </div> </div>`; }); html += '</div></div>'; // WEAKEST SKILL ALERT const skillPcts = skillOrder.map(s => ({skill:s,pct:sr[s]?.pct||0})); const weakest = skillPcts.sort((a,b)=>a.pct-b.pct)[0]; if (weakest.pct < 50) { html += `<div class="alert warn"> <span class="alert-ico">⚠️</span> <span class="alert-txt"><strong>${SKILL_CONFIG[weakest.skill].label} is your weakest area (${weakest.pct}%).</strong> Prioritize this skill in your study plan for fastest level improvement.</span> </div>`; } const strong = skillPcts.sort((a,b)=>b.pct-a.pct)[0]; if (strong.pct >= 70) { html += `<div class="alert good"> <span class="alert-ico">✅</span> <span class="alert-txt"><strong>${SKILL_CONFIG[strong.skill].label} is your strongest skill (${strong.pct}%).</strong> Build on this strength in your applications and interviews.</span> </div>`; } // CTA html += `<div class="res-cta"> <div class="cta-title">Langkah Selanjutnya</div> <div class="cta-sub">Career Consultant STAR4Hire siap membantu kamu merancang jalur belajar yang tepat dan memilih program yang sesuai level English kamu.</div> <button class="btn-wa-res" onclick="manualSendWA()"> <span>💬</span> Kirim Ulang ke Career Consultant </button> <button class="btn-restart" onclick="restartTest()">↺ Test Ulang</button> </div>`; document.getElementById('resWrap').innerHTML = html; show('res'); } function getLevelDescription(lvId) { const descs = { A0: 'Kamu baru memulai perjalanan English. Tidak masalah — setiap expert pernah menjadi beginner. Dengan 90 hari belajar konsisten, kamu bisa mencapai A2.', A1: 'Kamu memahami kata-kata dan frasa sederhana. Fondasi sudah ada — sekarang saatnya membangun struktur kalimat dan memperluas kosakata hospitality.', A2: 'Kamu bisa berkomunikasi dalam situasi rutin. Level ini sudah eligible untuk program Malaysia. Dengan 3 bulan persiapan serius, Taiwan ada di depanmu.', B1: 'Kamu bisa menangani sebagian besar situasi kerja dalam English. Taiwan dan Hong Kong sudah dalam jangkauan. Fokus pada fluency dan interview skills.', B2: 'English kamu solid dan profesional. J1 USA sudah bisa dijangkau. Satu level lagi — dan F1 MBA pun terbuka untukmu.', C1: 'English kamu advanced. Kamu siap untuk program internasional apapun yang STAR4Hire tawarkan — termasuk F1 MBA USA. Pertahankan dan tingkatkan ke bidang spesialisasi.', }; return descs[lvId] || descs['A2']; } function restartTest() { state = { currentSkill:'reading', skillOrder:['reading','vocabulary','grammar','writing'], skillIdx:0, qIdx:0, adaptiveLevel:2, answers:{}, skillScores:{reading:[],vocabulary:[],grammar:[],writing:[]}, questionQueue:[], totalAnswered:0, startTime:0, uData:{}, }; cachedResults = null; document.getElementById('topbar').classList.add('show'); document.getElementById('topbar').style.display = ''; document.getElementById('qBottom').classList.add('show'); document.getElementById('qBottom').style.display = ''; startTest(); } </script> </body> </html>
💾 Save Changes
❌ Cancel