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_VIDEOS = {
dark: "https://www.pexels.com/download/video/39880903/",
light: "https://www.pexels.com/download/video/36600568/",
midnight: "https://www.pexels.com/download/video/15860507/",
nord: "https://www.pexels.com/download/video/31468607/",
dracula: "https://www.pexels.com/download/video/8116002/",
forest: "https://www.pexels.com/download/video/11265968/",
sunset: "https://www.pexels.com/download/video/855437/",
cyberpunk: "https://www.pexels.com/download/video/32336464/"
};
var SITE_TREE = [
{
id: "vyla",
title: "Vyla",
path: "/start-here/introduction",
groups: [
{
title: "Start here",
items: [
{ title: "Introduction", file: "/start-here/introduction.md", path: "/start-here/introduction" },
{ title: "Architecture", file: "/start-here/architecture.md", path: "/start-here/architecture" }
]
},
{
title: "Products",
items: [
{ title: "Home", file: "/products/home.md", path: "/products/home" },
{ title: "Go", file: "/products/go.md", path: "/products/go" },
{ title: "TV", file: "/products/tv.md", path: "/products/tv" },
{ title: "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: "Vyla SDK",
items: [
{ title: "SDK", file: "/sdk/vyla-sdk/index.md", path: "/sdk" },
{ title: "SDK Quickstart", file: "/sdk/vyla-sdk/quickstart.md", path: "/sdk/vyla-sdk/quickstart" },
{ title: "Manga", file: "/sdk/vyla-sdk/manga.md", path: "/sdk/vyla-sdk/manga" },
{ title: "Contributing", file: "/sdk/vyla-sdk/contributing.md", path: "/sdk/vyla-sdk/contributing" }
]
}
]
},
{
id: "stream-api",
title: "Stream API",
path: "/stream-api/overview",
groups: [
{
title: "Overview",
items: [
{ title: "Overview", file: "/stream-api/overview/index.md", path: "/stream-api/overview" }
]
},
{
title: "Reference",
items: [
{ title: "Paths", 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" },
{ title: "Operations", 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: "misc",
title: "Misc",
path: "/misc/project/credit",
groups: [
{
title: "Project",
items: [
{ title: "Story", file: "/misc/project/story.md", path: "/misc/project/story" },
{ title: "Changelog", file: "/misc/project/changelog.md", path: "/misc/project/changelog" },
{ title: "Credit", file: "/misc/project/credit.md", path: "/misc/project/credit" },
{ title: "Donate", file: "/misc/project/donate.md", path: "/misc/project/donate" },
{ title: "DMCA", 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_VIDEOS[theme] || THEME_VIDEOS.halloween;
var videosOff = getPref("video", "on") === "off";
document.querySelectorAll(".body-blurred-video, .header-video").forEach(function (v) {
if (videosOff) {
v.pause();
if (v.getAttribute("src")) {
v.removeAttribute("src");
v.load();
}
return;
}
if (v.getAttribute("src") !== targetSrc) {
v.src = targetSrc;
v.load();
v.play().catch(function () { });
}
});
applyThemeCopy(theme);
}
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 = '
';
THEMES_GENERAL.forEach(function (t) {
html += '' + icon(t.id) + '' + esc(t.label) + ' ✓ ';
});
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 =
'Using your own server? ' +
' ' +
'Apply
' +
'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 (/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 '' + icon(t.id) + "" + esc(t.label) + " ";
}).join("");
}
function switchRow(id, title, hint) {
return '' + title + ' ' + hint + ' ' +
' ';
}
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("" + listType + ">");
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 += "" + parseInline(ths[i]) + " ";
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 += "" + parseInline(tds[c]) + " ";
html += " ";
}
html += " ";
}
html += "
";
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 ' ";
})
.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 += '' + esc(tab) + ' ';
});
cgHtml += '
';
codeGroupPanels.forEach(function (panel, idx) {
var activeClass = idx === 0 ? " active" : "";
var langClass = panel.lang ? ' class="language-' + panel.lang + '"' : ' class="language-none"';
cgHtml += '
';
});
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('");
}
}
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("" + parseInline(olMatch[1]) + " ");
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 =
'' + icon("menu") + ' ' +
'' +
'' + 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 =
'' + esc(currentTab.title) + " " + icon("chevron") + " " +
'";
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 initGlassSurface(el, opts) {
if (!el || el.getAttribute("data-glass")) return;
el.setAttribute("data-glass", "true");
opts = opts || {};
var distortionScale = opts.distortionScale !== undefined ? opts.distortionScale : -300;
var displace = opts.displace !== undefined ? opts.displace : 0;
var backgroundOpacity = opts.backgroundOpacity !== undefined ? opts.backgroundOpacity : 0.1;
var saturation = opts.saturation !== undefined ? opts.saturation : 1;
var redOffset = opts.redOffset !== undefined ? opts.redOffset : 0;
var greenOffset = opts.greenOffset !== undefined ? opts.greenOffset : 10;
var blueOffset = opts.blueOffset !== undefined ? opts.blueOffset : 20;
var borderWidth = opts.borderWidth !== undefined ? opts.borderWidth : 0.07;
var brightness = opts.brightness !== undefined ? opts.brightness : 50;
var opacity = opts.opacity !== undefined ? opts.opacity : 0.93;
var blur = opts.blur !== undefined ? opts.blur : 11;
var mixBlendMode = opts.mixBlendMode || "difference";
var uid = Math.random().toString(36).slice(2, 9);
var filterId = "glass-filter-" + uid;
var redGradId = "red-grad-" + uid;
var blueGradId = "blue-grad-" + uid;
var softId = "soft-" + uid;
var isWebkit = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
var isFirefox = /Firefox/.test(navigator.userAgent);
var div = document.createElement("div");
div.style.backdropFilter = "url(#" + filterId + ")";
var svgSupported = !isWebkit && !isFirefox && div.style.backdropFilter !== "";
var rect = el.getBoundingClientRect();
var w = Math.round(rect.width) || 160;
var h = Math.round(rect.height) || 44;
var r = opts.cornerRadius !== undefined ? opts.cornerRadius : Math.round(h / 2);
var edge = Math.min(w, h) * (borderWidth * 0.5);
var mapSvg = '' +
'' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ';
var mapUrl = "data:image/svg+xml," + encodeURIComponent(mapSvg);
el.style.setProperty("--glass-frost", backgroundOpacity);
el.style.setProperty("--glass-saturation", saturation);
el.style.setProperty("--filter-id", "url(#" + filterId + ")");
el.classList.add(svgSupported ? "glass-surface--svg" : "glass-surface--fallback");
var filterSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
filterSvg.setAttribute("class", "glass-surface__filter");
filterSvg.innerHTML = '' +
'' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ';
var childNodes = Array.prototype.slice.call(el.childNodes);
var wrap = document.createElement("div");
wrap.className = "glass-surface__content";
childNodes.forEach(function (n) { wrap.appendChild(n); });
el.appendChild(filterSvg);
if (!svgSupported) {
var sheen = document.createElement("span");
sheen.className = "glass-surface__sheen";
sheen.setAttribute("aria-hidden", "true");
var rim = document.createElement("span");
rim.className = "glass-surface__rim";
rim.setAttribute("aria-hidden", "true");
el.appendChild(sheen);
el.appendChild(rim);
}
el.appendChild(wrap);
}
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);
}
if (typeof applyGlassSurfaces === "function") {
applyGlassSurfaces(root);
}
}
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) {
document.title = "Vyla Entertainment - Vyla";
renderNavTabs(SITE_TREE[0]);
renderSidebar(SITE_TREE[0], { title: "Home", path: "/" });
if (contentEl) {
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.title = route.item.title + " - Vyla";
var contentEl = document.getElementById("main-content");
document.body.classList.remove("is-404");
var route = resolveRoute(window.location.pathname);
renderNavTabs(route.tab);
renderSidebar(route.tab, route.item);
document.title = route.item.title + " - Vyla";
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) {
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");
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();
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 ";
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.
' +
'
' +
'
';
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);
});