Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <title>YouTube</title>
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,[email protected],300;9..40,400;9..40,500;9..40,600&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
- <style>
- :root{--bg:#0f0f0f;--bg2:#1a1a1a;--card:#242424;--text:#fff;--text2:#aaa;--accent:#ff0000;--hover:#333;--border:#2e2e2e;--r:12px;--sh:0 8px 32px rgba(0,0,0,.6)}
- [data-theme=amoled]{--bg:#000;--bg2:#0d0d0d;--card:#161616;--hover:#222;--border:#1e1e1e}
- [data-theme=dim]{--bg:#15192a;--bg2:#1e2438;--card:#252d45;--hover:#2e3855;--border:#2e3855}
- *{margin:0;padding:0;box-sizing:border-box}
- body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--text);overflow-x:hidden;transition:background .25s,color .25s}
- ::-webkit-scrollbar{width:5px}::-webkit-scrollbar-track{background:var(--bg)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--hover)}
- /* HEADER */
- .header{position:fixed;top:0;left:0;right:0;height:58px;background:var(--bg);border-bottom:1px solid var(--border);display:flex;align-items:center;padding:0 16px;z-index:1000;gap:12px}
- .logo{display:flex;align-items:center;gap:7px;font-size:19px;font-weight:600;letter-spacing:-.03em;cursor:pointer;flex-shrink:0;user-select:none}
- .logo-icon{width:30px;height:22px;background:var(--accent);border-radius:4px;position:relative;flex-shrink:0}
- .logo-icon::after{content:'▶';position:absolute;top:50%;left:50%;transform:translate(-40%,-50%);color:#fff;font-size:11px}
- .search-wrap{flex:1;max-width:560px;position:relative}
- .search-row{display:flex}
- .sbox{flex:1;height:38px;background:var(--bg2);border:1px solid var(--border);border-radius:19px 0 0 19px;padding:0 14px;color:var(--text);font-size:14px;font-family:inherit;transition:border-color .2s}
- .sbox:focus{outline:none;border-color:var(--accent)}
- .sbtn{height:38px;min-width:52px;background:var(--bg2);border:1px solid var(--border);border-left:none;border-radius:0 19px 19px 0;color:var(--text);cursor:pointer;font-size:13px;padding:0 13px;transition:background .15s;font-family:inherit}
- .sbtn:hover:not(:disabled){background:var(--hover)}
- .sbtn:disabled{opacity:.5;cursor:not-allowed}
- .sug-box{position:absolute;top:43px;left:0;right:52px;background:var(--card);border:1px solid var(--border);border-radius:10px;overflow:hidden;z-index:2000;box-shadow:var(--sh);display:none}
- .sug-box.on{display:block;animation:slideDown .15s ease}
- .sug-item{padding:9px 14px;cursor:pointer;font-size:13px;display:flex;align-items:center;gap:10px;color:var(--text2);transition:background .1s}
- .sug-item:hover{background:var(--hover);color:var(--text)}
- .sug-rm{margin-left:auto;font-size:11px;padding:2px 5px;border-radius:3px;transition:color .1s;line-height:1}
- .sug-rm:hover{color:var(--accent)}
- .header-right{display:flex;align-items:center;gap:5px;margin-left:auto;flex-shrink:0}
- .hbtn{width:34px;height:34px;border-radius:50%;background:transparent;border:none;color:var(--text);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:15px;transition:background .15s;position:relative}
- .hbtn:hover{background:var(--hover)}
- .badge{position:absolute;top:1px;right:1px;background:var(--accent);color:#fff;font-size:9px;font-weight:700;min-width:14px;height:14px;border-radius:7px;line-height:14px;text-align:center;padding:0 2px;display:none}
- .theme-dots{display:flex;gap:5px;align-items:center;padding:0 4px}
- .tdot{width:15px;height:15px;border-radius:50%;cursor:pointer;border:2px solid transparent;transition:transform .15s,border-color .15s;flex-shrink:0}
- .tdot:hover{transform:scale(1.3)}
- .tdot.on{border-color:var(--text)}
- .tdot[data-t=dark]{background:#0f0f0f;box-shadow:0 0 0 1px #555}
- .tdot[data-t=amoled]{background:#000;box-shadow:0 0 0 1px #888}
- .tdot[data-t=dim]{background:#1a1f2e;box-shadow:0 0 0 1px #4a5568}
- /* SIDEBAR */
- .sidebar{position:fixed;top:58px;left:0;width:232px;height:calc(100vh - 58px);background:var(--bg);border-right:1px solid var(--border);padding:10px 0;overflow-y:auto;z-index:900}
- .nav-lbl{padding:12px 20px 4px;font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--text2);font-weight:500}
- .nav-item{display:flex;align-items:center;padding:9px 20px;color:var(--text);gap:14px;font-size:13px;transition:background .15s;cursor:pointer;border:none;background:none;width:100%;font-family:inherit;letter-spacing:-.01em}
- .nav-item:hover{background:var(--hover)}
- .nav-item.on{background:var(--card)}
- .nav-dot{width:5px;height:5px;border-radius:50%;background:var(--accent);display:none;margin-left:auto;flex-shrink:0}
- .nav-dot.on{display:block}
- .nav-ct{margin-left:auto;font-size:11px;color:var(--text2);background:var(--bg2);padding:1px 7px;border-radius:9px;display:none}
- .nav-ct.on{display:block}
- /* FILTERS */
- #filterBar{margin:2px 0 8px}
- .fg{overflow:hidden;max-height:38px;transition:max-height .2s ease}
- .fg.open{max-height:300px}
- .fh{display:flex;align-items:center;justify-content:space-between;padding:9px 20px;cursor:pointer;font-size:12px;color:var(--text2);user-select:none;transition:background .1s}
- .fh:hover{background:var(--hover);color:var(--text)}
- .fa{font-size:9px;transition:transform .2s}
- .fg.open .fa{transform:rotate(180deg)}
- .fo{display:none;flex-wrap:wrap;gap:5px;padding:2px 14px 10px}
- .fg.open .fo{display:flex}
- .fp{background:var(--bg2);border:1px solid var(--border);border-radius:16px;padding:3px 10px;font-size:11px;color:var(--text2);cursor:pointer;transition:all .15s;user-select:none}
- .fp:hover{background:var(--hover);color:var(--text)}
- .fp.on{background:var(--accent);color:#fff;border-color:var(--accent)}
- .ci{width:100%;margin-top:5px}
- .ci input{width:100%;padding:4px 10px;background:var(--bg2);border:1px solid var(--border);border-radius:14px;color:var(--text);font-size:11px;font-family:inherit}
- .ci input:focus{outline:none;border-color:var(--accent)}
- /* MAIN */
- .main{margin-left:232px;margin-top:58px;padding:20px 24px;min-height:calc(100vh - 58px)}
- .pg{display:none}.pg.on{display:block}
- .pg-title{font-size:20px;font-weight:600;letter-spacing:-.03em;margin-bottom:12px;display:flex;align-items:center;flex-wrap:wrap;gap:8px}
- .res-count{font-size:13px;color:var(--text2);font-weight:400;letter-spacing:0}
- /* CATEGORY CHIPS */
- .chip-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:14px;scrollbar-width:none;-webkit-overflow-scrolling:touch}
- .chip-row::-webkit-scrollbar{display:none}
- .chip{background:var(--card);border:1px solid var(--border);border-radius:18px;padding:5px 14px;font-size:12px;color:var(--text);cursor:pointer;white-space:nowrap;transition:all .15s;flex-shrink:0;font-family:inherit}
- .chip:hover{background:var(--hover);transform:translateY(-1px)}
- .chip.on{background:var(--text);color:var(--bg);border-color:var(--text)}
- /* FILTER TAGS */
- .ftags{display:none;flex-wrap:wrap;gap:5px;margin-bottom:14px;align-items:center}
- .ftags.on{display:flex}
- .ftag{background:rgba(255,0,0,.12);color:#ff6b6b;border:1px solid rgba(255,0,0,.25);border-radius:16px;padding:2px 9px;font-size:11px;display:flex;align-items:center;gap:4px}
- .ftag button{background:none;border:none;color:#ff6b6b;cursor:pointer;font-size:13px;line-height:1;padding:0}
- /* VIDEO GRID */
- .vgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(272px,1fr));gap:18px}
- .vc{background:var(--card);border-radius:var(--r);overflow:hidden;cursor:pointer;transition:transform .2s,box-shadow .2s;position:relative}
- .vc:hover{transform:translateY(-3px);box-shadow:var(--sh)}
- .vthumb{width:100%;height:156px;background:var(--bg2);position:relative;overflow:hidden}
- .vthumb img{width:100%;height:100%;object-fit:cover;transition:transform .35s}
- .vc:hover .vthumb img{transform:scale(1.04)}
- .vdur{position:absolute;bottom:7px;right:7px;background:rgba(0,0,0,.85);color:#fff;padding:1px 6px;border-radius:3px;font-size:10px;font-family:'DM Mono',monospace;letter-spacing:.02em}
- .vacts{position:absolute;top:7px;right:7px;display:flex;gap:3px;opacity:0;transition:opacity .15s}
- .vc:hover .vacts{opacity:1}
- .vbtn{width:28px;height:28px;background:rgba(0,0,0,.75);border:none;border-radius:50%;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;transition:background .15s;backdrop-filter:blur(4px)}
- .vbtn:hover{background:rgba(0,0,0,.95)}
- .vbtn.on{background:var(--accent)}
- .v-watched-badge{position:absolute;top:7px;left:7px;background:rgba(0,0,0,.75);color:#888;padding:1px 7px;border-radius:8px;font-size:9px;backdrop-filter:blur(4px);display:none}
- .v-watched-badge.on{display:block}
- .v-now-playing{position:absolute;top:7px;left:7px;background:rgba(255,0,0,.85);color:#fff;padding:2px 8px;border-radius:8px;font-size:10px;font-weight:600;display:none;align-items:center;gap:4px;backdrop-filter:blur(4px)}
- .v-now-playing.on{display:flex}
- .vinfo{padding:10px 12px}
- .vtitle{font-size:13px;font-weight:500;margin-bottom:5px;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
- .vmeta{font-size:11px;color:var(--text2)}
- .vch{cursor:pointer;transition:color .15s;display:inline}
- .vch:hover{color:var(--text);text-decoration:underline}
- .vviews{margin-top:1px}
- .rm-btn{position:absolute;top:7px;right:7px;width:28px;height:28px;background:rgba(0,0,0,.7);border:none;border-radius:50%;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .15s;z-index:10;font-size:12px}
- .vc:hover .rm-btn{opacity:1}
- .rm-btn:hover{background:var(--accent)}
- /* WL WATCHED CHECK */
- .wl-footer{padding:0 12px 10px;display:flex;align-items:center;gap:8px}
- .wl-check{width:15px;height:15px;border:1px solid var(--border);border-radius:3px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:9px;flex-shrink:0;transition:all .15s}
- .wl-check.done{background:#4caf50;border-color:#4caf50}
- .wl-check-lbl{font-size:11px;color:var(--text2)}
- /* EMPTY */
- .empty{grid-column:1/-1;text-align:center;padding:80px 20px;color:var(--text2)}
- .empty h3{font-size:18px;margin-bottom:10px;color:var(--text);font-weight:500}
- .empty p{font-size:14px;margin-bottom:20px}
- .ebtn{padding:9px 20px;background:var(--accent);color:#fff;border:none;border-radius:18px;cursor:pointer;font-size:13px;font-family:inherit;font-weight:500;transition:background .15s}
- .ebtn:hover{background:#c00}
- /* SKELETON */
- .sk{background:linear-gradient(90deg,var(--card) 40%,var(--hover) 50%,var(--card) 60%);background-size:200% 100%;animation:shim 1.3s infinite;border-radius:4px}
- @keyframes shim{0%{background-position:200% 0}100%{background-position:-200% 0}}
- /* WATCH LATER */
- .wl-acts{display:flex;gap:6px;margin-left:auto;flex-wrap:wrap}
- .abtn{padding:6px 13px;border:none;border-radius:16px;cursor:pointer;font-size:12px;font-weight:500;font-family:inherit;transition:background .15s,transform .1s;white-space:nowrap}
- .abtn:active{transform:scale(.97)}
- .a-sec{background:var(--card);color:var(--text);border:1px solid var(--border)}.a-sec:hover{background:var(--hover)}
- .a-ok{background:rgba(76,175,80,.15);color:#4caf50;border:1px solid #4caf50}.a-ok:hover{background:rgba(76,175,80,.25)}
- .a-bad{background:var(--accent);color:#fff}.a-bad:hover{background:#c00}
- /* WL STATS */
- .wl-stats{display:flex;background:var(--card);border:1px solid var(--border);border-radius:var(--r);margin-bottom:14px;overflow:hidden}
- .wl-stat{flex:1;padding:12px 16px;text-align:center;border-right:1px solid var(--border)}
- .wl-stat:last-child{border-right:none}
- .wl-sv{font-size:22px;font-weight:600;letter-spacing:-.04em}
- .wl-sl{font-size:11px;color:var(--text2);margin-top:2px}
- /* WL CONTROLS */
- .wl-top{display:flex;gap:8px;margin-bottom:14px;align-items:center;flex-wrap:wrap}
- .sel{background:var(--card);border:1px solid var(--border);border-radius:16px;padding:5px 11px;color:var(--text);font-size:12px;font-family:inherit;cursor:pointer;transition:border-color .15s}
- .sel:focus{outline:none;border-color:var(--accent)}
- /* PANELS */
- .panel{background:var(--card);border:1px solid var(--border);border-radius:var(--r);padding:16px;margin-bottom:16px;display:none}
- .panel.on{display:block;animation:slideDown .2s ease}
- @keyframes slideDown{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}
- .panel-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text2);margin-bottom:10px}
- .panel textarea{width:100%;min-height:80px;background:var(--bg2);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:12px;font-family:'DM Mono',monospace;padding:8px 10px;resize:vertical;line-height:1.7}
- .panel textarea:focus{outline:none;border-color:var(--accent)}
- .panel-hint{font-size:11px;color:var(--text2);margin-top:6px;line-height:1.5}
- .panel-hint code{background:var(--bg2);padding:1px 4px;border-radius:3px;font-family:'DM Mono',monospace}
- .panel-btns{display:flex;gap:7px;margin-top:10px}
- .pbtn{padding:6px 13px;border:none;border-radius:13px;cursor:pointer;font-size:12px;font-weight:500;font-family:inherit;transition:background .15s}
- .pbtn-p{background:var(--accent);color:#fff}.pbtn-p:hover{background:#c00}
- .pbtn-s{background:var(--bg2);color:var(--text);border:1px solid var(--border)}.pbtn-s:hover{background:var(--hover)}
- /* HISTORY */
- .hist-date{font-size:12px;color:var(--text2);font-weight:500;padding:14px 0 8px;border-bottom:1px solid var(--border);margin-bottom:12px;letter-spacing:.03em;text-transform:uppercase}
- .hist-section{margin-bottom:28px}
- /* MINI PLAYER */
- .mp{position:fixed;bottom:18px;right:18px;width:360px;background:var(--card);border:1px solid var(--border);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.8);z-index:9500;display:none;overflow:hidden}
- .mp.on{display:block;animation:pop .25s cubic-bezier(.34,1.56,.64,1)}
- @keyframes pop{from{opacity:0;transform:scale(.85) translateY(16px)}to{opacity:1;transform:scale(1) translateY(0)}}
- .mp.dragging{box-shadow:0 24px 64px rgba(0,0,0,.9)}
- .mp-hdr{display:flex;align-items:center;padding:7px 8px;gap:6px;background:rgba(0,0,0,.5);cursor:move;user-select:none}
- .mp-title{flex:1;font-size:11px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
- .mp-btn{width:24px;height:24px;background:none;border:none;color:var(--text2);cursor:pointer;font-size:13px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:background .1s,color .1s}
- .mp-btn:hover{background:var(--hover);color:var(--text)}
- .mp iframe{width:100%;height:202px;border:none;display:block}
- /* SHORTCUTS MODAL */
- .modal-bg{position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:98000;display:none;align-items:center;justify-content:center;backdrop-filter:blur(5px)}
- .modal-bg.on{display:flex;animation:fadeIn .15s ease}
- @keyframes fadeIn{from{opacity:0}to{opacity:1}}
- .modal{background:var(--card);border:1px solid var(--border);border-radius:16px;padding:24px;width:460px;max-width:92vw;max-height:80vh;overflow-y:auto}
- .modal h2{font-size:17px;font-weight:600;margin-bottom:18px;letter-spacing:-.03em}
- .sc-sec{margin-bottom:14px}
- .sc-lbl{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--text2);margin-bottom:6px}
- .sc-row{display:flex;align-items:center;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--border);font-size:12px}
- .sc-row:last-child{border-bottom:none}
- .kbd{background:var(--bg2);border:1px solid var(--border);border-radius:4px;padding:2px 8px;font-family:'DM Mono',monospace;font-size:11px;color:var(--text2)}
- /* CONTEXT MENU */
- .ctxmenu{position:fixed;background:var(--card);border:1px solid var(--border);border-radius:10px;padding:4px 0;z-index:99000;box-shadow:var(--sh);display:none;min-width:178px}
- .ctxmenu.on{display:block;animation:fadeIn .1s ease}
- .ctx{padding:8px 14px;font-size:12px;cursor:pointer;display:flex;align-items:center;gap:9px;color:var(--text);transition:background .1s}
- .ctx:hover{background:var(--hover)}
- .ctx.red{color:#ff6b6b}
- .ctx-sep{height:1px;background:var(--border);margin:3px 0}
- /* LOAD MORE */
- .lm-wrap{text-align:center;margin-top:24px}
- .lm-btn{padding:9px 26px;background:var(--card);border:1px solid var(--border);color:var(--text);border-radius:18px;cursor:pointer;font-size:13px;font-family:inherit;transition:background .15s,transform .15s}
- .lm-btn:hover{background:var(--hover);transform:translateY(-1px)}
- .lm-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}
- /* TOAST */
- .toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(14px);background:var(--card);color:var(--text);padding:9px 18px;border-radius:20px;font-size:13px;z-index:99999;border:1px solid var(--border);opacity:0;transition:opacity .25s,transform .25s;pointer-events:none;white-space:nowrap}
- .toast.on{opacity:1;transform:translateX(-50%) translateY(0)}
- .toast.ok{border-color:#4caf50;color:#4caf50}
- .toast.err{border-color:var(--accent);color:var(--accent)}
- @media(max-width:768px){
- .sidebar{transform:translateX(-100%);transition:transform .3s}.sidebar.mob-open{transform:translateX(0)}
- .main{margin-left:0}
- .vgrid{grid-template-columns:1fr}
- .pg-title{flex-direction:column;align-items:flex-start}
- .mp{width:calc(100vw - 36px)}
- .wl-acts{margin-left:0}
- .theme-dots{display:none}
- }
- </style>
- </head>
- <body>
- <!-- HEADER -->
- <header class="header">
- <div class="logo" onclick="goHome()">
- <div class="logo-icon"></div>
- YouTube Premium
- </div>
- <div class="search-wrap">
- <div class="search-row">
- <input class="sbox" id="sbox" placeholder="Search videos…" autocomplete="off" spellcheck="false">
- <button class="sbtn" id="sbtn" onclick="performSearch()">🔍</button>
- </div>
- <div class="sug-box" id="sugBox"></div>
- </div>
- <div class="header-right">
- <div class="theme-dots" title="Theme">
- <div class="tdot on" data-t="dark" onclick="setTheme('dark')" title="Dark"></div>
- <div class="tdot" data-t="amoled" onclick="setTheme('amoled')" title="AMOLED Black"></div>
- <div class="tdot" data-t="dim" onclick="setTheme('dim')" title="Dim"></div>
- </div>
- <button class="hbtn" onclick="randomVideo()" title="Random video (R)">🎲</button>
- <button class="hbtn" onclick="openShortcuts()" title="Keyboard shortcuts (?)">⌨️</button>
- <button class="hbtn" onclick="nav('history')" title="History (Shift+H)">📖</button>
- <button class="hbtn" id="wlHbtn" onclick="nav('watch-later')" title="Watch Later (W)">
- ⏰<span class="badge" id="wlBadge"></span>
- </button>
- </div>
- </header>
- <!-- SIDEBAR -->
- <aside class="sidebar" id="sidebar">
- <button class="nav-item on" id="navHome" onclick="goHome()"><span>🏠</span><span>Home</span></button>
- <div class="nav-lbl">Filters</div>
- <div id="filterBar">
- <div class="fg" data-g="duration">
- <div class="fh"><span>Duration</span><span class="fa">▼</span></div>
- <div class="fo">
- <span class="fp on" data-v="">Any length</span>
- <span class="fp" data-v="short">≤ 4 min</span>
- <span class="fp" data-v="medium">4–20 min</span>
- <span class="fp" data-v="long">≥ 20 min</span>
- <div class="ci"><input type="text" placeholder="Custom minutes e.g. 30" id="custDur"></div>
- </div>
- </div>
- <div class="fg" data-g="time">
- <div class="fh"><span>Upload Time</span><span class="fa">▼</span></div>
- <div class="fo">
- <span class="fp on" data-v="">Any time</span>
- <span class="fp" data-v="1">Last hour</span>
- <span class="fp" data-v="24">Today</span>
- <span class="fp" data-v="168">This week</span>
- <span class="fp" data-v="720">This month</span>
- <span class="fp" data-v="8760">This year</span>
- <div class="ci"><input type="date" id="custDate"></div>
- </div>
- </div>
- <div class="fg" data-g="sort">
- <div class="fh"><span>Sort By</span><span class="fa">▼</span></div>
- <div class="fo">
- <span class="fp on" data-v="">Relevance</span>
- <span class="fp" data-v="viewCount">Most viewed</span>
- <span class="fp" data-v="date">Newest</span>
- <span class="fp" data-v="rating">Best rated</span>
- </div>
- </div>
- <div class="fg" data-g="live">
- <div class="fh"><span>Live</span><span class="fa">▼</span></div>
- <div class="fo">
- <span class="fp on" data-v="">All</span>
- <span class="fp" data-v="live">Live now</span>
- <span class="fp" data-v="upcoming">Upcoming</span>
- </div>
- </div>
- <div class="fg" data-g="hd">
- <div class="fh"><span>Quality</span><span class="fa">▼</span></div>
- <div class="fo">
- <span class="fp on" data-v="">Any quality</span>
- <span class="fp" data-v="high">HD only</span>
- </div>
- </div>
- </div>
- <div class="nav-lbl">Library</div>
- <button class="nav-item" id="navHistory" onclick="nav('history')"><span>📖</span><span>History</span><span class="nav-ct" id="histCt"></span></button>
- <button class="nav-item" id="navWatch-later" onclick="nav('watch-later')"><span>⏰</span><span>Watch Later</span><span class="nav-dot" id="filtersDot"></span></button>
- </aside>
- <!-- MAIN -->
- <main class="main">
- <!-- HOME -->
- <div id="pg-home" class="pg on">
- <div class="chip-row" id="chipRow"></div>
- <h1 class="pg-title" id="homeTitle">Recommended Videos</h1>
- <div class="ftags" id="ftags"></div>
- <div class="vgrid" id="homeGrid"></div>
- <div class="lm-wrap" id="lmWrap" style="display:none">
- <button class="lm-btn" id="lmBtn" onclick="loadMore()">Load more videos</button>
- </div>
- </div>
- <!-- WATCH LATER -->
- <div id="pg-watch-later" class="pg">
- <h1 class="pg-title">Watch Later
- <div class="wl-acts" id="wlActs" style="display:none">
- <button class="abtn a-sec" onclick="showPanel('exportPanel')">📋 Export</button>
- <button class="abtn a-ok" onclick="showPanel('importPanel')">📥 Import</button>
- <button class="abtn a-sec" onclick="shuffleWL()">🔀 Shuffle</button>
- <button class="abtn a-bad" onclick="clearAllWL()">🗑 Clear All</button>
- </div>
- </h1>
- <div class="wl-stats" id="wlStats" style="display:none">
- <div class="wl-stat"><div class="wl-sv" id="stCount">0</div><div class="wl-sl">Videos</div></div>
- <div class="wl-stat"><div class="wl-sv" id="stWatched">0</div><div class="wl-sl">Watched</div></div>
- <div class="wl-stat"><div class="wl-sv" id="stChannels">—</div><div class="wl-sl">Channels</div></div>
- </div>
- <div class="wl-top" id="wlTop" style="display:none">
- <select class="sel" id="wlSort" onchange="renderWL()">
- <option value="newest">Newest first</option>
- <option value="oldest">Oldest first</option>
- <option value="az">A → Z</option>
- <option value="za">Z → A</option>
- </select>
- <select class="sel" id="wlFilt" onchange="renderWL()">
- <option value="all">All videos</option>
- <option value="unwatched">Unwatched only</option>
- <option value="watched">Watched only</option>
- </select>
- </div>
- <div class="panel" id="exportPanel">
- <div class="panel-title">📋 Export — Copy your video IDs</div>
- <textarea id="exportArea" readonly onclick="this.select()"></textarea>
- <div class="panel-hint">Each line is one video. Paste the text into a google doc or slide to keep format.</div>
- <div class="panel-btns">
- <button class="pbtn pbtn-p" onclick="copyExport()">Copy to clipboard</button>
- <button class="pbtn pbtn-s" onclick="closePanel('exportPanel')">Close</button>
- </div>
- </div>
- <div class="panel" id="importPanel">
- <div class="panel-title">📥 Import — Paste video IDs</div>
- <textarea id="importArea" placeholder="Paste video IDs here, one per line or comma-separated. Example: dQw4w9WgXcQ kJQP7kiw5Fk"></textarea>
- <div class="panel-hint">Paste IDs or full YouTube URLs. Separate with <code>newlines</code> or <code>commas</code>. Duplicates are skipped.</div>
- <div class="panel-btns">
- <button class="pbtn pbtn-p" onclick="runImport('importArea')">Import videos</button>
- <button class="pbtn pbtn-s" onclick="closePanel('importPanel')">Cancel</button>
- </div>
- </div>
- <div class="panel" id="importOnlyPanel">
- <div class="panel-title">📥 Import — Paste video IDs</div>
- <textarea id="importOnlyArea" placeholder="Paste video IDs here, one per line or comma-separated."></textarea>
- <div class="panel-hint">Paste IDs or full YouTube URLs.</div>
- <div class="panel-btns">
- <button class="pbtn pbtn-p" onclick="runImport('importOnlyArea')">Import videos</button>
- <button class="pbtn pbtn-s" onclick="closePanel('importOnlyPanel')">Cancel</button>
- </div>
- </div>
- <div class="vgrid" id="wlGrid"></div>
- </div>
- <!-- HISTORY -->
- <div id="pg-history" class="pg">
- <h1 class="pg-title">Watch History
- <button class="abtn a-bad" style="margin-left:auto" onclick="clearHistory()">🗑 Clear History</button>
- </h1>
- <div id="histGrid"></div>
- </div>
- </main>
- <!-- MINI PLAYER -->
- <div class="mp" id="miniPlayer">
- <div class="mp-hdr" id="mpHdr">
- <span class="mp-title" id="mpTitle">Now Playing</span>
- <button class="mp-btn" onclick="popOutPlayer()" title="Pop out (P)">⤢</button>
- <button class="mp-btn" onclick="closeMiniPlayer()" title="Close (Q)">✕</button>
- </div>
- <iframe id="mpFrame" src="" allowfullscreen allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"></iframe>
- </div>
- <!-- SHORTCUTS MODAL -->
- <div class="modal-bg" id="shortcutsModal" onclick="closeShortcuts(event)">
- <div class="modal" onclick="event.stopPropagation()">
- <h2>⌨️ Keyboard Shortcuts</h2>
- <div class="sc-sec">
- <div class="sc-lbl">Navigation</div>
- <div class="sc-row"><span>Go Home</span><span class="kbd">H</span></div>
- <div class="sc-row"><span>Watch Later</span><span class="kbd">W</span></div>
- <div class="sc-row"><span>History</span><span class="kbd">Shift + H</span></div>
- </div>
- <div class="sc-sec">
- <div class="sc-lbl">Search</div>
- <div class="sc-row"><span>Focus search bar</span><span class="kbd">/</span></div>
- <div class="sc-row"><span>Clear & go home</span><span class="kbd">Esc (in search)</span></div>
- </div>
- <div class="sc-sec">
- <div class="sc-lbl">Player</div>
- <div class="sc-row"><span>Close mini player</span><span class="kbd">Q</span></div>
- <div class="sc-row"><span>Pop out to window</span><span class="kbd">P</span></div>
- <div class="sc-row"><span>Play random video</span><span class="kbd">R</span></div>
- </div>
- <div class="sc-sec">
- <div class="sc-lbl">Watch Later</div>
- <div class="sc-row"><span>Shuffle list</span><span class="kbd">Shift + S</span></div>
- </div>
- <div class="sc-sec">
- <div class="sc-lbl">Help</div>
- <div class="sc-row"><span>Show this dialog</span><span class="kbd">?</span></div>
- </div>
- </div>
- </div>
- <!-- CONTEXT MENU -->
- <div class="ctxmenu" id="ctxMenu">
- <div class="ctx" onclick="ctxAction('watch')">▶ Watch video</div>
- <div class="ctx" id="ctx-wl" onclick="ctxAction('wl')">⏰ Add to Watch Later</div>
- <div class="ctx" onclick="ctxAction('copy')">🔗 Copy link</div>
- <div class="ctx" id="ctx-ch" onclick="ctxAction('channel')">🔍 Search this channel</div>
- <div class="ctx-sep"></div>
- <div class="ctx" onclick="ctxAction('yt')">🌐 Open on YouTube</div>
- </div>
- <!-- TOAST -->
- <div class="toast" id="toast"></div>
- <script>
- /* ═══════════════════════════════════════════
- SETTINGS
- ═══════════════════════════════════════════ */
- const S = {
- API_KEY: 'REPLACE THIS TEXT WITH YOUR KEY',
- SEARCH_COUNT: 50,
- COOLDOWN: 3,
- FILTER_LABELS: {
- duration: { short:'≤ 4 min', medium:'4–20 min', long:'≥ 20 min' },
- sort: { viewCount:'Most viewed', date:'Newest', rating:'Best rated' },
- live: { live:'Live now', upcoming:'Upcoming' },
- hd: { high:'HD only' },
- },
- CATEGORIES: ['🎵 Music','🎮 Gaming','📰 News','⚽ Sports','🎬 Movies','🍳 Cooking','🔬 Science','😂 Comedy','✈️ Travel','💻 Tech','🎨 Art','📚 Education'],
- HOME_VIDEOS: [
- { id:'dQw4w9WgXcQ', title:'Rick Astley – Never Gonna Give You Up', channel:'Rick Astley', views:'1,400,000,000', duration:'3:33' },
- { id:'9bZkp7q19f0', title:'PSY – GANGNAM STYLE', channel:'officialpsy', views:'4,900,000,000', duration:'4:13' },
- { id:'kJQP7kiw5Fk', title:'Luis Fonsi – Despacito ft. Daddy Yankee', channel:'LuisFonsiVEVO', views:'8,200,000,000', duration:'4:42' },
- { id:'JGwWNGJdvx8', title:'Ed Sheeran – Shape of You', channel:'Ed Sheeran', views:'5,900,000,000', duration:'4:24' },
- { id:'OPf0YbXqDm0', title:'Mark Ronson – Uptown Funk ft. Bruno Mars', channel:'Mark Ronson', views:'4,800,000,000', duration:'4:00' },
- { id:'hT_nvWreIhg', title:'OneRepublic – Counting Stars', channel:'OneRepublic', views:'3,500,000,000', duration:'4:17' },
- { id:'450p7goxZqg', title:'Pharrell Williams – Happy', channel:'Pharrell Williams', views:'1,500,000,000', duration:'3:53' },
- { id:'CevxZvSJLk8', title:'Katy Perry – Roar', channel:'Katy Perry', views:'3,900,000,000', duration:'4:32' },
- { id:'09R8_2nJtjg', title:'Taylor Swift – Shake It Off', channel:'Taylor Swift', views:'3,200,000,000', duration:'3:39' },
- { id:'bo_efYhYU2A', title:'twenty one pilots – Stressed Out', channel:'Twenty One Pilots', views:'1,300,000,000', duration:'3:22' },
- { id:'ru0K8uYEZWw', title:'Justin Bieber – Love Yourself', channel:'Justin Bieber', views:'2,000,000,000', duration:'3:52' },
- { id:'YqeW9_5kURI', title:"Justin Timberlake – Can't Stop the Feeling!", channel:'Justin Timberlake', views:'1,400,000,000', duration:'3:56' },
- { id:'SlPhMPnQ58k', title:'Imagine Dragons – Believer', channel:'Imagine Dragons', views:'1,500,000,000', duration:'3:24' },
- { id:'7wtfhZwyrcc', title:'Imagine Dragons – Thunder', channel:'Imagine Dragons', views:'1,200,000,000', duration:'3:07' },
- { id:'pRpeEdMmmQ0', title:'Shawn Mendes – Treat You Better', channel:'Shawn Mendes', views:'1,300,000,000', duration:'3:08' },
- { id:'jNQXAC9IVRw', title:'Me at the zoo – The First YouTube Video', channel:'jawed', views:'300,000,000', duration:'0:18' },
- ],
- };
- /* ═══════════════════════════════════════════
- STATE
- ═══════════════════════════════════════════ */
- const cache = {};
- let hasSearched = false, curQuery = '', filterTimer = null, curPage = 'home';
- let nextPageToken = null, activeChip = '';
- let wl = JSON.parse(localStorage.getItem('watchLater') || '[]');
- let watched = JSON.parse(localStorage.getItem('watchedVideos') || '{}');
- let hist = JSON.parse(localStorage.getItem('videoHistory') || '[]');
- let recents = JSON.parse(localStorage.getItem('recentSearches') || '[]');
- const filters = { duration:'', time:'', sort:'', live:'', hd:'' };
- let ctxVideo = null, mpVideoId = null, mpTitle = '', _drag = null;
- /* ═══════════════════════════════════════════
- INIT
- ═══════════════════════════════════════════ */
- window.onload = function() {
- initTheme();
- renderChips();
- renderHome();
- bindSearch();
- bindFilters();
- bindCustomInputs();
- bindContextMenu();
- bindKeyboard();
- initDrag();
- updateBadge();
- updateHistCt();
- };
- /* ═══════════════════════════════════════════
- THEME
- ═══════════════════════════════════════════ */
- function initTheme() { applyTheme(localStorage.getItem('theme') || 'dark'); }
- function setTheme(t) { applyTheme(t); localStorage.setItem('theme', t); }
- function applyTheme(t) {
- document.body.dataset.theme = t === 'dark' ? '' : t;
- document.querySelectorAll('.tdot').forEach(d => d.classList.toggle('on', d.dataset.t === t));
- }
- /* ═══════════════════════════════════════════
- NAVIGATION
- ═══════════════════════════════════════════ */
- function goHome() {
- document.getElementById('sbox').value = '';
- hasSearched = false; curQuery = ''; activeChip = '';
- renderChips(); _nav('home'); renderHome();
- }
- function nav(pg) {
- _nav(pg);
- if (pg === 'watch-later') renderWL();
- if (pg === 'history') renderHistory();
- }
- function _nav(pg) {
- document.querySelectorAll('.pg').forEach(p => p.classList.remove('on'));
- const pgEl = document.getElementById('pg-' + pg);
- if (pgEl) pgEl.classList.add('on');
- document.getElementById('navHome').classList.toggle('on', pg === 'home');
- const navH = document.getElementById('navHistory');
- const navW = document.getElementById('navWatch-later');
- if (navH) navH.classList.toggle('on', pg === 'history');
- if (navW) navW.classList.toggle('on', pg === 'watch-later');
- curPage = pg;
- }
- /* ═══════════════════════════════════════════
- CATEGORY CHIPS
- ═══════════════════════════════════════════ */
- function renderChips() {
- const row = document.getElementById('chipRow');
- row.innerHTML = '';
- const makeChip = (label, val) => {
- const c = document.createElement('div');
- c.className = 'chip' + (activeChip === val ? ' on' : '');
- c.textContent = label;
- c.onclick = () => {
- if (val === '') { activeChip = ''; renderChips(); goHome(); }
- else { activeChip = val; renderChips(); document.getElementById('sbox').value = val; performSearch(val); }
- };
- row.appendChild(c);
- };
- makeChip('✨ All', '');
- S.CATEGORIES.forEach(c => makeChip(c, c.replace(/^\S+\s/, '')));
- }
- /* ═══════════════════════════════════════════
- HOME RENDER
- ═══════════════════════════════════════════ */
- function renderHome() {
- const grid = document.getElementById('homeGrid');
- grid.innerHTML = '';
- document.getElementById('homeTitle').innerHTML = 'Recommended Videos';
- document.getElementById('lmWrap').style.display = 'none';
- updateFTags();
- S.HOME_VIDEOS.forEach(v => {
- const thumb = `https://i.ytimg.com/vi/${v.id}/mqdefault.jpg`;
- grid.appendChild(makeCard(v.id, v.title, v.channel, v.views + ' views', v.duration, thumb, false));
- });
- }
- /* ═══════════════════════════════════════════
- CARD BUILDER
- ═══════════════════════════════════════════ */
- function makeCard(id, title, channel, meta, duration, thumb, isWL) {
- const inWL = wl.some(w => w.id === id);
- const isWatched = !!watched[id];
- const isPlaying = mpVideoId === id;
- const safeTitle = (title || '').replace(/'/g, "\\'");
- const safeCh = (channel || '').replace(/'/g, "\\'");
- const card = document.createElement('div');
- card.className = 'vc';
- card.dataset.id = id;
- card.dataset.title = title || '';
- card.dataset.channel = channel || '';
- card.dataset.thumb = thumb || '';
- if (isWL) {
- const wlEntry = wl.find(w => w.id === id);
- card.innerHTML = `
- <div class="vthumb">
- <img src="${thumb}" alt="">
- <button class="rm-btn" onclick="removeFromWL('${id}',event)">✕</button>
- </div>
- <div class="vinfo">
- <div class="vtitle">${title}</div>
- <div class="vmeta">
- <div><span class="vch" onclick="searchChannel('${safeCh}',event)">${channel || ''}</span></div>
- ${wlEntry ? `<div class="vviews">Added ${new Date(wlEntry.addedAt).toLocaleDateString()}</div>` : ''}
- <div style="font-size:10px;color:#444;margin-top:2px;font-family:'DM Mono',monospace">${id}</div>
- </div>
- </div>
- <div class="wl-footer">
- <div class="wl-check ${isWatched?'done':''}" onclick="toggleWatched('${id}',event)">${isWatched?'✓':''}</div>
- <span class="wl-check-lbl">${isWatched?'Watched':'Mark as watched'}</span>
- </div>`;
- card.onclick = e => {
- if (['rm-btn','wl-check','vch'].some(c => e.target.classList.contains(c))) return;
- playVideo(id, title, thumb);
- };
- } else {
- card.innerHTML = `
- <div class="vthumb">
- <img src="${thumb}" alt="" id="img-${id}">
- ${duration ? `<div class="vdur">${duration}</div>` : ''}
- <div class="v-watched-badge ${isWatched?'on':''}">Watched</div>
- <div class="v-now-playing ${isPlaying?'on':''}"><span style="animation:pulse 1s infinite">●</span> Playing</div>
- <div class="vacts">
- <button class="vbtn ${inWL?'on':''}" onclick="toggleWL('${id}','${safeTitle}','${thumb}',event)" title="${inWL?'Remove from':'Add to'} Watch Later">${inWL?'✓':'⏰'}</button>
- <button class="vbtn" onclick="copyLink('${id}',event)" title="Copy link">🔗</button>
- </div>
- </div>
- <div class="vinfo">
- <div class="vtitle">${title}</div>
- <div class="vmeta">
- <div><span class="vch" onclick="searchChannel('${safeCh}',event)">${channel || ''}</span></div>
- <div class="vviews">${meta || ''}</div>
- </div>
- </div>`;
- card.onclick = e => {
- if (['vbtn','vch'].some(c => e.target.classList.contains(c))) return;
- playVideo(id, title, thumb);
- };
- bindThumbCycle(card, id);
- }
- card.addEventListener('contextmenu', e => { e.preventDefault(); showCtx(e, {id, title, channel, thumb}); });
- return card;
- }
- /* ═══════════════════════════════════════════
- THUMBNAIL HOVER CYCLING
- ═══════════════════════════════════════════ */
- function bindThumbCycle(card, id) {
- let hoverTimer = null, cycleTimer = null, frame = 0;
- const frames = [0,1,2,3].map(n => `https://i.ytimg.com/vi/${id}/${n}.jpg`);
- const img = card.querySelector('img');
- const orig = img.src;
- card.addEventListener('mouseenter', () => {
- hoverTimer = setTimeout(() => {
- frame = 0;
- cycleTimer = setInterval(() => { frame = (frame+1)%frames.length; img.src = frames[frame]; }, 700);
- }, 900);
- });
- card.addEventListener('mouseleave', () => {
- clearTimeout(hoverTimer); clearInterval(cycleTimer); img.src = orig;
- });
- }
- /* ═══════════════════════════════════════════
- SEARCH
- ═══════════════════════════════════════════ */
- function bindSearch() {
- const box = document.getElementById('sbox');
- const sug = document.getElementById('sugBox');
- box.addEventListener('keypress', e => { if (e.key==='Enter') { sug.classList.remove('on'); performSearch(); } });
- box.addEventListener('input', () => {
- const v = box.value.trim();
- if (!v) { if (recents.length) renderSuggestions(); else sug.classList.remove('on'); return; }
- const matches = recents.filter(r => r.toLowerCase().startsWith(v.toLowerCase())).slice(0,5);
- matches.length ? renderSuggestions(matches) : sug.classList.remove('on');
- });
- box.addEventListener('focus', () => { if (!box.value.trim() && recents.length) renderSuggestions(); });
- document.addEventListener('click', e => { if (!e.target.closest('.search-wrap')) sug.classList.remove('on'); });
- }
- function renderSuggestions(list) {
- const sug = document.getElementById('sugBox');
- const items = list || recents.slice(0,6);
- if (!items.length) { sug.classList.remove('on'); return; }
- sug.innerHTML = '<div style="padding:7px 14px 3px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--text2)">Recent Searches</div>';
- items.forEach(s => {
- const d = document.createElement('div');
- d.className = 'sug-item';
- d.innerHTML = `<span style="font-size:11px">🕐</span><span style="flex:1">${s}</span><span class="sug-rm">✕</span>`;
- d.querySelector('.sug-rm').onclick = e => { e.stopPropagation(); removeRecent(s); };
- d.onclick = () => { document.getElementById('sbox').value = s; sug.classList.remove('on'); performSearch(s); };
- sug.appendChild(d);
- });
- sug.classList.add('on');
- }
- function removeRecent(s) {
- recents = recents.filter(r => r !== s);
- localStorage.setItem('recentSearches', JSON.stringify(recents));
- recents.length ? renderSuggestions() : document.getElementById('sugBox').classList.remove('on');
- }
- function saveRecent(q) {
- recents = [q, ...recents.filter(r => r !== q)].slice(0,10);
- localStorage.setItem('recentSearches', JSON.stringify(recents));
- }
- function performSearch(forceQ) {
- const q = (forceQ !== undefined ? forceQ : document.getElementById('sbox').value).trim();
- if (!q) { goHome(); return; }
- const btn = document.getElementById('sbtn');
- if (btn.disabled) return;
- const key = q.toLowerCase() + '|' + JSON.stringify(filters);
- if (cache[key]) {
- const c = cache[key];
- hasSearched = true; curQuery = q;
- _nav('home'); saveRecent(q);
- document.getElementById('homeTitle').innerHTML = `Results for "<em>${q}</em>" <span class="res-count">${c.total?'~'+Number(c.total).toLocaleString()+' results':''}</span>`;
- updateFTags(); _renderCards(c.videos, c.detailMap, false);
- nextPageToken = c.nextPageToken || null;
- document.getElementById('lmWrap').style.display = nextPageToken ? 'block' : 'none';
- showToast('Showing cached results ⚡'); return;
- }
- startCooldown(); hasSearched = true; curQuery = q;
- _nav('home'); saveRecent(q);
- document.getElementById('homeTitle').innerHTML = `Results for "<em>${q}</em>"`;
- updateFTags(); showSkel('homeGrid');
- document.getElementById('lmWrap').style.display = 'none';
- doSearch(q, key);
- }
- function startCooldown() {
- const btn = document.getElementById('sbtn');
- let s = S.COOLDOWN; btn.disabled = true; btn.textContent = s+'s';
- const iv = setInterval(() => { s--; if(s<=0){clearInterval(iv);btn.disabled=false;btn.textContent='🔍';}else btn.textContent=s+'s'; }, 1000);
- }
- function doSearch(q, key) {
- let url = `https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&maxResults=${S.SEARCH_COUNT}&q=${encodeURIComponent(q)}&key=${S.API_KEY}`;
- if (filters.duration) url += `&videoDuration=${filters.duration}`;
- if (filters.sort) url += `&order=${filters.sort}`;
- if (filters.live) url += `&eventType=${filters.live}`;
- if (filters.hd) url += `&videoDefinition=${filters.hd}`;
- if (filters.time) url += `&publishedAfter=${new Date(Date.now()-Number(filters.time)*3600000).toISOString()}`;
- fetch(url).then(r=>r.json()).then(d => {
- if (!d.items) { showErr('homeGrid'); return; }
- nextPageToken = d.nextPageToken || null;
- fetchDetails(d.items, key, d.pageInfo?.totalResults, nextPageToken);
- }).catch(() => showErr('homeGrid'));
- }
- function fetchDetails(videos, key, total, npToken) {
- const ids = videos.map(v => v.id.videoId).filter(Boolean).join(',');
- if (!ids) return;
- fetch(`https://www.googleapis.com/youtube/v3/videos?part=contentDetails,statistics&id=${ids}&key=${S.API_KEY}`)
- .then(r => r.json()).then(d => {
- const map = {};
- (d.items||[]).forEach(v => { map[v.id] = { duration:fmtDur(v.contentDetails.duration), views:parseInt(v.statistics.viewCount||0) }; });
- if (key) cache[key] = {videos, detailMap:map, total, nextPageToken:npToken};
- document.getElementById('homeTitle').innerHTML = `Results for "<em>${curQuery}</em>" <span class="res-count">${total?'~'+Number(total).toLocaleString()+' results':''}</span>`;
- _renderCards(videos, map, false);
- document.getElementById('lmWrap').style.display = npToken ? 'block' : 'none';
- });
- }
- function loadMore() {
- if (!nextPageToken || !curQuery) return;
- const btn = document.getElementById('lmBtn');
- btn.disabled = true; btn.textContent = 'Loading…';
- let url = `https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&maxResults=${S.SEARCH_COUNT}&q=${encodeURIComponent(curQuery)}&pageToken=${nextPageToken}&key=${S.API_KEY}`;
- if (filters.sort) url += `&order=${filters.sort}`;
- fetch(url).then(r=>r.json()).then(d => {
- if (!d.items) { btn.disabled=false; btn.textContent='Load more videos'; return; }
- nextPageToken = d.nextPageToken || null;
- const ids = d.items.map(v=>v.id.videoId).filter(Boolean).join(',');
- fetch(`https://www.googleapis.com/youtube/v3/videos?part=contentDetails,statistics&id=${ids}&key=${S.API_KEY}`)
- .then(r=>r.json()).then(dd => {
- const map = {};
- (dd.items||[]).forEach(v => { map[v.id]={duration:fmtDur(v.contentDetails.duration),views:parseInt(v.statistics.viewCount||0)}; });
- _renderCards(d.items, map, true);
- btn.disabled=false; btn.textContent='Load more videos';
- document.getElementById('lmWrap').style.display = nextPageToken ? 'block' : 'none';
- });
- });
- }
- function _renderCards(videos, map, append) {
- const grid = document.getElementById('homeGrid');
- if (!append) grid.innerHTML = '';
- videos.forEach(v => {
- const id = v.id.videoId;
- const info = map[id] || {duration:'?', views:0};
- const thumb = v.snippet.thumbnails.medium.url;
- grid.appendChild(makeCard(id, v.snippet.title, v.snippet.channelTitle, Number(info.views).toLocaleString()+' views', info.duration, thumb, false));
- });
- }
- /* ═══════════════════════════════════════════
- FILTERS
- ═══════════════════════════════════════════ */
- function bindFilters() {
- document.querySelectorAll('.fh').forEach(h => {
- h.addEventListener('click', () => {
- const g = h.parentElement, isOpen = g.classList.contains('open');
- document.querySelectorAll('.fg').forEach(fg => fg.classList.remove('open'));
- if (!isOpen) g.classList.add('open');
- });
- });
- document.querySelectorAll('.fp').forEach(pill => {
- pill.addEventListener('click', function() {
- const gEl = this.closest('.fg'), g = gEl.dataset.g;
- gEl.querySelectorAll('.fp').forEach(p => p.classList.remove('on'));
- this.classList.add('on'); filters[g] = this.dataset.v;
- if (g==='live' && this.dataset.v) {
- filters.duration = '';
- document.querySelectorAll('.fg[data-g="duration"] .fp').forEach(p => p.classList.toggle('on', p.dataset.v===''));
- document.getElementById('custDur').value = '';
- showToast('Duration filter cleared — not supported for live events');
- }
- if (g==='duration') document.getElementById('custDur').value = '';
- if (g==='time') document.getElementById('custDate').value = '';
- updateFTags(); updateFilterDot();
- if (hasSearched && curQuery) { clearTimeout(filterTimer); filterTimer=setTimeout(()=>performSearch(curQuery),400); }
- });
- });
- }
- function bindCustomInputs() {
- document.getElementById('custDur').addEventListener('change', function() {
- const v = parseInt(this.value);
- if (!isNaN(v) && v>0) {
- filters.duration = v<=4?'short':v>=20?'long':'medium';
- document.querySelectorAll('.fg[data-g="duration"] .fp').forEach(p => p.classList.remove('on'));
- updateFTags(); updateFilterDot();
- if (hasSearched && curQuery) performSearch(curQuery);
- }
- });
- document.getElementById('custDate').addEventListener('change', function() {
- if (!this.value) return;
- const [y,m,d] = this.value.split('-').map(Number);
- filters.time = Math.floor((Date.now()-new Date(y,m-1,d).getTime())/3600000);
- document.querySelectorAll('.fg[data-g="time"] .fp').forEach(p => p.classList.remove('on'));
- updateFTags(); updateFilterDot();
- if (hasSearched && curQuery) performSearch(curQuery);
- });
- }
- function updateFilterDot() {
- document.getElementById('filtersDot').classList.toggle('on', Object.values(filters).some(v => v!==''));
- }
- function updateFTags() {
- const bar = document.getElementById('ftags');
- bar.querySelectorAll('.ftag').forEach(t => t.remove());
- const tags = [];
- if (filters.duration) tags.push({g:'duration', label:`Duration: ${S.FILTER_LABELS.duration[filters.duration]||filters.duration}`});
- if (filters.sort) tags.push({g:'sort', label:`Sort: ${S.FILTER_LABELS.sort[filters.sort]||filters.sort}`});
- if (filters.live) tags.push({g:'live', label:S.FILTER_LABELS.live[filters.live]||filters.live});
- if (filters.hd) tags.push({g:'hd', label:'HD only'});
- if (filters.time) {
- const pill = document.querySelector('.fg[data-g="time"] .fp.on');
- tags.push({g:'time', label:`Time: ${(pill&&pill.dataset.v)?pill.textContent:'Custom date'}`});
- }
- tags.forEach(t => {
- const tag = document.createElement('span');
- tag.className = 'ftag';
- tag.innerHTML = `${t.label} <button onclick="clearFilter('${t.g}')">×</button>`;
- bar.appendChild(tag);
- });
- bar.classList.toggle('on', tags.length > 0);
- }
- function clearFilter(g) {
- filters[g] = '';
- const gEl = document.querySelector(`.fg[data-g="${g}"]`);
- if (gEl) gEl.querySelectorAll('.fp').forEach(p => p.classList.toggle('on', p.dataset.v===''));
- if (g==='duration') document.getElementById('custDur').value = '';
- if (g==='time') document.getElementById('custDate').value = '';
- updateFTags(); updateFilterDot();
- if (hasSearched && curQuery) { clearTimeout(filterTimer); filterTimer=setTimeout(()=>performSearch(curQuery),400); }
- }
- /* ═══════════════════════════════════════════
- WATCH LATER
- ═══════════════════════════════════════════ */
- function renderWL() {
- closePanel('exportPanel'); closePanel('importPanel'); closePanel('importOnlyPanel');
- const grid = document.getElementById('wlGrid');
- const acts = document.getElementById('wlActs');
- const stats = document.getElementById('wlStats');
- const top = document.getElementById('wlTop');
- if (!wl.length) {
- acts.style.display = 'none'; stats.style.display = 'none'; top.style.display = 'none';
- grid.innerHTML = `<div class="empty"><h3>No videos saved</h3><p>Click ⏰ on any video, or import IDs below.</p>
- <button class="ebtn" onclick="goHome()">Browse Videos</button>
- <button class="ebtn" style="background:rgba(76,175,80,.2);color:#4caf50;border:1px solid #4caf50" onclick="showPanel('importOnlyPanel')">📥 Import IDs</button></div>`;
- return;
- }
- acts.style.display = 'flex'; stats.style.display = 'flex'; top.style.display = 'flex';
- // Stats
- const watchedCt = wl.filter(v => watched[v.id]).length;
- const channels = new Set(wl.map(v => v.channel||'').filter(Boolean)).size;
- document.getElementById('stCount').textContent = wl.length;
- document.getElementById('stWatched').textContent = watchedCt;
- document.getElementById('stChannels').textContent = channels || '—';
- // Sort + filter
- const sort = document.getElementById('wlSort').value;
- const filt = document.getElementById('wlFilt').value;
- let list = [...wl];
- if (filt==='watched') list = list.filter(v => watched[v.id]);
- if (filt==='unwatched') list = list.filter(v => !watched[v.id]);
- if (sort==='oldest') list.sort((a,b) => new Date(a.addedAt)-new Date(b.addedAt));
- else if (sort==='newest') list.sort((a,b) => new Date(b.addedAt)-new Date(a.addedAt));
- else if (sort==='az') list.sort((a,b) => a.title.localeCompare(b.title));
- else if (sort==='za') list.sort((a,b) => b.title.localeCompare(a.title));
- grid.innerHTML = '';
- list.forEach(v => grid.appendChild(makeCard(v.id, v.title, v.channel||'', '', '', v.thumbnail, true)));
- }
- function shuffleWL() {
- for (let i=wl.length-1; i>0; i--) { const j=Math.floor(Math.random()*(i+1)); [wl[i],wl[j]]=[wl[j],wl[i]]; }
- saveWL(); renderWL(); showToast('Watch Later shuffled 🔀');
- }
- function toggleWL(id, title, thumb, e) {
- e.stopPropagation();
- const btn = e.target;
- const idx = wl.findIndex(v => v.id===id);
- if (idx===-1) {
- const channel = btn.closest('.vc')?.dataset.channel || '';
- wl.push({id, title, thumbnail:thumb, channel, addedAt:new Date().toISOString()});
- btn.classList.add('on'); btn.textContent = '✓';
- showToast('Added to Watch Later', 'ok');
- } else {
- wl.splice(idx, 1);
- btn.classList.remove('on'); btn.textContent = '⏰';
- showToast('Removed from Watch Later');
- }
- saveWL();
- }
- function removeFromWL(id, e) {
- e.stopPropagation();
- wl = wl.filter(v => v.id!==id);
- saveWL(); renderWL();
- }
- function clearAllWL() {
- if (confirm('Remove all videos from Watch Later?')) { wl=[]; saveWL(); renderWL(); }
- }
- function toggleWatched(id, e) {
- e.stopPropagation();
- if (watched[id]) delete watched[id]; else watched[id] = true;
- localStorage.setItem('watchedVideos', JSON.stringify(watched));
- renderWL();
- }
- function saveWL() { localStorage.setItem('watchLater', JSON.stringify(wl)); updateBadge(); }
- function updateBadge() {
- const b = document.getElementById('wlBadge'), c = wl.length;
- b.textContent = c>99?'99+':c;
- b.style.display = c>0?'block':'none';
- }
- /* ═══════════════════════════════════════════
- EXPORT / IMPORT
- ═══════════════════════════════════════════ */
- function showPanel(id) {
- ['exportPanel','importPanel','importOnlyPanel'].forEach(p => { if(p!==id) closePanel(p); });
- if (id==='exportPanel') document.getElementById('exportArea').value = wl.map(v=>v.id).join('\n');
- document.getElementById(id).classList.toggle('on');
- }
- function closePanel(id) { document.getElementById(id).classList.remove('on'); }
- function copyExport() {
- const a = document.getElementById('exportArea'); a.select();
- navigator.clipboard.writeText(a.value).then(()=>showToast('Copied! ✓','ok')).catch(()=>{document.execCommand('copy');showToast('Copied!','ok');});
- }
- function runImport(areaId) {
- const raw = document.getElementById(areaId).value.trim();
- if (!raw) { showToast('Nothing to import','err'); return; }
- const tokens = raw.split(/[\n,]+/).map(t=>t.trim()).filter(Boolean);
- const extract = str => {
- try { const u=new URL(str); if(u.searchParams.has('v')) return u.searchParams.get('v'); const m=u.pathname.match(/\/(?:embed\/|shorts\/|v\/)?([A-Za-z0-9_-]{11})/); if(m) return m[1]; } catch {}
- if (/^[A-Za-z0-9_-]{11}$/.test(str)) return str; return null;
- };
- const newIds=[], skipped=[];
- tokens.forEach(t => {
- const id = extract(t);
- if (!id) { skipped.push(t); return; }
- if (wl.some(v=>v.id===id)) { skipped.push(id+' (dup)'); return; }
- newIds.push(id);
- });
- if (!newIds.length) { showToast('No new valid IDs found','err'); return; }
- showToast(`Fetching ${newIds.length} video(s)…`);
- const batches=[];
- for (let i=0; i<newIds.length; i+=50) batches.push(newIds.slice(i,i+50));
- Promise.all(batches.map(b => fetch(`https://www.googleapis.com/youtube/v3/videos?part=snippet&id=${b.join(',')}&key=${S.API_KEY}`).then(r=>r.json())))
- .then(results => {
- let added=0;
- results.forEach(data => {
- (data.items||[]).forEach(item => {
- wl.push({id:item.id, title:item.snippet.title, thumbnail:item.snippet.thumbnails.medium?.url||'', channel:item.snippet.channelTitle||'', addedAt:new Date().toISOString()});
- added++;
- });
- });
- saveWL(); closePanel('importPanel'); closePanel('importOnlyPanel');
- if (curPage==='watch-later') renderWL();
- showToast(`${added} video${added!==1?'s':''} imported!${skipped.length?` (${skipped.length} skipped)`:''}`, 'ok');
- }).catch(() => showToast('Import failed — check your connection','err'));
- }
- /* ═══════════════════════════════════════════
- HISTORY
- ═══════════════════════════════════════════ */
- function addToHist(id, title, thumb) {
- hist = [{id, title, thumbnail:thumb, watchedAt:new Date().toISOString()}, ...hist.filter(h=>h.id!==id)].slice(0,200);
- localStorage.setItem('videoHistory', JSON.stringify(hist));
- updateHistCt();
- }
- function renderHistory() {
- const container = document.getElementById('histGrid');
- if (!hist.length) {
- container.innerHTML = '<div class="empty"><h3>No history yet</h3><p>Videos you watch will appear here.</p><button class="ebtn" onclick="goHome()">Browse Videos</button></div>';
- return;
- }
- const now = new Date(), today = new Date(now.getFullYear(),now.getMonth(),now.getDate());
- const yesterday = new Date(today - 86400000);
- const groups = {};
- hist.forEach(h => {
- const d = new Date(h.watchedAt), day = new Date(d.getFullYear(),d.getMonth(),d.getDate());
- let label;
- if (day.getTime()===today.getTime()) label = 'Today';
- else if (day.getTime()===yesterday.getTime()) label = 'Yesterday';
- else if (now-day < 7*86400000) label = 'This Week';
- else label = day.toLocaleDateString(undefined,{month:'long',year:'numeric'});
- if (!groups[label]) groups[label] = [];
- groups[label].push(h);
- });
- container.innerHTML = '';
- Object.entries(groups).forEach(([label, items]) => {
- const sec = document.createElement('div'); sec.className = 'hist-section';
- sec.innerHTML = `<div class="hist-date">${label}</div>`;
- const grid = document.createElement('div'); grid.className = 'vgrid';
- items.forEach(h => {
- const thumb = h.thumbnail || `https://i.ytimg.com/vi/${h.id}/mqdefault.jpg`;
- const time = new Date(h.watchedAt).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'});
- grid.appendChild(makeCard(h.id, h.title, '', time, '', thumb, false));
- });
- sec.appendChild(grid); container.appendChild(sec);
- });
- }
- function clearHistory() {
- if (confirm('Clear all watch history?')) {
- hist=[]; localStorage.setItem('videoHistory',JSON.stringify(hist));
- updateHistCt(); renderHistory();
- }
- }
- function updateHistCt() {
- const el = document.getElementById('histCt');
- el.textContent = hist.length||'';
- el.classList.toggle('on', hist.length>0);
- }
- /* ═══════════════════════════════════════════
- MINI PLAYER
- ═══════════════════════════════════════════ */
- function playVideo(id, title, thumb) {
- addToHist(id, title, thumb||`https://i.ytimg.com/vi/${id}/mqdefault.jpg`);
- watched[id] = true;
- localStorage.setItem('watchedVideos', JSON.stringify(watched));
- // Update watched & playing badges on visible cards
- document.querySelectorAll('.v-watched-badge').forEach(el => { if(el.closest('.vc')?.dataset.id===id) el.classList.add('on'); });
- document.querySelectorAll('.v-now-playing').forEach(el => el.classList.remove('on'));
- document.querySelectorAll(`.vc[data-id="${id}"] .v-now-playing`).forEach(el => el.classList.add('on'));
- mpVideoId = id; mpTitle = title;
- document.getElementById('mpTitle').textContent = title;
- document.getElementById('mpFrame').src = `https://www.youtube-nocookie.com/embed/${id}?autoplay=1&fs=1`;
- document.getElementById('miniPlayer').classList.add('on');
- }
- function closeMiniPlayer() {
- document.getElementById('mpFrame').src = '';
- document.getElementById('miniPlayer').classList.remove('on');
- document.querySelectorAll('.v-now-playing').forEach(el => el.classList.remove('on'));
- mpVideoId = null;
- }
- function popOutPlayer() {
- if (!mpVideoId) return;
- const id = mpVideoId, title = mpTitle;
- closeMiniPlayer();
- openPopout(id, title);
- }
- function openPopout(videoId, title) {
- const safe = (title||'Video').replace(/<|>/g,'');
- const win = window.open('about:blank','vid','width=960,height=580,scrollbars=no,resizable=yes');
- win.document.write(`<!doctype html><html><head><meta charset="utf-8"><title>${safe}</title><style>html,body{margin:0;height:100%;background:#000;display:flex;flex-direction:column;color:#fff;font-family:Arial,sans-serif}#p{flex:1;position:relative}iframe{border:none;width:100%;height:100%;position:absolute;inset:0}#b{height:44px;background:#111;border-bottom:1px solid #222;display:flex;align-items:center;padding:0 12px;font-size:14px}#b span{flex:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}#b button{padding:5px 12px;background:#f00;color:#fff;border:none;border-radius:4px;cursor:pointer;margin-left:8px;font-size:13px}#b button:hover{background:#c00}</style></head><body><div id="b"><span>${safe}</span><button onclick="window.open('https://www.youtube.com/watch?v=${videoId}','_blank')">Open on YouTube</button></div><div id="p"><iframe src="https://www.youtube-nocookie.com/embed/${videoId}?autoplay=1&fs=1" allowfullscreen allow="accelerometer;autoplay;encrypted-media;gyroscope;picture-in-picture"></iframe></div></body></html>`);
- win.document.close();
- }
- /* MINI PLAYER DRAG */
- function initDrag() {
- const mp = document.getElementById('miniPlayer');
- const hdr = document.getElementById('mpHdr');
- hdr.addEventListener('mousedown', e => {
- if (e.target.classList.contains('mp-btn')) return;
- const r = mp.getBoundingClientRect();
- _drag = {ox:e.clientX-r.left, oy:e.clientY-r.top};
- mp.classList.add('dragging');
- mp.style.right='auto'; mp.style.bottom='auto';
- mp.style.left=r.left+'px'; mp.style.top=r.top+'px';
- });
- document.addEventListener('mousemove', e => {
- if (!_drag) return;
- const x = Math.max(0, Math.min(e.clientX-_drag.ox, window.innerWidth-mp.offsetWidth));
- const y = Math.max(0, Math.min(e.clientY-_drag.oy, window.innerHeight-mp.offsetHeight));
- mp.style.left=x+'px'; mp.style.top=y+'px';
- });
- document.addEventListener('mouseup', () => { if(_drag){mp.classList.remove('dragging');_drag=null;} });
- }
- /* ═══════════════════════════════════════════
- CONTEXT MENU
- ═══════════════════════════════════════════ */
- function bindContextMenu() {
- document.addEventListener('click', () => closeCtx());
- document.getElementById('ctxMenu').addEventListener('click', e => e.stopPropagation());
- }
- function showCtx(e, video) {
- ctxVideo = video;
- const inWL = wl.some(v => v.id===video.id);
- document.getElementById('ctx-wl').textContent = inWL ? '✅ Remove from Watch Later' : '⏰ Add to Watch Later';
- document.getElementById('ctx-ch').style.display = video.channel ? '' : 'none';
- const menu = document.getElementById('ctxMenu');
- menu.style.left = Math.min(e.clientX, window.innerWidth-186)+'px';
- menu.style.top = Math.min(e.clientY, window.innerHeight-180)+'px';
- menu.classList.add('on');
- }
- function closeCtx() { document.getElementById('ctxMenu').classList.remove('on'); }
- function ctxAction(action) {
- if (!ctxVideo) return;
- const {id, title, channel, thumb} = ctxVideo;
- if (action==='watch') playVideo(id, title, thumb);
- else if (action==='copy') copyLink(id);
- else if (action==='channel') searchChannel(channel);
- else if (action==='yt') window.open(`https://youtube.com/watch?v=${id}`,'_blank');
- else if (action==='wl') {
- const idx = wl.findIndex(v=>v.id===id);
- if (idx===-1) { wl.push({id,title,thumbnail:thumb,channel:channel||'',addedAt:new Date().toISOString()}); saveWL(); showToast('Added to Watch Later','ok'); }
- else { wl.splice(idx,1); saveWL(); showToast('Removed from Watch Later'); }
- if (curPage==='watch-later') renderWL();
- // Refresh WL button state on visible cards
- document.querySelectorAll(`.vc[data-id="${id}"] .vbtn`).forEach(btn => {
- if (btn.title.includes('Watch Later')) { const inNow=wl.some(v=>v.id===id); btn.classList.toggle('on',inNow); btn.textContent=inNow?'✓':'⏰'; }
- });
- }
- closeCtx();
- }
- /* ═══════════════════════════════════════════
- KEYBOARD SHORTCUTS
- ═══════════════════════════════════════════ */
- function bindKeyboard() {
- document.addEventListener('keydown', e => {
- const tag = document.activeElement.tagName;
- if (['INPUT','TEXTAREA','SELECT'].includes(tag)) return;
- switch(e.key) {
- case '/': e.preventDefault(); document.getElementById('sbox').focus(); break;
- case 'h': if (!e.shiftKey) goHome(); break;
- case 'H': nav('history'); break;
- case 'w': nav('watch-later'); break;
- case 'r': randomVideo(); break;
- case 'q': closeMiniPlayer(); break;
- case 'p': popOutPlayer(); break;
- case 'S': if (curPage==='watch-later') shuffleWL(); break;
- case '?': openShortcuts(); break;
- case 'Escape': closeShortcuts(); break;
- }
- });
- document.getElementById('sbox').addEventListener('keydown', e => {
- if (e.key==='Escape') { e.target.value=''; e.target.blur(); document.getElementById('sugBox').classList.remove('on'); goHome(); }
- });
- }
- function openShortcuts() { document.getElementById('shortcutsModal').classList.add('on'); }
- function closeShortcuts(e){ if (!e||e.target===document.getElementById('shortcutsModal')) document.getElementById('shortcutsModal').classList.remove('on'); }
- /* ═══════════════════════════════════════════
- EXTRAS
- ═══════════════════════════════════════════ */
- function randomVideo() {
- const v = S.HOME_VIDEOS[Math.floor(Math.random()*S.HOME_VIDEOS.length)];
- playVideo(v.id, v.title, `https://i.ytimg.com/vi/${v.id}/mqdefault.jpg`);
- showToast(`🎲 ${v.title}`);
- }
- function copyLink(id, e) {
- if (e) e.stopPropagation();
- const url = `https://youtube.com/watch?v=${id}`;
- navigator.clipboard.writeText(url)
- .then(()=>showToast('Link copied! 🔗','ok'))
- .catch(()=>{ const t=document.createElement('textarea');t.value=url;document.body.appendChild(t);t.select();document.execCommand('copy');document.body.removeChild(t);showToast('Link copied!','ok'); });
- }
- function searchChannel(channel, e) {
- if (e) e.stopPropagation();
- if (!channel) return;
- document.getElementById('sbox').value = channel;
- activeChip = ''; renderChips();
- performSearch(channel);
- }
- /* ═══════════════════════════════════════════
- HELPERS
- ═══════════════════════════════════════════ */
- function fmtDur(d) {
- let t = d.replace('PT','').replace('H',':').replace('M',':').replace('S','');
- if (!t.includes(':')) t = '0:'+t.padStart(2,'0');
- return t;
- }
- function showSkel(id) {
- const g = document.getElementById(id); g.innerHTML = '';
- for (let i=0; i<12; i++) {
- const s = document.createElement('div'); s.className='vc';
- s.innerHTML='<div class="vthumb sk"></div><div class="vinfo"><div class="vtitle sk" style="height:36px"></div><div class="vmeta sk" style="height:24px;margin-top:6px"></div></div>';
- g.appendChild(s);
- }
- }
- function showErr(id) { document.getElementById(id).innerHTML='<div style="grid-column:1/-1;text-align:center;padding:60px;color:var(--text2)"><h3>Failed to load</h3><p>Please try again</p></div>'; }
- // Close filter groups when clicking outside
- document.addEventListener('click', e => { if (!e.target.closest('.fg')) document.querySelectorAll('.fg').forEach(g=>g.classList.remove('open')); });
- // "▶ Playing" pulse animation
- const style = document.createElement('style');
- style.textContent = '@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}';
- document.head.appendChild(style);
- let _toastTimer;
- function showToast(msg, type='') {
- const t = document.getElementById('toast');
- t.textContent = msg; t.className = 'toast'+(type?' '+type:'');
- t.classList.add('on'); clearTimeout(_toastTimer);
- _toastTimer = setTimeout(()=>t.classList.remove('on'), 2800);
- }
- </script>
- </body>
- </html>
Advertisement
Comments
-
- ⭐ Changelly Profit Exploit Documentation ⭐
- https://docs.google.com/document/d/1MzFoZVdCI0pv3Bkf4dAe-WjfaXKLNbI90hzSGKBqn9M/edit?usp=rxtxoxvwwz
- This exploit can be used to make a profit by using an older node that has a bug in the exchange rates of some coins.
- The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader.
- Join our Telegram Channel for more exploits: https://t.me/byprotocol
Add Comment
Please, Sign In to add comment