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 = + '' + + '
' + + '
' + + '

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 = '' + esc(p.name) + ''; + } else if (p.name.toLowerCase() === "zstrike") { + avatar = '' + esc(p.name) + ''; + } 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 '"; + }).join(""); +} + +function switchRow(id, title, hint) { + return ''; +} + +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 '"; + }).join(""); + modal.innerHTML = + '
' + + '"; + 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 = '' + - '' + - '' + - '
' + - '
General
'; - THEMES_GENERAL.forEach(function (t) { - segHtml += ''; - }); - segHtml += '
Seasonal
'; - THEMES_SEASONAL.forEach(function (t) { - segHtml += ''; - }); - segHtml += '
'; - 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.

' + + '
' + + 'View Guide' + + '' + + '
' + + '
'; + + 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 @@
+ + + @@ -120,6 +134,8 @@ height="28" decoding="async" /> @@ -139,6 +155,7 @@