Files
2026-02-23 14:08:21 +08:00

1894 lines
95 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Discord ID Bypass Tool — by PromptPirate</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;600&family=Space+Grotesk:wght@400;600&display=swap');
*{margin:0;padding:0;box-sizing:border-box}
body{background:#0a0a0f;color:#e0e0e8;font-family:'JetBrains Mono',monospace;overflow:hidden;height:100vh;touch-action:none}
canvas{display:block}
.hud{position:absolute;top:0;left:0;right:0;bottom:0;pointer-events:none;z-index:10}
.hp{position:absolute;background:rgba(10,10,20,0.85);backdrop-filter:blur(12px);border:1px solid rgba(100,220,255,0.15);border-radius:8px;padding:14px 18px;pointer-events:auto;user-select:none;max-width:90vw}
.hp>*:not(.ht){cursor:default}
.hp.collapsed>*:not(.ht){display:none!important}
.hp.collapsed{min-width:auto!important}
.ht{cursor:move}
.ht{font-family:'Space Grotesk',sans-serif;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:2.5px;color:rgba(100,220,255,0.7);margin-bottom:10px;display:flex;align-items:center;gap:8px;justify-content:space-between}
.ht::before{content:'';display:inline-block;width:6px;height:6px;border-radius:50%;background:#64dcff;box-shadow:0 0 8px #64dcff}
.ht-toggle{cursor:pointer;opacity:0.5;font-size:14px;line-height:1;padding:0 4px}
.ht-toggle:hover{opacity:1}
.main-title{position:absolute;top:20px;left:20px;font-family:'Space Grotesk',sans-serif;font-size:18px;font-weight:600;letter-spacing:1px;color:#e8e8f0;text-shadow:0 0 20px rgba(100,220,255,0.3);pointer-events:none;text-align:left;z-index:5}
.main-title span{color:#64dcff}
.controller-status{top:60px;right:20px}
.sensitivity-panel{top:20px;left:400px}
.model-panel{top:180px;right:20px;max-width:380px;max-height:calc(100vh - 200px);overflow-y:auto}
.model-panel::-webkit-scrollbar{width:5px}.model-panel::-webkit-scrollbar-track{background:transparent}.model-panel::-webkit-scrollbar-thumb{background:rgba(100,220,255,0.2);border-radius:3px}
.controls-panel{bottom:180px;left:20px;max-width:320px}
.values-panel{bottom:20px;left:20px;min-width:220px;max-width:320px}
.kid-panel{top:60px;left:20px;max-width:360px;max-height:20vh;overflow-y:auto}
.kid-panel::-webkit-scrollbar{width:5px}.kid-panel::-webkit-scrollbar-track{background:transparent}.kid-panel::-webkit-scrollbar-thumb{background:rgba(100,220,255,0.2);border-radius:3px}
.lighting-panel{top:250px;left:20px;max-width:320px;max-height:22vh;overflow-y:auto}
.lighting-panel::-webkit-scrollbar{width:5px}.lighting-panel::-webkit-scrollbar-track{background:transparent}.lighting-panel::-webkit-scrollbar-thumb{background:rgba(100,220,255,0.2);border-radius:3px}
@media(max-width:768px){
.hp{font-size:10px;padding:10px 12px;max-width:calc(100vw - 20px)}
.hp.collapsed>*:not(.ht){display:none!important}
.hp.collapsed{min-width:auto!important;padding:8px 10px!important}
.main-title{font-size:14px;top:10px;left:10px}
.controller-status{top:35px;right:10px;max-width:calc(48vw - 15px)}
.sensitivity-panel{top:35px;left:10px;max-width:calc(48vw - 15px)}
.kid-panel{top:100px;left:10px;max-width:calc(100vw - 20px);max-height:25vh;overflow-y:auto}
.model-panel{top:calc(25vh + 110px);left:10px;max-width:calc(100vw - 20px);max-height:25vh;overflow-y:auto}
.controls-panel{top:calc(50vh + 120px);left:10px;max-width:calc(48vw - 15px)}
.values-panel{top:calc(50vh + 120px);right:10px;max-width:calc(48vw - 15px)}
.ht{font-size:10px;letter-spacing:1.5px}
.hide-ui-btn{top:10px;right:10px;font-size:9px;padding:4px 10px;transform:none}
}
.sd{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px;vertical-align:middle}
.sd.on{background:#4ade80;box-shadow:0 0 8px #4ade80}.sd.off{background:#f87171;box-shadow:0 0 8px #f87171}
.cr{display:flex;justify-content:space-between;align-items:center;padding:4px 0;font-size:11px;color:#a0a0b0;gap:12px}
.ck{background:rgba(100,220,255,0.1);border:1px solid rgba(100,220,255,0.25);border-radius:4px;padding:2px 8px;font-size:10px;color:#64dcff;font-family:'JetBrains Mono',monospace;white-space:nowrap}
.vr{display:flex;justify-content:space-between;align-items:center;padding:3px 0;font-size:12px}
.vl{color:#808090}.vn{color:#64dcff;font-weight:600;font-variant-numeric:tabular-nums}
.vbt{width:80px;height:4px;background:rgba(100,220,255,0.1);border-radius:2px;overflow:hidden;margin-left:10px}
.vbf{height:100%;background:linear-gradient(90deg,#64dcff,#a78bfa);border-radius:2px;transition:width 0.05s linear}
.sr{display:flex;align-items:center;gap:8px;padding:3px 0;font-size:11px;color:#a0a0b0}
.sb{background:rgba(100,220,255,0.1);border:1px solid rgba(100,220,255,0.3);color:#64dcff;width:22px;height:22px;border-radius:4px;cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center}
.sb:hover{background:rgba(100,220,255,0.25)}
.sv{color:#64dcff;font-weight:600;min-width:30px;text-align:center}
.cb{flex:1;background:rgba(167,139,250,0.1);border:1px solid rgba(167,139,250,0.3);color:#a78bfa;padding:5px 8px;border-radius:4px;cursor:pointer;font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:0.3px;white-space:nowrap}
.cb:hover{background:rgba(167,139,250,0.2);border-color:rgba(167,139,250,0.5)}
.lbtn{background:rgba(100,220,255,0.1);border:1px solid rgba(100,220,255,0.3);color:#64dcff;padding:8px 16px;border-radius:6px;cursor:pointer;font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:0.5px;width:100%}
.lbtn:hover{background:rgba(100,220,255,0.2);border-color:rgba(100,220,255,0.5)}
#model-file,#model-multi{display:none}
.li{font-size:10px;color:#707080;margin-top:8px;line-height:1.5;white-space:pre-line}
.err{background:rgba(248,113,113,0.1);border:1px solid rgba(248,113,113,0.3);border-radius:4px;padding:8px;margin-top:8px;font-size:10px;color:#f87171;display:none;word-break:break-all}
.ok-box{background:rgba(74,222,128,0.08);border:1px solid rgba(74,222,128,0.25);border-radius:4px;padding:8px;margin-top:8px;font-size:10px;color:#4ade80;display:none;white-space:pre-line;line-height:1.5}
.bsel{background:rgba(10,10,20,0.9);border:1px solid rgba(100,220,255,0.2);color:#e0e0e8;padding:4px 8px;border-radius:4px;font-size:10px;font-family:'JetBrains Mono',monospace;width:100%;margin-top:2px}
.bsel option{background:#1a1a2e}
.brow{display:flex;align-items:center;gap:8px;padding:4px 0;font-size:11px}
.blbl{min-width:45px;font-weight:600}
.blbl.hd{color:#4ade80}.blbl.nk{color:#facc15}.blbl.jw{color:#f97316}
.jaw-row{display:flex;align-items:center;gap:6px;margin-top:6px;font-size:10px;color:#a0a0b0}
.axb{background:rgba(249,115,22,0.1);border:1px solid rgba(249,115,22,0.25);color:#f97316;padding:2px 8px;border-radius:3px;cursor:pointer;font-size:10px;font-family:'JetBrains Mono',monospace}
.axb:hover{background:rgba(249,115,22,0.25)}.axb.active{background:rgba(249,115,22,0.3);border-color:#f97316;font-weight:600}
.sub{font-size:10px;color:#5a5a6a;margin-top:3px}
.orbit-hint{position:absolute;bottom:55px;left:50%;transform:translateX(-50%);font-size:10px;color:#353545;pointer-events:none}
.format-badges{display:flex;gap:4px;margin-top:6px;flex-wrap:wrap}
.fbadge{font-size:9px;padding:2px 7px;border-radius:3px;font-weight:600;letter-spacing:0.5px}
.fbadge.vrm{background:rgba(251,146,60,0.15);color:#fb923c;border:1px solid rgba(251,146,60,0.3)}
.fbadge.fbx{background:rgba(167,139,250,0.15);color:#a78bfa;border:1px solid rgba(167,139,250,0.3)}
.fbadge.glb{background:rgba(100,220,255,0.15);color:#64dcff;border:1px solid rgba(100,220,255,0.3)}
.fbadge.zip{background:rgba(250,204,21,0.15);color:#facc15;border:1px solid rgba(250,204,21,0.3)}
.drop-zone{border:2px dashed rgba(100,220,255,0.3);border-radius:6px;padding:12px;margin-top:8px;text-align:center;font-size:10px;color:#5a5a6a;transition:all 0.2s;cursor:pointer}
.drop-zone.over{border-color:#64dcff;background:rgba(100,220,255,0.05);color:#64dcff}
.morph-tester{margin-top:8px;padding:8px;background:rgba(249,115,22,0.05);border:1px solid rgba(249,115,22,0.15);border-radius:6px}
.morph-tester-row{display:flex;align-items:center;gap:6px;padding:2px 0;font-size:10px;color:#b0b0c0}
.morph-tester-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer;padding:2px 4px;border-radius:3px}
.morph-tester-name:hover{background:rgba(249,115,22,0.1)}
.morph-tester-name.active{color:#f97316;background:rgba(249,115,22,0.15)}
.morph-tester-val{min-width:30px;text-align:right;color:#f97316;font-weight:600}
.morph-slider{width:60px;accent-color:#f97316}
.morph-map{font-size:8px;padding:2px 6px;border-radius:3px;background:rgba(74,222,128,0.08);color:#4ade80;border:1px solid rgba(74,222,128,0.2);cursor:pointer;white-space:nowrap;font-family:'JetBrains Mono',monospace}
.morph-map:hover{background:rgba(74,222,128,0.2);border-color:#4ade80}
.morph-map.mapped{background:rgba(74,222,128,0.2);border-color:#4ade80;font-weight:600}
.morph-auto{font-size:9px;padding:2px 6px;border-radius:3px;background:rgba(74,222,128,0.15);color:#4ade80;border:1px solid rgba(74,222,128,0.25)}
.hp{cursor:move;user-select:none}
.hp.dragging{opacity:0.8;z-index:999}
.hide-ui-btn{position:absolute;top:20px;right:20px;background:rgba(10,10,20,0.85);backdrop-filter:blur(12px);border:1px solid rgba(100,220,255,0.15);border-radius:6px;padding:6px 14px;color:#64dcff;font-family:'JetBrains Mono',monospace;font-size:10px;cursor:pointer;pointer-events:auto;z-index:20}
.hide-ui-btn:hover{background:rgba(100,220,255,0.15)}
.hud.hidden .hp{display:none!important}
.kid-input{background:rgba(10,10,20,0.9);border:1px solid rgba(100,220,255,0.2);color:#e0e0e8;padding:6px 10px;border-radius:4px;font-size:11px;font-family:'JetBrains Mono',monospace;width:100%;margin-top:4px}
.kid-btn{background:rgba(100,220,255,0.1);border:1px solid rgba(100,220,255,0.3);color:#64dcff;padding:8px 16px;border-radius:6px;cursor:pointer;font-family:'JetBrains Mono',monospace;font-size:11px;width:100%;margin-top:8px}
.kid-btn:hover{background:rgba(100,220,255,0.2)}
.kid-output{background:rgba(10,10,20,0.95);border:1px solid rgba(100,220,255,0.2);border-radius:4px;padding:8px;margin-top:8px;font-size:9px;color:#a0a0b0;max-height:200px;overflow-y:auto;font-family:'JetBrains Mono',monospace;white-space:pre-wrap;word-break:break-all;display:none}
.kid-output::-webkit-scrollbar{width:4px}.kid-output::-webkit-scrollbar-track{background:transparent}.kid-output::-webkit-scrollbar-thumb{background:rgba(100,220,255,0.2);border-radius:2px}
@media(max-width:768px){
.orbit-hint{display:none}
.vbt{width:50px}
.cr{font-size:9px;gap:6px}
.ck{font-size:8px;padding:1px 5px}
}
.hidden{display:none}
/* ═══════════════════════════════════════════════════════
QUICK START TUTORIAL
═══════════════════════════════════════════════════════ */
.tutorial-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(10,10,15,0.95);backdrop-filter:blur(8px);z-index:10000;display:none;align-items:center;justify-content:center}
.tutorial-overlay.active{display:flex}
.tutorial-card{background:rgba(10,10,20,0.95);border:2px solid rgba(100,220,255,0.3);border-radius:12px;padding:32px;max-width:500px;text-align:center;box-shadow:0 20px 60px rgba(0,0,0,0.5)}
.tutorial-title{font-family:'Space Grotesk',sans-serif;font-size:24px;color:#64dcff;margin-bottom:16px;letter-spacing:1px}
.tutorial-text{font-size:14px;color:#c0c0cc;line-height:1.8;margin-bottom:24px}
.tutorial-step{font-size:12px;color:#808090;margin-bottom:20px}
.tutorial-highlight{color:#64dcff;font-weight:600;background:rgba(100,220,255,0.1);padding:2px 8px;border-radius:4px}
.tutorial-btn{background:linear-gradient(135deg,#64dcff,#a78bfa);border:none;color:#0a0a0f;padding:12px 32px;border-radius:6px;font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:600;cursor:pointer;letter-spacing:0.5px;transition:transform 0.2s}
.tutorial-btn:hover{transform:scale(1.05)}
.tutorial-skip{margin-top:16px;font-size:11px;color:#606070;cursor:pointer;text-decoration:underline}
.tutorial-skip:hover{color:#64dcff}
.tutorial-progress{display:flex;gap:8px;justify-content:center;margin-top:20px}
.tutorial-dot{width:8px;height:8px;border-radius:50%;background:rgba(100,220,255,0.2);transition:all 0.3s}
.tutorial-dot.active{background:#64dcff;width:24px;border-radius:4px}
/* ═══════════════════════════════════════════════════════
MOBILE VIRTUAL JOYSTICK
═══════════════════════════════════════════════════════ */
.mobile-controls{position:fixed;bottom:20px;left:0;right:0;display:none;justify-content:space-between;padding:0 20px;pointer-events:none;z-index:100}
@media(max-width:768px){.mobile-controls{display:flex}}
.joystick-container{width:120px;height:120px;position:relative;pointer-events:auto}
.joystick-base{width:100%;height:100%;border-radius:50%;background:rgba(10,10,20,0.6);backdrop-filter:blur(8px);border:2px solid rgba(100,220,255,0.3);position:relative}
.joystick-stick{width:50px;height:50px;border-radius:50%;background:linear-gradient(135deg,#64dcff,#a78bfa);position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);box-shadow:0 4px 12px rgba(100,220,255,0.4);transition:all 0.1s}
.joystick-label{position:absolute;top:-25px;left:50%;transform:translateX(-50%);font-size:10px;color:#64dcff;font-family:'JetBrains Mono',monospace;white-space:nowrap}
.mobile-btn-group{display:flex;flex-direction:column;gap:12px;pointer-events:auto}
.mobile-btn{width:60px;height:60px;border-radius:50%;background:rgba(10,10,20,0.6);backdrop-filter:blur(8px);border:2px solid rgba(100,220,255,0.3);display:flex;align-items:center;justify-content:center;font-size:11px;color:#64dcff;font-family:'JetBrains Mono',monospace;font-weight:600;touch-action:none;user-select:none}
.mobile-btn.active{background:rgba(100,220,255,0.3);border-color:#64dcff;box-shadow:0 0 20px rgba(100,220,255,0.5)}
/* ═══════════════════════════════════════════════════════
PHONEME SELECTOR
═══════════════════════════════════════════════════════ */
.phoneme-panel{position:absolute;bottom:20px;right:20px;background:rgba(10,10,20,0.85);backdrop-filter:blur(12px);border:1px solid rgba(100,220,255,0.15);border-radius:8px;padding:14px;pointer-events:auto;display:none}
@media(max-width:768px){.phoneme-panel{bottom:160px;right:10px}}
.phoneme-panel.visible{display:block}
.phoneme-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.phoneme-btn{width:50px;height:50px;border-radius:8px;background:rgba(167,139,250,0.1);border:1px solid rgba(167,139,250,0.3);color:#a78bfa;font-family:'Space Grotesk',sans-serif;font-size:20px;font-weight:600;cursor:pointer;transition:all 0.2s;display:flex;align-items:center;justify-content:center}
.phoneme-btn:hover{background:rgba(167,139,250,0.25);border-color:#a78bfa;transform:scale(1.1)}
.phoneme-btn.active{background:rgba(167,139,250,0.3);border-color:#a78bfa;box-shadow:0 0 16px rgba(167,139,250,0.5)}
.phoneme-toggle{position:absolute;top:20px;right:380px;background:rgba(10,10,20,0.85);backdrop-filter:blur(12px);border:1px solid rgba(100,220,255,0.15);border-radius:6px;padding:6px 14px;color:#a78bfa;font-family:'JetBrains Mono',monospace;font-size:10px;cursor:pointer;pointer-events:auto;z-index:20}
.phoneme-toggle:hover{background:rgba(167,139,250,0.15)}
@media(max-width:768px){.phoneme-toggle{top:10px;right:10px}}
/* ═══════════════════════════════════════════════════════
LIGHTING CONTROLS
═══════════════════════════════════════════════════════ */
.light-row{display:flex;align-items:center;gap:8px;padding:4px 0;font-size:11px}
.light-label{min-width:60px;font-weight:600}
.light-slider{flex:1;accent-color:#64dcff;height:4px}
.light-value{min-width:35px;text-align:right;color:#64dcff;font-weight:600;font-size:10px}
.light-color{width:20px;height:20px;border-radius:4px;border:1px solid rgba(100,220,255,0.3);cursor:pointer}
.light-toggle{background:rgba(100,220,255,0.1);border:1px solid rgba(100,220,255,0.3);color:#64dcff;padding:4px 10px;border-radius:4px;cursor:pointer;font-size:9px;font-family:'JetBrains Mono',monospace}
.light-toggle:hover{background:rgba(100,220,255,0.2)}
.light-toggle.active{background:rgba(74,222,128,0.2);border-color:#4ade80;color:#4ade80}
/* ═══════════════════════════════════════════════════════
SKELETON LOADING
═══════════════════════════════════════════════════════ */
.skeleton-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#0a0a0f;z-index:9999;display:flex;align-items:center;justify-content:center;opacity:1;transition:opacity 0.4s ease}
.skeleton-overlay.fade-out{opacity:0;pointer-events:none}
.skeleton-container{width:90%;max-width:1400px;height:90vh;display:grid;grid-template-columns:1fr 2fr 1fr;grid-template-rows:auto 1fr auto;gap:20px;padding:20px}
/* Skeleton shimmer animation */
@keyframes shimmer{0%{background-position:-1000px 0}100%{background-position:1000px 0}}
.skeleton-box{background:linear-gradient(90deg,rgba(100,220,255,0.03) 0%,rgba(100,220,255,0.08) 50%,rgba(100,220,255,0.03) 100%);background-size:1000px 100%;animation:shimmer 2s infinite linear;border:1px solid rgba(100,220,255,0.1);border-radius:8px;position:relative;overflow:hidden}
.skeleton-box::after{content:'';position:absolute;top:0;left:0;right:0;bottom:0;background:linear-gradient(90deg,transparent 0%,rgba(100,220,255,0.15) 50%,transparent 100%);animation:shimmer 2s infinite linear}
/* Header skeleton */
.skeleton-header{grid-column:1/4;height:60px;display:flex;align-items:center;padding:0 20px;gap:12px}
.skeleton-logo{width:24px;height:24px;border-radius:4px;background:rgba(100,220,255,0.15)}
.skeleton-title{flex:1;height:20px;max-width:300px;border-radius:4px}
/* Left panel (controls) */
.skeleton-left{grid-column:1;grid-row:2;display:flex;flex-direction:column;gap:20px}
.skeleton-panel{padding:18px;display:flex;flex-direction:column;gap:12px}
.skeleton-panel-header{height:14px;width:60%;border-radius:3px;background:rgba(100,220,255,0.2);margin-bottom:8px}
.skeleton-row{height:28px;border-radius:4px;background:rgba(100,220,255,0.05)}
.skeleton-row.tall{height:40px}
.skeleton-row.short{height:20px;width:70%}
/* Center canvas */
.skeleton-canvas{grid-column:2;grid-row:1/4;background:radial-gradient(circle at center,rgba(100,220,255,0.05) 0%,transparent 70%);border:1px solid rgba(100,220,255,0.08);border-radius:12px;position:relative}
.skeleton-canvas::before{content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:120px;height:120px;border:3px solid rgba(100,220,255,0.2);border-top-color:#64dcff;border-radius:50%;animation:spin 1.5s linear infinite}
@keyframes spin{to{transform:translate(-50%,-50%) rotate(360deg)}}
.skeleton-canvas-text{position:absolute;bottom:30px;left:50%;transform:translateX(-50%);font-size:11px;color:rgba(100,220,255,0.5);font-family:'JetBrains Mono',monospace;letter-spacing:1px}
/* Right panel (model) */
.skeleton-right{grid-column:3;grid-row:2;display:flex;flex-direction:column;gap:20px}
.skeleton-button{height:36px;border-radius:6px;background:rgba(100,220,255,0.08);border:1px solid rgba(100,220,255,0.15)}
.skeleton-dropzone{height:80px;border:2px dashed rgba(100,220,255,0.15);border-radius:6px;background:rgba(100,220,255,0.02)}
/* Footer skeleton */
.skeleton-footer{grid-column:1/4;height:100px;display:flex;gap:20px}
.skeleton-footer-panel{flex:1;padding:18px}
/* Mobile responsive */
@media(max-width:768px){
.skeleton-container{grid-template-columns:1fr;grid-template-rows:auto auto 1fr auto;gap:12px;padding:12px}
.skeleton-header{grid-column:1;height:50px}
.skeleton-left{grid-column:1;grid-row:2}
.skeleton-canvas{grid-column:1;grid-row:3;min-height:300px}
.skeleton-right{grid-column:1;grid-row:4}
.skeleton-footer{grid-column:1;grid-row:5;flex-direction:column;height:auto}
}
</style>
</head>
<body>
<!-- ═══════════════════════════════════════════════════════
QUICK START TUTORIAL
═══════════════════════════════════════════════════════ -->
<div class="tutorial-overlay" id="tutorial-overlay">
<div class="tutorial-card">
<div class="tutorial-title" id="tut-title">Welcome to Discord ID Bypass</div>
<div class="tutorial-text" id="tut-text">Let's get you started in 3 simple steps</div>
<div class="tutorial-step" id="tut-step"></div>
<button class="tutorial-btn" id="tut-btn">Start</button>
<div class="tutorial-skip" id="tut-skip">Skip tutorial</div>
<div class="tutorial-progress" id="tut-progress"></div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════
SKELETON LOADING OVERLAY
═══════════════════════════════════════════════════════ -->
<div class="skeleton-overlay" id="skeleton-overlay">
<div class="skeleton-container">
<!-- Header -->
<div class="skeleton-header skeleton-box">
<div class="skeleton-logo"></div>
<div class="skeleton-title"></div>
</div>
<!-- Left panels -->
<div class="skeleton-left">
<div class="skeleton-box skeleton-panel">
<div class="skeleton-panel-header"></div>
<div class="skeleton-row short"></div>
<div class="skeleton-row short"></div>
</div>
<div class="skeleton-box skeleton-panel">
<div class="skeleton-panel-header"></div>
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
</div>
</div>
<!-- Center canvas -->
<div class="skeleton-canvas skeleton-box">
<div class="skeleton-canvas-text">INITIALIZING THREE.JS</div>
</div>
<!-- Right panel -->
<div class="skeleton-right">
<div class="skeleton-box skeleton-panel">
<div class="skeleton-panel-header"></div>
<div class="skeleton-button"></div>
<div class="skeleton-dropzone"></div>
<div class="skeleton-row short"></div>
<div class="skeleton-row short"></div>
</div>
</div>
<!-- Footer -->
<div class="skeleton-footer">
<div class="skeleton-box skeleton-footer-panel">
<div class="skeleton-panel-header"></div>
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
</div>
<div class="skeleton-box skeleton-footer-panel">
<div class="skeleton-panel-header"></div>
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
</div>
</div>
</div>
</div>
<div id="canvas-container" style="width:100vw;height:100vh;position:relative;">
<button class="hide-ui-btn" id="hide-ui-btn">Hide UI [H]</button>
<div class="hud" id="hud">
<button class="phoneme-toggle" id="phoneme-toggle">Phonemes [P]</button>
<div class="main-title"><span></span> Discord ID Bypass Tool <span style="font-size:11px;opacity:0.5;margin-left:6px">by PromptPirate</span></div>
<div class="hp controller-status">
<div class="ht">Input<span class="ht-toggle"></span></div>
<div style="font-size:12px;color:#c0c0cc"><span class="sd off" id="gp-dot"></span><span id="gp-status">No Gamepad</span></div>
<div class="sub" id="gp-hint">Press any button on controller</div>
<div class="sub">Keyboard: Always Active</div>
</div>
<div class="hp sensitivity-panel">
<div class="ht">Sensitivity<span class="ht-toggle"></span></div>
<div class="sr"><span style="min-width:45px">Head</span><button class="sb" id="hs-d"></button><span class="sv" id="hs-v">1.0</span><button class="sb" id="hs-u">+</button></div>
<div class="sr"><span style="min-width:45px">Mouth</span><button class="sb" id="ms-d"></button><span class="sv" id="ms-v">1.0</span><button class="sb" id="ms-u">+</button></div>
<div class="sr"><span style="min-width:45px;color:#f97316">Range</span><button class="sb" id="mr-d" style="border-color:rgba(249,115,22,0.3);color:#f97316"></button><span class="sv" id="mr-v" style="color:#f97316">100%</span><button class="sb" id="mr-u" style="border-color:rgba(249,115,22,0.3);color:#f97316">+</button></div>
<div style="display:flex;gap:6px;margin-top:10px">
<button class="cb" id="cam-head">Focus Head</button>
<button class="cb" id="cam-body">Full Body</button>
<button class="cb" id="cam-reset">Reset Cam</button>
</div>
</div>
<div class="hp lighting-panel">
<div class="ht">Lighting<span class="ht-toggle"></span></div>
<div class="light-row">
<span class="light-label">Key Light</span>
<button class="light-toggle active" id="key-toggle">ON</button>
<input type="color" class="light-color" id="key-color" value="#dce8ff" />
</div>
<div class="light-row">
<span class="light-label" style="min-width:80px">Intensity</span>
<input type="range" class="light-slider" id="key-intensity" min="0" max="3" step="0.1" value="1.4" />
<span class="light-value" id="key-val">1.4</span>
</div>
<div class="light-row">
<span class="light-label" style="min-width:20px">X</span>
<input type="range" class="light-slider" id="key-x" min="-10" max="10" step="0.5" value="3" />
<span class="light-value" id="key-x-val">3</span>
</div>
<div class="light-row">
<span class="light-label" style="min-width:20px">Y</span>
<input type="range" class="light-slider" id="key-y" min="-10" max="10" step="0.5" value="4" />
<span class="light-value" id="key-y-val">4</span>
</div>
<div class="light-row">
<span class="light-label" style="min-width:20px">Z</span>
<input type="range" class="light-slider" id="key-z" min="-10" max="10" step="0.5" value="5" />
<span class="light-value" id="key-z-val">5</span>
</div>
<div class="light-row">
<span class="light-label">Fill Light</span>
<button class="light-toggle active" id="fill-toggle">ON</button>
<input type="color" class="light-color" id="fill-color" value="#64dcff" />
</div>
<div class="light-row">
<span class="light-label" style="min-width:80px">Intensity</span>
<input type="range" class="light-slider" id="fill-intensity" min="0" max="2" step="0.1" value="0.4" />
<span class="light-value" id="fill-val">0.4</span>
</div>
<div class="light-row">
<span class="light-label" style="min-width:20px">X</span>
<input type="range" class="light-slider" id="fill-x" min="-10" max="10" step="0.5" value="-3" />
<span class="light-value" id="fill-x-val">-3</span>
</div>
<div class="light-row">
<span class="light-label" style="min-width:20px">Y</span>
<input type="range" class="light-slider" id="fill-y" min="-10" max="10" step="0.5" value="2" />
<span class="light-value" id="fill-y-val">2</span>
</div>
<div class="light-row">
<span class="light-label" style="min-width:20px">Z</span>
<input type="range" class="light-slider" id="fill-z" min="-10" max="10" step="0.5" value="2" />
<span class="light-value" id="fill-z-val">2</span>
</div>
<div class="light-row">
<span class="light-label">Rim Light</span>
<button class="light-toggle active" id="rim-toggle">ON</button>
<input type="color" class="light-color" id="rim-color" value="#a78bfa" />
</div>
<div class="light-row">
<span class="light-label" style="min-width:80px">Intensity</span>
<input type="range" class="light-slider" id="rim-intensity" min="0" max="2" step="0.1" value="0.5" />
<span class="light-value" id="rim-val">0.5</span>
</div>
<div class="light-row">
<span class="light-label" style="min-width:20px">X</span>
<input type="range" class="light-slider" id="rim-x" min="-10" max="10" step="0.5" value="0" />
<span class="light-value" id="rim-x-val">0</span>
</div>
<div class="light-row">
<span class="light-label" style="min-width:20px">Y</span>
<input type="range" class="light-slider" id="rim-y" min="-10" max="10" step="0.5" value="2" />
<span class="light-value" id="rim-y-val">2</span>
</div>
<div class="light-row">
<span class="light-label" style="min-width:20px">Z</span>
<input type="range" class="light-slider" id="rim-z" min="-10" max="10" step="0.5" value="-4" />
<span class="light-value" id="rim-z-val">-4</span>
</div>
<div class="light-row">
<span class="light-label">Ambient</span>
<button class="light-toggle active" id="ambient-toggle">ON</button>
<input type="color" class="light-color" id="ambient-color" value="#506080" />
</div>
<div class="light-row">
<span class="light-label" style="min-width:80px">Intensity</span>
<input type="range" class="light-slider" id="ambient-intensity" min="0" max="2" step="0.1" value="0.8" />
<span class="light-value" id="ambient-val">0.8</span>
</div>
</div>
<div class="hp model-panel" id="model-panel">
<div class="ht">Model<span class="ht-toggle"></span></div>
<button class="lbtn" id="load-btn">Load Model File</button>
<input type="file" id="model-file" accept=".vrm,.fbx,.glb,.gltf,.zip" />
<input type="file" id="model-multi" accept=".fbx,.png,.jpg,.jpeg,.tga,.bmp" multiple style="display:none" />
<div class="format-badges">
<span class="fbadge vrm">VRM</span>
<span class="fbadge fbx">FBX</span>
<span class="fbadge glb">GLB/GLTF</span>
<span class="fbadge zip">ZIP</span>
</div>
<div class="drop-zone" id="drop-zone">
Drop folder or ZIP here<br>for FBX + textures
</div>
<button class="lbtn" id="load-multi-btn" style="margin-top:4px;font-size:9px;padding:5px 10px;opacity:0.7">
Or select FBX + texture files together
</button>
<div class="li" id="model-info">VRM = instant setup. FBX with textures = use ZIP or drop folder. GLB = self-contained.</div>
<div class="err" id="err-box"></div>
<div class="ok-box" id="ok-box"></div>
<!-- Bone assignment dropdowns -->
<div id="bone-section" style="display:none;margin-top:12px">
<div class="ht">Bone Assignment<span class="ht-toggle"></span></div>
<div class="brow"><span class="blbl hd">Head</span><select class="bsel" id="sel-head"><option value="">(none)</option></select></div>
<div class="brow"><span class="blbl nk">Neck</span><select class="bsel" id="sel-neck"><option value="">(none)</option></select></div>
<div class="brow"><span class="blbl jw">Jaw</span><select class="bsel" id="sel-jaw"><option value="">(none)</option></select></div>
<div id="jaw-section" style="display:none;margin-top:8px">
<div style="font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:#f97316;opacity:0.7;margin-bottom:6px">Jaw Open Axis</div>
<div class="jaw-row">
<button class="axb active" data-a="x" id="ax-x">X</button>
<button class="axb" data-a="y" id="ax-y">Y</button>
<button class="axb" data-a="z" id="ax-z">Z</button>
<span style="margin-left:6px">Dir:</span>
<button class="axb active" data-d="1" id="dir-p">+</button>
<button class="axb" data-d="-1" id="dir-n"></button>
</div>
</div>
<div id="morph-section" style="display:none;margin-top:10px">
<div style="font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:#f97316;opacity:0.7;margin-bottom:6px">Mouth (Morph Targets)</div>
<div style="font-size:9px;color:#6a6a7a;margin-bottom:6px">Mouth morph auto-assigned to RT/Space. Use tester below to preview each shape.</div>
<select class="bsel" id="morph-sel"><option value="">None (use jaw bone)</option></select>
<div class="morph-tester" id="morph-tester" style="display:none">
<div style="font-size:9px;text-transform:uppercase;letter-spacing:1px;color:#f97316;opacity:0.7;margin-bottom:4px">Morph Tester — click name to assign to mouth</div>
<div id="morph-tester-list"></div>
</div>
</div>
</div>
</div>
<div class="hp controls-panel">
<div class="ht">Controls<span class="ht-toggle"></span></div>
<div class="cr"><span>Head Yaw</span><span class="ck">A D / L-Stick X</span></div>
<div class="cr"><span>Head Pitch</span><span class="ck">W S / L-Stick Y</span></div>
<div class="cr"><span>Head Roll</span><span class="ck">Q E / R-Stick X</span></div>
<div class="cr"><span>Mouth</span><span class="ck">Space / RT</span></div>
<div class="cr"><span>Mouth (Slow)</span><span class="ck">No. 1-5</span></div>
<div class="cr"><span>Reset</span><span class="ck">R / Y Button</span></div>
</div>
<div class="hp values-panel">
<div class="ht">State<span class="ht-toggle"></span></div>
<div class="vr"><span class="vl">Yaw</span><span class="vn" id="v-yaw">0.0°</span></div>
<div class="vr"><span class="vl">Pitch</span><span class="vn" id="v-pitch">0.0°</span></div>
<div class="vr"><span class="vl">Roll</span><span class="vn" id="v-roll">0.0°</span></div>
<div class="vr"><span class="vl">Mouth</span><div style="display:flex;align-items:center"><span class="vn" id="v-mouth" style="margin-right:8px">0%</span><div class="vbt"><div class="vbf" id="mouth-bar" style="width:0%"></div></div></div></div>
</div>
<div class="hp kid-panel" id="kid-panel">
<div class="ht">k-ID Bypass<span class="ht-toggle"></span></div>
<div style="font-size:10px;color:#f87171;margin-bottom:8px;padding:6px;background:rgba(248,113,113,0.1);border:1px solid rgba(248,113,113,0.3);border-radius:4px;line-height:1.4">
⚠️ CURRENTLY NOT FUNCTIONAL<br>
Both API and Standalone modes are not working. Discord has patched their verification system.
</div>
<div style="font-size:10px;color:#707080;margin-bottom:6px;opacity:0.5">Verify your Discord account as adult using QR code</div>
<div style="font-size:9px;color:#5a5a6a;margin-bottom:8px;line-height:1.4;opacity:0.5">
1. Start Discord age verification<br>
2. Choose "Face Scan" option<br>
3. Scan/upload the QR code below
</div>
<div style="display:flex;gap:6px;margin-bottom:8px;opacity:0.4;pointer-events:none">
<button class="cb" id="mode-api" style="flex:1;font-size:9px;padding:4px;background:rgba(100,220,255,0.2);border-color:#64dcff" disabled>API Mode</button>
<button class="cb" id="mode-standalone" style="flex:1;font-size:9px;padding:4px" disabled>Standalone</button>
</div>
<div style="font-size:8px;color:#f87171;margin-bottom:6px" id="mode-desc">⚠️ Both modes are currently patched and non-functional</div>
<label style="font-size:10px;color:#a0a0b0;opacity:0.4">QR Code URL or Image:</label>
<input type="text" class="kid-input" id="kid-qr-url" placeholder="https://... or drop QR image" style="margin-bottom:4px;opacity:0.4" disabled />
<label class="kid-btn" style="cursor:not-allowed;text-align:center;margin-top:4px;opacity:0.4;font-size:10px;padding:6px;pointer-events:none">
📷 Upload QR Image
<input type="file" id="kid-qr-file" accept="image/*" style="display:none" disabled />
</label>
<button class="kid-btn" id="kid-verify" style="background:rgba(248,113,113,0.1);border-color:rgba(248,113,113,0.3);color:#f87171;opacity:0.4;cursor:not-allowed" disabled>
✗ Not Functional
</button>
<div style="font-size:9px;color:#f87171;margin-top:8px;line-height:1.4">
Status: <span id="kid-status" style="color:#f87171">Not Functional (Discord Patched)</span>
</div>
<div class="kid-output" id="kid-output"></div>
</div>
<div class="orbit-hint">Right-click drag to orbit · Scroll to zoom</div>
<!-- Phoneme Panel -->
<div class="phoneme-panel" id="phoneme-panel">
<div class="phoneme-grid">
<button class="phoneme-btn" data-phoneme="a">A</button>
<button class="phoneme-btn" data-phoneme="e">E</button>
<button class="phoneme-btn" data-phoneme="i">I</button>
<button class="phoneme-btn" data-phoneme="o">O</button>
<button class="phoneme-btn" data-phoneme="u">U</button>
<button class="phoneme-btn" data-phoneme="neutral"></button>
</div>
</div>
</div>
<!-- Mobile Controls -->
<div class="mobile-controls">
<div class="joystick-container" id="joystick-head">
<div class="joystick-label">HEAD</div>
<div class="joystick-base">
<div class="joystick-stick" id="joystick-head-stick"></div>
</div>
</div>
<div class="mobile-btn-group">
<div class="mobile-btn" id="mobile-mouth">MOUTH</div>
<div class="mobile-btn" id="mobile-roll-l">ROLL L</div>
<div class="mobile-btn" id="mobile-roll-r">ROLL R</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script>
<script src="kid-bypass-standalone.js"></script>
<script src="kid-bypass.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// ═══════════════════════════════════════════════════════
// CONFIG & STATE
// ═══════════════════════════════════════════════════════
var CFG={hS:1,mS:1,mRange:1.0,yawMax:80,pitchMax:50,rollMax:35,dz:0.12,sm:0.12,msm:0.15,jawAxis:'x',jawDir:1,jawAngle:0.4};
var S={tY:0,tP:0,tR:0,tM:0,cY:0,cP:0,cR:0,cM:0,keys:{},phoneme:'neutral',gyroEnabled:false};
var A={head:null,neck:null,jaw:null,headI:null,neckI:null,jawI:null,morphMesh:null,morphIdx:-1,phonemeMorphs:{}};
var loadedModel=null, vrmData=null, allBones=[];
var orbitTarget=new THREE.Vector3(0,0.3,0),orbitTheta=0,orbitPhi=0.15,orbitDist=3.5,isDrag=false,lastM={x:0,y:0};
var mobileJoystick={active:false,startX:0,startY:0,currentX:0,currentY:0};
function $(id){return document.getElementById(id)}
function showErr(m){$('err-box').textContent=m;$('err-box').style.display='block';$('ok-box').style.display='none'}
function showOk(m){$('ok-box').textContent=m;$('ok-box').style.display='block';$('err-box').style.display='none'}
function clearMsg(){$('err-box').style.display='none';$('ok-box').style.display='none'}
// ═══════════════════════════════════════════════════════
// THREE.JS SCENE
// ═══════════════════════════════════════════════════════
var container=$('canvas-container');
var scene=new THREE.Scene();scene.fog=new THREE.FogExp2(0x0a0a12,0.008);
var cam=new THREE.PerspectiveCamera(40,innerWidth/innerHeight,0.01,200);
cam.position.set(0,0.5,3.5);cam.lookAt(orbitTarget);
var ren=new THREE.WebGLRenderer({antialias:true});
ren.setSize(innerWidth,innerHeight);ren.setPixelRatio(Math.min(devicePixelRatio,2));
ren.setClearColor(0x0a0a12);ren.shadowMap.enabled=true;ren.shadowMap.type=THREE.PCFSoftShadowMap;
ren.toneMapping=THREE.ACESFilmicToneMapping;ren.toneMappingExposure=1.2;
container.prepend(ren.domElement);
scene.add(new THREE.AmbientLight(0x506080,0.8));
var kl=new THREE.DirectionalLight(0xdce8ff,1.4);kl.position.set(3,4,5);kl.castShadow=true;kl.shadow.mapSize.set(1024,1024);scene.add(kl);
var fl=new THREE.DirectionalLight(0x64dcff,0.4);fl.position.set(-3,2,2);scene.add(fl);
var rl=new THREE.DirectionalLight(0xa78bfa,0.5);rl.position.set(0,2,-4);scene.add(rl);
scene.add(new THREE.PointLight(0x64dcff,0.3,10).translateY(-2).translateZ(2));
var ambientLight=scene.children.find(function(c){return c.isAmbientLight});
var gnd=new THREE.Mesh(new THREE.PlaneGeometry(30,30),new THREE.MeshStandardMaterial({color:0x0a0a12,roughness:0.95}));
gnd.rotation.x=-Math.PI/2;gnd.position.y=-3;gnd.receiveShadow=true;scene.add(gnd);
// ═══════════════════════════════════════════════════════
// ORBIT CAMERA
// ═══════════════════════════════════════════════════════
ren.domElement.addEventListener('mousedown',function(e){if(e.button===2||e.button===1||(e.button===0&&e.altKey)){isDrag=true;lastM={x:e.clientX,y:e.clientY};e.preventDefault()}});
window.addEventListener('mousemove',function(e){if(!isDrag)return;orbitTheta-=(e.clientX-lastM.x)*0.005;orbitPhi=Math.max(-1.2,Math.min(1.2,orbitPhi+(e.clientY-lastM.y)*0.005));lastM={x:e.clientX,y:e.clientY}});
window.addEventListener('mouseup',function(){isDrag=false});
ren.domElement.addEventListener('wheel',function(e){orbitDist=Math.max(0.3,Math.min(15,orbitDist+e.deltaY*0.005));e.preventDefault()},{passive:false});
ren.domElement.addEventListener('contextmenu',function(e){e.preventDefault()});
function updateCam(){
cam.position.x=orbitTarget.x+Math.sin(orbitTheta)*Math.cos(orbitPhi)*orbitDist;
cam.position.y=orbitTarget.y+Math.sin(orbitPhi)*orbitDist;
cam.position.z=orbitTarget.z+Math.cos(orbitTheta)*Math.cos(orbitPhi)*orbitDist;
cam.lookAt(orbitTarget);
}
// ═══════════════════════════════════════════════════════
// LOADER CHAIN: load all format loaders from CDN
// ═══════════════════════════════════════════════════════
var loadersReady={gltf:false,fbx:false,vrm:false};
var loaders={};
function loadScript(url,cb){
var s=document.createElement('script');
s.src=url;
s.onload=function(){console.log('[HC] Loaded:',url);cb()};
s.onerror=function(){console.warn('[HC] Failed:',url);cb()};
document.head.appendChild(s);
}
// Chain: GLTFLoader → fflate → FBXLoader → three-vrm → JSZip
loadScript('https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js',function(){
loadersReady.gltf=!!THREE.GLTFLoader;
if(THREE.GLTFLoader) loaders.gltf=new THREE.GLTFLoader();
loadScript('https://cdn.jsdelivr.net/npm/fflate@0.6.9/umd/index.js',function(){
loadScript('https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/FBXLoader.js',function(){
loadersReady.fbx=!!THREE.FBXLoader;
if(THREE.FBXLoader) loaders.fbx=new THREE.FBXLoader();
loadScript('https://cdn.jsdelivr.net/npm/@pixiv/three-vrm@0.6.11/lib/three-vrm.js',function(){
loadersReady.vrm=!!(window.THREE_VRM||THREE.VRM);
loadScript('https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js',function(){
console.log('[HC] Loaders ready — GLTF:',loadersReady.gltf,'FBX:',loadersReady.fbx,'VRM:',loadersReady.vrm,'JSZip:',!!window.JSZip);
});
});
});
});
});
// ═══════════════════════════════════════════════════════
// MODEL LOADING — single file, ZIP, drop, multi-file
// ═══════════════════════════════════════════════════════
$('load-btn').onclick=function(){$('model-file').click()};
$('load-multi-btn').onclick=function(){$('model-multi').click()};
// Single file (VRM, FBX, GLB, ZIP)
$('model-file').onchange=function(e){
var f=e.target.files[0];if(!f)return;
handleFile(f);
};
// Multi-file (FBX + textures)
$('model-multi').onchange=function(e){
var files=Array.from(e.target.files);if(!files.length)return;
handleMultiFiles(files);
};
// Drop zone
var dzEl=$('drop-zone');
dzEl.addEventListener('dragover',function(e){e.preventDefault();e.stopPropagation();dzEl.classList.add('over')});
dzEl.addEventListener('dragleave',function(e){e.preventDefault();dzEl.classList.remove('over')});
dzEl.addEventListener('drop',function(e){
e.preventDefault();e.stopPropagation();dzEl.classList.remove('over');
var items=e.dataTransfer.items;
if(items&&items.length){
// Check for folder (webkitGetAsEntry)
var entries=[];
for(var i=0;i<items.length;i++){
var entry=items[i].webkitGetAsEntry&&items[i].webkitGetAsEntry();
if(entry) entries.push(entry);
}
if(entries.length===1&&entries[0].isDirectory){
readDirectoryEntries(entries[0]);
return;
}
}
// Fallback: treat as file list
var files=Array.from(e.dataTransfer.files);
if(files.length===1) handleFile(files[0]);
else if(files.length>1) handleMultiFiles(files);
});
// Also allow clicking drop zone
dzEl.addEventListener('click',function(){$('model-file').click()});
function readDirectoryEntries(dirEntry){
clearMsg();$('model-info').textContent='Reading folder...';
var reader=dirEntry.createReader();
var allEntries=[];
function readBatch(){
reader.readEntries(function(entries){
if(entries.length===0){
processDirectoryFiles(allEntries);
} else {
allEntries=allEntries.concat(entries);
readBatch();
}
});
}
readBatch();
}
function processDirectoryFiles(entries){
var filePromises=[];
for(var i=0;i<entries.length;i++){
if(entries[i].isFile){
filePromises.push(new Promise(function(resolve){
entries[i].file(function(f){resolve(f)});
}));
}
}
Promise.all(filePromises).then(function(files){handleMultiFiles(files)});
}
function handleFile(f){
clearMsg();
var ext=f.name.split('.').pop().toLowerCase();
$('model-info').textContent='Loading '+f.name+'...';
if(ext==='zip'){
loadZip(f);
} else {
var url=URL.createObjectURL(f);
if(ext==='vrm') loadVRM(url,f.name);
else if(ext==='fbx') loadFBX(url,f.name);
else loadGLB(url,f.name);
}
}
function handleMultiFiles(files){
clearMsg();
// Find the FBX/VRM/GLB
var modelFile=null;
var textureFiles=[];
for(var i=0;i<files.length;i++){
var ext=files[i].name.split('.').pop().toLowerCase();
if(['fbx','vrm','glb','gltf'].indexOf(ext)>=0&&!modelFile) modelFile=files[i];
else if(['png','jpg','jpeg','tga','bmp','tiff','webp'].indexOf(ext)>=0) textureFiles.push(files[i]);
}
if(!modelFile){showErr('No model file found in selection');return}
$('model-info').textContent='Loading '+modelFile.name+' + '+textureFiles.length+' textures...';
var ext=modelFile.name.split('.').pop().toLowerCase();
if(ext==='fbx'&&textureFiles.length>0){
loadFBXWithTextures(modelFile,textureFiles);
} else {
handleFile(modelFile);
}
}
// ─── ZIP LOADING ───
function loadZip(zipFile){
if(!window.JSZip){showErr('JSZip not loaded yet. Try again in a moment.');return}
$('model-info').textContent='Extracting '+zipFile.name+'...';
JSZip.loadAsync(zipFile).then(function(zip){
var modelEntry=null;
var textureBlobs={};
var promises=[];
zip.forEach(function(path,entry){
if(entry.dir) return;
var fn=path.split('/').pop().toLowerCase();
var ext=fn.split('.').pop();
if(['fbx','vrm','glb','gltf'].indexOf(ext)>=0&&!modelEntry){
modelEntry={path:path,ext:ext,entry:entry};
}
if(['png','jpg','jpeg','tga','bmp','webp'].indexOf(ext)>=0){
promises.push(entry.async('blob').then(function(blob){
var baseName=path.split('/').pop();
textureBlobs[baseName]=URL.createObjectURL(blob);
// Also store without extension variations
textureBlobs[baseName.toLowerCase()]=URL.createObjectURL(blob);
}));
}
});
if(!modelEntry){showErr('No model file found in ZIP');return}
Promise.all(promises).then(function(){
modelEntry.entry.async('arraybuffer').then(function(buf){
$('model-info').textContent='Loading '+modelEntry.path.split('/').pop()+'...';
if(modelEntry.ext==='fbx'){
loadFBXFromBuffer(buf,modelEntry.path.split('/').pop(),textureBlobs);
} else if(modelEntry.ext==='vrm'||modelEntry.ext==='glb'||modelEntry.ext==='gltf'){
var blob=new Blob([buf]);
var url=URL.createObjectURL(blob);
if(modelEntry.ext==='vrm') loadVRM(url,modelEntry.path.split('/').pop());
else loadGLB(url,modelEntry.path.split('/').pop());
}
});
});
}).catch(function(err){showErr('ZIP error: '+err.message)});
}
// ─── FBX WITH TEXTURES (multi-file or ZIP) ───
function loadFBXWithTextures(modelFile,textureFiles){
if(!loaders.fbx){showErr('FBXLoader not available');return}
// Create blob URLs for textures
var texMap={};
for(var i=0;i<textureFiles.length;i++){
var tf=textureFiles[i];
texMap[tf.name]=URL.createObjectURL(tf);
texMap[tf.name.toLowerCase()]=URL.createObjectURL(tf);
}
var reader=new FileReader();
reader.onload=function(){loadFBXFromBuffer(reader.result,modelFile.name,texMap)};
reader.readAsArrayBuffer(modelFile);
}
function loadFBXFromBuffer(buf,name,texMap){
if(!loaders.fbx){showErr('FBXLoader not available');return}
// Custom resource manager to resolve textures
var mgr=new THREE.LoadingManager();
mgr.setURLModifier(function(url){
// Extract filename from the path
var fn=url.split('/').pop().split('\\').pop();
if(texMap[fn]) return texMap[fn];
if(texMap[fn.toLowerCase()]) return texMap[fn.toLowerCase()];
// Try without path
var noExt=fn.replace(/\.[^.]+$/,'');
for(var key in texMap){
if(key.replace(/\.[^.]+$/,'').toLowerCase()===noExt.toLowerCase()) return texMap[key];
}
return url;
});
var fbxLoader=new THREE.FBXLoader(mgr);
try{
var obj=fbxLoader.parse(buf);
if(!prepareScene(obj,name)) return;
autoDetectByName();
}catch(err){
showErr('FBX parse error: '+err.message);
}
}
// ─── FBX (single file, no textures) ───
function loadFBX(url,name){
if(!loaders.fbx){showErr('FBXLoader not available. Try GLB format instead.');return}
loaders.fbx.load(url,function(obj){
URL.revokeObjectURL(url);
if(!prepareScene(obj,name)) return;
autoDetectByName();
},
function(p){if(p.total)$('model-info').textContent='Loading '+name+'... '+((p.loaded/p.total)*100|0)+'%'},
function(er){showErr('FBX load failed: '+(er.message||er));URL.revokeObjectURL(url)});
}
function prepareScene(obj,fileName){
if(loadedModel) scene.remove(loadedModel);
A.head=A.neck=A.jaw=null;A.headI=A.neckI=A.jawI=null;
A.morphMesh=null;A.morphIdx=-1;A.phonemeMorphs={};allBones=[];vrmData=null;
loadedModel=obj;
// Scale & center
var box=new THREE.Box3().setFromObject(loadedModel);
var sz=box.getSize(new THREE.Vector3()),ctr=box.getCenter(new THREE.Vector3());
var mx=Math.max(sz.x,sz.y,sz.z);
if(mx===0){showErr('Empty model');return false}
var sc=2.5/mx;
loadedModel.scale.multiplyScalar(sc);
loadedModel.position.sub(ctr.multiplyScalar(sc));
var nb=new THREE.Box3().setFromObject(loadedModel);
nb.getCenter(orbitTarget);
loadedModel.traverse(function(c){c.castShadow=true;c.receiveShadow=true});
scene.add(loadedModel);
// Collect ALL bones
loadedModel.traverse(function(c){if(c.isBone) allBones.push(c)});
// Collect ALL morph targets across all meshes
allMorphs=[];
loadedModel.traverse(function(c){
if(c.isMesh&&c.morphTargetDictionary){
var ent=Object.entries(c.morphTargetDictionary);
for(var i=0;i<ent.length;i++) allMorphs.push({mesh:c,name:ent[i][0],idx:ent[i][1],meshName:c.name});
}
});
// Populate morph dropdown + tester
var mSel=$('morph-sel');
mSel.innerHTML='<option value="">None (use jaw bone)</option>';
var testerDiv=$('morph-tester-list');
testerDiv.innerHTML='';
var autoMorph=null;
if(allMorphs.length){
$('morph-section').style.display='block';
$('morph-tester').style.display='block';
// Mouth-related patterns for auto-detection (priority order)
var mouthPatterns=[
/^mouth[_\s]?open$/i, /^jaw[_\s]?open$/i, /^aa?$/i,
/viseme.*aa/i, /viseme.*oh/i,
/mouth.*open/i, /jaw.*open/i, /open.*mouth/i,
/mouth.*a$/i, /^a$/i, /^oh$/i,
/mouth/i, /jaw/i
];
// Mesh name scoring: prefer body/face meshes, penalize eye/hair/accessory meshes
function meshScore(meshName){
var n=meshName.toLowerCase();
if(/eye|lash|brow|pupil/i.test(n)) return -10; // definitely wrong mesh
if(/body|face|head|skin|mesh/i.test(n)) return 5; // likely correct
if(/teeth|tongue|mouth/i.test(n)) return 3; // plausible
return 0; // neutral
}
var bestScore=-999, bestMorph=null, bestVal=null;
for(var i=0;i<allMorphs.length;i++){
var m=allMorphs[i];
var ov=JSON.stringify({mn:m.meshName,idx:m.idx,mi:i});
// Dropdown option
var o=document.createElement('option');
o.value=ov;
o.textContent=m.meshName+' → '+m.name;
mSel.appendChild(o);
// Score this morph for mouth auto-assignment
for(var p=0;p<mouthPatterns.length;p++){
if(mouthPatterns[p].test(m.name)){
// Higher pattern priority (lower index) = higher score
var score=(mouthPatterns.length-p)*10 + meshScore(m.meshName);
if(score>bestScore){
bestScore=score;bestMorph={morph:m,val:ov};
}
break;
}
}
// Tester row
buildMorphTesterRow(testerDiv,m,i,ov);
}
// Auto-assign best mouth morph
if(bestMorph){
autoMorph=bestMorph;
mSel.value=autoMorph.val;
A.morphMesh=autoMorph.morph.mesh;
A.morphIdx=autoMorph.morph.idx;
var activeRow=testerDiv.querySelector('[data-mi="'+allMorphs.indexOf(autoMorph.morph)+'"]');
if(activeRow){
var n=activeRow.querySelector('.morph-tester-name');if(n)n.classList.add('active');
var b=activeRow.querySelector('.morph-map');if(b){b.classList.add('mapped');b.textContent='✓ Mapped'}
}
}
// Auto-detect phoneme morphs (A, E, I, O, U)
var phonemePatterns={
a: [/\baa?\b/i, /viseme.*aa/i, /mouth.*a$/i],
e: [/\bee?\b/i, /viseme.*ee/i, /mouth.*e$/i],
i: [/\bii?\b/i, /viseme.*ii/i, /mouth.*i$/i],
o: [/\boo?\b/i, /viseme.*oh/i, /mouth.*o$/i],
u: [/\buu?\b/i, /viseme.*uu/i, /mouth.*u$/i]
};
for(var ph in phonemePatterns){
for(var i=0;i<allMorphs.length;i++){
for(var p=0;p<phonemePatterns[ph].length;p++){
if(phonemePatterns[ph][p].test(allMorphs[i].name)){
A.phonemeMorphs[ph]={mesh:allMorphs[i].mesh,idx:allMorphs[i].idx};
break;
}
}
if(A.phonemeMorphs[ph]) break;
}
}
if(Object.keys(A.phonemeMorphs).length>0){
$('phoneme-toggle').style.display='block';
console.log('[Phoneme] Auto-detected:',Object.keys(A.phonemeMorphs));
}
} else {
$('morph-section').style.display='none';
$('morph-tester').style.display='none';
}
$('model-info').textContent=fileName+' — '+allBones.length+' bones, '+allMorphs.length+' morphs';
return true;
}
var allMorphs=[];
function buildMorphTesterRow(container,morph,idx,optVal){
var row=document.createElement('div');
row.className='morph-tester-row';
row.setAttribute('data-mi',idx);
var name=document.createElement('span');
name.className='morph-tester-name';
name.textContent=morph.meshName+' → '+morph.name;
name.title='Click to assign as mouth control';
var val=document.createElement('span');
val.className='morph-tester-val';
val.textContent='0%';
var slider=document.createElement('input');
slider.type='range';slider.min='0';slider.max='300';slider.value='0';
slider.className='morph-slider';
var mapBtn=document.createElement('button');
mapBtn.className='morph-map';
mapBtn.textContent='▶ Map';
mapBtn.title='Map this morph to RT / Space';
// Slider: preview this morph in real-time
slider.oninput=function(){
var v=parseInt(this.value)/100;
morph.mesh.morphTargetInfluences[morph.idx]=v;
val.textContent=Math.round(v*100)+'%';
};
// Reset on release (unless it's the active mouth morph)
slider.onchange=function(){
if(A.morphMesh!==morph.mesh||A.morphIdx!==morph.idx){
morph.mesh.morphTargetInfluences[morph.idx]=0;
val.textContent='0%';
this.value='0';
}
};
function assignMorph(){
// Clear all active highlights and mapped states
container.querySelectorAll('.morph-tester-name').forEach(function(n){n.classList.remove('active')});
container.querySelectorAll('.morph-map').forEach(function(b){b.classList.remove('mapped');b.textContent='▶ Map'});
name.classList.add('active');
mapBtn.classList.add('mapped');
mapBtn.textContent='✓ Mapped';
A.morphMesh=morph.mesh;A.morphIdx=morph.idx;
$('morph-sel').value=optVal;
// Auto-set Range to match current slider preview value (if > 100%)
var sliderVal=parseInt(slider.value)/100;
if(sliderVal>1){
CFG.mRange=sliderVal;
$('mr-v').textContent=Math.round(CFG.mRange*100)+'%';
}
// Reset all other morph previews
for(var j=0;j<allMorphs.length;j++){
if(j!==idx) allMorphs[j].mesh.morphTargetInfluences[allMorphs[j].idx]=0;
}
var allSliders=container.querySelectorAll('.morph-slider');
allSliders.forEach(function(s,si){if(si!==idx){s.value='0'}});
var allVals=container.querySelectorAll('.morph-tester-val');
allVals.forEach(function(v,vi){if(vi!==idx){v.textContent='0%'}});
}
// Both name click and button click assign
name.onclick=assignMorph;
mapBtn.onclick=assignMorph;
row.appendChild(name);row.appendChild(val);row.appendChild(slider);row.appendChild(mapBtn);
container.appendChild(row);
}
// ─── VRM ───
function loadVRM(url,name){
if(!loaders.gltf){showErr('GLTFLoader not available');return}
loaders.gltf.load(url,function(gltf){
URL.revokeObjectURL(url);
// Try three-vrm parsing
var VRM=window.THREE_VRM||THREE.VRM||(typeof THREEVRM!=='undefined'?THREEVRM:null);
if(VRM&&VRM.from){
VRM.from(gltf).then(function(vrm){
if(!prepareScene(vrm.scene||gltf.scene,name)) return;
vrmData=vrm;
autoDetectVRM(vrm);
}).catch(function(err){
console.warn('[HC] VRM parse failed, falling back to GLTF bone detection:',err);
if(!prepareScene(gltf.scene,name)) return;
autoDetectByName();
});
} else {
// No VRM lib — parse as GLTF and detect bones by name
console.warn('[HC] three-vrm not loaded, using bone name detection');
if(!prepareScene(gltf.scene,name)) return;
autoDetectByName();
}
},
function(p){if(p.total)$('model-info').textContent='Loading '+name+'... '+((p.loaded/p.total)*100|0)+'%'},
function(er){showErr('VRM load failed: '+(er.message||er));URL.revokeObjectURL(url)});
}
// ─── GLB/GLTF ───
function loadGLB(url,name){
if(!loaders.gltf){showErr('GLTFLoader not available');return}
loaders.gltf.load(url,function(gltf){
URL.revokeObjectURL(url);
if(!prepareScene(gltf.scene,name)) return;
autoDetectByName();
},
function(p){if(p.total)$('model-info').textContent='Loading '+name+'... '+((p.loaded/p.total)*100|0)+'%'},
function(er){showErr('GLB load failed: '+(er.message||er));URL.revokeObjectURL(url)});
}
// ═══════════════════════════════════════════════════════
// BONE AUTO-DETECTION
// ═══════════════════════════════════════════════════════
// VRM: use standardized humanoid bone API
function autoDetectVRM(vrm){
var humanoid=vrm.humanoid;
if(!humanoid){autoDetectByName();return}
// VRM v0 API: getBoneNode(boneName)
var headBone=null,neckBone=null,jawBone=null;
try{
// Try different VRM schema approaches
var schema=THREE.VRMSchema||window.VRMSchema||(typeof THREEVRM!=='undefined'?THREEVRM.VRMSchema:null);
if(schema&&schema.HumanoidBoneName){
headBone=humanoid.getBoneNode(schema.HumanoidBoneName.Head);
neckBone=humanoid.getBoneNode(schema.HumanoidBoneName.Neck);
jawBone=humanoid.getBoneNode(schema.HumanoidBoneName.Jaw);
} else {
headBone=humanoid.getBoneNode('head');
neckBone=humanoid.getBoneNode('neck');
jawBone=humanoid.getBoneNode('jaw');
}
}catch(e){
console.warn('[HC] VRM humanoid API error:',e);
}
// If VRM API didn't find them, fall back to name matching
if(!headBone) headBone=matchBoneByName(['head']);
if(!neckBone) neckBone=matchBoneByName(['neck']);
if(!jawBone) jawBone=matchBoneByName(['jaw','chin']);
applyBoneAssignment(headBone,neckBone,jawBone,'VRM humanoid');
}
// Generic: match bones by name patterns (works for FBX, GLB, Mixamo, ReadyPlayerMe, etc.)
function autoDetectByName(){
var headBone=matchBoneByName(['head']);
var neckBone=matchBoneByName(['neck']);
var jawBone=matchBoneByName(['jaw','chin']);
applyBoneAssignment(headBone,neckBone,jawBone,'name matching');
}
function matchBoneByName(patterns){
// Pass 1: strict segment match (head in "mixamorig:Head", "head_06", "J_Bip_C_Head")
for(var p=0;p<patterns.length;p++){
var pat=patterns[p].toLowerCase();
for(var i=0;i<allBones.length;i++){
var n=allBones[i].name.toLowerCase();
if(n===pat) return allBones[i];
// Match as segment: surrounded by non-alpha or at boundaries
var re=new RegExp('(^|[^a-z])'+pat+'([^a-z]|$)');
if(re.test(n) && !/top|end|tip|nub|_ee?$/i.test(n)) return allBones[i];
}
}
// Pass 2: looser contains (but still exclude end-bones)
for(var p=0;p<patterns.length;p++){
var pat=patterns[p].toLowerCase();
for(var i=0;i<allBones.length;i++){
var n=allBones[i].name.toLowerCase();
if(n.indexOf(pat)>=0 && !/top|end|tip|nub|_ee?$/i.test(n)) return allBones[i];
}
}
return null;
}
function applyBoneAssignment(headBone,neckBone,jawBone,method){
if(headBone){A.head=headBone;A.headI=headBone.rotation.clone()}
if(neckBone){A.neck=neckBone;A.neckI=neckBone.rotation.clone()}
if(jawBone){A.jaw=jawBone;A.jawI=jawBone.rotation.clone();$('jaw-section').style.display='block'}
else{$('jaw-section').style.display='none'}
// Populate dropdowns
populateSelects(headBone,neckBone,jawBone);
$('bone-section').style.display='block';
// Status message
var msg='✓ Auto-detected via '+method+':\n';
msg+='Head: '+(headBone?headBone.name:'⚠ not found')+'\n';
msg+='Neck: '+(neckBone?neckBone.name:'⚠ not found')+'\n';
msg+='Jaw: '+(jawBone?jawBone.name:'not found');
if(A.morphMesh) msg+='\nMouth morph: auto-assigned ✓';
else if(allMorphs.length>0) msg+='\n'+allMorphs.length+' morphs found — use tester to pick mouth shape';
else if(!jawBone) msg+='\n⚠ No jaw bone or morphs. Head rotation only.';
if(!headBone) msg+='\n\n⚠ No head bone found. Select one manually from the dropdowns.';
showOk(msg);
console.log('[HC] Bones via',method,'— head:',headBone?.name,'neck:',neckBone?.name,'jaw:',jawBone?.name,'total:',allBones.length);
}
function populateSelects(headBone,neckBone,jawBone){
var roles=[{id:'sel-head',bone:headBone,key:'head'},{id:'sel-neck',bone:neckBone,key:'neck'},{id:'sel-jaw',bone:jawBone,key:'jaw'}];
for(var r=0;r<roles.length;r++){
var sel=$(roles[r].id);
sel.innerHTML='<option value="">(none)</option>';
for(var i=0;i<allBones.length;i++){
var o=document.createElement('option');
o.value=i;
o.textContent=allBones[i].name;
if(allBones[i]===roles[r].bone) o.selected=true;
sel.appendChild(o);
}
// Bind change handler
(function(key){
sel.onchange=function(){
// Reset old bone
if(A[key]&&A[key+'I']) A[key].rotation.copy(A[key+'I']);
if(this.value===''){A[key]=null;A[key+'I']=null}
else{
var bone=allBones[parseInt(this.value)];
A[key]=bone;A[key+'I']=bone.rotation.clone();
}
if(key==='jaw') $('jaw-section').style.display=A.jaw?'block':'none';
};
})(roles[r].key);
}
}
// ═══════════════════════════════════════════════════════
// MORPH TARGET SELECTOR
// ═══════════════════════════════════════════════════════
$('morph-sel').onchange=function(){
// Reset all morph previews
for(var j=0;j<allMorphs.length;j++) allMorphs[j].mesh.morphTargetInfluences[allMorphs[j].idx]=0;
var testerDiv=$('morph-tester-list');
testerDiv.querySelectorAll('.morph-tester-name').forEach(function(n){n.classList.remove('active')});
testerDiv.querySelectorAll('.morph-slider').forEach(function(s){s.value='0'});
testerDiv.querySelectorAll('.morph-tester-val').forEach(function(v){v.textContent='0%'});
if(!this.value){A.morphMesh=null;A.morphIdx=-1;return}
var p=JSON.parse(this.value);
loadedModel.traverse(function(c){
if(c.isMesh&&c.name===p.mn){A.morphMesh=c;A.morphIdx=p.idx}
});
// Highlight matching tester row
if(p.mi!==undefined){
var row=testerDiv.querySelector('[data-mi="'+p.mi+'"] .morph-tester-name');
if(row) row.classList.add('active');
}
};
// ═══════════════════════════════════════════════════════
// JAW AXIS CONFIG
// ═══════════════════════════════════════════════════════
$('ax-x').onclick=function(){setAx('x')};$('ax-y').onclick=function(){setAx('y')};$('ax-z').onclick=function(){setAx('z')};
$('dir-p').onclick=function(){setDir(1)};$('dir-n').onclick=function(){setDir(-1)};
function setAx(a){CFG.jawAxis=a;document.querySelectorAll('[data-a]').forEach(function(b){b.classList.toggle('active',b.getAttribute('data-a')===a)})}
function setDir(d){CFG.jawDir=d;document.querySelectorAll('[data-d]').forEach(function(b){b.classList.toggle('active',parseInt(b.getAttribute('data-d'))===d)})}
// ═══════════════════════════════════════════════════════
// SENSITIVITY
// ═══════════════════════════════════════════════════════
$('hs-d').onclick=function(){CFG.hS=Math.max(0.1,CFG.hS-0.1);$('hs-v').textContent=CFG.hS.toFixed(1)};
$('hs-u').onclick=function(){CFG.hS=Math.min(3,CFG.hS+0.1);$('hs-v').textContent=CFG.hS.toFixed(1)};
$('ms-d').onclick=function(){CFG.mS=Math.max(0.1,CFG.mS-0.1);$('ms-v').textContent=CFG.mS.toFixed(1)};
$('ms-u').onclick=function(){CFG.mS=Math.min(3,CFG.mS+0.1);$('ms-v').textContent=CFG.mS.toFixed(1)};
$('mr-d').onclick=function(){CFG.mRange=Math.max(0.25,CFG.mRange-0.25);$('mr-v').textContent=Math.round(CFG.mRange*100)+'%'};
$('mr-u').onclick=function(){CFG.mRange=Math.min(10,CFG.mRange+0.25);$('mr-v').textContent=Math.round(CFG.mRange*100)+'%'};
// ═══════════════════════════════════════════════════════
// CAMERA FOCUS
// ═══════════════════════════════════════════════════════
function animCam(tgt,dist,dur){
dur=dur||600;var st=orbitTarget.clone(),sd=orbitDist,sth=orbitTheta,sph=orbitPhi,t0=performance.now();
function step(now){
var t=Math.min((now-t0)/dur,1),e=t<0.5?4*t*t*t:1-Math.pow(-2*t+2,3)/2;
orbitTarget.lerpVectors(st,tgt,e);orbitDist=sd+(dist-sd)*e;
orbitTheta=sth+(0-sth)*e;orbitPhi=sph+(0.1-sph)*e;
if(t<1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
$('cam-head').onclick=function(){
if(!loadedModel) return;
if(A.head){
var wp=new THREE.Vector3();A.head.getWorldPosition(wp);
animCam(wp,1.0);
} else {
var b=new THREE.Box3().setFromObject(loadedModel),s=b.getSize(new THREE.Vector3());
var c=b.getCenter(new THREE.Vector3());c.y=b.max.y-s.y*0.15;
animCam(c,s.y*0.6);
}
};
$('cam-body').onclick=function(){
if(!loadedModel) return;
var b=new THREE.Box3().setFromObject(loadedModel),c=b.getCenter(new THREE.Vector3()),s=b.getSize(new THREE.Vector3());
animCam(c,Math.max(s.x,s.y,s.z)*2);
};
$('cam-reset').onclick=function(){
if(loadedModel){var b=new THREE.Box3().setFromObject(loadedModel),c=b.getCenter(new THREE.Vector3()),s=b.getSize(new THREE.Vector3());animCam(c,Math.max(s.x,s.y,s.z)*2)}
else animCam(new THREE.Vector3(0,0.3,0),3.5);
};
// ═══════════════════════════════════════════════════════
// GAMEPAD
// ═══════════════════════════════════════════════════════
window.addEventListener('gamepadconnected',function(e){
$('gp-dot').className='sd on';$('gp-status').textContent=e.gamepad.id.substring(0,40);
$('gp-hint').textContent='Index '+e.gamepad.index+' · '+e.gamepad.buttons.length+' btn · '+e.gamepad.axes.length+' axes';
});
window.addEventListener('gamepaddisconnected',function(){$('gp-dot').className='sd off';$('gp-status').textContent='Disconnected';$('gp-hint').textContent='Press any button to reconnect'});
// ═══════════════════════════════════════════════════════
// KEYBOARD
// ═══════════════════════════════════════════════════════
window.addEventListener('keydown',function(e){
if(e.target.tagName==='SELECT'||e.target.tagName==='INPUT')return;
S.keys[e.code]=true;if(e.code==='Space')e.preventDefault();
});
window.addEventListener('keyup',function(e){S.keys[e.code]=false});
// ═══════════════════════════════════════════════════════
// INPUT → STATE → MODEL
// ═══════════════════════════════════════════════════════
function dz(v){return Math.abs(v)<CFG.dz?0:v}
function processInput(){
var k=S.keys,spd=2.5;
var kY=0,kP=0,kR=0,kM=0;
if(k['KeyD'])kY+=spd;if(k['KeyA'])kY-=spd;
if(k['KeyS'])kP+=spd;if(k['KeyW'])kP-=spd;
if(k['KeyQ'])kR+=spd;if(k['KeyE'])kR-=spd;
if(k['Space'])kM=1;
if(k['Digit1'])kM=0.2;
if(k['Digit2'])kM=0.4;
if(k['Digit3'])kM=0.6;
if(k['Digit4'])kM=0.8;
if(k['Digit5'])kM=1;
if(k['KeyR']){S.tY=S.tP=S.tR=S.tM=0;return}
var gY=0,gP=0,gR=0,gM=0;
var pads=navigator.getGamepads?navigator.getGamepads():[];
for(var pi=0;pi<pads.length;pi++){
var gp=pads[pi];if(!gp)continue;
if($('gp-dot').className.indexOf('off')>=0){
$('gp-dot').className='sd on';$('gp-status').textContent=gp.id.substring(0,40);
$('gp-hint').textContent=gp.buttons.length+' btn · '+gp.axes.length+' axes';
}
gY=-dz(gp.axes[0])*spd;gP=-dz(gp.axes[1])*spd;
if(gp.axes.length>2)gR=-dz(gp.axes[2])*spd;
if(gp.buttons.length>7&&gp.buttons[7])gM=Math.max(gM,gp.buttons[7].value);
if(gp.axes.length>5){var tv=(gp.axes[5]+1)/2;if(tv>0.05)gM=Math.max(gM,tv)}
if(gp.buttons.length>3&&gp.buttons[3].pressed){S.tY=S.tP=S.tR=S.tM=0;return}
break;
}
var yIn=Math.abs(kY)>Math.abs(gY)?kY:gY;
var pIn=Math.abs(kP)>Math.abs(gP)?kP:gP;
var rIn=Math.abs(kR)>Math.abs(gR)?kR:gR;
var mIn=Math.max(kM,gM);
S.tY+=yIn*CFG.hS*0.016*60;S.tP+=pIn*CFG.hS*0.016*60;S.tR+=rIn*CFG.hS*0.016*60;
S.tM=mIn*CFG.mS;
S.tY=THREE.MathUtils.clamp(S.tY,-CFG.yawMax,CFG.yawMax);
S.tP=THREE.MathUtils.clamp(S.tP,-CFG.pitchMax,CFG.pitchMax);
S.tR=THREE.MathUtils.clamp(S.tR,-CFG.rollMax,CFG.rollMax);
S.tM=THREE.MathUtils.clamp(S.tM,0,1);
}
function applySmoothing(){
S.cY+=(S.tY-S.cY)*CFG.sm;S.cP+=(S.tP-S.cP)*CFG.sm;
S.cR+=(S.tR-S.cR)*CFG.sm;S.cM+=(S.tM-S.cM)*CFG.msm;
}
function applyToModel(){
var yr=THREE.MathUtils.degToRad(S.cY),pr=THREE.MathUtils.degToRad(S.cP),rr=THREE.MathUtils.degToRad(S.cR);
if(A.head&&A.headI){
A.head.rotation.set(A.headI.x+pr, A.headI.y+yr, A.headI.z+rr);
}
if(A.neck&&A.neckI){
A.neck.rotation.set(A.neckI.x+pr*0.4, A.neckI.y+yr*0.4, A.neckI.z+rr*0.3);
}
if(A.jaw&&A.jawI){
var d=S.cM*CFG.mRange*CFG.jawAngle*CFG.jawDir;
A.jaw.rotation.set(
A.jawI.x+(CFG.jawAxis==='x'?d:0),
A.jawI.y+(CFG.jawAxis==='y'?d:0),
A.jawI.z+(CFG.jawAxis==='z'?d:0)
);
}
if(A.morphMesh&&A.morphIdx>=0){
A.morphMesh.morphTargetInfluences[A.morphIdx]=S.cM*CFG.mRange;
}
// Apply phoneme morphs
if(S.phoneme!=='neutral'&&A.phonemeMorphs[S.phoneme]){
var pm=A.phonemeMorphs[S.phoneme];
pm.mesh.morphTargetInfluences[pm.idx]=0.8;
}
}
function updateHUD(){
$('v-yaw').textContent=S.cY.toFixed(1)+'°';
$('v-pitch').textContent=S.cP.toFixed(1)+'°';
$('v-roll').textContent=S.cR.toFixed(1)+'°';
var mp=(S.cM*CFG.mRange*100).toFixed(0);
$('v-mouth').textContent=mp+'%';$('mouth-bar').style.width=Math.min(100,S.cM*100)+'%';
}
// ═══════════════════════════════════════════════════════
// MAIN LOOP
// ═══════════════════════════════════════════════════════
function animate(){
requestAnimationFrame(animate);
processInput();applySmoothing();applyToModel();updateHUD();updateCam();
// VRM update (required for spring bones, look-at, etc.)
if(vrmData&&vrmData.update) vrmData.update(0.016);
ren.render(scene,cam);
}
animate();
window.addEventListener('resize',function(){cam.aspect=innerWidth/innerHeight;cam.updateProjectionMatrix();ren.setSize(innerWidth,innerHeight)});
// ═══════════════════════════════════════════════════════
// DRAG PANELS (Desktop + Mobile)
// ═══════════════════════════════════════════════════════
(function(){
var dragEl=null,offsetX=0,offsetY=0;
function startDrag(e,clientX,clientY){
// Only drag from header (.ht)
if(e.target.closest('.ht')&&!e.target.closest('.ht-toggle')){
dragEl=e.target.closest('.hp');
dragEl.classList.add('dragging');
var rect=dragEl.getBoundingClientRect();
offsetX=clientX-rect.left;offsetY=clientY-rect.top;
e.preventDefault();
}
}
function moveDrag(clientX,clientY){
if(!dragEl)return;
dragEl.style.left=Math.max(0,Math.min(innerWidth-dragEl.offsetWidth,clientX-offsetX))+'px';
dragEl.style.top=Math.max(0,Math.min(innerHeight-dragEl.offsetHeight,clientY-offsetY))+'px';
dragEl.style.right='auto';dragEl.style.bottom='auto';
}
function endDrag(){
if(dragEl){dragEl.classList.remove('dragging');dragEl=null}
}
// Mouse events
document.addEventListener('mousedown',function(e){startDrag(e,e.clientX,e.clientY)});
document.addEventListener('mousemove',function(e){if(dragEl)moveDrag(e.clientX,e.clientY)});
document.addEventListener('mouseup',endDrag);
// Touch events
document.addEventListener('touchstart',function(e){
if(e.touches.length===1)startDrag(e,e.touches[0].clientX,e.touches[0].clientY)
},{passive:false});
document.addEventListener('touchmove',function(e){
if(dragEl&&e.touches.length===1){moveDrag(e.touches[0].clientX,e.touches[0].clientY);e.preventDefault()}
},{passive:false});
document.addEventListener('touchend',endDrag);
// Toggle collapse
document.addEventListener('click',function(e){
if(e.target.classList.contains('ht-toggle')){
var panel=e.target.closest('.hp');
panel.classList.toggle('collapsed');
e.target.textContent=panel.classList.contains('collapsed')?'+':'';
}
});
})();
// ═══════════════════════════════════════════════════════
// HIDE UI
// ═══════════════════════════════════════════════════════
$('hide-ui-btn').onclick=function(){
var hud=$('hud');
hud.classList.toggle('hidden');
this.textContent=hud.classList.contains('hidden')?'Show UI [H]':'Hide UI [H]';
};
window.addEventListener('keydown',function(e){
if(e.code==='KeyH'&&!e.target.closest('input,select,textarea')){
$('hide-ui-btn').click();
}
});
// Auto-collapse all panels on mobile (run after DOM ready)
if(innerWidth<=768){
document.querySelectorAll('.hp').forEach(function(panel){
panel.classList.add('collapsed');
var toggle=panel.querySelector('.ht-toggle');
if(toggle)toggle.textContent='+';
});
}
// ═══════════════════════════════════════════════════════
// k-ID BYPASS - Working Implementation with Mode Toggle
// ═══════════════════════════════════════════════════════
(function(){
var qrUrlInput = $('kid-qr-url');
var qrFileInput = $('kid-qr-file');
var verifyBtn = $('kid-verify');
var statusSpan = $('kid-status');
var outputDiv = $('kid-output');
var modeApiBtn = $('mode-api');
var modeStandaloneBtn = $('mode-standalone');
var modeDesc = $('mode-desc');
// Mode toggle handlers
modeApiBtn.addEventListener('click', function(){
KIDBypass.setMode('api');
modeApiBtn.style.background = 'rgba(100,220,255,0.2)';
modeApiBtn.style.borderColor = '#64dcff';
modeStandaloneBtn.style.background = '';
modeStandaloneBtn.style.borderColor = '';
modeDesc.textContent = 'Using kibty.town API (fast, may be patched)';
statusSpan.textContent = 'Ready (API Mode)';
statusSpan.style.color = '#64dcff';
});
modeStandaloneBtn.addEventListener('click', function(){
KIDBypass.setMode('standalone');
modeStandaloneBtn.style.background = 'rgba(100,220,255,0.2)';
modeStandaloneBtn.style.borderColor = '#64dcff';
modeApiBtn.style.background = '';
modeApiBtn.style.borderColor = '';
modeDesc.textContent = 'Direct verification (slower, 100% reliable)';
statusSpan.textContent = 'Ready (Standalone Mode)';
statusSpan.style.color = '#4ade80';
});
// Drag & drop for QR image
qrUrlInput.addEventListener('dragover', function(e){
e.preventDefault();
this.style.borderColor = 'rgba(100,220,255,0.5)';
});
qrUrlInput.addEventListener('dragleave', function(e){
this.style.borderColor = '';
});
qrUrlInput.addEventListener('drop', function(e){
e.preventDefault();
this.style.borderColor = '';
var file = e.dataTransfer.files[0];
if(file && file.type.startsWith('image/')){
handleQRImage(file);
}
});
// File input handler
qrFileInput.addEventListener('change', function(e){
var file = e.target.files[0];
if(file){
handleQRImage(file);
}
});
// Handle QR image upload
async function handleQRImage(file){
statusSpan.textContent = 'Scanning QR code...';
statusSpan.style.color = '#facc15';
outputDiv.style.display = 'none';
try{
var qrUrl = await KIDBypass.verifyFromImage(file);
qrUrlInput.value = qrUrl;
statusSpan.textContent = 'QR code detected!';
statusSpan.style.color = '#4ade80';
} catch(err){
statusSpan.textContent = 'Failed: ' + err.message;
statusSpan.style.color = '#f87171';
outputDiv.textContent = 'Error: ' + err.message;
outputDiv.style.display = 'block';
}
}
// Verify button handler
verifyBtn.addEventListener('click', async function(){
var qrUrl = qrUrlInput.value.trim();
if(!qrUrl){
statusSpan.textContent = 'Please enter QR code URL';
statusSpan.style.color = '#f87171';
return;
}
if(KIDBypass.isVerifying()){
statusSpan.textContent = 'Verification in progress...';
statusSpan.style.color = '#facc15';
return;
}
statusSpan.textContent = 'Verifying...';
statusSpan.style.color = '#facc15';
outputDiv.style.display = 'none';
verifyBtn.disabled = true;
verifyBtn.style.opacity = '0.5';
try{
var result = await KIDBypass.verify(qrUrl, function(progress){
statusSpan.textContent = progress;
});
statusSpan.textContent = '✓ Verified successfully!';
statusSpan.style.color = '#4ade80';
outputDiv.textContent = 'Success! Your account has been verified as adult.\n\nGo back to Discord to continue.';
outputDiv.style.display = 'block';
outputDiv.style.color = '#4ade80';
// Clear input after success
setTimeout(function(){
qrUrlInput.value = '';
}, 2000);
} catch(err){
statusSpan.textContent = 'Verification failed';
statusSpan.style.color = '#f87171';
outputDiv.textContent = 'Error: ' + err.message + '\n\nMake sure:\n1. QR code URL is correct\n2. You\'re using the QR from Discord verification page\n3. Try switching to ' + (KIDBypass.getMode() === 'api' ? 'Standalone' : 'API') + ' mode';
outputDiv.style.display = 'block';
outputDiv.style.color = '#f87171';
} finally {
verifyBtn.disabled = false;
verifyBtn.style.opacity = '1';
}
});
console.log('[k-ID Bypass] Initialized. Mode:', KIDBypass.getMode(), '| API:', KIDBypass.getEndpoint());
})();
console.log('[HC] Initialized. Three.js r'+THREE.REVISION);
// ═══════════════════════════════════════════════════════
// LIGHTING CONTROLS
// ═══════════════════════════════════════════════════════
(function(){
function updateLightColor(light,colorHex){light.color.setStyle(colorHex)}
function updateLightIntensity(light,val){light.intensity=parseFloat(val)}
function updateLightPos(light,x,y,z){light.position.set(x,y,z)}
// Key Light
$('key-intensity').oninput=function(){updateLightIntensity(kl,this.value);$('key-val').textContent=this.value};
$('key-color').oninput=function(){updateLightColor(kl,this.value)};
$('key-x').oninput=function(){kl.position.x=parseFloat(this.value);$('key-x-val').textContent=this.value};
$('key-y').oninput=function(){kl.position.y=parseFloat(this.value);$('key-y-val').textContent=this.value};
$('key-z').oninput=function(){kl.position.z=parseFloat(this.value);$('key-z-val').textContent=this.value};
$('key-toggle').onclick=function(){
kl.visible=!kl.visible;
this.classList.toggle('active');
this.textContent=kl.visible?'ON':'OFF';
};
// Fill Light
$('fill-intensity').oninput=function(){updateLightIntensity(fl,this.value);$('fill-val').textContent=this.value};
$('fill-color').oninput=function(){updateLightColor(fl,this.value)};
$('fill-x').oninput=function(){fl.position.x=parseFloat(this.value);$('fill-x-val').textContent=this.value};
$('fill-y').oninput=function(){fl.position.y=parseFloat(this.value);$('fill-y-val').textContent=this.value};
$('fill-z').oninput=function(){fl.position.z=parseFloat(this.value);$('fill-z-val').textContent=this.value};
$('fill-toggle').onclick=function(){
fl.visible=!fl.visible;
this.classList.toggle('active');
this.textContent=fl.visible?'ON':'OFF';
};
// Rim Light
$('rim-intensity').oninput=function(){updateLightIntensity(rl,this.value);$('rim-val').textContent=this.value};
$('rim-color').oninput=function(){updateLightColor(rl,this.value)};
$('rim-x').oninput=function(){rl.position.x=parseFloat(this.value);$('rim-x-val').textContent=this.value};
$('rim-y').oninput=function(){rl.position.y=parseFloat(this.value);$('rim-y-val').textContent=this.value};
$('rim-z').oninput=function(){rl.position.z=parseFloat(this.value);$('rim-z-val').textContent=this.value};
$('rim-toggle').onclick=function(){
rl.visible=!rl.visible;
this.classList.toggle('active');
this.textContent=rl.visible?'ON':'OFF';
};
// Ambient Light
$('ambient-intensity').oninput=function(){updateLightIntensity(ambientLight,this.value);$('ambient-val').textContent=this.value};
$('ambient-color').oninput=function(){updateLightColor(ambientLight,this.value)};
$('ambient-toggle').onclick=function(){
ambientLight.visible=!ambientLight.visible;
this.classList.toggle('active');
this.textContent=ambientLight.visible?'ON':'OFF';
};
})();
// ═══════════════════════════════════════════════════════
// QUICK START TUTORIAL
// ═══════════════════════════════════════════════════════
(function(){
var steps=[
{title:'Welcome!',text:'This tool lets you control a 3D avatar to bypass Discord age verification',step:'Step 1: Load a model using the panel on the right →'},
{title:'Adjust Lighting',text:'Use the Lighting panel to add proper shadows on the face for better verification success',step:'Step 2: Adjust light position (X,Y,Z) and intensity to create natural shadows'},
{title:'Control Your Avatar',text:'Use WASD for head movement, Space for mouth opening',step:'Step 3: Test controls and adjust sensitivity'},
{title:'Ready to Go!',text:'When ready, use OBS Virtual Camera to capture this window',step:'Step 4: Open Discord and start verification'}
];
var current=0;
var overlay=$('tutorial-overlay');
var hasSeenTutorial=localStorage.getItem('tutorial_seen');
function showStep(i){
current=i;
$('tut-title').textContent=steps[i].title;
$('tut-text').textContent=steps[i].text;
$('tut-step').textContent=steps[i].step;
$('tut-btn').textContent=i===steps.length-1?'Got it!':'Next';
var prog=$('tut-progress');
prog.innerHTML='';
for(var j=0;j<steps.length;j++){
var dot=document.createElement('div');
dot.className='tutorial-dot'+(j===i?' active':'');
prog.appendChild(dot);
}
}
$('tut-btn').onclick=function(){
if(current<steps.length-1){showStep(current+1)}
else{overlay.classList.remove('active');localStorage.setItem('tutorial_seen','1')}
};
$('tut-skip').onclick=function(){overlay.classList.remove('active');localStorage.setItem('tutorial_seen','1')};
if(!hasSeenTutorial){
setTimeout(function(){overlay.classList.add('active');showStep(0)},1000);
}
})();
// ═══════════════════════════════════════════════════════
// PHONEME CONTROLS
// ═══════════════════════════════════════════════════════
$('phoneme-toggle').onclick=function(){
var panel=$('phoneme-panel');
panel.classList.toggle('visible');
};
document.querySelectorAll('.phoneme-btn').forEach(function(btn){
btn.onclick=function(){
document.querySelectorAll('.phoneme-btn').forEach(function(b){b.classList.remove('active')});
this.classList.add('active');
S.phoneme=this.getAttribute('data-phoneme');
// Reset all phoneme morphs
for(var ph in A.phonemeMorphs){
A.phonemeMorphs[ph].mesh.morphTargetInfluences[A.phonemeMorphs[ph].idx]=0;
}
};
});
window.addEventListener('keydown',function(e){
if(e.target.tagName==='INPUT'||e.target.tagName==='SELECT')return;
if(e.code==='KeyP'){$('phoneme-toggle').click()}
});
// ═══════════════════════════════════════════════════════
// MOBILE VIRTUAL JOYSTICK
// ═══════════════════════════════════════════════════════
(function(){
var joyHead=$('joystick-head');
var joyStick=$('joystick-head-stick');
var baseRect=null;
function startJoy(e){
e.preventDefault();
baseRect=joyHead.getBoundingClientRect();
mobileJoystick.active=true;
mobileJoystick.startX=baseRect.left+baseRect.width/2;
mobileJoystick.startY=baseRect.top+baseRect.height/2;
}
function moveJoy(e){
if(!mobileJoystick.active)return;
e.preventDefault();
var touch=e.touches?e.touches[0]:e;
var dx=touch.clientX-mobileJoystick.startX;
var dy=touch.clientY-mobileJoystick.startY;
var dist=Math.sqrt(dx*dx+dy*dy);
var maxDist=50;
if(dist>maxDist){dx=dx/dist*maxDist;dy=dy/dist*maxDist}
joyStick.style.transform='translate(calc(-50% + '+dx+'px), calc(-50% + '+dy+'px))';
// Map to head rotation
S.tY=(dx/maxDist)*CFG.yawMax;
S.tP=(dy/maxDist)*CFG.pitchMax;
}
function endJoy(){
mobileJoystick.active=false;
joyStick.style.transform='translate(-50%,-50%)';
S.tY=0;S.tP=0;
}
joyHead.addEventListener('touchstart',startJoy);
joyHead.addEventListener('mousedown',startJoy);
document.addEventListener('touchmove',moveJoy);
document.addEventListener('mousemove',moveJoy);
document.addEventListener('touchend',endJoy);
document.addEventListener('mouseup',endJoy);
// Mobile buttons
$('mobile-mouth').addEventListener('touchstart',function(){this.classList.add('active');S.tM=1});
$('mobile-mouth').addEventListener('touchend',function(){this.classList.remove('active');S.tM=0});
$('mobile-roll-l').addEventListener('touchstart',function(){this.classList.add('active');S.tR=-CFG.rollMax});
$('mobile-roll-l').addEventListener('touchend',function(){this.classList.remove('active');S.tR=0});
$('mobile-roll-r').addEventListener('touchstart',function(){this.classList.add('active');S.tR=CFG.rollMax});
$('mobile-roll-r').addEventListener('touchend',function(){this.classList.remove('active');S.tR=0});
})();
// ═══════════════════════════════════════════════════════
// GYROSCOPE SUPPORT
// ═══════════════════════════════════════════════════════
if(window.DeviceOrientationEvent){
window.addEventListener('deviceorientation',function(e){
if(!S.gyroEnabled)return;
if(e.beta!==null&&e.gamma!==null){
// Beta: front-back tilt (-180 to 180), Gamma: left-right tilt (-90 to 90)
S.tP=THREE.MathUtils.clamp((e.beta-90)/90*CFG.pitchMax,-CFG.pitchMax,CFG.pitchMax);
S.tY=THREE.MathUtils.clamp(e.gamma/90*CFG.yawMax,-CFG.yawMax,CFG.yawMax);
}
});
// Auto-enable gyro on mobile
if(/Android|iPhone|iPad/i.test(navigator.userAgent)){
setTimeout(function(){
if(typeof DeviceOrientationEvent.requestPermission==='function'){
DeviceOrientationEvent.requestPermission().then(function(state){
if(state==='granted'){S.gyroEnabled=true;console.log('[Gyro] Enabled')}
});
} else {
S.gyroEnabled=true;console.log('[Gyro] Enabled (no permission needed)');
}
},2000);
}
}
// ═══════════════════════════════════════════════════════
// TOUCH GESTURES - Pinch Zoom & Two-Finger Rotate
// ═══════════════════════════════════════════════════════
(function(){
var lastDist=0,lastAngle=0;
ren.domElement.addEventListener('touchstart',function(e){
if(e.touches.length===2){
var dx=e.touches[1].clientX-e.touches[0].clientX;
var dy=e.touches[1].clientY-e.touches[0].clientY;
lastDist=Math.sqrt(dx*dx+dy*dy);
lastAngle=Math.atan2(dy,dx);
e.preventDefault();
}
});
ren.domElement.addEventListener('touchmove',function(e){
if(e.touches.length===2){
var dx=e.touches[1].clientX-e.touches[0].clientX;
var dy=e.touches[1].clientY-e.touches[0].clientY;
var dist=Math.sqrt(dx*dx+dy*dy);
var angle=Math.atan2(dy,dx);
// Pinch zoom
if(lastDist>0){
var scale=dist/lastDist;
orbitDist=Math.max(0.3,Math.min(15,orbitDist/scale));
}
// Two-finger rotate
if(lastAngle!==0){
var deltaAngle=angle-lastAngle;
orbitTheta-=deltaAngle;
}
lastDist=dist;
lastAngle=angle;
e.preventDefault();
}
});
ren.domElement.addEventListener('touchend',function(){lastDist=0;lastAngle=0});
})();
// ═══════════════════════════════════════════════════════
// SKELETON LOADING CONTROLLER
// ═══════════════════════════════════════════════════════
(function(){
var overlay = $('skeleton-overlay');
// Hide skeleton immediately after DOM ready
setTimeout(function(){
if(overlay && overlay.parentNode){
overlay.classList.add('fade-out');
setTimeout(function(){
if(overlay.parentNode) overlay.remove();
}, 400);
}
}, 500); // Hide after 0.5 second
})();
</script>
</body>
</html>