diff --git a/assets/app.js b/assets/app.js
index bcbdacd..4c84993 100644
--- a/assets/app.js
+++ b/assets/app.js
@@ -1,4 +1,5 @@
var THEME_KEY = "vyla-theme";
+
var ICONS = {
system: ' ',
light: ' ',
@@ -10,6 +11,12 @@ var ICONS = {
sunset: ' ',
cyberpunk: ' ',
halloween: ' ',
+ christmas: ' ',
+ autumn: ' ',
+ winter: ' ',
+ auto: ' ',
+ sliders: ' ',
+ star: ' ',
search: ' ',
menu: ' ',
doc: ' ',
@@ -22,6 +29,7 @@ var ICONS = {
};
var THEMES_GENERAL = [
+ { id: "auto", label: "Auto (seasonal)" },
{ id: "dark", label: "Dark" },
{ id: "light", label: "Light" },
{ id: "midnight", label: "Midnight" },
@@ -33,11 +41,17 @@ var THEMES_GENERAL = [
];
var THEMES_SEASONAL = [
- { id: "halloween", label: "Halloween" }
+ { id: "halloween", label: "Halloween" },
+ { id: "autumn", label: "Autumn" },
+ { id: "winter", label: "Winter" },
+ { id: "christmas", label: "Christmas" }
];
var THEME_VIDEOS = {
- halloween: "https://www.pexels.com/download/video/5605039/",
+ halloween: "https://www.pexels.com/download/video/5652678/",
+ autumn: "https://www.pexels.com/download/video/34463521/",
+ winter: "https://www.pexels.com/download/video/30181650/",
+ christmas: "https://www.pexels.com/download/video/35282426/",
dark: "https://www.pexels.com/download/video/31270356/",
light: "https://www.pexels.com/download/video/13956986/",
midnight: "https://www.pexels.com/download/video/15860507/",
@@ -66,7 +80,7 @@ var SITE_TREE = [
items: [
{ title: "Home", file: "/products/home.md", path: "/products/home" },
{ title: "Vyla Go", file: "/products/go.md", path: "/products/go" },
- { title: "Tv", file: "/products/tv.md", path: "/products/tv" },
+ { title: "TV", file: "/products/tv.md", path: "/products/tv" },
{ title: "Player", file: "/products/player.md", path: "/products/player" }
]
},
@@ -86,8 +100,9 @@ var SITE_TREE = [
{
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: "SDK", file: "/sdk/vyla-sdk/index.md", path: "/sdk" },
+ { title: "SDK Quickstart", file: "/sdk/vyla-sdk/quickstart.md", path: "/sdk/vyla-sdk/quickstart" },
+ { title: "Contributing", file: "/sdk/vyla-sdk/contributing.md", path: "/sdk/vyla-sdk/contributing" }
]
}
]
@@ -127,6 +142,8 @@ var SITE_TREE = [
{
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: "DMCA", file: "/misc/project/DMCA.md", path: "/misc/project/DMCA" }
]
@@ -150,22 +167,34 @@ function esc(s) {
}
function getThemeMode() {
- return localStorage.getItem(THEME_KEY) || "halloween";
+ return localStorage.getItem(THEME_KEY) || "auto";
}
function applyTheme() {
var mode = getThemeMode();
- document.documentElement.setAttribute("data-theme", mode);
+ var theme = resolveTheme(mode);
+ document.documentElement.setAttribute("data-theme", theme);
var logo = document.getElementById("brand-logo");
- if (logo) logo.src = mode === "light" ? "/assets/images/dark_logo.png" : "/assets/images/logo.png";
- var targetSrc = THEME_VIDEOS[mode] || THEME_VIDEOS.halloween;
+ if (logo) logo.src = theme === "light" ? "/assets/images/dark_logo.png" : "/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) {
@@ -188,15 +217,9 @@ 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, #theme-seg button[data-mode], #theme-seg-menu button").forEach(function (b) {
+ document.querySelectorAll("#theme-menu button, .st-theme").forEach(function (b) {
b.classList.toggle("on", b.getAttribute("data-mode") === mode);
});
- var moreBtn = document.getElementById("theme-seg-more");
- if (moreBtn) {
- var isOther = mode !== "dark" && mode !== "light";
- moreBtn.classList.toggle("on", isOther);
- moreBtn.innerHTML = icon(ICONS[mode] ? mode : "halloween");
- }
}
function buildThemeControls() {
@@ -257,6 +280,707 @@ function cgSwitch(btn) {
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
+ },
+ halloween: {
+ banner: "π Happy Halloween!",
+ footer: "Stay spooky, keep streaming.",
+ notFound: "Something is lurking where this page should be. Let's get you back before the lights go out.",
+ greeting: "Spooky season is here.",
+ favicon: "π",
+ confetti: ["π", "π¦", "π»", "π·οΈ"]
+ },
+ autumn: {
+ banner: "π Happy fall!",
+ footer: "Grab something warm and press play.",
+ notFound: "This page blew away with the leaves. Let's find you a better path.",
+ greeting: "Cozy season is here.",
+ favicon: "π",
+ confetti: ["π", "π", "π"]
+ },
+ winter: {
+ banner: "βοΈ Stay warm!",
+ footer: "Cold outside, perfect for a movie night.",
+ notFound: "This page got buried in the snow. Let's dig you back out.",
+ greeting: "Perfect weather for staying in.",
+ favicon: "βοΈ",
+ confetti: ["βοΈ", "β", "β¨"]
+ },
+ christmas: {
+ banner: "π Happy holidays!",
+ footer: "Wishing you a quiet night and a great watchlist.",
+ notFound: "This page isn't on the nice list or the naughty list. Let's head home.",
+ greeting: "Happy holidays.",
+ favicon: "π",
+ confetti: ["π", "β", "π", "βοΈ"]
+ }
+};
+
+var KONAMI = ["ArrowUp", "ArrowUp", "ArrowDown", "ArrowDown", "ArrowLeft", "ArrowRight", "ArrowLeft", "ArrowRight", "b", "a"];
+var PROJECTS = ["home", "go", "TV", "vyla-sdk", "stream-api"];
+var PROJECT_LABELS = { home: "Vyla Home", go: "Vyla Go", TV: "Vyla TV", "vyla-sdk": "Vyla SDK", "stream-api": "Stream API" };
+
+function seasonalTheme() {
+ var d = new Date();
+ var m = d.getMonth();
+ if (m === 9) return "halloween";
+ if (m === 10) return "autumn";
+ if (m === 11) return "christmas";
+ if (m === 0 || m === 1) return "winter";
+ if (m === 8 && d.getDate() >= 22) return "autumn";
+ return "dark";
+}
+
+function resolveTheme(mode) {
+ return mode === "auto" ? seasonalTheme() : 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 renderChangelog() {
+ var box = document.getElementById("changelog");
+ if (!box) return;
+ box.innerHTML = 'Loading releases...
';
+ Promise.all(PROJECTS.map(function (p) {
+ return fetchProjectReleases(p).then(function (list) {
+ return list.map(function (r) { return { project: p, rel: r }; });
+ });
+ })).then(function (groups) {
+ var all = [].concat.apply([], groups).sort(function (a, b) {
+ return new Date(b.rel.released_at || b.rel.created_at) - new Date(a.rel.released_at || a.rel.created_at);
+ }).slice(0, 15);
+ if (!all.length) {
+ box.innerHTML = 'No releases found yet.
';
+ return;
+ }
+ box.innerHTML = all.map(function (entry) {
+ var r = entry.rel;
+ var d = new Date(r.released_at || r.created_at);
+ var date = isNaN(d) ? "" : d.toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" });
+ var notes = String(r.description || "").replace(/[#*`>_\[\]]/g, "").replace(/\s+/g, " ").trim().slice(0, 280);
+ var url = r._links && typeof r._links.self === "string" && /^https?:\/\//.test(r._links.self) ? r._links.self : "";
+ return '' + esc(PROJECT_LABELS[entry.project] || entry.project) + " " + esc(r.tag_name || "") + " " + esc(date) + "
" +
+ (r.name && r.name !== r.tag_name ? '' + esc(r.name) + "
" : "") +
+ (notes ? '' + esc(notes) + "
" : "") +
+ (url ? 'View on GitLab ' : "") +
+ " ";
+ }).join("");
+ });
+}
+
+function fetchContributors(project) {
+ var url = "https://gitlab.com/api/v4/projects/vyla-entertainment%2F" + encodeURIComponent(project) + "/repository/contributors?order_by=commits&sort=desc&per_page=100";
+ return fetch(url)
+ .then(function (res) {
+ if (!res.ok) throw new Error();
+ return res.json();
+ })
+ .then(function (data) { return Array.isArray(data) ? data : []; })
+ .catch(function () { return []; });
+}
+
+function renderContributors() {
+ var box = document.getElementById("contributors");
+ if (!box) return;
+ box.innerHTML = 'Loading contributors...
';
+ Promise.all(PROJECTS.map(fetchContributors)).then(function (groups) {
+ var totals = {};
+ [].concat.apply([], groups).forEach(function (c) {
+ var name = String(c.name || "").trim();
+ if (!name) return;
+ var key = name.toLowerCase();
+ if (!totals[key]) totals[key] = { name: name, commits: 0 };
+ totals[key].commits += Number(c.commits) || 0;
+ });
+ var people = Object.keys(totals).map(function (k) { return totals[k]; }).sort(function (a, b) { return b.commits - a.commits; });
+ if (!people.length) {
+ box.innerHTML = 'Contributors will show up here.
';
+ return;
+ }
+ box.innerHTML = people.map(function (p) {
+ var avatar;
+ if (p.name.toLowerCase() === "alwaysmesmerizingyou") {
+ avatar = ' ';
+ } else if (p.name.toLowerCase() === "zstrike") {
+ avatar = ' ';
+ } else {
+ avatar = '' + esc(p.name.charAt(0).toUpperCase()) + " ";
+ }
+ return '' + avatar + esc(p.name) + " " + p.commits + " ";
+ }).join("");
+ });
+}
+
+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 {
+ console.log("%cVyla", "font:700 22px Inter,sans-serif;color:#ff7518", "\nYou found the console. Vyla is built by its community. Come help: https://gitlab.com/vyla-entertainment/");
+ } 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 syncSettingsUI() {
+ var m = settingsEl("settings-modal");
+ if (!m) return;
+ settingsEl("set-name").value = getPref("name", "");
+ settingsEl("set-api").value = getPref("api", "");
+ settingsEl("set-api").classList.remove("bad");
+ setApiHint("");
+ var accent = String(getPref("accent", "") || "").toLowerCase();
+ var validAccent = /^#[0-9a-f]{6}$/.test(accent);
+ m.querySelectorAll(".st-swatch[data-color]").forEach(function (b) {
+ b.classList.toggle("on", validAccent && b.getAttribute("data-color") === accent);
+ });
+ m.querySelector(".st-swatch.reset").classList.toggle("on", !validAccent);
+ m.querySelector(".st-swatch.custom").classList.toggle("on", validAccent && ACCENT_PRESETS.indexOf(accent) === -1);
+ settingsEl("set-accent").value = validAccent ? accent : "#ffffff";
+ var size = String(parseInt(getPref("text", "100"), 10) || 100);
+ m.querySelectorAll(".st-seg button").forEach(function (b) {
+ b.classList.toggle("on", b.getAttribute("data-v") === size);
+ });
+ settingsEl("set-video").checked = getPref("video", "on") !== "off";
+ settingsEl("set-motion").checked = getPref("motion", "on") !== "off";
+ settingsEl("set-deco").checked = getPref("deco", "on") !== "off";
+}
+
+var saveTimer = null;
+
+function measureSettings() {
+ var m = settingsEl("settings-modal");
+ if (!m) return;
+ var panel = m.querySelector(".sheet-panel");
+ var top = m.querySelector(".st-top");
+ var bottom = m.querySelector(".st-bottom");
+ if (top) panel.style.setProperty("--st-top", Math.max(0, top.offsetHeight - 10) + "px");
+ if (bottom) panel.style.setProperty("--st-bottom", Math.max(0, bottom.offsetHeight - 10) + "px");
+}
+
+function openSettings() {
+ var m = settingsEl("settings-modal");
+ if (!m) return;
+ syncSettingsUI();
+ m.querySelector(".sheet-panel").style.transform = "";
+ m.classList.add("active");
+ measureSettings();
+ document.body.style.overflow = "hidden";
+}
+
+function closeSettings() {
+ var m = settingsEl("settings-modal");
+ if (!m || !m.classList.contains("active")) return;
+ m.querySelector(".sheet-panel").style.transform = "";
+ m.classList.remove("active");
+ document.body.style.overflow = "";
+}
+
+function buildSettings() {
+ var trigger = settingsEl("settings-trigger");
+ if (trigger) trigger.innerHTML = icon("sliders");
+ var modal = document.createElement("div");
+ modal.id = "settings-modal";
+ modal.className = "sheet-overlay settings-overlay";
+ var swatches = ACCENT_PRESETS.map(function (c) {
+ return ' ';
+ }).join("");
+ var sizes = TEXT_STEPS.map(function (s) {
+ return '' + s.l + " ";
+ }).join("");
+ modal.innerHTML =
+ '
' +
+ '' +
+ '
' +
+ '
Make it yours ' +
+ '
Saved only in this browser. Nothing leaves your device.
' +
+ '
" +
+ '
Reset Save preferences
' +
+ "
";
+ document.body.appendChild(modal);
+ refreshThemeUI();
+ measureSettings();
+ if (window.ResizeObserver) {
+ var settingsRO = new ResizeObserver(measureSettings);
+ settingsRO.observe(modal.querySelector(".st-top"));
+ settingsRO.observe(modal.querySelector(".st-bottom"));
+ }
+
+ var panel = modal.querySelector(".sheet-panel");
+ var handle = modal.querySelector(".st-top");
+
+ modal.addEventListener("click", function (e) {
+ if (e.target.closest(".sheet-backdrop")) {
+ closeSettings();
+ return;
+ }
+ var saveBtn = e.target.closest(".st-save");
+ if (saveBtn) {
+ var apiBox = settingsEl("set-api");
+ var apiRaw = apiBox.value.trim();
+ commitApi();
+ if (apiRaw && !cleanApiUrl(apiRaw)) {
+ apiBox.focus();
+ return;
+ }
+ saveBtn.textContent = "Saved";
+ saveBtn.classList.add("done");
+ clearTimeout(saveTimer);
+ saveTimer = setTimeout(function () {
+ closeSettings();
+ saveBtn.textContent = "Save preferences";
+ saveBtn.classList.remove("done");
+ }, 450);
+ return;
+ }
+ var tb = e.target.closest(".st-theme");
+ if (tb) {
+ setTheme(tb.getAttribute("data-mode"));
+ return;
+ }
+ var sw = e.target.closest(".st-swatch[data-color]");
+ if (sw) {
+ var color = sw.getAttribute("data-color");
+ setPref("accent", color);
+ applyAccent(color);
+ syncSettingsUI();
+ return;
+ }
+ if (e.target.closest(".st-swatch.reset")) {
+ setPref("accent", null);
+ applyAccent("");
+ syncSettingsUI();
+ return;
+ }
+ var seg = e.target.closest(".st-seg button");
+ if (seg) {
+ var v = seg.getAttribute("data-v");
+ setPref("text", v === "100" ? null : v);
+ applyPreferences();
+ syncSettingsUI();
+ return;
+ }
+ if (e.target.closest(".st-reset")) {
+ Object.keys(PREF_KEYS).forEach(function (k) { setPref(k, null); });
+ applyTheme();
+ syncSettingsUI();
+ refreshPersonalPages();
+ }
+ });
+
+ settingsEl("set-name").addEventListener("input", function () {
+ setPref("name", this.value.trim());
+ });
+
+ function commitApi() {
+ var input = settingsEl("set-api");
+ var raw = input.value.trim();
+ var clean = cleanApiUrl(raw);
+ if (raw && !clean) {
+ input.classList.add("bad");
+ setApiHint("Enter a full URL starting with http:// or https://", true);
+ return;
+ }
+ input.classList.remove("bad");
+ setPref("api", clean);
+ input.value = clean;
+ setApiHint(clean ? "Saved. Docs examples now use your server." : "");
+ refreshPersonalPages();
+ }
+
+ settingsEl("set-api").addEventListener("change", commitApi);
+ settingsEl("set-api").addEventListener("keydown", function (e) {
+ if (e.key === "Enter") commitApi();
+ });
+
+ settingsEl("set-accent").addEventListener("input", function () {
+ var color = this.value.toLowerCase();
+ setPref("accent", color);
+ applyAccent(color);
+ syncSettingsUI();
+ });
+
+ [["set-video", "video"], ["set-motion", "motion"], ["set-deco", "deco"]].forEach(function (pair) {
+ settingsEl(pair[0]).addEventListener("change", function () {
+ setPref(pair[1], this.checked ? null : "off");
+ applyTheme();
+ });
+ });
+
+ var drag = null;
+ handle.addEventListener("touchstart", function (e) {
+ if (window.innerWidth > 900) return;
+ drag = { y: e.touches[0].clientY, d: 0 };
+ panel.classList.add("dragging");
+ }, { passive: true });
+ window.addEventListener("touchmove", function (e) {
+ if (!drag) return;
+ drag.d = Math.max(0, e.touches[0].clientY - drag.y);
+ panel.style.transform = "translateY(" + drag.d + "px)";
+ }, { passive: true });
+ window.addEventListener("touchend", function () {
+ if (!drag) return;
+ var d = drag.d;
+ drag = null;
+ panel.classList.remove("dragging");
+ if (d > 110) closeSettings();
+ else panel.style.transform = "";
+ });
+
+ document.addEventListener("keydown", function (e) {
+ if (e.key === "Escape" && modal.classList.contains("active")) closeSettings();
+ });
+}
+
function parseMarkdown(md) {
var lines = md.replace(/\r\n/g, "\n").split("\n");
var out = [];
@@ -495,6 +1219,10 @@ function resolveRoute(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++) {
@@ -502,7 +1230,7 @@ function resolveRoute(pathname) {
for (var i = 0; i < grp.items.length; i++) {
var item = grp.items[i];
var itemPath = item.path.replace(/\/+$/, "") || "/";
- if (itemPath === path) {
+ if (itemPath.toLowerCase() === path.toLowerCase()) {
return { tab: tab, group: grp, item: item };
}
}
@@ -564,7 +1292,7 @@ function buildMobileBar(currentTab, currentItem) {
' ' +
'' +
'';
- headerContainer.appendChild(bar);
+ if (!document.body.classList.contains("is-home")) headerContainer.appendChild(bar);
var pageBtn = bar.querySelector("#mobile-crumb-page");
if (pageBtn) {
@@ -639,42 +1367,21 @@ function buildMobileBar(currentTab, currentItem) {
var sideLinks = document.createElement("div");
sideLinks.className = "side-links";
- var seg = document.createElement("div");
- seg.id = "theme-seg";
- var segHtml = '' + icon("dark") + ' ' +
- '' + icon("light") + ' ' +
- '' + icon("halloween") + ' ' +
- '';
- seg.innerHTML = segHtml;
-
- seg.addEventListener("click", function (e) {
- var moreBtn = e.target.closest("#theme-seg-more");
- var segMenu = document.getElementById("theme-seg-menu");
- if (moreBtn && segMenu) {
- segMenu.classList.toggle("open");
- return;
- }
- var btn = e.target.closest("button[data-mode]");
- if (btn) {
- setTheme(btn.getAttribute("data-mode"));
- if (segMenu) segMenu.classList.remove("open");
- }
+ 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(seg);
+ sideLinks.appendChild(gear);
head.appendChild(b);
head.appendChild(sideLinks);
sidebar.insertBefore(head, sidebar.firstChild);
- refreshThemeUI();
}
}
@@ -864,6 +1571,28 @@ function navigateTo(path) {
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 loadPage() {
var path = window.location.pathname.replace(/\/+$/, "") || "/";
var isHome = path === "/";
@@ -872,8 +1601,9 @@ function loadPage() {
var contentEl = document.getElementById("main-content");
if (isHome) {
- document.title = "Vyla Entertainment β Vyla";
+ 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");
@@ -884,6 +1614,7 @@ function loadPage() {
})
.then(function (html) {
contentEl.innerHTML = html;
+ mountGreeting(contentEl);
applyTheme();
window.scrollTo(0, 0);
})
@@ -912,7 +1643,7 @@ function loadPage() {
renderNavTabs(route.tab);
renderSidebar(route.tab, route.item);
- document.title = route.item.title + " β Vyla";
+ document.title = route.item.title + " - Vyla";
var contentEl = document.getElementById("main-content");
@@ -922,13 +1653,13 @@ function loadPage() {
renderNavTabs(route.tab);
renderSidebar(route.tab, route.item);
- document.title = route.item.title + " β Vyla";
+ 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";
+ document.title = "404 - Page Not Found - Vyla";
if (contentEl) {
contentEl.classList.remove("content-animate");
@@ -936,7 +1667,7 @@ function loadPage() {
'' +
'
404
' +
'
PAGE NOT FOUND
' +
- '
But if you don\'t change your direction, and if you keep looking, you may end up where you are heading.
' +
+ '
' + esc(notFoundCopy()) + '
' +
'
Take me home ' +
'
';
@@ -979,7 +1710,8 @@ function loadPage() {
})
.then(function (md) {
if (contentEl) {
- contentEl.innerHTML = parseMarkdown(md);
+ var hasApiToken = md.indexOf("YOUR_API_URL") !== -1;
+ contentEl.innerHTML = parseMarkdown(personalize(md));
if (window.Prism) {
window.Prism.highlightAllUnder(contentEl);
@@ -987,7 +1719,10 @@ function loadPage() {
var eyebrow = document.createElement("div");
eyebrow.className = "eyebrow";
- eyebrow.textContent = route.group.title;
+ 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);
@@ -1019,7 +1754,26 @@ function loadPage() {
addCopyButtons();
initCodeFades();
wrapTables();
- window.scrollTo(0, 0);
+ 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 () {
@@ -1035,8 +1789,102 @@ function loadPage() {
}
}
+var RELEASE_CACHE = {};
+
+function fetchProjectReleases(project) {
+ if (RELEASE_CACHE[project]) return Promise.resolve(RELEASE_CACHE[project]);
+ var url = "https://gitlab.com/api/v4/projects/vyla-entertainment%2F" + encodeURIComponent(project) + "/releases";
+ return fetch(url)
+ .then(function (res) {
+ if (!res.ok) throw new Error();
+ return res.json();
+ })
+ .then(function (data) {
+ RELEASE_CACHE[project] = Array.isArray(data) ? data : [];
+ return RELEASE_CACHE[project];
+ })
+ .catch(function () {
+ return [];
+ });
+}
+
+function findAssetInReleases(releases, ext) {
+ for (var r = 0; r < releases.length; r++) {
+ var rel = releases[r];
+ var tag = rel.tag_name || "";
+ var links = (rel.assets && rel.assets.links) || [];
+ for (var i = 0; i < links.length; i++) {
+ var l = links[i];
+ var name = (l.name || "").toLowerCase();
+ var url = (l.url || "").toLowerCase();
+ if (name.indexOf(ext) !== -1 || url.indexOf(ext) !== -1) {
+ return { tag: tag, url: l.direct_asset_url || l.url };
+ }
+ }
+ if (rel.description) {
+ var reg = new RegExp("\\[([^\\]]+)\\]\\((https?:[^)]+\\." + ext + "[^)]*)\\)", "i");
+ var match = rel.description.match(reg);
+ if (match) return { tag: tag, url: match[2] };
+ var rawMatch = rel.description.match(new RegExp("(https?:\\S+\\." + ext + ")", "i"));
+ if (rawMatch) return { tag: tag, url: rawMatch[1] };
+ }
+ }
+ return null;
+}
+
+function resolveLatestDownloads() {
+ var path = window.location.pathname;
+ var project = null;
+ if (path.indexOf("/products/home") !== -1) project = "home";
+ else if (path.indexOf("/products/go") !== -1) project = "go";
+ else if (path.indexOf("/products/tv") !== -1) project = "TV";
+
+ if (!project) return;
+
+ fetchProjectReleases(project).then(function (releases) {
+ markRecommended(project);
+ if (!releases.length) return;
+
+ var links = document.querySelectorAll("#main-content a");
+ links.forEach(function (a) {
+ var text = a.textContent.toLowerCase();
+ var href = a.getAttribute("href") || "";
+
+ if (project === "home") {
+ if (text.indexOf(".exe") !== -1 || text.indexOf("windows") !== -1) {
+ var matchExe = findAssetInReleases(releases, "exe");
+ if (matchExe) {
+ a.href = matchExe.url;
+ a.textContent = "Download: " + matchExe.tag + " (.exe)";
+ a.setAttribute("target", "_blank");
+ a.setAttribute("rel", "noopener");
+ }
+ } else if (text.indexOf(".dmg") !== -1 || text.indexOf("macos") !== -1) {
+ var matchDmg = findAssetInReleases(releases, "dmg");
+ if (matchDmg) {
+ a.href = matchDmg.url;
+ a.textContent = "Download: " + matchDmg.tag + " (.dmg)";
+ a.setAttribute("target", "_blank");
+ a.setAttribute("rel", "noopener");
+ }
+ }
+ } else if (project === "go" || project === "TV") {
+ if (text.indexOf(".apk") !== -1 || href.indexOf("releases") !== -1) {
+ var matchApk = findAssetInReleases(releases, "apk");
+ if (matchApk) {
+ a.href = matchApk.url;
+ a.textContent = "Download: " + matchApk.tag + " (.apk)";
+ a.setAttribute("target", "_blank");
+ a.setAttribute("rel", "noopener");
+ }
+ }
+ }
+ });
+ });
+}
+
function getRecent() {
- try { return JSON.parse(localStorage.getItem("vyla-recent-v2")) || []; } catch (e) { return []; }
+ try { return JSON.parse(localStorage.getItem("vyla-recent")) || []; } catch (e) { return []; }
}
function saveRecent(item) {
@@ -1044,7 +1892,7 @@ function saveRecent(item) {
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-v2", JSON.stringify(list.slice(0, 5)));
+ localStorage.setItem("vyla-recent", JSON.stringify(list.slice(0, 5)));
}
function loadSearchData() {
@@ -1104,18 +1952,26 @@ function renderSearch(keepActive) {
.slice(0, 10)
.map(function (r) { return r.it; });
} else {
- searchResults = getRecent();
+ 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" : "No recent searches") + "
";
+ box.innerHTML = '' + (tokens.length ? "No results found" : "Nothing saved or recent yet") + "
";
return;
}
- box.innerHTML = (tokens.length ? "" : 'Recent searches
') + searchResults.map(function (it, i) {
+ 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 '' +
- icon("doc") +
+ return label + ' ' +
+ icon(i < savedCount && !tokens.length ? "star" : "doc") +
'' + esc(it.title) + "
" +
'
' + crumbs + "
";
}).join("");
@@ -1183,6 +2039,230 @@ function buildSearch() {
document.addEventListener("keydown", onSearchKey);
}
+var lightboxScale = 1;
+var activeOriginImg = null;
+
+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 =
+ '
' +
+ '' +
+ '
' +
+ '
Help Add & Fix SDK Sources ' +
+ '
Streaming endpoints and scrapers constantly break as CDNs evolve. We are actively looking for contributors to help repair broken providers, add new video sources, and implement direct download scrapers.
' +
+ '
' +
+ '
';
+
+ 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");
@@ -1205,15 +2285,54 @@ document.addEventListener("DOMContentLoaded", function () {
applyTheme();
buildThemeControls();
buildSearch();
+ buildSettings();
+ initEasterEggs();
+ initContribSheet();
loadPage();
window.addEventListener("popstate", loadPage);
document.addEventListener("click", function (e) {
+ var img = e.target.closest("main.prose img");
+ if (img && !img.closest(".spider-body") && !img.closest(".cobweb")) {
+ e.preventDefault();
+ openLightbox(img);
+ return;
+ }
+
+ var heading = e.target.closest("main.prose h1, main.prose h2, main.prose h3, main.prose h4");
+ if (heading && heading.id) {
+ var header = document.getElementById("header-container") || document.getElementById("navbar");
+ var offset = header ? header.offsetHeight + 16 : 80;
+ var topPos = heading.getBoundingClientRect().top + window.pageYOffset - offset;
+ window.scrollTo({ top: Math.max(0, topPos), behavior: "smooth" });
+ window.history.pushState(null, "", "#" + heading.id);
+ return;
+ }
+
var a = e.target.closest("a");
if (a) {
var href = a.getAttribute("href");
if (href && href.startsWith("#")) {
+ var targetId = href.slice(1);
+ var targetEl = document.getElementById(targetId);
+ if (!targetEl) {
+ var headings = document.querySelectorAll("h1, h2, h3, h4");
+ for (var h = 0; h < headings.length; h++) {
+ if (headings[h].textContent.trim().toLowerCase() === targetId.replace(/-/g, " ").toLowerCase()) {
+ targetEl = headings[h];
+ break;
+ }
+ }
+ }
+ if (targetEl) {
+ e.preventDefault();
+ 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" });
+ window.history.pushState(null, "", "#" + (targetEl.id || targetId));
+ }
return;
}
if (href && href.startsWith("/") && !href.startsWith("//") && a.target !== "_blank") {
diff --git a/assets/styling/404.css b/assets/styling/404.css
index 2e614f2..17d48ee 100644
--- a/assets/styling/404.css
+++ b/assets/styling/404.css
@@ -33,7 +33,7 @@ body.is-404 .error-container {
body.is-404 .error-code {
margin: 0 0 16px;
- color: var(--text-heading);
+ color: var(--text);
font-size: clamp(74px, 14vw, 112px);
font-weight: 700;
line-height: 1;
@@ -42,7 +42,7 @@ body.is-404 .error-code {
body.is-404 .error-title {
margin: 0 0 24px;
- color: var(--text-heading);
+ color: var(--text);
font-size: 18px;
font-weight: 800;
letter-spacing: 0.12em;
diff --git a/assets/styling/components.css b/assets/styling/components.css
index 2abffd7..75a4861 100644
--- a/assets/styling/components.css
+++ b/assets/styling/components.css
@@ -29,145 +29,6 @@
color: inherit;
}
-#search-modal {
- position: fixed;
- inset: 0;
- z-index: 100;
- display: none;
-}
-
-#search-modal.open {
- display: block;
-}
-
-.search-panel {
- position: relative;
- width: min(640px, calc(100vw - 24px));
- margin: 52px auto 0;
- border-radius: 20px;
- overflow: hidden;
- background: var(--search-panel-bg) !important;
- border: 1px solid var(--border);
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
-}
-
-.search-input-row {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 18px 20px;
-}
-
-.search-input-row .ico {
- width: 18px;
- height: 18px;
- color: var(--muted);
-}
-
-#search-input {
- flex: 1;
- border: none;
- outline: none;
- background: transparent;
- color: var(--text-heading);
- font: inherit;
- font-size: 18px;
-}
-
-#search-results {
- max-height: 40vh;
- overflow-y: auto;
- padding: 0 6px 8px;
-}
-
-.search-label {
- padding: 8px 14px;
- font-size: 12px;
- color: var(--muted);
-}
-
-.sr {
- display: flex;
- align-items: flex-start;
- gap: 12px;
- padding: 8px 12px;
- border-radius: 12px;
-}
-
-.sr>.ico {
- margin-top: 3px;
- color: var(--muted);
-}
-
-.sr.on {
- background: var(--hover-bg);
-}
-
-.sr-body {
- min-width: 0;
-}
-
-.sr .t {
- font-size: 14px;
- font-weight: 600;
- line-height: 1.35;
- color: var(--text-heading);
-}
-
-.sr .s {
- display: -webkit-box;
- margin-top: 2px;
- font-size: 12.5px;
- line-height: 1.4;
- color: var(--muted);
- overflow: hidden;
- -webkit-line-clamp: 2;
- line-clamp: 2;
- -webkit-box-orient: vertical;
-}
-
-.sr .p {
- margin-top: 2px;
- font-size: 12px;
- line-height: 1.35;
- color: var(--muted);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.sr .sep {
- margin: 0 6px;
- opacity: 0.7;
-}
-
-.sr .hit {
- font-weight: 700;
- color: var(--text-heading);
-}
-
-.search-footer {
- display: flex;
- gap: 18px;
- padding: 12px 20px;
- font-size: 12px;
- color: var(--muted);
- border-top: 1px solid var(--border-subtle);
-}
-
-.search-footer .close-hint {
- margin-left: auto;
-}
-
-.search-footer kbd {
- margin-right: 4px;
- padding: 1px 5px;
- border-radius: 4px;
- font: 11px var(--font);
-}
-
.code-wrap {
position: relative;
}
@@ -254,7 +115,7 @@
}
.cg-copy-btn:hover {
- color: var(--text-heading);
+ color: var(--text);
}
.code-group-panel {
@@ -456,13 +317,11 @@ main.prose blockquote p {
#search-trigger kbd {
display: none;
}
+}
- .search-panel {
- margin-top: 50px;
- border-radius: 30px;
- }
-
- .search-footer {
- display: none;
- }
+main.prose h1,
+main.prose h2,
+main.prose h3,
+main.prose h4 {
+ cursor: pointer;
}
\ No newline at end of file
diff --git a/assets/styling/features.css b/assets/styling/features.css
new file mode 100644
index 0000000..744091c
--- /dev/null
+++ b/assets/styling/features.css
@@ -0,0 +1,262 @@
+.bm-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 30px;
+ height: 30px;
+ padding: 0;
+ border: none;
+ border-radius: 50%;
+ background: none;
+ color: var(--muted);
+ cursor: pointer;
+ transition: color 0.15s ease, background 0.15s ease;
+}
+
+.bm-btn .ico {
+ width: 18px;
+ height: 18px;
+}
+
+.bm-btn:hover {
+ background: var(--hover-bg);
+ color: var(--text);
+}
+
+.bm-btn.on {
+ color: var(--text);
+}
+
+.bm-btn.on .ico {
+ fill: currentColor;
+}
+
+.personal-card {
+ margin: 18px 0 8px;
+ padding: 16px 18px;
+ border-radius: 22px;
+ background: var(--callout-note-bg);
+}
+
+.personal-card label {
+ display: block;
+ margin-bottom: 10px;
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--text);
+}
+
+.pc-row {
+ display: flex;
+ gap: 8px;
+}
+
+.pc-row input {
+ flex: 1;
+ min-width: 0;
+ height: 36px;
+ padding: 0 14px;
+ border: 1px solid var(--border);
+ border-radius: 100px;
+ background: var(--search-bg);
+ color: var(--text);
+ font: inherit;
+ font-size: 13.5px;
+ outline: none;
+}
+
+.pc-row input.bad {
+ border-color: #ef4444;
+}
+
+.pc-btn {
+ height: 36px;
+ padding: 0 18px;
+ border: none;
+ border-radius: 100px;
+ background: var(--btn-primary-bg);
+ color: var(--btn-primary-text);
+ font: inherit;
+ font-size: 13.5px;
+ font-weight: 600;
+ cursor: pointer;
+}
+
+main.prose .pc-hint {
+ margin: 8px 0 0;
+ font-size: 12.5px;
+ line-height: 1.5;
+ color: var(--muted);
+}
+
+.rec-badge {
+ display: inline-flex;
+ align-items: center;
+ margin-left: 8px;
+ padding: 2px 10px;
+ border-radius: 100px;
+ background: var(--btn-primary-bg);
+ color: var(--btn-primary-text);
+ font-size: 11.5px;
+ font-weight: 700;
+ letter-spacing: 0.02em;
+ vertical-align: middle;
+ white-space: nowrap;
+}
+
+tbody tr.rec-row td {
+ background: var(--hover-bg);
+}
+
+tbody tr.rec-row td:first-child {
+ padding-left: 12px;
+ border-top-left-radius: 12px;
+ border-bottom-left-radius: 12px;
+}
+
+tbody tr.rec-row td:last-child {
+ padding-right: 12px;
+ border-top-right-radius: 12px;
+ border-bottom-right-radius: 12px;
+}
+
+#footer-note {
+ margin: 0 0 16px;
+ font-size: 13.5px;
+ color: var(--muted);
+}
+
+.changelog,
+.contributors {
+ margin: 20px 0;
+}
+
+.contributors {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+main.prose .muted-line {
+ margin: 0;
+ color: var(--muted);
+}
+
+.cl-item {
+ padding: 16px 0;
+ border-bottom: 1px solid var(--border-subtle);
+}
+
+.cl-meta {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 10px;
+ font-size: 12.5px;
+ color: var(--muted);
+}
+
+.cl-tag {
+ padding: 2px 10px;
+ border-radius: 100px;
+ background: var(--hover-bg);
+ color: var(--text);
+ font-weight: 600;
+}
+
+.cl-title {
+ margin-top: 8px;
+ font-size: 16px;
+ font-weight: 700;
+ color: var(--text);
+}
+
+main.prose .cl-notes {
+ margin: 6px 0 0;
+ font-size: 14px;
+}
+
+.cl-link {
+ display: inline-block;
+ margin-top: 8px;
+ font-size: 13px;
+}
+
+.ct-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 0px 14px 0px 5px;
+ border-radius: 100px;
+ background: var(--hover-bg);
+ color: var(--text);
+ ;
+ font-size: 13.5px;
+ margin-right: 10px;
+ height: 40px;
+ align-items: center;
+}
+
+.ct-chip em {
+ font-style: normal;
+ font-size: 12px;
+ color: var(--muted);
+}
+
+.ct-avatar {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background: var(--btn-primary-bg);
+ color: var(--btn-primary-text);
+ font-size: 12px;
+ font-weight: 700;
+}
+
+.ct-avatar-img {
+ width: 26px;
+ height: 26px;
+}
+
+.confetti-canvas {
+ position: fixed;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 3000;
+}
+
+body:not(.is-home) main.prose {
+ zoom: var(--text-scale, 1);
+}
+
+body.no-deco .cobweb,
+body.no-deco .spider-container,
+body.no-deco .snowfall,
+body.no-deco .leaf-fall,
+body.no-deco .light-string {
+ display: none !important;
+}
+
+body.no-video .video-bg,
+body.no-video .body-blurred-video,
+body.no-video .header-video {
+ display: none !important;
+}
+
+body.no-motion *,
+body.no-motion *::before,
+body.no-motion *::after {
+ animation: none !important;
+ transition: none !important;
+ scroll-behavior: auto !important;
+}
+
+@media (max-width: 900px) {
+ .rec-badge {
+ margin-left: 0 !important;
+ margin-top: 10px !important;
+ }
+}
\ No newline at end of file
diff --git a/assets/styling/layout.css b/assets/styling/layout.css
index 556a2c3..fdead95 100644
--- a/assets/styling/layout.css
+++ b/assets/styling/layout.css
@@ -6,6 +6,7 @@
z-index: 41;
backdrop-filter: blur(10px) saturate(1.2);
-webkit-backdrop-filter: blur(10px) saturate(1.2);
+ background: var(--bg-card);
width: 50%;
border-radius: 30px;
}
@@ -99,11 +100,11 @@
}
#navbar .tabs a:hover {
- color: var(--text-heading);
+ color: var(--text);
}
#navbar .tabs a.active {
- color: var(--text-heading);
+ color: var(--text);
font-weight: 600;
}
@@ -155,19 +156,19 @@
}
#sidebar li a.active {
- color: var(--text-heading);
+ color: var(--text);
}
#sidebar li a:hover {
background: var(--hover-bg);
- color: var(--text-heading);
+ color: var(--text);
}
#content-col {
flex: 1;
min-width: 0;
display: flex;
- padding: 16px 20px 20px;
+ padding: 16px max(20px, env(safe-area-inset-right)) 20px max(20px, env(safe-area-inset-left));
gap: 92px;
max-width: 100%;
box-sizing: border-box;
@@ -178,7 +179,7 @@
flex: 1;
min-width: 0;
max-width: 720px;
- margin-top: 130px;
+ margin-top: 120px;
border-radius: 12px;
width: 100%;
box-sizing: border-box;
@@ -213,7 +214,7 @@ footer#site-footer {
gap: 8px;
font-size: 14px;
font-weight: 600;
- color: var(--text-heading);
+ color: var(--text);
transition: opacity 0.15s ease;
}
@@ -248,7 +249,7 @@ footer#site-footer {
}
.footer-socials a:hover {
- color: var(--text-heading);
+ color: var(--text);
}
.footer-icon {
@@ -361,6 +362,7 @@ footer#site-footer {
border-radius: 10px;
background: none;
color: var(--text);
+ ;
font: inherit;
font-size: 13.5px;
cursor: pointer;
@@ -397,11 +399,11 @@ footer#site-footer {
#theme-menu button.on {
font-weight: 600;
- color: var(--text-heading);
+ color: var(--text);
}
#theme-menu button.on .ico {
- color: var(--text-heading);
+ color: var(--text);
}
#theme-menu button.on .check {
@@ -479,6 +481,7 @@ footer#site-footer {
border-radius: 8px;
background: none;
color: var(--text);
+ ;
font: inherit;
font-size: 13.5px;
cursor: pointer;
@@ -505,15 +508,16 @@ footer#site-footer {
overflow: hidden;
text-overflow: ellipsis;
color: var(--text);
+ ;
}
#theme-seg-menu button.on span {
- color: var(--text-heading);
+ color: var(--text);
font-weight: 600;
}
#theme-seg-menu button.on .ico {
- color: var(--text-heading);
+ color: var(--text);
}
#theme-seg-menu button .check {
@@ -526,7 +530,7 @@ footer#site-footer {
#theme-seg-menu button.on .check {
visibility: visible;
- color: var(--text-heading);
+ color: var(--text);
}
#theme-seg-menu button:hover {
@@ -535,7 +539,7 @@ footer#site-footer {
#theme-seg-menu button.on {
font-weight: 600;
- color: var(--text-heading);
+ color: var(--text);
}
#theme-seg-menu .check {
@@ -564,7 +568,7 @@ footer#site-footer {
#theme-seg>button.on {
background: var(--hover-bg);
- color: var(--text-heading);
+ color: var(--text);
}
#mobile-bar {
@@ -645,7 +649,7 @@ footer#site-footer {
}
.mobile-crumbs .crumb-heading {
- color: var(--text-heading);
+ color: var(--text);
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
@@ -667,7 +671,7 @@ footer#site-footer {
border: none;
background: var(--hover-bg);
border-radius: 100px;
- color: var(--text-heading);
+ color: var(--text);
font: inherit;
font-size: 15px;
cursor: pointer;
@@ -728,11 +732,11 @@ footer#site-footer {
.mt-menu .mt-item:hover {
background: var(--hover-bg);
- color: var(--text-heading);
+ color: var(--text);
}
.mt-menu .mt-item.on {
- color: var(--text-heading);
+ color: var(--text);
font-weight: 600;
}
@@ -754,12 +758,6 @@ footer#site-footer {
display: none;
}
-body.is-home #sidebar,
-body.is-home #toc,
-body.is-home #mobile-bar {
- display: none !important;
-}
-
body.is-home #layout {
display: block;
width: 100%;
@@ -805,14 +803,23 @@ body.is-home main.prose {
align-items: center;
}
+body.is-home #toc,
+body.is-home #sidebar,
+body.is-home #mobile-bar {
+ display: none !important;
+}
+
@media (max-width: 900px) {
:root {
--header-height: 60px;
}
#header-container {
- width: 91%;
- border-radius: 25px;
+ width: 90%;
+ border-radius: 30px;
+ backdrop-filter: blur(15px) saturate(1.2);
+ -webkit-backdrop-filter: blur(15px) saturate(1.2);
+ top: max(6px, env(safe-area-inset-top));
}
#navbar .tabs,
@@ -865,7 +872,7 @@ body.is-home main.prose {
}
#sidebar #theme-seg>button.on {
- color: var(--text-heading);
+ color: var(--text);
font-weight: 900;
}
@@ -939,6 +946,7 @@ body.is-home main.prose {
height: 60px;
margin: 0 -16px 12px;
padding: 0 16px;
+ margin-top: max(6px, env(safe-area-inset-top));
}
#sidebar .side-head .brand {
@@ -958,4 +966,44 @@ body.is-home main.prose {
align-items: center;
margin-right: 4px;
}
+}
+
+@media (orientation: landscape) and (max-height: 550px) {
+ :root {
+ --header-height: 52px;
+ }
+
+ #header-container {
+ top: max(6px, env(safe-area-inset-top));
+ width: min(600px, 88vw);
+ border-radius: 20px;
+ }
+
+ #navbar .nav-top {
+ height: 46px;
+ }
+
+ #navbar .tabs {
+ height: 38px;
+ }
+
+ #navbar .tabs a {
+ padding: 4px 10px;
+ font-size: 13px;
+ }
+
+ #main-wrap {
+ margin-top: 100px;
+ }
+
+ #sidebar {
+ top: calc(var(--header-height) + 16px);
+ max-height: calc(100vh - var(--header-height) - 16px);
+ margin-top: 10px;
+ }
+
+ #mobile-bar {
+ padding: 6px 14px;
+ font-size: 13px;
+ }
}
\ No newline at end of file
diff --git a/assets/styling/overlay.css b/assets/styling/overlay.css
new file mode 100644
index 0000000..956a120
--- /dev/null
+++ b/assets/styling/overlay.css
@@ -0,0 +1,858 @@
+#search-modal {
+ position: fixed;
+ inset: 0;
+ z-index: 100;
+ display: none;
+}
+
+#search-modal.open {
+ display: block;
+}
+
+.search-panel {
+ position: relative;
+ width: min(640px, calc(100vw - 24px));
+ margin: 52px auto 0;
+ border-radius: 20px;
+ overflow: hidden;
+ background: var(--search-panel-bg) !important;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+}
+
+.search-input-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 18px 20px;
+}
+
+.search-input-row .ico {
+ width: 18px;
+ height: 18px;
+ color: var(--muted);
+}
+
+#search-input {
+ flex: 1;
+ border: none;
+ outline: none;
+ background: transparent;
+ color: var(--text);
+ font: inherit;
+ font-size: 18px;
+}
+
+#search-results {
+ max-height: 40vh;
+ overflow-y: auto;
+ padding: 0 6px 8px;
+}
+
+.search-label {
+ padding: 8px 14px;
+ font-size: 12px;
+ color: var(--muted);
+}
+
+.sr {
+ display: flex;
+ align-items: flex-start;
+ gap: 12px;
+ padding: 8px 12px;
+ border-radius: 12px;
+}
+
+.sr>.ico {
+ margin-top: 3px;
+ color: var(--muted);
+}
+
+.sr.on {
+ background: var(--hover-bg);
+}
+
+.sr-body {
+ min-width: 0;
+}
+
+.sr .t {
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 1.35;
+ color: var(--text);
+}
+
+.sr .s {
+ display: -webkit-box;
+ margin-top: 2px;
+ font-size: 12.5px;
+ line-height: 1.4;
+ color: var(--muted);
+ overflow: hidden;
+ -webkit-line-clamp: 2;
+ line-clamp: 2;
+ -webkit-box-orient: vertical;
+}
+
+.sr .p {
+ margin-top: 2px;
+ font-size: 12px;
+ line-height: 1.35;
+ color: var(--muted);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.sr .sep {
+ margin: 0 6px;
+ opacity: 0.7;
+}
+
+.sr .hit {
+ font-weight: 700;
+ color: var(--text);
+}
+
+.search-footer {
+ display: flex;
+ gap: 18px;
+ padding: 12px 20px;
+ font-size: 12px;
+ color: var(--muted);
+ border-top: 1px solid var(--border-subtle);
+}
+
+.search-footer .close-hint {
+ margin-left: auto;
+}
+
+.search-footer kbd {
+ margin-right: 4px;
+ padding: 1px 5px;
+ border-radius: 4px;
+ font: 11px var(--font);
+}
+
+@media (max-width: 900px) {
+ .search-panel {
+ margin-top: 50px;
+ border-radius: 30px;
+ }
+
+ .search-footer {
+ display: none;
+ }
+}
+
+.sheet-overlay {
+ position: fixed;
+ inset: 0;
+ z-index: 1000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.35s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.sheet-overlay.active {
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.sheet-backdrop {
+ position: absolute;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.65);
+ backdrop-filter: blur(8px);
+ -webkit-backdrop-filter: blur(8px);
+}
+
+.sheet-panel {
+ position: relative;
+ width: min(500px, calc(100vw - 32px));
+ background: var(--bg-modal);
+ border-radius: 28px;
+ backdrop-filter: blur(24px);
+ -webkit-backdrop-filter: blur(24px);
+ padding: 32px 28px 28px;
+ box-sizing: border-box;
+ transform: scale(0.92) translateY(16px);
+ transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
+ display: flex;
+ flex-direction: column;
+}
+
+.sheet-overlay.active .sheet-panel {
+ transform: scale(1) translateY(0);
+}
+
+.sheet-handle-bar {
+ display: none;
+}
+
+.sheet-title {
+ font-size: 22px;
+ font-weight: 700;
+ color: var(--text);
+ margin: 0 0 10px;
+ line-height: 1.3;
+ text-align: center;
+}
+
+.sheet-desc {
+ font-size: 14.5px;
+ color: var(--muted);
+ line-height: 1.6;
+ margin: 0 0 24px;
+ text-align: center;
+}
+
+.sheet-actions {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ justify-content: center;
+}
+
+.sheet-btn-primary {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 10px 22px;
+ border-radius: 100px;
+ background: var(--btn-primary-bg);
+ color: var(--btn-primary-text) !important;
+ font-size: 14px;
+ font-weight: 600;
+ text-decoration: none !important;
+ transition: transform 0.15s ease, opacity 0.15s ease;
+}
+
+.sheet-btn-primary:hover {
+ opacity: 0.9;
+ transform: translateY(-1px);
+}
+
+.sheet-btn-never {
+ padding: 10px 18px;
+ border: none;
+ background: none;
+ color: var(--muted);
+ font: inherit;
+ font-size: 13.5px;
+ font-weight: 500;
+ cursor: pointer;
+ border-radius: 100px;
+ transition: color 0.15s ease, background 0.15s ease;
+}
+
+.sheet-btn-never:hover {
+ color: var(--text);
+ background: var(--hover-bg);
+}
+
+@media (max-width: 900px) {
+ .sheet-overlay {
+ align-items: flex-end;
+ }
+
+ .sheet-panel {
+ width: 100%;
+ max-width: 100%;
+ border-radius: 28px 28px 0 0;
+ border-bottom: none;
+ padding: 14px 22px 32px;
+ transform: translateY(100%);
+ transition: transform 0.45s cubic-bezier(0.32, 0.72, 0, 1);
+ touch-action: none;
+ max-height: 90vh;
+ }
+
+ .sheet-panel.dragging {
+ transition: none !important;
+ }
+
+ .sheet-overlay.active .sheet-panel {
+ transform: translateY(0);
+ }
+
+ .sheet-handle-bar {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ padding: 6px 0 16px;
+ cursor: grab;
+ }
+
+ .sheet-handle {
+ width: 40px;
+ height: 5px;
+ border-radius: 999px;
+ background: var(--muted);
+ opacity: 0.45;
+ }
+
+ .sheet-actions {
+ flex-direction: column;
+ width: 100%;
+ gap: 8px;
+ }
+
+ .sheet-btn-primary {
+ width: 100%;
+ }
+
+ .sheet-btn-never {
+ width: 100%;
+ text-align: center;
+ }
+}
+
+@media (orientation: landscape) and (max-height: 550px) {
+ .sheet-panel {
+ max-height: 94vh;
+ overflow-y: auto;
+ padding: 10px max(24px, env(safe-area-inset-right)) 16px max(24px, env(safe-area-inset-left));
+ }
+
+ .sheet-title {
+ font-size: 18px;
+ margin-bottom: 6px;
+ }
+
+ .sheet-desc {
+ font-size: 13px;
+ margin-bottom: 12px;
+ line-height: 1.45;
+ }
+
+ .sheet-actions {
+ flex-direction: row;
+ gap: 10px;
+ }
+
+ .sheet-btn-primary,
+ .sheet-btn-never {
+ width: auto;
+ padding: 8px 18px;
+ font-size: 13px;
+ }
+}
+
+.image-lightbox {
+ position: fixed;
+ inset: 0;
+ z-index: 2000;
+ background: transparent;
+ backdrop-filter: blur(0px);
+ -webkit-backdrop-filter: blur(0px);
+ opacity: 0;
+ pointer-events: none;
+ transition: background 0.42s cubic-bezier(0.2, 0.9, 0.3, 1),
+ backdrop-filter 0.42s cubic-bezier(0.2, 0.9, 0.3, 1),
+ opacity 0.25s ease;
+}
+
+.image-lightbox.active {
+ background: var(--bg-modal);
+ backdrop-filter: blur(24px);
+ -webkit-backdrop-filter: blur(24px);
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.lightbox-controls {
+ position: fixed;
+ top: max(16px, env(safe-area-inset-top));
+ right: max(16px, env(safe-area-inset-right));
+ display: flex;
+ align-items: center;
+ gap: 3px;
+ z-index: 2003;
+ background: var(--bg-menu);
+ border-radius: 100px;
+ padding: 3px 6px;
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
+ backdrop-filter: blur(16px);
+ -webkit-backdrop-filter: blur(16px);
+ opacity: 0;
+ transform: translateY(-8px) scale(0.96);
+ transition: opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1) 0.08s,
+ transform 0.3s cubic-bezier(0.16, 1, 0.3, 1) 0.08s;
+}
+
+.image-lightbox.active .lightbox-controls {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+}
+
+.lightbox-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ border: none;
+ border-radius: 50%;
+ background: transparent;
+ color: var(--muted);
+ font-family: var(--font);
+ font-size: 16px;
+ line-height: 1;
+ cursor: pointer;
+ transition: background 0.15s ease, color 0.15s ease;
+}
+
+.lightbox-btn:hover {
+ background: var(--hover-bg);
+ color: var(--text);
+}
+
+.lightbox-btn svg {
+ display: block;
+ width: 16px;
+ height: 16px;
+ fill: currentColor;
+ flex-shrink: 0;
+}
+
+.lightbox-img {
+ position: fixed;
+ object-fit: contain;
+ border-radius: 16px;
+ transition: top 0.44s cubic-bezier(0.2, 0.9, 0.3, 1),
+ left 0.44s cubic-bezier(0.2, 0.9, 0.3, 1),
+ width 0.44s cubic-bezier(0.2, 0.9, 0.3, 1),
+ height 0.44s cubic-bezier(0.2, 0.9, 0.3, 1),
+ border-radius 0.44s cubic-bezier(0.2, 0.9, 0.3, 1),
+ transform 0.25s ease;
+ z-index: 2002;
+ transform-origin: center center;
+ will-change: top, left, width, height, transform;
+}
+
+main.prose img {
+ cursor: zoom-in;
+ transition: opacity 0.2s ease, transform 0.2s ease;
+}
+
+main.prose img:hover {
+ opacity: 0.95;
+}
+
+#settings-trigger,
+.side-settings {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 36px;
+ height: 36px;
+ padding: 0;
+ border: none;
+ border-radius: 10px;
+ background: none;
+ color: var(--muted);
+ cursor: pointer;
+}
+
+#settings-trigger .ico,
+.side-settings .ico {
+ width: 18px;
+ height: 18px;
+}
+
+#settings-trigger:hover,
+.side-settings:hover {
+ background: var(--hover-bg);
+ color: var(--text);
+}
+
+.settings-panel {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ padding: 0;
+ overflow: hidden;
+}
+
+.st-top,
+.st-bottom {
+ position: absolute;
+ left: 0;
+ right: 0;
+ z-index: 3;
+ isolation: isolate;
+}
+
+.st-top::before,
+.st-bottom::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ z-index: -1;
+ pointer-events: none;
+ backdrop-filter: blur(18px) saturate(1.2);
+ -webkit-backdrop-filter: blur(18px) saturate(1.2);
+}
+
+.st-top {
+ top: 0;
+ padding: 26px 28px 30px;
+}
+
+.st-top::before {
+ background: linear-gradient(to bottom, var(--bg-menu) 40%, transparent);
+ background: linear-gradient(to bottom, color-mix(in srgb, var(--bg-menu) 94%, transparent), color-mix(in srgb, var(--bg-menu) 80%, transparent) 60%, transparent);
+ -webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent);
+ mask-image: linear-gradient(to bottom, #000 55%, transparent);
+}
+
+.st-top .sheet-title {
+ margin: 0 0 6px;
+ font-size: 20px;
+}
+
+.st-top .sheet-desc {
+ margin: 0;
+ font-size: 13.5px;
+ line-height: 1.5;
+}
+
+.st-bottom {
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 10px;
+ padding: 36px 28px calc(20px + env(safe-area-inset-bottom));
+}
+
+.st-bottom::before {
+ background: linear-gradient(to top, var(--bg-menu) 40%, transparent);
+ background: linear-gradient(to top, color-mix(in srgb, var(--bg-menu) 94%, transparent), color-mix(in srgb, var(--bg-menu) 80%, transparent) 60%, transparent);
+ -webkit-mask-image: linear-gradient(to top, #000 55%, transparent);
+ mask-image: linear-gradient(to top, #000 55%, transparent);
+}
+
+.st-bottom .sheet-btn-primary {
+ border: none;
+ font-family: inherit;
+ cursor: pointer;
+}
+
+.st-bottom .sheet-btn-primary.done {
+ opacity: 0.85;
+}
+
+.st-scroll {
+ flex: 1;
+ min-height: 0;
+ padding: var(--st-top, 110px) 28px var(--st-bottom, 84px);
+ overflow-y: auto;
+ overscroll-behavior: contain;
+ touch-action: pan-y;
+ scrollbar-width: thin;
+ scrollbar-color: var(--border) transparent;
+}
+
+.st-group {
+ margin-bottom: 18px;
+}
+
+.st-heading {
+ margin: 0 0 8px 6px;
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--muted);
+}
+
+.st-card {
+ border-radius: 20px;
+ background: var(--hover-bg);
+ overflow: hidden;
+}
+
+.st-row,
+.st-field {
+ display: flex;
+ gap: 14px;
+ padding: 14px 16px;
+ border-top: 1px solid var(--border-subtle);
+ cursor: default;
+}
+
+.st-card>:first-child {
+ border-top: none;
+}
+
+.st-row {
+ align-items: center;
+ justify-content: space-between;
+}
+
+.st-row.stack {
+ flex-direction: column;
+ align-items: stretch;
+}
+
+label.st-row {
+ cursor: pointer;
+}
+
+.st-field {
+ flex-direction: column;
+ align-items: stretch;
+ gap: 8px;
+}
+
+.st-text {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ min-width: 0;
+}
+
+.st-label {
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 1.35;
+ color: var(--text);
+}
+
+.st-hint {
+ font-size: 12.5px;
+ line-height: 1.45;
+ color: var(--muted);
+}
+
+.st-hint.err {
+ color: #ef4444;
+}
+
+.st-input {
+ width: 100%;
+ height: 40px;
+ padding: 0 16px;
+ border: none;
+ border-radius: 100px;
+ background: var(--search-bg);
+ color: var(--text);
+ font: inherit;
+ font-size: 14px;
+ outline: none;
+ transition: border-color 0.15s ease;
+}
+
+.st-input::placeholder {
+ color: var(--muted);
+ opacity: 0.8;
+}
+
+.st-input:focus {
+ border-color: var(--text);
+}
+
+.st-input.bad {
+ border-color: #ef4444;
+}
+
+.st-swatches {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 12px;
+}
+
+.st-swatch {
+ display: block;
+ position: relative;
+ flex-shrink: 0;
+ width: 26px;
+ height: 26px;
+ padding: 0;
+ border: none;
+ border-radius: 50%;
+ background: var(--sw, var(--hover-bg));
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
+ cursor: pointer;
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
+}
+
+.st-swatch:hover {
+ transform: scale(1.12);
+}
+
+.st-swatch.on {
+ box-shadow: 0 0 0 2px var(--bg-menu), 0 0 0 4px var(--text);
+ ;
+}
+
+.st-swatch.reset {
+ background: linear-gradient(135deg, transparent 46%, var(--muted) 46%, var(--muted) 54%, transparent 54%), var(--search-bg);
+ box-shadow: inset 0 0 0 1.5px var(--muted);
+}
+
+.st-swatch.reset.on {
+ box-shadow: inset 0 0 0 1.5px var(--muted), 0 0 0 2px var(--bg-menu), 0 0 0 4px var(--text);
+ ;
+}
+
+.st-swatch.custom {
+ background: conic-gradient(from 0deg, #ff5c5c, #f4b942, #5fd68f, #8ad1ff, #c084fc, #ff5c5c);
+}
+
+.st-swatch.custom input {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ border: none;
+ opacity: 0;
+ cursor: pointer;
+}
+
+.st-seg {
+ display: flex;
+ gap: 2px;
+ padding: 3px;
+ border-radius: 100px;
+ background: var(--search-bg);
+}
+
+.st-seg button {
+ flex: 1;
+ height: 32px;
+ padding: 0 10px;
+ border: none;
+ border-radius: 100px;
+ background: none;
+ color: var(--muted);
+ font: inherit;
+ font-size: 13px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: background 0.15s ease, color 0.15s ease;
+}
+
+.st-seg button:hover {
+ color: var(--text);
+}
+
+.st-seg button.on {
+ background: var(--btn-primary-bg);
+ color: var(--btn-primary-text);
+}
+
+.st-switch {
+ position: relative;
+ flex-shrink: 0;
+ width: 44px;
+ height: 26px;
+}
+
+.st-switch input {
+ position: absolute;
+ inset: 0;
+ z-index: 1;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ opacity: 0;
+ cursor: pointer;
+}
+
+.st-track {
+ position: absolute;
+ inset: 0;
+ border: 1px solid var(--border);
+ border-radius: 100px;
+ background: var(--search-bg);
+ transition: background 0.2s ease, border-color 0.2s ease;
+}
+
+.st-track::after {
+ content: "";
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ background: var(--muted);
+ transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1), background 0.2s ease;
+}
+
+.st-switch input:checked+.st-track {
+ border-color: transparent;
+ background: var(--btn-primary-bg);
+}
+
+.st-switch input:checked+.st-track::after {
+ transform: translateX(18px);
+ background: var(--btn-primary-text);
+}
+
+.st-switch input:focus-visible+.st-track {
+ outline: 2px solid var(--text);
+ outline-offset: 2px;
+}
+
+@media (min-width: 901px) {
+ .settings-panel {
+ width: min(540px, calc(100vw - 32px));
+ max-height: min(88vh, 780px);
+ }
+}
+
+@media (max-width: 900px) {
+ .st-top {
+ padding: 8px 22px 28px;
+ }
+
+ .st-scroll {
+ padding-left: 22px;
+ padding-right: 22px;
+ }
+
+ .st-bottom {
+ padding: 36px 22px calc(18px + env(safe-area-inset-bottom));
+ }
+
+ .st-bottom .sheet-btn-primary {
+ flex: 1;
+ width: auto;
+ }
+
+ .st-bottom .sheet-btn-never {
+ flex: 0 0 auto;
+ width: auto;
+ }
+}
+
+@media (orientation: landscape) and (max-height: 550px) {
+ .settings-panel {
+ max-height: 94vh;
+ }
+
+ .st-top {
+ padding: 14px 24px 22px;
+ }
+
+ .st-top .sheet-title {
+ font-size: 17px;
+ margin-bottom: 0;
+ }
+
+ .st-top .sheet-desc {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/assets/styling/prose.css b/assets/styling/prose.css
index df87ac4..da260b5 100644
--- a/assets/styling/prose.css
+++ b/assets/styling/prose.css
@@ -11,6 +11,10 @@ main.prose .eyebrow {
font-weight: 600;
margin: 0 0 8px;
color: var(--text);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
}
main.prose h1 {
@@ -18,7 +22,7 @@ main.prose h1 {
font-weight: 700;
letter-spacing: 0;
margin: 0 0 6px;
- color: var(--text-heading);
+ color: var(--text);
}
main.prose .page-description {
@@ -48,21 +52,22 @@ main.prose h3 {
main.prose h2,
main.prose h3 {
scroll-margin-top: 130px;
- color: var(--text-heading);
+ color: var(--text);
}
main.prose p,
main.prose li {
- color: var(--text);
+ color: var(--muted);
line-height: 1.75;
}
main.prose strong {
- color: var(--text-heading);
+ color: var(--text);
}
main.prose a {
color: var(--text);
+ ;
font-weight: 600;
text-decoration: underline;
}
@@ -93,7 +98,7 @@ main.prose hr {
main.prose code {
background: var(--code-inline-bg);
- color: var(--code-inline-text);
+ color: var(--text);
font-family: var(--mono);
font-size: 13px;
padding: 2px 6px;
@@ -162,6 +167,7 @@ td {
vertical-align: top;
border-bottom: 1px solid var(--border-subtle);
color: var(--text);
+ ;
}
tbody tr:last-child td {
@@ -246,7 +252,7 @@ aside#toc .toc-title {
font-size: 14px;
font-weight: 600;
line-height: 20px;
- color: var(--text-heading);
+ color: var(--text);
margin-bottom: 2px;
}
@@ -274,11 +280,11 @@ aside#toc li a {
}
aside#toc li a:hover {
- color: var(--text-heading);
+ color: var(--text);
}
aside#toc li a.active {
- color: var(--text-heading);
+ color: var(--text);
font-weight: 600;
}
@@ -286,4 +292,10 @@ aside#toc li a.active {
aside#toc {
display: none;
}
+}
+
+@supports (-webkit-touch-callout: none) {
+ #main-wrap {
+ margin-top: max(180px, env(safe-area-inset-top)) !important;
+ }
}
\ No newline at end of file
diff --git a/assets/styling/roots.css b/assets/styling/roots.css
index e0ff05e..6d8793d 100644
--- a/assets/styling/roots.css
+++ b/assets/styling/roots.css
@@ -4,7 +4,7 @@
:root[data-theme="dark"] {
--bg: #000000;
--text: #f3f3f3;
- --text-heading: #ffffff;
+ --text: #ffffff;
--muted: #a3a3a3;
--border: #2b2b2b98;
--bg-elevated: rgba(255, 255, 255, 0.08);
@@ -38,6 +38,9 @@
--spider-display: none;
--cobweb-display: none;
--spider-thread: rgba(255, 255, 255, 0.3);
+ --snow-display: none;
+ --leaf-display: none;
+ --lights-display: none;
--hl-comment: #5c6370;
--hl-punct: #abb2bf;
@@ -71,7 +74,7 @@
:root[data-theme="light"] {
--bg: #ffffff;
--text: #0b0b0c;
- --text-heading: #111827;
+ --text: #111827;
--muted: #6b7280;
--border: #e5e7eb;
--bg-elevated: rgba(0, 0, 0, 0.05);
@@ -82,7 +85,7 @@
--code-inline-text: #0b0b0c;
--code-text: #24292e;
--bg-menu: #ffffff;
- --bg-modal: rgba(240, 240, 240, 0.7);
+ --bg-modal: rgba(240, 240, 240);
--hover-bg: rgba(0, 0, 0, 0.05);
--search-bg: rgba(0, 0, 0, 0.05);
--search-panel-bg: rgba(255, 255, 255, 0.85);
@@ -126,7 +129,7 @@
:root[data-theme="midnight"] {
--bg: #070913;
--text: #e2e8f0;
- --text-heading: #f8fafc;
+ --text: #f8fafc;
--muted: #7f8ea3;
--border: #1a2234;
--bg-elevated: rgba(30, 41, 59, 0.5);
@@ -181,7 +184,7 @@
:root[data-theme="nord"] {
--bg: #2e3440;
--text: #eceff4;
- --text-heading: #88c0d0;
+ --text: #88c0d0;
--muted: #d8dee9;
--border: #3b4252;
--bg-elevated: rgba(67, 76, 94, 0.5);
@@ -236,7 +239,7 @@
:root[data-theme="dracula"] {
--bg: #282a36;
--text: #f8f8f2;
- --text-heading: #bd93f9;
+ --text: #bd93f9;
--muted: #6272a4;
--border: #44475a;
--bg-elevated: rgba(68, 71, 90, 0.5);
@@ -291,7 +294,7 @@
:root[data-theme="forest"] {
--bg: #0d1612;
--text: #e2ede5;
- --text-heading: #86efac;
+ --text: #86efac;
--muted: #739481;
--border: #1a2f24;
--bg-elevated: rgba(34, 76, 56, 0.35);
@@ -346,7 +349,7 @@
:root[data-theme="sunset"] {
--bg: #180d18;
--text: #fce7f3;
- --text-heading: #fb7185;
+ --text: #fb7185;
--muted: #a77c98;
--border: #3b1d36;
--bg-elevated: rgba(80, 30, 70, 0.35);
@@ -401,7 +404,7 @@
:root[data-theme="cyberpunk"] {
--bg: #050508;
--text: #00ffcc;
- --text-heading: #ffe600;
+ --text: #ffe600;
--muted: #5e7a75;
--border: #1a2a26;
--bg-elevated: rgba(0, 255, 204, 0.08);
@@ -453,61 +456,6 @@
--hl-inserted-bg: rgba(0, 255, 204, 0.2);
}
-:root[data-theme="halloween"] {
- --bg: #0a0604;
- --text: #f5ebe6;
- --text-heading: #ff7518;
- --muted: #a37559;
- --border: #3d1c0c;
- --bg-elevated: rgba(255, 117, 24, 0.12);
- --bg-card: rgba(28, 14, 8, 0.6);
- --bg-card-hover: rgba(46, 21, 10, 0.85);
- --bg-code: #050201;
- --code-inline-bg: rgba(255, 117, 24, 0.15);
- --code-inline-text: #ff944d;
- --code-text: #fed7aa;
- --bg-menu: #0e0704;
- --bg-modal: rgba(10, 6, 4, 0.92);
- --hover-bg: rgba(255, 117, 24, 0.12);
- --search-bg: rgba(255, 117, 24, 0.08);
- --search-panel-bg: rgba(14, 7, 4, 0.95);
- --code-group-bg: #1c0e07;
- --code-group-panel-bg: #0a0604;
- --code-tab-active: #ff7518;
- --footer-border: #3d1c0c;
- --border-subtle: rgba(255, 117, 24, 0.15);
- --btn-primary-bg: #ff7518;
- --btn-primary-text: #000000;
- --btn-home-bg: #ff7518;
- --btn-home-text: #000000;
- --callout-note-bg: rgba(255, 117, 24, 0.14);
- --callout-info-bg: rgba(168, 85, 247, 0.15);
- --callout-warning-bg: rgba(255, 154, 77, 0.18);
- --callout-warning-text: #ffaa5e;
- --body-radial-tint: rgba(10, 6, 4, 0.85);
- --skeleton-pulse-1: rgba(255, 117, 24, 0.06);
- --skeleton-pulse-2: rgba(255, 117, 24, 0.18);
- --spider-display: block;
- --cobweb-display: block;
- --spider-thread: rgba(255, 154, 77, 0.4);
-
- --hl-comment: #7c4f34;
- --hl-punct: #fed7aa;
- --hl-func: #ff944d;
- --hl-keyword: #a855f7;
- --hl-string: #84cc16;
- --hl-var: #ff7518;
- --hl-op: #a855f7;
- --hl-num: #f59e0b;
- --hl-param: #ffedd5;
- --hl-class: #c084fc;
- --hl-tag: #ff7518;
- --hl-deleted-color: #ef4444;
- --hl-deleted-bg: rgba(239, 68, 68, 0.2);
- --hl-inserted-color: #84cc16;
- --hl-inserted-bg: rgba(132, 204, 22, 0.2);
-}
-
body.banner-dismissed {
--header-height: 109px;
}
@@ -526,14 +474,18 @@ html {
body {
min-height: 100%;
+ min-height: -webkit-fill-available;
margin: 0;
font-family: var(--font);
background: var(--bg);
color: var(--text);
+ ;
line-height: 1.65;
overflow-x: hidden;
-ms-overflow-style: none;
scrollbar-width: none;
+ padding-left: env(safe-area-inset-left);
+ padding-right: env(safe-area-inset-right);
}
html::-webkit-scrollbar,
@@ -564,101 +516,4 @@ body::before {
a {
color: inherit;
text-decoration: none;
-}
-
-.cobweb {
- display: var(--cobweb-display);
- position: fixed;
- width: 160px;
- height: 160px;
- pointer-events: none;
- z-index: 35;
- opacity: 0.8;
-}
-
-.cobweb-top-left {
- top: 0;
- left: 0;
- transform: scaleX(-1);
-}
-
-.cobweb-top-right {
- top: 0;
- right: 0;
-}
-
-.cobweb-media {
- width: 100%;
- height: 100%;
- object-fit: contain;
- display: block;
-}
-
-.spider-container {
- display: var(--spider-display);
- position: fixed;
- top: 0;
- right: 12%;
- pointer-events: none;
- z-index: 36;
- animation: spiderDescend 10s ease-in-out infinite;
-}
-
-.spider-thread {
- width: 1.5px;
- height: 100%;
- position: absolute;
- top: 0;
- left: 50%;
- transform: translateX(-50%);
- background: var(--spider-thread);
-}
-
-.spider-body {
- position: relative;
- width: 48px;
- height: 48px;
- margin-top: 100px;
- animation: spiderSway 3s ease-in-out infinite alternate;
-}
-
-.spider-media {
- width: 100%;
- height: 100%;
- object-fit: contain;
- display: block;
- scale: 2;
- padding-top: 20px;
-}
-
-@keyframes spiderDescend {
- 0% {
- transform: translateY(-80px);
- }
-
- 45% {
- transform: translateY(240px);
- }
-
- 55% {
- transform: translateY(240px);
- }
-
- 90% {
- transform: translateY(10px);
- }
-
- 100% {
- transform: translateY(-80px);
- }
-}
-
-@keyframes spiderSway {
- 0% {
- transform: rotate(-10deg);
- }
-
- 100% {
- transform: rotate(10deg);
- }
}
\ No newline at end of file
diff --git a/assets/styling/themes.css b/assets/styling/themes.css
new file mode 100644
index 0000000..3e8e15c
--- /dev/null
+++ b/assets/styling/themes.css
@@ -0,0 +1,574 @@
+:root[data-theme="halloween"] {
+ --bg: #0a0604;
+ --text: #f5ebe6;
+ --text: #ff7518;
+ --muted: #a37559;
+ --border: #3d1c0c;
+ --bg-elevated: rgba(255, 117, 24, 0.12);
+ --bg-card: rgba(28, 14, 8, 0.6);
+ --bg-card-hover: rgba(46, 21, 10, 0.85);
+ --bg-code: #050201;
+ --code-inline-bg: rgba(255, 117, 24, 0.15);
+ --code-inline-text: #ff944d;
+ --code-text: #fed7aa;
+ --bg-menu: #0e0704;
+ --bg-modal: rgba(10, 6, 4, 0.92);
+ --hover-bg: rgba(255, 117, 24, 0.12);
+ --search-bg: rgba(255, 117, 24, 0.08);
+ --search-panel-bg: rgba(14, 7, 4, 0.95);
+ --code-group-bg: #1c0e07;
+ --code-group-panel-bg: #0a0604;
+ --code-tab-active: #ff7518;
+ --footer-border: #3d1c0c;
+ --border-subtle: rgba(255, 117, 24, 0.15);
+ --btn-primary-bg: #ff7518;
+ --btn-primary-text: #000000;
+ --btn-home-bg: #ff7518;
+ --btn-home-text: #000000;
+ --callout-note-bg: rgba(255, 117, 24, 0.14);
+ --callout-info-bg: rgba(168, 85, 247, 0.15);
+ --callout-warning-bg: rgba(255, 154, 77, 0.18);
+ --callout-warning-text: #ffaa5e;
+ --body-radial-tint: rgba(10, 6, 4, 0.85);
+ --skeleton-pulse-1: rgba(255, 117, 24, 0.06);
+ --skeleton-pulse-2: rgba(255, 117, 24, 0.18);
+ --spider-display: block;
+ --cobweb-display: block;
+ --spider-thread: rgba(255, 154, 77, 0.4);
+
+ --hl-comment: #7c4f34;
+ --hl-punct: #fed7aa;
+ --hl-func: #ff944d;
+ --hl-keyword: #a855f7;
+ --hl-string: #84cc16;
+ --hl-var: #ff7518;
+ --hl-op: #a855f7;
+ --hl-num: #f59e0b;
+ --hl-param: #ffedd5;
+ --hl-class: #c084fc;
+ --hl-tag: #ff7518;
+ --hl-deleted-color: #ef4444;
+ --hl-deleted-bg: rgba(239, 68, 68, 0.2);
+ --hl-inserted-color: #84cc16;
+ --hl-inserted-bg: rgba(132, 204, 22, 0.2);
+}
+
+:root[data-theme="christmas"] {
+ --bg: #07110c;
+ --text: #f4efe6;
+ --text: #ff5c5c;
+ --muted: #8fa89a;
+ --border: #1f3a2b;
+ --bg-elevated: rgba(255, 92, 92, 0.1);
+ --bg-card: rgba(10, 26, 18, 0.6);
+ --bg-card-hover: rgba(18, 44, 30, 0.85);
+ --bg-code: #040a07;
+ --code-inline-bg: rgba(245, 196, 81, 0.14);
+ --code-inline-text: #f5c451;
+ --code-text: #e8f5e9;
+ --bg-menu: #09150e;
+ --bg-modal: rgba(7, 17, 12, 0.9);
+ --hover-bg: rgba(255, 92, 92, 0.12);
+ --search-bg: rgba(245, 196, 81, 0.08);
+ --search-panel-bg: rgba(9, 21, 14, 0.95);
+ --code-group-bg: #11261a;
+ --code-group-panel-bg: #07110c;
+ --code-tab-active: #ff5c5c;
+ --footer-border: #1f3a2b;
+ --border-subtle: rgba(245, 196, 81, 0.14);
+ --btn-primary-bg: #d92d3a;
+ --btn-primary-text: #ffffff;
+ --btn-home-bg: #d92d3a;
+ --btn-home-text: #ffffff;
+ --callout-note-bg: rgba(47, 191, 113, 0.14);
+ --callout-info-bg: rgba(245, 196, 81, 0.14);
+ --callout-warning-bg: rgba(255, 92, 92, 0.18);
+ --callout-warning-text: #ff9a9a;
+ --body-radial-tint: rgba(7, 17, 12, 0.85);
+ --skeleton-pulse-1: rgba(245, 196, 81, 0.06);
+ --skeleton-pulse-2: rgba(245, 196, 81, 0.16);
+ --snow-display: block;
+ --lights-display: block;
+
+ --hl-comment: #5f8a72;
+ --hl-punct: #e8f5e9;
+ --hl-func: #f5c451;
+ --hl-keyword: #ff6b6b;
+ --hl-string: #5fd68f;
+ --hl-var: #ff9f68;
+ --hl-op: #ff6b6b;
+ --hl-num: #f5c451;
+ --hl-param: #fdf2d0;
+ --hl-class: #7fe3b0;
+ --hl-tag: #ff6b6b;
+ --hl-deleted-color: #ff6b6b;
+ --hl-deleted-bg: rgba(255, 107, 107, 0.18);
+ --hl-inserted-color: #5fd68f;
+ --hl-inserted-bg: rgba(95, 214, 143, 0.18);
+}
+
+:root[data-theme="autumn"] {
+ --bg: #1a0f08;
+ --text: #f7ead7;
+ --text: #f4b942;
+ --muted: #a68a6d;
+ --border: #3a2412;
+ --bg-elevated: rgba(244, 185, 66, 0.1);
+ --bg-card: rgba(40, 23, 11, 0.6);
+ --bg-card-hover: rgba(58, 36, 18, 0.85);
+ --bg-code: #0e0703;
+ --code-inline-bg: rgba(244, 185, 66, 0.14);
+ --code-inline-text: #f4b942;
+ --code-text: #fde7c0;
+ --bg-menu: #1a0f08;
+ --bg-modal: rgba(26, 15, 8, 0.9);
+ --hover-bg: rgba(244, 185, 66, 0.12);
+ --search-bg: rgba(244, 185, 66, 0.08);
+ --search-panel-bg: rgba(26, 15, 8, 0.95);
+ --code-group-bg: #28170b;
+ --code-group-panel-bg: #1a0f08;
+ --code-tab-active: #f4b942;
+ --footer-border: #3a2412;
+ --border-subtle: rgba(244, 185, 66, 0.14);
+ --btn-primary-bg: #f4b942;
+ --btn-primary-text: #1a0f08;
+ --btn-home-bg: #f4b942;
+ --btn-home-text: #1a0f08;
+ --callout-note-bg: rgba(200, 69, 44, 0.16);
+ --callout-info-bg: rgba(132, 160, 60, 0.16);
+ --callout-warning-bg: rgba(244, 185, 66, 0.18);
+ --callout-warning-text: #f8cf7a;
+ --body-radial-tint: rgba(26, 15, 8, 0.85);
+ --skeleton-pulse-1: rgba(244, 185, 66, 0.06);
+ --skeleton-pulse-2: rgba(244, 185, 66, 0.17);
+ --leaf-display: block;
+
+ --hl-comment: #8a6a4a;
+ --hl-punct: #fde7c0;
+ --hl-func: #f4b942;
+ --hl-keyword: #e0643f;
+ --hl-string: #a3b94a;
+ --hl-var: #e8913a;
+ --hl-op: #e0643f;
+ --hl-num: #f4b942;
+ --hl-param: #fdf0d8;
+ --hl-class: #d9a35b;
+ --hl-tag: #e0643f;
+ --hl-deleted-color: #e0643f;
+ --hl-deleted-bg: rgba(224, 100, 63, 0.18);
+ --hl-inserted-color: #a3b94a;
+ --hl-inserted-bg: rgba(163, 185, 74, 0.18);
+}
+
+:root[data-theme="winter"] {
+ --bg: #0d1b2a;
+ --text: #e6f0fa;
+ --text: #d6ecff;
+ --muted: #7f98b3;
+ --border: #1b2c44;
+ --bg-elevated: rgba(138, 209, 255, 0.1);
+ --bg-card: rgba(16, 32, 52, 0.6);
+ --bg-card-hover: rgba(26, 48, 76, 0.85);
+ --bg-code: #08121e;
+ --code-inline-bg: rgba(138, 209, 255, 0.14);
+ --code-inline-text: #8ad1ff;
+ --code-text: #cfe4f7;
+ --bg-menu: #0d1b2a;
+ --bg-modal: rgba(13, 27, 42, 0.88);
+ --hover-bg: rgba(138, 209, 255, 0.1);
+ --search-bg: rgba(138, 209, 255, 0.08);
+ --search-panel-bg: rgba(13, 27, 42, 0.95);
+ --code-group-bg: #132640;
+ --code-group-panel-bg: #0d1b2a;
+ --code-tab-active: #8ad1ff;
+ --footer-border: #1b2c44;
+ --border-subtle: rgba(138, 209, 255, 0.14);
+ --btn-primary-bg: #8ad1ff;
+ --btn-primary-text: #06101c;
+ --btn-home-bg: #8ad1ff;
+ --btn-home-text: #06101c;
+ --callout-note-bg: rgba(138, 209, 255, 0.14);
+ --callout-info-bg: rgba(185, 166, 255, 0.14);
+ --callout-warning-bg: rgba(255, 214, 138, 0.16);
+ --callout-warning-text: #ffd68a;
+ --body-radial-tint: rgba(13, 27, 42, 0.85);
+ --skeleton-pulse-1: rgba(138, 209, 255, 0.06);
+ --skeleton-pulse-2: rgba(138, 209, 255, 0.16);
+ --snow-display: block;
+
+ --hl-comment: #5b7491;
+ --hl-punct: #cfe4f7;
+ --hl-func: #8ad1ff;
+ --hl-keyword: #b9a6ff;
+ --hl-string: #9be7d4;
+ --hl-var: #ffb4c7;
+ --hl-op: #8ad1ff;
+ --hl-num: #ffd68a;
+ --hl-param: #e6f0fa;
+ --hl-class: #a9e4ff;
+ --hl-tag: #b9a6ff;
+ --hl-deleted-color: #ff8fa3;
+ --hl-deleted-bg: rgba(255, 143, 163, 0.18);
+ --hl-inserted-color: #9be7d4;
+ --hl-inserted-bg: rgba(155, 231, 212, 0.18);
+}
+
+.cobweb {
+ display: var(--cobweb-display);
+ position: fixed;
+ width: 160px;
+ height: 160px;
+ pointer-events: none;
+ z-index: 35;
+ opacity: 0.8;
+}
+
+.cobweb-top-left {
+ top: 0;
+ left: 0;
+ transform: scaleX(-1);
+}
+
+.cobweb-top-right {
+ top: 0;
+ right: 0;
+}
+
+.cobweb-media {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ display: block;
+}
+
+.spider-container {
+ display: var(--spider-display);
+ position: fixed;
+ top: 0;
+ right: 12%;
+ pointer-events: none;
+ z-index: 100;
+ animation: spiderDescend 10s ease-in-out infinite;
+}
+
+.spider-thread {
+ width: 1.5px;
+ height: 100vh;
+ position: absolute;
+ bottom: 24px;
+ left: 50%;
+ transform: translateX(-50%);
+ background: var(--spider-thread);
+}
+
+.spider-body {
+ position: relative;
+ width: 48px;
+ height: 48px;
+ margin-top: 100px;
+ pointer-events: auto;
+ cursor: pointer;
+ animation: spiderSway 3s ease-in-out infinite alternate;
+}
+
+.spider-media {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ display: block;
+ scale: 2;
+ padding-top: 20px;
+}
+
+@keyframes spiderDescend {
+ 0% {
+ transform: translateY(-80px);
+ }
+
+ 45% {
+ transform: translateY(240px);
+ }
+
+ 55% {
+ transform: translateY(240px);
+ }
+
+ 90% {
+ transform: translateY(10px);
+ }
+
+ 100% {
+ transform: translateY(-80px);
+ }
+}
+
+@keyframes spiderSway {
+ 0% {
+ transform: rotate(-10deg);
+ }
+
+ 100% {
+ transform: rotate(10deg);
+ }
+}
+
+@keyframes spiderDrop {
+ from {
+ transform: translateY(240px);
+ }
+
+ to {
+ transform: translateY(130vh);
+ }
+}
+
+:root {
+ --snow-tile: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cg fill='white'%3E%3Ccircle cx='22' cy='38' r='2.4' fill-opacity='.9'/%3E%3Ccircle cx='96' cy='126' r='1.8' fill-opacity='.7'/%3E%3Ccircle cx='168' cy='28' r='3' fill-opacity='.85'/%3E%3Ccircle cx='236' cy='204' r='2' fill-opacity='.75'/%3E%3Ccircle cx='272' cy='92' r='2.8' fill-opacity='.9'/%3E%3Ccircle cx='58' cy='252' r='2.2' fill-opacity='.8'/%3E%3Ccircle cx='196' cy='282' r='1.6' fill-opacity='.7'/%3E%3Ccircle cx='140' cy='190' r='2.4' fill-opacity='.85'/%3E%3C/g%3E%3Cg stroke='white' stroke-opacity='.9' stroke-width='1.3' stroke-linecap='round' fill='none'%3E%3Cpath transform='translate(126 72)' d='M0 -8V8M-6.9 -4L6.9 4M-6.9 4L6.9 -4'/%3E%3Cpath transform='translate(246 150)' d='M0 -6V6M-5.2 -3L5.2 3M-5.2 3L5.2 -3'/%3E%3Cpath transform='translate(40 160)' d='M0 -7V7M-6 -3.5L6 3.5M-6 3.5L6 -3.5'/%3E%3Cpath transform='translate(210 250)' d='M0 -6V6M-5.2 -3L5.2 3M-5.2 3L5.2 -3'/%3E%3C/g%3E%3C/svg%3E");
+ --leaf-tile: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='360' height='360'%3E%3Cg transform='translate(50 60) rotate(25) scale(1.2)' fill='%23e8710a'%3E%3Cpath d='M0 -14C10 -8 12 6 0 16C-12 6 -10 -8 0 -14Z'/%3E%3Cpath d='M0 -12V14' stroke='%23000' stroke-opacity='.25' stroke-width='1'/%3E%3C/g%3E%3Cg transform='translate(190 40) rotate(-40)' fill='%23c2410c'%3E%3Cpath d='M0 -14C10 -8 12 6 0 16C-12 6 -10 -8 0 -14Z'/%3E%3Cpath d='M0 -12V14' stroke='%23000' stroke-opacity='.25' stroke-width='1'/%3E%3C/g%3E%3Cg transform='translate(300 130) rotate(70) scale(1.3)' fill='%23eab308'%3E%3Cpath d='M0 -14C10 -8 12 6 0 16C-12 6 -10 -8 0 -14Z'/%3E%3Cpath d='M0 -12V14' stroke='%23000' stroke-opacity='.25' stroke-width='1'/%3E%3C/g%3E%3Cg transform='translate(120 200) rotate(-15) scale(1.1)' fill='%23b45309'%3E%3Cpath d='M0 -14C10 -8 12 6 0 16C-12 6 -10 -8 0 -14Z'/%3E%3Cpath d='M0 -12V14' stroke='%23000' stroke-opacity='.25' stroke-width='1'/%3E%3C/g%3E%3Cg transform='translate(250 280) rotate(140) scale(1.2)' fill='%23d97706'%3E%3Cpath d='M0 -14C10 -8 12 6 0 16C-12 6 -10 -8 0 -14Z'/%3E%3Cpath d='M0 -12V14' stroke='%23000' stroke-opacity='.25' stroke-width='1'/%3E%3C/g%3E%3Cg transform='translate(40 310) rotate(-80)' fill='%23ca8a04'%3E%3Cpath d='M0 -14C10 -8 12 6 0 16C-12 6 -10 -8 0 -14Z'/%3E%3Cpath d='M0 -12V14' stroke='%23000' stroke-opacity='.25' stroke-width='1'/%3E%3C/g%3E%3C/svg%3E");
+ --lights-tile: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='56'%3E%3Cpath d='M0 4Q40 32 80 4Q120 32 160 4' fill='none' stroke='%23245c3a' stroke-width='1.6'/%3E%3Cg fill='%23222'%3E%3Crect x='18' y='13' width='4' height='4'/%3E%3Crect x='38' y='17' width='4' height='4'/%3E%3Crect x='58' y='13' width='4' height='4'/%3E%3Crect x='98' y='13' width='4' height='4'/%3E%3Crect x='118' y='17' width='4' height='4'/%3E%3Crect x='138' y='13' width='4' height='4'/%3E%3C/g%3E%3Cellipse cx='20' cy='22' rx='4' ry='5.5' fill='%23ff4d4d'/%3E%3Cellipse cx='40' cy='26' rx='4' ry='5.5' fill='%23ffd24d'/%3E%3Cellipse cx='60' cy='22' rx='4' ry='5.5' fill='%234dff88'/%3E%3Cellipse cx='100' cy='22' rx='4' ry='5.5' fill='%234da6ff'/%3E%3Cellipse cx='120' cy='26' rx='4' ry='5.5' fill='%23ff4d4d'/%3E%3Cellipse cx='140' cy='22' rx='4' ry='5.5' fill='%23ffd24d'/%3E%3C/svg%3E");
+}
+
+.snowfall,
+.leaf-fall {
+ position: fixed;
+ inset: 0;
+ overflow: hidden;
+ pointer-events: none;
+ z-index: 30;
+}
+
+.snowfall {
+ display: var(--snow-display);
+}
+
+.leaf-fall {
+ display: var(--leaf-display);
+}
+
+.snowfall::before,
+.snowfall::after,
+.leaf-fall::before,
+.leaf-fall::after {
+ content: "";
+ position: absolute;
+ background-repeat: repeat;
+ will-change: transform;
+}
+
+.snowfall::before {
+ top: -600px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-image: var(--snow-tile);
+ background-size: 300px 300px;
+ opacity: 0.9;
+ animation: snowFallNear 18s linear infinite;
+}
+
+.snowfall::after {
+ top: -500px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-image: var(--snow-tile);
+ background-size: 500px 500px;
+ opacity: 0.6;
+ animation: snowFallFar 30s linear infinite;
+}
+
+.leaf-fall::before {
+ top: -720px;
+ left: 0;
+ right: -360px;
+ bottom: 0;
+ background-image: var(--leaf-tile);
+ background-size: 360px 360px;
+ opacity: 0.9;
+ animation: leafFallNear 26s linear infinite;
+}
+
+.leaf-fall::after {
+ top: -540px;
+ left: -540px;
+ right: 0;
+ bottom: 0;
+ background-image: var(--leaf-tile);
+ background-size: 540px 540px;
+ opacity: 0.7;
+ animation: leafFallFar 40s linear infinite;
+}
+
+.light-string {
+ display: var(--lights-display);
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 56px;
+ pointer-events: none;
+ z-index: 100;
+ background-image: var(--lights-tile);
+ background-repeat: repeat-x;
+ background-size: 160px 56px;
+ animation: lightTwinkle 2.6s ease-in-out infinite alternate;
+}
+
+@keyframes snowFallNear {
+ from {
+ transform: translate3d(0, 0, 0);
+ }
+
+ to {
+ transform: translate3d(0, 600px, 0);
+ }
+}
+
+@keyframes snowFallFar {
+ from {
+ transform: translate3d(0, 0, 0);
+ }
+
+ to {
+ transform: translate3d(0, 500px, 0);
+ }
+}
+
+@keyframes leafFallNear {
+ from {
+ transform: translate3d(0, 0, 0);
+ }
+
+ to {
+ transform: translate3d(-360px, 720px, 0);
+ }
+}
+
+@keyframes leafFallFar {
+ from {
+ transform: translate3d(0, 0, 0);
+ }
+
+ to {
+ transform: translate3d(540px, 540px, 0);
+ }
+}
+
+@keyframes lightTwinkle {
+ from {
+ opacity: 0.65;
+ filter: drop-shadow(0 0 3px rgba(255, 214, 120, 0.5));
+ }
+
+ to {
+ opacity: 1;
+ filter: drop-shadow(0 0 8px rgba(255, 214, 120, 0.9));
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+
+ .snowfall::before,
+ .snowfall::after,
+ .leaf-fall::before,
+ .leaf-fall::after,
+ .light-string {
+ animation: none;
+ }
+}
+
+#navbar .brand {
+ position: relative;
+}
+
+#navbar .brand::after {
+ content: "";
+ position: absolute;
+ top: -9px;
+ right: -9px;
+ font-size: 14px;
+ line-height: 1;
+ pointer-events: none;
+}
+
+:root[data-theme="halloween"] #navbar .brand::after {
+ content: "π";
+}
+
+:root[data-theme="autumn"] #navbar .brand::after {
+ content: "π";
+}
+
+:root[data-theme="winter"] #navbar .brand::after {
+ content: "βοΈ";
+}
+
+:root[data-theme="christmas"] #navbar .brand::after {
+ content: "π";
+}
+
+.st-themes {
+ padding: 14px 16px 16px;
+}
+
+.st-sub {
+ margin: 0 0 8px;
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--muted);
+}
+
+.st-theme-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 8px;
+ margin-bottom: 16px;
+}
+
+.st-theme-grid:last-child {
+ margin-bottom: 0;
+}
+
+.st-theme {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ min-width: 0;
+ height: 40px;
+ padding: 0 14px;
+ border: 1px solid transparent;
+ border-radius: 100px;
+ background: var(--search-bg);
+ color: var(--muted);
+ font: inherit;
+ font-size: 13.5px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
+}
+
+.st-theme .ico {
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
+}
+
+.st-theme span {
+ min-width: 0;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.st-theme:hover {
+ color: var(--text);
+}
+
+.st-theme.on {
+ border-color: var(--text);
+ background: var(--hover-bg);
+ color: var(--text);
+ font-weight: 600;
+}
\ No newline at end of file
diff --git a/concepts/providers.md b/concepts/providers.md
index f162b13..1a552a7 100644
--- a/concepts/providers.md
+++ b/concepts/providers.md
@@ -12,14 +12,14 @@ Provider availability and performance can vary based on your network environment
## How Providers Are Used
-When your app calls `getStream()`βdirectly or through Stream API:
+When your app calls `getStream()`-directly or through Stream API:
1. All active (non-disabled) providers are queried **simultaneously** with per-source random jitter to stagger upstream requests
2. Each provider has its own **timeout**, **retry count**, and **backoff delay**
-3. Every returned URL is **verified** β first via a HEAD request to the raw upstream URL, then via a proxied HLS playability check
+3. Every returned URL is **verified** - first via a HEAD request to the raw upstream URL, then via a proxied HLS playability check
4. Only URLs that pass both verification steps appear in the response as `source` events
-Because the endpoint is SSE, **verified sources are emitted immediately as each provider resolves** β your player receives them progressively. The stream closes once every provider has either resolved or timed out.
+Because the endpoint is SSE, **verified sources are emitted immediately as each provider resolves** - your player receives them progressively. The stream closes once every provider has either resolved or timed out.
:::callout-info
Call `sdk.getSources(true)` to inspect enabled providers and `sdk.probeAllSources()` for a live availability signal. If you use Stream API, its `/health` endpoint exposes probes over HTTP.
@@ -68,7 +68,7 @@ CACHE_TTL = 5 minutes
All provider stream URLs are wrapped in the server proxy (unless `skipProxy: true`):
```
-http://localhost:7860/api?url=&=1
+http://YOUR_API_URL/api?url=&=1
```
When your player fetches this path from your self-hosted instance, the server:
@@ -83,5 +83,5 @@ When your player fetches this path from your self-hosted instance, the server:
Before a URL is emitted as a `source` event, it passes two verification steps:
-1. **`verifyStream`** β a HEAD request to the raw upstream URL. Must return HTTP `< 400`.
-2. **`verifyPlayable`** β fetches the proxied URL, validates `#EXTM3U`, checks for `#EXTINF` or `#EXT-X-STREAM-INF`, then fetches the first segment.
\ No newline at end of file
+1. **`verifyStream`** - a HEAD request to the raw upstream URL. Must return HTTP `< 400`.
+2. **`verifyPlayable`** - fetches the proxied URL, validates `#EXTM3U`, checks for `#EXTINF` or `#EXT-X-STREAM-INF`, then fetches the first segment.
\ No newline at end of file
diff --git a/home.txt b/home.txt
index c09aef2..2e89680 100644
--- a/home.txt
+++ b/home.txt
@@ -93,19 +93,14 @@
gap: 6px;
padding: 10px 22px;
border-radius: 100px;
- background: #000000;
- color: #ffffff !important;
+ background: var(--btn-primary-bg);
+ color: var(--btn-primary-text) !important;
font-size: 13.5px;
font-weight: 600;
text-decoration: none !important;
transition: transform 0.15s ease;
}
- html.dark .btn-primary {
- background: #FFFFFF;
- color: #000000 !important;
- }
-
.btn-secondary {
display: inline-flex;
align-items: center;
@@ -156,6 +151,11 @@
z-index: 1;
}
+ .header-desc {
+ font-size: 14.5px;
+ margin-top: 0 !important;
+ }
+
.section-head {
margin-bottom: 22px;
text-align: left;
@@ -166,13 +166,13 @@
font-size: 22px !important;
font-weight: 700 !important;
margin: 0 0 6px !important;
- color: #ffffff !important;
+ color: var(--text) !important;
}
.section-head p {
font-size: 14.5px;
margin: 0;
- color: #a1a1aa;
+ color: var(--muted);
}
.vyla-grid {
@@ -247,11 +247,71 @@
opacity: 0.75;
}
+ .hero-greeting {
+ margin: 0 0 14px;
+ padding: 6px 16px;
+ border-radius: 100px;
+ background: var(--hover-bg);
+ backdrop-filter: blur(16px);
+ -webkit-backdrop-filter: blur(16px);
+ color: var(--text);
+ font-size: 13px;
+ font-weight: 600;
+ letter-spacing: 0.01em;
+}
+
+.hero-resume {
+ max-width: min(90vw, 360px);
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ color: var(--text);
+}
+
@media (max-width: 900px) {
#header-container {
border-radius: 100px;
}
}
+
+ @media (orientation: landscape) and (max-height: 550px) {
+ .home-header {
+ height: auto;
+ min-height: 280px;
+ padding-top: calc(var(--header-height) + 24px);
+ padding-bottom: 28px;
+ }
+
+ .header-logo-badge {
+ width: 44px;
+ height: 44px;
+ margin-bottom: 8px;
+ }
+
+ .header-title {
+ font-size: 26px;
+ margin-bottom: 6px;
+ }
+
+ .header-desc {
+ font-size: 13.5px;
+ margin-bottom: 14px;
+ max-width: 540px;
+ }
+
+ .content-section {
+ margin-top: 32px;
+ }
+
+ .vyla-grid {
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+ gap: 12px;
+ }
+
+ .feature-box {
+ padding: 18px;
+ }
+ }
diff --git a/index.html b/index.html
index 9809c39..c26a865 100644
--- a/index.html
+++ b/index.html
@@ -3,8 +3,11 @@
-
-
Vyla Entertainment β Vyla
+
+
+
+
+
Vyla Entertainment - Vyla
-
+
@@ -26,7 +29,7 @@
-
+
@@ -77,6 +80,9 @@
+
+
+
@@ -87,7 +93,12 @@
src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js">
@@ -106,10 +117,13 @@
+
+
+
- Rights holders can request source removal β DMCA policy .
+ Rights holders can request source removal - DMCA policy .
β
@@ -120,6 +134,8 @@
height="28" decoding="async" />
+
@@ -139,6 +155,7 @@