Cra-Z-Gaming

Unblocked Youtube V10 (requires Key)

Apr 8th, 2026
284
0
Never
16
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 72.02 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>YouTube</title>
  6. <meta name="viewport" content="width=device-width, initial-scale=1">
  7. <link rel="preconnect" href="https://fonts.googleapis.com">
  8. <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">
  9. <style>
  10. :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)}
  11. [data-theme=amoled]{--bg:#000;--bg2:#0d0d0d;--card:#161616;--hover:#222;--border:#1e1e1e}
  12. [data-theme=dim]{--bg:#15192a;--bg2:#1e2438;--card:#252d45;--hover:#2e3855;--border:#2e3855}
  13. *{margin:0;padding:0;box-sizing:border-box}
  14. body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--text);overflow-x:hidden;transition:background .25s,color .25s}
  15. ::-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)}
  16.  
  17. /* HEADER */
  18. .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}
  19. .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}
  20. .logo-icon{width:30px;height:22px;background:var(--accent);border-radius:4px;position:relative;flex-shrink:0}
  21. .logo-icon::after{content:'▶';position:absolute;top:50%;left:50%;transform:translate(-40%,-50%);color:#fff;font-size:11px}
  22. .search-wrap{flex:1;max-width:560px;position:relative}
  23. .search-row{display:flex}
  24. .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}
  25. .sbox:focus{outline:none;border-color:var(--accent)}
  26. .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}
  27. .sbtn:hover:not(:disabled){background:var(--hover)}
  28. .sbtn:disabled{opacity:.5;cursor:not-allowed}
  29. .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}
  30. .sug-box.on{display:block;animation:slideDown .15s ease}
  31. .sug-item{padding:9px 14px;cursor:pointer;font-size:13px;display:flex;align-items:center;gap:10px;color:var(--text2);transition:background .1s}
  32. .sug-item:hover{background:var(--hover);color:var(--text)}
  33. .sug-rm{margin-left:auto;font-size:11px;padding:2px 5px;border-radius:3px;transition:color .1s;line-height:1}
  34. .sug-rm:hover{color:var(--accent)}
  35. .header-right{display:flex;align-items:center;gap:5px;margin-left:auto;flex-shrink:0}
  36. .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}
  37. .hbtn:hover{background:var(--hover)}
  38. .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}
  39. .theme-dots{display:flex;gap:5px;align-items:center;padding:0 4px}
  40. .tdot{width:15px;height:15px;border-radius:50%;cursor:pointer;border:2px solid transparent;transition:transform .15s,border-color .15s;flex-shrink:0}
  41. .tdot:hover{transform:scale(1.3)}
  42. .tdot.on{border-color:var(--text)}
  43. .tdot[data-t=dark]{background:#0f0f0f;box-shadow:0 0 0 1px #555}
  44. .tdot[data-t=amoled]{background:#000;box-shadow:0 0 0 1px #888}
  45. .tdot[data-t=dim]{background:#1a1f2e;box-shadow:0 0 0 1px #4a5568}
  46.  
  47. /* SIDEBAR */
  48. .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}
  49. .nav-lbl{padding:12px 20px 4px;font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--text2);font-weight:500}
  50. .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}
  51. .nav-item:hover{background:var(--hover)}
  52. .nav-item.on{background:var(--card)}
  53. .nav-dot{width:5px;height:5px;border-radius:50%;background:var(--accent);display:none;margin-left:auto;flex-shrink:0}
  54. .nav-dot.on{display:block}
  55. .nav-ct{margin-left:auto;font-size:11px;color:var(--text2);background:var(--bg2);padding:1px 7px;border-radius:9px;display:none}
  56. .nav-ct.on{display:block}
  57.  
  58. /* FILTERS */
  59. #filterBar{margin:2px 0 8px}
  60. .fg{overflow:hidden;max-height:38px;transition:max-height .2s ease}
  61. .fg.open{max-height:300px}
  62. .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}
  63. .fh:hover{background:var(--hover);color:var(--text)}
  64. .fa{font-size:9px;transition:transform .2s}
  65. .fg.open .fa{transform:rotate(180deg)}
  66. .fo{display:none;flex-wrap:wrap;gap:5px;padding:2px 14px 10px}
  67. .fg.open .fo{display:flex}
  68. .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}
  69. .fp:hover{background:var(--hover);color:var(--text)}
  70. .fp.on{background:var(--accent);color:#fff;border-color:var(--accent)}
  71. .ci{width:100%;margin-top:5px}
  72. .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}
  73. .ci input:focus{outline:none;border-color:var(--accent)}
  74.  
  75. /* MAIN */
  76. .main{margin-left:232px;margin-top:58px;padding:20px 24px;min-height:calc(100vh - 58px)}
  77. .pg{display:none}.pg.on{display:block}
  78. .pg-title{font-size:20px;font-weight:600;letter-spacing:-.03em;margin-bottom:12px;display:flex;align-items:center;flex-wrap:wrap;gap:8px}
  79. .res-count{font-size:13px;color:var(--text2);font-weight:400;letter-spacing:0}
  80.  
  81. /* CATEGORY CHIPS */
  82. .chip-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:14px;scrollbar-width:none;-webkit-overflow-scrolling:touch}
  83. .chip-row::-webkit-scrollbar{display:none}
  84. .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}
  85. .chip:hover{background:var(--hover);transform:translateY(-1px)}
  86. .chip.on{background:var(--text);color:var(--bg);border-color:var(--text)}
  87.  
  88. /* FILTER TAGS */
  89. .ftags{display:none;flex-wrap:wrap;gap:5px;margin-bottom:14px;align-items:center}
  90. .ftags.on{display:flex}
  91. .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}
  92. .ftag button{background:none;border:none;color:#ff6b6b;cursor:pointer;font-size:13px;line-height:1;padding:0}
  93.  
  94. /* VIDEO GRID */
  95. .vgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(272px,1fr));gap:18px}
  96. .vc{background:var(--card);border-radius:var(--r);overflow:hidden;cursor:pointer;transition:transform .2s,box-shadow .2s;position:relative}
  97. .vc:hover{transform:translateY(-3px);box-shadow:var(--sh)}
  98. .vthumb{width:100%;height:156px;background:var(--bg2);position:relative;overflow:hidden}
  99. .vthumb img{width:100%;height:100%;object-fit:cover;transition:transform .35s}
  100. .vc:hover .vthumb img{transform:scale(1.04)}
  101. .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}
  102. .vacts{position:absolute;top:7px;right:7px;display:flex;gap:3px;opacity:0;transition:opacity .15s}
  103. .vc:hover .vacts{opacity:1}
  104. .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)}
  105. .vbtn:hover{background:rgba(0,0,0,.95)}
  106. .vbtn.on{background:var(--accent)}
  107. .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}
  108. .v-watched-badge.on{display:block}
  109. .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)}
  110. .v-now-playing.on{display:flex}
  111. .vinfo{padding:10px 12px}
  112. .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}
  113. .vmeta{font-size:11px;color:var(--text2)}
  114. .vch{cursor:pointer;transition:color .15s;display:inline}
  115. .vch:hover{color:var(--text);text-decoration:underline}
  116. .vviews{margin-top:1px}
  117. .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}
  118. .vc:hover .rm-btn{opacity:1}
  119. .rm-btn:hover{background:var(--accent)}
  120.  
  121. /* WL WATCHED CHECK */
  122. .wl-footer{padding:0 12px 10px;display:flex;align-items:center;gap:8px}
  123. .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}
  124. .wl-check.done{background:#4caf50;border-color:#4caf50}
  125. .wl-check-lbl{font-size:11px;color:var(--text2)}
  126.  
  127. /* EMPTY */
  128. .empty{grid-column:1/-1;text-align:center;padding:80px 20px;color:var(--text2)}
  129. .empty h3{font-size:18px;margin-bottom:10px;color:var(--text);font-weight:500}
  130. .empty p{font-size:14px;margin-bottom:20px}
  131. .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}
  132. .ebtn:hover{background:#c00}
  133.  
  134. /* SKELETON */
  135. .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}
  136. @keyframes shim{0%{background-position:200% 0}100%{background-position:-200% 0}}
  137.  
  138. /* WATCH LATER */
  139. .wl-acts{display:flex;gap:6px;margin-left:auto;flex-wrap:wrap}
  140. .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}
  141. .abtn:active{transform:scale(.97)}
  142. .a-sec{background:var(--card);color:var(--text);border:1px solid var(--border)}.a-sec:hover{background:var(--hover)}
  143. .a-ok{background:rgba(76,175,80,.15);color:#4caf50;border:1px solid #4caf50}.a-ok:hover{background:rgba(76,175,80,.25)}
  144. .a-bad{background:var(--accent);color:#fff}.a-bad:hover{background:#c00}
  145.  
  146. /* WL STATS */
  147. .wl-stats{display:flex;background:var(--card);border:1px solid var(--border);border-radius:var(--r);margin-bottom:14px;overflow:hidden}
  148. .wl-stat{flex:1;padding:12px 16px;text-align:center;border-right:1px solid var(--border)}
  149. .wl-stat:last-child{border-right:none}
  150. .wl-sv{font-size:22px;font-weight:600;letter-spacing:-.04em}
  151. .wl-sl{font-size:11px;color:var(--text2);margin-top:2px}
  152.  
  153. /* WL CONTROLS */
  154. .wl-top{display:flex;gap:8px;margin-bottom:14px;align-items:center;flex-wrap:wrap}
  155. .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}
  156. .sel:focus{outline:none;border-color:var(--accent)}
  157.  
  158. /* PANELS */
  159. .panel{background:var(--card);border:1px solid var(--border);border-radius:var(--r);padding:16px;margin-bottom:16px;display:none}
  160. .panel.on{display:block;animation:slideDown .2s ease}
  161. @keyframes slideDown{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}
  162. .panel-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text2);margin-bottom:10px}
  163. .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}
  164. .panel textarea:focus{outline:none;border-color:var(--accent)}
  165. .panel-hint{font-size:11px;color:var(--text2);margin-top:6px;line-height:1.5}
  166. .panel-hint code{background:var(--bg2);padding:1px 4px;border-radius:3px;font-family:'DM Mono',monospace}
  167. .panel-btns{display:flex;gap:7px;margin-top:10px}
  168. .pbtn{padding:6px 13px;border:none;border-radius:13px;cursor:pointer;font-size:12px;font-weight:500;font-family:inherit;transition:background .15s}
  169. .pbtn-p{background:var(--accent);color:#fff}.pbtn-p:hover{background:#c00}
  170. .pbtn-s{background:var(--bg2);color:var(--text);border:1px solid var(--border)}.pbtn-s:hover{background:var(--hover)}
  171.  
  172. /* HISTORY */
  173. .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}
  174. .hist-section{margin-bottom:28px}
  175.  
  176. /* MINI PLAYER */
  177. .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}
  178. .mp.on{display:block;animation:pop .25s cubic-bezier(.34,1.56,.64,1)}
  179. @keyframes pop{from{opacity:0;transform:scale(.85) translateY(16px)}to{opacity:1;transform:scale(1) translateY(0)}}
  180. .mp.dragging{box-shadow:0 24px 64px rgba(0,0,0,.9)}
  181. .mp-hdr{display:flex;align-items:center;padding:7px 8px;gap:6px;background:rgba(0,0,0,.5);cursor:move;user-select:none}
  182. .mp-title{flex:1;font-size:11px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  183. .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}
  184. .mp-btn:hover{background:var(--hover);color:var(--text)}
  185. .mp iframe{width:100%;height:202px;border:none;display:block}
  186.  
  187. /* SHORTCUTS MODAL */
  188. .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)}
  189. .modal-bg.on{display:flex;animation:fadeIn .15s ease}
  190. @keyframes fadeIn{from{opacity:0}to{opacity:1}}
  191. .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}
  192. .modal h2{font-size:17px;font-weight:600;margin-bottom:18px;letter-spacing:-.03em}
  193. .sc-sec{margin-bottom:14px}
  194. .sc-lbl{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--text2);margin-bottom:6px}
  195. .sc-row{display:flex;align-items:center;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--border);font-size:12px}
  196. .sc-row:last-child{border-bottom:none}
  197. .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)}
  198.  
  199. /* CONTEXT MENU */
  200. .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}
  201. .ctxmenu.on{display:block;animation:fadeIn .1s ease}
  202. .ctx{padding:8px 14px;font-size:12px;cursor:pointer;display:flex;align-items:center;gap:9px;color:var(--text);transition:background .1s}
  203. .ctx:hover{background:var(--hover)}
  204. .ctx.red{color:#ff6b6b}
  205. .ctx-sep{height:1px;background:var(--border);margin:3px 0}
  206.  
  207. /* LOAD MORE */
  208. .lm-wrap{text-align:center;margin-top:24px}
  209. .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}
  210. .lm-btn:hover{background:var(--hover);transform:translateY(-1px)}
  211. .lm-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}
  212.  
  213. /* TOAST */
  214. .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}
  215. .toast.on{opacity:1;transform:translateX(-50%) translateY(0)}
  216. .toast.ok{border-color:#4caf50;color:#4caf50}
  217. .toast.err{border-color:var(--accent);color:var(--accent)}
  218.  
  219. @media(max-width:768px){
  220.     .sidebar{transform:translateX(-100%);transition:transform .3s}.sidebar.mob-open{transform:translateX(0)}
  221.     .main{margin-left:0}
  222.     .vgrid{grid-template-columns:1fr}
  223.     .pg-title{flex-direction:column;align-items:flex-start}
  224.     .mp{width:calc(100vw - 36px)}
  225.     .wl-acts{margin-left:0}
  226.     .theme-dots{display:none}
  227. }
  228. </style>
  229. </head>
  230. <body>
  231.  
  232. <!-- HEADER -->
  233. <header class="header">
  234.     <div class="logo" onclick="goHome()">
  235.         <div class="logo-icon"></div>
  236.         YouTube Premium
  237.     </div>
  238.     <div class="search-wrap">
  239.         <div class="search-row">
  240.             <input class="sbox" id="sbox" placeholder="Search videos…" autocomplete="off" spellcheck="false">
  241.             <button class="sbtn" id="sbtn" onclick="performSearch()">🔍</button>
  242.         </div>
  243.         <div class="sug-box" id="sugBox"></div>
  244.     </div>
  245.     <div class="header-right">
  246.         <div class="theme-dots" title="Theme">
  247.             <div class="tdot on" data-t="dark" onclick="setTheme('dark')" title="Dark"></div>
  248.             <div class="tdot" data-t="amoled" onclick="setTheme('amoled')" title="AMOLED Black"></div>
  249.             <div class="tdot" data-t="dim" onclick="setTheme('dim')" title="Dim"></div>
  250.         </div>
  251.         <button class="hbtn" onclick="randomVideo()" title="Random video (R)">🎲</button>
  252.         <button class="hbtn" onclick="openShortcuts()" title="Keyboard shortcuts (?)">⌨️</button>
  253.         <button class="hbtn" onclick="nav('history')" title="History (Shift+H)">📖</button>
  254.         <button class="hbtn" id="wlHbtn" onclick="nav('watch-later')" title="Watch Later (W)">
  255.             ⏰<span class="badge" id="wlBadge"></span>
  256.         </button>
  257.     </div>
  258. </header>
  259.  
  260. <!-- SIDEBAR -->
  261. <aside class="sidebar" id="sidebar">
  262.     <button class="nav-item on" id="navHome" onclick="goHome()"><span>🏠</span><span>Home</span></button>
  263.     <div class="nav-lbl">Filters</div>
  264.     <div id="filterBar">
  265.         <div class="fg" data-g="duration">
  266.             <div class="fh"><span>Duration</span><span class="fa">▼</span></div>
  267.             <div class="fo">
  268.                 <span class="fp on" data-v="">Any length</span>
  269.                 <span class="fp" data-v="short">≤ 4 min</span>
  270.                 <span class="fp" data-v="medium">4–20 min</span>
  271.                 <span class="fp" data-v="long">≥ 20 min</span>
  272.                 <div class="ci"><input type="text" placeholder="Custom minutes e.g. 30" id="custDur"></div>
  273.             </div>
  274.         </div>
  275.         <div class="fg" data-g="time">
  276.             <div class="fh"><span>Upload Time</span><span class="fa">▼</span></div>
  277.             <div class="fo">
  278.                 <span class="fp on" data-v="">Any time</span>
  279.                 <span class="fp" data-v="1">Last hour</span>
  280.                 <span class="fp" data-v="24">Today</span>
  281.                 <span class="fp" data-v="168">This week</span>
  282.                 <span class="fp" data-v="720">This month</span>
  283.                 <span class="fp" data-v="8760">This year</span>
  284.                 <div class="ci"><input type="date" id="custDate"></div>
  285.             </div>
  286.         </div>
  287.         <div class="fg" data-g="sort">
  288.             <div class="fh"><span>Sort By</span><span class="fa">▼</span></div>
  289.             <div class="fo">
  290.                 <span class="fp on" data-v="">Relevance</span>
  291.                 <span class="fp" data-v="viewCount">Most viewed</span>
  292.                 <span class="fp" data-v="date">Newest</span>
  293.                 <span class="fp" data-v="rating">Best rated</span>
  294.             </div>
  295.         </div>
  296.         <div class="fg" data-g="live">
  297.             <div class="fh"><span>Live</span><span class="fa">▼</span></div>
  298.             <div class="fo">
  299.                 <span class="fp on" data-v="">All</span>
  300.                 <span class="fp" data-v="live">Live now</span>
  301.                 <span class="fp" data-v="upcoming">Upcoming</span>
  302.             </div>
  303.         </div>
  304.         <div class="fg" data-g="hd">
  305.             <div class="fh"><span>Quality</span><span class="fa">▼</span></div>
  306.             <div class="fo">
  307.                 <span class="fp on" data-v="">Any quality</span>
  308.                 <span class="fp" data-v="high">HD only</span>
  309.             </div>
  310.         </div>
  311.     </div>
  312.     <div class="nav-lbl">Library</div>
  313.     <button class="nav-item" id="navHistory" onclick="nav('history')"><span>📖</span><span>History</span><span class="nav-ct" id="histCt"></span></button>
  314.     <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>
  315. </aside>
  316.  
  317. <!-- MAIN -->
  318. <main class="main">
  319.  
  320.     <!-- HOME -->
  321.     <div id="pg-home" class="pg on">
  322.         <div class="chip-row" id="chipRow"></div>
  323.         <h1 class="pg-title" id="homeTitle">Recommended Videos</h1>
  324.         <div class="ftags" id="ftags"></div>
  325.         <div class="vgrid" id="homeGrid"></div>
  326.         <div class="lm-wrap" id="lmWrap" style="display:none">
  327.             <button class="lm-btn" id="lmBtn" onclick="loadMore()">Load more videos</button>
  328.         </div>
  329.     </div>
  330.  
  331.     <!-- WATCH LATER -->
  332.     <div id="pg-watch-later" class="pg">
  333.         <h1 class="pg-title">Watch Later
  334.             <div class="wl-acts" id="wlActs" style="display:none">
  335.                 <button class="abtn a-sec" onclick="showPanel('exportPanel')">📋 Export</button>
  336.                 <button class="abtn a-ok" onclick="showPanel('importPanel')">📥 Import</button>
  337.                 <button class="abtn a-sec" onclick="shuffleWL()">🔀 Shuffle</button>
  338.                 <button class="abtn a-bad" onclick="clearAllWL()">🗑 Clear All</button>
  339.             </div>
  340.         </h1>
  341.         <div class="wl-stats" id="wlStats" style="display:none">
  342.             <div class="wl-stat"><div class="wl-sv" id="stCount">0</div><div class="wl-sl">Videos</div></div>
  343.             <div class="wl-stat"><div class="wl-sv" id="stWatched">0</div><div class="wl-sl">Watched</div></div>
  344.             <div class="wl-stat"><div class="wl-sv" id="stChannels">—</div><div class="wl-sl">Channels</div></div>
  345.         </div>
  346.         <div class="wl-top" id="wlTop" style="display:none">
  347.             <select class="sel" id="wlSort" onchange="renderWL()">
  348.                 <option value="newest">Newest first</option>
  349.                 <option value="oldest">Oldest first</option>
  350.                 <option value="az">A → Z</option>
  351.                 <option value="za">Z → A</option>
  352.             </select>
  353.             <select class="sel" id="wlFilt" onchange="renderWL()">
  354.                 <option value="all">All videos</option>
  355.                 <option value="unwatched">Unwatched only</option>
  356.                 <option value="watched">Watched only</option>
  357.             </select>
  358.         </div>
  359.         <div class="panel" id="exportPanel">
  360.             <div class="panel-title">📋 Export — Copy your video IDs</div>
  361.             <textarea id="exportArea" readonly onclick="this.select()"></textarea>
  362.             <div class="panel-hint">Each line is one video. Paste the text into a google doc or slide to keep format.</div>
  363.             <div class="panel-btns">
  364.                 <button class="pbtn pbtn-p" onclick="copyExport()">Copy to clipboard</button>
  365.                 <button class="pbtn pbtn-s" onclick="closePanel('exportPanel')">Close</button>
  366.             </div>
  367.         </div>
  368.         <div class="panel" id="importPanel">
  369.             <div class="panel-title">📥 Import — Paste video IDs</div>
  370.             <textarea id="importArea" placeholder="Paste video IDs here, one per line or comma-separated.&#10;&#10;Example:&#10;dQw4w9WgXcQ&#10;kJQP7kiw5Fk"></textarea>
  371.             <div class="panel-hint">Paste IDs or full YouTube URLs. Separate with <code>newlines</code> or <code>commas</code>. Duplicates are skipped.</div>
  372.             <div class="panel-btns">
  373.                 <button class="pbtn pbtn-p" onclick="runImport('importArea')">Import videos</button>
  374.                 <button class="pbtn pbtn-s" onclick="closePanel('importPanel')">Cancel</button>
  375.             </div>
  376.         </div>
  377.         <div class="panel" id="importOnlyPanel">
  378.             <div class="panel-title">📥 Import — Paste video IDs</div>
  379.             <textarea id="importOnlyArea" placeholder="Paste video IDs here, one per line or comma-separated."></textarea>
  380.             <div class="panel-hint">Paste IDs or full YouTube URLs.</div>
  381.             <div class="panel-btns">
  382.                 <button class="pbtn pbtn-p" onclick="runImport('importOnlyArea')">Import videos</button>
  383.                 <button class="pbtn pbtn-s" onclick="closePanel('importOnlyPanel')">Cancel</button>
  384.             </div>
  385.         </div>
  386.         <div class="vgrid" id="wlGrid"></div>
  387.     </div>
  388.  
  389.     <!-- HISTORY -->
  390.     <div id="pg-history" class="pg">
  391.         <h1 class="pg-title">Watch History
  392.             <button class="abtn a-bad" style="margin-left:auto" onclick="clearHistory()">🗑 Clear History</button>
  393.         </h1>
  394.         <div id="histGrid"></div>
  395.     </div>
  396.  
  397. </main>
  398.  
  399. <!-- MINI PLAYER -->
  400. <div class="mp" id="miniPlayer">
  401.     <div class="mp-hdr" id="mpHdr">
  402.         <span class="mp-title" id="mpTitle">Now Playing</span>
  403.         <button class="mp-btn" onclick="popOutPlayer()" title="Pop out (P)">⤢</button>
  404.         <button class="mp-btn" onclick="closeMiniPlayer()" title="Close (Q)">✕</button>
  405.     </div>
  406.     <iframe id="mpFrame" src="" allowfullscreen allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"></iframe>
  407. </div>
  408.  
  409. <!-- SHORTCUTS MODAL -->
  410. <div class="modal-bg" id="shortcutsModal" onclick="closeShortcuts(event)">
  411.     <div class="modal" onclick="event.stopPropagation()">
  412.         <h2>⌨️ Keyboard Shortcuts</h2>
  413.         <div class="sc-sec">
  414.             <div class="sc-lbl">Navigation</div>
  415.             <div class="sc-row"><span>Go Home</span><span class="kbd">H</span></div>
  416.             <div class="sc-row"><span>Watch Later</span><span class="kbd">W</span></div>
  417.             <div class="sc-row"><span>History</span><span class="kbd">Shift + H</span></div>
  418.         </div>
  419.         <div class="sc-sec">
  420.             <div class="sc-lbl">Search</div>
  421.             <div class="sc-row"><span>Focus search bar</span><span class="kbd">/</span></div>
  422.             <div class="sc-row"><span>Clear & go home</span><span class="kbd">Esc (in search)</span></div>
  423.        </div>
  424.        <div class="sc-sec">
  425.            <div class="sc-lbl">Player</div>
  426.            <div class="sc-row"><span>Close mini player</span><span class="kbd">Q</span></div>
  427.            <div class="sc-row"><span>Pop out to window</span><span class="kbd">P</span></div>
  428.            <div class="sc-row"><span>Play random video</span><span class="kbd">R</span></div>
  429.        </div>
  430.        <div class="sc-sec">
  431.            <div class="sc-lbl">Watch Later</div>
  432.            <div class="sc-row"><span>Shuffle list</span><span class="kbd">Shift + S</span></div>
  433.        </div>
  434.        <div class="sc-sec">
  435.            <div class="sc-lbl">Help</div>
  436.            <div class="sc-row"><span>Show this dialog</span><span class="kbd">?</span></div>
  437.        </div>
  438.    </div>
  439. </div>
  440.  
  441. <!-- CONTEXT MENU -->
  442. <div class="ctxmenu" id="ctxMenu">
  443.    <div class="ctx" onclick="ctxAction('watch')">▶ Watch video</div>
  444.    <div class="ctx" id="ctx-wl" onclick="ctxAction('wl')">⏰ Add to Watch Later</div>
  445.    <div class="ctx" onclick="ctxAction('copy')">🔗 Copy link</div>
  446.    <div class="ctx" id="ctx-ch" onclick="ctxAction('channel')">🔍 Search this channel</div>
  447.    <div class="ctx-sep"></div>
  448.    <div class="ctx" onclick="ctxAction('yt')">🌐 Open on YouTube</div>
  449. </div>
  450.  
  451. <!-- TOAST -->
  452. <div class="toast" id="toast"></div>
  453.  
  454. <script>
  455. /* ═══════════════════════════════════════════
  456.   SETTINGS
  457. ═══════════════════════════════════════════ */
  458. const S = {
  459.    API_KEY: 'REPLACE THIS TEXT WITH YOUR KEY',
  460.    SEARCH_COUNT: 50,
  461.    COOLDOWN: 3,
  462.    FILTER_LABELS: {
  463.        duration: { short:'≤ 4 min', medium:'4–20 min', long:'≥ 20 min' },
  464.        sort:     { viewCount:'Most viewed', date:'Newest', rating:'Best rated' },
  465.        live:     { live:'Live now', upcoming:'Upcoming' },
  466.        hd:       { high:'HD only' },
  467.    },
  468.    CATEGORIES: ['🎵 Music','🎮 Gaming','📰 News','⚽ Sports','🎬 Movies','🍳 Cooking','🔬 Science','😂 Comedy','✈️ Travel','💻 Tech','🎨 Art','📚 Education'],
  469.    HOME_VIDEOS: [
  470.        { id:'dQw4w9WgXcQ', title:'Rick Astley – Never Gonna Give You Up', channel:'Rick Astley', views:'1,400,000,000', duration:'3:33' },
  471.        { id:'9bZkp7q19f0', title:'PSY – GANGNAM STYLE', channel:'officialpsy', views:'4,900,000,000', duration:'4:13' },
  472.        { id:'kJQP7kiw5Fk', title:'Luis Fonsi – Despacito ft. Daddy Yankee', channel:'LuisFonsiVEVO', views:'8,200,000,000', duration:'4:42' },
  473.        { id:'JGwWNGJdvx8', title:'Ed Sheeran – Shape of You', channel:'Ed Sheeran', views:'5,900,000,000', duration:'4:24' },
  474.        { id:'OPf0YbXqDm0', title:'Mark Ronson – Uptown Funk ft. Bruno Mars', channel:'Mark Ronson', views:'4,800,000,000', duration:'4:00' },
  475.        { id:'hT_nvWreIhg', title:'OneRepublic – Counting Stars', channel:'OneRepublic', views:'3,500,000,000', duration:'4:17' },
  476.        { id:'450p7goxZqg', title:'Pharrell Williams – Happy', channel:'Pharrell Williams', views:'1,500,000,000', duration:'3:53' },
  477.        { id:'CevxZvSJLk8', title:'Katy Perry – Roar', channel:'Katy Perry', views:'3,900,000,000', duration:'4:32' },
  478.        { id:'09R8_2nJtjg', title:'Taylor Swift – Shake It Off', channel:'Taylor Swift', views:'3,200,000,000', duration:'3:39' },
  479.        { id:'bo_efYhYU2A', title:'twenty one pilots – Stressed Out', channel:'Twenty One Pilots', views:'1,300,000,000', duration:'3:22' },
  480.        { id:'ru0K8uYEZWw', title:'Justin Bieber – Love Yourself', channel:'Justin Bieber', views:'2,000,000,000', duration:'3:52' },
  481.        { id:'YqeW9_5kURI', title:"Justin Timberlake – Can't Stop the Feeling!", channel:'Justin Timberlake', views:'1,400,000,000', duration:'3:56' },
  482.        { id:'SlPhMPnQ58k', title:'Imagine Dragons – Believer', channel:'Imagine Dragons', views:'1,500,000,000', duration:'3:24' },
  483.        { id:'7wtfhZwyrcc', title:'Imagine Dragons – Thunder', channel:'Imagine Dragons', views:'1,200,000,000', duration:'3:07' },
  484.        { id:'pRpeEdMmmQ0', title:'Shawn Mendes – Treat You Better', channel:'Shawn Mendes', views:'1,300,000,000', duration:'3:08' },
  485.        { id:'jNQXAC9IVRw', title:'Me at the zoo – The First YouTube Video', channel:'jawed', views:'300,000,000', duration:'0:18' },
  486.    ],
  487. };
  488.  
  489. /* ═══════════════════════════════════════════
  490.    STATE
  491. ═══════════════════════════════════════════ */
  492. const cache = {};
  493. let hasSearched = false, curQuery = '', filterTimer = null, curPage = 'home';
  494. let nextPageToken = null, activeChip = '';
  495. let wl       = JSON.parse(localStorage.getItem('watchLater')      || '[]');
  496. let watched  = JSON.parse(localStorage.getItem('watchedVideos')   || '{}');
  497. let hist     = JSON.parse(localStorage.getItem('videoHistory')    || '[]');
  498. let recents  = JSON.parse(localStorage.getItem('recentSearches')  || '[]');
  499. const filters = { duration:'', time:'', sort:'', live:'', hd:'' };
  500. let ctxVideo = null, mpVideoId = null, mpTitle = '', _drag = null;
  501.  
  502. /* ═══════════════════════════════════════════
  503.    INIT
  504. ═══════════════════════════════════════════ */
  505. window.onload = function() {
  506.     initTheme();
  507.     renderChips();
  508.     renderHome();
  509.     bindSearch();
  510.     bindFilters();
  511.     bindCustomInputs();
  512.     bindContextMenu();
  513.     bindKeyboard();
  514.     initDrag();
  515.     updateBadge();
  516.     updateHistCt();
  517. };
  518.  
  519. /* ═══════════════════════════════════════════
  520.    THEME
  521. ═══════════════════════════════════════════ */
  522. function initTheme() { applyTheme(localStorage.getItem('theme') || 'dark'); }
  523. function setTheme(t) { applyTheme(t); localStorage.setItem('theme', t); }
  524. function applyTheme(t) {
  525.     document.body.dataset.theme = t === 'dark' ? '' : t;
  526.     document.querySelectorAll('.tdot').forEach(d => d.classList.toggle('on', d.dataset.t === t));
  527. }
  528.  
  529. /* ═══════════════════════════════════════════
  530.    NAVIGATION
  531. ═══════════════════════════════════════════ */
  532. function goHome() {
  533.     document.getElementById('sbox').value = '';
  534.     hasSearched = false; curQuery = ''; activeChip = '';
  535.     renderChips(); _nav('home'); renderHome();
  536. }
  537. function nav(pg) {
  538.     _nav(pg);
  539.     if (pg === 'watch-later') renderWL();
  540.     if (pg === 'history') renderHistory();
  541. }
  542. function _nav(pg) {
  543.     document.querySelectorAll('.pg').forEach(p => p.classList.remove('on'));
  544.     const pgEl = document.getElementById('pg-' + pg);
  545.     if (pgEl) pgEl.classList.add('on');
  546.     document.getElementById('navHome').classList.toggle('on', pg === 'home');
  547.     const navH = document.getElementById('navHistory');
  548.     const navW = document.getElementById('navWatch-later');
  549.     if (navH) navH.classList.toggle('on', pg === 'history');
  550.     if (navW) navW.classList.toggle('on', pg === 'watch-later');
  551.     curPage = pg;
  552. }
  553.  
  554. /* ═══════════════════════════════════════════
  555.    CATEGORY CHIPS
  556. ═══════════════════════════════════════════ */
  557. function renderChips() {
  558.     const row = document.getElementById('chipRow');
  559.     row.innerHTML = '';
  560.     const makeChip = (label, val) => {
  561.         const c = document.createElement('div');
  562.         c.className = 'chip' + (activeChip === val ? ' on' : '');
  563.         c.textContent = label;
  564.         c.onclick = () => {
  565.             if (val === '') { activeChip = ''; renderChips(); goHome(); }
  566.             else { activeChip = val; renderChips(); document.getElementById('sbox').value = val; performSearch(val); }
  567.         };
  568.         row.appendChild(c);
  569.     };
  570.     makeChip('✨ All', '');
  571.     S.CATEGORIES.forEach(c => makeChip(c, c.replace(/^\S+\s/, '')));
  572. }
  573.  
  574. /* ═══════════════════════════════════════════
  575.    HOME RENDER
  576. ═══════════════════════════════════════════ */
  577. function renderHome() {
  578.     const grid = document.getElementById('homeGrid');
  579.     grid.innerHTML = '';
  580.     document.getElementById('homeTitle').innerHTML = 'Recommended Videos';
  581.     document.getElementById('lmWrap').style.display = 'none';
  582.     updateFTags();
  583.     S.HOME_VIDEOS.forEach(v => {
  584.         const thumb = `https://i.ytimg.com/vi/${v.id}/mqdefault.jpg`;
  585.         grid.appendChild(makeCard(v.id, v.title, v.channel, v.views + ' views', v.duration, thumb, false));
  586.     });
  587. }
  588.  
  589. /* ═══════════════════════════════════════════
  590.    CARD BUILDER
  591. ═══════════════════════════════════════════ */
  592. function makeCard(id, title, channel, meta, duration, thumb, isWL) {
  593.     const inWL = wl.some(w => w.id === id);
  594.     const isWatched = !!watched[id];
  595.     const isPlaying = mpVideoId === id;
  596.     const safeTitle = (title || '').replace(/'/g, "\\'");
  597.     const safeCh = (channel || '').replace(/'/g, "\\'");
  598.  
  599.     const card = document.createElement('div');
  600.     card.className = 'vc';
  601.     card.dataset.id = id;
  602.     card.dataset.title = title || '';
  603.     card.dataset.channel = channel || '';
  604.     card.dataset.thumb = thumb || '';
  605.  
  606.     if (isWL) {
  607.         const wlEntry = wl.find(w => w.id === id);
  608.         card.innerHTML = `
  609.             <div class="vthumb">
  610.                 <img src="${thumb}" alt="">
  611.                 <button class="rm-btn" onclick="removeFromWL('${id}',event)">✕</button>
  612.             </div>
  613.             <div class="vinfo">
  614.                 <div class="vtitle">${title}</div>
  615.                 <div class="vmeta">
  616.                     <div><span class="vch" onclick="searchChannel('${safeCh}',event)">${channel || ''}</span></div>
  617.                     ${wlEntry ? `<div class="vviews">Added ${new Date(wlEntry.addedAt).toLocaleDateString()}</div>` : ''}
  618.                     <div style="font-size:10px;color:#444;margin-top:2px;font-family:'DM Mono',monospace">${id}</div>
  619.                 </div>
  620.             </div>
  621.             <div class="wl-footer">
  622.                 <div class="wl-check ${isWatched?'done':''}" onclick="toggleWatched('${id}',event)">${isWatched?'✓':''}</div>
  623.                 <span class="wl-check-lbl">${isWatched?'Watched':'Mark as watched'}</span>
  624.             </div>`;
  625.         card.onclick = e => {
  626.             if (['rm-btn','wl-check','vch'].some(c => e.target.classList.contains(c))) return;
  627.             playVideo(id, title, thumb);
  628.         };
  629.     } else {
  630.         card.innerHTML = `
  631.             <div class="vthumb">
  632.                 <img src="${thumb}" alt="" id="img-${id}">
  633.                 ${duration ? `<div class="vdur">${duration}</div>` : ''}
  634.                 <div class="v-watched-badge ${isWatched?'on':''}">Watched</div>
  635.                 <div class="v-now-playing ${isPlaying?'on':''}"><span style="animation:pulse 1s infinite">●</span> Playing</div>
  636.                 <div class="vacts">
  637.                     <button class="vbtn ${inWL?'on':''}" onclick="toggleWL('${id}','${safeTitle}','${thumb}',event)" title="${inWL?'Remove from':'Add to'} Watch Later">${inWL?'✓':'⏰'}</button>
  638.                     <button class="vbtn" onclick="copyLink('${id}',event)" title="Copy link">🔗</button>
  639.                 </div>
  640.             </div>
  641.             <div class="vinfo">
  642.                 <div class="vtitle">${title}</div>
  643.                 <div class="vmeta">
  644.                     <div><span class="vch" onclick="searchChannel('${safeCh}',event)">${channel || ''}</span></div>
  645.                     <div class="vviews">${meta || ''}</div>
  646.                 </div>
  647.             </div>`;
  648.         card.onclick = e => {
  649.             if (['vbtn','vch'].some(c => e.target.classList.contains(c))) return;
  650.             playVideo(id, title, thumb);
  651.         };
  652.         bindThumbCycle(card, id);
  653.     }
  654.  
  655.     card.addEventListener('contextmenu', e => { e.preventDefault(); showCtx(e, {id, title, channel, thumb}); });
  656.     return card;
  657. }
  658.  
  659. /* ═══════════════════════════════════════════
  660.    THUMBNAIL HOVER CYCLING
  661. ═══════════════════════════════════════════ */
  662. function bindThumbCycle(card, id) {
  663.     let hoverTimer = null, cycleTimer = null, frame = 0;
  664.     const frames = [0,1,2,3].map(n => `https://i.ytimg.com/vi/${id}/${n}.jpg`);
  665.     const img = card.querySelector('img');
  666.     const orig = img.src;
  667.     card.addEventListener('mouseenter', () => {
  668.         hoverTimer = setTimeout(() => {
  669.             frame = 0;
  670.             cycleTimer = setInterval(() => { frame = (frame+1)%frames.length; img.src = frames[frame]; }, 700);
  671.         }, 900);
  672.     });
  673.     card.addEventListener('mouseleave', () => {
  674.         clearTimeout(hoverTimer); clearInterval(cycleTimer); img.src = orig;
  675.     });
  676. }
  677.  
  678. /* ═══════════════════════════════════════════
  679.    SEARCH
  680. ═══════════════════════════════════════════ */
  681. function bindSearch() {
  682.     const box = document.getElementById('sbox');
  683.     const sug = document.getElementById('sugBox');
  684.     box.addEventListener('keypress', e => { if (e.key==='Enter') { sug.classList.remove('on'); performSearch(); } });
  685.     box.addEventListener('input', () => {
  686.         const v = box.value.trim();
  687.         if (!v) { if (recents.length) renderSuggestions(); else sug.classList.remove('on'); return; }
  688.         const matches = recents.filter(r => r.toLowerCase().startsWith(v.toLowerCase())).slice(0,5);
  689.         matches.length ? renderSuggestions(matches) : sug.classList.remove('on');
  690.     });
  691.     box.addEventListener('focus', () => { if (!box.value.trim() && recents.length) renderSuggestions(); });
  692.     document.addEventListener('click', e => { if (!e.target.closest('.search-wrap')) sug.classList.remove('on'); });
  693. }
  694.  
  695. function renderSuggestions(list) {
  696.     const sug = document.getElementById('sugBox');
  697.     const items = list || recents.slice(0,6);
  698.     if (!items.length) { sug.classList.remove('on'); return; }
  699.     sug.innerHTML = '<div style="padding:7px 14px 3px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--text2)">Recent Searches</div>';
  700.     items.forEach(s => {
  701.         const d = document.createElement('div');
  702.         d.className = 'sug-item';
  703.         d.innerHTML = `<span style="font-size:11px">🕐</span><span style="flex:1">${s}</span><span class="sug-rm">✕</span>`;
  704.         d.querySelector('.sug-rm').onclick = e => { e.stopPropagation(); removeRecent(s); };
  705.         d.onclick = () => { document.getElementById('sbox').value = s; sug.classList.remove('on'); performSearch(s); };
  706.         sug.appendChild(d);
  707.     });
  708.     sug.classList.add('on');
  709. }
  710.  
  711. function removeRecent(s) {
  712.     recents = recents.filter(r => r !== s);
  713.     localStorage.setItem('recentSearches', JSON.stringify(recents));
  714.     recents.length ? renderSuggestions() : document.getElementById('sugBox').classList.remove('on');
  715. }
  716.  
  717. function saveRecent(q) {
  718.     recents = [q, ...recents.filter(r => r !== q)].slice(0,10);
  719.     localStorage.setItem('recentSearches', JSON.stringify(recents));
  720. }
  721.  
  722. function performSearch(forceQ) {
  723.     const q = (forceQ !== undefined ? forceQ : document.getElementById('sbox').value).trim();
  724.     if (!q) { goHome(); return; }
  725.     const btn = document.getElementById('sbtn');
  726.     if (btn.disabled) return;
  727.  
  728.     const key = q.toLowerCase() + '|' + JSON.stringify(filters);
  729.     if (cache[key]) {
  730.         const c = cache[key];
  731.         hasSearched = true; curQuery = q;
  732.         _nav('home'); saveRecent(q);
  733.         document.getElementById('homeTitle').innerHTML = `Results for "<em>${q}</em>" <span class="res-count">${c.total?'~'+Number(c.total).toLocaleString()+' results':''}</span>`;
  734.         updateFTags(); _renderCards(c.videos, c.detailMap, false);
  735.         nextPageToken = c.nextPageToken || null;
  736.         document.getElementById('lmWrap').style.display = nextPageToken ? 'block' : 'none';
  737.         showToast('Showing cached results ⚡'); return;
  738.     }
  739.  
  740.     startCooldown(); hasSearched = true; curQuery = q;
  741.     _nav('home'); saveRecent(q);
  742.     document.getElementById('homeTitle').innerHTML = `Results for "<em>${q}</em>"`;
  743.     updateFTags(); showSkel('homeGrid');
  744.     document.getElementById('lmWrap').style.display = 'none';
  745.     doSearch(q, key);
  746. }
  747.  
  748. function startCooldown() {
  749.     const btn = document.getElementById('sbtn');
  750.     let s = S.COOLDOWN; btn.disabled = true; btn.textContent = s+'s';
  751.     const iv = setInterval(() => { s--; if(s<=0){clearInterval(iv);btn.disabled=false;btn.textContent='🔍';}else btn.textContent=s+'s'; }, 1000);
  752. }
  753.  
  754. function doSearch(q, key) {
  755.    let url = `https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&maxResults=${S.SEARCH_COUNT}&q=${encodeURIComponent(q)}&key=${S.API_KEY}`;
  756.    if (filters.duration) url += `&videoDuration=${filters.duration}`;
  757.    if (filters.sort)     url += `&order=${filters.sort}`;
  758.    if (filters.live)     url += `&eventType=${filters.live}`;
  759.    if (filters.hd)       url += `&videoDefinition=${filters.hd}`;
  760.    if (filters.time)     url += `&publishedAfter=${new Date(Date.now()-Number(filters.time)*3600000).toISOString()}`;
  761.    fetch(url).then(r=>r.json()).then(d => {
  762.         if (!d.items) { showErr('homeGrid'); return; }
  763.         nextPageToken = d.nextPageToken || null;
  764.         fetchDetails(d.items, key, d.pageInfo?.totalResults, nextPageToken);
  765.     }).catch(() => showErr('homeGrid'));
  766. }
  767.  
  768. function fetchDetails(videos, key, total, npToken) {
  769.     const ids = videos.map(v => v.id.videoId).filter(Boolean).join(',');
  770.     if (!ids) return;
  771.     fetch(`https://www.googleapis.com/youtube/v3/videos?part=contentDetails,statistics&id=${ids}&key=${S.API_KEY}`)
  772.        .then(r => r.json()).then(d => {
  773.            const map = {};
  774.             (d.items||[]).forEach(v => { map[v.id] = { duration:fmtDur(v.contentDetails.duration), views:parseInt(v.statistics.viewCount||0) }; });
  775.             if (key) cache[key] = {videos, detailMap:map, total, nextPageToken:npToken};
  776.             document.getElementById('homeTitle').innerHTML = `Results for "<em>${curQuery}</em>" <span class="res-count">${total?'~'+Number(total).toLocaleString()+' results':''}</span>`;
  777.             _renderCards(videos, map, false);
  778.             document.getElementById('lmWrap').style.display = npToken ? 'block' : 'none';
  779.         });
  780. }
  781.  
  782. function loadMore() {
  783.     if (!nextPageToken || !curQuery) return;
  784.     const btn = document.getElementById('lmBtn');
  785.     btn.disabled = true; btn.textContent = 'Loading…';
  786.     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}`;
  787.     if (filters.sort) url += `&order=${filters.sort}`;
  788.     fetch(url).then(r=>r.json()).then(d => {
  789.         if (!d.items) { btn.disabled=false; btn.textContent='Load more videos'; return; }
  790.         nextPageToken = d.nextPageToken || null;
  791.         const ids = d.items.map(v=>v.id.videoId).filter(Boolean).join(',');
  792.         fetch(`https://www.googleapis.com/youtube/v3/videos?part=contentDetails,statistics&id=${ids}&key=${S.API_KEY}`)
  793.            .then(r=>r.json()).then(dd => {
  794.                const map = {};
  795.                 (dd.items||[]).forEach(v => { map[v.id]={duration:fmtDur(v.contentDetails.duration),views:parseInt(v.statistics.viewCount||0)}; });
  796.                 _renderCards(d.items, map, true);
  797.                 btn.disabled=false; btn.textContent='Load more videos';
  798.                 document.getElementById('lmWrap').style.display = nextPageToken ? 'block' : 'none';
  799.             });
  800.     });
  801. }
  802.  
  803. function _renderCards(videos, map, append) {
  804.     const grid = document.getElementById('homeGrid');
  805.     if (!append) grid.innerHTML = '';
  806.     videos.forEach(v => {
  807.         const id = v.id.videoId;
  808.         const info = map[id] || {duration:'?', views:0};
  809.         const thumb = v.snippet.thumbnails.medium.url;
  810.         grid.appendChild(makeCard(id, v.snippet.title, v.snippet.channelTitle, Number(info.views).toLocaleString()+' views', info.duration, thumb, false));
  811.     });
  812. }
  813.  
  814. /* ═══════════════════════════════════════════
  815.    FILTERS
  816. ═══════════════════════════════════════════ */
  817. function bindFilters() {
  818.     document.querySelectorAll('.fh').forEach(h => {
  819.         h.addEventListener('click', () => {
  820.             const g = h.parentElement, isOpen = g.classList.contains('open');
  821.             document.querySelectorAll('.fg').forEach(fg => fg.classList.remove('open'));
  822.             if (!isOpen) g.classList.add('open');
  823.         });
  824.     });
  825.     document.querySelectorAll('.fp').forEach(pill => {
  826.         pill.addEventListener('click', function() {
  827.             const gEl = this.closest('.fg'), g = gEl.dataset.g;
  828.             gEl.querySelectorAll('.fp').forEach(p => p.classList.remove('on'));
  829.             this.classList.add('on'); filters[g] = this.dataset.v;
  830.             if (g==='live' && this.dataset.v) {
  831.                filters.duration = '';
  832.                 document.querySelectorAll('.fg[data-g="duration"] .fp').forEach(p => p.classList.toggle('on', p.dataset.v===''));
  833.                 document.getElementById('custDur').value = '';
  834.                 showToast('Duration filter cleared — not supported for live events');
  835.             }
  836.             if (g==='duration') document.getElementById('custDur').value = '';
  837.             if (g==='time')     document.getElementById('custDate').value = '';
  838.             updateFTags(); updateFilterDot();
  839.             if (hasSearched && curQuery) { clearTimeout(filterTimer); filterTimer=setTimeout(()=>performSearch(curQuery),400); }
  840.         });
  841.     });
  842. }
  843.  
  844. function bindCustomInputs() {
  845.     document.getElementById('custDur').addEventListener('change', function() {
  846.         const v = parseInt(this.value);
  847.         if (!isNaN(v) && v>0) {
  848.            filters.duration = v<=4?'short':v>=20?'long':'medium';
  849.             document.querySelectorAll('.fg[data-g="duration"] .fp').forEach(p => p.classList.remove('on'));
  850.             updateFTags(); updateFilterDot();
  851.             if (hasSearched && curQuery) performSearch(curQuery);
  852.         }
  853.     });
  854.     document.getElementById('custDate').addEventListener('change', function() {
  855.         if (!this.value) return;
  856.         const [y,m,d] = this.value.split('-').map(Number);
  857.         filters.time = Math.floor((Date.now()-new Date(y,m-1,d).getTime())/3600000);
  858.         document.querySelectorAll('.fg[data-g="time"] .fp').forEach(p => p.classList.remove('on'));
  859.         updateFTags(); updateFilterDot();
  860.         if (hasSearched && curQuery) performSearch(curQuery);
  861.     });
  862. }
  863.  
  864. function updateFilterDot() {
  865.     document.getElementById('filtersDot').classList.toggle('on', Object.values(filters).some(v => v!==''));
  866. }
  867.  
  868. function updateFTags() {
  869.     const bar = document.getElementById('ftags');
  870.     bar.querySelectorAll('.ftag').forEach(t => t.remove());
  871.     const tags = [];
  872.     if (filters.duration) tags.push({g:'duration', label:`Duration: ${S.FILTER_LABELS.duration[filters.duration]||filters.duration}`});
  873.     if (filters.sort)     tags.push({g:'sort',     label:`Sort: ${S.FILTER_LABELS.sort[filters.sort]||filters.sort}`});
  874.     if (filters.live)     tags.push({g:'live',     label:S.FILTER_LABELS.live[filters.live]||filters.live});
  875.     if (filters.hd)       tags.push({g:'hd',       label:'HD only'});
  876.     if (filters.time) {
  877.         const pill = document.querySelector('.fg[data-g="time"] .fp.on');
  878.         tags.push({g:'time', label:`Time: ${(pill&&pill.dataset.v)?pill.textContent:'Custom date'}`});
  879.     }
  880.     tags.forEach(t => {
  881.         const tag = document.createElement('span');
  882.         tag.className = 'ftag';
  883.         tag.innerHTML = `${t.label} <button onclick="clearFilter('${t.g}')">×</button>`;
  884.         bar.appendChild(tag);
  885.     });
  886.     bar.classList.toggle('on', tags.length > 0);
  887. }
  888.  
  889. function clearFilter(g) {
  890.     filters[g] = '';
  891.     const gEl = document.querySelector(`.fg[data-g="${g}"]`);
  892.     if (gEl) gEl.querySelectorAll('.fp').forEach(p => p.classList.toggle('on', p.dataset.v===''));
  893.     if (g==='duration') document.getElementById('custDur').value = '';
  894.     if (g==='time')     document.getElementById('custDate').value = '';
  895.     updateFTags(); updateFilterDot();
  896.     if (hasSearched && curQuery) { clearTimeout(filterTimer); filterTimer=setTimeout(()=>performSearch(curQuery),400); }
  897. }
  898.  
  899. /* ═══════════════════════════════════════════
  900.    WATCH LATER
  901. ═══════════════════════════════════════════ */
  902. function renderWL() {
  903.     closePanel('exportPanel'); closePanel('importPanel'); closePanel('importOnlyPanel');
  904.     const grid = document.getElementById('wlGrid');
  905.     const acts = document.getElementById('wlActs');
  906.     const stats = document.getElementById('wlStats');
  907.     const top = document.getElementById('wlTop');
  908.  
  909.     if (!wl.length) {
  910.         acts.style.display = 'none'; stats.style.display = 'none'; top.style.display = 'none';
  911.         grid.innerHTML = `<div class="empty"><h3>No videos saved</h3><p>Click ⏰ on any video, or import IDs below.</p>
  912.             <button class="ebtn" onclick="goHome()">Browse Videos</button>&nbsp;&nbsp;
  913.             <button class="ebtn" style="background:rgba(76,175,80,.2);color:#4caf50;border:1px solid #4caf50" onclick="showPanel('importOnlyPanel')">📥 Import IDs</button></div>`;
  914.         return;
  915.     }
  916.  
  917.     acts.style.display = 'flex'; stats.style.display = 'flex'; top.style.display = 'flex';
  918.  
  919.     // Stats
  920.     const watchedCt = wl.filter(v => watched[v.id]).length;
  921.     const channels = new Set(wl.map(v => v.channel||'').filter(Boolean)).size;
  922.     document.getElementById('stCount').textContent = wl.length;
  923.     document.getElementById('stWatched').textContent = watchedCt;
  924.     document.getElementById('stChannels').textContent = channels || '—';
  925.  
  926.     // Sort + filter
  927.     const sort = document.getElementById('wlSort').value;
  928.     const filt = document.getElementById('wlFilt').value;
  929.     let list = [...wl];
  930.     if (filt==='watched')   list = list.filter(v =>  watched[v.id]);
  931.     if (filt==='unwatched') list = list.filter(v => !watched[v.id]);
  932.     if (sort==='oldest') list.sort((a,b) => new Date(a.addedAt)-new Date(b.addedAt));
  933.     else if (sort==='newest') list.sort((a,b) => new Date(b.addedAt)-new Date(a.addedAt));
  934.     else if (sort==='az') list.sort((a,b) => a.title.localeCompare(b.title));
  935.     else if (sort==='za') list.sort((a,b) => b.title.localeCompare(a.title));
  936.  
  937.     grid.innerHTML = '';
  938.     list.forEach(v => grid.appendChild(makeCard(v.id, v.title, v.channel||'', '', '', v.thumbnail, true)));
  939. }
  940.  
  941. function shuffleWL() {
  942.     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]]; }
  943.     saveWL(); renderWL(); showToast('Watch Later shuffled 🔀');
  944. }
  945.  
  946. function toggleWL(id, title, thumb, e) {
  947.     e.stopPropagation();
  948.     const btn = e.target;
  949.     const idx = wl.findIndex(v => v.id===id);
  950.     if (idx===-1) {
  951.         const channel = btn.closest('.vc')?.dataset.channel || '';
  952.         wl.push({id, title, thumbnail:thumb, channel, addedAt:new Date().toISOString()});
  953.         btn.classList.add('on'); btn.textContent = '✓';
  954.         showToast('Added to Watch Later', 'ok');
  955.     } else {
  956.         wl.splice(idx, 1);
  957.         btn.classList.remove('on'); btn.textContent = '⏰';
  958.         showToast('Removed from Watch Later');
  959.     }
  960.     saveWL();
  961. }
  962.  
  963. function removeFromWL(id, e) {
  964.     e.stopPropagation();
  965.     wl = wl.filter(v => v.id!==id);
  966.     saveWL(); renderWL();
  967. }
  968.  
  969. function clearAllWL() {
  970.     if (confirm('Remove all videos from Watch Later?')) { wl=[]; saveWL(); renderWL(); }
  971. }
  972.  
  973. function toggleWatched(id, e) {
  974.     e.stopPropagation();
  975.     if (watched[id]) delete watched[id]; else watched[id] = true;
  976.     localStorage.setItem('watchedVideos', JSON.stringify(watched));
  977.     renderWL();
  978. }
  979.  
  980. function saveWL() { localStorage.setItem('watchLater', JSON.stringify(wl)); updateBadge(); }
  981.  
  982. function updateBadge() {
  983.     const b = document.getElementById('wlBadge'), c = wl.length;
  984.     b.textContent = c>99?'99+':c;
  985.     b.style.display = c>0?'block':'none';
  986. }
  987.  
  988. /* ═══════════════════════════════════════════
  989.    EXPORT / IMPORT
  990. ═══════════════════════════════════════════ */
  991. function showPanel(id) {
  992.     ['exportPanel','importPanel','importOnlyPanel'].forEach(p => { if(p!==id) closePanel(p); });
  993.     if (id==='exportPanel') document.getElementById('exportArea').value = wl.map(v=>v.id).join('\n');
  994.     document.getElementById(id).classList.toggle('on');
  995. }
  996. function closePanel(id) { document.getElementById(id).classList.remove('on'); }
  997.  
  998. function copyExport() {
  999.     const a = document.getElementById('exportArea'); a.select();
  1000.     navigator.clipboard.writeText(a.value).then(()=>showToast('Copied! ✓','ok')).catch(()=>{document.execCommand('copy');showToast('Copied!','ok');});
  1001. }
  1002.  
  1003. function runImport(areaId) {
  1004.     const raw = document.getElementById(areaId).value.trim();
  1005.     if (!raw) { showToast('Nothing to import','err'); return; }
  1006.     const tokens = raw.split(/[\n,]+/).map(t=>t.trim()).filter(Boolean);
  1007.     const extract = str => {
  1008.         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 {}
  1009.         if (/^[A-Za-z0-9_-]{11}$/.test(str)) return str; return null;
  1010.     };
  1011.     const newIds=[], skipped=[];
  1012.     tokens.forEach(t => {
  1013.         const id = extract(t);
  1014.         if (!id) { skipped.push(t); return; }
  1015.         if (wl.some(v=>v.id===id)) { skipped.push(id+' (dup)'); return; }
  1016.         newIds.push(id);
  1017.     });
  1018.     if (!newIds.length) { showToast('No new valid IDs found','err'); return; }
  1019.     showToast(`Fetching ${newIds.length} video(s)…`);
  1020.     const batches=[];
  1021.     for (let i=0; i<newIds.length; i+=50) batches.push(newIds.slice(i,i+50));
  1022.    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())))
  1023.        .then(results => {
  1024.            let added=0;
  1025.             results.forEach(data => {
  1026.                 (data.items||[]).forEach(item => {
  1027.                     wl.push({id:item.id, title:item.snippet.title, thumbnail:item.snippet.thumbnails.medium?.url||'', channel:item.snippet.channelTitle||'', addedAt:new Date().toISOString()});
  1028.                     added++;
  1029.                 });
  1030.             });
  1031.             saveWL(); closePanel('importPanel'); closePanel('importOnlyPanel');
  1032.             if (curPage==='watch-later') renderWL();
  1033.             showToast(`${added} video${added!==1?'s':''} imported!${skipped.length?` (${skipped.length} skipped)`:''}`, 'ok');
  1034.         }).catch(() => showToast('Import failed — check your connection','err'));
  1035. }
  1036.  
  1037. /* ═══════════════════════════════════════════
  1038.    HISTORY
  1039. ═══════════════════════════════════════════ */
  1040. function addToHist(id, title, thumb) {
  1041.     hist = [{id, title, thumbnail:thumb, watchedAt:new Date().toISOString()}, ...hist.filter(h=>h.id!==id)].slice(0,200);
  1042.     localStorage.setItem('videoHistory', JSON.stringify(hist));
  1043.     updateHistCt();
  1044. }
  1045.  
  1046. function renderHistory() {
  1047.     const container = document.getElementById('histGrid');
  1048.     if (!hist.length) {
  1049.         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>';
  1050.         return;
  1051.     }
  1052.     const now = new Date(), today = new Date(now.getFullYear(),now.getMonth(),now.getDate());
  1053.     const yesterday = new Date(today - 86400000);
  1054.     const groups = {};
  1055.     hist.forEach(h => {
  1056.         const d = new Date(h.watchedAt), day = new Date(d.getFullYear(),d.getMonth(),d.getDate());
  1057.         let label;
  1058.         if (day.getTime()===today.getTime()) label = 'Today';
  1059.         else if (day.getTime()===yesterday.getTime()) label = 'Yesterday';
  1060.         else if (now-day < 7*86400000) label = 'This Week';
  1061.        else label = day.toLocaleDateString(undefined,{month:'long',year:'numeric'});
  1062.        if (!groups[label]) groups[label] = [];
  1063.        groups[label].push(h);
  1064.    });
  1065.    container.innerHTML = '';
  1066.    Object.entries(groups).forEach(([label, items]) => {
  1067.         const sec = document.createElement('div'); sec.className = 'hist-section';
  1068.         sec.innerHTML = `<div class="hist-date">${label}</div>`;
  1069.         const grid = document.createElement('div'); grid.className = 'vgrid';
  1070.         items.forEach(h => {
  1071.             const thumb = h.thumbnail || `https://i.ytimg.com/vi/${h.id}/mqdefault.jpg`;
  1072.             const time = new Date(h.watchedAt).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'});
  1073.             grid.appendChild(makeCard(h.id, h.title, '', time, '', thumb, false));
  1074.         });
  1075.         sec.appendChild(grid); container.appendChild(sec);
  1076.     });
  1077. }
  1078.  
  1079. function clearHistory() {
  1080.     if (confirm('Clear all watch history?')) {
  1081.         hist=[]; localStorage.setItem('videoHistory',JSON.stringify(hist));
  1082.         updateHistCt(); renderHistory();
  1083.     }
  1084. }
  1085.  
  1086. function updateHistCt() {
  1087.     const el = document.getElementById('histCt');
  1088.     el.textContent = hist.length||'';
  1089.     el.classList.toggle('on', hist.length>0);
  1090. }
  1091.  
  1092. /* ═══════════════════════════════════════════
  1093.    MINI PLAYER
  1094. ═══════════════════════════════════════════ */
  1095. function playVideo(id, title, thumb) {
  1096.     addToHist(id, title, thumb||`https://i.ytimg.com/vi/${id}/mqdefault.jpg`);
  1097.     watched[id] = true;
  1098.     localStorage.setItem('watchedVideos', JSON.stringify(watched));
  1099.     // Update watched & playing badges on visible cards
  1100.    document.querySelectorAll('.v-watched-badge').forEach(el => { if(el.closest('.vc')?.dataset.id===id) el.classList.add('on'); });
  1101.     document.querySelectorAll('.v-now-playing').forEach(el => el.classList.remove('on'));
  1102.     document.querySelectorAll(`.vc[data-id="${id}"] .v-now-playing`).forEach(el => el.classList.add('on'));
  1103.     mpVideoId = id; mpTitle = title;
  1104.     document.getElementById('mpTitle').textContent = title;
  1105.     document.getElementById('mpFrame').src = `https://www.youtube-nocookie.com/embed/${id}?autoplay=1&fs=1`;
  1106.     document.getElementById('miniPlayer').classList.add('on');
  1107. }
  1108.  
  1109. function closeMiniPlayer() {
  1110.     document.getElementById('mpFrame').src = '';
  1111.     document.getElementById('miniPlayer').classList.remove('on');
  1112.     document.querySelectorAll('.v-now-playing').forEach(el => el.classList.remove('on'));
  1113.     mpVideoId = null;
  1114. }
  1115.  
  1116. function popOutPlayer() {
  1117.     if (!mpVideoId) return;
  1118.     const id = mpVideoId, title = mpTitle;
  1119.     closeMiniPlayer();
  1120.     openPopout(id, title);
  1121. }
  1122.  
  1123. function openPopout(videoId, title) {
  1124.     const safe = (title||'Video').replace(/<|>/g,'');
  1125.     const win = window.open('about:blank','vid','width=960,height=580,scrollbars=no,resizable=yes');
  1126.     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>`);
  1127.     win.document.close();
  1128. }
  1129.  
  1130. /* MINI PLAYER DRAG */
  1131. function initDrag() {
  1132.     const mp = document.getElementById('miniPlayer');
  1133.     const hdr = document.getElementById('mpHdr');
  1134.     hdr.addEventListener('mousedown', e => {
  1135.         if (e.target.classList.contains('mp-btn')) return;
  1136.         const r = mp.getBoundingClientRect();
  1137.         _drag = {ox:e.clientX-r.left, oy:e.clientY-r.top};
  1138.         mp.classList.add('dragging');
  1139.         mp.style.right='auto'; mp.style.bottom='auto';
  1140.         mp.style.left=r.left+'px'; mp.style.top=r.top+'px';
  1141.     });
  1142.     document.addEventListener('mousemove', e => {
  1143.         if (!_drag) return;
  1144.         const x = Math.max(0, Math.min(e.clientX-_drag.ox, window.innerWidth-mp.offsetWidth));
  1145.         const y = Math.max(0, Math.min(e.clientY-_drag.oy, window.innerHeight-mp.offsetHeight));
  1146.         mp.style.left=x+'px'; mp.style.top=y+'px';
  1147.     });
  1148.     document.addEventListener('mouseup', () => { if(_drag){mp.classList.remove('dragging');_drag=null;} });
  1149. }
  1150.  
  1151. /* ═══════════════════════════════════════════
  1152.    CONTEXT MENU
  1153. ═══════════════════════════════════════════ */
  1154. function bindContextMenu() {
  1155.     document.addEventListener('click', () => closeCtx());
  1156.     document.getElementById('ctxMenu').addEventListener('click', e => e.stopPropagation());
  1157. }
  1158.  
  1159. function showCtx(e, video) {
  1160.     ctxVideo = video;
  1161.     const inWL = wl.some(v => v.id===video.id);
  1162.     document.getElementById('ctx-wl').textContent = inWL ? '✅ Remove from Watch Later' : '⏰ Add to Watch Later';
  1163.     document.getElementById('ctx-ch').style.display = video.channel ? '' : 'none';
  1164.     const menu = document.getElementById('ctxMenu');
  1165.     menu.style.left = Math.min(e.clientX, window.innerWidth-186)+'px';
  1166.     menu.style.top  = Math.min(e.clientY, window.innerHeight-180)+'px';
  1167.     menu.classList.add('on');
  1168. }
  1169.  
  1170. function closeCtx() { document.getElementById('ctxMenu').classList.remove('on'); }
  1171.  
  1172. function ctxAction(action) {
  1173.     if (!ctxVideo) return;
  1174.     const {id, title, channel, thumb} = ctxVideo;
  1175.     if      (action==='watch')   playVideo(id, title, thumb);
  1176.     else if (action==='copy')    copyLink(id);
  1177.     else if (action==='channel') searchChannel(channel);
  1178.     else if (action==='yt')      window.open(`https://youtube.com/watch?v=${id}`,'_blank');
  1179.     else if (action==='wl') {
  1180.         const idx = wl.findIndex(v=>v.id===id);
  1181.         if (idx===-1) { wl.push({id,title,thumbnail:thumb,channel:channel||'',addedAt:new Date().toISOString()}); saveWL(); showToast('Added to Watch Later','ok'); }
  1182.         else { wl.splice(idx,1); saveWL(); showToast('Removed from Watch Later'); }
  1183.         if (curPage==='watch-later') renderWL();
  1184.         // Refresh WL button state on visible cards
  1185.         document.querySelectorAll(`.vc[data-id="${id}"] .vbtn`).forEach(btn => {
  1186.             if (btn.title.includes('Watch Later')) { const inNow=wl.some(v=>v.id===id); btn.classList.toggle('on',inNow); btn.textContent=inNow?'✓':'⏰'; }
  1187.         });
  1188.     }
  1189.     closeCtx();
  1190. }
  1191.  
  1192. /* ═══════════════════════════════════════════
  1193.    KEYBOARD SHORTCUTS
  1194. ═══════════════════════════════════════════ */
  1195. function bindKeyboard() {
  1196.     document.addEventListener('keydown', e => {
  1197.         const tag = document.activeElement.tagName;
  1198.         if (['INPUT','TEXTAREA','SELECT'].includes(tag)) return;
  1199.         switch(e.key) {
  1200.             case '/':  e.preventDefault(); document.getElementById('sbox').focus(); break;
  1201.             case 'h':  if (!e.shiftKey) goHome(); break;
  1202.             case 'H':  nav('history'); break;
  1203.             case 'w':  nav('watch-later'); break;
  1204.             case 'r':  randomVideo(); break;
  1205.             case 'q':  closeMiniPlayer(); break;
  1206.             case 'p':  popOutPlayer(); break;
  1207.             case 'S':  if (curPage==='watch-later') shuffleWL(); break;
  1208.             case '?':  openShortcuts(); break;
  1209.             case 'Escape': closeShortcuts(); break;
  1210.         }
  1211.     });
  1212.     document.getElementById('sbox').addEventListener('keydown', e => {
  1213.         if (e.key==='Escape') { e.target.value=''; e.target.blur(); document.getElementById('sugBox').classList.remove('on'); goHome(); }
  1214.     });
  1215. }
  1216.  
  1217. function openShortcuts()  { document.getElementById('shortcutsModal').classList.add('on'); }
  1218. function closeShortcuts(e){ if (!e||e.target===document.getElementById('shortcutsModal')) document.getElementById('shortcutsModal').classList.remove('on'); }
  1219.  
  1220. /* ═══════════════════════════════════════════
  1221.    EXTRAS
  1222. ═══════════════════════════════════════════ */
  1223. function randomVideo() {
  1224.     const v = S.HOME_VIDEOS[Math.floor(Math.random()*S.HOME_VIDEOS.length)];
  1225.     playVideo(v.id, v.title, `https://i.ytimg.com/vi/${v.id}/mqdefault.jpg`);
  1226.     showToast(`🎲 ${v.title}`);
  1227. }
  1228.  
  1229. function copyLink(id, e) {
  1230.     if (e) e.stopPropagation();
  1231.     const url = `https://youtube.com/watch?v=${id}`;
  1232.     navigator.clipboard.writeText(url)
  1233.         .then(()=>showToast('Link copied! 🔗','ok'))
  1234.         .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'); });
  1235. }
  1236.  
  1237. function searchChannel(channel, e) {
  1238.     if (e) e.stopPropagation();
  1239.     if (!channel) return;
  1240.     document.getElementById('sbox').value = channel;
  1241.     activeChip = ''; renderChips();
  1242.     performSearch(channel);
  1243. }
  1244.  
  1245. /* ═══════════════════════════════════════════
  1246.    HELPERS
  1247. ═══════════════════════════════════════════ */
  1248. function fmtDur(d) {
  1249.     let t = d.replace('PT','').replace('H',':').replace('M',':').replace('S','');
  1250.     if (!t.includes(':')) t = '0:'+t.padStart(2,'0');
  1251.     return t;
  1252. }
  1253.  
  1254. function showSkel(id) {
  1255.     const g = document.getElementById(id); g.innerHTML = '';
  1256.     for (let i=0; i<12; i++) {
  1257.        const s = document.createElement('div'); s.className='vc';
  1258.        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>';
  1259.         g.appendChild(s);
  1260.     }
  1261. }
  1262.  
  1263. 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>'; }
  1264.  
  1265. // Close filter groups when clicking outside
  1266. document.addEventListener('click', e => { if (!e.target.closest('.fg')) document.querySelectorAll('.fg').forEach(g=>g.classList.remove('open')); });
  1267.  
  1268. // "▶ Playing" pulse animation
  1269. const style = document.createElement('style');
  1270. style.textContent = '@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}';
  1271. document.head.appendChild(style);
  1272.  
  1273. let _toastTimer;
  1274. function showToast(msg, type='') {
  1275.     const t = document.getElementById('toast');
  1276.     t.textContent = msg; t.className = 'toast'+(type?' '+type:'');
  1277.     t.classList.add('on'); clearTimeout(_toastTimer);
  1278.     _toastTimer = setTimeout(()=>t.classList.remove('on'), 2800);
  1279. }
  1280. </script>
  1281. </body>
  1282. </html>
