/* Build-local classic-script loader. The inert manifest precedes this head script. */ (function (root) { 'use strict'; const doc = root.document; let manifest; try { manifest = JSON.parse(doc.getElementById('ytp-assets').textContent); } catch { return; } // The index must supply this map; never fetch one during boot. const assets = new Map(), groups = new Map(), hooks = new Map(); const css = new Set([...doc.querySelectorAll('link[data-lazy-css]')].map(link => link.dataset.lazyCss)); const selected = new Map(); const url = path => selected.get(path) || (manifest.files[path]?.h ? path + '?v=' + manifest.files[path].h : './' + path.replace(/^\//,'')); function reloadRequired(name) { api.reloadRequired = true; if (root.CustomEvent) root.dispatchEvent?.(new root.CustomEvent('ytp-reload-required', { detail: { group: name } })); } function compatible(name, current, seen = new Set()) { if(seen.has(name))return true; seen.add(name); const built=manifest.groups[name], newer=current.groups[name]; if(!built || !newer || built.contract!==newer.contract)return false; return (newer.dependencies || []).every(dependency=>(built.dependencies || []).includes(dependency)&&compatible(dependency,current,seen)); } async function select(name) { if (!root.caches) return; try { const cache = await root.caches.open('ytplayer-assets'); const response = await cache.match('/__ytp_asset_state'); const current = response && (await response.json()).current; const built = manifest.groups[name], newer = current?.groups[name]; // The active layout's CSS already executed in the parser before app boot. if(name === 'layout:'+api.layout) return; if (!newer || current.buildTag === manifest.buildTag) return; if (!compatible(name,current)) { reloadRequired(name); return; } const changedList = newer.files.join('\n') !== built.files.join('\n'); const available = new Map(); for (const path of changedList ? newer.files : built.files) { // Membership and contract must both match the running core's expectation. if (!newer.files.includes(path) || !current.files[path]) continue; const key = path + '?v=' + current.files[path].h; const held = await cache.match(key); if (held?.headers.get('X-Asset-Hash') === current.files[path].h) available.set(path, key); } if(changedList && available.size !== newer.files.length)return; for(const [path,key] of available)selected.set(path,key); if(changedList)return newer; } catch { /* Private browsing/cache eviction: keep the embedded build-local map. */ } } function inject(path) { if (assets.has(path)) return assets.get(path); const style = /\.css$/.test(path); if (style && css.has(path)) return Promise.resolve(); if (!style && !/\.js$/.test(path)) return Promise.resolve(); // workers/imports/fonts are not page scripts const task = new Promise((resolve, reject) => { const node = doc.createElement(style ? 'link' : 'script'); if (style) { node.rel = 'stylesheet'; node.href = url(path); } else { node.async = false; node.src = url(path); } node.onload = resolve; node.onerror = () => reject(new Error('Unable to load ' + path)); doc.head.append(node); }); assets.set(path, task); task.catch(() => assets.delete(path)); return task; } function load(name) { if (groups.has(name)) return groups.get(name); const group = manifest.groups[name]; if (!group) return Promise.reject(new Error('Unknown asset group: ' + name)); const task = (async () => { for (const dependency of group.dependencies || []) await load(dependency); const selectedGroup = await select(name) || group; for (const path of selectedGroup.files) if (!/-worker\.js$/.test(path)) await inject(path); for (const fn of hooks.get(name) || []) await fn(); group.executed = true; })(); groups.set(name, task); task.catch(error => { groups.delete(name); if (root.CustomEvent) root.dispatchEvent?.(new root.CustomEvent('ytp-lazy-error', { detail: { group: name, error } })); }); return task; } const api = { load, prefetch: load, loaded: name => !!manifest.groups[name]?.executed, url, manifest, layout: 'classic', reloadRequired: false, worker(path) { const worker=new root.Worker(url(path)), post=worker.postMessage.bind(worker); let first=true; worker.postMessage=(message,transfer)=>{ if(first) { first=false; message={...message,assetUrls:{sha256:url('/sha256.js'),wasm:url('/sha256-wasm.js'),resume:url('/resume-core.js')}}; } post(message,transfer); }; return worker; }, reportLayout(registration) { const send = reg => { const workers = new Set([root.navigator.serviceWorker.controller, reg?.installing, reg?.waiting, reg?.active]); for(const worker of workers) worker?.postMessage({type:'LAYOUT',value:api.layout}); }; if(registration)send(registration); else root.navigator.serviceWorker?.getRegistration().then(send).catch(()=>{}); }, warm() { if(root.navigator.connection?.saveData || !root.navigator.serviceWorker) return; const idle = root.requestIdleCallback || (fn => root.setTimeout(fn,1000)); idle(()=>root.navigator.serviceWorker.ready.then(reg=>reg.active?.postMessage({type:'WARM_ASSETS',saveData:!!root.navigator.connection?.saveData})).catch(()=>{})); }, onLoad(name, fn) { if(api.loaded(name)) { Promise.resolve().then(fn).catch(error=>{if(root.CustomEvent)root.dispatchEvent?.(new root.CustomEvent('ytp-lazy-error',{detail:{group:name,error}}));}); return; } const list = hooks.get(name) || []; list.push(fn); hooks.set(name, list); }, proxy(name, methods, globalName) { return Object.fromEntries(methods.map(method => [method, async (...args) => { await load(name); const key = globalName || name.split(':')[1]; const target = root.YT?.[key] || root[key]; if (typeof target?.[method] !== 'function') throw new Error('Missing lazy method: ' + method); return target[method](...args); }])); }, intent(element, name) { if (!element) return; for (const type of ['pointerenter', 'focus', 'touchstart']) element.addEventListener(type, () => load(name).catch(() => {}), { once: true, passive: true }); }, }; root.Lazy = api; root.navigator.serviceWorker?.addEventListener('message',event=>{ if(event.data?.type==='ASSET_LAYOUT_REQUEST') event.source?.postMessage({type:'LAYOUT',value:api.layout}); if(event.data?.type==='SW_UPDATE_AVAILABLE') api.warm(); }); root.navigator.serviceWorker?.addEventListener('controllerchange',()=>{api.reportLayout();api.warm();}); let settings={}; try { settings=JSON.parse(root.localStorage.getItem('_ytpdata') || '{}').settings || {}; api.layout=settings.layout || 'classic'; } catch {} Object.assign(doc.documentElement.dataset,{layout:api.layout,theme:settings.theme || 'dark',font:settings.fontScale || 'normal',density:settings.density || 'comfortable',perf:settings.perfMode?'on':'off',motion:settings.reduceMotion?'reduced':'full'}); const layoutName = manifest.groups['layout:' + api.layout] ? 'layout:'+api.layout : 'layout:classic'; const layoutGroups = new Set([layoutName]); for(const name of layoutGroups) for(const dependency of manifest.groups[name]?.dependencies || []) layoutGroups.add(dependency); if (doc.readyState === 'loading') for (const path of [...layoutGroups].flatMap(name=>manifest.groups[name]?.files || [])) { if (!/\.css$/.test(path) || css.has(path)) continue; // Parser-inserted CSS blocks rendering; dynamic head links do not on all WebKit versions. css.add(path); doc.write(''); } })(typeof window !== 'undefined' ? window : globalThis);