var THEME_KEY = "vyla-theme"; var ICONS = { system: '', light: '', dark: '', midnight: '', nord: '', dracula: '', forest: '', sunset: '', cyberpunk: '', halloween: '', christmas: '', autumn: '', winter: '', auto: '', sliders: '', star: '', search: '', menu: '', doc: '', hash: '', copy: '', check: '', chevron: '', chevronLeft: '', chevronRight: '' }; var THEMES_GENERAL = [ { id: "dark", label: "Dark" }, { id: "light", label: "Light" }, { id: "midnight", label: "Midnight" }, { id: "nord", label: "Nord" }, { id: "dracula", label: "Dracula" }, { id: "forest", label: "Forest" }, { id: "sunset", label: "Sunset" }, { id: "cyberpunk", label: "Cyberpunk" } ]; var THEME_MEDIA = { dark: "/src/assets/images/wallpaper-media/tree.webp", light: "/src/assets/images/wallpaper-media/tree.webp", midnight: "/src/assets/images/wallpaper-media/tree.webp", nord: "/src/assets/images/wallpaper-media/tree.webp", dracula: "/src/assets/images/wallpaper-media/tree.webp", forest: "/src/assets/images/wallpaper-media/tree.webp", sunset: "/src/assets/images/wallpaper-media/tree.webp", cyberpunk: "/src/assets/images/wallpaper-media/tree.webp" }; var SITE_TREE = [ { "id": "vyla", "title": "Ecosystem", "path": "/start-here/introduction", "groups": [ { "title": "Start here", "items": [ { "title": "Introduction", "file": "/start-here/introduction.md", "path": "/start-here/introduction", "hasMedia": true }, { "title": "Architecture", "file": "/start-here/architecture.md", "path": "/start-here/architecture" } ] }, { "title": "Products", "items": [ { "title": "Vyla Home", "file": "/products/home.md", "path": "/products/home", "hasMedia": true }, { "title": "Vyla Go", "file": "/products/go.md", "path": "/products/go", "hasMedia": true }, { "title": "Vyla TV", "file": "/products/tv.md", "path": "/products/tv", "hasMedia": true }, { "title": "Build a Player", "file": "/products/player.md", "path": "/products/player" } ] }, { "title": "Concepts", "items": [ { "title": "Providers", "file": "/concepts/providers.md", "path": "/concepts/providers" } ] } ] }, { "id": "sdk", "title": "SDK", "path": "/sdk", "groups": [ { "title": "Getting Started", "items": [ { "title": "SDK Overview", "file": "/sdk/vyla-sdk/index.md", "path": "/sdk", "hasMedia": true }, { "title": "SDK Quickstart", "file": "/sdk/vyla-sdk/quickstart.md", "path": "/sdk/vyla-sdk/quickstart" } ] }, { "title": "Reference", "items": [ { "title": "API Reference", "file": "/sdk/vyla-sdk/reference.md", "path": "/sdk/vyla-sdk/reference" }, { "title": "Manga Module", "file": "/sdk/vyla-sdk/manga.md", "path": "/sdk/vyla-sdk/manga", "hasMedia": true } ] }, { "title": "Development", "items": [ { "title": "Contributing Sources", "file": "/sdk/vyla-sdk/contributing.md", "path": "/sdk/vyla-sdk/contributing", "hasMedia": true } ] } ] }, { "id": "stream-api", "title": "Stream API", "path": "/stream-api/overview", "groups": [ { "title": "Overview", "items": [ { "title": "Stream API Overview", "file": "/stream-api/overview/index.md", "path": "/stream-api/overview" }, { "title": "Authentication & Keys", "file": "/stream-api/authentication/index.md", "path": "/stream-api/authentication" } ] }, { "title": "Reference", "items": [ { "title": "Paths & Playback", "file": "/stream-api/reference/paths.md", "path": "/stream-api/reference/paths" }, { "title": "Media Data", "file": "/stream-api/reference/media-data.md", "path": "/stream-api/reference/media-data", "hasMedia": true }, { "title": "Operations & Health", "file": "/stream-api/reference/operations.md", "path": "/stream-api/reference/operations" } ] }, { "title": "Deployment", "items": [ { "title": "Self Hosting", "file": "/stream-api/deployment/self-hosting.md", "path": "/stream-api/deployment/self-hosting" } ] } ] }, { "id": "guides", "title": "Guides", "path": "/guides/integrations", "groups": [ { "title": "Player Guides", "items": [ { "title": "Client Integration", "file": "/guides/integrations.md", "path": "/guides/integrations" }, { "title": "Error Handling", "file": "/guides/error-handling.md", "path": "/guides/error-handling" } ] } ] }, { "id": "resources", "title": "Resources", "path": "/resources/faq", "groups": [ { "title": "Reference & Help", "items": [ { "title": "FAQ", "file": "/resources/faq.md", "path": "/resources/faq" }, { "title": "Troubleshooting", "file": "/resources/troubleshooting.md", "path": "/resources/troubleshooting" }, { "title": "Glossary", "file": "/resources/glossary.md", "path": "/resources/glossary" } ] }, { "title": "Project", "items": [ { "title": "Project Story", "file": "/misc/project/story.md", "path": "/misc/project/story" }, { "title": "Changelog", "file": "/misc/project/changelog.md", "path": "/misc/project/changelog" }, { "title": "Credits", "file": "/misc/project/credit.md", "path": "/misc/project/credit" }, { "title": "Support Us", "file": "/misc/project/support-us.md", "path": "/misc/project/support-us" }, { "title": "DMCA Policy", "file": "/misc/project/DMCA.md", "path": "/misc/project/dmca" } ] } ] } ]; var searchData = null; var searchResults = []; var searchActive = 0; function icon(name) { return '' + ICONS[name] + ''; } function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { "&": "&", "<": "<", ">": ">", '"': """ }[c]; }); } function getThemeMode() { return localStorage.getItem(THEME_KEY) || "auto"; } function applyTheme() { var mode = getThemeMode(); var theme = resolveTheme(mode); document.documentElement.setAttribute("data-theme", theme); var logo = document.getElementById("brand-logo"); if (logo) logo.src = theme === "light" ? "/src/assets/images/dark_logo.png" : "/src/assets/images/logo.png"; applyPreferences(); var targetSrc = THEME_MEDIA[theme] || THEME_MEDIA.dark; if (typeof window !== "undefined" && window.innerWidth <= 768 && targetSrc && targetSrc.indexOf("tree.webp") !== -1) { targetSrc = "/src/assets/images/wallpaper-media/tree-mobile.webp"; } var videosOff = getPref("video", "on") === "off"; var isImageSource = /\.(jpe?g|png|gif|webp|avif|svg)(\?.*)?$/i.test(targetSrc || ""); document.querySelectorAll(".body-blurred-video, .header-video, .body-blurred-image, .header-image").forEach(function (element) { var isVideo = element.tagName === "VIDEO"; var isImage = element.tagName === "IMG"; if ((isVideo && (videosOff || isImageSource)) || (isImage && !isImageSource)) { if (isVideo) { element.pause(); element.load(); } element.removeAttribute("src"); return; } if (element.getAttribute("src") !== targetSrc) { element.src = targetSrc; if (isVideo) { element.load(); element.play().catch(function () { }); } } }); updateThemeAmbient(targetSrc, isImageSource, videosOff); applyThemeCopy(theme); } function updateThemeAmbient(src, isImage, videosOff) { var el = document.getElementById("theme-ambient"); if (!el) { el = document.createElement("div"); el.id = "theme-ambient"; el.className = "theme-ambient"; el.setAttribute("aria-hidden", "true"); document.body.appendChild(el); } var blocked = !src || (!isImage && videosOff); el.hidden = blocked; if (blocked) return; var want = isImage ? "img" : "video"; var media = el.querySelector(".theme-ambient-media"); if (!media || media.tagName.toLowerCase() !== want) { el.innerHTML = ""; media = document.createElement(want); media.className = "theme-ambient-media"; if (isImage) { media.alt = ""; media.setAttribute("width", "1920"); media.setAttribute("height", "1080"); media.setAttribute("aria-hidden", "true"); } else { media.muted = true; media.loop = true; media.autoplay = true; media.playsInline = true; media.setAttribute("muted", ""); media.setAttribute("playsinline", ""); } el.appendChild(media); } if (media.getAttribute("src") !== src) { media.src = src; if (!isImage) { media.load(); media.play().catch(function () { }); } } } function revealThemeAmbient() { var el = document.getElementById("theme-ambient"); if (!el || el.hidden) return; el.classList.remove("is-in"); void el.offsetWidth; el.classList.add("is-in"); } function setTheme(mode) { localStorage.setItem(THEME_KEY, mode); applyTheme(); refreshThemeUI(); var menu = document.getElementById("theme-menu"); if (menu) menu.classList.remove("open"); var segMenu = document.getElementById("theme-seg-menu"); if (segMenu) segMenu.classList.remove("open"); } function toggleThemeMenu(e) { e.stopPropagation(); var menu = document.getElementById("theme-menu"); if (menu) menu.classList.toggle("open"); } function refreshThemeUI() { var mode = getThemeMode(); var btn = document.getElementById("theme-toggle"); if (btn) btn.innerHTML = icon(ICONS[mode] ? mode : "dark"); document.querySelectorAll("#theme-menu button, .st-theme").forEach(function (b) { b.classList.toggle("on", b.getAttribute("data-mode") === mode); }); } function buildThemeControls() { var btn = document.getElementById("theme-toggle"); var links = document.querySelector("#navbar .links"); if (!btn || !links) return; var wrap = document.createElement("div"); wrap.className = "theme-wrap"; btn.parentNode.insertBefore(wrap, btn); wrap.appendChild(btn); var menu = document.createElement("div"); menu.id = "theme-menu"; var html = '
General
'; THEMES_GENERAL.forEach(function (t) { html += ''; }); menu.innerHTML = html; wrap.appendChild(menu); menu.addEventListener("click", function (e) { var b = e.target.closest("button"); if (b) setTheme(b.getAttribute("data-mode")); }); refreshThemeUI(); } function dismissBanner() { var banner = document.getElementById("banner"); if (banner) banner.setAttribute("data-dismissed", "true"); document.body.classList.add("banner-dismissed"); localStorage.setItem("vyla-banner-dismissed", "true"); var header = document.getElementById("header-container"); if (header) document.documentElement.style.setProperty("--header-height", header.offsetHeight + "px"); } function toggleSidebar() { var sidebar = document.getElementById("sidebar"); var nav = document.getElementById("navbar"); var top = Math.max(0, Math.round(nav.getBoundingClientRect().bottom)); sidebar.style.setProperty("--sidebar-top", top + "px"); var open = sidebar.classList.toggle("open"); document.body.classList.toggle("nav-open", open); } function cgSwitch(btn) { var group = btn.closest(".code-group"); var index = btn.getAttribute("data-tab-index"); group.querySelectorAll(".code-group-tab").forEach(function (t) { t.classList.toggle("active", t === btn); }); group.querySelectorAll(".code-group-panel").forEach(function (p, i) { p.classList.toggle("active", String(i) === index); }); window.dispatchEvent(new Event("resize")); } var savedCount = 0; var PREF_KEYS = { video: "vyla-video", motion: "vyla-motion", deco: "vyla-deco", text: "vyla-text", accent: "vyla-accent", name: "vyla-name", api: "vyla-api-url" }; var THEME_COPY = { base: { banner: "", footer: "Built by the community, for the community.", notFound: "But if you don't change your direction, and if you keep looking, you may end up where you are heading.", greeting: "", favicon: "", confetti: null } }; var KONAMI = ["ArrowUp", "ArrowUp", "ArrowDown", "ArrowDown", "ArrowLeft", "ArrowRight", "ArrowLeft", "ArrowRight", "b", "a"]; var PROJECTS = ["home", "go", "TV", "sdk", "stream-api"]; var PROJECT_LABELS = { home: "Home", go: "Go", TV: "TV", sdk: "SDK", "stream-api": "Stream API" }; function resolveTheme(mode) { return mode === "auto" ? "dark" : mode; } function getPref(key, fallback) { var v = null; try { v = localStorage.getItem(PREF_KEYS[key]); } catch (e) { } return v === null ? fallback : v; } function setPref(key, value) { try { if (value === null || value === "") localStorage.removeItem(PREF_KEYS[key]); else localStorage.setItem(PREF_KEYS[key], value); } catch (e) { } } function applyAccent(hex) { var root = document.documentElement; var props = ["--text", "--btn-primary-bg", "--btn-home-bg", "--code-tab-active", "--btn-primary-text", "--btn-home-text"]; if (!/^#[0-9a-f]{6}$/i.test(hex || "")) { props.forEach(function (p) { root.style.removeProperty(p); }); return; } var r = parseInt(hex.slice(1, 3), 16); var g = parseInt(hex.slice(3, 5), 16); var b = parseInt(hex.slice(5, 7), 16); var on = (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.6 ? "#000000" : "#ffffff"; root.style.setProperty("--text", hex); root.style.setProperty("--btn-primary-bg", hex); root.style.setProperty("--btn-home-bg", hex); root.style.setProperty("--code-tab-active", hex); root.style.setProperty("--btn-primary-text", on); root.style.setProperty("--btn-home-text", on); } function applyPreferences() { document.body.classList.toggle("no-video", getPref("video", "on") === "off"); document.body.classList.toggle("no-motion", getPref("motion", "on") === "off"); document.body.classList.toggle("no-deco", getPref("deco", "on") === "off"); var scale = parseInt(getPref("text", "100"), 10); if (!(scale >= 85 && scale <= 130)) scale = 100; document.documentElement.style.setProperty("--text-scale", String(scale / 100)); applyAccent(getPref("accent", "")); } function applyThemeCopy(theme) { var copy = THEME_COPY[theme] || THEME_COPY.base; var fav = document.querySelector('link[rel="icon"]'); if (fav) { if (copy.favicon) { fav.type = "image/svg+xml"; fav.href = "data:image/svg+xml," + encodeURIComponent("" + copy.favicon + ""); } else { fav.removeAttribute("type"); fav.href = "/favicon.ico"; } } var note = document.getElementById("footer-note"); if (note) note.textContent = copy.footer; var span = document.querySelector("#banner span"); if (span) { var old = document.getElementById("banner-greet"); if (old) old.remove(); if (copy.banner) { var g = document.createElement("strong"); g.id = "banner-greet"; g.textContent = copy.banner + " "; span.insertBefore(g, span.firstChild); } } } function notFoundCopy() { var copy = THEME_COPY[resolveTheme(getThemeMode())] || THEME_COPY.base; return copy.notFound; } function cleanApiUrl(v) { v = String(v || "").trim().replace(/\/+$/, ""); return /^https?:\/\/[^\s<>"'`]+$/i.test(v) ? v : ""; } var API_DEFAULT = "http://localhost:7860"; function personalize(md) { var url = cleanApiUrl(getPref("api", "")) || API_DEFAULT; return md.split("YOUR_API_URL").join(url); } function insertApiCard() { var main = document.getElementById("main-content"); var h1 = main && main.querySelector("h1"); if (!h1) return; var card = document.createElement("div"); card.className = "personal-card"; card.innerHTML = '' + '
' + '
' + '

Examples on this page will use your URL. It stays in this browser.

'; h1.parentNode.insertBefore(card, h1.nextSibling); var input = card.querySelector("input"); input.value = cleanApiUrl(getPref("api", "")); function apply() { var raw = input.value.trim(); var clean = cleanApiUrl(raw); if (raw && !clean) { input.classList.add("bad"); return; } input.classList.remove("bad"); setPref("api", clean); loadPage(); } card.querySelector(".pc-btn").addEventListener("click", apply); input.addEventListener("keydown", function (e) { if (e.key === "Enter") apply(); }); } function rememberPage(route) { if (!route || route.is404) return; try { localStorage.setItem("vyla-last", JSON.stringify({ title: route.item.title, path: route.item.path })); } catch (e) { } } function timeGreeting() { var h = new Date().getHours(); if (h < 5) return "Up late"; if (h < 12) return "Good morning"; if (h < 18) return "Good afternoon"; return "Good evening"; } function getBookmarks() { try { return JSON.parse(localStorage.getItem("vyla-bookmarks")) || []; } catch (e) { return []; } } function isBookmarked(href) { return getBookmarks().some(function (b) { return b.href === href; }); } function buildBookmarkButton(route) { var b = document.createElement("button"); b.type = "button"; b.className = "bm-btn"; var href = route.item.path; function paint() { var on = isBookmarked(href); b.classList.toggle("on", on); b.setAttribute("aria-pressed", on ? "true" : "false"); b.setAttribute("aria-label", on ? "Remove bookmark" : "Save this page"); b.title = on ? "Remove bookmark" : "Save this page"; b.innerHTML = icon("star"); } b.addEventListener("click", function (e) { e.stopPropagation(); var list = getBookmarks(); var idx = list.findIndex(function (x) { return x.href === href; }); if (idx !== -1) list.splice(idx, 1); else list.unshift({ title: route.item.title, crumbs: [route.tab.title, route.group.title, route.item.title], href: href, type: "page" }); try { localStorage.setItem("vyla-bookmarks", JSON.stringify(list.slice(0, 20))); } catch (err) { } paint(); }); paint(); return b; } function detectPlatform() { var ua = navigator.userAgent || ""; if (/Android.*(TV|AFT|BRAVIA|SHIELD)|AndroidTV|GoogleTV|SmartTV/i.test(ua)) return "tv"; if (/Android/i.test(ua)) return "android"; if (/iPhone|iPad|iPod/i.test(ua)) return "ios"; if (/Windows/i.test(ua)) return "windows"; if (/Mac OS X|Macintosh/i.test(ua)) return "mac"; if (/Linux|X11/i.test(ua)) return "linux"; return "other"; } function markRecommended(project) { var platform = detectPlatform(); var wanted = null; if (project === "home" && platform === "windows") wanted = [".exe"]; else if (project === "home" && platform === "mac") wanted = [".dmg"]; else if (project === "home" && platform === "linux") wanted = ["build from source"]; else if (project === "go" && platform === "android") wanted = [".apk"]; else if (project === "TV" && platform === "tv") wanted = [".apk"]; if (!wanted) return; var done = false; document.querySelectorAll("#main-content table a").forEach(function (a) { if (done || a.getAttribute("data-rec")) return; var text = a.textContent.toLowerCase(); var hit = wanted.some(function (w) { return text.indexOf(w) !== -1; }); if (!hit) return; done = true; a.setAttribute("data-rec", "1"); var badge = document.createElement("span"); badge.className = "rec-badge"; badge.textContent = "Recommended for your device"; a.parentNode.insertBefore(badge, a.nextSibling); var row = a.closest("tr"); if (row) { row.classList.add("rec-row"); var body = row.parentNode; if (body && body.firstElementChild !== row) body.insertBefore(row, body.firstElementChild); } }); } function burstConfetti() { if (document.body.classList.contains("no-motion")) return; var copy = THEME_COPY[resolveTheme(getThemeMode())] || THEME_COPY.base; var canvas = document.createElement("canvas"); canvas.className = "confetti-canvas"; var dpr = window.devicePixelRatio || 1; var w = window.innerWidth; var h = window.innerHeight; canvas.width = w * dpr; canvas.height = h * dpr; document.body.appendChild(canvas); var ctx = canvas.getContext("2d"); ctx.scale(dpr, dpr); var accent = getComputedStyle(document.documentElement).getPropertyValue("--text").trim() || "#ffffff"; var colors = [accent, "#ffffff", "#ffd24d", "#4da6ff", "#ff5c8a", "#5fd68f"]; var glyphs = copy.confetti; var parts = []; for (var i = 0; i < 140; i++) { parts.push({ x: w / 2 + (Math.random() - 0.5) * 120, y: h * 0.85, vx: (Math.random() - 0.5) * 16, vy: -(8 + Math.random() * 12), size: 8 + Math.random() * 8, rot: Math.random() * 6.28, vr: (Math.random() - 0.5) * 0.4, color: colors[i % colors.length], glyph: glyphs && i % 3 === 0 ? glyphs[i % glyphs.length] : null }); } var start = performance.now(); function frame(now) { var t = now - start; ctx.clearRect(0, 0, w, h); parts.forEach(function (p) { p.vy += 0.35; p.vx *= 0.99; p.x += p.vx; p.y += p.vy; p.rot += p.vr; ctx.save(); ctx.translate(p.x, p.y); ctx.rotate(p.rot); ctx.globalAlpha = Math.max(0, 1 - t / 4200); if (p.glyph) { ctx.font = p.size * 2 + "px serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(p.glyph, 0, 0); } else { ctx.fillStyle = p.color; ctx.fillRect(-p.size / 2, -p.size / 4, p.size, p.size / 2); } ctx.restore(); }); if (t < 4200) requestAnimationFrame(frame); else canvas.remove(); } requestAnimationFrame(frame); } function initEasterEggs() { var progress = 0; document.addEventListener("keydown", function (e) { var t = e.target; if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA")) return; var key = e.key.length === 1 ? e.key.toLowerCase() : e.key; if (key === KONAMI[progress]) { progress++; if (progress === KONAMI.length) { progress = 0; burstConfetti(); } } else { progress = key === KONAMI[0] ? 1 : 0; } }); try { } catch (e) { } } var ACCENT_PRESETS = ["#ff7518", "#f4b942", "#ff5c5c", "#f472b6", "#c084fc", "#8ad1ff", "#5fd68f", "#ffffff"]; var TEXT_STEPS = [ { v: "90", l: "Small" }, { v: "100", l: "Default" }, { v: "115", l: "Large" }, { v: "130", l: "XL" } ]; function settingsEl(id) { return document.getElementById(id); } function setApiHint(msg, isErr) { var h = settingsEl("set-api-hint"); if (!h) return; h.textContent = msg || "Used in code examples across the Stream API docs."; h.classList.toggle("err", !!isErr); } function refreshPersonalPages() { if (document.querySelector(".personal-card")) loadPage(); } function themeChips(list) { return list.map(function (t) { return '"; }).join(""); } function switchRow(id, title, hint) { return ''; } function parseMarkdown(md) { var lines = md.replace(/\r\n/g, "\n").split("\n"); var out = []; var inPre = false; var preLang = ""; var preCode = []; var inTable = false; var tableRows = []; var inList = false; var listType = "ul"; var inCodeGroup = false; var codeGroupTabs = []; var codeGroupPanels = []; function flushList() { if (inList) { out.push(""); inList = false; } } function flushTable() { if (inTable) { var html = '
'; if (tableRows.length > 0) { html += ""; var ths = tableRows[0]; for (var i = 0; i < ths.length; i++) html += ""; html += ""; } if (tableRows.length > 1) { html += ""; for (var r = 1; r < tableRows.length; r++) { html += ""; var tds = tableRows[r]; for (var c = 0; c < tds.length; c++) html += ""; html += ""; } html += ""; } html += "
" + parseInline(ths[i]) + "
" + parseInline(tds[c]) + "
"; out.push(html); inTable = false; tableRows = []; } } function slugify(text) { return text.toLowerCase().replace(/<[^>]+>/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""); } function parseInline(text) { return text .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, function (_, alt, src) { var cls = src.indexOf("devices.png") !== -1 || src.indexOf("site_preview.png") !== -1 || src.indexOf("site-preview.png") !== -1 ? ' class="vyla-banner"' : ""; return '' + esc(alt) + '"; }) .replace(/\[([^\]]+)\]\(([^)]+)\)/g, function (_, label, href) { return '' + label + ""; }) .replace(/`([^`]+)`/g, function (_, code) { return "" + esc(code) + ""; }) .replace(/\*\*([^*]+)\*\*/g, "$1") .replace(/\*([^*]+)\*/g, "$1"); } for (var i = 0; i < lines.length; i++) { var line = lines[i]; if (line.startsWith(":::code-group")) { flushList(); flushTable(); inCodeGroup = true; codeGroupTabs = []; codeGroupPanels = []; continue; } if (inCodeGroup && line === ":::") { flushList(); flushTable(); var cgHtml = '
'; codeGroupTabs.forEach(function (tab, idx) { var activeClass = idx === 0 ? " active" : ""; cgHtml += ''; }); cgHtml += '
'; codeGroupPanels.forEach(function (panel, idx) { var activeClass = idx === 0 ? " active" : ""; var langClass = panel.lang ? ' class="language-' + panel.lang + '"' : ' class="language-none"'; cgHtml += '
' + esc(panel.code) + '
'; }); cgHtml += '
'; out.push(cgHtml); inCodeGroup = false; codeGroupTabs = []; codeGroupPanels = []; continue; } if (line.startsWith("```")) { flushList(); flushTable(); if (!inPre) { inPre = true; var info = line.slice(3).trim(); var tabMatch = info.match(/\[([^\]]+)\]/); var tabTitle = tabMatch ? tabMatch[1] : ""; preLang = info.replace(/\[([^\]]+)\]/, "").trim(); if (inCodeGroup && tabTitle) { codeGroupTabs.push(tabTitle); } preCode = []; } else { inPre = false; var codeContent = preCode.join("\n"); var resolvedLang = preLang; if (!resolvedLang) { if (codeContent.trim().startsWith("{") || codeContent.trim().startsWith("[")) resolvedLang = "json"; else if (codeContent.indexOf("=") !== -1 && !codeContent.includes("function") && !codeContent.includes(";")) resolvedLang = "ini"; else if (codeContent.startsWith("http://") || codeContent.startsWith("https://")) resolvedLang = "http"; else resolvedLang = "bash"; } if (inCodeGroup) { codeGroupPanels.push({ lang: resolvedLang, code: codeContent }); } else { var langClass = resolvedLang ? ' class="language-' + resolvedLang + '"' : ' class="language-none"'; out.push('
" + esc(codeContent) + "
"); } } continue; } if (inPre) { preCode.push(line); continue; } if (line.startsWith(":::callout-")) { flushList(); flushTable(); var type = line.slice(11).trim(); out.push('
'); continue; } if (line === ":::") { flushList(); flushTable(); out.push("
"); continue; } if (line.startsWith(" 0 && cells[0].match(/^:?-+:?$/)) { continue; } inTable = true; tableRows.push(cells); continue; } else { flushTable(); } if (line.match(/^---+$/)) { flushList(); out.push("
"); continue; } var hMatch = line.match(/^(#{1,6})\s+(.*)$/); if (hMatch) { flushList(); var level = hMatch[1].length; var titleText = parseInline(hMatch[2]); var rawTitle = hMatch[2]; var id = slugify(rawTitle); out.push("' + titleText + ""); continue; } var olMatch = line.match(/^\d+\.\s+(.*)$/); if (olMatch) { if (!inList || listType !== "ol") { flushList(); inList = true; listType = "ol"; out.push("
    "); } out.push("
  1. " + parseInline(olMatch[1]) + "
  2. "); continue; } var ulMatch = line.match(/^[-*]\s+(.*)$/); if (ulMatch) { if (!inList || listType !== "ul") { flushList(); inList = true; listType = "ul"; out.push("
      "); } out.push("
    • " + parseInline(ulMatch[1]) + "
    • "); continue; } flushList(); if (line.trim().length === 0) { continue; } if (line.startsWith("> ")) { out.push("

      " + parseInline(line.slice(2)) + "

      "); continue; } out.push("

      " + parseInline(line) + "

      "); } flushList(); flushTable(); return out.join("\n"); } function resolveRoute(pathname) { var path = pathname || window.location.pathname; if (!path.startsWith("/")) path = "/" + path; path = path.replace(/\/+$/, "") || "/"; if (path === "/") { return { tab: SITE_TREE[0], group: { title: "Vyla" }, item: { title: "Home", path: "/" } }; } for (var t = 0; t < SITE_TREE.length; t++) { var tab = SITE_TREE[t]; for (var g = 0; g < tab.groups.length; g++) { var grp = tab.groups[g]; for (var i = 0; i < grp.items.length; i++) { var item = grp.items[i]; var itemPath = item.path.replace(/\/+$/, "") || "/"; if (itemPath.toLowerCase() === path.toLowerCase()) { return { tab: tab, group: grp, item: item }; } } } } return { is404: true, tab: SITE_TREE[0], group: { title: "Error" }, item: { title: "404 Not Found", path: path } }; } function renderNavTabs(currentTab) { var nav = document.getElementById("nav-tabs"); if (!nav) return; nav.innerHTML = SITE_TREE.map(function (tab) { var activeClass = tab.id === currentTab.id ? " active" : ""; return '' + esc(tab.title) + ""; }).join(""); } function renderSidebar(currentTab, currentItem) { var sidebar = document.getElementById("sidebar"); if (!sidebar) return; var html = ""; currentTab.groups.forEach(function (group) { html += '
      '; html += '

      ' + esc(group.title) + "

      "; html += "
        "; group.items.forEach(function (item) { var activeClass = item.path === currentItem.path ? ' class="active"' : ""; html += '
      • ' + esc(item.title) + "
      • "; }); html += "
      "; }); sidebar.innerHTML = html; buildMobileBar(currentTab, currentItem); } function buildMobileBar(currentTab, currentItem) { var oldBar = document.getElementById("mobile-bar"); if (oldBar) oldBar.remove(); var headerContainer = document.getElementById("header-container") || document.getElementById("navbar"); if (!headerContainer) return; var route = resolveRoute(window.location.pathname); var bar = document.createElement("div"); bar.id = "mobile-bar"; bar.innerHTML = '' + '
      ' + '' + esc(route.group.title) + '' + '›' + '' + esc(currentItem.title) + '' + '' + '
      '; if (!document.body.classList.contains("is-home")) headerContainer.appendChild(bar); var pageBtn = bar.querySelector("#mobile-crumb-page"); if (pageBtn) { pageBtn.addEventListener("click", function () { window.scrollTo({ top: 0, behavior: "smooth" }); }); } var subBtn = bar.querySelector("#mobile-crumb-sub"); if (subBtn) { subBtn.addEventListener("click", function () { var targetId = subBtn.getAttribute("data-target-id"); var targetEl = targetId ? document.getElementById(targetId) : null; if (targetEl) { var header = document.getElementById("header-container") || document.getElementById("navbar"); var offset = header ? header.offsetHeight + 16 : 80; var topPos = targetEl.getBoundingClientRect().top + window.pageYOffset - offset; window.scrollTo({ top: Math.max(0, topPos), behavior: "smooth" }); } }); } var sidebar = document.getElementById("sidebar"); if (sidebar) { var existingTabs = sidebar.querySelector(".mobile-tabs"); if (existingTabs) existingTabs.remove(); var g = document.createElement("div"); g.className = "mobile-tabs"; g.innerHTML = '" + '
      ' + SITE_TREE.map(function (t) { var onClass = t.id === currentTab.id ? " on" : ""; return '"; }).join("") + "
      "; sidebar.insertBefore(g, sidebar.firstChild); g.querySelector(".mt-btn").addEventListener("click", function () { g.classList.toggle("open"); }); g.querySelectorAll(".mt-item").forEach(function (btn) { btn.addEventListener("click", function (e) { e.stopPropagation(); var targetId = btn.getAttribute("data-tab-id"); var targetTab = SITE_TREE.find(function (t) { return t.id === targetId; }); if (targetTab) { g.querySelectorAll(".mt-item").forEach(function (el) { el.classList.remove("on"); }); btn.classList.add("on"); g.classList.remove("open"); renderSidebar(targetTab, currentItem); sidebar.scrollTop = 0; } }); }); document.addEventListener("click", function (e) { if (!g.contains(e.target)) g.classList.remove("open"); }); } var existingHead = sidebar.querySelector(".side-head"); if (existingHead) existingHead.remove(); var head = document.createElement("div"); head.className = "side-head"; var brand = document.querySelector("#navbar .brand"); if (brand && sidebar) { var b = brand.cloneNode(true); b.querySelector("img").removeAttribute("id"); var sideLinks = document.createElement("div"); sideLinks.className = "side-links"; var gear = document.createElement("button"); gear.type = "button"; gear.className = "side-settings"; gear.setAttribute("aria-label", "Personalize"); gear.innerHTML = icon("sliders"); gear.addEventListener("click", function () { sidebar.classList.remove("open"); document.body.classList.remove("nav-open"); openSettings(); }); sideLinks.appendChild(gear); head.appendChild(b); head.appendChild(sideLinks); sidebar.insertBefore(head, sidebar.firstChild); } } function updateFooterNav(currentItem) { var allItems = []; SITE_TREE.forEach(function (tab) { tab.groups.forEach(function (grp) { grp.items.forEach(function (itm) { allItems.push(itm); }); }); }); var idx = allItems.findIndex(function (it) { return it.path.replace(/\/+$/, "") === currentItem.path.replace(/\/+$/, ""); }); var prev = idx > 0 ? allItems[idx - 1] : null; var next = idx >= 0 && idx < allItems.length - 1 ? allItems[idx + 1] : null; var prevBtn = document.getElementById("footer-prev"); var nextBtn = document.getElementById("footer-next"); if (prevBtn) { if (prev) { prevBtn.href = prev.path; prevBtn.innerHTML = icon("chevronLeft") + "" + esc(prev.title) + ""; prevBtn.classList.remove("hidden"); } else { prevBtn.classList.add("hidden"); } } if (nextBtn) { if (next) { nextBtn.href = next.path; nextBtn.innerHTML = "" + esc(next.title) + "" + icon("chevronRight"); nextBtn.classList.remove("hidden"); } else { nextBtn.classList.add("hidden"); } } } function updateToc() { var main = document.getElementById("main-content"); var tocList = document.getElementById("toc-list"); if (!main || !tocList) return; var headings = main.querySelectorAll("h2"); var html = ""; headings.forEach(function (h) { var id = h.id || h.textContent.toLowerCase().replace(/[^a-z0-9]+/g, "-"); h.id = id; html += '
    • ' + esc(h.textContent) + "
    • "; }); tocList.innerHTML = html; initTocSpy(); } function initTocSpy() { var main = document.getElementById("main-content"); var links = Array.prototype.slice.call(document.querySelectorAll("aside#toc a")); var headings = main ? Array.prototype.slice.call(main.querySelectorAll("h2, h3")) : []; function update() { var activeHeading = null; headings.forEach(function (h) { if (h.getBoundingClientRect().top <= 160) { activeHeading = h; } }); if (links.length) { var activeId = activeHeading ? activeHeading.id : ""; links.forEach(function (a) { var href = a.getAttribute("href").replace(/^#/, ""); a.classList.toggle("active", href === activeId); }); } var crumbPage = document.getElementById("mobile-crumb-page"); var crumbSub = document.getElementById("mobile-crumb-sub"); var crumbTarget = document.getElementById("mobile-crumb-target"); if (crumbPage && crumbSub && crumbTarget) { if (activeHeading && window.scrollY > 80) { crumbPage.classList.add("muted"); crumbSub.style.display = "inline-flex"; crumbSub.setAttribute("data-target-id", activeHeading.id); crumbTarget.textContent = activeHeading.textContent; } else { crumbPage.classList.remove("muted"); crumbSub.style.display = "none"; crumbSub.removeAttribute("data-target-id"); crumbTarget.textContent = ""; } } } window.addEventListener("scroll", update, { passive: true }); update(); } function addCopyButtons() { document.querySelectorAll(".code-group").forEach(function (cg) { if (cg.querySelector(".cg-copy-btn")) return; var b = document.createElement("button"); b.type = "button"; b.className = "cg-copy-btn"; b.setAttribute("aria-label", "Copy"); b.innerHTML = icon("copy"); b.onclick = function () { var activePre = cg.querySelector(".code-group-panel.active pre"); if (!activePre) return; navigator.clipboard.writeText(activePre.innerText).then(function () { b.innerHTML = icon("check"); setTimeout(function () { b.innerHTML = icon("copy"); }, 1500); }); }; cg.appendChild(b); }); document.querySelectorAll("main.prose pre").forEach(function (pre) { if (pre.closest(".code-group")) return; if (pre.parentNode.classList.contains("code-wrap") && pre.parentNode.querySelector(".copy-btn")) return; var wrap = pre.parentNode.classList.contains("code-wrap") ? pre.parentNode : document.createElement("div"); if (!pre.parentNode.classList.contains("code-wrap")) { wrap.className = "code-wrap"; pre.parentNode.insertBefore(wrap, pre); wrap.appendChild(pre); } var b = document.createElement("button"); b.type = "button"; b.className = "copy-btn"; b.setAttribute("aria-label", "Copy"); b.innerHTML = icon("copy"); b.onclick = function () { navigator.clipboard.writeText(pre.innerText).then(function () { b.innerHTML = icon("check"); setTimeout(function () { b.innerHTML = icon("copy"); }, 1500); }); }; wrap.appendChild(b); }); } function bindScrollFade(box, getScroller) { function update() { var el = getScroller(); var max = el.scrollWidth - el.clientWidth; var left = 0; var right = 0; if (max > 1) { left = Math.min(el.scrollLeft, 56); right = Math.min(Math.max(Math.round(max - el.scrollLeft), 0), 56); } box.style.setProperty("--fade-l", left + "px"); box.style.setProperty("--fade-r", right + "px"); } box.addEventListener("scroll", update, true); window.addEventListener("resize", update); update(); } function initCodeFades() { document.querySelectorAll("main.prose pre").forEach(function (pre) { var code = pre.querySelector("code"); bindScrollFade(pre, function () { return code && code.scrollWidth > code.clientWidth + 1 ? code : pre; }); }); } function wrapTables() { document.querySelectorAll("main.prose table").forEach(function (table) { if (table.parentNode.classList.contains("table-wrap")) { bindScrollFade(table.parentNode, function () { return table.parentNode; }); } }); } function navigateTo(path) { var cleanPath = path.startsWith("/") ? path : "/" + path; if (window.location.pathname !== cleanPath) { window.history.pushState(null, "", cleanPath); } loadPage(); } function mountGreeting(root) { var content = root.querySelector(".header-content"); var title = root.querySelector(".header-title"); if (!content || !title) return; var name = getPref("name", ""); var copy = THEME_COPY[resolveTheme(getThemeMode())] || THEME_COPY.base; var pill = document.createElement("div"); pill.className = "hero-greeting"; pill.textContent = timeGreeting() + (name ? ", " + name : "") + (copy.greeting ? " · " + copy.greeting : ""); content.insertBefore(pill, title); var last = null; try { last = JSON.parse(localStorage.getItem("vyla-last")); } catch (e) { } var actions = root.querySelector(".header-actions"); if (actions && last && typeof last.path === "string" && last.path.charAt(0) === "/" && last.path.indexOf("//") !== 0) { var resume = document.createElement("a"); resume.className = "btn-secondary hero-resume"; resume.href = last.path; resume.textContent = "Continue: " + (last.title || last.path); actions.appendChild(resume); } } function updatePageSEO(route, mdContent) { var isHome = !route || !route.item || route.item.path === "/"; var canonicalLink = document.querySelector('link[rel="canonical"]'); var metaDesc = document.querySelector('meta[name="description"]'); var ogTitle = document.querySelector('meta[property="og:title"]'); var ogDesc = document.querySelector('meta[property="og:description"]'); var ogUrl = document.querySelector('meta[property="og:url"]'); var twTitle = document.querySelector('meta[name="twitter:title"]'); var twDesc = document.querySelector('meta[name="twitter:description"]'); if (isHome) { var homeTitle = "Vyla Entertainment - Free & Decentralized Media Platform"; var homeDesc = "Vyla Entertainment is a community-built, decentralized media platform and developer ecosystem. Featuring native client apps for Desktop, Mobile, and Android TV, a scraping SDK, and a self-hosted Stream API."; var homeUrl = "https://vyla.cc/"; document.title = homeTitle; if (canonicalLink) canonicalLink.setAttribute("href", homeUrl); if (metaDesc) metaDesc.setAttribute("content", homeDesc); if (ogTitle) ogTitle.setAttribute("content", homeTitle); if (ogDesc) ogDesc.setAttribute("content", homeDesc); if (ogUrl) ogUrl.setAttribute("content", homeUrl); if (twTitle) twTitle.setAttribute("content", homeTitle); if (twDesc) twDesc.setAttribute("content", homeDesc); var dynJson = document.getElementById("dynamic-jsonld"); if (dynJson) dynJson.remove(); return; } var pageTitle = route.item.title + " - Vyla Docs"; document.title = pageTitle; var pageUrl = "https://vyla.cc" + route.item.path; if (canonicalLink) canonicalLink.setAttribute("href", pageUrl); if (ogUrl) ogUrl.setAttribute("content", pageUrl); if (ogTitle) ogTitle.setAttribute("content", pageTitle); if (twTitle) twTitle.setAttribute("content", pageTitle); var desc = ""; if (mdContent) { var descMatch = mdContent.match(/

      ([\s\S]*?)<\/p>/i); if (descMatch) { desc = descMatch[1].replace(/<[^>]+>/g, "").trim(); } else { var pMatch = mdContent.match(/^(?:#+[^\n]*\n+)+([^\n#<][^\n]+)/m); if (pMatch) { desc = pMatch[1].replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").trim(); } } } if (!desc) { desc = route.item.title + " guide and developer documentation for the Vyla Ecosystem."; } if (metaDesc) metaDesc.setAttribute("content", desc); if (ogDesc) ogDesc.setAttribute("content", desc); if (twDesc) twDesc.setAttribute("content", desc); var dynScript = document.getElementById("dynamic-jsonld"); if (!dynScript) { dynScript = document.createElement("script"); dynScript.id = "dynamic-jsonld"; dynScript.type = "application/ld+json"; document.head.appendChild(dynScript); } var graph = [ { "@type": "TechArticle", "@id": pageUrl + "#article", "url": pageUrl, "headline": route.item.title, "description": desc, "inLanguage": "en-US", "isPartOf": { "@id": "https://vyla.cc/#website" }, "publisher": { "@id": "https://vyla.cc/#organization" } }, { "@type": "BreadcrumbList", "@id": pageUrl + "#breadcrumb", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", "item": "https://vyla.cc/" }, { "@type": "ListItem", "position": 2, "name": route.tab.title, "item": "https://vyla.cc" + route.tab.path }, { "@type": "ListItem", "position": 3, "name": route.item.title, "item": pageUrl } ] } ]; dynScript.textContent = JSON.stringify({ "@context": "https://schema.org", "@graph": graph }); } function loadPage() { var path = window.location.pathname.replace(/\/+$/, "") || "/"; var isHome = path === "/"; document.body.classList.toggle("is-home", isHome); var contentEl = document.getElementById("main-content"); if (isHome) { updatePageSEO(null, null); renderNavTabs(SITE_TREE[0]); renderSidebar(SITE_TREE[0], { title: "Home", path: "/" }); if (contentEl) { if (contentEl.querySelector(".home-container")) { mountGreeting(contentEl); applyTheme(); } else { contentEl.classList.remove("content-animate", "caught-up"); fetch("/home.txt") .then(function (res) { if (!res.ok) throw new Error(); return res.text(); }) .then(function (html) { contentEl.innerHTML = html; mountGreeting(contentEl); applyTheme(); window.scrollTo(0, 0); }) .catch(function () { contentEl.innerHTML = "

      Home

      Create /home.txt to customize this page.

      "; }); } } var tocList = document.getElementById("toc-list"); if (tocList) tocList.innerHTML = ""; var prevBtn = document.getElementById("footer-prev"); var nextBtn = document.getElementById("footer-next"); if (prevBtn) prevBtn.classList.add("hidden"); if (nextBtn) nextBtn.classList.add("hidden"); var sidebar = document.getElementById("sidebar"); if (sidebar && sidebar.classList.contains("open")) { sidebar.classList.remove("open"); document.body.classList.remove("nav-open"); } return; } var route = resolveRoute(window.location.pathname); renderNavTabs(route.tab); renderSidebar(route.tab, route.item); document.body.classList.remove("is-404"); var contentEl = document.getElementById("main-content"); if (route.is404) { document.body.classList.add("is-404"); document.title = "404 - Page Not Found - Vyla"; if (contentEl) { contentEl.classList.remove("content-animate"); contentEl.innerHTML = '
      ' + '
      404
      ' + '
      PAGE NOT FOUND
      ' + '

      ' + esc(notFoundCopy()) + '

      ' + ' Take me home' + '
      '; var tocList = document.getElementById("toc-list"); if (tocList) tocList.innerHTML = ""; var prevBtn = document.getElementById("footer-prev"); var nextBtn = document.getElementById("footer-next"); if (prevBtn) prevBtn.classList.add("hidden"); if (nextBtn) nextBtn.classList.add("hidden"); window.scrollTo(0, 0); } var sidebar = document.getElementById("sidebar"); if (sidebar && sidebar.classList.contains("open")) { sidebar.classList.remove("open"); document.body.classList.remove("nav-open"); } return; } if (contentEl) { contentEl.classList.remove("content-animate"); contentEl.innerHTML = ''; } fetch(route.item.file) .then(function (res) { var ctype = res.headers.get("content-type") || ""; if (!res.ok || ctype.indexOf("text/html") !== -1) throw new Error("Not found"); return res.text(); }) .then(function (md) { if (contentEl) { updatePageSEO(route, md); var hasApiToken = md.indexOf("YOUR_API_URL") !== -1; contentEl.innerHTML = parseMarkdown(personalize(md)); if (window.Prism) { window.Prism.highlightAllUnder(contentEl); } var eyebrow = document.createElement("div"); eyebrow.className = "eyebrow"; var eyebrowText = document.createElement("span"); eyebrowText.textContent = route.group.title; eyebrow.appendChild(eyebrowText); eyebrow.appendChild(buildBookmarkButton(route)); var h1 = contentEl.querySelector("h1"); if (h1) h1.parentNode.insertBefore(eyebrow, h1); contentEl.classList.remove("caught-up"); contentEl.classList.add("content-animate"); revealThemeAmbient(); var catchUpOnScroll = function () { if (contentEl.classList.contains("content-animate")) { contentEl.classList.add("caught-up"); } window.removeEventListener("scroll", catchUpOnScroll, { passive: true }); window.removeEventListener("wheel", catchUpOnScroll, { passive: true }); window.removeEventListener("touchmove", catchUpOnScroll, { passive: true }); }; window.addEventListener("scroll", catchUpOnScroll, { passive: true }); window.addEventListener("wheel", catchUpOnScroll, { passive: true }); window.addEventListener("touchmove", catchUpOnScroll, { passive: true }); setTimeout(function () { contentEl.classList.add("caught-up"); window.removeEventListener("scroll", catchUpOnScroll); window.removeEventListener("wheel", catchUpOnScroll); window.removeEventListener("touchmove", catchUpOnScroll); }, 700); updateFooterNav(route.item); updateToc(); addCopyButtons(); initCodeFades(); wrapTables(); resolveLatestDownloads(); if (hasApiToken) insertApiCard(); renderChangelog(); renderContributors(); if (typeof renderDonationLeaderboard === "function") renderDonationLeaderboard(); rememberPage(route); if (window.location.hash) { var hashId = window.location.hash.slice(1); var hashEl = document.getElementById(hashId); if (hashEl) { var header = document.getElementById("header-container") || document.getElementById("navbar"); var offset = header ? header.offsetHeight + 16 : 80; var topPos = hashEl.getBoundingClientRect().top + window.pageYOffset - offset; window.scrollTo({ top: Math.max(0, topPos), behavior: "smooth" }); } else { window.scrollTo(0, 0); } } else { window.scrollTo(0, 0); } } }) .catch(function () { if (contentEl) { contentEl.innerHTML = "

      Page not found

      The requested page could not be located.

      "; } }); var sidebar = document.getElementById("sidebar"); if (sidebar && sidebar.classList.contains("open")) { sidebar.classList.remove("open"); document.body.classList.remove("nav-open"); } } function getRecent() { try { return JSON.parse(localStorage.getItem("vyla-recent")) || []; } catch (e) { return []; } } function saveRecent(item) { if (!item) return; var slim = { title: item.title, crumbs: item.crumbs, href: item.href, type: item.type }; var list = getRecent().filter(function (r) { return r.href !== slim.href; }); list.unshift(slim); localStorage.setItem("vyla-recent", JSON.stringify(list.slice(0, 5))); } function loadSearchData() { if (searchData) return Promise.resolve(searchData); var promises = []; SITE_TREE.forEach(function (tab) { tab.groups.forEach(function (group) { group.items.forEach(function (item) { promises.push( fetch(item.file) .then(function (r) { return r.text(); }) .then(function (text) { return { title: item.title, crumbs: [tab.title, group.title, item.title], href: item.path, type: "page", text: text.replace(/[#*`[\]()]/g, " ").slice(0, 3000) }; }) .catch(function () { return null; }) ); }); }); }); return Promise.all(promises).then(function (results) { searchData = results.filter(Boolean); return searchData; }); } function renderSearch(keepActive) { var q = document.getElementById("search-input").value.trim().toLowerCase(); var tokens = q.split(/\s+/).filter(Boolean); var box = document.getElementById("search-results"); if (!keepActive) searchActive = 0; if (tokens.length) { if (!searchData) { searchResults = []; box.innerHTML = '
      Indexing pages...
      '; return; } searchResults = searchData .map(function (it) { var score = 0; var t = it.title.toLowerCase(); var txt = it.text.toLowerCase(); tokens.forEach(function (tok) { if (t.indexOf(tok) !== -1) score += 20; if (txt.indexOf(tok) !== -1) score += 2; }); return { it: it, s: score }; }) .filter(function (r) { return r.s > 0; }) .sort(function (a, b) { return b.s - a.s; }) .slice(0, 10) .map(function (r) { return r.it; }); } else { var saved = getBookmarks(); var recent = getRecent().filter(function (r) { return !saved.some(function (s) { return s.href === r.href; }); }); savedCount = saved.length; searchResults = saved.concat(recent); } if (!searchResults.length) { box.innerHTML = '
      ' + (tokens.length ? "No results found" : "Nothing saved or recent yet") + "
      "; return; } box.innerHTML = searchResults.map(function (it, i) { var label = ""; if (!tokens.length && i === 0 && savedCount) label = '
      Saved
      '; if (!tokens.length && i === savedCount) label = '
      Recent searches
      '; var crumbs = it.crumbs.join('›'); return label + '' + icon(i < savedCount && !tokens.length ? "star" : "doc") + '
      ' + esc(it.title) + "
      " + '
      ' + crumbs + "
      "; }).join(""); } function openSearch() { var input = document.getElementById("search-input"); document.getElementById("search-modal").classList.add("open"); document.body.style.overflow = "hidden"; input.value = ""; renderSearch(false); loadSearchData().then(function () { renderSearch(false); }); input.focus(); } function closeSearch() { document.getElementById("search-modal").classList.remove("open"); document.body.style.overflow = ""; } function onSearchKey(e) { if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") { e.preventDefault(); openSearch(); return; } var modal = document.getElementById("search-modal"); if (!modal || !modal.classList.contains("open")) return; if (e.key === "Escape") { closeSearch(); } else if (e.key === "ArrowDown" || e.key === "ArrowUp") { e.preventDefault(); var n = searchResults.length || 1; searchActive = (searchActive + (e.key === "ArrowDown" ? 1 : -1) + n) % n; renderSearch(true); } else if (e.key === "Enter" && searchResults[searchActive]) { var item = searchResults[searchActive]; saveRecent(item); closeSearch(); navigateTo(item.href); } } function buildSearch() { var trigger = document.getElementById("search-trigger"); if (trigger) { trigger.innerHTML = icon("search") + "Search...Ctrl K"; trigger.setAttribute("aria-label", "Search... (Ctrl+K)"); } var modal = document.createElement("div"); modal.id = "search-modal"; modal.innerHTML = '
      ' + icon("search") + '
      ' + '
      '; document.body.appendChild(modal); modal.querySelector(".search-backdrop").addEventListener("click", closeSearch); document.getElementById("search-input").addEventListener("input", function () { renderSearch(false); }); document.getElementById("search-results").addEventListener("click", function (e) { var a = e.target.closest(".sr"); if (a) { e.preventDefault(); saveRecent(searchResults[Number(a.getAttribute("data-i"))]); closeSearch(); navigateTo(a.getAttribute("href")); } }); document.addEventListener("keydown", onSearchKey); } var lightboxScale = 1; var activeOriginImg = null; function bindLightboxClicks() { document.addEventListener("click", function (e) { if (!(e.target instanceof HTMLImageElement)) return; var img = e.target; if (!img.closest("main.prose")) return; if (img.closest("a")) return; e.preventDefault(); openLightbox(img); }); } function initLightbox() { if (document.getElementById("image-lightbox")) return; var lb = document.createElement("div"); lb.id = "image-lightbox"; lb.className = "image-lightbox"; lb.innerHTML = '' + ''; document.body.appendChild(lb); var img = lb.querySelector(".lightbox-img"); var btnClose = lb.querySelector(".lb-close"); var btnIn = lb.querySelector(".lb-zoom-in"); var btnOut = lb.querySelector(".lb-zoom-out"); function closeLb() { if (!lb.classList.contains("active")) return; lb.classList.remove("active"); lightboxScale = 1; img.style.transform = "none"; if (activeOriginImg) { var rect = activeOriginImg.getBoundingClientRect(); img.style.top = rect.top + "px"; img.style.left = rect.left + "px"; img.style.width = rect.width + "px"; img.style.height = rect.height + "px"; img.style.borderRadius = "16px"; } document.body.style.overflow = ""; setTimeout(function () { if (!lb.classList.contains("active")) { img.src = ""; activeOriginImg = null; } }, 450); } function setScale(s) { lightboxScale = Math.min(Math.max(0.5, s), 3); img.style.transform = "scale(" + lightboxScale + ")"; } btnClose.addEventListener("click", closeLb); lb.addEventListener("click", function (e) { if (e.target === lb) closeLb(); }); btnIn.addEventListener("click", function (e) { e.stopPropagation(); setScale(lightboxScale + 0.25); }); btnOut.addEventListener("click", function (e) { e.stopPropagation(); setScale(lightboxScale - 0.25); }); window.addEventListener("keydown", function (e) { if (lb.classList.contains("active")) { if (e.key === "Escape") closeLb(); else if (e.key === "+" || e.key === "=") setScale(lightboxScale + 0.25); else if (e.key === "-") setScale(lightboxScale - 0.25); } }); } function openLightbox(originImg) { initLightbox(); var lb = document.getElementById("image-lightbox"); var img = lb.querySelector(".lightbox-img"); activeOriginImg = originImg; var rect = originImg.getBoundingClientRect(); img.style.transition = "none"; img.style.top = rect.top + "px"; img.style.left = rect.left + "px"; img.style.width = rect.width + "px"; img.style.height = rect.height + "px"; img.style.borderRadius = "16px"; img.style.transform = "none"; img.src = originImg.src; img.alt = originImg.alt || ""; lightboxScale = 1; lb.classList.add("active"); document.body.style.overflow = "hidden"; void img.offsetWidth; var naturalW = originImg.naturalWidth || rect.width; var naturalH = originImg.naturalHeight || rect.height; var maxW = window.innerWidth * 0.92; var maxH = window.innerHeight * 0.88; var ratio = Math.min(maxW / naturalW, maxH / naturalH, 1); if (ratio <= 0) ratio = 1; var targetW = naturalW * ratio; var targetH = naturalH * ratio; var targetTop = (window.innerHeight - targetH) / 2; var targetLeft = (window.innerWidth - targetW) / 2; img.style.transition = ""; img.style.top = targetTop + "px"; img.style.left = targetLeft + "px"; img.style.width = targetW + "px"; img.style.height = targetH + "px"; img.style.borderRadius = "14px"; } function initContribSheet() { if (localStorage.getItem("vyla-contrib-dismissed") === "true") return; var overlay = document.createElement("div"); overlay.className = "sheet-overlay"; overlay.innerHTML = '
      ' + '
      ' + '
      ' + '

      Source Removal Requests

      ' + '

      If your source is listed here, I sincerely apologize. I understand that its inclusion may be costing you a lot of money, and that was never my intention. Please contact me as soon as possible, and I\'ll remove it ASAP.

      ' + '

      This is a small hobby project that I built for learning and personal interest, not for profit. I genuinely want to respect the wishes of source owners and will address any removal requests as quickly as I can.

      ' + '
      ' + 'Contact Me' + '' + '
      ' + '
      '; document.body.appendChild(overlay); var panel = overlay.querySelector(".sheet-panel"); var backdrop = overlay.querySelector(".sheet-backdrop"); var neverBtn = overlay.querySelector(".sheet-btn-never"); var guideLink = overlay.querySelector(".sheet-btn-primary"); var handleBar = overlay.querySelector(".sheet-handle-bar"); function openSheet() { overlay.classList.add("active"); document.body.style.overflow = "hidden"; } function closeSheet() { overlay.classList.remove("active"); document.body.style.overflow = ""; setTimeout(function () { if (overlay.parentNode) overlay.parentNode.removeChild(overlay); }, 400); } function dismissForever() { localStorage.setItem("vyla-contrib-dismissed", "true"); closeSheet(); } backdrop.addEventListener("click", closeSheet); neverBtn.addEventListener("click", dismissForever); guideLink.addEventListener("click", closeSheet); var isDragging = false; var startY = 0; var currentY = 0; var currentTranslate = 0; function getSnapOffsets() { var panelH = panel.offsetHeight; return [0, panelH * 0.35, panelH * 0.65]; } handleBar.addEventListener("touchstart", function (e) { if (window.innerWidth > 900) return; isDragging = true; startY = e.touches[0].clientY; currentY = startY; panel.classList.add("dragging"); }, { passive: true }); window.addEventListener("touchmove", function (e) { if (!isDragging || window.innerWidth > 900) return; currentY = e.touches[0].clientY; var delta = currentY - startY; var val = Math.max(0, currentTranslate + delta); panel.style.transform = "translateY(" + val + "px)"; }, { passive: true }); window.addEventListener("touchend", function () { if (!isDragging || window.innerWidth > 900) return; isDragging = false; panel.classList.remove("dragging"); var delta = currentY - startY; var finalOffset = currentTranslate + delta; if (delta > 110 || finalOffset > panel.offsetHeight * 0.7) { panel.style.transform = "translateY(100%)"; closeSheet(); return; } var snaps = getSnapOffsets(); var closest = snaps.reduce(function (prev, curr) { return Math.abs(curr - finalOffset) < Math.abs(prev - finalOffset) ? curr : prev; }, 0); currentTranslate = closest; panel.style.transform = "translateY(" + closest + "px)"; }); setTimeout(openSheet, 400); } document.addEventListener("DOMContentLoaded", function () { if (localStorage.getItem("vyla-banner-dismissed") === "true") { var banner = document.getElementById("banner"); if (banner) banner.setAttribute("data-dismissed", "true"); document.body.classList.add("banner-dismissed"); } var header = document.getElementById("header-container"); if (header) { requestAnimationFrame(function () { document.documentElement.style.setProperty("--header-height", header.offsetHeight + "px"); }); window.addEventListener("resize", function () { requestAnimationFrame(function () { document.documentElement.style.setProperty("--header-height", header.offsetHeight + "px"); }); }); } applyTheme(); buildThemeControls(); buildSearch(); buildSettings(); initEasterEggs(); initContribSheet(); bindLightboxClicks(); loadPage(); window.addEventListener("popstate", loadPage); });