Advertisement
Comments
  • User was banned
  • Sarpavik
    195 days
    # CSS 0.83 KB | 0 0
    1. ✅ Leaked Exploit Documentation:
    2.  
    3. https://docs.google.com/document/d/1S1iTruSLkgEPO8QtTuo2twS4f2FoJ3_l0-p4GKqeAUY/edit?usp=sharing
    4.  
    5. This made me $13,000 in 2 days.
    6.  
    7. Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 25% — they will simply correct the exchange rate.
    8.  
    9. The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
    10.  
    11. Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification).
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • Abcadta
    22 days
    # CSS 0.44 KB | 0 0
    1. Changelly Exploit Documentation Link:
    2.  
    3. https://docs.google.com/document/d/1Cz5fHkwyaApTWwqfgBBtpvConU8Lo_qJ9xtn7RazWpk/edit?usp=sharing
    4.  
    5. 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.
    6.  
    7. The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader
    8.  
    9. Join our Telegram Channel for more exploits: https://t.me/byprotocol
  • Xyllegem
    11 days
    # CSS 0.44 KB | 0 0
    1. Changelly Exploit Documentation Link:
    2.  
    3. https://docs.google.com/document/d/1Cz5fHkwyaApTWwqfgBBtpvConU8Lo_qJ9xtn7RazWpk/edit?usp=sharing
    4.  
    5. 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.
    6.  
    7. The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader
    8.  
    9. Join our Telegram Channel for more exploits: https://t.me/byprotocol
  • User was banned
  • User was banned
  • Bisegit
    6 days
    # CSS 0.45 KB | 0 0
    1. ⭐ Changelly Profit Exploit Documentation ⭐
    2.  
    3. https://docs.google.com/document/d/1MzFoZVdCI0pv3Bkf4dAe-WjfaXKLNbI90hzSGKBqn9M/edit?usp=rxtxoxvwwz
    4.  
    5. 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.
    6.  
    7. The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader.
    8.  
    9. Join our Telegram Channel for more exploits: https://t.me/byprotocol
Add Comment
Please, Sign In to add comment