Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1.0"> | |
| <title>Recap Studio</title> | |
| <link rel="manifest" href="/manifest.json"> | |
| <meta name="theme-color" content="#1a1a2e"> | |
| <link rel="apple-touch-icon" href="https://raw.githubusercontent.com/Shangyi69/psonlineshop/main/logo.png"> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Noto+Sans+Myanmar:wght@400;700&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"> | |
| <style> | |
| :root{ | |
| --bg:#f5f6fa;--bg2:#fff;--bg3:#f0f1f7;--bg4:#e8eaf2; | |
| --border:#e2e4ee;--border2:#cdd0e0; | |
| --text:#1a1d2e;--muted:#6b6f8a;--muted2:#9ba1bc; | |
| --primary:#5b4cf5;--primary2:#4a3de0; | |
| --orange:#ff6b35;--orange2:#e85a25; | |
| --green:#10b981;--red:#ef4444;--blue:#3b82f6;--purple:#8b5cf6; | |
| --gold:#f59e0b; | |
| --F:'Plus Jakarta Sans',sans-serif; | |
| } | |
| *{box-sizing:border-box;margin:0;padding:0} | |
| body{background:var(--bg);color:var(--text);font-family:var(--F);min-height:100vh} | |
| #login-screen{display:flex;align-items:center;justify-content:center;min-height:100vh;padding:20px; | |
| background:linear-gradient(135deg,#1a0533 0%,#0d1b4b 40%,#0a2a1a 100%); | |
| position:relative;overflow:hidden} | |
| #login-screen::before{content:'';position:absolute;inset:0; | |
| background:radial-gradient(ellipse at 20% 50%,rgba(139,92,246,.25) 0%,transparent 60%), | |
| radial-gradient(ellipse at 80% 20%,rgba(59,130,246,.2) 0%,transparent 50%), | |
| radial-gradient(ellipse at 60% 80%,rgba(16,185,129,.15) 0%,transparent 50%); | |
| pointer-events:none} | |
| #app-screen{display:none} | |
| /* ── LOGIN GLASS ── */ | |
| .lw{width:100%;max-width:420px;position:relative;z-index:1} | |
| .lb{text-align:center;margin-bottom:24px} | |
| .lb .ico{width:68px;height:68px;background:rgba(255,255,255,.15);backdrop-filter:blur(12px); | |
| border:1.5px solid rgba(255,255,255,.3);border-radius:20px; | |
| display:inline-flex;align-items:center;justify-content:center;font-size:1.8rem; | |
| margin-bottom:14px;box-shadow:0 8px 32px rgba(0,0,0,.3)} | |
| .lb h1{font-size:1.9rem;font-weight:800;color:#fff;letter-spacing:-.02em} | |
| .lb p{color:rgba(255,255,255,.6);font-size:.84rem;margin-top:5px} | |
| .lc{background:rgba(255,255,255,.08);backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px); | |
| border:1px solid rgba(255,255,255,.15);border-radius:24px;padding:28px; | |
| box-shadow:0 32px 64px rgba(0,0,0,.4),inset 0 1px 0 rgba(255,255,255,.1)} | |
| .tabs{display:flex;gap:3px;background:rgba(0,0,0,.2);border-radius:10px;padding:3px;margin-bottom:20px} | |
| .tab{flex:1;padding:9px;border:none;background:transparent;color:rgba(255,255,255,.5); | |
| font-family:var(--F);font-size:.83rem;font-weight:600;border-radius:8px;cursor:pointer;transition:.2s} | |
| .tab.on{background:rgba(255,255,255,.15);color:#fff;box-shadow:0 2px 8px rgba(0,0,0,.2)} | |
| .fi{margin-bottom:13px} | |
| .fi label{display:block;font-size:.7rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase; | |
| color:rgba(255,255,255,.5);margin-bottom:6px} | |
| .inp{width:100%;padding:11px 14px;background:rgba(255,255,255,.08); | |
| border:1px solid rgba(255,255,255,.15);border-radius:10px; | |
| color:#fff;font-family:var(--F);font-size:.88rem;outline:none;transition:.2s} | |
| .inp::placeholder{color:rgba(255,255,255,.3)} | |
| .inp:focus{border-color:rgba(139,92,246,.7);background:rgba(255,255,255,.12); | |
| box-shadow:0 0 0 3px rgba(139,92,246,.2)} | |
| .bp{width:100%;padding:13px; | |
| background:linear-gradient(135deg,#7c3aed,#5b4cf5); | |
| border:none;border-radius:11px;color:#fff;font-family:var(--F); | |
| font-size:.9rem;font-weight:700;cursor:pointer;transition:.2s;margin-top:4px; | |
| box-shadow:0 4px 20px rgba(124,58,237,.4)} | |
| .bp:hover{transform:translateY(-1px);box-shadow:0 8px 28px rgba(124,58,237,.5)} | |
| .am{font-size:.79rem;padding:9px 12px;border-radius:9px;margin-top:10px;display:none} | |
| .am-ok{background:rgba(16,185,129,.2);border:1px solid rgba(16,185,129,.4);color:#6ee7b7} | |
| .am-err{background:rgba(239,68,68,.15);border:1px solid rgba(239,68,68,.3);color:#fca5a5} | |
| /* Google button */ | |
| .ldiv{display:flex;align-items:center;gap:8px;margin:14px 0} | |
| .ldiv hr{flex:1;border:none;border-top:1px solid rgba(255,255,255,.12)} | |
| .ldiv span{color:rgba(255,255,255,.35);font-size:.73rem;font-weight:500} | |
| .gbtn{display:flex;align-items:center;justify-content:center;gap:10px;width:100%; | |
| padding:12px;background:rgba(255,255,255,.95);border:none;border-radius:11px; | |
| color:#1a1d2e;font-family:var(--F);font-size:.88rem;font-weight:600; | |
| text-decoration:none;cursor:pointer;transition:.2s; | |
| box-shadow:0 2px 12px rgba(0,0,0,.2)} | |
| .gbtn:hover{background:#fff;transform:translateY(-1px);box-shadow:0 6px 20px rgba(0,0,0,.25)} | |
| .lfoot{text-align:center;padding:14px 0 0;font-size:.7rem;color:rgba(255,255,255,.3)} | |
| .lfoot a{color:rgba(255,255,255,.4);text-decoration:none} | |
| .lfoot a:hover{color:rgba(255,255,255,.7)} | |
| @keyframes floatOrb{0%,100%{transform:translate(0,0) scale(1)}33%{transform:translate(20px,-20px) scale(1.05)}66%{transform:translate(-15px,15px) scale(.97)}} | |
| @keyframes logoPop{0%{transform:scale(.5);opacity:0}100%{transform:scale(1);opacity:1}} | |
| @keyframes srtprog{0%{width:20%}100%{width:80%}} | |
| /* ── HEADER ── */ | |
| .hdr{display:flex;align-items:center;justify-content:space-between;padding:0 16px;height:56px;background:#fff;border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;box-shadow:0 1px 8px rgba(0,0,0,.06)} | |
| .brand{display:flex;align-items:center;gap:9px;font-weight:800;font-size:1rem;color:var(--text)} | |
| .brand-i{width:30px;height:30px;background:linear-gradient(135deg,var(--primary),var(--purple));border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:.75rem} | |
| .hr{display:flex;align-items:center;gap:7px} | |
| .cpill{display:flex;align-items:center;gap:5px;padding:5px 12px;background:linear-gradient(135deg,#fff7ed,#fef3c7);border:1.5px solid #fcd34d;border-radius:20px;font-size:.78rem;font-weight:700;color:#92400e;cursor:pointer;transition:.2s} | |
| .cpill:hover{box-shadow:0 2px 8px rgba(245,158,11,.2)} | |
| .hb{padding:7px 13px;border-radius:8px;border:1.5px solid var(--border);background:#fff;color:var(--muted);font-family:var(--F);font-size:.78rem;font-weight:600;cursor:pointer;transition:.2s} | |
| .hb:hover{border-color:var(--primary);color:var(--primary)} | |
| .hb-p{background:linear-gradient(135deg,var(--orange),var(--orange2));border-color:transparent;color:#fff} | |
| .hb-p:hover{box-shadow:0 4px 14px rgba(255,107,53,.3);color:#fff;transform:translateY(-1px)} | |
| .huser{display:flex;align-items:center;gap:6px;padding:4px 10px 4px 5px;background:#f8f7ff;border:1.5px solid var(--border);border-radius:20px;cursor:pointer;transition:.2s;max-width:160px} | |
| .huser:hover{border-color:var(--primary);background:#ede9fe} | |
| .huser-av{width:24px;height:24px;border-radius:8px;background:linear-gradient(135deg,var(--primary),var(--purple));display:flex;align-items:center;justify-content:center;font-weight:700;font-size:.65rem;color:#fff;flex-shrink:0} | |
| .huser-name{font-size:.72rem;font-weight:700;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} | |
| .mbtn{width:33px;height:33px;border:1.5px solid var(--border);background:#fff;border-radius:8px;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;transition:.2s} | |
| .mbtn:hover{border-color:var(--primary)} | |
| .mbtn span{display:block;width:15px;height:1.5px;background:var(--muted);border-radius:2px} | |
| .mbtn:hover span{background:var(--primary)} | |
| /* ── DRAWER ── */ | |
| .dov{display:none;position:fixed;inset:0;z-index:200} | |
| .dov.on{display:block} | |
| .dbg{position:absolute;inset:0;background:rgba(0,0,0,.4);backdrop-filter:blur(3px)} | |
| .drw{position:absolute;top:0;right:0;width:280px;height:100%;background:#fff;box-shadow:-8px 0 32px rgba(0,0,0,.12);display:flex;flex-direction:column;animation:sli .2s ease} | |
| @keyframes sli{from{transform:translateX(100%)}to{transform:translateX(0)}} | |
| .drw-h{padding:18px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between} | |
| .drw-u{display:flex;align-items:center;gap:11px} | |
| .av{width:40px;height:40px;border-radius:12px;background:linear-gradient(135deg,var(--primary),var(--purple));display:flex;align-items:center;justify-content:center;font-weight:700;font-size:.95rem;color:#fff} | |
| .dn{font-weight:700;font-size:.9rem} | |
| .drl{font-size:.68rem;color:var(--muted);margin-top:1px} | |
| .dxbtn{width:28px;height:28px;border:1.5px solid var(--border);background:#fff;color:var(--muted);border-radius:7px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:.75rem;transition:.2s} | |
| .dxbtn:hover{border-color:var(--red);color:var(--red)} | |
| .drw-s{display:grid;grid-template-columns:1fr 1fr;gap:8px;padding:14px 18px;border-bottom:1px solid var(--border)} | |
| .ds{background:var(--bg);border-radius:10px;padding:12px;text-align:center} | |
| .dsv{font-size:1.3rem;font-weight:800;color:var(--primary)} | |
| .dsl{font-size:.63rem;color:var(--muted);margin-top:2px} | |
| .drw-n{flex:1;padding:8px} | |
| .dni{display:flex;align-items:center;gap:9px;padding:10px 13px;border-radius:9px;cursor:pointer;transition:.15s;border:none;background:transparent;width:100%;text-align:left;font-family:var(--F);font-size:.83rem;font-weight:500;color:var(--text)} | |
| .dni:hover{background:var(--bg)} | |
| .dni i{width:17px;font-size:.82rem;color:var(--muted)} | |
| .dni-r{color:var(--red)}.dni-r i{color:var(--red)} | |
| .drw-f{padding:12px 18px;border-top:1px solid var(--border);font-size:.66rem;color:var(--muted2);text-align:center} | |
| /* ── BODY ── */ | |
| .ab{max-width:900px;margin:0 auto;padding:18px 14px 70px} | |
| /* ── CARD ── */ | |
| .card{background:#fff;border:1px solid var(--border);border-radius:16px;padding:16px;margin-bottom:12px;box-shadow:0 1px 6px rgba(0,0,0,.04)} | |
| .ch{display:flex;align-items:center;gap:8px;margin-bottom:13px} | |
| .ci{width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:.74rem} | |
| .ci-p{background:linear-gradient(135deg,#ede9fe,#ddd6fe);color:var(--primary)} | |
| .ci-o{background:linear-gradient(135deg,#fff7ed,#fed7aa);color:var(--orange)} | |
| .ci-g{background:linear-gradient(135deg,#d1fae5,#a7f3d0);color:var(--green)} | |
| .ci-b{background:linear-gradient(135deg,#dbeafe,#bfdbfe);color:var(--blue)} | |
| .ci-pu{background:linear-gradient(135deg,#ede9fe,#c4b5fd);color:var(--purple)} | |
| .ct{font-weight:800;font-size:.88rem;color:var(--text)} | |
| /* ── URL ── */ | |
| .uw{position:relative} | |
| .uinp{width:100%;padding:11px 100px 11px 42px;background:var(--bg);border:1.5px solid var(--border);border-radius:10px;color:var(--text);font-family:var(--F);font-size:.86rem;outline:none;transition:.2s} | |
| .uinp:focus{border-color:var(--primary);background:#fff;box-shadow:0 0 0 3px rgba(91,76,245,.08)} | |
| .uico{position:absolute;left:13px;top:50%;transform:translateY(-50%);color:var(--muted);font-size:.82rem;pointer-events:none} | |
| .ubadge{position:absolute;right:80px;top:50%;transform:translateY(-50%);font-size:.63rem;font-weight:700;padding:3px 8px;border-radius:20px;display:none} | |
| .paste-btn{position:absolute;right:10px;top:50%;transform:translateY(-50%);padding:5px 11px;background:var(--primary);border:none;border-radius:7px;color:#fff;font-family:var(--F);font-size:.72rem;font-weight:700;cursor:pointer;transition:.2s;display:flex;align-items:center;gap:4px} | |
| .paste-btn:hover{background:var(--primary2);box-shadow:0 2px 8px rgba(91,76,245,.3)} | |
| .b-yt{background:#fee2e2;color:#dc2626;border:1px solid #fca5a5} | |
| .b-tt{background:#f3f4f6;color:#374151;border:1px solid #d1d5db} | |
| .b-ig{background:#fdf2f8;color:#be185d;border:1px solid #fbcfe8} | |
| .b-fb{background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe} | |
| .orsep{text-align:center;color:var(--muted2);font-size:.73rem;margin:10px 0;position:relative} | |
| .orsep::before,.orsep::after{content:'';position:absolute;top:50%;width:44%;height:1px;background:var(--border)} | |
| .orsep::before{left:0}.orsep::after{right:0} | |
| .upz{border:2px dashed var(--border2);border-radius:10px;padding:14px;text-align:center;cursor:pointer;transition:.2s;position:relative} | |
| .upz:hover{border-color:var(--primary);background:#faf9ff} | |
| .upz input{position:absolute;inset:0;opacity:0;cursor:pointer} | |
| .upz-t{font-size:.78rem;color:var(--muted)} | |
| .upz-n{font-size:.76rem;color:var(--green);margin-top:3px;display:none;font-weight:600} | |
| .prev-loading{display:none;margin-top:8px;padding:9px 13px;background:linear-gradient(135deg,#ede9fe,#e0e7ff);border-radius:9px;font-size:.78rem;color:var(--primary);font-weight:600;align-items:center;gap:7px} | |
| .prev-loading.on{display:flex} | |
| /* ── SETTINGS ── */ | |
| .sgrid{display:grid;grid-template-columns:1fr 1fr;gap:9px} | |
| @media(max-width:460px){.sgrid{grid-template-columns:1fr}} | |
| .si{background:var(--bg);border:1.5px solid var(--border);border-radius:10px;padding:11px 13px;transition:.2s} | |
| .si:focus-within{border-color:var(--primary);background:#fff} | |
| .sil{font-size:.65rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);margin-bottom:6px} | |
| .ssel{width:100%;background:transparent;border:none;color:var(--text);font-family:var(--F);font-size:.84rem;font-weight:500;outline:none;cursor:pointer} | |
| .ssel option{background:#fff;color:var(--text)} | |
| .vrow{display:flex;align-items:center;gap:7px} | |
| .vrow .ssel{flex:1;background:var(--bg);border:1.5px solid var(--border);border-radius:9px;padding:9px 11px} | |
| .pvbtn{width:34px;height:34px;flex-shrink:0;background:#fff;border:1.5px solid var(--border);border-radius:9px;color:var(--muted);cursor:pointer;transition:.2s;display:flex;align-items:center;justify-content:center;font-size:.78rem} | |
| .pvbtn:hover{border-color:var(--green);color:var(--green);box-shadow:0 2px 8px rgba(16,185,129,.15)} | |
| .spd-row{display:flex;align-items:center;gap:9px;margin-top:9px} | |
| .spd-row label{font-size:.7rem;font-weight:600;color:var(--muted);white-space:nowrap;width:52px} | |
| .spd-sl{flex:1;-webkit-appearance:none;height:4px;border-radius:2px;background:var(--bg4);outline:none;cursor:pointer} | |
| .spd-sl::-webkit-slider-thumb{-webkit-appearance:none;width:14px;height:14px;border-radius:50%;background:var(--primary);cursor:pointer;box-shadow:0 0 0 3px rgba(91,76,245,.15)} | |
| .spd-v{font-size:.73rem;color:var(--primary);font-weight:700;width:36px;text-align:right} | |
| /* ── VIDEO OPTIONS ── */ | |
| .egrid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px} | |
| @media(max-width:420px){.egrid{grid-template-columns:1fr 1fr}} | |
| .ei{background:var(--bg);border:1.5px solid var(--border);border-radius:10px;padding:10px 12px;transition:.2s} | |
| .ei:focus-within{border-color:var(--primary)} | |
| .eil{font-size:.63rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);margin-bottom:5px} | |
| .esel{width:100%;background:transparent;border:none;color:var(--text);font-family:var(--F);font-size:.81rem;font-weight:500;outline:none;cursor:pointer} | |
| .esel option{background:#fff} | |
| .icon-toggle{display:flex;align-items:center;justify-content:center;gap:5px;padding:8px 10px;background:var(--bg);border:1.5px solid var(--border);border-radius:8px;cursor:pointer;transition:.2s;font-size:.79rem;font-weight:600;color:var(--muted);font-family:var(--F);width:100%} | |
| .icon-toggle:hover{border-color:var(--primary);color:var(--primary)} | |
| .icon-toggle.on{background:linear-gradient(135deg,#ede9fe,#e0e7ff);border-color:var(--primary);color:var(--primary)} | |
| .winp{width:100%;padding:9px 11px;background:var(--bg);border:1.5px solid var(--border);border-radius:9px;color:var(--text);font-family:var(--F);font-size:.81rem;font-weight:500;outline:none;transition:.2s} | |
| .winp:focus{border-color:var(--primary);background:#fff} | |
| .winp::placeholder{color:var(--muted2)} | |
| /* ── WATERMARK TOGGLE ── */ | |
| .wm-row{display:flex;align-items:center;justify-content:space-between;margin-top:12px;border-top:1px solid var(--border);padding-top:12px} | |
| .wm-lbl{font-size:.67rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);display:flex;align-items:center;gap:6px} | |
| .wm-wrap{margin-top:8px;display:none} | |
| .wm-toggle{padding:5px 13px;border-radius:20px;border:1.5px solid var(--border);background:var(--bg);color:var(--muted);font-family:var(--F);font-size:.75rem;font-weight:700;cursor:pointer;transition:.2s} | |
| .wm-toggle.on{background:#fffbeb;border-color:#fcd34d;color:#92400e} | |
| /* ── OVERLAY (REMOVED: watermark, logo, blur boxes) ── */ | |
| /* Only subtitle settings remain */ | |
| .canvas-wrap{position:relative;background:#111;border-radius:10px;overflow:hidden;margin-top:10px;min-height:200px} | |
| .canvas-bg-blur{position:absolute;inset:0;background-size:cover;background-position:center;filter:blur(18px) brightness(.6);transform:scale(1.1);pointer-events:none;display:none;z-index:0} | |
| .canvas-video-wrap{position:relative;z-index:1} | |
| .canvas-wrap video{width:100%;display:block;height:auto} | |
| .canvas-ph{display:flex;align-items:center;justify-content:center;height:200px;color:rgba(255,255,255,.3);font-size:.78rem;flex-direction:column;gap:6px;pointer-events:none} | |
| .canvas-ph i{font-size:1.8rem;opacity:.4} | |
| /* Overlay buttons hidden */ | |
| .ov-controls{display:grid;grid-template-columns:repeat(4,1fr);gap:7px;margin-top:10px} | |
| .ov-btn{padding:9px 5px;background:var(--bg);border:1.5px solid var(--border);border-radius:9px;color:var(--muted);font-family:var(--F);font-size:.71rem;font-weight:600;cursor:pointer;transition:.2s;text-align:center;display:flex;flex-direction:column;align-items:center;gap:4px} | |
| .ov-btn i{font-size:.88rem} | |
| .ov-btn:hover{border-color:var(--primary);color:var(--primary);background:#faf9ff} | |
| .ov-btn.on-wm {background:#fffbeb;border-color:#fcd34d;color:#92400e} | |
| .ov-btn.on-logo{background:#ecfdf5;border-color:#6ee7b7;color:#065f46} | |
| .ov-btn.on-sub {background:#eff6ff;border-color:#93c5fd;color:#1d4ed8} | |
| .ov-btn.on-blur{background:#fef2f2;border-color:#fca5a5;color:#991b1b} | |
| /* Hide watermark, logo, blur buttons */ | |
| /* ov-wm, ov-logo — visible as canvas overlay buttons */ | |
| .sub-settings{background:var(--bg);border:1.5px solid var(--border);border-radius:10px;padding:11px;margin-top:9px;display:none} | |
| .sub-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px} | |
| .sub-inp{width:100%;padding:7px 9px;background:#fff;border:1.5px solid var(--border);border-radius:7px;color:var(--text);font-family:var(--F);font-size:.79rem;font-weight:500;outline:none;transition:.2s} | |
| .sub-inp:focus{border-color:var(--blue)} | |
| .slbl{font-size:.64rem;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--muted);margin-bottom:4px} | |
| .btn-auto{width:100%;padding:15px;background:linear-gradient(135deg,var(--primary),var(--purple));border:none;border-radius:12px;color:#fff;font-family:var(--F);font-size:.94rem;font-weight:700;cursor:pointer;transition:.2s;display:flex;align-items:center;justify-content:center;gap:8px;box-shadow:0 4px 18px rgba(91,76,245,.3)} | |
| .btn-auto:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 8px 28px rgba(91,76,245,.4)} | |
| .btn-auto:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none} | |
| /* ── PROGRESS ── */ | |
| .pc{background:#fff;border:1px solid var(--border);border-radius:16px;padding:18px;margin-top:12px;display:none;box-shadow:var(--shadow)} | |
| .ph{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px} | |
| .ptit{font-weight:800;font-size:.86rem;color:var(--text)} | |
| .ptmr{font-size:.73rem;color:var(--primary);font-variant-numeric:tabular-nums;font-weight:700;background:linear-gradient(135deg,#ede9fe,#e0e7ff);padding:3px 9px;border-radius:20px} | |
| .pb-bg{height:6px;background:var(--bg4);border-radius:3px;overflow:hidden;margin-bottom:9px} | |
| .pb{height:100%;background:linear-gradient(90deg,var(--primary),var(--orange));border-radius:3px;width:0%;transition:width .4s ease} | |
| .pmsg{font-size:.8rem;color:var(--muted);font-weight:500} | |
| .psteps{display:flex;gap:5px;margin-top:11px;flex-wrap:wrap} | |
| .ps{font-size:.66rem;font-weight:600;padding:4px 10px;border-radius:20px;background:var(--bg);color:var(--muted2);border:1px solid var(--border);transition:.3s} | |
| .ps.active{background:linear-gradient(135deg,#ede9fe,#e0e7ff);color:var(--primary);border-color:#c4b5fd} | |
| .ps.done{background:linear-gradient(135deg,#d1fae5,#a7f3d0);color:#065f46;border-color:#6ee7b7} | |
| /* ── RESULT ── */ | |
| .rc{background:#fff;border:1px solid var(--border);border-radius:16px;overflow:hidden;margin-top:12px;display:none;box-shadow:var(--shadow)} | |
| .rvw{background:#000} | |
| .rvw video{width:100%;display:block;max-height:380px;object-fit:contain} | |
| .rb{padding:15px} | |
| .rtm{font-size:.7rem;color:var(--muted);margin-bottom:8px;display:flex;align-items:center;gap:4px;font-weight:500} | |
| .rtit{font-weight:800;font-size:.94rem;margin-bottom:5px;line-height:1.3;color:var(--text)} | |
| .rtag{font-size:.73rem;color:var(--primary);line-height:1.6;margin-bottom:12px;font-weight:500} | |
| .ract{display:grid;grid-template-columns:1fr 1fr;gap:8px} | |
| .rb-btn{padding:11px;border-radius:9px;border:none;font-family:var(--F);font-size:.81rem;font-weight:700;cursor:pointer;transition:.2s;display:flex;align-items:center;justify-content:center;gap:6px} | |
| .rb-g{background:linear-gradient(135deg,#d1fae5,#a7f3d0);color:#065f46} | |
| .rb-g:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(16,185,129,.2)} | |
| .rb-b{background:linear-gradient(135deg,#dbeafe,#bfdbfe);color:#1d4ed8} | |
| .rb-b:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(59,130,246,.2)} | |
| /* ── ADMIN ── */ | |
| .admp{background:#fff;border:2px solid #fef3c7;border-radius:16px;padding:16px;margin-top:12px;display:none;box-shadow:0 2px 12px rgba(245,158,11,.1)} | |
| .admbadge{font-size:.63rem;font-weight:700;padding:3px 8px;background:linear-gradient(135deg,#fef3c7,#fde68a);color:#92400e;border-radius:20px;border:1px solid #fcd34d} | |
| .admg{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-bottom:11px} | |
| @media(max-width:460px){.admg{grid-template-columns:1fr}} | |
| .ai-row{display:flex;gap:5px} | |
| .ai{flex:1;padding:8px 10px;background:var(--bg);border:1.5px solid var(--border);border-radius:8px;color:var(--text);font-family:var(--F);font-size:.8rem;font-weight:500;outline:none} | |
| .ai:focus{border-color:var(--primary)} | |
| .ai::placeholder{color:var(--muted2)} | |
| .abtn{padding:8px 12px;border-radius:8px;border:none;font-family:var(--F);font-size:.76rem;font-weight:700;cursor:pointer;transition:.2s;white-space:nowrap} | |
| .ab-g{background:linear-gradient(135deg,#ede9fe,#ddd6fe);color:var(--primary)} | |
| .ab-g:hover{background:linear-gradient(135deg,#ddd6fe,#c4b5fd)} | |
| .ab-r{background:linear-gradient(135deg,#fee2e2,#fecaca);color:#dc2626} | |
| .ab-r:hover{background:linear-gradient(135deg,#fecaca,#fca5a5)} | |
| .admmsg{font-size:.76rem;margin-top:7px;min-height:18px;color:var(--green);font-weight:600} | |
| .adm-act-btn{padding:7px 13px;background:var(--bg);border:1.5px solid var(--border);border-radius:8px;color:var(--muted);font-family:var(--F);font-size:.75rem;font-weight:600;cursor:pointer;transition:.2s} | |
| .adm-act-btn:hover{border-color:var(--primary);color:var(--primary)} | |
| /* pending payment card */ | |
| .adm-pay-card{background:var(--bg);border:1.5px solid var(--border);border-radius:12px;padding:12px 14px;margin-bottom:8px} | |
| .adm-pay-card-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px} | |
| .adm-pay-card-user{font-weight:700;font-size:.85rem;color:var(--text)} | |
| .adm-pay-card-coins{font-size:.78rem;font-weight:700;color:var(--primary)} | |
| .adm-pay-card-info{font-size:.72rem;color:var(--muted);margin-bottom:10px;line-height:1.6} | |
| .adm-pay-card-btns{display:flex;gap:7px} | |
| .adm-pay-approve{flex:1;padding:8px;background:linear-gradient(135deg,#059669,#10b981);border:none;border-radius:8px;color:#fff;font-family:var(--F);font-size:.78rem;font-weight:700;cursor:pointer;transition:.2s} | |
| .adm-pay-approve:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(16,185,129,.35)} | |
| .adm-pay-reject{padding:8px 14px;background:rgba(239,68,68,.1);border:1.5px solid rgba(239,68,68,.25);border-radius:8px;color:#ef4444;font-family:var(--F);font-size:.78rem;font-weight:700;cursor:pointer;transition:.2s} | |
| .adm-pay-reject:hover{background:rgba(239,68,68,.18)} | |
| .adm-pay-slip-btn{padding:8px 12px;background:rgba(91,76,245,.1);border:1.5px solid rgba(91,76,245,.25);border-radius:8px;color:var(--primary);font-family:var(--F);font-size:.78rem;font-weight:700;cursor:pointer;transition:.2s} | |
| .adm-pay-slip-btn:hover{background:rgba(91,76,245,.18)} | |
| .adm-slip-img{width:100%;max-height:180px;object-fit:contain;border-radius:10px;margin-top:8px;display:none;border:1px solid var(--border)} | |
| .ut{width:100%;border-collapse:collapse;font-size:.76rem;margin-top:10px} | |
| .ut th{padding:7px 9px;text-align:left;font-size:.63rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);border-bottom:2px solid var(--border)} | |
| .ut td{padding:7px 9px;border-bottom:1px solid var(--bg4);color:var(--text);font-weight:500} | |
| .ut tr:last-child td{border-bottom:none} | |
| .delbtn{background:transparent;border:none;color:var(--muted2);cursor:pointer;padding:2px 5px;border-radius:5px;transition:.2s;font-size:.73rem} | |
| .delbtn:hover{color:var(--red);background:#fee2e2} | |
| #toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%) translateY(14px);background:var(--text);border-radius:10px;padding:9px 16px;font-size:.8rem;color:#fff;z-index:9999;opacity:0;transition:.3s;pointer-events:none;white-space:nowrap;max-width:88vw;box-shadow:0 8px 28px rgba(0,0,0,.25);font-weight:600} | |
| #toast.show{opacity:1;transform:translateX(-50%) translateY(0)} | |
| @keyframes spin{to{transform:rotate(360deg)}} | |
| .sp{display:inline-block;animation:spin .7s linear infinite} | |
| .modal-ov{display:none;position:fixed;inset:0;z-index:300;align-items:flex-end;justify-content:center} | |
| .modal-ov.on{display:flex} | |
| .modal-bg{position:absolute;inset:0;background:rgba(0,0,0,.45);backdrop-filter:blur(3px)} | |
| .modal-sheet{position:relative;width:100%;max-width:480px;background:#fff;border-radius:20px 20px 0 0;padding:24px 20px 36px;animation:slideUp .25s ease;box-shadow:0 -8px 40px rgba(0,0,0,.15)} | |
| @keyframes slideUp{from{transform:translateY(100%)}to{transform:translateY(0)}} | |
| .modal-sheet h3{font-size:1.1rem;font-weight:800;margin-bottom:4px;color:var(--text)} | |
| .modal-sheet p{font-size:.8rem;color:var(--muted);margin-bottom:18px} | |
| .pkg-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:16px} | |
| .pkg-card{background:var(--bg);border:1.5px solid var(--border);border-radius:12px;padding:14px 12px;text-align:center;cursor:pointer;transition:.2s;position:relative} | |
| .pkg-card:hover{border-color:var(--primary);background:#faf9ff;transform:translateY(-2px);box-shadow:0 4px 16px rgba(91,76,245,.12)} | |
| .pkg-card.popular{border-color:var(--primary);background:linear-gradient(135deg,#faf9ff,#ede9fe)} | |
| .pkg-pop-badge{position:absolute;top:-9px;left:50%;transform:translateX(-50%);background:var(--primary);color:#fff;font-size:.6rem;font-weight:700;padding:2px 8px;border-radius:10px;white-space:nowrap} | |
| .pkg-emoji{font-size:1.5rem;margin-bottom:5px} | |
| .pkg-name{font-size:.75rem;font-weight:700;color:var(--text);margin-bottom:3px} | |
| .pkg-coins{font-size:1.2rem;font-weight:800;color:var(--primary)} | |
| .pkg-coins span{font-size:.7rem;font-weight:500;color:var(--muted)} | |
| .pkg-contact{background:var(--bg);border:1.5px solid var(--border);border-radius:10px;padding:12px;text-align:center;font-size:.8rem;color:var(--muted)} | |
| .pkg-contact b{color:var(--text)} | |
| .pkg-price{font-size:.72rem;font-weight:700;color:var(--green);margin-top:3px} | |
| .pkg-contact a{color:var(--primary);font-weight:600;text-decoration:none} | |
| .modal-close{position:absolute;top:14px;right:16px;width:28px;height:28px;border:1.5px solid var(--border);background:#fff;border-radius:7px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:.75rem;color:var(--muted);transition:.2s} | |
| .modal-close:hover{border-color:var(--red);color:var(--red)} | |
| /* ══ PAYMENT MODAL ══ */ | |
| .pov{display:none;position:fixed;inset:0;z-index:400; | |
| background:rgba(8,8,20,.8);backdrop-filter:blur(14px); | |
| align-items:flex-end;justify-content:center} | |
| .pov.on{display:flex} | |
| @keyframes sheetUp{from{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}} | |
| @keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}} | |
| @keyframes shimmer{0%{background-position:200% center}100%{background-position:-200% center}} | |
| @keyframes pulse-glow{0%,100%{box-shadow:0 0 0 0 rgba(139,92,246,.3)}50%{box-shadow:0 0 0 8px rgba(139,92,246,0)}} | |
| .psheet{ | |
| position:relative;width:100%;max-width:520px; | |
| background:linear-gradient(165deg,#0f0f1e 0%,#13112b 60%,#0e1628 100%); | |
| border-radius:28px 28px 0 0; | |
| padding:0 0 44px;max-height:93vh;overflow-y:auto; | |
| box-shadow:0 -4px 60px rgba(91,76,245,.25),0 -1px 0 rgba(255,255,255,.06); | |
| animation:sheetUp .32s cubic-bezier(.34,1.1,.64,1)} | |
| .psheet::-webkit-scrollbar{width:3px} | |
| .psheet::-webkit-scrollbar-track{background:transparent} | |
| .psheet::-webkit-scrollbar-thumb{background:rgba(139,92,246,.3);border-radius:10px} | |
| /* header */ | |
| .psheet-hdr{ | |
| padding:20px 22px 0; | |
| display:flex;align-items:center;justify-content:space-between; | |
| position:sticky;top:0;z-index:2; | |
| background:linear-gradient(165deg,#0f0f1e,#13112b); | |
| padding-bottom:16px; | |
| border-bottom:1px solid rgba(255,255,255,.06)} | |
| .psheet-hdr h2{font-size:1.05rem;font-weight:800;color:#fff;display:flex;align-items:center;gap:8px} | |
| .psheet-hdr h2 span{ | |
| display:inline-flex;align-items:center;justify-content:center; | |
| width:32px;height:32px;background:linear-gradient(135deg,#7c3aed,#5b4cf5); | |
| border-radius:10px;font-size:.9rem} | |
| .pclose{ | |
| width:32px;height:32px;border:1px solid rgba(255,255,255,.12); | |
| background:rgba(255,255,255,.06);border-radius:9px;cursor:pointer; | |
| display:flex;align-items:center;justify-content:center; | |
| font-size:.8rem;color:rgba(255,255,255,.5);transition:.2s} | |
| .pclose:hover{background:rgba(239,68,68,.15);border-color:rgba(239,68,68,.4);color:#f87171} | |
| /* tab2 */ | |
| .tab2{display:flex;gap:6px;margin:18px 22px 0} | |
| .tab2 button{ | |
| flex:1;padding:10px;border:1px solid rgba(255,255,255,.08); | |
| background:rgba(255,255,255,.04); | |
| color:rgba(255,255,255,.4);font-family:var(--F);font-size:.8rem;font-weight:600; | |
| border-radius:10px;cursor:pointer;transition:.2s} | |
| .tab2 button.on{ | |
| border-color:rgba(139,92,246,.5); | |
| background:linear-gradient(135deg,rgba(124,58,237,.2),rgba(91,76,245,.15)); | |
| color:#c4b5fd} | |
| /* ── MODE TOGGLE ── */ | |
| .mode-bar{display:flex;gap:0;background:#f0f1f7;border-bottom:1px solid var(--border);padding:0} | |
| .mode-btn{flex:1;padding:11px 8px;border:none;background:transparent;font-family:var(--F);font-size:.82rem;font-weight:700;color:var(--muted);cursor:pointer;transition:.2s;display:flex;align-items:center;justify-content:center;gap:6px;border-bottom:2px solid transparent} | |
| .mode-btn.on{background:#fff;color:var(--primary);border-bottom-color:var(--primary)} | |
| .mode-btn:hover:not(.on){background:rgba(91,76,245,.05);color:var(--primary)} | |
| /* ── SRT SECTION ── */ | |
| .srt-drop{border:2px dashed var(--border2);border-radius:10px;padding:20px;text-align:center;cursor:pointer;transition:.2s;position:relative;background:var(--bg)} | |
| .srt-drop:hover{border-color:var(--primary);background:#faf9ff} | |
| .srt-drop input{position:absolute;inset:0;opacity:0;cursor:pointer;width:100%;height:100%} | |
| .srt-drop-t{font-size:.82rem;color:var(--muted);margin-top:6px} | |
| .srt-step{display:flex;align-items:center;gap:10px;padding:12px 14px;background:var(--bg);border:1.5px solid var(--border);border-radius:10px;margin-bottom:8px;transition:.2s} | |
| .srt-step.done{border-color:var(--green);background:#f0fdf4} | |
| .srt-step.active{border-color:var(--primary);background:#faf9ff} | |
| .srt-step-n{width:26px;height:26px;border-radius:50%;background:var(--border);color:var(--muted);font-size:.72rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0} | |
| .srt-step.done .srt-step-n{background:var(--green);color:#fff} | |
| .srt-step.active .srt-step-n{background:var(--primary);color:#fff} | |
| .srt-step-t{font-size:.82rem;font-weight:600;color:var(--text)} | |
| .srt-step-s{font-size:.72rem;color:var(--muted);margin-top:1px} | |
| .srt-preview{background:#1a1d2e;border-radius:10px;padding:12px;max-height:200px;overflow-y:auto;font-size:.73rem;color:#c4b5fd;font-family:monospace;line-height:1.6;white-space:pre-wrap;word-break:break-word} | |
| .srt-dl-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:13px;background:linear-gradient(135deg,#10b981,#059669);border:none;border-radius:11px;color:#fff;font-family:var(--F);font-size:.9rem;font-weight:700;cursor:pointer;transition:.2s;margin-top:8px} | |
| .srt-dl-btn:hover{transform:translateY(-1px);box-shadow:0 6px 20px rgba(16,185,129,.35)} | |
| .srt-burn-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:13px;background:linear-gradient(135deg,var(--primary),var(--purple));border:none;border-radius:11px;color:#fff;font-family:var(--F);font-size:.9rem;font-weight:700;cursor:pointer;transition:.2s;margin-top:8px} | |
| .srt-burn-btn:hover{transform:translateY(-1px);box-shadow:0 6px 20px rgba(91,76,245,.35)} | |
| .srt-burn-btn:disabled{opacity:.4;cursor:not-allowed;transform:none} | |
| .ppkg{ | |
| border:1.5px solid rgba(255,255,255,.07); | |
| border-radius:16px;padding:14px 10px 12px;cursor:pointer; | |
| transition:all .22s cubic-bezier(.34,1.1,.64,1); | |
| background:rgba(255,255,255,.04); | |
| text-align:center;position:relative;overflow:hidden} | |
| .ppkg::before{ | |
| content:'';position:absolute;inset:0; | |
| background:radial-gradient(ellipse at 50% 0%,rgba(139,92,246,.12),transparent 70%); | |
| opacity:0;transition:.2s} | |
| .ppkg:hover{border-color:rgba(139,92,246,.4);transform:translateY(-3px); | |
| box-shadow:0 8px 24px rgba(91,76,245,.2);background:rgba(139,92,246,.08)} | |
| .ppkg:hover::before{opacity:1} | |
| .ppkg.sel{ | |
| border-color:rgba(167,139,250,.6); | |
| background:linear-gradient(145deg,rgba(124,58,237,.18),rgba(91,76,245,.12)); | |
| box-shadow:0 0 0 3px rgba(139,92,246,.15),0 8px 28px rgba(91,76,245,.25)} | |
| .ppkg.sel::before{opacity:1} | |
| .ppkg.pop{border-color:rgba(245,158,11,.35);background:rgba(245,158,11,.05)} | |
| .ppkg.pop::after{ | |
| content:'🔥 Popular';position:absolute;top:-1px;left:50%;transform:translateX(-50%); | |
| background:linear-gradient(90deg,#f59e0b,#fbbf24);color:#1a0a00; | |
| font-size:.58rem;font-weight:800;padding:3px 10px;border-radius:0 0 10px 10px; | |
| white-space:nowrap;letter-spacing:.03em} | |
| .ppkg.pop:hover{border-color:rgba(245,158,11,.6);box-shadow:0 8px 24px rgba(245,158,11,.2)} | |
| .ppkg.pop.sel{border-color:#f59e0b;box-shadow:0 0 0 3px rgba(245,158,11,.15),0 8px 28px rgba(245,158,11,.2)} | |
| .ppkg-icon{font-size:1.6rem;margin-bottom:6px;display:block; | |
| filter:drop-shadow(0 2px 8px rgba(0,0,0,.3))} | |
| .ppkg-c{ | |
| font-size:1.8rem;font-weight:800; | |
| background:linear-gradient(135deg,#c4b5fd,#a78bfa); | |
| -webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text; | |
| line-height:1;margin-bottom:2px} | |
| .ppkg.pop .ppkg-c{ | |
| background:linear-gradient(135deg,#fde68a,#fbbf24); | |
| -webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text} | |
| .ppkg-u{font-size:.65rem;color:rgba(255,255,255,.4);text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px} | |
| .ppkg-p{ | |
| font-size:.78rem;font-weight:700; | |
| background:rgba(255,255,255,.08);border-radius:6px; | |
| padding:4px 8px;color:rgba(255,255,255,.7);display:inline-block} | |
| .ppkg.pop .ppkg-p{background:rgba(245,158,11,.15);color:#fbbf24} | |
| /* selected badge */ | |
| .ppkg.sel .ppkg-c{background:linear-gradient(135deg,#e9d5ff,#c4b5fd); | |
| -webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text} | |
| /* pinfo */ | |
| .pinfo{ | |
| margin:14px 22px 0;padding:10px 14px; | |
| background:rgba(139,92,246,.1);border:1px solid rgba(139,92,246,.2); | |
| border-radius:10px;font-size:.78rem;color:#c4b5fd; | |
| text-align:center;min-height:36px;display:flex;align-items:center;justify-content:center; | |
| font-weight:600} | |
| /* payment info box */ | |
| .pkbz{ | |
| margin:14px 22px 0; | |
| background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08); | |
| border-radius:14px;padding:14px;overflow:hidden;position:relative} | |
| .pkbz::before{ | |
| content:'';position:absolute;top:0;left:0;right:0;height:1px; | |
| background:linear-gradient(90deg,transparent,rgba(139,92,246,.4),transparent)} | |
| .pkbz-t{ | |
| font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em; | |
| color:rgba(255,255,255,.35);margin-bottom:10px;display:flex;align-items:center;gap:6px} | |
| .pkbz-t::after{content:'';flex:1;height:1px;background:rgba(255,255,255,.06)} | |
| .pkbz-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px} | |
| .pkbz-row:last-child{margin-bottom:0} | |
| .pkbz-lbl{font-size:.7rem;color:rgba(255,255,255,.35);font-weight:500} | |
| .pkbz-val{font-size:.88rem;font-weight:700;color:rgba(255,255,255,.9)} | |
| .pcopy{ | |
| background:rgba(139,92,246,.12);border:1px solid rgba(139,92,246,.25); | |
| color:#a78bfa;font-size:.65rem;padding:4px 10px;border-radius:6px; | |
| cursor:pointer;font-family:var(--F);font-weight:700;transition:.2s} | |
| .pcopy:hover{background:rgba(139,92,246,.25);border-color:rgba(167,139,250,.5);color:#c4b5fd} | |
| /* slip upload */ | |
| .pslip-label{ | |
| font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em; | |
| color:rgba(255,255,255,.4);margin:14px 22px 8px;display:block} | |
| .pslip-drop{ | |
| margin:0 22px;border:1.5px dashed rgba(139,92,246,.3);border-radius:14px; | |
| padding:24px 20px;text-align:center;cursor:pointer;transition:.25s; | |
| position:relative;overflow:hidden; | |
| background:rgba(139,92,246,.04)} | |
| .pslip-drop:hover{border-color:rgba(167,139,250,.6);background:rgba(139,92,246,.09)} | |
| .pslip-drop input{position:absolute;inset:0;opacity:0;cursor:pointer;width:100%;height:100%} | |
| .pslip-drop .pi{font-size:2rem;margin-bottom:8px;display:block; | |
| filter:drop-shadow(0 2px 8px rgba(139,92,246,.4))} | |
| .pslip-drop .pt{font-size:.75rem;color:rgba(255,255,255,.35);line-height:1.5} | |
| .pslip-preview{width:100%;max-height:160px;object-fit:contain;border-radius:10px;display:none;margin-top:10px; | |
| box-shadow:0 4px 20px rgba(0,0,0,.4)} | |
| /* submit button */ | |
| .psubmit{ | |
| display:none;width:calc(100% - 44px);margin:16px 22px 0; | |
| padding:15px;border:none;border-radius:14px; | |
| background:linear-gradient(135deg,#7c3aed,#5b4cf5,#4f46e5); | |
| background-size:200% auto; | |
| color:#fff;font-family:var(--F);font-size:.92rem;font-weight:800; | |
| cursor:pointer;transition:.3s;letter-spacing:.01em; | |
| box-shadow:0 4px 24px rgba(91,76,245,.35)} | |
| .psubmit.on{display:block} | |
| .psubmit:hover:not(:disabled){ | |
| background-position:right center; | |
| transform:translateY(-2px);box-shadow:0 8px 32px rgba(91,76,245,.5)} | |
| .psubmit:disabled{opacity:.4;cursor:not-allowed;transform:none} | |
| /* history tab */ | |
| .pmeth-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:9px;padding:16px 22px 0} | |
| .pmeth{border:1.5px solid rgba(255,255,255,.09);border-radius:14px;padding:14px 8px 11px; | |
| cursor:pointer;text-align:center;transition:.22s;background:rgba(255,255,255,.04)} | |
| .pmeth:hover{border-color:rgba(139,92,246,.45);background:rgba(139,92,246,.09);transform:translateY(-2px)} | |
| .pmeth.sel{border-color:#a78bfa;background:rgba(124,58,237,.18);box-shadow:0 0 0 3px rgba(139,92,246,.15)} | |
| .pmeth-icon{font-size:1.5rem;display:block;margin-bottom:6px;width:44px;height:44px;object-fit:contain;margin-left:auto;margin-right:auto;border-radius:10px} | |
| .pmeth-name{font-size:.65rem;font-weight:700;color:rgba(255,255,255,.7);line-height:1.3} | |
| .pmeth-sub{font-size:.6rem;color:rgba(255,255,255,.35);margin-top:2px} | |
| .pinfo-box{margin:12px 22px 0;padding:12px 14px;background:rgba(255,255,255,.04); | |
| border:1px solid rgba(255,255,255,.09);border-radius:13px} | |
| .pinfo-row{display:flex;align-items:center;justify-content:space-between;padding:5px 0; | |
| border-bottom:1px solid rgba(255,255,255,.05)} | |
| .pinfo-row:last-child{border-bottom:none} | |
| .pinfo-lbl{font-size:.69rem;color:rgba(255,255,255,.35);font-weight:500} | |
| .pinfo-val{font-size:.86rem;font-weight:700;color:rgba(255,255,255,.88)} | |
| .pcopy{background:rgba(139,92,246,.12);border:1px solid rgba(139,92,246,.25); | |
| color:#a78bfa;font-size:.62rem;padding:3px 9px;border-radius:6px; | |
| cursor:pointer;font-family:var(--F);font-weight:700;transition:.2s} | |
| .pcopy:hover{background:rgba(139,92,246,.28);color:#c4b5fd} | |
| .pstep-lbl{font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.1em; | |
| color:rgba(255,255,255,.3);padding:16px 22px 0;display:flex;align-items:center;gap:8px} | |
| .pstep-lbl::after{content:'';flex:1;height:1px;background:rgba(255,255,255,.06)} | |
| .pstep-num{width:18px;height:18px;border-radius:50%;background:rgba(139,92,246,.35); | |
| color:#c4b5fd;font-size:.6rem;font-weight:800;display:inline-flex;align-items:center;justify-content:center} | |
| .buy-now-btn{display:block;width:calc(100% - 44px);margin:14px 22px 0; | |
| padding:14px;border:none;border-radius:14px; | |
| background:linear-gradient(135deg,#7c3aed,#5b4cf5); | |
| color:#fff;font-family:var(--F);font-size:.9rem;font-weight:800; | |
| cursor:pointer;transition:.3s;box-shadow:0 4px 20px rgba(91,76,245,.35)} | |
| .buy-now-btn:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 8px 28px rgba(91,76,245,.5)} | |
| .buy-now-btn:disabled{opacity:.35;cursor:not-allowed} | |
| .pay-step{display:none}.pay-step.on{display:block} | |
| .pback-btn{background:none;border:1px solid rgba(255,255,255,.1);color:rgba(255,255,255,.5); | |
| font-size:.75rem;padding:6px 14px;border-radius:8px;cursor:pointer;font-family:var(--F); | |
| margin:12px 22px 0;display:inline-flex;align-items:center;gap:6px;transition:.2s} | |
| .pback-btn:hover{border-color:rgba(255,255,255,.25);color:rgba(255,255,255,.8)} | |
| .ph-card{ | |
| background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07); | |
| border-radius:14px;padding:14px 16px;margin-bottom:9px; | |
| animation:fadeIn .2s ease both} | |
| .ph-top{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px} | |
| .ph-coins{font-size:1.05rem;font-weight:800; | |
| background:linear-gradient(135deg,#c4b5fd,#a78bfa); | |
| -webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text} | |
| .ph-badge{font-size:.65rem;padding:4px 10px;border-radius:20px;font-weight:700;letter-spacing:.03em} | |
| .ph-badge.pending{background:rgba(251,191,36,.12);color:#fbbf24;border:1px solid rgba(251,191,36,.25)} | |
| .ph-badge.approved{background:rgba(52,211,153,.12);color:#34d399;border:1px solid rgba(52,211,153,.25)} | |
| .ph-badge.rejected{background:rgba(239,68,68,.12);color:#f87171;border:1px solid rgba(239,68,68,.25)} | |
| .ph-meta{font-size:.69rem;color:rgba(255,255,255,.3)} | |
| .ph-empty{text-align:center;padding:36px 0;color:rgba(255,255,255,.25);font-size:.85rem} | |
| .ph-list-wrap{padding:0 22px} | |
| </style> | |
| </head> | |
| <body> | |
| <!-- LOGIN --> | |
| <div id="login-screen"> | |
| <!-- Animated floating orbs --> | |
| <div style="position:absolute;width:300px;height:300px;border-radius:50%;background:radial-gradient(circle,rgba(139,92,246,.3),transparent);top:-80px;left:-80px;animation:floatOrb 8s ease-in-out infinite;pointer-events:none"></div> | |
| <div style="position:absolute;width:250px;height:250px;border-radius:50%;background:radial-gradient(circle,rgba(59,130,246,.25),transparent);bottom:-60px;right:-60px;animation:floatOrb 10s ease-in-out infinite reverse;pointer-events:none"></div> | |
| <div style="position:absolute;width:180px;height:180px;border-radius:50%;background:radial-gradient(circle,rgba(16,185,129,.2),transparent);top:40%;right:10%;animation:floatOrb 12s ease-in-out infinite .5s;pointer-events:none"></div> | |
| <div class="lw"> | |
| <!-- Brand --> | |
| <div class="lb"> | |
| <div class="ico" style="width:76px;height:76px;animation:logoPop .6s cubic-bezier(.34,1.56,.64,1) both;padding:0;overflow:hidden;background:transparent;border:none;box-shadow:none"><img src="https://raw.githubusercontent.com/Shangyi69/psonlineshop/main/logo.png" alt="Recap Studio" style="width:100%;height:100%;object-fit:contain;display:block"></div> | |
| <h1 style="font-size:2.1rem;background:linear-gradient(135deg,#fff 0%,rgba(196,181,253,.9) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text">Recap Studio</h1> | |
| <p style="color:rgba(255,255,255,.5);font-size:.85rem;margin-top:6px;letter-spacing:.02em">AI-powered video recap generator</p> | |
| </div> | |
| <!-- Card --> | |
| <div class="lc"> | |
| <div class="tabs"> | |
| <button class="tab on" id="tab-li" onclick="stab('li')">Login</button> | |
| <button class="tab" id="tab-re" onclick="stab('re')">Register</button> | |
| </div> | |
| <div id="pane-li"> | |
| <div class="fi"> | |
| <label>USERNAME</label> | |
| <div style="position:relative"> | |
| <span style="position:absolute;left:13px;top:50%;transform:translateY(-50%);color:rgba(255,255,255,.35);font-size:.85rem"><i class="fas fa-user"></i></span> | |
| <input class="inp" id="l-u" placeholder="your username" autocomplete="username" style="padding-left:36px"> | |
| </div> | |
| </div> | |
| <div class="fi"> | |
| <label>PASSWORD</label> | |
| <div style="position:relative"> | |
| <span style="position:absolute;left:13px;top:50%;transform:translateY(-50%);color:rgba(255,255,255,.35);font-size:.85rem"><i class="fas fa-lock"></i></span> | |
| <input class="inp" id="l-p" type="password" placeholder="••••••••" style="padding-left:36px"> | |
| </div> | |
| </div> | |
| <button class="bp" onclick="doLogin()" style="margin-top:8px;letter-spacing:.03em;font-size:.95rem"> | |
| Sign In <i class="fas fa-arrow-right" style="font-size:.8rem"></i> | |
| </button> | |
| <div id="google-wrap" style="display:none"> | |
| <div class="ldiv"><hr><span>or</span><hr></div> | |
| <a href="/auth/google" class="gbtn"> | |
| <svg width="18" height="18" viewBox="0 0 48 48"><path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/><path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/><path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"/><path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.18 1.48-4.97 2.29-8.16 2.29-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/></svg> | |
| Continue with Google | |
| </a> | |
| </div> | |
| </div> | |
| <div id="pane-re" style="display:none"> | |
| <div class="fi"> | |
| <label>USERNAME <span style="color:rgba(255,255,255,.35);text-transform:none;letter-spacing:0;font-weight:400;font-size:.68rem">(optional)</span></label> | |
| <div style="position:relative"> | |
| <span style="position:absolute;left:13px;top:50%;transform:translateY(-50%);color:rgba(255,255,255,.35);font-size:.85rem"><i class="fas fa-user"></i></span> | |
| <input class="inp" id="r-u" placeholder="leave blank for random" style="padding-left:36px"> | |
| </div> | |
| </div> | |
| <div class="fi"> | |
| <label>PASSWORD</label> | |
| <div style="position:relative"> | |
| <span style="position:absolute;left:13px;top:50%;transform:translateY(-50%);color:rgba(255,255,255,.35);font-size:.85rem"><i class="fas fa-lock"></i></span> | |
| <input class="inp" id="r-p" type="password" placeholder="••••••••" style="padding-left:36px"> | |
| </div> | |
| </div> | |
| <button class="bp" onclick="doReg()" style="margin-top:8px;letter-spacing:.03em;font-size:.95rem"> | |
| Create Account <i class="fas fa-arrow-right" style="font-size:.8rem"></i> | |
| </button> | |
| </div> | |
| <div id="am" class="am"></div> | |
| </div> | |
| <div class="lfoot"> | |
| <a href="/terms">Terms of Service</a> · <a href="/privacy">Privacy Policy</a> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- APP --> | |
| <div id="app-screen"> | |
| <header class="hdr"> | |
| <div class="brand"><div class="brand-i"><img src="https://raw.githubusercontent.com/Shangyi69/psonlineshop/main/logo.png" alt="Recap Studio" style="width:100%;height:100%;object-fit:contain;border-radius:8px"></div>Recap Studio</div> | |
| <div class="hr"> | |
| <div class="cpill" id="cpill" onclick="openPay()">🪙 <span id="hcoins">0</span> coins</div> | |
| <button class="hb hb-p" onclick="openPay()">Buy Coins</button> | |
| <button class="mbtn" onclick="odw()"><span></span><span></span><span></span></button> | |
| </div> | |
| </header> | |
| <!-- DRAWER --> | |
| <div class="dov" id="dov"> | |
| <div class="dbg" onclick="cdw()"></div> | |
| <div class="drw"> | |
| <div class="drw-h"> | |
| <div class="drw-u"> | |
| <div class="av" id="dav">U</div> | |
| <div><div class="dn" id="dun">—</div><div class="drl" id="drl">Member</div></div> | |
| </div> | |
| <button class="dxbtn" onclick="cdw()"><i class="fas fa-times"></i></button> | |
| </div> | |
| <div class="drw-s"> | |
| <div class="ds"><div class="dsv" id="dcoins">0</div><div class="dsl">Coins</div></div> | |
| <div class="ds"><div class="dsv" id="dvids">0</div><div class="dsl">Videos</div></div> | |
| </div> | |
| <div class="drw-n"> | |
| <button class="dni" onclick="cdw()"><i class="fas fa-home"></i>Home</button> | |
| <button class="dni" onclick="cdw();openVideoHistory()"><i class="fas fa-film"></i>Video History</button> | |
| <button class="dni" onclick="cdw();openPay();setTimeout(()=>showPayTab('his'),200)"><i class="fas fa-receipt"></i>Order History</button> | |
| <button class="dni" id="adm-dlink" style="display:none" onclick="scrollToAdmin();cdw()"><i class="fas fa-crown"></i>Admin Panel</button> | |
| <a href="https://t.me/recapstudio" target="_blank" class="dni" style="text-decoration:none"><i class="fab fa-telegram" style="color:#29b6f6"></i>Telegram Channel</a> | |
| <button class="dni dni-r" onclick="doLogout()"><i class="fas fa-sign-out-alt"></i>Logout</button> | |
| </div> | |
| <div class="drw-f">recap.psonline.shop</div> | |
| </div> | |
| </div> | |
| <!-- MODE TOGGLE BAR --> | |
| <div class="mode-bar"> | |
| <button class="mode-btn on" id="mode-recap" onclick="switchMode('recap')"> | |
| <i class="fas fa-magic"></i> MOVIE RECAP | |
| </button> | |
| <button class="mode-btn" id="mode-srt" onclick="switchMode('srt')"> | |
| <i class="fas fa-closed-captioning"></i> TRANSLATE SRT | |
| </button> | |
| </div> | |
| <!-- ══ MOVIE RECAP MODE ══ --> | |
| <div id="section-recap"> | |
| <div class="ab"> | |
| <!-- VIDEO SOURCE --> | |
| <div class="card"> | |
| <div class="ch"><div class="ci ci-p"><i class="fas fa-video"></i></div><div class="ct">Video Source</div></div> | |
| <div class="uw"> | |
| <i class="fas fa-link uico"></i> | |
| <input class="uinp" id="vurl" placeholder="Paste YouTube / TikTok / Facebook / Instagram link…" oninput="onUrl(this.value)" style="padding-right:80px"> | |
| <span class="ubadge" id="ubadge"></span> | |
| <button class="paste-btn" onclick="doPaste()" title="Paste from clipboard"><i class="fas fa-paste"></i> Paste</button> | |
| </div> | |
| <div class="prev-loading" id="prev-loading"><i class="fas fa-spinner sp"></i> Loading thumbnail…</div> | |
| <div class="orsep">or upload file</div> | |
| <div class="upz"> | |
| <input type="file" id="vfile" accept="video/*" onchange="onFile(this)"> | |
| <div class="upz-t"><i class="fas fa-cloud-upload-alt" style="margin-right:6px;font-size:1.1rem"></i>Click or drag video file here</div> | |
| <div class="upz-n" id="upzn"></div> | |
| </div> | |
| </div> | |
| <!-- SETTINGS --> | |
| <div class="card"> | |
| <div class="ch"><div class="ci ci-o"><i class="fas fa-sliders-h"></i></div><div class="ct">Settings</div></div> | |
| <div class="sgrid"> | |
| <div class="si"><div class="sil">Language</div> | |
| <select class="ssel" id="vlang" onchange="onLang()"> | |
| <option value="my">🇲🇲 Myanmar</option> | |
| <option value="th">🇹🇭 Thai</option> | |
| <option value="en">🇬🇧 English</option> | |
| </select> | |
| </div> | |
| <div class="si"><div class="sil">Content Type</div> | |
| <select class="ssel" id="ctype"> | |
| <option value="Movie Recap">🎬 Movie Recap</option> | |
| <option value="Medical/Health">💊 Medical</option> | |
| </select> | |
| </div> | |
| <div class="si"><div class="sil">AI Model</div> | |
| <select class="ssel" id="aimodel"> | |
| <option value="Gemini">✨ Gemini</option> | |
| <option value="DeepSeek">🤖 DeepSeek</option> | |
| </select> | |
| </div> | |
| <div class="si"><div class="sil">TTS Engine</div> | |
| <select class="ssel" id="engine" onchange="onEng()"> | |
| <option value="ms">🎙️ Edge TTS</option> | |
| <option value="gemini">🔮 Gemini TTS</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div style="margin-top:10px"> | |
| <div class="sil" style="margin-bottom:6px">Voice</div> | |
| <div class="vrow"> | |
| <select class="ssel" id="vsel"></select> | |
| <button class="pvbtn" id="pvbtn" onclick="doPrevVoice()" title="Preview Voice"><i class="fas fa-play"></i></button> | |
| </div> | |
| <div style="margin-top:10px"> | |
| <button onclick="toggleAdvanced()" id="adv-btn" style="background:none;border:none;color:var(--muted);font-size:.72rem;font-weight:600;cursor:pointer;display:flex;align-items:center;gap:5px;padding:0;font-family:var(--F)"> | |
| <i class="fas fa-chevron-right" id="adv-icon" style="font-size:.6rem;transition:.2s"></i> Advanced | |
| </button> | |
| <div id="adv-wrap" style="display:none;margin-top:8px"> | |
| <div class="spd-row"> | |
| <label>Speed</label> | |
| <input type="range" class="spd-sl" id="spd" min="-50" max="100" value="30" oninput="document.getElementById('spdv').textContent=this.value+'%'"> | |
| <span class="spd-v" id="spdv">30%</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- VIDEO OPTIONS --> | |
| <div class="card"> | |
| <div class="ch"><div class="ci ci-g"><i class="fas fa-film"></i></div><div class="ct">Video Options</div></div> | |
| <div class="egrid" style="grid-template-columns:1fr 1fr;gap:8px"> | |
| <div class="ei"><div class="eil">Aspect Ratio</div> | |
| <select class="esel" id="crop" onchange="syncCanvasInner(document.getElementById('pvid'));_syncSubPreview();"> | |
| <option value="original" selected>Original</option> | |
| <option value="9:16">9:16 TikTok</option> | |
| <option value="16:9">16:9 YouTube</option> | |
| <option value="1:1">1:1 Square</option> | |
| </select> | |
| </div> | |
| <div class="ei"><div class="eil">Flip</div> | |
| <button class="icon-toggle" id="btn-flip" onclick="toggleOpt('flip')"><i class="fas fa-arrows-alt-h"></i> Off</button> | |
| </div> | |
| <div class="ei"><div class="eil">Color Boost</div> | |
| <button class="icon-toggle" id="btn-col" onclick="toggleOpt('col')"><i class="fas fa-adjust"></i> Off</button> | |
| </div> | |
| <div class="ei"><div class="eil">Background Music</div> | |
| <button class="icon-toggle" id="btn-bgm" onclick="toggleBgm()"><i class="fas fa-music"></i> Off</button> | |
| </div> | |
| <div class="ei" style="grid-column:1/-1;padding:0;border:none;background:none"> | |
| <div id="bgm-wrap" style="display:none"> | |
| <label style="display:flex;align-items:center;gap:10px;background:rgba(255,255,255,.05);border:1px dashed rgba(168,85,247,.3);border-radius:9px;padding:10px 12px;cursor:pointer"> | |
| <i class="fas fa-cloud-upload-alt" style="color:#a855f7;font-size:1.1rem"></i> | |
| <span id="bgm-name" style="color:rgba(255,255,255,.5);font-size:.82rem">Click to upload MP3 / WAV</span> | |
| <input type="file" id="bgm-file" accept=".mp3,.wav,audio/*" style="display:none" onchange="onBgmFile(this)"> | |
| </label> | |
| </div> | |
| </div> | |
| <!-- Watermark moved to Canvas Overlay panel below --> | |
| </div> | |
| </div> | |
| <!-- PREVIEW CANVAS + SUBTITLES --> | |
| <div class="card" style="padding:0;overflow:hidden"> | |
| <div class="canvas-wrap" id="canvas-wrap" style="margin-top:0;border-radius:0"> | |
| <div class="canvas-bg-blur" id="canvas-bg-blur"></div> | |
| <div class="canvas-video-wrap" id="canvas-video-wrap" style="position:relative"> | |
| <video id="pvid" controls playsinline style="display:none;width:100%;height:auto;display:block"></video> | |
| <img id="pthumb" style="display:none;width:100%;height:auto;object-fit:cover;display:block" alt="thumbnail"> | |
| <div id="canvas-inner" class="canvas-inner" style="position:absolute;inset:0;pointer-events:none;z-index:10"> | |
| <div id="db-blur" style="display:none;position:absolute;left:20px;top:80px;width:160px;height:90px;pointer-events:auto;cursor:move;border:2px dashed rgba(255,100,100,.85);background:rgba(0,0,0,.25);border-radius:6px;box-sizing:border-box;touch-action:none"> | |
| <div style="position:absolute;top:2px;left:0;right:0;text-align:center;font-size:.58rem;font-weight:700;color:rgba(255,160,160,.9);letter-spacing:.05em;pointer-events:none;user-select:none">BLUR ZONE</div> | |
| <div class="rh" style="position:absolute;right:0;bottom:0;width:18px;height:18px;cursor:se-resize;background:rgba(255,100,100,.6);border-radius:3px 0 4px 0;display:flex;align-items:center;justify-content:center"> | |
| <i class="fas fa-expand-arrows-alt" style="font-size:.5rem;color:#fff;pointer-events:none"></i> | |
| </div> | |
| </div> | |
| <div id="db-wm" style="display:none;position:absolute;left:10px;bottom:40px;width:180px;height:36px;pointer-events:auto;cursor:move;border:2px dashed rgba(251,191,36,.85);background:rgba(0,0,0,.22);border-radius:6px;box-sizing:border-box;touch-action:none"> | |
| <div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none"> | |
| <span id="db-wm-txt" style="font-size:.8rem;font-weight:700;color:#fef9c3;text-shadow:0 1px 3px rgba(0,0,0,.9);white-space:nowrap;overflow:hidden;max-width:92%;pointer-events:none">Watermark</span> | |
| </div> | |
| <div class="rh" style="position:absolute;right:0;bottom:0;width:16px;height:16px;cursor:se-resize;background:rgba(251,191,36,.6);border-radius:3px 0 4px 0;display:flex;align-items:center;justify-content:center"> | |
| <i class="fas fa-expand-arrows-alt" style="font-size:.45rem;color:#fff;pointer-events:none"></i> | |
| </div> | |
| </div> | |
| <div id="db-logo" style="display:none;position:absolute;right:10px;top:10px;width:72px;height:72px;pointer-events:auto;cursor:move;border:2px dashed rgba(52,211,153,.85);background:rgba(0,0,0,.22);border-radius:6px;box-sizing:border-box;touch-action:none;background-size:contain;background-repeat:no-repeat;background-position:center"> | |
| <div id="db-logo-lbl" style="position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;pointer-events:none"> | |
| <i class="fas fa-image" style="font-size:.9rem;color:rgba(52,211,153,.8)"></i> | |
| <span style="font-size:.52rem;font-weight:700;color:rgba(52,211,153,.8)">LOGO</span> | |
| </div> | |
| <div class="rh" style="position:absolute;right:0;bottom:0;width:16px;height:16px;cursor:se-resize;background:rgba(52,211,153,.6);border-radius:3px 0 4px 0;display:flex;align-items:center;justify-content:center"> | |
| <i class="fas fa-expand-arrows-alt" style="font-size:.45rem;color:#fff;pointer-events:none"></i> | |
| </div> | |
| </div> | |
| <div id="db-sub" style="display:none;position:absolute;left:0;right:0;width:100%;text-align:center;pointer-events:none"> | |
| <span style="display:inline-block;padding:3px 10px;font-size:.85rem;font-weight:700;line-height:1.4;border-radius:4px">နမူနာ မြန်မာ စာတန်းထိုး</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="canvas-ph" class="canvas-ph"><i class="fas fa-film"></i><span>Preview appears after URL is pasted</span></div> | |
| </div> | |
| <!-- Canvas Overlay Controls panel --> | |
| <div style="background:linear-gradient(135deg,#0e2233,#0a1f2e);padding:10px 12px"> | |
| <!-- Canvas Overlays Section --> | |
| <div style="margin-bottom:2px"> | |
| <div style="font-size:.62rem;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:#94a3b8;margin-bottom:8px;display:flex;align-items:center;gap:5px"> | |
| <i class="fas fa-layer-group" style="color:#a78bfa"></i>Canvas Overlays | |
| </div> | |
| <!-- 3-column toggle grid --> | |
| <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;margin-bottom:10px"> | |
| <!-- Watermark card --> | |
| <div style="background:rgba(251,191,36,.05);border:1.5px solid rgba(251,191,36,.18);border-radius:10px;padding:8px 6px;display:flex;flex-direction:column;align-items:center;gap:5px"> | |
| <div style="width:30px;height:30px;border-radius:8px;background:rgba(251,191,36,.12);display:flex;align-items:center;justify-content:center"> | |
| <i class="fas fa-stamp" style="color:rgba(251,191,36,.9);font-size:.85rem"></i> | |
| </div> | |
| <div style="font-size:.62rem;font-weight:700;color:rgba(251,191,36,.8);text-align:center">Watermark</div> | |
| <button id="ov-wm" onclick="ovToggle('wm')" style="width:100%;padding:4px 0;font-size:.68rem;font-weight:700;font-family:var(--F);border-radius:6px;border:1.5px solid rgba(251,191,36,.35);background:rgba(251,191,36,.07);color:rgba(251,191,36,.8);cursor:pointer;transition:.2s"> | |
| Off | |
| </button> | |
| </div> | |
| <!-- Logo card --> | |
| <div style="background:rgba(52,211,153,.05);border:1.5px solid rgba(52,211,153,.18);border-radius:10px;padding:8px 6px;display:flex;flex-direction:column;align-items:center;gap:5px"> | |
| <div style="width:30px;height:30px;border-radius:8px;background:rgba(52,211,153,.12);display:flex;align-items:center;justify-content:center"> | |
| <i class="fas fa-image" style="color:rgba(52,211,153,.9);font-size:.85rem"></i> | |
| </div> | |
| <div style="font-size:.62rem;font-weight:700;color:rgba(52,211,153,.8);text-align:center">Logo</div> | |
| <button id="ov-logo" onclick="ovToggle('logo')" style="width:100%;padding:4px 0;font-size:.68rem;font-weight:700;font-family:var(--F);border-radius:6px;border:1.5px solid rgba(52,211,153,.35);background:rgba(52,211,153,.07);color:rgba(52,211,153,.8);cursor:pointer;transition:.2s"> | |
| Off | |
| </button> | |
| </div> | |
| <!-- Blur card --> | |
| <div style="background:rgba(239,68,68,.04);border:1.5px solid rgba(252,165,165,.18);border-radius:10px;padding:8px 6px;display:flex;flex-direction:column;align-items:center;gap:5px"> | |
| <div style="width:30px;height:30px;border-radius:8px;background:rgba(239,68,68,.1);display:flex;align-items:center;justify-content:center"> | |
| <i class="fas fa-eraser" style="color:rgba(252,165,165,.9);font-size:.85rem"></i> | |
| </div> | |
| <div style="font-size:.62rem;font-weight:700;color:rgba(252,165,165,.8);text-align:center">Blur</div> | |
| <button id="ov-blur" onclick="ovToggle('blur')" style="width:100%;padding:4px 0;font-size:.68rem;font-weight:700;font-family:var(--F);border-radius:6px;border:1.5px solid rgba(252,165,165,.35);background:rgba(239,68,68,.07);color:rgba(252,165,165,.8);cursor:pointer;transition:.2s"> | |
| Off | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Watermark panel --> | |
| <div id="ov-wm-wrap" style="display:none;margin-bottom:8px;padding:10px 12px;background:rgba(251,191,36,.05);border:1px solid rgba(251,191,36,.22);border-radius:10px"> | |
| <div style="font-size:.58rem;font-weight:700;color:rgba(251,191,36,.7);letter-spacing:.06em;text-transform:uppercase;margin-bottom:6px"> | |
| <i class="fas fa-stamp" style="margin-right:4px"></i>Watermark Text | |
| </div> | |
| <input id="wm-text2" class="winp" placeholder="@YourPage သို့မဟုတ် Recap Studio" maxlength="40" | |
| oninput="document.getElementById('db-wm-txt').textContent=this.value||'Watermark'" | |
| style="width:100%;font-size:.8rem;background:rgba(255,255,255,.06);border-color:rgba(251,191,36,.3);color:#fef9c3;box-sizing:border-box"> | |
| <div style="font-size:.6rem;color:rgba(251,191,36,.4);margin-top:5px;display:flex;align-items:center;gap:4px"> | |
| <i class="fas fa-hand-pointer"></i>Preview canvas ပေါ်မှာ drag ပြီး position ချိန်ပါ | |
| </div> | |
| </div> | |
| <!-- Logo panel --> | |
| <div id="ov-logo-wrap" style="display:none;margin-bottom:8px;padding:10px 12px;background:rgba(52,211,153,.05);border:1px solid rgba(52,211,153,.22);border-radius:10px"> | |
| <div style="font-size:.58rem;font-weight:700;color:rgba(52,211,153,.7);letter-spacing:.06em;text-transform:uppercase;margin-bottom:6px"> | |
| <i class="fas fa-image" style="margin-right:4px"></i>Logo Image | |
| </div> | |
| <label style="display:flex;align-items:center;gap:8px;background:rgba(255,255,255,.04);border:1px dashed rgba(52,211,153,.3);border-radius:8px;padding:8px 10px;cursor:pointer;transition:.2s"> | |
| <i class="fas fa-cloud-upload-alt" style="color:rgba(52,211,153,.7);font-size:1rem"></i> | |
| <span id="logo-name" style="color:rgba(52,211,153,.55);font-size:.78rem;flex:1">Click to upload PNG / JPG</span> | |
| <input type="file" id="logo-file2" accept="image/*" style="display:none" onchange="onLogoFile(this)"> | |
| </label> | |
| <div style="font-size:.6rem;color:rgba(52,211,153,.4);margin-top:5px;display:flex;align-items:center;gap:4px"> | |
| <i class="fas fa-hand-pointer"></i>Canvas ပေါ်မှာ drag / resize ပြီး position ချိန်ပါ | |
| </div> | |
| </div> | |
| <!-- Blur hint panel --> | |
| <div id="ov-blur-hint" style="display:none;margin-bottom:8px;padding:10px 12px;background:rgba(239,68,68,.04);border:1px solid rgba(239,68,68,.2);border-radius:10px"> | |
| <div style="font-size:.6rem;color:rgba(252,165,165,.7);display:flex;align-items:center;gap:5px"> | |
| <i class="fas fa-hand-pointer"></i>Preview canvas ပေါ်မှာ blur zone ကို drag နှင့် resize လုပ်ပါ | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Divider --> | |
| <div style="height:1px;background:rgba(255,255,255,.07);margin:6px 0 8px"></div> | |
| <!-- Row 2: Subtitle toggle --> | |
| <div> | |
| <div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:6px"> | |
| <div style="font-size:.65rem;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:#38bdf8;display:flex;align-items:center;gap:5px"> | |
| <i class="fas fa-closed-captioning"></i>Subtitles (Auto) | |
| </div> | |
| <button class="wm-toggle" id="btn-sub" onclick="toggleSub()" style="border-color:#38bdf8;background:rgba(56,189,248,.08);color:#38bdf8;padding:3px 12px;font-size:.72rem">Off</button> | |
| </div> | |
| <div id="sub-wrap" style="display:none;margin-top:8px"> | |
| <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;margin-bottom:8px"> | |
| <div> | |
| <div style="font-size:.58rem;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:rgba(56,189,248,.6);margin-bottom:3px">Font Size</div> | |
| <select class="esel" id="sub-size" onchange="_syncSubPreview()" style="color:#e0f2fe;background:rgba(255,255,255,.06);border:1px solid rgba(56,189,248,.2);border-radius:6px;padding:4px 6px;width:100%;font-size:.75rem"> | |
| <option value="0.0391">XSmall</option> | |
| <option value="0.0547" selected>Small</option> | |
| <option value="0.0781">Medium</option> | |
| <option value="0.0938">Large</option> | |
| <option value="0.1172">XLarge</option> | |
| </select> | |
| </div> | |
| <div> | |
| <div style="font-size:.58rem;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:rgba(56,189,248,.6);margin-bottom:3px">Color</div> | |
| <select class="esel" id="sub-color" onchange="_syncSubPreview()" style="color:#e0f2fe;background:rgba(255,255,255,.06);border:1px solid rgba(56,189,248,.2);border-radius:6px;padding:4px 6px;width:100%;font-size:.75rem"> | |
| <option value="white" selected>⬜ White</option> | |
| <option value="yellow">🟡 Yellow</option> | |
| <option value="cyan">🔵 Cyan</option> | |
| <option value="green">🟢 Green</option> | |
| <option value="orange">🟠 Orange</option> | |
| <option value="pink">🩷 Pink</option> | |
| <option value="red">🔴 Red</option> | |
| <option value="lime">💚 Lime</option> | |
| <option value="hotpink">💗 Hot Pink</option> | |
| <option value="gold">✨ Gold</option> | |
| <option value="violet">💜 Violet</option> | |
| <option value="deepskyblue">🩵 Sky Blue</option> | |
| <option value="coral">🪸 Coral</option> | |
| </select> | |
| </div> | |
| <div> | |
| <div style="font-size:.58rem;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:rgba(56,189,248,.6);margin-bottom:3px">Style</div> | |
| <select class="esel" id="sub-style" onchange="_syncSubPreview()" style="color:#e0f2fe;background:rgba(255,255,255,.06);border:1px solid rgba(56,189,248,.2);border-radius:6px;padding:4px 6px;width:100%;font-size:.75rem"> | |
| <option value="outline" selected>Outline</option> | |
| <option value="box">Box</option> | |
| <option value="shadow">Shadow</option> | |
| <option value="glow">Glow</option> | |
| <option value="stroke">Stroke</option> | |
| <option value="plain">Plain</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div> | |
| <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:3px"> | |
| <div style="font-size:.58rem;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:rgba(56,189,248,.6)">Position</div> | |
| <span id="sub-pos-lbl" style="font-size:.68rem;font-weight:700;color:#38bdf8">90%</span> | |
| </div> | |
| <input type="range" id="sub-pos" min="0" max="95" value="90" | |
| oninput="document.getElementById('sub-pos-lbl').textContent=this.value+'%';_syncSubPreview()" | |
| style="width:100%;accent-color:#38bdf8;cursor:pointer"> | |
| <div style="display:flex;justify-content:space-between;font-size:.58rem;color:rgba(56,189,248,.35);margin-top:1px"><span>Top</span><span>Bottom</span></div> | |
| </div> | |
| </div> | |
| </div><!-- /subtitle hidden --> | |
| </div> | |
| </div> | |
| <!-- AUTO PROCESS --> | |
| <div style="margin-bottom:12px"> | |
| <button class="btn-auto" id="btnauto" onclick="doAuto()"> | |
| <i class="fas fa-magic"></i> Auto Process | |
| <span style="opacity:.7;font-size:.78em;background:rgba(255,255,255,.2);padding:2px 8px;border-radius:10px">1 Coin</span> | |
| </button> | |
| </div> | |
| <!-- PROGRESS --> | |
| <div class="pc" id="pc"> | |
| <div class="ph"><span class="ptit">⚙️ Processing…</span><span class="ptmr" id="ptmr">⏱ 0s</span></div> | |
| <div class="pb-bg"><div class="pb" id="pb"></div></div> | |
| <div class="pmsg" id="pmsg">Starting…</div> | |
| <div class="psteps"> | |
| <span class="ps" id="ps-dl">📥 Download</span> | |
| <span class="ps" id="ps-tr">🎙️ Transcribe</span> | |
| <span class="ps" id="ps-ai">🤖 AI Script</span> | |
| <span class="ps" id="ps-ts">🔊 TTS</span> | |
| <span class="ps" id="ps-vd">🎬 Render</span> | |
| </div> | |
| </div> | |
| <!-- RESULT --> | |
| <div class="rc" id="rc"> | |
| <div class="rvw"><video id="rvid" controls playsinline></video></div> | |
| <div class="rb"> | |
| <div class="rtm" id="rtm" style="display:none"><i class="fas fa-clock"></i><span></span></div> | |
| <div class="rtit" id="rtit" style="display:none"></div> | |
| <div class="rtag" id="rtag" style="display:none"></div> | |
| <div class="ract"> | |
| <button class="rb-btn rb-g" onclick="dlVideo()"><i class="fas fa-download"></i> Download MP4</button> | |
| <button class="rb-btn rb-b" onclick="copyCap()"><i class="fas fa-copy"></i> Copy Caption</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ADMIN --> | |
| <div class="admp" id="admp"> | |
| <div class="ch"><div class="ci ci-p"><i class="fas fa-crown"></i></div><div class="ct">Admin Panel</div><span class="admbadge">Admin</span></div> | |
| <div class="admg"> | |
| <div><div class="sil" style="margin-bottom:5px">Add Coins</div> | |
| <div class="ai-row"><input class="ai" id="au-add" placeholder="username"><input class="ai" id="an-add" type="number" placeholder="n" style="width:60px"><button class="abtn ab-g" onclick="admCoins('add')">Add</button></div> | |
| </div> | |
| <div><div class="sil" style="margin-bottom:5px">Set Coins</div> | |
| <div class="ai-row"><input class="ai" id="au-set" placeholder="username"><input class="ai" id="an-set" type="number" placeholder="n" style="width:60px"><button class="abtn ab-g" onclick="admCoins('set')">Set</button></div> | |
| </div> | |
| <div><div class="sil" style="margin-bottom:5px">Create User</div> | |
| <div class="ai-row"><input class="ai" id="au-new" placeholder="name (blank=auto)"><button class="abtn ab-g" onclick="admCreate()">Create</button></div> | |
| <div id="au-res" style="font-size:.71rem;color:var(--green);margin-top:3px;font-weight:600"></div> | |
| </div> | |
| <div><div class="sil" style="margin-bottom:5px">Delete User</div> | |
| <div class="ai-row"><input class="ai" id="au-del" placeholder="username"><button class="abtn ab-r" onclick="admDel()">Delete</button></div> | |
| </div> | |
| </div> | |
| <div id="admmsg" class="admmsg"></div> | |
| <div style="display:flex;gap:8px;margin-top:8px;flex-wrap:wrap"> | |
| <button class="adm-act-btn" onclick="loadUsers()"><i class="fas fa-users"></i> Users</button> | |
| <button class="adm-act-btn" style="background:rgba(239,68,68,.1);border-color:rgba(239,68,68,.3);color:#ef4444" onclick="loadPendingPayments()"><i class="fas fa-clock"></i> Pending Payments <span id="adm-pending-count" style="background:#ef4444;color:#fff;border-radius:20px;padding:1px 7px;font-size:.62rem;margin-left:4px;display:none">0</span></button> | |
| <button class="adm-act-btn" style="background:rgba(99,102,241,.1);border-color:rgba(99,102,241,.3);color:#6366f1" onclick="openBroadcast()"><i class="fas fa-bullhorn"></i> Broadcast</button> | |
| </div> | |
| <div id="utw" style="overflow-x:auto;margin-top:8px"></div> | |
| <!-- Pending Payments --> | |
| <div id="adm-pending-wrap" style="display:none;margin-top:10px"> | |
| <div style="font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin-bottom:8px;display:flex;align-items:center;justify-content:space-between"> | |
| <span>💰 Pending Payments</span> | |
| <button onclick="loadPendingPayments()" style="background:none;border:none;color:var(--primary);font-size:.75rem;cursor:pointer;font-family:var(--F);font-weight:600"><i class="fas fa-sync-alt"></i> Refresh</button> | |
| </div> | |
| <div id="adm-pending-list"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div><!-- /section-recap --> | |
| <!-- ══ TRANSLATE SRT MODE ══ --> | |
| <div id="section-srt" style="display:none"> | |
| <div class="ab"> | |
| <!-- ══ STEP 1: VIDEO INPUT ══ --> | |
| <div class="card" style="padding:0;overflow:hidden" id="srt-step1-card"> | |
| <!-- Step label --> | |
| <div style="padding:10px 14px 0;display:flex;align-items:center;gap:8px"> | |
| <div style="width:22px;height:22px;border-radius:50%;background:linear-gradient(135deg,#7c3aed,#5b4cf5);display:flex;align-items:center;justify-content:center;font-size:.7rem;font-weight:800;color:#fff;flex-shrink:0">1</div> | |
| <span style="font-size:.78rem;font-weight:700;color:var(--text)">Video ထည့်ပါ</span> | |
| </div> | |
| <!-- URL input --> | |
| <div style="padding:8px 12px 0"> | |
| <div class="uw"> | |
| <i class="fas fa-link uico"></i> | |
| <input class="uinp" id="srt-vurl" placeholder="YouTube / TikTok / XHS / Facebook URL…" oninput="onSrtUrl(this.value)" style="padding-right:80px"> | |
| <span class="ubadge" id="srt-ubadge"></span> | |
| <button class="paste-btn" onclick="srtPaste()"><i class="fas fa-paste"></i> Paste</button> | |
| </div> | |
| <div class="prev-loading" id="srt-prev-loading" style="display:none"><i class="fas fa-spinner sp"></i> Loading…</div> | |
| <div class="orsep" style="margin:6px 0">or upload file</div> | |
| <div class="upz" style="margin-bottom:8px"> | |
| <input type="file" id="srt-vfile" accept="video/*" onchange="onSrtVFile(this)"> | |
| <div class="upz-t"><i class="fas fa-cloud-upload-alt" style="margin-right:6px;font-size:1.1rem"></i>Click or drag video file here</div> | |
| <div class="upz-n" id="srt-upzn"></div> | |
| </div> | |
| </div> | |
| <!-- Generate SRT button --> | |
| <div style="padding:10px 12px 12px"> | |
| <button class="btn-auto" id="srt-gen-btn" onclick="doGenerateSrt()" style="background:linear-gradient(135deg,#7c3aed,#5b4cf5)"> | |
| <i class="fas fa-robot"></i> Generate Myanmar SRT | |
| <span style="opacity:.7;font-size:.78em;background:rgba(255,255,255,.2);padding:2px 8px;border-radius:10px">1 Coin</span> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Generate progress --> | |
| <div class="pc" id="srt-gen-pc" style="display:none"> | |
| <div class="ph"><span class="ptit">🤖 SRT ထုတ်နေသည်…</span><span class="ptmr" id="srt-gen-ptmr">⏱ 0s</span></div> | |
| <div class="pb-bg"><div class="pb" id="srt-gen-pb"></div></div> | |
| <div class="pmsg" id="srt-gen-pmsg">Starting…</div> | |
| <div class="psteps"> | |
| <span class="ps" id="srt-ps-dl">📥 Download</span> | |
| <span class="ps" id="srt-ps-ai">🤖 Gemini SRT</span> | |
| </div> | |
| </div> | |
| <!-- ══ STEP 2: SRT PREVIEW + SETTINGS ══ --> | |
| <div id="srt-step2-wrap" style="display:none"> | |
| <!-- Step label --> | |
| <div style="display:flex;align-items:center;gap:8px;padding:4px 4px 0"> | |
| <div style="width:22px;height:22px;border-radius:50%;background:linear-gradient(135deg,#0ea5e9,#6366f1);display:flex;align-items:center;justify-content:center;font-size:.7rem;font-weight:800;color:#fff;flex-shrink:0">2</div> | |
| <span style="font-size:.78rem;font-weight:700;color:var(--text)">SRT စစ်ဆေးပြီး Render လုပ်ပါ</span> | |
| </div> | |
| <!-- SRT Preview card --> | |
| <div class="card" style="padding:12px"> | |
| <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px"> | |
| <div style="font-size:.65rem;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:#94a3b8;display:flex;align-items:center;gap:5px"> | |
| <i class="fas fa-closed-captioning" style="color:#a78bfa"></i>Myanmar SRT Preview | |
| <span id="srt-block-count" style="background:rgba(167,139,250,.15);color:#a78bfa;border-radius:10px;padding:1px 7px;font-size:.62rem"></span> | |
| </div> | |
| <button onclick="doRegenSrt()" style="padding:5px 10px;background:rgba(251,191,36,.1);border:1px solid rgba(251,191,36,.3);border-radius:7px;color:#fbbf24;font-family:var(--F);font-size:.7rem;font-weight:700;cursor:pointer;display:flex;align-items:center;gap:4px;transition:.2s"> | |
| <i class="fas fa-redo"></i> Regenerate | |
| </button> | |
| </div> | |
| <!-- First subtitle shown on thumbnail --> | |
| <div id="srt-first-sub-wrap" style="display:none;margin-bottom:8px;padding:7px 10px;background:rgba(167,139,250,.08);border:1px solid rgba(167,139,250,.2);border-radius:8px"> | |
| <div style="font-size:.58rem;font-weight:700;color:rgba(167,139,250,.6);text-transform:uppercase;letter-spacing:.06em;margin-bottom:3px">First Subtitle</div> | |
| <div id="srt-first-sub-txt" style="font-size:.82rem;color:#e0f2fe;font-weight:500"></div> | |
| </div> | |
| <!-- SRT scrollable preview --> | |
| <div class="srt-preview" id="srt-preview-box" style="max-height:180px"></div> | |
| </div> | |
| <!-- PROCESS button --> | |
| <div style="margin-bottom:12px;margin-top:4px"> | |
| <button class="btn-auto" id="srt-burn-btn" onclick="doProcessSrt()" style="background:linear-gradient(135deg,#0ea5e9,#6366f1)"> | |
| <i class="fas fa-film"></i> Render Video | |
| </button> | |
| </div> | |
| <!-- Render progress --> | |
| <div class="pc" id="srt-pc" style="display:none"> | |
| <div class="ph"><span class="ptit">⚙️ Render လုပ်နေသည်…</span><span class="ptmr" id="srt-ptmr">⏱ 0s</span></div> | |
| <div class="pb-bg"><div class="pb" id="srt-pb"></div></div> | |
| <div class="pmsg" id="srt-pmsg">Starting…</div> | |
| <div class="psteps"> | |
| <span class="ps" id="srt-ps-burn">🎬 Render</span> | |
| </div> | |
| </div> | |
| <!-- VIDEO RESULT --> | |
| <div class="rc" id="srt-rc" style="display:none"> | |
| <div class="rvw"><video id="srt-rvid" controls playsinline></video></div> | |
| <div class="rb"> | |
| <div class="ract"> | |
| <button class="rb-btn rb-g" onclick="srtDlVideo()"><i class="fas fa-download"></i> Download MP4</button> | |
| <button class="rb-btn" style="background:var(--bg);border:1.5px solid var(--border);color:var(--text)" onclick="srtReset()"><i class="fas fa-redo"></i> Reset</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div><!-- /step2-wrap --> | |
| <!-- VIDEO FX OVERLAYS — exact movie recap style canvas drag/resize --> | |
| <div class="card" style="padding:0;overflow:hidden"> | |
| <!-- Canvas preview with draggable overlays --> | |
| <div class="canvas-wrap" id="srt-canvas-wrap" style="margin-top:0;border-radius:0;position:relative"> | |
| <div class="canvas-bg-blur" id="srt-canvas-bg-blur"></div> | |
| <div class="canvas-video-wrap" id="srt-canvas-video-wrap" style="position:relative;min-height:130px;border-radius:10px;overflow:hidden"> | |
| <video id="srt-pvid" controls playsinline style="width:100%;height:auto;max-height:480px;object-fit:contain;display:none"></video> | |
| <img id="srt-pthumb" style="display:none;width:100%;height:auto;object-fit:cover;display:block;border-radius:10px;transition:transform .3s ease" alt=""> | |
| <!-- Draggable overlay elements --> | |
| <div id="srt-canvas-inner" class="canvas-inner" style="position:absolute;inset:0;pointer-events:none;z-index:10"> | |
| <!-- Blur zone --> | |
| <div id="srt-db-blur" style="display:none;position:absolute;left:20px;top:80px;width:160px;height:90px;pointer-events:auto;cursor:move;border:2px dashed rgba(255,100,100,.85);background:rgba(0,0,0,.25);border-radius:6px;box-sizing:border-box;touch-action:none"> | |
| <div style="position:absolute;top:2px;left:0;right:0;text-align:center;font-size:.58rem;font-weight:700;color:rgba(255,160,160,.9);letter-spacing:.05em;pointer-events:none;user-select:none">BLUR ZONE</div> | |
| <div class="rh" style="position:absolute;right:0;bottom:0;width:18px;height:18px;cursor:se-resize;background:rgba(255,100,100,.6);border-radius:3px 0 4px 0;display:flex;align-items:center;justify-content:center"> | |
| <i class="fas fa-expand-arrows-alt" style="font-size:.5rem;color:#fff;pointer-events:none"></i> | |
| </div> | |
| </div> | |
| <!-- Watermark --> | |
| <div id="srt-db-wm" style="display:none;position:absolute;left:10px;bottom:40px;width:180px;height:36px;pointer-events:auto;cursor:move;border:2px dashed rgba(251,191,36,.85);background:rgba(0,0,0,.22);border-radius:6px;box-sizing:border-box;touch-action:none"> | |
| <div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none"> | |
| <span id="srt-db-wm-txt" style="font-size:.8rem;font-weight:700;color:#fef9c3;text-shadow:0 1px 3px rgba(0,0,0,.9);white-space:nowrap;overflow:hidden;max-width:92%;pointer-events:none">Watermark</span> | |
| </div> | |
| <div class="rh" style="position:absolute;right:0;bottom:0;width:16px;height:16px;cursor:se-resize;background:rgba(251,191,36,.6);border-radius:3px 0 4px 0;display:flex;align-items:center;justify-content:center"> | |
| <i class="fas fa-expand-arrows-alt" style="font-size:.45rem;color:#fff;pointer-events:none"></i> | |
| </div> | |
| </div> | |
| <!-- Logo --> | |
| <div id="srt-db-logo" style="display:none;position:absolute;right:10px;top:10px;width:72px;height:72px;pointer-events:auto;cursor:move;border:2px dashed rgba(52,211,153,.85);background:rgba(0,0,0,.22);border-radius:6px;box-sizing:border-box;touch-action:none;background-size:contain;background-repeat:no-repeat;background-position:center"> | |
| <div id="srt-db-logo-lbl" style="position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;pointer-events:none"> | |
| <i class="fas fa-image" style="font-size:.9rem;color:rgba(52,211,153,.8)"></i> | |
| <span style="font-size:.52rem;font-weight:700;color:rgba(52,211,153,.8)">LOGO</span> | |
| </div> | |
| <div class="rh" style="position:absolute;right:0;bottom:0;width:16px;height:16px;cursor:se-resize;background:rgba(52,211,153,.6);border-radius:3px 0 4px 0;display:flex;align-items:center;justify-content:center"> | |
| <i class="fas fa-expand-arrows-alt" style="font-size:.45rem;color:#fff;pointer-events:none"></i> | |
| </div> | |
| </div> | |
| <!-- Subtitle preview --> | |
| <div id="srt-sub-overlay" style="display:none;position:absolute;left:0;right:0;width:100%;text-align:center;pointer-events:none;top:85%;transform:translateY(-50%)"> | |
| <span id="srt-sub-preview-span" style="display:inline-block;padding:0 8px;line-height:1.25;font-weight:700;font-family:'Noto Sans Myanmar','Padauk',sans-serif;color:#fff;text-shadow:0 0 4px #000,0 1px 6px #000,-1px -1px 0 #000,1px 1px 0 #000;border-radius:4px;max-width:95%;font-size:.9rem;pointer-events:none"> စာတန်းထိုး</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="srt-canvas-ph" style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:130px;color:rgba(255,255,255,.3);font-size:.78rem;gap:6px"> | |
| <i class="fas fa-film" style="font-size:1.8rem;opacity:.4"></i> | |
| <span>Preview appears after URL is pasted</span> | |
| </div> | |
| </div> | |
| <!-- Overlay Controls panel — exact movie recap style --> | |
| <div style="background:linear-gradient(135deg,#0e2233,#0a1f2e);padding:10px 12px"> | |
| <!-- Subtitle Position slider — top of controls --> | |
| <div style="margin-bottom:10px"> | |
| <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:3px"> | |
| <div style="font-size:.58rem;font-weight:700;text-transform:uppercase;color:#64748b">Subtitle Position</div> | |
| <span id="srt-sub-pos-lbl" style="font-size:.68rem;font-weight:700;color:#38bdf8">90%</span> | |
| </div> | |
| <input type="range" id="srt-sub-pos" min="5" max="95" value="90" | |
| oninput="document.getElementById('srt-sub-pos-lbl').textContent=this.value+'%';syncSrtSubPreview()" | |
| style="width:100%;accent-color:#38bdf8;cursor:pointer"> | |
| <div style="display:flex;justify-content:space-between;font-size:.58rem;color:#475569"><span>Top (5%)</span><span>Bottom (90%)</span></div> | |
| </div> | |
| <!-- Divider --> | |
| <div style="height:1px;background:rgba(255,255,255,.07);margin:0 0 10px"></div> | |
| <!-- CANVAS OVERLAYS: 4-col grid: Watermark | Logo | Blur | Gentle Zoom --> | |
| <div style="font-size:.62rem;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:#94a3b8;margin-bottom:8px;display:flex;align-items:center;gap:5px"> | |
| <i class="fas fa-layer-group" style="color:#a78bfa"></i>Canvas Overlays | |
| </div> | |
| <div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:5px;margin-bottom:10px"> | |
| <!-- Watermark --> | |
| <div style="background:rgba(251,191,36,.05);border:1.5px solid rgba(251,191,36,.18);border-radius:10px;padding:8px 4px;display:flex;flex-direction:column;align-items:center;gap:4px"> | |
| <div style="width:26px;height:26px;border-radius:7px;background:rgba(251,191,36,.12);display:flex;align-items:center;justify-content:center"> | |
| <i class="fas fa-stamp" style="color:rgba(251,191,36,.9);font-size:.8rem"></i> | |
| </div> | |
| <div style="font-size:.58rem;font-weight:700;color:rgba(251,191,36,.8);text-align:center">Watermark</div> | |
| <button id="srt-ov-wm" onclick="srtOvToggle('wm')" style="width:100%;padding:3px 0;font-size:.62rem;font-weight:700;font-family:var(--F);border-radius:6px;border:1.5px solid rgba(251,191,36,.35);background:rgba(251,191,36,.07);color:rgba(251,191,36,.8);cursor:pointer;transition:.2s">Off</button> | |
| </div> | |
| <!-- Logo --> | |
| <div style="background:rgba(52,211,153,.05);border:1.5px solid rgba(52,211,153,.18);border-radius:10px;padding:8px 4px;display:flex;flex-direction:column;align-items:center;gap:4px"> | |
| <div style="width:26px;height:26px;border-radius:7px;background:rgba(52,211,153,.12);display:flex;align-items:center;justify-content:center"> | |
| <i class="fas fa-image" style="color:rgba(52,211,153,.9);font-size:.8rem"></i> | |
| </div> | |
| <div style="font-size:.58rem;font-weight:700;color:rgba(52,211,153,.8);text-align:center">Logo</div> | |
| <button id="srt-ov-logo" onclick="srtOvToggle('logo')" style="width:100%;padding:3px 0;font-size:.62rem;font-weight:700;font-family:var(--F);border-radius:6px;border:1.5px solid rgba(52,211,153,.35);background:rgba(52,211,153,.07);color:rgba(52,211,153,.8);cursor:pointer;transition:.2s">Off</button> | |
| </div> | |
| <!-- Blur --> | |
| <div style="background:rgba(239,68,68,.04);border:1.5px solid rgba(252,165,165,.18);border-radius:10px;padding:8px 4px;display:flex;flex-direction:column;align-items:center;gap:4px"> | |
| <div style="width:26px;height:26px;border-radius:7px;background:rgba(239,68,68,.1);display:flex;align-items:center;justify-content:center"> | |
| <i class="fas fa-eraser" style="color:rgba(252,165,165,.9);font-size:.8rem"></i> | |
| </div> | |
| <div style="font-size:.58rem;font-weight:700;color:rgba(252,165,165,.8);text-align:center">Blur</div> | |
| <button id="srt-ov-blur" onclick="srtOvToggle('blur')" style="width:100%;padding:3px 0;font-size:.62rem;font-weight:700;font-family:var(--F);border-radius:6px;border:1.5px solid rgba(252,165,165,.35);background:rgba(239,68,68,.07);color:rgba(252,165,165,.8);cursor:pointer;transition:.2s">Off</button> | |
| </div> | |
| <!-- Gentle Zoom hidden --> | |
| <div style="display:none;background:rgba(139,92,246,.05);border:1.5px solid rgba(139,92,246,.18);border-radius:10px;padding:8px 4px;flex-direction:column;align-items:center;gap:4px"> | |
| <div style="width:26px;height:26px;border-radius:7px;background:rgba(139,92,246,.12);display:flex;align-items:center;justify-content:center"> | |
| <i class="fas fa-search-plus" style="color:rgba(167,139,250,.9);font-size:.8rem"></i> | |
| </div> | |
| <div style="font-size:.58rem;font-weight:700;color:rgba(167,139,250,.8);text-align:center">G.Zoom</div> | |
| <button id="srt-ov-zoom" onclick="srtOvToggle('zoom')" style="width:100%;padding:3px 0;font-size:.62rem;font-weight:700;font-family:var(--F);border-radius:6px;border:1.5px solid rgba(139,92,246,.35);background:rgba(139,92,246,.07);color:rgba(167,139,250,.8);cursor:pointer;transition:.2s">Off</button> | |
| </div> | |
| </div> | |
| <!-- Expandable panels --> | |
| <!-- Watermark panel --> | |
| <div id="srt-ov-wm-wrap" style="display:none;margin-bottom:8px;padding:10px 12px;background:rgba(251,191,36,.05);border:1px solid rgba(251,191,36,.22);border-radius:10px"> | |
| <div style="font-size:.58rem;font-weight:700;color:rgba(251,191,36,.7);letter-spacing:.06em;text-transform:uppercase;margin-bottom:6px"> | |
| <i class="fas fa-stamp" style="margin-right:4px"></i>Watermark Text | |
| </div> | |
| <input id="srt-wm-text" class="winp" placeholder="@YourPage သို့မဟုတ် Channel Name" maxlength="40" | |
| oninput="document.getElementById('srt-db-wm-txt').textContent=this.value||'Watermark'" | |
| style="width:100%;font-size:.8rem;background:rgba(255,255,255,.06);border-color:rgba(251,191,36,.3);color:#fef9c3;box-sizing:border-box"> | |
| <div style="font-size:.6rem;color:rgba(251,191,36,.4);margin-top:5px;display:flex;align-items:center;gap:4px"> | |
| <i class="fas fa-hand-pointer"></i>Preview canvas ပေါ်မှာ drag ပြီး position ချိန်ပါ | |
| </div> | |
| </div> | |
| <!-- Logo panel --> | |
| <div id="srt-ov-logo-wrap" style="display:none;margin-bottom:8px;padding:10px 12px;background:rgba(52,211,153,.05);border:1px solid rgba(52,211,153,.22);border-radius:10px"> | |
| <div style="font-size:.58rem;font-weight:700;color:rgba(52,211,153,.7);letter-spacing:.06em;text-transform:uppercase;margin-bottom:6px"> | |
| <i class="fas fa-image" style="margin-right:4px"></i>Logo Image | |
| </div> | |
| <label style="display:flex;align-items:center;gap:8px;background:rgba(255,255,255,.04);border:1px dashed rgba(52,211,153,.3);border-radius:8px;padding:8px 10px;cursor:pointer;transition:.2s"> | |
| <i class="fas fa-cloud-upload-alt" style="color:rgba(52,211,153,.7);font-size:1rem"></i> | |
| <span id="srt-logo-name" style="color:rgba(52,211,153,.55);font-size:.78rem;flex:1">Click to upload PNG / JPG</span> | |
| <input type="file" id="srt-logo-file" accept="image/*" style="display:none" onchange="onSrtLogoFile(this)"> | |
| </label> | |
| <div style="font-size:.6rem;color:rgba(52,211,153,.4);margin-top:5px;display:flex;align-items:center;gap:4px"> | |
| <i class="fas fa-hand-pointer"></i>Canvas ပေါ်မှာ drag / resize ပြီး position ချိန်ပါ | |
| </div> | |
| </div> | |
| <!-- Blur hint panel --> | |
| <div id="srt-ov-blur-hint" style="display:none;margin-bottom:8px;padding:10px 12px;background:rgba(239,68,68,.04);border:1px solid rgba(239,68,68,.2);border-radius:10px"> | |
| <div style="font-size:.6rem;color:rgba(252,165,165,.7);display:flex;align-items:center;gap:5px"> | |
| <i class="fas fa-hand-pointer"></i>Preview canvas ပေါ်မှာ blur zone ကို drag နှင့် resize လုပ်ပါ | |
| </div> | |
| </div> | |
| <!-- Zoom slider removed (now always visible below) --> | |
| <!-- Divider --> | |
| <div style="height:1px;background:rgba(255,255,255,.07);margin:4px 0 8px"></div> | |
| <!-- Flip | Color | Audio Boost — 3 buttons below canvas overlays, above subtitle settings --> | |
| <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:5px;margin-bottom:8px"> | |
| <button id="srt-btn-flip" onclick="srtToggleOpt('flip')" style="padding:6px 2px;font-size:.68rem;font-weight:700;font-family:var(--F);border-radius:7px;border:1.5px solid rgba(255,255,255,.12);background:rgba(255,255,255,.05);color:rgba(255,255,255,.55);cursor:pointer;transition:.2s"><i class="fas fa-arrows-alt-h"></i> Flip</button> | |
| <button id="srt-btn-col" onclick="srtToggleOpt('col')" style="padding:6px 2px;font-size:.68rem;font-weight:700;font-family:var(--F);border-radius:7px;border:1.5px solid rgba(255,255,255,.12);background:rgba(255,255,255,.05);color:rgba(255,255,255,.55);cursor:pointer;transition:.2s"><i class="fas fa-adjust"></i> Color</button> | |
| <button id="srt-ov-audio" onclick="srtOvToggle('audio')" style="padding:6px 2px;font-size:.68rem;font-weight:700;font-family:var(--F);border-radius:7px;border:1.5px solid rgba(59,130,246,.35);background:rgba(59,130,246,.07);color:rgba(96,165,250,.8);cursor:pointer;transition:.2s"><i class="fas fa-volume-up"></i> Audio</button> | |
| </div> | |
| <!-- Divider --> | |
| <div style="height:1px;background:rgba(255,255,255,.07);margin:4px 0 8px"></div> | |
| <!-- Zoom Factor slider hidden --> | |
| <!-- SUBTITLE SETTINGS label --> | |
| <div style="font-size:.62rem;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:#94a3b8;margin-bottom:8px;display:flex;align-items:center;gap:5px"> | |
| <i class="fas fa-closed-captioning" style="color:#60a5fa"></i>Subtitle Settings | |
| </div> | |
| <div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:8px"> | |
| <div style="background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:8px;padding:7px 9px"> | |
| <div style="font-size:.58rem;font-weight:700;text-transform:uppercase;color:#64748b;margin-bottom:4px">Color</div> | |
| <select id="srt-sub-color" onchange="syncSrtSubPreview()" style="width:100%;background:transparent;border:none;color:#e0f2fe;font-family:var(--F);font-size:.78rem;font-weight:500;outline:none;cursor:pointer"> | |
| <option value="white" selected>⬜ White</option> | |
| <option value="yellow">🟡 Yellow</option> | |
| <option value="cyan">🔵 Cyan</option> | |
| <option value="green">🟢 Green</option> | |
| <option value="orange">🟠 Orange</option> | |
| <option value="pink">🩷 Pink</option> | |
| <option value="red">🔴 Red</option> | |
| <option value="lime">💚 Lime</option> | |
| <option value="hotpink">💗 Hot Pink</option> | |
| <option value="gold">✨ Gold</option> | |
| <option value="violet">💜 Violet</option> | |
| <option value="deepskyblue">🩵 Sky Blue</option> | |
| <option value="coral">🪸 Coral</option> | |
| </select> | |
| </div> | |
| <div style="background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:8px;padding:7px 9px"> | |
| <div style="font-size:.58rem;font-weight:700;text-transform:uppercase;color:#64748b;margin-bottom:4px">Font Size</div> | |
| <select id="srt-sub-size" onchange="syncSrtSubPreview()" style="width:100%;background:transparent;border:none;color:#e0f2fe;font-family:var(--F);font-size:.78rem;font-weight:500;outline:none;cursor:pointer"> | |
| <option value="0.0391">XSmall</option> | |
| <option value="0.0547" selected>Small</option> | |
| <option value="0.0781">Medium</option> | |
| <option value="0.0938">Large</option> | |
| <option value="0.1172">XLarge</option> | |
| </select> | |
| </div> | |
| <div style="background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:8px;padding:7px 9px"> | |
| <div style="font-size:.58rem;font-weight:700;text-transform:uppercase;color:#64748b;margin-bottom:4px">Style</div> | |
| <select id="srt-sub-style" onchange="syncSrtSubPreview()" style="width:100%;background:transparent;border:none;color:#e0f2fe;font-family:var(--F);font-size:.78rem;font-weight:500;outline:none;cursor:pointer"> | |
| <option value="outline" selected>Outline</option> | |
| <option value="box">Box</option> | |
| <option value="shadow">Shadow</option> | |
| <option value="glow">Glow</option> | |
| <option value="stroke">Stroke</option> | |
| <option value="plain">Plain</option> | |
| </select> | |
| </div> | |
| <div style="background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:8px;padding:7px 9px"> | |
| <div style="font-size:.58rem;font-weight:700;text-transform:uppercase;color:#64748b;margin-bottom:4px">Aspect Ratio</div> | |
| <select id="srt-crop" onchange="syncSrtSubPreview()" style="width:100%;background:transparent;border:none;color:#e0f2fe;font-family:var(--F);font-size:.78rem;font-weight:500;outline:none;cursor:pointer"> | |
| <option value="original" selected>Original</option> | |
| <option value="9:16">9:16</option> | |
| <option value="16:9">16:9</option> | |
| <option value="1:1">1:1</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div><!-- /overlay controls panel --> | |
| </div><!-- /canvas card --> | |
| </div><!-- /ab --> | |
| </div><!-- /section-srt --> | |
| </div><!-- /app-screen --> | |
| <div id="toast"></div> | |
| <audio id="pva"></audio> | |
| <script> | |
| /* ══ STATE ══ */ | |
| let U='',COINS=0,ISADM=false; | |
| let OUT_URL='',OUT_CAP='',OUT_TAGS=''; | |
| let SSE=null,TICK=null,T0=0,COIN_POLL=null; | |
| let FLIP_ON=false,COL_ON=false,BGM_ON=false,SUB_ON=false; | |
| function toggleAdvanced(){ | |
| const w=document.getElementById('adv-wrap'); | |
| const ic=document.getElementById('adv-icon'); | |
| const open=w.style.display==='none'; | |
| w.style.display=open?'block':'none'; | |
| ic.style.transform=open?'rotate(90deg)':'rotate(0deg)'; | |
| } | |
| function toggleSub(){ | |
| SUB_ON=!SUB_ON; | |
| const btn=document.getElementById('btn-sub'); | |
| const wrap=document.getElementById('sub-wrap'); | |
| btn.textContent=SUB_ON?'On':'Off'; | |
| btn.className='wm-toggle'+(SUB_ON?' on':''); | |
| btn.style.borderColor=SUB_ON?'#38bdf8':''; | |
| wrap.style.display=SUB_ON?'block':'none'; | |
| // Sync canvas preview subtitle box | |
| _syncSubPreview(); | |
| } | |
| function _syncSubPreview(){ | |
| const box=document.getElementById('db-sub'); | |
| if(!box)return; | |
| if(SUB_ON){ | |
| const posVal=parseInt((document.getElementById('sub-pos')||{value:85}).value)||85; | |
| const col=(document.getElementById('sub-color')||{value:'white'}).value; | |
| const sty=(document.getElementById('sub-style')||{value:'outline'}).value; | |
| /* sizeVal = fraction of video height (e.g. 0.055 = Small) | |
| pxSize = sizeVal * ciR.height — same ciR as getBoxPct() watermark uses. | |
| Server converts: fontsize = round(sizeVal * play_res_y) */ | |
| const sizeFrac=parseFloat((document.getElementById('sub-size')||{value:0.0547}).value)||0.0547; | |
| const _cropSel=(document.getElementById('crop')||{value:'9:16'}).value; | |
| const _ci=document.getElementById('canvas-inner'); | |
| const _ciR=_ci?_ci.getBoundingClientRect():{width:300,height:300}; | |
| /* For 9:16 crop: canvas-inner may be landscape thumbnail height, but actual | |
| output is portrait. Use width*(1280/720) as effective height, capped at | |
| container height to avoid overflow. For other crops ciR.height is fine. */ | |
| let _effH; | |
| if(_cropSel==='9:16'){ | |
| // canvas-inner width is already crop-adjusted by applyCropPreview | |
| // effective 9:16 display height = width * (1280/720) | |
| const _vwrap=document.getElementById('canvas-video-wrap'); | |
| const _vwrapW=_vwrap?_vwrap.getBoundingClientRect().width:_ciR.width; | |
| _effH=_vwrapW*(1280/720); | |
| } else _effH=_ciR.height; | |
| const pxSize=Math.round(sizeFrac*_effH); | |
| const colMap={ | |
| 'white':'#fff','yellow':'#ffe066','cyan':'#67e8f9','green':'#4ade80', | |
| 'orange':'#fb923c','pink':'#f0abfc','red':'#f87171','lime':'#a3e635', | |
| 'hotpink':'#ff69b4','gold':'#ffd700','violet':'#ee82ee','deepskyblue':'#00bfff','coral':'#ff7f50' | |
| }; | |
| const fc=colMap[col]||'#fff'; | |
| /* Position: use crop-AR-aware display height. | |
| For 9:16: displayH = ciR.width * (1280/720) but clamp to ciR.height if smaller | |
| For others: displayH = ciR.height | |
| subtitle top = displayH * posVal/100 */ | |
| const _topPx=_effH*posVal/100; | |
| box.style.top=_topPx+'px'; | |
| box.style.bottom='auto'; | |
| box.style.transform='translateY(-50%)'; | |
| const span=box.querySelector('span'); | |
| if(span){ | |
| span.style.color=fc; | |
| span.style.fontSize=pxSize+'px'; | |
| span.style.fontWeight='700'; | |
| span.style.fontFamily="'Noto Sans Myanmar','Padauk',sans-serif"; | |
| span.style.lineHeight='1.25'; | |
| if(sty==='box'){ | |
| span.style.background='rgba(0,0,0,0.65)'; | |
| span.style.textShadow='none'; | |
| span.style.webkitTextStroke=''; | |
| } else if(sty==='shadow'){ | |
| span.style.background='transparent'; | |
| span.style.textShadow='3px 3px 8px rgba(0,0,0,.9),0 0 20px rgba(0,0,0,.8)'; | |
| span.style.webkitTextStroke=''; | |
| } else if(sty==='glow'){ | |
| span.style.background='transparent'; | |
| span.style.textShadow=`0 0 8px ${fc},0 0 20px ${fc},0 0 40px ${fc},2px 2px 4px #000`; | |
| span.style.webkitTextStroke=''; | |
| } else if(sty==='stroke'){ | |
| span.style.background='transparent'; | |
| span.style.textShadow='none'; | |
| span.style.webkitTextStroke='2px #000'; | |
| } else { | |
| span.style.background='transparent'; | |
| span.style.textShadow='0 0 4px #000,0 1px 6px #000,-1px -1px 0 #000,1px 1px 0 #000'; | |
| span.style.webkitTextStroke=''; | |
| } | |
| } | |
| box.style.display='block'; | |
| } else { | |
| box.style.display='none'; | |
| box.style.transform=''; | |
| } | |
| } | |
| let CACHE_KEY=''; | |
| let URL_DEBOUNCE=null; | |
| const OV={sub:{on:false}}; // Only subtitles | |
| const EV={ | |
| my:[ | |
| {id:'my-MM-ThihaNeural', name:'Thiha (Male)'}, | |
| {id:'my-MM-NilarNeural', name:'Nilar (Female)'}, | |
| {id:'fr-FR-RemyMultilingualNeural', name:'Remy (Male)'}, | |
| // ── Multilingual Neural (support Myanmar & all languages) ── | |
| {id:'en-US-AndrewMultilingualNeural', name:'Andrew (Male)'}, | |
| {id:'en-US-AvaMultilingualNeural', name:'Ava (Female)'}, | |
| {id:'en-US-BrianMultilingualNeural', name:'Brian (Male)'}, | |
| {id:'en-US-EmmaMultilingualNeural', name:'Emma (Female)'}, | |
| {id:'en-US-JennyMultilingualNeural', name:'Jenny (Female)'}, | |
| {id:'en-US-RyanMultilingualNeural', name:'Ryan (Male)'}, | |
| {id:'zh-CN-XiaoxiaoMultilingualNeural', name:'Xiaoxiao (Female)'}, | |
| {id:'zh-CN-YunxiMultilingualNeural', name:'Yunxi (Male)'}, | |
| {id:'de-DE-FlorianMultilingualNeural', name:'Florian (Male)'}, | |
| {id:'de-DE-SeraphinaMultilingualNeural',name:'Seraphina (Female)'}, | |
| {id:'fr-FR-VivienneMultilingualNeural', name:'Vivienne (Female)'} | |
| ], | |
| th:[ | |
| {id:'th-TH-NiwatNeural', name:'Niwat (Male)'}, | |
| {id:'th-TH-PremwadeeNeural', name:'Premwadee (Female)'}, | |
| {id:'th-TH-AcharaNeural', name:'Achara (Female)'}, | |
| // ── Multilingual Neural (support Thai & all languages) ── | |
| {id:'en-US-AndrewMultilingualNeural', name:'Andrew (Male)'}, | |
| {id:'en-US-AvaMultilingualNeural', name:'Ava (Female)'}, | |
| {id:'en-US-BrianMultilingualNeural', name:'Brian (Male)'}, | |
| {id:'en-US-EmmaMultilingualNeural', name:'Emma (Female)'}, | |
| {id:'en-US-JennyMultilingualNeural', name:'Jenny (Female)'}, | |
| {id:'en-US-RyanMultilingualNeural', name:'Ryan (Male)'}, | |
| {id:'zh-CN-XiaoxiaoMultilingualNeural', name:'Xiaoxiao (Female)'}, | |
| {id:'zh-CN-YunxiMultilingualNeural', name:'Yunxi (Male)'}, | |
| {id:'de-DE-FlorianMultilingualNeural', name:'Florian (Male)'}, | |
| {id:'de-DE-SeraphinaMultilingualNeural',name:'Seraphina (Female)'}, | |
| {id:'fr-FR-RemyMultilingualNeural', name:'Remy (Male)'}, | |
| {id:'fr-FR-VivienneMultilingualNeural', name:'Vivienne (Female)'} | |
| ], | |
| en:[ | |
| {id:'en-US-GuyNeural', name:'Guy – US (Male)'}, | |
| {id:'en-US-JennyNeural', name:'Jenny – US (Female)'}, | |
| {id:'en-US-AriaNeural', name:'Aria – US (Female)'}, | |
| {id:'en-US-DavisNeural', name:'Davis – US (Male)'}, | |
| {id:'en-US-JasonNeural', name:'Jason – US (Male)'}, | |
| {id:'en-US-SaraNeural', name:'Sara – US (Female)'}, | |
| {id:'en-US-TonyNeural', name:'Tony – US (Male)'}, | |
| {id:'en-US-AndrewNeural', name:'Andrew – US (Male)'}, | |
| {id:'en-US-EmmaNeural', name:'Emma – US (Female)'}, | |
| {id:'en-GB-RyanNeural', name:'Ryan – GB (Male)'}, | |
| {id:'en-GB-SoniaNeural', name:'Sonia – GB (Female)'}, | |
| {id:'en-GB-LibbyNeural', name:'Libby – GB (Female)'}, | |
| {id:'en-AU-NatashaNeural', name:'Natasha – AU (Female)'}, | |
| {id:'en-AU-WilliamNeural', name:'William – AU (Male)'}, | |
| {id:'en-IN-NeerjaNeural', name:'Neerja – IN (Female)'}, | |
| {id:'en-IN-PrabhatNeural', name:'Prabhat – IN (Male)'}, | |
| // ── Multilingual Neural (all-language support) ── | |
| {id:'en-US-AndrewMultilingualNeural', name:'Andrew (Male)'}, | |
| {id:'en-US-AvaMultilingualNeural', name:'Ava (Female)'}, | |
| {id:'en-US-BrianMultilingualNeural', name:'Brian (Male)'}, | |
| {id:'en-US-EmmaMultilingualNeural', name:'Emma (Female)'}, | |
| {id:'en-US-JennyMultilingualNeural', name:'Jenny (Female)'}, | |
| {id:'en-US-RyanMultilingualNeural', name:'Ryan (Male)'}, | |
| {id:'zh-CN-XiaoxiaoMultilingualNeural', name:'Xiaoxiao (Female)'}, | |
| {id:'zh-CN-YunxiMultilingualNeural', name:'Yunxi (Male)'}, | |
| {id:'de-DE-FlorianMultilingualNeural', name:'Florian (Male)'}, | |
| {id:'de-DE-SeraphinaMultilingualNeural',name:'Seraphina (Female)'}, | |
| {id:'fr-FR-RemyMultilingualNeural', name:'Remy (Male)'}, | |
| {id:'fr-FR-VivienneMultilingualNeural', name:'Vivienne (Female)'} | |
| ] | |
| }; | |
| let GV=[]; | |
| /* ══ INIT ══ */ | |
| addEventListener('DOMContentLoaded',()=>{ | |
| // Google button visibility | |
| fetch('/api/auth/google_enabled').then(r=>r.json()).then(d=>{ | |
| if(d.enabled) document.getElementById('google-wrap').style.display='block'; | |
| }).catch(()=>{}); | |
| // Enter key on login | |
| document.addEventListener('keydown',e=>{ | |
| if(e.key==='Enter'&&document.getElementById('login-screen').style.display!=='none'){ | |
| if(document.getElementById('pane-li').style.display!=='none') doLogin(); | |
| else doReg(); | |
| } | |
| }); | |
| // Google OAuth callback | |
| const sp=new URLSearchParams(location.search); | |
| if(sp.get('auth')==='google'){ | |
| const u=sp.get('u'),n=sp.get('n'),c=parseInt(sp.get('c')||'0'),a=sp.get('a')==='1'; | |
| if(u){ | |
| sessionStorage.setItem('recap_user',JSON.stringify({u,coins:c,is_admin:a,name:n})); | |
| history.replaceState({},'','/'); | |
| eApp(u,c,a,n); return; | |
| } | |
| } else if(sp.get('auth_error')){ | |
| history.replaceState({},'','/'); | |
| sam('❌ Google login failed: '+sp.get('auth_error'),false); | |
| } | |
| // Session restore | |
| const saved=sessionStorage.getItem('recap_user'); | |
| if(saved){try{const s=JSON.parse(saved);eApp(s.u,s.coins,s.is_admin,s.name);return;}catch{}} | |
| renderV(); | |
| window.addEventListener('resize',()=>{const v=document.getElementById('pvid');if(v.src&&v.videoWidth)syncCanvasInner(v);}); | |
| fetch('/api/gemini_voices').then(r=>r.ok?r.json():null).then(d=>{if(d?.ok){GV=d.voices;if(document.getElementById('engine').value==='gemini')renderV();}}).catch(()=>{}); | |
| initDrag(); | |
| initSrtDrag(); | |
| }); | |
| /* ══ LOGIN ══ */ | |
| function stab(t){['li','re'].forEach(x=>{document.getElementById('tab-'+x).classList.toggle('on',x===t);document.getElementById('pane-'+x).style.display=x===t?'':'none';});document.getElementById('am').style.display='none';} | |
| function sam(msg,ok){const e=document.getElementById('am');e.textContent=msg;e.className='am '+(ok?'am-ok':'am-err');e.style.display='block';} | |
| async function doLogin(){ | |
| const u=document.getElementById('l-u').value.trim(),p=document.getElementById('l-p').value; | |
| if(!u){sam('❌ Username required',false);return;} | |
| const btn=document.querySelector('#pane-li .bp'); | |
| if(btn){btn.disabled=true;btn.textContent='⏳ နေပါ...';} | |
| try{ | |
| const r=await fetch('/api/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:u,password:p})}); | |
| const d=await r.json(); | |
| if(d.ok){ | |
| sessionStorage.setItem('recap_user',JSON.stringify({u,coins:d.coins,is_admin:d.is_admin})); | |
| eApp(u,d.coins,d.is_admin); | |
| } else { | |
| sam(d.msg||'❌ Login failed',false); | |
| if(btn){btn.disabled=false;btn.textContent='Login';} | |
| } | |
| } catch{ | |
| sam('❌ Connection error',false); | |
| if(btn){btn.disabled=false;btn.textContent='Login';} | |
| } | |
| } | |
| async function doReg(){ | |
| const u=document.getElementById('r-u').value.trim(),p=document.getElementById('r-p').value; | |
| const btn=document.querySelector('#pane-re .bp'); | |
| if(btn){btn.disabled=true;btn.textContent='⏳ နေပါ...';} | |
| try{ | |
| const r=await fetch('/api/register',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:u,password:p})}); | |
| const d=await r.json(); | |
| if(d.ok){ | |
| sam('✅ '+d.username+' created!',true); | |
| setTimeout(()=>{sessionStorage.setItem('recap_user',JSON.stringify({u:d.username,coins:d.coins,is_admin:false}));eApp(d.username,d.coins,false);},700); | |
| } else { | |
| sam(d.msg||'❌ Failed',false); | |
| if(btn){btn.disabled=false;btn.textContent='Register';} | |
| } | |
| } catch{ | |
| sam('❌ Connection error',false); | |
| if(btn){btn.disabled=false;btn.textContent='Register';} | |
| } | |
| } | |
| async function fetchAndUpdateCoins(){ | |
| if(!U||ISADM)return; | |
| try{ | |
| const r=await fetch('/api/coins?username='+encodeURIComponent(U)+'&_='+Date.now()); | |
| const d=await r.json(); | |
| if(d.ok){ | |
| if(d.coins!==COINS){ | |
| const diff=d.coins-COINS; | |
| updC(d.coins); | |
| // Update sessionStorage so refresh shows correct coins | |
| try{ | |
| const s=JSON.parse(sessionStorage.getItem('recap_user')||'{}'); | |
| s.coins=d.coins; | |
| sessionStorage.setItem('recap_user',JSON.stringify(s)); | |
| }catch(e){} | |
| if(diff>0)toast('🎉 +'+diff+' Coins ရောက်ပြီ! လက်ကျန်: '+d.coins); | |
| } | |
| } | |
| }catch(e){} | |
| } | |
| function startCoinPoll(){ | |
| if(COIN_POLL)clearInterval(COIN_POLL); | |
| fetchAndUpdateCoins(); // immediate check on login | |
| COIN_POLL=setInterval(fetchAndUpdateCoins,5000); | |
| } | |
| function stopCoinPoll(){if(COIN_POLL){clearInterval(COIN_POLL);COIN_POLL=null;}} | |
| function eApp(u,coins,isAdm,displayName){ | |
| U=u;COINS=coins;ISADM=isAdm; | |
| document.getElementById('login-screen').style.display='none'; | |
| document.getElementById('app-screen').style.display='block'; | |
| const dn=displayName||u; | |
| updC(coins); | |
| document.getElementById('dun').textContent=dn; | |
| document.getElementById('drl').textContent=isAdm?'👑 Admin':'Member'; | |
| document.getElementById('dav').textContent=dn[0].toUpperCase(); | |
| document.getElementById('dcoins').textContent=isAdm?'∞':coins; | |
| fetch('/api/config').then(r=>r.json()).then(d=>{if(d.admin_tg)window._ADMIN_TG=d.admin_tg;}).catch(()=>{}); | |
| if(isAdm){document.getElementById('admp').style.display='block';document.getElementById('adm-dlink').style.display='flex';document.getElementById('cpill').style.display='none';document.getElementById('dcoins').textContent='∞';} | |
| else{startCoinPoll();} | |
| renderV(); | |
| window.addEventListener('resize',()=>{const v=document.getElementById('pvid');if(v.src&&v.videoWidth)syncCanvasInner(v);}); | |
| fetch('/api/gemini_voices').then(r=>r.ok?r.json():null).then(d=>{if(d?.ok){GV=d.voices;if(document.getElementById('engine').value==='gemini')renderV();}}).catch(()=>{}); | |
| initDrag(); | |
| } | |
| function doLogout(){ | |
| stopCoinPoll(); | |
| U='';COINS=0;ISADM=false; | |
| sessionStorage.removeItem('recap_user'); | |
| document.getElementById('app-screen').style.display='none'; | |
| document.getElementById('login-screen').style.display='flex'; | |
| cdw(); | |
| } | |
| /* ══ PAYMENT MODAL JS ══ */ | |
| let _pkgs=[], _pkgsMmk=[], _pkgsThb=[], _selPkg=null, _slipB64=null, _payInited=false, _selMeth=null, _payInfo={}, _curCurrency='mmk'; | |
| async function openPay(){ | |
| document.getElementById('pov').classList.add('on'); | |
| if(!_payInited){ await initPayModal(); _payInited=true; } | |
| } | |
| function closePay(){ | |
| document.getElementById('pov').classList.remove('on'); | |
| // reset to step 1 for next open | |
| goPayStep(1); | |
| } | |
| function showPayTab(t){ | |
| document.getElementById('ptab-buy').classList.toggle('on',t==='buy'); | |
| document.getElementById('ptab-his').classList.toggle('on',t==='his'); | |
| document.getElementById('ptab-buy-content').style.display=t==='buy'?'':'none'; | |
| document.getElementById('ptab-his-content').style.display=t==='his'?'':'none'; | |
| if(t==='his') loadPayHistory(); | |
| } | |
| async function initPayModal(){ | |
| const FB_MMK=[ | |
| {coins:10,price:'12,000 MMK',price_thb:100,desc:'Process 10 ကြိမ်'}, | |
| {coins:20,price:'24,000 MMK',price_thb:200,desc:'Process 20 ကြိမ်'}, | |
| {coins:30,price:'36,000 MMK',price_thb:300,desc:'Process 30 ကြိမ်'}, | |
| {coins:60,price:'72,000 MMK',price_thb:600,desc:'Process 60 ကြိမ်'}, | |
| ]; | |
| const FB_THB=[ | |
| {coins:10,price:'100 THB',price_thb:100,desc:'Process 10 ကြိမ်'}, | |
| {coins:20,price:'200 THB',price_thb:200,desc:'Process 20 ကြိမ်'}, | |
| {coins:30,price:'300 THB',price_thb:300,desc:'Process 30 ကြိမ်'}, | |
| {coins:60,price:'600 THB',price_thb:600,desc:'Process 60 ကြိမ်'}, | |
| ]; | |
| try{ | |
| const r=await fetch('/api/payment/packages').then(r=>r.json()); | |
| // Map packages from API — add price_thb field | |
| if(r.packages&&r.packages.length){ | |
| _pkgsMmk=r.packages.map(p=>({coins:p.coins,price:p.price,price_thb:p.price_thb||0,desc:p.desc})); | |
| _pkgsThb=r.packages.map(p=>({coins:p.coins,price:(p.price_thb||'—')+' THB',price_thb:p.price_thb||0,desc:p.desc})); | |
| } else { | |
| _pkgsMmk=FB_MMK; _pkgsThb=FB_THB; | |
| } | |
| _payInfo={ | |
| kbz_name:r.kbz_name||'—', kbz_number:r.kbz_number||'—', | |
| kbz_qr_url:r.kbz_qr_url||'', | |
| scb_name:r.scb_name||'—', scb_number:r.scb_number||'—', | |
| promptpay:r.promptpay||'—', | |
| truemoney_name:r.truemoney_name||'—', truemoney_number:r.truemoney_number||'—', | |
| truemoney_qr_url:r.truemoney_qr_url||'' | |
| }; | |
| }catch(e){ | |
| _pkgsMmk=FB_MMK; _pkgsThb=FB_THB; | |
| _payInfo={kbz_name:'—',kbz_number:'—',kbz_qr_url:'',scb_name:'—',scb_number:'—',promptpay:'—',truemoney_name:'—',truemoney_number:'—',truemoney_qr_url:''}; | |
| } | |
| renderPkgs('mmk'); | |
| } | |
| function renderPkgs(currency){ | |
| _curCurrency=currency||_curCurrency; | |
| _pkgs=_curCurrency==='thb'?_pkgsThb:_pkgsMmk; | |
| _selPkg=null; | |
| document.getElementById('buy-now-btn').disabled=true; | |
| // update currency tab highlight | |
| const mmkBtn=document.getElementById('ctab-mmk'); | |
| const thbBtn=document.getElementById('ctab-thb'); | |
| if(mmkBtn&&thbBtn){ | |
| const onS='flex:1;padding:10px 8px;border-radius:12px;border:2px solid rgba(139,92,246,.5);background:rgba(124,58,237,.2);color:#c4b5fd;font-family:var(--F);font-size:.82rem;font-weight:800;cursor:pointer;transition:.2s;display:flex;align-items:center;justify-content:center;gap:6px'; | |
| const offS='flex:1;padding:10px 8px;border-radius:12px;border:2px solid rgba(255,255,255,.08);background:rgba(255,255,255,.04);color:rgba(255,255,255,.35);font-family:var(--F);font-size:.82rem;font-weight:800;cursor:pointer;transition:.2s;display:flex;align-items:center;justify-content:center;gap:6px'; | |
| mmkBtn.style.cssText=_curCurrency==='mmk'?onS:offS; | |
| thbBtn.style.cssText=_curCurrency==='thb'?onS:offS; | |
| mmkBtn.innerHTML='<span style="font-size:1.1rem">🇲🇲</span> MMK'; | |
| thbBtn.innerHTML='<span style="font-size:1.1rem">🇹🇭</span> THB'; | |
| } | |
| const icons=['🥉','🥈','🥇','💎']; | |
| const tiers=['Starter','Basic','Pro','Unlimited']; | |
| const pBg =['rgba(255,255,255,.07)','rgba(139,92,246,.2)','rgba(251,146,60,.15)','rgba(251,191,36,.15)']; | |
| const pClr =['rgba(255,255,255,.75)','#c4b5fd','#fb923c','#fbbf24']; | |
| document.getElementById('ppkgs').innerHTML=_pkgs.map((p,i)=>` | |
| <div class="ppkg${i===1?' pop':''}" id="ppkg${i}" onclick="selPkg(${i})"> | |
| <span class="ppkg-icon">${icons[i]||'🪙'}</span> | |
| <div style="font-size:.58rem;font-weight:800;text-transform:uppercase;letter-spacing:.1em;color:rgba(255,255,255,.28);margin-bottom:5px">${tiers[i]}</div> | |
| <div class="ppkg-c">${p.coins}</div> | |
| <div class="ppkg-u">Coins</div> | |
| <div class="ppkg-p" style="background:${pBg[i]};color:${pClr[i]}">${p.price}</div> | |
| <div style="font-size:.6rem;color:rgba(255,255,255,.28);margin-top:5px">${p.desc||''}</div> | |
| </div>`).join(''); | |
| } | |
| function selPkg(i){ | |
| document.querySelectorAll('.ppkg').forEach(e=>e.classList.remove('sel')); | |
| document.getElementById('ppkg'+i).classList.add('sel'); | |
| _selPkg=_pkgs[i]; | |
| document.getElementById('buy-now-btn').disabled=false; | |
| } | |
| // Step navigation | |
| function goPayStep(n){ | |
| document.querySelectorAll('.pay-step').forEach(e=>e.classList.remove('on')); | |
| document.getElementById('pay-step-'+n).classList.add('on'); | |
| } | |
| function goPayStep2(){ | |
| if(!_selPkg)return; | |
| _selMeth=null; | |
| document.querySelectorAll('.pmeth').forEach(e=>e.classList.remove('sel')); | |
| document.getElementById('pay-info-box').style.display='none'; | |
| goPayStep(2); | |
| } | |
| function goPayStep3(){ | |
| if(!_selMeth)return; | |
| _slipB64=null; | |
| // Reset slip UI | |
| document.getElementById('pslip-prev').style.display='none'; | |
| document.getElementById('pslip-icon').style.display='block'; | |
| document.getElementById('pslip-txt').style.display='block'; | |
| document.getElementById('pslip-input').value=''; | |
| document.getElementById('psubmit').disabled=true; | |
| goPayStep(3); | |
| } | |
| function selMeth(m){ | |
| _selMeth=m; | |
| document.querySelectorAll('.pmeth').forEach(e=>e.classList.remove('sel')); | |
| document.getElementById('pmeth-'+m).classList.add('sel'); | |
| // Build info box | |
| let rows=''; | |
| if(m==='kbz'){ | |
| const mmkAmt=_selPkg?(_selPkg.price||''):''; | |
| const kbzCoins=_selPkg?_selPkg.coins:0; | |
| const kbzQrUrl=`/api/payment/kbz_qr?amount=${kbzCoins*1000}`; | |
| rows=`<div class="pinfo-row"><span class="pinfo-lbl">📱 KBZ Pay</span><span></span></div> | |
| <div class="pinfo-row"><span class="pinfo-lbl">အမည် / Name</span><div style="display:flex;gap:8px;align-items:center"><span class="pinfo-val" id="pi-name">${_payInfo.kbz_name}</span><button class="pcopy" onclick="pcopy2('pi-name')">Copy</button></div></div> | |
| <div class="pinfo-row"><span class="pinfo-lbl">ဖုန်းနံပါတ်</span><div style="display:flex;gap:8px;align-items:center"><span class="pinfo-val" id="pi-num">${_payInfo.kbz_number}</span><button class="pcopy" onclick="pcopy2('pi-num')">Copy</button></div></div> | |
| ${_payInfo.kbz_qr_url||kbzQrUrl?`<div style="text-align:center;margin:10px 0"><img src="${_payInfo.kbz_qr_url||kbzQrUrl}" alt="KBZ Pay QR" style="width:180px;height:180px;border-radius:12px;border:2px solid rgba(255,255,255,.15)" onerror="this.style.display='none'"><div style="font-size:.68rem;color:rgba(255,255,255,.4);margin-top:4px">KBZ Pay QR Scan — ${mmkAmt}</div></div>`:''}`; | |
| } else if(m==='wave'){ | |
| rows=`<div class="pinfo-row"><span class="pinfo-lbl">🌊 Wave Money</span><span></span></div> | |
| <div class="pinfo-row"><span class="pinfo-lbl">Name</span><div style="display:flex;gap:8px;align-items:center"><span class="pinfo-val" id="pi-name">${_payInfo.kbz_name}</span><button class="pcopy" onclick="pcopy2('pi-name')">Copy</button></div></div> | |
| <div class="pinfo-row"><span class="pinfo-lbl">Phone</span><div style="display:flex;gap:8px;align-items:center"><span class="pinfo-val" id="pi-num">${_payInfo.kbz_number}</span><button class="pcopy" onclick="pcopy2('pi-num')">Copy</button></div></div>`; | |
| } else if(m==='scb'){ | |
| rows=`<div class="pinfo-row"><span class="pinfo-lbl">🏦 SCB Bank</span><span></span></div> | |
| <div class="pinfo-row"><span class="pinfo-lbl">ชื่อ / Name</span><div style="display:flex;gap:8px;align-items:center"><span class="pinfo-val" id="pi-name">${_payInfo.scb_name}</span><button class="pcopy" onclick="pcopy2('pi-name')">Copy</button></div></div> | |
| <div class="pinfo-row"><span class="pinfo-lbl">Account No.</span><div style="display:flex;gap:8px;align-items:center"><span class="pinfo-val" id="pi-num">${_payInfo.scb_number}</span><button class="pcopy" onclick="pcopy2('pi-num')">Copy</button></div></div>`; | |
| } else if(m==='prompt'){ | |
| const thbAmt=_selPkg?(_selPkg.price_thb||''):''; | |
| const qrUrl=thbAmt?`/api/payment/promptpay_qr?amount=${thbAmt}`:''; | |
| rows=`<div class="pinfo-row"><span class="pinfo-lbl">⚡ PromptPay</span><span></span></div> | |
| <div class="pinfo-row"><span class="pinfo-lbl">เบอร์โทร</span><div style="display:flex;gap:8px;align-items:center"><span class="pinfo-val" id="pi-num">${_payInfo.promptpay}</span><button class="pcopy" onclick="pcopy2('pi-num')">Copy</button></div></div> | |
| ${qrUrl?`<div style="text-align:center;margin:10px 0"><img src="${qrUrl}" alt="PromptPay QR" style="width:180px;height:180px;border-radius:12px;border:2px solid rgba(255,255,255,.15)" onerror="this.style.display='none'"><div style="font-size:.68rem;color:rgba(255,255,255,.4);margin-top:4px">QR Scan — ${thbAmt} ฿</div></div>`:''}`; | |
| } else if(m==='truemoney'){ | |
| const thbAmtTM=_selPkg?(_selPkg.price_thb||''):''; | |
| const tmQrUrl=thbAmtTM?`/api/payment/truemoney_qr?amount=${thbAmtTM}`:''; | |
| rows=`<div class="pinfo-row"><span class="pinfo-lbl">💛 TrueMoney Wallet</span><span></span></div> | |
| <div class="pinfo-row"><span class="pinfo-lbl">ชื่อ / Name</span><div style="display:flex;gap:8px;align-items:center"><span class="pinfo-val" id="pi-name">${_payInfo.truemoney_name}</span><button class="pcopy" onclick="pcopy2('pi-name')">Copy</button></div></div> | |
| <div class="pinfo-row"><span class="pinfo-lbl">เบอร์โทร</span><div style="display:flex;gap:8px;align-items:center"><span class="pinfo-val" id="pi-num">${_payInfo.truemoney_number}</span><button class="pcopy" onclick="pcopy2('pi-num')">Copy</button></div></div> | |
| ${(_payInfo.truemoney_qr_url||tmQrUrl)?`<div style="text-align:center;margin:10px 0"><img src="${_payInfo.truemoney_qr_url||tmQrUrl}" alt="TrueMoney QR" style="width:180px;height:180px;border-radius:12px;border:2px solid rgba(255,255,255,.15)" onerror="this.style.display='none'"><div style="font-size:.68rem;color:rgba(255,255,255,.4);margin-top:4px">TrueMoney QR Scan — ${thbAmtTM} ฿</div></div>`:''}`; | |
| } | |
| // Show selected package price | |
| rows+=`<div class="pinfo-row" style="margin-top:4px"><span class="pinfo-lbl">💰 ชำระ / Amount</span><span class="pinfo-val" style="color:#fbbf24">${_selPkg?_selPkg.price:'—'}</span></div>`; | |
| document.getElementById('pinfo-box-inner').innerHTML=rows; | |
| document.getElementById('pay-info-box').style.display='block'; | |
| } | |
| function pcopy2(id){ | |
| const el=document.getElementById(id); | |
| if(!el)return; | |
| navigator.clipboard.writeText(el.textContent).then(()=>toast('✅ Copied!')); | |
| } | |
| function handlePSlip(inp){ | |
| const f=inp.files[0]; if(!f) return; | |
| if(f.size>5*1024*1024){toast('❌ ပုံဆိုဒ် 5MB မကြီးပါနှင့်');return;} | |
| const rd=new FileReader(); | |
| rd.onload=e=>{ | |
| _slipB64=e.target.result; | |
| const img=document.getElementById('pslip-prev'); | |
| img.src=_slipB64; img.style.display='block'; | |
| document.getElementById('pslip-icon').style.display='none'; | |
| document.getElementById('pslip-txt').style.display='none'; | |
| document.getElementById('psubmit').disabled=false; | |
| }; | |
| rd.readAsDataURL(f); | |
| } | |
| function checkPReady(){ | |
| const btn=document.getElementById('psubmit'); | |
| const ready=_selPkg&&_slipB64; | |
| btn.disabled=!ready; | |
| } | |
| async function submitPay(){ | |
| if(!_selPkg||!_slipB64) return; | |
| const btn=document.getElementById('psubmit'); | |
| btn.disabled=true; btn.textContent='⏳ တင်နေသည်…'; | |
| const methLabel={'kbz':'KBZ Pay','wave':'Wave Money','scb':'SCB Bank','prompt':'PromptPay','truemoney':'TrueMoney Wallet'}; | |
| const priceLabel=_selPkg.price + ((_selMeth==='scb'||_selMeth==='prompt')?' (Thai Baht)':''); | |
| try{ | |
| const r=await fetch('/api/payment/submit',{ | |
| method:'POST',headers:{'Content-Type':'application/json'}, | |
| body:JSON.stringify({username:U,coins:_selPkg.coins,price:priceLabel, | |
| method:methLabel[_selMeth]||_selMeth||'—',slip_image:_slipB64}) | |
| }).then(r=>r.json()); | |
| if(r.ok){ | |
| toast('✅ '+r.msg); | |
| setTimeout(()=>{closePay();setTimeout(()=>{openPay();showPayTab('his');},300);},1500); | |
| } else { toast('❌ '+(r.msg||'Failed')); btn.disabled=false; btn.textContent='💰 Payment တင်ပေးပါ'; } | |
| }catch(e){ toast('❌ Error: '+e.message); btn.disabled=false; btn.textContent='💰 Payment တင်ပေးပါ'; } | |
| } | |
| async function loadPayHistory(){ | |
| const el=document.getElementById('ph-list'); | |
| if(!U){el.innerHTML='<div class="ph-empty">⚠️ Login ဦးဝင်ပါ</div>';return;} | |
| el.innerHTML='<div class="ph-empty">⏳ Loading…</div>'; | |
| try{ | |
| const resp=await fetch(`/api/payment/history?username=${encodeURIComponent(U)}`); | |
| if(!resp.ok){el.innerHTML=`<div class="ph-empty">❌ Server error ${resp.status}</div>`;return;} | |
| const r=await resp.json(); | |
| if(!r.ok||!r.payments?.length){ | |
| el.innerHTML='<div class="ph-empty">📭 Payment မှတ်တမ်း မရှိသေးပါ</div>'; return; | |
| } | |
| const lbl={pending:'⏳ Pending',approved:'✅ Approved',rejected:'❌ Rejected'}; | |
| el.innerHTML=[...r.payments].reverse().map(p=>` | |
| <div class="ph-card"> | |
| <div class="ph-top"> | |
| <div> | |
| <div class="ph-coins">🪙 ${p.coins} Coins</div> | |
| <div class="ph-meta" style="margin-top:3px">${p.price}</div> | |
| </div> | |
| <span class="ph-badge ${p.status}">${lbl[p.status]||p.status}</span> | |
| </div> | |
| <div class="ph-meta">🆔 ${p.id} · ${p.created_at.slice(0,16).replace('T',' ')}</div> | |
| ${p.admin_note?`<div style="margin-top:6px;font-size:.72rem;color:rgba(255,255,255,.35);padding:6px 10px;background:rgba(255,255,255,.04);border-radius:7px">📝 ${p.admin_note}</div>`:''} | |
| </div>`).join(''); | |
| }catch(e){ el.innerHTML=`<div class="ph-empty">❌ ${e.message}</div>`; } | |
| } | |
| function pcopy(id){ | |
| navigator.clipboard.writeText(document.getElementById(id).textContent) | |
| .then(()=>toast('✅ Copied!')); | |
| } | |
| /* ══ DRAWER ══ */ | |
| function odw(){document.getElementById('dcoins').textContent=ISADM?'∞':COINS;document.getElementById('dov').classList.add('on');} | |
| function cdw(){document.getElementById('dov').classList.remove('on');} | |
| function scrollToAdmin(){document.getElementById('admp').scrollIntoView({behavior:'smooth'});} | |
| function updC(n){if(n===-1||ISADM)return;COINS=n;document.getElementById('hcoins').textContent=n;document.getElementById('dcoins').textContent=n;} | |
| /* ══ URL INPUT — auto preview with debounce ══ */ | |
| function onUrl(v){ | |
| const b=document.getElementById('ubadge'); | |
| b.style.display='none';CACHE_KEY=''; | |
| if(!v)return; | |
| const vl=v.toLowerCase(); | |
| if(vl.includes('youtube.com')||vl.includes('youtu.be')){b.textContent='YouTube';b.className='ubadge b-yt';b.style.display='';} | |
| else if(vl.includes('tiktok.com')){b.textContent='TikTok';b.className='ubadge b-tt';b.style.display='';} | |
| else if(vl.includes('instagram.com')){b.textContent='Instagram';b.className='ubadge b-ig';b.style.display='';} | |
| else if(vl.includes('facebook.com')||vl.includes('fb.watch')){b.textContent='Facebook';b.className='ubadge b-fb';b.style.display='';} | |
| if(URL_DEBOUNCE)clearTimeout(URL_DEBOUNCE); | |
| if(v.startsWith('http')){ | |
| URL_DEBOUNCE=setTimeout(()=>autoPreview(v),1200); | |
| } | |
| } | |
| async function autoPreview(url){ | |
| const loading=document.getElementById('prev-loading'); | |
| loading.classList.add('on'); | |
| // Reset both video and thumb | |
| const vid=document.getElementById('pvid'); | |
| const thumb=document.getElementById('pthumb'); | |
| vid.style.display='none'; vid.src=''; | |
| thumb.style.display='none'; thumb.src=''; | |
| try{ | |
| const r=await fetch('/api/thumb',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url})}); | |
| if(!r.ok)throw new Error('Server error '+r.status); | |
| const d=await r.json(); | |
| if(d.ok){ | |
| thumb.src=d.url; | |
| thumb.style.display='block'; | |
| document.getElementById('canvas-ph').style.display='none'; | |
| thumb.onload=()=>{_syncSubPreview();}; | |
| thumb.onerror=()=>{ | |
| // If thumbnail image fails to load, just show placeholder | |
| thumb.style.display='none'; | |
| document.getElementById('canvas-ph').style.display='flex'; | |
| }; | |
| }else{ | |
| // No thumbnail available — just hide placeholder with a note | |
| document.getElementById('canvas-ph').innerHTML='<i class="fas fa-check-circle" style="font-size:1.8rem;opacity:.5;color:var(--green)"></i><span>URL ထည့်ပြီးပါပြီ</span>'; | |
| } | |
| }catch(e){toast('⚠️ Preview: '+e);} | |
| loading.classList.remove('on'); | |
| } | |
| function onFile(inp){const f=inp.files[0];if(!f)return;const n=document.getElementById('upzn');n.textContent=f.name;n.style.display='block';document.getElementById('vurl').value='';document.getElementById('ubadge').style.display='none';CACHE_KEY='';} | |
| function setFN(id,inp){const f=inp.files[0];const el=document.getElementById(id);el.textContent=f?f.name:'';el.style.display=f?'block':'none';} | |
| function hasVid(){return document.getElementById('vurl').value.trim()||(document.getElementById('vfile').files||[]).length>0;} | |
| /* ══ FLIP / COLOR ══ */ | |
| function toggleOpt(k){ | |
| if(k==='flip'){FLIP_ON=!FLIP_ON;const b=document.getElementById('btn-flip');b.className='icon-toggle'+(FLIP_ON?' on':'');b.innerHTML='<i class="fas fa-arrows-alt-h"></i> '+(FLIP_ON?'On':'Off');} | |
| else{COL_ON=!COL_ON;const b=document.getElementById('btn-col');b.className='icon-toggle'+(COL_ON?' on':'');b.innerHTML='<i class="fas fa-adjust"></i> '+(COL_ON?'On':'Off');} | |
| } | |
| /* ══ BACKGROUND MUSIC ══ */ | |
| function toggleBgm(){ | |
| BGM_ON=!BGM_ON; | |
| const b=document.getElementById('btn-bgm'); | |
| b.className='icon-toggle'+(BGM_ON?' on':''); | |
| b.innerHTML='<i class="fas fa-music"></i> '+(BGM_ON?'On':'Off'); | |
| document.getElementById('bgm-wrap').style.display=BGM_ON?'block':'none'; | |
| if(!BGM_ON){ | |
| document.getElementById('bgm-file').value=''; | |
| document.getElementById('bgm-name').textContent='Click to upload MP3 / WAV'; | |
| document.getElementById('bgm-name').style.color='rgba(255,255,255,.5)'; | |
| } | |
| } | |
| function onBgmFile(input){ | |
| const f=input.files[0]; | |
| if(!f)return; | |
| document.getElementById('bgm-name').textContent=f.name; | |
| document.getElementById('bgm-name').style.color='#c4b5fd'; | |
| } | |
| /* ══ VOICE ══ */ | |
| function onLang(){renderV();setSpd();} | |
| function onEng(){renderV();const g=document.getElementById('engine').value==='gemini';document.getElementById('pvbtn').style.display=g?'none':'flex';if(g)setSpd();} | |
| function renderV(){const lang=document.getElementById('vlang').value,eng=document.getElementById('engine').value;const sel=document.getElementById('vsel');sel.innerHTML='';(eng==='gemini'?GV:(EV[lang]||EV.my)).forEach(v=>{const o=document.createElement('option');o.value=v.id;o.textContent=v.name||v.id;sel.appendChild(o);});} | |
| function setSpd(){const s={my:30,th:20,en:0}[document.getElementById('vlang').value]??30;document.getElementById('spd').value=s;document.getElementById('spdv').textContent=s+'%';} | |
| async function doPrevVoice(){ | |
| const voice=document.getElementById('vsel').value,speed=parseInt(document.getElementById('spd').value); | |
| const vo_lang=document.getElementById('vlang').value; | |
| const btn=document.getElementById('pvbtn');btn.innerHTML='<i class="fas fa-spinner sp"></i>'; | |
| try{const r=await fetch('/api/preview_voice',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({voice,speed,engine:'ms',vo_lang})}); | |
| if(!r.ok)throw new Error();const d=await r.json(); | |
| if(d.ok){const a=document.getElementById('pva');a.src=d.url;a.play();}else toast('❌ '+d.msg);} | |
| catch{toast('❌ Preview failed');} | |
| btn.innerHTML='<i class="fas fa-play"></i>'; | |
| } | |
| /* ══ OVERLAY TOGGLES ══ */ | |
| let BLUR_ON=false, WM2_ON=false, LOGO2_ON=false; | |
| function _ovBtn(id, on, ac, bc){ | |
| const b=document.getElementById(id); if(!b)return; | |
| if(on){b.style.background=ac;b.style.borderColor=bc;b.style.color='#fff';b.textContent='On';} | |
| else{b.style.background='';b.style.borderColor='';b.style.color='';b.textContent='Off';} | |
| } | |
| function ovToggle(k){ | |
| if(k==='blur'){ | |
| BLUR_ON=!BLUR_ON; | |
| _ovBtn('ov-blur',BLUR_ON,'rgba(239,68,68,.28)','#ef4444'); | |
| document.getElementById('db-blur').style.display=BLUR_ON?'block':'none'; | |
| const bh=document.getElementById('ov-blur-hint');if(bh)bh.style.display=BLUR_ON?'block':'none'; | |
| return; | |
| } | |
| if(k==='wm'){ | |
| WM2_ON=!WM2_ON; | |
| _ovBtn('ov-wm',WM2_ON,'rgba(234,179,8,.28)','#ca8a04'); | |
| document.getElementById('db-wm').style.display=WM2_ON?'block':'none'; | |
| document.getElementById('ov-wm-wrap').style.display=WM2_ON?'block':'none'; | |
| if(WM2_ON)setTimeout(()=>{const i=document.getElementById('wm-text2');if(i)i.focus();},60); | |
| return; | |
| } | |
| if(k==='logo'){ | |
| LOGO2_ON=!LOGO2_ON; | |
| _ovBtn('ov-logo',LOGO2_ON,'rgba(16,185,129,.28)','#10b981'); | |
| document.getElementById('db-logo').style.display=LOGO2_ON?'block':'none'; | |
| document.getElementById('ov-logo-wrap').style.display=LOGO2_ON?'block':'none'; | |
| return; | |
| } | |
| } | |
| function onLogoFile(inp){ | |
| const f=inp.files[0]; if(!f)return; | |
| document.getElementById('logo-name').textContent=f.name; | |
| document.getElementById('logo-name').style.color='#6ee7b7'; | |
| const r=new FileReader(); | |
| r.onload=e=>{ | |
| const box=document.getElementById('db-logo'); | |
| box.style.backgroundImage='url('+e.target.result+')'; | |
| box.style.backgroundSize='contain'; | |
| box.style.backgroundRepeat='no-repeat'; | |
| box.style.backgroundPosition='center'; | |
| const lbl=document.getElementById('db-logo-lbl'); | |
| if(lbl)lbl.style.display='none'; | |
| }; | |
| r.readAsDataURL(f); | |
| } | |
| /* ══ DRAG & RESIZE ══ */ | |
| function syncCanvasInner(v){ | |
| applyCropPreview(v); | |
| } | |
| function applyCropPreview(v){ | |
| const crop=document.getElementById('crop').value; | |
| const bg=document.getElementById('canvas-bg-blur'); | |
| const vw=document.getElementById('canvas-video-wrap'); | |
| const ci=document.getElementById('canvas-inner'); | |
| vw.style.position='';vw.style.left='';vw.style.right=''; | |
| vw.style.width='100%';vw.style.margin=''; | |
| bg.style.display='none'; | |
| if(!v.videoWidth) return; | |
| const RATIOS={'9:16':[9,16],'16:9':[16,9],'1:1':[1,1],'original':null}; | |
| const tgt=RATIOS[crop]; | |
| if(!tgt) return; | |
| const srcW=v.videoWidth, srcH=v.videoHeight; | |
| const tgtRatio=tgt[0]/tgt[1]; | |
| const srcRatio=srcW/srcH; | |
| if(Math.abs(tgtRatio-srcRatio)<0.05) return; | |
| try{ | |
| const c=document.createElement('canvas'); | |
| c.width=srcW;c.height=srcH; | |
| const ctx=c.getContext('2d'); | |
| ctx.drawImage(v,0,0,srcW,srcH); | |
| bg.style.backgroundImage='url('+c.toDataURL('image/jpeg',0.4)+')'; | |
| bg.style.display='block'; | |
| } catch(e){ | |
| bg.style.backgroundImage='none'; | |
| bg.style.background='rgba(20,20,20,.9)'; | |
| bg.style.display='block'; | |
| } | |
| if(srcRatio > tgtRatio){ | |
| const pct=(tgtRatio/srcRatio*100).toFixed(2)+'%'; | |
| vw.style.position='relative'; | |
| vw.style.width=pct; | |
| vw.style.margin='0 auto'; | |
| ci.style.left=((100-(tgtRatio/srcRatio*100))/2).toFixed(2)+'%'; | |
| ci.style.width=(tgtRatio/srcRatio*100).toFixed(2)+'%'; | |
| } else { | |
| ci.style.left='0';ci.style.width='100%'; | |
| } | |
| } | |
| function initDrag(){ | |
| ['db-sub','db-blur','db-wm','db-logo'].forEach(id=>{ | |
| const el=document.getElementById(id); | |
| if(el){makeDrag(el);makeResize(el,el.querySelector('.rh'));} | |
| }); | |
| } | |
| function makeDrag(el){ | |
| let ox,oy,ex,ey; | |
| const down=e=>{if(e.target.classList.contains('rh'))return;e.preventDefault();const t=e.touches?e.touches[0]:e;ox=t.clientX;oy=t.clientY;const r=el.getBoundingClientRect(),pr=el.parentElement.getBoundingClientRect();ex=r.left-pr.left;ey=r.top-pr.top; | |
| const mv=e=>{e.preventDefault();const t=e.touches?e.touches[0]:e;el.style.left=(ex+t.clientX-ox)+'px';el.style.top=(ey+t.clientY-oy)+'px';el.style.bottom='auto';}; | |
| const up=()=>{document.removeEventListener('mousemove',mv);document.removeEventListener('mouseup',up);el.removeEventListener('touchmove',mv);el.removeEventListener('touchend',up);}; | |
| document.addEventListener('mousemove',mv);document.addEventListener('mouseup',up);el.addEventListener('touchmove',mv,{passive:false});el.addEventListener('touchend',up); | |
| }; | |
| el.addEventListener('mousedown',down);el.addEventListener('touchstart',down,{passive:false}); | |
| } | |
| function makeResize(el,h){if(!h)return; | |
| const down=e=>{e.stopPropagation();e.preventDefault();const t=e.touches?e.touches[0]:e;const sw=el.offsetWidth,sh=el.offsetHeight,sx=t.clientX,sy=t.clientY; | |
| const mv=e=>{e.preventDefault();const t=e.touches?e.touches[0]:e;el.style.width=Math.max(40,sw+t.clientX-sx)+'px';el.style.height=Math.max(30,sh+t.clientY-sy)+'px';}; | |
| const up=()=>{document.removeEventListener('mousemove',mv);document.removeEventListener('mouseup',up);h.removeEventListener('touchmove',mv);h.removeEventListener('touchend',up);}; | |
| document.addEventListener('mousemove',mv);document.addEventListener('mouseup',up);h.addEventListener('touchmove',mv,{passive:false});h.addEventListener('touchend',up); | |
| }; | |
| h.addEventListener('mousedown',down);h.addEventListener('touchstart',down,{passive:false}); | |
| } | |
| function getBoxPct(id){ | |
| const el=document.getElementById(id); | |
| const ci=document.getElementById('canvas-inner'); | |
| const video=document.getElementById('pvid'); | |
| const crop=document.getElementById('crop').value; | |
| const srcW=video.videoWidth||720, srcH=video.videoHeight||1280; | |
| let tW,tH; | |
| if(crop==='9:16'){tW=720;tH=1280;} | |
| else if(crop==='16:9'){tW=1280;tH=720;} | |
| else if(crop==='1:1'){tW=720;tH=720;} | |
| else{tW=srcW;tH=srcH;} | |
| const ciR=ci.getBoundingClientRect(); | |
| const elR=el.getBoundingClientRect(); | |
| const sx=tW/ciR.width, sy=tH/ciR.height; | |
| return{ | |
| x:Math.max(0,Math.min(1,((elR.left-ciR.left)*sx)/tW)), | |
| y:Math.max(0,Math.min(1,((elR.top-ciR.top)*sy)/tH)), | |
| w:Math.max(0,Math.min(1,(elR.width*sx)/tW)), | |
| h:Math.max(0,Math.min(1,(elR.height*sy)/tH)) | |
| }; | |
| } | |
| /* ══ FORM DATA ══ */ | |
| function bfd(){ | |
| const fd=new FormData(); | |
| fd.append('username',U);fd.append('video_url',document.getElementById('vurl').value.trim()); | |
| fd.append('voice',document.getElementById('vsel').value);fd.append('engine',document.getElementById('engine').value); | |
| fd.append('speed',document.getElementById('spd').value); | |
| // Watermark canvas-positioned | |
| const _wmTxt=WM2_ON?(document.getElementById('wm-text2').value.trim()):''; | |
| fd.append('watermark',_wmTxt); | |
| if(WM2_ON && _wmTxt){ | |
| const wp=getBoxPct('db-wm'); | |
| fd.append('wmk_xp',wp.x.toFixed(4)); | |
| fd.append('wmk_yp',wp.y.toFixed(4)); | |
| fd.append('wmk_fontsize','28'); | |
| } | |
| // Logo canvas-positioned | |
| const _lf=document.getElementById('logo-file2'); | |
| if(LOGO2_ON && _lf && _lf.files[0]){ | |
| fd.append('logo_file',_lf.files[0]); | |
| const lp=getBoxPct('db-logo'); | |
| fd.append('logo_xp',lp.x.toFixed(4)); | |
| fd.append('logo_yp',lp.y.toFixed(4)); | |
| fd.append('logo_wp',lp.w.toFixed(4)); | |
| } | |
| fd.append('crop',document.getElementById('crop').value);fd.append('flip',FLIP_ON?'1':'0');fd.append('color',COL_ON?'1':'0'); | |
| fd.append('content_type',document.getElementById('ctype').value);fd.append('ai_model',document.getElementById('aimodel').value);fd.append('vo_lang',document.getElementById('vlang').value); | |
| if(CACHE_KEY)fd.append('cache_key',CACHE_KEY); | |
| // Blur box | |
| fd.append('blur_enabled',BLUR_ON?'1':'0'); | |
| if(BLUR_ON){ | |
| const bp=getBoxPct('db-blur'); | |
| const vw=document.getElementById('pvid'); | |
| const vW=vw.videoWidth||720, vH=vw.videoHeight||1280; | |
| const crop=document.getElementById('crop').value; | |
| let tW=vW,tH=vH; | |
| if(crop==='9:16'){tW=720;tH=1280;} | |
| else if(crop==='16:9'){tW=1280;tH=720;} | |
| else if(crop==='1:1'){tW=720;tH=720;} | |
| fd.append('blur_xp',bp.x.toFixed(4)); | |
| fd.append('blur_yp',bp.y.toFixed(4)); | |
| fd.append('blur_wp',bp.w.toFixed(4)); | |
| fd.append('blur_hp',bp.h.toFixed(4)); | |
| } | |
| fd.append('sub_enabled',SUB_ON?'1':'0'); | |
| if(SUB_ON){ | |
| fd.append('sub_size',document.getElementById('sub-size').value); | |
| fd.append('sub_pos',document.getElementById('sub-pos').value); | |
| fd.append('sub_color',document.getElementById('sub-color').value); | |
| fd.append('sub_style',document.getElementById('sub-style').value); | |
| } | |
| const vf=document.getElementById('vfile');if(vf&&vf.files[0])fd.append('video_file',vf.files[0]); | |
| const mf=document.getElementById('bgm-file');if(BGM_ON&&mf&&mf.files[0])fd.append('music_file',mf.files[0]); | |
| return fd; | |
| } | |
| /* ══ PROGRESS ══ */ | |
| function fmt(s){const m=Math.floor(s/60);return m>0?m+'m '+(s%60)+'s':s+'s';} | |
| const SMAP={8:'ps-dl',20:'ps-tr',45:'ps-ai',65:'ps-ts',78:'ps-vd'}; | |
| let _waitMsgTimer=null; | |
| function _startWaitMsgs(){ | |
| const msgs=['⏳ တန်းစီစောင့်နေသည်…','⏳ Server နဲ့ ချိတ်ဆက်နေသည်…','⏳ Video URL စစ်ဆေးနေသည်…','⏳ ဒေါင်းလုပ်အတွက် ပြင်ဆင်နေသည်…']; | |
| let i=0; | |
| _waitMsgTimer=setInterval(()=>{i=(i+1)%msgs.length;document.getElementById('pmsg').textContent=msgs[i];},2500); | |
| } | |
| function _stopWaitMsgs(){if(_waitMsgTimer){clearInterval(_waitMsgTimer);_waitMsgTimer=null;}} | |
| function showProg(on){document.getElementById('pc').style.display=on?'block':'none';if(on){document.getElementById('pb').style.width='0%';document.getElementById('pmsg').textContent='⏳ တန်းစီစောင့်နေသည်…';document.getElementById('ptmr').textContent='⏱ 0s';Object.values(SMAP).forEach(id=>document.getElementById(id).className='ps');}else{_stopWaitMsgs();}} | |
| function updSteps(pct){let hit=false;const e=Object.entries(SMAP);for(let i=0;i<e.length;i++){const[p,id]=e[i],el=document.getElementById(id);if(pct>=parseInt(p)){el.className='ps done';}else if(!hit){el.className='ps active';hit=true;}else el.className='ps';}} | |
| function startTick(){T0=Date.now();if(TICK)clearInterval(TICK);TICK=setInterval(()=>{document.getElementById('ptmr').textContent='⏱ '+fmt(Math.floor((Date.now()-T0)/1000));},1000);} | |
| function stopTick(){if(TICK){clearInterval(TICK);TICK=null;}return Math.floor((Date.now()-T0)/1000);} | |
| function _resetBtn(){const b=document.getElementById('btnauto');b.disabled=false;b.innerHTML='<i class="fas fa-magic"></i> Auto Process <span style="opacity:.7;font-size:.78em;background:rgba(255,255,255,.2);padding:2px 8px;border-radius:10px">1 Coin</span>';} | |
| function startSSE(tid){if(SSE)SSE.close();SSE=new EventSource('/api/progress/'+tid);SSE.onmessage=e=>{try{const p=JSON.parse(e.data);const msg=(p.msg||'').replace(/KEY-\d+\s*·\s*\S+/g,'').trim();document.getElementById('pb').style.width=(p.pct||0)+'%';document.getElementById('pmsg').textContent=msg;updSteps(p.pct||0);if(p.done){SSE.close();SSE=null;const el=stopTick();_resetBtn();if(p.output_url){updC(p.coins!=null?p.coins:0);showRes(p.output_url,p.title,p.hashtags,p.caption,el);}else{showProg(false);toast('❌ output မတွေ့ပါ');}}else if(p.error){SSE.close();SSE=null;stopTick();showProg(false);_resetBtn();toast(msg||'❌ Process မအောင်မြင်ပါ');}}catch(ex){};};} | |
| /* ══ AUTO PROCESS ══ */ | |
| async function doAuto(){ | |
| if(!hasVid()){toast('❌ Select a video or enter a URL');return;} | |
| const btn=document.getElementById('btnauto');btn.disabled=true;btn.innerHTML='<i class="fas fa-spinner sp"></i> Processing…'; | |
| showProg(true);startTick();_startWaitMsgs(); | |
| const fd=bfd(); | |
| const tid=uid8();fd.append('tid',tid); | |
| try{ | |
| const ctrl=new AbortController(),tout=setTimeout(()=>ctrl.abort(),600000); | |
| const r=await fetch('/api/process_all',{method:'POST',body:fd,signal:ctrl.signal}); | |
| clearTimeout(tout); | |
| if(!r.ok){_stopWaitMsgs();stopTick();showProg(false);_resetBtn();toast('❌ Server error '+r.status);return;} | |
| const d=await r.json(); | |
| if(!d.ok){_stopWaitMsgs();stopTick();showProg(false);_resetBtn();toast(d.msg||'❌ Process မအောင်မြင်ပါ');return;} | |
| // Server confirmed — connect SSE, stop local animation on first real message | |
| _stopWaitMsgs(); | |
| startSSE(tid); | |
| }catch(e){ | |
| _stopWaitMsgs();stopTick();showProg(false);_resetBtn(); | |
| toast(e.name==='AbortError'?'❌ Timeout':'❌ '+e); | |
| } | |
| } | |
| /* ══ RESULT ══ */ | |
| function showRes(url,title,tags,cap,el){ | |
| showProg(false);OUT_URL=url;OUT_CAP=cap||title;OUT_TAGS=tags; | |
| const rc=document.getElementById('rc'); | |
| const rvid=document.getElementById('rvid'); | |
| rvid.src='';rc.style.display='block'; | |
| if(title){document.getElementById('rtit').textContent=title;document.getElementById('rtit').style.display='block';} | |
| if(tags){document.getElementById('rtag').textContent=tags;document.getElementById('rtag').style.display='block';} | |
| if(el>0){const rt=document.getElementById('rtm');rt.querySelector('span').textContent='Process time: '+fmt(el);rt.style.display='flex';} | |
| rc.scrollIntoView({behavior:'smooth'}); | |
| let tries=0; | |
| const poll=setInterval(async()=>{ | |
| tries++; | |
| try{const r=await fetch(url,{method:'HEAD'});if(r.ok){clearInterval(poll);rvid.src=url;toast('✅ Video ပြီးပါပြီ!');return;}}catch(e){} | |
| if(tries>=20){clearInterval(poll);rvid.src=url;toast('✅ Done');} | |
| },1000); | |
| } | |
| function dlVideo(){if(!OUT_URL)return;const a=document.createElement('a');a.href=OUT_URL;a.download='recap.mp4';a.click();} | |
| function copyCap(){const t=[OUT_CAP,OUT_TAGS].filter(Boolean).join('\n\n');if(!t){toast('Nothing to copy');return;}navigator.clipboard.writeText(t).then(()=>toast('✅ Copied!')).catch(()=>toast('❌ Copy failed'));} | |
| /* ══ ADMIN ══ */ | |
| async function admCoins(act){const u=document.getElementById('au-'+act).value.trim(),n=parseInt(document.getElementById('an-'+act).value)||0;if(!u){toast('❌ Enter username');return;}const r=await fetch('/api/admin/coins',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u,amount:n,action:act})});const d=await r.json();document.getElementById('admmsg').textContent=d.msg||'';} | |
| async function admCreate(){const u=document.getElementById('au-new').value.trim();const r=await fetch('/api/admin/create_user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u,coins:0})});const d=await r.json();document.getElementById('au-res').textContent=d.msg+(d.username?' → '+d.username:'');} | |
| async function admDel(){const u=document.getElementById('au-del').value.trim();if(!u||!confirm('Delete '+u+'?'))return;const r=await fetch('/api/admin/delete_user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u})});const d=await r.json();document.getElementById('admmsg').textContent=d.msg;if(d.ok)loadUsers();} | |
| async function loadUsers(){ | |
| const r=await fetch('/api/admin/users?caller='+encodeURIComponent(U)); | |
| if(!r.ok)return;const d=await r.json();if(!d.ok)return; | |
| const w=document.getElementById('utw'); | |
| if(!d.users.length){w.innerHTML='<div style="font-size:.75rem;color:var(--muted);padding:6px">No users</div>';return;} | |
| let h='<table class="ut"><thead><tr><th>User</th><th>🪙</th><th>Vids</th><th>Status</th><th>Actions</th></tr></thead><tbody>'; | |
| d.users.forEach(u=>{ | |
| const banned=u.banned; | |
| const rowStyle=banned?'opacity:.5;background:rgba(239,68,68,.05)':''; | |
| const banBtn=banned | |
| ?`<button class="delbtn" style="color:#10b981" onclick="qBan('${u.username}',false)" title="Unban"><i class="fas fa-unlock"></i></button>` | |
| :`<button class="delbtn" style="color:#f59e0b" onclick="qBan('${u.username}',true)" title="Ban"><i class="fas fa-ban"></i></button>`; | |
| h+=`<tr style="${rowStyle}"><td style="max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${u.username}</td><td>${u.coins}</td><td>${u.videos}</td><td>${banned?'<span style="color:#ef4444;font-size:.7rem">🚫 Banned</span>':'<span style="color:#10b981;font-size:.7rem">✅ Active</span>'}</td><td style="display:flex;gap:3px">${banBtn}<button class="delbtn" onclick="qDel('${u.username}')"><i class="fas fa-trash"></i></button></td></tr>`; | |
| }); | |
| w.innerHTML=h+'</tbody></table>'; | |
| } | |
| async function qDel(u){if(!confirm('Delete '+u+'?'))return;const r=await fetch('/api/admin/delete_user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u})});const d=await r.json();toast(d.msg);if(d.ok)loadUsers();} | |
| async function qBan(u,ban){const label=ban?'Ban':'Unban';if(!confirm(label+' '+u+'?'))return;const r=await fetch('/api/admin/ban_user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u,ban})});const d=await r.json();toast(d.msg);if(d.ok)loadUsers();} | |
| /* ══ PENDING PAYMENTS ══ */ | |
| function openBroadcast(){ | |
| const m=document.getElementById('bc-modal'); | |
| m.style.display='flex'; | |
| document.getElementById('bc-msg').focus(); | |
| document.getElementById('bc-res').textContent=''; | |
| document.getElementById('bc-msg').oninput=function(){ | |
| document.getElementById('bc-char').textContent=this.value.length+' / 4096'; | |
| }; | |
| } | |
| function closeBroadcast(){ | |
| document.getElementById('bc-modal').style.display='none'; | |
| document.getElementById('bc-msg').value=''; | |
| document.getElementById('bc-char').textContent='0 / 4096'; | |
| } | |
| async function sendBroadcast(){ | |
| const msg=document.getElementById('bc-msg').value.trim(); | |
| const res=document.getElementById('bc-res'); | |
| const btn=document.getElementById('bc-send-btn'); | |
| if(!msg){res.style.color='#ef4444';res.textContent='❌ Message ထည့်ပါ';return;} | |
| btn.disabled=true;btn.innerHTML='<i class="fas fa-spinner fa-spin"></i> ပို့နေသည်…'; | |
| res.style.color='rgba(255,255,255,.4)';res.textContent=''; | |
| try{ | |
| const r=await fetch('/api/admin/broadcast',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,message:msg})}); | |
| const d=await r.json(); | |
| if(d.ok){ | |
| res.style.color='#4ade80';res.textContent=d.msg; | |
| document.getElementById('bc-msg').value=''; | |
| document.getElementById('bc-char').textContent='0 / 4096'; | |
| setTimeout(closeBroadcast,2000); | |
| }else{res.style.color='#ef4444';res.textContent=d.msg||'❌ Error';} | |
| }catch(e){res.style.color='#ef4444';res.textContent='❌ Network error';} | |
| btn.disabled=false;btn.innerHTML='<i class="fas fa-paper-plane"></i> ပို့မည်'; | |
| } | |
| async function loadPendingPayments(){ | |
| const wrap=document.getElementById('adm-pending-wrap'); | |
| const list=document.getElementById('adm-pending-list'); | |
| const cnt=document.getElementById('adm-pending-count'); | |
| list.innerHTML='<div style="font-size:.75rem;color:var(--muted);padding:8px">⏳ Loading…</div>'; | |
| wrap.style.display='block'; | |
| try{ | |
| const r=await fetch('/api/admin/payments?caller='+encodeURIComponent(U)+'&status=pending'); | |
| let d; | |
| try{ d=await r.json(); } | |
| catch(je){ list.innerHTML='<div style="color:var(--red);font-size:.75rem">❌ Server error (not JSON) — HTTP '+r.status+'</div>'; return; } | |
| if(!d.ok){list.innerHTML='<div style="color:var(--red);font-size:.75rem">❌ '+(d.msg||'Error')+'</div>';return;} | |
| const pays=d.payments||[]; | |
| cnt.textContent=pays.length; | |
| cnt.style.display=pays.length?'':'none'; | |
| if(!pays.length){list.innerHTML='<div style="font-size:.78rem;color:var(--muted);padding:10px;text-align:center">✅ Pending payment မရှိပါ</div>';return;} | |
| list.innerHTML=pays.map(p=>{ | |
| const dt=p.created_at?p.created_at.replace('T',' ').substring(0,16):''; | |
| return `<div class="adm-pay-card" id="paycard-${p.id}"> | |
| <div class="adm-pay-card-top"> | |
| <span class="adm-pay-card-user">👤 ${p.username}</span> | |
| <span class="adm-pay-card-coins">🪙 ${p.coins} Coins</span> | |
| </div> | |
| <div class="adm-pay-card-info"> | |
| 💵 ${p.price} MMK · 🆔 <code style="background:var(--bg3);padding:2px 5px;border-radius:4px;font-size:.7rem">${p.id}</code><br> | |
| ⏰ ${dt} | |
| </div> | |
| ${p.slip_image?`<img class="adm-slip-img" id="slip-${p.id}" src="${p.slip_image}" onclick="this.style.display=this.style.display==='none'?'block':'none'">` :''} | |
| <div class="adm-pay-card-btns"> | |
| ${p.slip_image?`<button class="adm-pay-slip-btn" onclick="toggleSlip('${p.id}')"><i class="fas fa-image"></i> Slip</button>`:''} | |
| <button class="adm-pay-approve" onclick="admPayAction('approve','${p.id}','${p.username}',${p.coins},this)">✅ Approve +${p.coins}</button> | |
| <button class="adm-pay-reject" onclick="admPayAction('reject','${p.id}','${p.username}',0,this)">❌ Reject</button> | |
| </div> | |
| </div>`; | |
| }).join(''); | |
| }catch(e){list.innerHTML='<div style="color:var(--red);font-size:.75rem">❌ '+e+'</div>';} | |
| } | |
| function toggleSlip(pid){ | |
| const img=document.getElementById('slip-'+pid); | |
| if(img) img.style.display=img.style.display==='block'?'none':'block'; | |
| } | |
| async function admPayAction(action,pid,username,coins,btn){ | |
| if(action==='reject'&&!confirm('Reject payment from '+username+'?'))return; | |
| // Disable button to prevent double-click freeze | |
| if(btn){btn.disabled=true;btn.style.opacity='0.5';} | |
| const ep=action==='approve'?'/api/admin/payment/approve':'/api/admin/payment/reject'; | |
| const body={caller:U,payment_id:pid,username,coins}; | |
| try{ | |
| const r=await fetch(ep,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}); | |
| const d=await r.json(); | |
| if(d.ok){ | |
| toast(action==='approve'?'✅ Approved! +'+coins+' coins → '+username:'❌ Rejected'); | |
| const card=document.getElementById('paycard-'+pid); | |
| if(card)card.remove(); | |
| const remaining=document.querySelectorAll('[id^="paycard-"]').length; | |
| const cnt=document.getElementById('adm-pending-count'); | |
| if(cnt){cnt.textContent=remaining;cnt.style.display=remaining?'':'none';} | |
| }else{ | |
| toast('❌ '+(d.msg||'Error')); | |
| if(btn){btn.disabled=false;btn.style.opacity='';} | |
| } | |
| }catch(e){ | |
| toast('❌ Network error: '+e.message); | |
| if(btn){btn.disabled=false;btn.style.opacity='';} | |
| } | |
| } | |
| /* ══ UTILS ══ */ | |
| function toast(m){const e=document.getElementById('toast');e.textContent=m;e.classList.add('show');setTimeout(()=>e.classList.remove('show'),2800);} | |
| function uid8(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,c=>(c^crypto.getRandomValues(new Uint8Array(1))[0]&15>>c/4).toString(16)).replace(/-/g,'').substring(0,8);} | |
| async function doPaste(){try{const t=await navigator.clipboard.readText();if(t){const el=document.getElementById('vurl');el.value=t;onUrl(t);toast('✅ Pasted!');}else toast('❌ Clipboard empty');}catch(e){toast('❌ Clipboard access denied');}} | |
| document.addEventListener('keydown',e=>{if(e.key==='Enter'&&document.getElementById('login-screen').style.display!=='none'){if(document.getElementById('pane-li').style.display!=='none')doLogin();else doReg();}}); | |
| /* ══ MODE SWITCH ══ */ | |
| function switchMode(m){ | |
| const isRecap=m==='recap'; | |
| document.getElementById('section-recap').style.display=isRecap?'':'none'; | |
| document.getElementById('section-srt').style.display=isRecap?'none':''; | |
| document.getElementById('mode-recap').classList.toggle('on',isRecap); | |
| document.getElementById('mode-srt').classList.toggle('on',!isRecap); | |
| } | |
| /* ══ TRANSLATE SRT ══ */ | |
| let SRT_VOUT='', SRT_CACHE_KEY=''; | |
| let SRT_TEXT='', SRT_VPATH_KEY=''; | |
| let SRT_FLIP=false, SRT_COL=false; | |
| let SRT_ZOOM=false, SRT_AUDIO=false; | |
| let SRT_BLUR_ON=false, SRT_WM_ON=false, SRT_LOGO_ON=false; | |
| let SRT_SSE=null, SRT_TICK=null, SRT_T0=0; | |
| let SRT_URL_DEB=null; | |
| let SRT_REGEN_COUNT=0; | |
| /* ── Video source handlers ── */ | |
| function onSrtUrl(v){ | |
| const b=document.getElementById('srt-ubadge'); | |
| b.style.display='none'; SRT_CACHE_KEY=''; SRT_REGEN_COUNT=0; | |
| if(!v) return; | |
| const vl=v.toLowerCase(); | |
| if(vl.includes('youtube.com')||vl.includes('youtu.be')){b.textContent='YouTube';b.className='ubadge b-yt';b.style.display='';} | |
| else if(vl.includes('tiktok.com')){b.textContent='TikTok';b.className='ubadge b-tt';b.style.display='';} | |
| else if(vl.includes('instagram.com')){b.textContent='Instagram';b.className='ubadge b-ig';b.style.display='';} | |
| else if(vl.includes('facebook.com')||vl.includes('fb.watch')){b.textContent='Facebook';b.className='ubadge b-fb';b.style.display='';} | |
| if(SRT_URL_DEB) clearTimeout(SRT_URL_DEB); | |
| if(v.startsWith('http')){ | |
| SRT_URL_DEB=setTimeout(()=>srtAutoThumb(v),1200); | |
| } | |
| } | |
| async function srtAutoThumb(url){ | |
| const loading=document.getElementById('srt-prev-loading'); | |
| loading.classList.add('on'); | |
| try{ | |
| const r=await fetch('/api/thumb',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url})}); | |
| const d=await r.json(); | |
| if(d.ok){ | |
| const th=document.getElementById('srt-pthumb'); | |
| th.src=d.url; th.style.display='block'; | |
| document.getElementById('srt-pvid').style.display='none'; | |
| document.getElementById('srt-canvas-ph').style.display='none'; | |
| th.onload=()=>{syncSrtSubPreview();}; | |
| } | |
| }catch(e){} | |
| loading.classList.remove('on'); | |
| } | |
| function onSrtVFile(inp){ | |
| const f=inp.files[0]; if(!f) return; | |
| document.getElementById('srt-upzn').textContent=f.name; | |
| document.getElementById('srt-upzn').style.display='block'; | |
| document.getElementById('srt-vurl').value=''; | |
| document.getElementById('srt-ubadge').style.display='none'; | |
| SRT_CACHE_KEY=''; SRT_REGEN_COUNT=0; | |
| const url=URL.createObjectURL(f); | |
| const v=document.getElementById('srt-pvid'); | |
| v.src=url; v.style.display='block'; | |
| document.getElementById('srt-pthumb').style.display='none'; | |
| document.getElementById('srt-canvas-ph').style.display='none'; | |
| v.onloadedmetadata=()=>{syncSrtSubPreview();}; | |
| } | |
| function onSrtLogoFile(inp){ | |
| const f=inp.files[0]; if(!f) return; | |
| document.getElementById('srt-logo-name').textContent=f.name; | |
| document.getElementById('srt-logo-name').style.color='#6ee7b7'; | |
| const r=new FileReader(); | |
| r.onload=e=>{ | |
| const box=document.getElementById('srt-db-logo'); | |
| box.style.backgroundImage='url('+e.target.result+')'; | |
| box.style.backgroundSize='contain'; | |
| box.style.backgroundRepeat='no-repeat'; | |
| box.style.backgroundPosition='center'; | |
| const lbl=document.getElementById('srt-db-logo-lbl'); | |
| if(lbl) lbl.style.display='none'; | |
| }; | |
| r.readAsDataURL(f); | |
| } | |
| async function srtPaste(){ | |
| try{ | |
| const t=await navigator.clipboard.readText(); | |
| if(t){document.getElementById('srt-vurl').value=t;onSrtUrl(t);toast('✅ Pasted!');} | |
| else toast('❌ Clipboard empty'); | |
| }catch(e){toast('❌ Clipboard access denied');} | |
| } | |
| /* ── Overlay toggle (movie recap style) ── */ | |
| function _srtOvBtn(id, on, ac, bc){ | |
| const b=document.getElementById(id); if(!b) return; | |
| if(on){b.style.background=ac;b.style.borderColor=bc;b.style.color='#fff';b.textContent='On';} | |
| else{b.style.background='';b.style.borderColor='';b.style.color='';b.textContent='Off';} | |
| } | |
| function srtOvToggle(k){ | |
| if(k==='blur'){ | |
| SRT_BLUR_ON=!SRT_BLUR_ON; | |
| _srtOvBtn('srt-ov-blur',SRT_BLUR_ON,'rgba(239,68,68,.28)','#ef4444'); | |
| document.getElementById('srt-db-blur').style.display=SRT_BLUR_ON?'block':'none'; | |
| document.getElementById('srt-ov-blur-hint').style.display=SRT_BLUR_ON?'block':'none'; | |
| if(SRT_BLUR_ON) initSrtDrag(); | |
| return; | |
| } | |
| if(k==='wm'){ | |
| SRT_WM_ON=!SRT_WM_ON; | |
| _srtOvBtn('srt-ov-wm',SRT_WM_ON,'rgba(234,179,8,.28)','#ca8a04'); | |
| document.getElementById('srt-db-wm').style.display=SRT_WM_ON?'block':'none'; | |
| document.getElementById('srt-ov-wm-wrap').style.display=SRT_WM_ON?'block':'none'; | |
| if(SRT_WM_ON){initSrtDrag();setTimeout(()=>{const i=document.getElementById('srt-wm-text');if(i)i.focus();},60);} | |
| return; | |
| } | |
| if(k==='logo'){ | |
| SRT_LOGO_ON=!SRT_LOGO_ON; | |
| _srtOvBtn('srt-ov-logo',SRT_LOGO_ON,'rgba(16,185,129,.28)','#10b981'); | |
| document.getElementById('srt-db-logo').style.display=SRT_LOGO_ON?'block':'none'; | |
| document.getElementById('srt-ov-logo-wrap').style.display=SRT_LOGO_ON?'block':'none'; | |
| if(SRT_LOGO_ON) initSrtDrag(); | |
| return; | |
| } | |
| if(k==='zoom'){ | |
| SRT_ZOOM=!SRT_ZOOM; | |
| _srtOvBtn('srt-ov-zoom',SRT_ZOOM,'rgba(139,92,246,.28)','#7c3aed'); | |
| _srtApplyZoomPreview(); | |
| return; | |
| } | |
| if(k==='audio'){ | |
| SRT_AUDIO=!SRT_AUDIO; | |
| _srtOvBtn('srt-ov-audio',SRT_AUDIO,'rgba(59,130,246,.28)','#2563eb'); | |
| return; | |
| } | |
| } | |
| /* ── Flip / Color toggles ── */ | |
| function srtToggleOpt(k){ | |
| if(k==='flip'){ | |
| SRT_FLIP=!SRT_FLIP; | |
| const b=document.getElementById('srt-btn-flip'); | |
| b.style.background=SRT_FLIP?'rgba(56,189,248,.18)':'rgba(255,255,255,.05)'; | |
| b.style.borderColor=SRT_FLIP?'#38bdf8':'rgba(255,255,255,.12)'; | |
| b.style.color=SRT_FLIP?'#38bdf8':'rgba(255,255,255,.55)'; | |
| b.innerHTML='<i class="fas fa-arrows-alt-h"></i> Flip: '+(SRT_FLIP?'On':'Off'); | |
| } else if(k==='col'){ | |
| SRT_COL=!SRT_COL; | |
| const b=document.getElementById('srt-btn-col'); | |
| b.style.background=SRT_COL?'rgba(251,191,36,.18)':'rgba(255,255,255,.05)'; | |
| b.style.borderColor=SRT_COL?'#fbbf24':'rgba(255,255,255,.12)'; | |
| b.style.color=SRT_COL?'#fbbf24':'rgba(255,255,255,.55)'; | |
| b.innerHTML='<i class="fas fa-adjust"></i> Color: '+(SRT_COL?'On':'Off'); | |
| } | |
| } | |
| function _srtApplyZoomPreview(){ | |
| const thumb=document.getElementById('srt-pthumb'); | |
| const vid=document.getElementById('srt-pvid'); | |
| const zv=document.getElementById('srt-zoom-factor'); | |
| const scale=SRT_ZOOM&&zv?parseFloat('1.0'+(''+zv.value).slice(1)):1; | |
| if(thumb) thumb.style.transform=scale>1?`scale(${scale})`:''; | |
| if(vid) vid.style.transform=scale>1?`scale(${scale})`:''; | |
| } | |
| function syncSrtSubPreview(){ | |
| const overlay=document.getElementById('srt-sub-overlay'); | |
| const span=document.getElementById('srt-sub-preview-span'); | |
| if(!overlay||!span) return; | |
| const thumb=document.getElementById('srt-pthumb'); | |
| const vid=document.getElementById('srt-pvid'); | |
| const hasMedia=(thumb&&thumb.style.display!=='none'&&thumb.src)|| | |
| (vid&&vid.style.display!=='none'&&vid.src); | |
| if(!hasMedia){overlay.style.display='none';return;} | |
| const posVal=parseInt((document.getElementById('srt-sub-pos')||{value:85}).value)||85; | |
| const col=(document.getElementById('srt-sub-color')||{value:'white'}).value; | |
| const sty=(document.getElementById('srt-sub-style')||{value:'outline'}).value; | |
| const sizeVal=parseInt((document.getElementById('srt-sub-size')||{value:64}).value)||64; | |
| /* pxSize: must match ASS ratio → pxSize/renderedH = sizeVal/1280 | |
| object-fit:contain: getBoundingClientRect().height = CSS box (may include letterbox gaps). | |
| Compute actual contained image height: min(elemH, elemW × natH/natW) */ | |
| const _vidEl=document.getElementById('srt-pvid'); | |
| const _imgEl=document.getElementById('srt-pthumb'); | |
| let _sNatW=0,_sNatH=0,_sElemW=0,_sElemH=0; | |
| if(_vidEl&&_vidEl.style.display!=='none'&&_vidEl.videoHeight>0){ | |
| const r=_vidEl.getBoundingClientRect(); | |
| _sElemW=r.width;_sElemH=r.height;_sNatW=_vidEl.videoWidth;_sNatH=_vidEl.videoHeight; | |
| } else if(_imgEl&&_imgEl.style.display!=='none'&&_imgEl.naturalHeight>0){ | |
| const r=_imgEl.getBoundingClientRect(); | |
| _sElemW=r.width;_sElemH=r.height;_sNatW=_imgEl.naturalWidth;_sNatH=_imgEl.naturalHeight; | |
| } | |
| const srtSizeFrac=parseFloat((document.getElementById('srt-sub-size')||{value:0.0547}).value)||0.0547; | |
| const _srtCropSel=(document.getElementById('srt-crop')||{value:'original'}).value; | |
| const _srtCi=document.getElementById('srt-canvas-inner'); | |
| const _srtCiR=_srtCi?_srtCi.getBoundingClientRect():{width:300,height:300}; | |
| let _srtEffH; | |
| if(_srtCropSel==='9:16'){ | |
| const _srtVwrap=document.getElementById('srt-canvas-video-wrap'); | |
| const _srtVwrapW=_srtVwrap?_srtVwrap.getBoundingClientRect().width:_srtCiR.width; | |
| _srtEffH=_srtVwrapW*(1280/720); | |
| } else _srtEffH=_srtCiR.height; | |
| const pxSize=Math.round(srtSizeFrac*_srtEffH); | |
| const colMap={ | |
| 'white':'#fff','yellow':'#ffe066','cyan':'#67e8f9','green':'#4ade80', | |
| 'orange':'#fb923c','pink':'#f0abfc','red':'#f87171','lime':'#a3e635', | |
| 'hotpink':'#ff69b4','gold':'#ffd700','violet':'#ee82ee','deepskyblue':'#00bfff','coral':'#ff7f50' | |
| }; | |
| /* Position subtitle within actual rendered image area only. | |
| cover img: fills full element → offsetTop=0, imgH=sElemH. | |
| contain video: letterbox → offsetTop=(sElemH-renderedH)/2. */ | |
| const _sWrap=document.getElementById('srt-canvas-video-wrap'); | |
| const _sTopPx=_srtEffH*posVal/100; | |
| overlay.style.display='block'; | |
| overlay.style.top=_sTopPx+'px'; | |
| overlay.style.transform='translateY(-50%)'; | |
| span.style.color=colMap[col]||'#fff'; | |
| span.style.fontSize=pxSize+'px'; | |
| span.style.fontWeight='700'; | |
| span.style.fontFamily="'Noto Sans Myanmar','Padauk',sans-serif"; | |
| span.style.lineHeight='1.25'; | |
| span.style.padding='0 8px'; | |
| span.style.maxWidth='95%'; | |
| if(sty==='box'){ | |
| span.style.background='rgba(0,0,0,.65)'; | |
| span.style.textShadow='none'; | |
| span.style.webkitTextStroke=''; | |
| } else if(sty==='shadow'){ | |
| span.style.background='transparent'; | |
| span.style.textShadow='3px 3px 8px rgba(0,0,0,.9),0 0 20px rgba(0,0,0,.8)'; | |
| span.style.webkitTextStroke=''; | |
| } else if(sty==='glow'){ | |
| const gc=colMap[col]||'#fff'; | |
| span.style.background='transparent'; | |
| span.style.textShadow=`0 0 8px ${gc},0 0 20px ${gc},0 0 40px ${gc},2px 2px 4px #000`; | |
| span.style.webkitTextStroke=''; | |
| } else if(sty==='stroke'){ | |
| span.style.background='transparent'; | |
| span.style.textShadow='none'; | |
| span.style.webkitTextStroke='2px #000'; | |
| } else if(sty==='plain'){ | |
| span.style.background='transparent'; | |
| span.style.textShadow='none'; | |
| span.style.webkitTextStroke=''; | |
| } else { | |
| // outline (default) | |
| span.style.background='transparent'; | |
| span.style.textShadow='0 0 4px #000,0 1px 6px #000,-1px -1px 0 #000,1px 1px 0 #000'; | |
| span.style.webkitTextStroke=''; | |
| } | |
| // Sync drawtext preview box | |
| const dtPrev=document.getElementById('srt-sub-preview-text'); | |
| if(dtPrev){ | |
| dtPrev.style.color=colMap[col]||'#fff'; | |
| if(sty==='box'){dtPrev.style.background='rgba(0,0,0,.65)';dtPrev.style.textShadow='none';dtPrev.style.webkitTextStroke='';} | |
| else if(sty==='shadow'){dtPrev.style.background='transparent';dtPrev.style.textShadow='3px 3px 8px rgba(0,0,0,.9),0 0 20px rgba(0,0,0,.8)';dtPrev.style.webkitTextStroke='';} | |
| else if(sty==='glow'){const gc=colMap[col]||'#fff';dtPrev.style.background='transparent';dtPrev.style.textShadow=`0 0 8px ${gc},0 0 20px ${gc},0 0 40px ${gc},2px 2px 4px #000`;dtPrev.style.webkitTextStroke='';} | |
| else if(sty==='stroke'){dtPrev.style.background='transparent';dtPrev.style.textShadow='none';dtPrev.style.webkitTextStroke='2px #000';} | |
| else if(sty==='plain'){dtPrev.style.background='transparent';dtPrev.style.textShadow='none';dtPrev.style.webkitTextStroke='';} | |
| else{dtPrev.style.background='transparent';dtPrev.style.textShadow='0 0 4px #000,0 1px 6px #000,-1px -1px 0 #000,1px 1px 0 #000';dtPrev.style.webkitTextStroke='';} | |
| } | |
| } | |
| /* ── Drag/Resize for SRT canvas (same logic as movie recap initDrag) ── */ | |
| function initSrtDrag(){ | |
| ['srt-db-blur','srt-db-wm','srt-db-logo'].forEach(id=>{ | |
| const el=document.getElementById(id); | |
| if(el){srtMakeDrag(el);srtMakeResize(el,el.querySelector('.rh'));} | |
| }); | |
| } | |
| function srtMakeDrag(el){ | |
| if(el._srtDragDone) return; | |
| el._srtDragDone=true; | |
| let ox,oy,ex,ey; | |
| const down=e=>{ | |
| if(e.target.classList.contains('rh'))return; | |
| e.preventDefault(); | |
| const t=e.touches?e.touches[0]:e; | |
| const pr=document.getElementById('srt-canvas-inner'); | |
| const r=el.getBoundingClientRect(),pR=pr.getBoundingClientRect(); | |
| ox=t.clientX;oy=t.clientY; | |
| ex=r.left-pR.left;ey=r.top-pR.top; | |
| const mv=e=>{ | |
| e.preventDefault(); | |
| const t=e.touches?e.touches[0]:e; | |
| el.style.left=(ex+t.clientX-ox)+'px'; | |
| el.style.top=(ey+t.clientY-oy)+'px'; | |
| el.style.bottom='auto'; | |
| }; | |
| const up=()=>{ | |
| document.removeEventListener('mousemove',mv); | |
| document.removeEventListener('mouseup',up); | |
| document.removeEventListener('touchmove',mv); | |
| document.removeEventListener('touchend',up); | |
| }; | |
| document.addEventListener('mousemove',mv); | |
| document.addEventListener('mouseup',up); | |
| document.addEventListener('touchmove',mv,{passive:false}); | |
| document.addEventListener('touchend',up); | |
| }; | |
| el.addEventListener('mousedown',down); | |
| el.addEventListener('touchstart',down,{passive:false}); | |
| } | |
| function srtMakeResize(el,h){ | |
| if(!h||h._srtResizeDone) return; | |
| h._srtResizeDone=true; | |
| const down=e=>{ | |
| e.stopPropagation();e.preventDefault(); | |
| const t=e.touches?e.touches[0]:e; | |
| const sw=el.offsetWidth,sh=el.offsetHeight,sx=t.clientX,sy=t.clientY; | |
| const mv=e=>{ | |
| e.preventDefault(); | |
| const t=e.touches?e.touches[0]:e; | |
| el.style.width=Math.max(40,sw+t.clientX-sx)+'px'; | |
| el.style.height=Math.max(30,sh+t.clientY-sy)+'px'; | |
| }; | |
| const up=()=>{ | |
| document.removeEventListener('mousemove',mv); | |
| document.removeEventListener('mouseup',up); | |
| document.removeEventListener('touchmove',mv); | |
| document.removeEventListener('touchend',up); | |
| }; | |
| document.addEventListener('mousemove',mv); | |
| document.addEventListener('mouseup',up); | |
| document.addEventListener('touchmove',mv,{passive:false}); | |
| document.addEventListener('touchend',up); | |
| }; | |
| h.addEventListener('mousedown',down); | |
| h.addEventListener('touchstart',down,{passive:false}); | |
| } | |
| function getSrtBoxPct(id){ | |
| const el=document.getElementById(id); | |
| const ci=document.getElementById('srt-canvas-inner'); | |
| const video=document.getElementById('srt-pvid'); | |
| const crop=document.getElementById('srt-crop').value; | |
| const srcW=video.videoWidth||720, srcH=video.videoHeight||1280; | |
| let tW,tH; | |
| if(crop==='9:16'){tW=720;tH=1280;} | |
| else if(crop==='16:9'){tW=1280;tH=720;} | |
| else if(crop==='1:1'){tW=720;tH=720;} | |
| else{tW=srcW;tH=srcH;} | |
| const ciR=ci.getBoundingClientRect(); | |
| const elR=el.getBoundingClientRect(); | |
| const sx=tW/ciR.width, sy=tH/ciR.height; | |
| return{ | |
| x:Math.max(0,Math.min(1,((elR.left-ciR.left)*sx)/tW)), | |
| y:Math.max(0,Math.min(1,((elR.top-ciR.top)*sy)/tH)), | |
| w:Math.max(0,Math.min(1,(elR.width*sx)/tW)), | |
| h:Math.max(0,Math.min(1,(elR.height*sy)/tH)) | |
| }; | |
| } | |
| function hasSrtVid(){ | |
| return document.getElementById('srt-vurl').value.trim()|| | |
| (document.getElementById('srt-vfile').files||[]).length>0; | |
| } | |
| /* ── Progress helpers ── */ | |
| function srtShowProg(on){ | |
| document.getElementById('srt-pc').style.display=on?'block':'none'; | |
| if(on){ | |
| document.getElementById('srt-pb').style.width='0%'; | |
| document.getElementById('srt-pmsg').textContent='⏳ တန်းစီစောင့်နေသည်…'; | |
| document.getElementById('srt-ptmr').textContent='⏱ 0s'; | |
| ['srt-ps-dl','srt-ps-ai','srt-ps-burn'].forEach(id=>{document.getElementById(id).className='ps';}); | |
| } | |
| } | |
| function srtUpdSteps(pct){ | |
| const MAP={8:'srt-ps-dl',50:'srt-ps-ai',75:'srt-ps-burn'}; | |
| let hit=false; | |
| Object.entries(MAP).forEach(([p,id])=>{ | |
| const el=document.getElementById(id); | |
| if(pct>=parseInt(p)){el.className='ps done';} | |
| else if(!hit){el.className='ps active';hit=true;} | |
| else el.className='ps'; | |
| }); | |
| } | |
| function srtStartTick(){SRT_T0=Date.now();if(SRT_TICK)clearInterval(SRT_TICK);SRT_TICK=setInterval(()=>{document.getElementById('srt-ptmr').textContent='⏱ '+fmt(Math.floor((Date.now()-SRT_T0)/1000));},1000);} | |
| function srtStopTick(){if(SRT_TICK){clearInterval(SRT_TICK);SRT_TICK=null;}} | |
| function srtResetBtn(){ | |
| const b=document.getElementById('srt-gen-btn'); | |
| if(b){b.disabled=false;b.innerHTML='<i class="fas fa-robot"></i> Generate Myanmar SRT <span style="opacity:.7;font-size:.78em;background:rgba(255,255,255,.2);padding:2px 8px;border-radius:10px">1 Coin</span>';} | |
| const b2=document.getElementById('srt-burn-btn'); | |
| if(b2){b2.disabled=false;b2.innerHTML='<i class="fas fa-film"></i> Render Video';} | |
| _updateRegenBtn(); | |
| } | |
| /* ── STEP 1: Generate SRT ── */ | |
| async function doGenerateSrt(){ | |
| if(!hasSrtVid()){toast('❌ Video URL သို့ file ထည့်ပါ');return;} | |
| if(!U){toast('❌ Login ဦးဝင်ပါ');return;} | |
| const btn=document.getElementById('srt-gen-btn'); | |
| btn.disabled=true; | |
| btn.innerHTML='<i class="fas fa-spinner sp"></i> Generating…'; | |
| const pc=document.getElementById('srt-gen-pc'); | |
| pc.style.display='block'; | |
| document.getElementById('srt-step2-wrap').style.display='none'; | |
| document.getElementById('srt-rc').style.display='none'; | |
| // Start timer | |
| SRT_T0=Date.now(); | |
| if(SRT_TICK) clearInterval(SRT_TICK); | |
| SRT_TICK=setInterval(()=>{ | |
| const el=document.getElementById('srt-gen-ptmr'); | |
| if(el) el.textContent='⏱ '+Math.floor((Date.now()-SRT_T0)/1000)+'s'; | |
| },1000); | |
| const fd=new FormData(); | |
| fd.append('username',U); | |
| const vurl=document.getElementById('srt-vurl').value.trim(); | |
| const vf=document.getElementById('srt-vfile').files[0]; | |
| if(vf) fd.append('video_file',vf); | |
| else fd.append('video_url',vurl); | |
| if(SRT_CACHE_KEY) fd.append('cache_key',SRT_CACHE_KEY); | |
| const tid=uid8(); fd.append('tid',tid); | |
| try{ | |
| const ctrl=new AbortController(),tout=setTimeout(()=>ctrl.abort(),1200000); | |
| const r=await fetch('/api/generate_srt',{method:'POST',body:fd,signal:ctrl.signal}); | |
| clearTimeout(tout); | |
| if(!r.ok){_srtGenFail('❌ Server error '+r.status);return;} | |
| const d=await r.json(); | |
| if(!d.ok){_srtGenFail(d.msg||'❌ Failed');return;} | |
| _srtGenSSE(tid); | |
| }catch(e){ | |
| _srtGenFail(e.name==='AbortError'?'❌ Timeout':'❌ '+e); | |
| } | |
| } | |
| function _srtGenFail(msg){ | |
| clearInterval(SRT_TICK); SRT_TICK=null; | |
| document.getElementById('srt-gen-pc').style.display='none'; | |
| srtResetBtn(); toast(msg); | |
| } | |
| function _srtGenSSE(tid){ | |
| const sse=new EventSource('/api/progress/'+tid); | |
| sse.onmessage=e=>{ | |
| try{ | |
| const p=JSON.parse(e.data); | |
| document.getElementById('srt-gen-pb').style.width=(p.pct||0)+'%'; | |
| document.getElementById('srt-gen-pmsg').textContent=(p.msg||'').trim(); | |
| // Steps | |
| if((p.pct||0)>=8) document.getElementById('srt-ps-dl').classList.add('ps-done'); | |
| if((p.pct||0)>=50) document.getElementById('srt-ps-ai').classList.add('ps-done'); | |
| if(p.done){ | |
| sse.close(); clearInterval(SRT_TICK); SRT_TICK=null; | |
| document.getElementById('srt-gen-pc').style.display='none'; | |
| srtResetBtn(); | |
| if(p.coins!=null&&p.coins!==-1) updC(p.coins); | |
| SRT_TEXT=p.srt||''; | |
| SRT_VPATH_KEY=p.vpath_key||tid; | |
| SRT_REGEN_COUNT++; | |
| _updateRegenBtn(); | |
| _srtShowStep2(p); | |
| toast('✅ SRT ပြီးပါပြီ!'); | |
| } else if(p.error){ | |
| sse.close(); _srtGenFail((p.msg||'').trim()||'❌ Failed'); | |
| } | |
| }catch(ex){} | |
| }; | |
| } | |
| function _srtShowStep2(p){ | |
| // Show preview box | |
| const box=document.getElementById('srt-preview-box'); | |
| box.textContent=SRT_TEXT; | |
| // Block count badge | |
| const cnt=document.getElementById('srt-block-count'); | |
| if(cnt) cnt.textContent=(p.total||0)+' lines'; | |
| // First subtitle on thumbnail | |
| const blocks=SRT_TEXT.trim().split(/\n\s*\n/); | |
| let firstTxt=''; | |
| for(const blk of blocks){ | |
| const lines=blk.trim().split('\n'); | |
| for(let i=0;i<lines.length;i++){ | |
| if(lines[i].includes('-->')){ | |
| firstTxt=lines.slice(i+1).join(' ').trim(); | |
| break; | |
| } | |
| } | |
| if(firstTxt) break; | |
| } | |
| const fsw=document.getElementById('srt-first-sub-wrap'); | |
| const fst=document.getElementById('srt-first-sub-txt'); | |
| if(firstTxt && fsw && fst){ | |
| fst.textContent=firstTxt; | |
| fsw.style.display='block'; | |
| // Show on thumbnail overlay | |
| const span=document.getElementById('srt-sub-preview-span'); | |
| if(span) span.textContent=firstTxt; | |
| syncSrtSubPreview(); | |
| } | |
| document.getElementById('srt-step2-wrap').style.display='block'; | |
| document.getElementById('srt-step2-wrap').scrollIntoView({behavior:'smooth'}); | |
| } | |
| /* ── Regen button lock state ── */ | |
| function _updateRegenBtn(){ | |
| const btn=document.querySelector('[onclick="doRegenSrt()"]'); | |
| if(!btn) return; | |
| if(SRT_REGEN_COUNT>=2){ | |
| btn.disabled=true; | |
| btn.style.opacity='0.4'; | |
| btn.style.cursor='not-allowed'; | |
| btn.innerHTML='<i class="fas fa-lock"></i> Regen (၂/၂ ကုန်ပြီ)'; | |
| } else { | |
| btn.disabled=false; | |
| btn.style.opacity=''; | |
| btn.style.cursor=''; | |
| btn.innerHTML='<i class="fas fa-redo"></i> Regenerate ('+(2-SRT_REGEN_COUNT)+' ကြိမ် ကျန်)'; | |
| } | |
| } | |
| /* ── Regenerate SRT (Step 1 again, same video) ── */ | |
| async function doRegenSrt(){ | |
| if(!SRT_VPATH_KEY && !hasSrtVid()){toast('❌ Video မရှိပါ');return;} | |
| if(!U){toast('❌ Login ဦးဝင်ပါ');return;} | |
| if(SRT_REGEN_COUNT >= 2){toast('❌ Regenerate ၂ ခါ ကုန်ပြီ — Video အသစ်တင်ပါ');return;} | |
| document.getElementById('srt-step2-wrap').style.display='none'; | |
| SRT_TEXT=''; SRT_VPATH_KEY=''; | |
| const fd=new FormData(); | |
| fd.append('username',U); | |
| const vurl=document.getElementById('srt-vurl').value.trim(); | |
| const vf=document.getElementById('srt-vfile').files[0]; | |
| if(vf) fd.append('video_file',vf); | |
| else fd.append('video_url',vurl); | |
| if(SRT_CACHE_KEY) fd.append('cache_key',SRT_CACHE_KEY); | |
| fd.append('free_regen','1'); | |
| const tid=uid8(); fd.append('tid',tid); | |
| const btn=document.querySelector('[onclick="doRegenSrt()"]'); | |
| if(btn){btn.disabled=true;btn.innerHTML='<i class="fas fa-spinner sp"></i> Regenerating…';} | |
| const pc=document.getElementById('srt-gen-pc'); | |
| pc.style.display='block'; | |
| SRT_T0=Date.now(); | |
| if(SRT_TICK) clearInterval(SRT_TICK); | |
| SRT_TICK=setInterval(()=>{ | |
| const el=document.getElementById('srt-gen-ptmr'); | |
| if(el) el.textContent='⏱ '+Math.floor((Date.now()-SRT_T0)/1000)+'s'; | |
| },1000); | |
| try{ | |
| const ctrl=new AbortController(),tout=setTimeout(()=>ctrl.abort(),600000); | |
| const r=await fetch('/api/generate_srt',{method:'POST',body:fd,signal:ctrl.signal}); | |
| clearTimeout(tout); | |
| const d=await r.json(); | |
| if(!d.ok){pc.style.display='none';clearInterval(SRT_TICK);_updateRegenBtn();toast(d.msg||'❌ Failed');return;} | |
| _srtGenSSE(d.tid||tid); | |
| }catch(e){ | |
| pc.style.display='none'; clearInterval(SRT_TICK); | |
| _updateRegenBtn(); | |
| toast('❌ '+e); | |
| } | |
| } | |
| /* ── STEP 2: Burn/Process SRT ── */ | |
| async function doProcessSrt(){ | |
| if(!SRT_TEXT){toast('❌ SRT မရှိပါ — Generate ဦးလုပ်ပါ');return;} | |
| if(!U){toast('❌ Login ဦးဝင်ပါ');return;} | |
| const btn=document.getElementById('srt-burn-btn'); | |
| btn.disabled=true; | |
| btn.innerHTML='<i class="fas fa-spinner sp"></i> Rendering…'; | |
| document.getElementById('srt-pc').style.display='block'; | |
| document.getElementById('srt-rc').style.display='none'; | |
| SRT_T0=Date.now(); | |
| if(SRT_TICK) clearInterval(SRT_TICK); | |
| SRT_TICK=setInterval(()=>{ | |
| const el=document.getElementById('srt-ptmr'); | |
| if(el) el.textContent='⏱ '+Math.floor((Date.now()-SRT_T0)/1000)+'s'; | |
| },1000); | |
| const fd=new FormData(); | |
| fd.append('username',U); | |
| fd.append('srt_text',SRT_TEXT); | |
| fd.append('vpath_key',SRT_VPATH_KEY); | |
| // Video source fallback | |
| const vurl=document.getElementById('srt-vurl').value.trim(); | |
| const vf=document.getElementById('srt-vfile').files[0]; | |
| if(vf) fd.append('video_file',vf); | |
| else if(vurl) fd.append('video_url',vurl); | |
| if(SRT_CACHE_KEY) fd.append('cache_key',SRT_CACHE_KEY); | |
| // Subtitle settings | |
| fd.append('sub_pos',document.getElementById('srt-sub-pos').value); | |
| fd.append('sub_size',document.getElementById('srt-sub-size').value); | |
| fd.append('sub_color',document.getElementById('srt-sub-color').value); | |
| fd.append('sub_style',document.getElementById('srt-sub-style').value); | |
| // Video settings | |
| fd.append('crop',document.getElementById('srt-crop').value); | |
| fd.append('flip',SRT_FLIP?'1':'0'); | |
| fd.append('color',SRT_COL?'1':'0'); | |
| fd.append('zoom_enabled',SRT_ZOOM?'1':'0'); | |
| if(SRT_ZOOM){ | |
| const zvEl=document.getElementById('srt-zoom-factor'); | |
| const zv=zvEl?zvEl.value:'105'; | |
| fd.append('zoom_factor',(+zv/100).toFixed(2)); | |
| } | |
| fd.append('audio_boost',SRT_AUDIO?'1':'0'); | |
| // Blur | |
| fd.append('blur_enabled',SRT_BLUR_ON?'1':'0'); | |
| if(SRT_BLUR_ON){ | |
| const bp=getSrtBoxPct('srt-db-blur'); | |
| fd.append('blur_xp',bp.x.toFixed(4));fd.append('blur_yp',bp.y.toFixed(4)); | |
| fd.append('blur_wp',bp.w.toFixed(4));fd.append('blur_hp',bp.h.toFixed(4)); | |
| } | |
| // Watermark | |
| const wmkTxt=SRT_WM_ON?(document.getElementById('srt-wm-text').value.trim()):''; | |
| fd.append('watermark',wmkTxt); | |
| if(SRT_WM_ON&&wmkTxt){ | |
| const wp=getSrtBoxPct('srt-db-wm'); | |
| fd.append('wmk_xp',wp.x.toFixed(4));fd.append('wmk_yp',wp.y.toFixed(4)); | |
| fd.append('wmk_fontsize','28'); | |
| } | |
| // Logo | |
| const lf=document.getElementById('srt-logo-file'); | |
| if(SRT_LOGO_ON&&lf&&lf.files[0]){ | |
| fd.append('logo_file',lf.files[0]); | |
| const lp=getSrtBoxPct('srt-db-logo'); | |
| fd.append('logo_xp',lp.x.toFixed(4));fd.append('logo_yp',lp.y.toFixed(4)); | |
| fd.append('logo_wp',lp.w.toFixed(4)); | |
| } | |
| const tid=uid8(); fd.append('tid',tid); | |
| try{ | |
| const ctrl=new AbortController(),tout=setTimeout(()=>ctrl.abort(),600000); | |
| const r=await fetch('/api/burn_srt',{method:'POST',body:fd,signal:ctrl.signal}); | |
| clearTimeout(tout); | |
| if(!r.ok){_srtBurnFail('❌ Server error '+r.status);return;} | |
| const d=await r.json(); | |
| if(!d.ok){_srtBurnFail(d.msg||'❌ Failed');return;} | |
| _srtBurnSSE(tid); | |
| }catch(e){ | |
| _srtBurnFail(e.name==='AbortError'?'❌ Timeout':'❌ '+e); | |
| } | |
| } | |
| function _srtBurnFail(msg){ | |
| clearInterval(SRT_TICK); SRT_TICK=null; | |
| document.getElementById('srt-pc').style.display='none'; | |
| srtResetBtn(); toast(msg); | |
| } | |
| function _srtBurnSSE(tid){ | |
| if(SRT_SSE) SRT_SSE.close(); | |
| SRT_SSE=new EventSource('/api/progress/'+tid); | |
| SRT_SSE.onmessage=e=>{ | |
| try{ | |
| const p=JSON.parse(e.data); | |
| document.getElementById('srt-pb').style.width=(p.pct||0)+'%'; | |
| document.getElementById('srt-pmsg').textContent=(p.msg||'').trim(); | |
| if((p.pct||0)>=75) document.getElementById('srt-ps-burn').classList.add('ps-done'); | |
| if(p.done){ | |
| SRT_SSE.close(); SRT_SSE=null; clearInterval(SRT_TICK); SRT_TICK=null; | |
| document.getElementById('srt-pc').style.display='none'; | |
| srtResetBtn(); | |
| if(p.coins!=null&&p.coins!==-1) updC(p.coins); | |
| SRT_VOUT=p.output_url||''; | |
| document.getElementById('srt-rvid').src=SRT_VOUT; | |
| document.getElementById('srt-rc').style.display='block'; | |
| document.getElementById('srt-rc').scrollIntoView({behavior:'smooth'}); | |
| toast('✅ Video ပြီးပါပြီ!'); | |
| } else if(p.error){ | |
| SRT_SSE.close(); SRT_SSE=null; _srtBurnFail((p.msg||'').trim()||'❌ Failed'); | |
| } | |
| }catch(ex){} | |
| }; | |
| } | |
| function srtDlVideo(){ | |
| if(!SRT_VOUT) return; | |
| const a=document.createElement('a'); a.href=SRT_VOUT; a.download='myanmar_subtitle_video.mp4'; a.click(); | |
| } | |
| function srtReset(){ | |
| if(SRT_SSE){SRT_SSE.close();SRT_SSE=null;} | |
| srtStopTick(); srtShowProg(false); | |
| SRT_VOUT=''; SRT_CACHE_KEY=''; SRT_TEXT=''; SRT_VPATH_KEY=''; SRT_REGEN_COUNT=0; | |
| SRT_FLIP=false; SRT_COL=false; SRT_ZOOM=false; SRT_AUDIO=false; | |
| SRT_BLUR_ON=false; SRT_WM_ON=false; SRT_LOGO_ON=false; | |
| document.getElementById('srt-vurl').value=''; | |
| try{document.getElementById('srt-vfile').value='';}catch(e){} | |
| document.getElementById('srt-upzn').style.display='none'; | |
| document.getElementById('srt-ubadge').style.display='none'; | |
| document.getElementById('srt-pvid').style.display='none'; | |
| document.getElementById('srt-pvid').style.transform=''; | |
| document.getElementById('srt-pthumb').style.display='none'; | |
| document.getElementById('srt-pthumb').style.transform=''; | |
| document.getElementById('srt-canvas-ph').style.display='flex'; | |
| const sov=document.getElementById('srt-sub-overlay');if(sov)sov.style.display='none'; | |
| document.getElementById('srt-rc').style.display='none'; | |
| document.getElementById('srt-step2-wrap').style.display='none'; | |
| document.getElementById('srt-gen-pc').style.display='none'; | |
| const pb=document.getElementById('srt-preview-box');if(pb)pb.textContent=''; | |
| const fsw=document.getElementById('srt-first-sub-wrap');if(fsw)fsw.style.display='none'; | |
| const bflip=document.getElementById('srt-btn-flip'); | |
| bflip.style.background='rgba(255,255,255,.05)';bflip.style.borderColor='rgba(255,255,255,.12)';bflip.style.color='rgba(255,255,255,.55)'; | |
| bflip.innerHTML='<i class="fas fa-arrows-alt-h"></i> Flip: Off'; | |
| const bcol=document.getElementById('srt-btn-col'); | |
| bcol.style.background='rgba(255,255,255,.05)';bcol.style.borderColor='rgba(255,255,255,.12)';bcol.style.color='rgba(255,255,255,.55)'; | |
| bcol.innerHTML='<i class="fas fa-adjust"></i> Color: Off'; | |
| ['srt-ov-blur','srt-ov-wm','srt-ov-logo','srt-ov-zoom','srt-ov-audio'].forEach(id=>{ | |
| const b=document.getElementById(id); if(b){b.style.background='';b.style.borderColor='';b.style.color='';b.textContent='Off';} | |
| }); | |
| ['srt-db-blur','srt-db-wm','srt-db-logo'].forEach(id=>{ | |
| const el=document.getElementById(id); | |
| if(el){el.style.display='none';el._srtDragDone=false;const rh=el.querySelector('.rh');if(rh)rh._srtResizeDone=false;} | |
| }); | |
| ['srt-ov-wm-wrap','srt-ov-logo-wrap','srt-ov-blur-hint'].forEach(id=>{const el=document.getElementById(id);if(el)el.style.display='none';}); | |
| const lbl=document.getElementById('srt-db-logo-lbl'); if(lbl)lbl.style.display='flex'; | |
| const ln=document.getElementById('srt-logo-name'); if(ln){ln.textContent='Click to upload PNG / JPG';ln.style.color='';} | |
| const db=document.getElementById('srt-db-logo'); if(db){db.style.backgroundImage='';} | |
| srtResetBtn(); | |
| } | |
| </script> | |
| <!-- ══ PAYMENT MODAL ══ --> | |
| <div class="pov" id="pov" onclick="if(event.target===this)closePay()"> | |
| <div class="psheet"> | |
| <!-- Header --> | |
| <div class="psheet-hdr"> | |
| <h2><span>🪙</span> Buy Credits</h2> | |
| <button class="pclose" onclick="closePay()"><i class="fas fa-times"></i></button> | |
| </div> | |
| <!-- Tabs --> | |
| <div class="tab2"> | |
| <button id="ptab-buy" class="on" onclick="showPayTab('buy')">🛒 ဝယ်ရန်</button> | |
| <button id="ptab-his" onclick="showPayTab('his')">📋 မှတ်တမ်း</button> | |
| </div> | |
| <!-- BUY TAB --> | |
| <div id="ptab-buy-content"> | |
| <!-- STEP 1: Package selection --> | |
| <div class="pay-step on" id="pay-step-1"> | |
| <div class="pstep-lbl"><span class="pstep-num">1</span> Package ရွေးပါ</div> | |
| <!-- Currency tabs --> | |
| <div style="display:flex;gap:8px;padding:10px 22px 0"> | |
| <button id="ctab-mmk" onclick="renderPkgs('mmk')" style="flex:1;padding:10px 8px;border-radius:12px;border:2px solid rgba(139,92,246,.5);background:rgba(124,58,237,.2);color:#c4b5fd;font-family:var(--F);font-size:.82rem;font-weight:800;cursor:pointer;transition:.2s;display:flex;align-items:center;justify-content:center;gap:6px"><span style="font-size:1.1rem">🇲🇲</span> MMK</button> | |
| <button id="ctab-thb" onclick="renderPkgs('thb')" style="flex:1;padding:10px 8px;border-radius:12px;border:2px solid rgba(255,255,255,.08);background:rgba(255,255,255,.04);color:rgba(255,255,255,.35);font-family:var(--F);font-size:.82rem;font-weight:800;cursor:pointer;transition:.2s;display:flex;align-items:center;justify-content:center;gap:6px"><span style="font-size:1.1rem">🇹🇭</span> THB</button> | |
| </div> | |
| <div class="ppkgs" id="ppkgs" style="display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:14px 22px 4px"></div> | |
| <button class="buy-now-btn" id="buy-now-btn" onclick="goPayStep2()" disabled style="margin:0 22px 4px"> | |
| ဝယ်ရန် → | |
| </button> | |
| </div> | |
| <!-- STEP 2: Payment method --> | |
| <div class="pay-step" id="pay-step-2"> | |
| <button class="pback-btn" onclick="goPayStep(1)"><i class="fas fa-arrow-left"></i> ပြန်သွား</button> | |
| <div class="pstep-lbl" style="margin-top:6px"><span class="pstep-num">2</span> ငွေပေးချေနည်း ရွေးပါ</div> | |
| <div class="pmeth-grid"> | |
| <div class="pmeth" id="pmeth-kbz" onclick="selMeth('kbz')"> | |
| <img class="pmeth-icon" src="https://raw.githubusercontent.com/Shangyi69/psonlineshop/main/kbz.png" alt="KBZ Pay"> | |
| <div class="pmeth-name">KBZ Pay</div> | |
| </div> | |
| <div class="pmeth" id="pmeth-wave" style="opacity:.4;pointer-events:none;position:relative"> | |
| <img class="pmeth-icon" src="https://raw.githubusercontent.com/Shangyi69/psonlineshop/main/wave.jpeg" alt="Wave Money"> | |
| <div class="pmeth-name">Wave Money</div> | |
| <div class="pmeth-sub" style="color:#ef4444;font-size:.6rem">ခဏပိတ်ထားသည်</div> | |
| </div> | |
| <div class="pmeth" id="pmeth-scb" onclick="selMeth('scb')"> | |
| <img class="pmeth-icon" src="https://raw.githubusercontent.com/Shangyi69/psonlineshop/main/scb.jpeg" alt="SCB"> | |
| <div class="pmeth-name">SCB Bank</div> | |
| <div class="pmeth-sub">ไทยบาท</div> | |
| </div> | |
| <div class="pmeth" id="pmeth-prompt" onclick="selMeth('prompt')"> | |
| <img class="pmeth-icon" src="https://raw.githubusercontent.com/Shangyi69/psonlineshop/main/prompt.png" alt="PromptPay"> | |
| <div class="pmeth-name">PromptPay</div> | |
| <div class="pmeth-sub">ไทยบาท</div> | |
| </div> | |
| <div class="pmeth" id="pmeth-truemoney" onclick="selMeth('truemoney')"> | |
| <img class="pmeth-icon" src="https://raw.githubusercontent.com/Shangyi69/psonlineshop/main/true.png" alt="TrueMoney"> | |
| <div class="pmeth-name">TrueMoney</div> | |
| <div class="pmeth-sub">ไทยบาท</div> | |
| </div> | |
| </div> | |
| <!-- Payment info box (shown after method select) --> | |
| <div id="pay-info-box" style="display:none"> | |
| <div class="pinfo-box" id="pinfo-box-inner"></div> | |
| <button class="buy-now-btn" onclick="goPayStep3()"> | |
| Slip တင်မည် → | |
| </button> | |
| </div> | |
| </div> | |
| <!-- STEP 3: Slip upload + submit --> | |
| <div class="pay-step" id="pay-step-3"> | |
| <button class="pback-btn" onclick="goPayStep(2)"><i class="fas fa-arrow-left"></i> ပြန်သွား</button> | |
| <div class="pstep-lbl" style="margin-top:6px"><span class="pstep-num">3</span> Slip တင်ပေးပါ</div> | |
| <div class="pslip-drop" style="margin-top:10px" id="pslip-drop"> | |
| <input type="file" accept="image/*" id="pslip-input" onchange="handlePSlip(this)"> | |
| <div class="pi" id="pslip-icon">📎</div> | |
| <div class="pt" id="pslip-txt">Slip ပုံကို ဒီနေရာ ဖိဆွဲချပါ<br><span style="color:rgba(255,255,255,.2);font-size:.7rem">သို့မဟုတ် နှိပ်ပြီး ရွေးပါ</span></div> | |
| <img id="pslip-prev" class="pslip-preview"> | |
| </div> | |
| <button class="psubmit on" id="psubmit" onclick="submitPay()" disabled> | |
| <i class="fas fa-paper-plane" style="margin-right:8px"></i>Payment တင်ပေးပါ | |
| </button> | |
| </div> | |
| </div> | |
| <!-- HISTORY TAB --> | |
| <div id="ptab-his-content" style="display:none"> | |
| <div class="ph-list-wrap"> | |
| <div id="ph-list"><div class="ph-empty">⏳ Loading…</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ══ VIDEO HISTORY MODAL ══ --> | |
| <!-- Broadcast Modal --> | |
| <div id="bc-modal" style="display:none;position:fixed;inset:0;z-index:3000;align-items:center;justify-content:center;padding:16px;box-sizing:border-box" onclick="if(event.target===this)closeBroadcast()"> | |
| <div style="position:absolute;inset:0;background:rgba(0,0,0,.6);backdrop-filter:blur(6px)"></div> | |
| <div style="position:relative;width:100%;max-width:420px;background:linear-gradient(135deg,#1a1a2e 0%,#16213e 100%);border:1px solid rgba(99,102,241,.3);border-radius:20px;padding:24px;box-shadow:0 20px 60px rgba(0,0,0,.5)"> | |
| <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px"> | |
| <div style="display:flex;align-items:center;gap:10px"> | |
| <div style="width:36px;height:36px;border-radius:10px;background:rgba(99,102,241,.2);border:1px solid rgba(99,102,241,.4);display:flex;align-items:center;justify-content:center;color:#6366f1;font-size:.95rem"><i class="fas fa-bullhorn"></i></div> | |
| <div> | |
| <div style="font-weight:800;font-size:1rem;color:#fff">Broadcast</div> | |
| <div style="font-size:.7rem;color:rgba(255,255,255,.4)">Users အားလုံးထံ ပို့မည်</div> | |
| </div> | |
| </div> | |
| <button onclick="closeBroadcast()" style="background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.12);border-radius:8px;width:30px;height:30px;color:rgba(255,255,255,.5);cursor:pointer;font-size:.8rem;display:flex;align-items:center;justify-content:center"><i class="fas fa-times"></i></button> | |
| </div> | |
| <textarea id="bc-msg" placeholder="ကြေငြာချက် ရေးပါ… Markdown ပံ့ပိုးသည် — *bold* _italic_" style="width:100%;box-sizing:border-box;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1);border-radius:12px;color:#fff;padding:14px;font-size:.85rem;font-family:var(--F);resize:none;min-height:120px;outline:none;line-height:1.6;transition:.2s" onfocus="this.style.borderColor='rgba(99,102,241,.5)'" onblur="this.style.borderColor='rgba(255,255,255,.1)'"></textarea> | |
| <div id="bc-char" style="font-size:.68rem;color:rgba(255,255,255,.3);text-align:right;margin-top:4px;margin-bottom:12px">0 / 4096</div> | |
| <div style="display:flex;gap:8px"> | |
| <button onclick="closeBroadcast()" style="flex:1;padding:12px;border-radius:10px;border:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.06);color:rgba(255,255,255,.6);cursor:pointer;font-family:var(--F);font-size:.83rem;font-weight:600">မပို့တော့ပါ</button> | |
| <button id="bc-send-btn" onclick="sendBroadcast()" style="flex:2;padding:12px;border-radius:10px;border:none;background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff;cursor:pointer;font-family:var(--F);font-size:.83rem;font-weight:700;display:flex;align-items:center;justify-content:center;gap:7px"><i class="fas fa-paper-plane"></i> ပို့မည်</button> | |
| </div> | |
| <div id="bc-res" style="font-size:.78rem;margin-top:10px;text-align:center;font-weight:600;min-height:18px"></div> | |
| </div> | |
| </div> | |
| <div id="vh-overlay" style="display:none;position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.7);backdrop-filter:blur(4px);overflow-y:auto;padding:20px 12px 40px"> | |
| <div style="max-width:600px;margin:0 auto"> | |
| <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px"> | |
| <h2 style="color:#fff;font-size:1.2rem;margin:0"><i class="fas fa-film" style="color:#a855f7;margin-right:8px"></i>Video History</h2> | |
| <button onclick="closeVideoHistory()" style="background:rgba(255,255,255,.1);border:none;color:#fff;width:36px;height:36px;border-radius:50%;font-size:1.1rem;cursor:pointer"><i class="fas fa-times"></i></button> | |
| </div> | |
| <p style="color:#888;font-size:.82rem;margin:0 0 16px">Videos are kept for 24 hours after creation.</p> | |
| <div id="vh-list"></div> | |
| </div> | |
| </div> | |
| <style> | |
| .vh-card{background:#1a1a2e;border:1px solid rgba(168,85,247,.2);border-radius:12px;overflow:hidden;margin-bottom:14px} | |
| .vh-thumb-wrap{position:relative;width:100%;aspect-ratio:16/9;background:#0d0d1a;cursor:pointer} | |
| .vh-thumb-wrap video{width:100%;height:100%;object-fit:contain;display:block} | |
| .vh-thumb-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.3);transition:opacity .2s} | |
| .vh-thumb-overlay:hover{background:rgba(0,0,0,.1)} | |
| .vh-play-ic{width:52px;height:52px;border-radius:50%;background:rgba(168,85,247,.85);display:flex;align-items:center;justify-content:center;color:#fff;font-size:1.3rem;pointer-events:none} | |
| .vh-info{padding:12px 14px} | |
| .vh-title{color:#e0d7ff;font-size:.95rem;font-weight:600;margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} | |
| .vh-meta{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:10px} | |
| .vh-time{color:#888;font-size:.78rem}<br>.vh-url{color:#888;font-size:.75rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:280px} | |
| .vh-ttl{color:#a855f7;font-size:.75rem;background:rgba(168,85,247,.12);padding:2px 8px;border-radius:10px} | |
| .vh-dl-btn{display:inline-flex;align-items:center;gap:6px;padding:7px 16px;background:linear-gradient(135deg,#7c3aed,#a855f7);border:none;border-radius:8px;color:#fff;font-size:.82rem;cursor:pointer;font-weight:600;transition:opacity .2s} | |
| .vh-dl-btn:hover{opacity:.85} | |
| .vh-empty{text-align:center;color:#666;padding:40px 0;font-size:.9rem} | |
| </style> | |
| <script> | |
| function fmtExpire(secs){ | |
| if(secs<=0)return'Expired'; | |
| const h=Math.floor(secs/3600),m=Math.floor((secs%3600)/60); | |
| return h>0?`${h}h ${m}m left`:`${m}m left`; | |
| } | |
| async function openVideoHistory(){ | |
| document.getElementById('vh-overlay').style.display='block'; | |
| document.body.style.overflow='hidden'; | |
| const list=document.getElementById('vh-list'); | |
| list.innerHTML='<div class="vh-empty"><i class="fas fa-spinner" style="animation:spin 1s linear infinite"></i> Loading…</div>'; | |
| try{ | |
| const r=await fetch(`/api/video_history?username=${encodeURIComponent(U)}`); | |
| const d=await r.json(); | |
| if(!d.ok||!d.history||d.history.length===0){ | |
| list.innerHTML='<div class="vh-empty"><i class="fas fa-film" style="font-size:2rem;opacity:.3;display:block;margin-bottom:10px"></i>No video history yet.<br>Process a video to see it here.</div>'; | |
| return; | |
| } | |
| list.innerHTML=d.history.map(h=>{ | |
| const src=h.output_url; | |
| const srcUrl=h.source_url?`<span class="vh-url" title="${h.source_url}"><i class="fas fa-link" style="margin-right:3px"></i>${h.source_url}</span>`:''; | |
| return `<div class="vh-card"> | |
| <div class="vh-thumb-wrap" onclick="this.querySelector('video').paused?this.querySelector('video').play():this.querySelector('video').pause()"> | |
| <video src="${src}" preload="metadata" playsinline></video> | |
| <div class="vh-thumb-overlay" id="vhov_${h.tid}"> | |
| <div class="vh-play-ic"><i class="fas fa-play" style="margin-left:3px"></i></div> | |
| </div> | |
| </div> | |
| <div class="vh-info"> | |
| <div class="vh-title">${h.title||'(no title)'}</div> | |
| <div class="vh-meta"> | |
| <span class="vh-time"><i class="fas fa-clock" style="margin-right:3px"></i>${h.created_at||''}</span> | |
| <span class="vh-ttl">${fmtExpire(h.expires_in||0)}</span> | |
| ${srcUrl} | |
| </div> | |
| <button class="vh-dl-btn" onclick="vhDl('${src}','${h.tid}')"><i class="fas fa-download"></i>Download</button> | |
| </div> | |
| </div>`; | |
| }).join(''); | |
| // Hide overlay when video plays | |
| list.querySelectorAll('video').forEach(v=>{ | |
| v.addEventListener('play',()=>{const ov=v.closest('.vh-thumb-wrap').querySelector('.vh-thumb-overlay');if(ov)ov.style.opacity='0';}); | |
| v.addEventListener('pause',()=>{const ov=v.closest('.vh-thumb-wrap').querySelector('.vh-thumb-overlay');if(ov)ov.style.opacity='1';}); | |
| }); | |
| }catch(e){list.innerHTML='<div class="vh-empty">❌ Failed to load history</div>';} | |
| } | |
| function closeVideoHistory(){ | |
| document.getElementById('vh-overlay').style.display='none'; | |
| document.body.style.overflow=''; | |
| // Stop all playing videos | |
| document.querySelectorAll('#vh-list video').forEach(v=>v.pause()); | |
| } | |
| function vhDl(url,tid){ | |
| const a=document.createElement('a');a.href=url;a.download=`recap_${tid}.mp4`;a.click(); | |
| } | |
| document.getElementById('vh-overlay').addEventListener('click',function(e){if(e.target===this)closeVideoHistory();}); | |
| </script> | |
| <script> | |
| if("serviceWorker" in navigator){ | |
| window.addEventListener("load",()=>{ | |
| navigator.serviceWorker.register("/sw.js") | |
| .then(()=>console.log("[PWA] SW registered")) | |
| .catch(e=>console.warn("[PWA] SW failed:",e)); | |
| }); | |
| } | |
| </script> | |
| </body> | |
| </html> |