feat: add dark mode and style baking support for Samsung TV compatibility

This commit is contained in:
2026-05-13 10:31:46 +02:00
parent b69d830fc8
commit c294c8c85e
2 changed files with 176 additions and 11 deletions
+44 -5
View File
@@ -299,6 +299,21 @@
letter-spacing: 0;
color: var(--text);
}
.dark-toggle {
display: flex;
align-items: center;
gap: 3px;
font-size: 13px;
cursor: pointer;
white-space: nowrap;
user-select: none;
}
.dark-toggle input[type="checkbox"] {
width: 14px;
height: 14px;
cursor: pointer;
accent-color: var(--accent);
}
.hint {
font-size: 12px;
color: var(--muted);
@@ -700,7 +715,9 @@
container.innerHTML = views
.map((v) => {
const viewUrl = origin + "/v/" + v.id;
const urlPreview = v.urls.map((u, i) => `${u.url} (${u.durationSec}s)`).join(" → ");
const urlPreview = v.urls.map((u, i) =>
`${u.url} (${u.durationSec}s)${u.forceDarkMode ? " 🌙" : ""}`
).join(" → ");
const badges = [
`<span class="badge badge-${v.method}">${v.method.toUpperCase()}</span>`,
v.metaRefreshEnabled ? '<span class="badge badge-refresh">META-REFRESH</span>' : "",
@@ -795,7 +812,7 @@
const idAttr = isEdit ? ` data-id="${existing.id}"` : "";
const urls = existing ? existing.urls : [{ url: "", durationSec: 30 }];
let urlRowsHTML = urls
.map((u, i) => buildUrlRowHTML(u.url, u.durationSec, i, urls.length))
.map((u, i) => buildUrlRowHTML(u.url, u.durationSec, i, urls.length, u.forceDarkMode))
.join("");
const method = existing ? existing.method : "ssr";
const metaChecked = existing ? existing.metaRefreshEnabled : false;
@@ -803,6 +820,8 @@
const cacheTtl = existing ? existing.cacheTtlSec : 60;
const vpW = existing ? existing.viewportWidth : 1920;
const vpH = existing ? existing.viewportHeight : 1080;
const bakeStylesChecked = existing ? existing.bakeStyles : false;
const bakeSectionStyle = method === "ssr" ? "" : "display:none;";
return `
<h2>${title}</h2>
@@ -836,6 +855,14 @@
<div class="hint" style="margin-top:-8px;margin-bottom:16px;">TV reloads when rotation advances. Required for multi-URL on non-JS browsers.</div>
</div>
<div id="bake-section" style="${bakeSectionStyle}">
<div class="checkbox-row">
<input type="checkbox" id="bake-styles" ${bakeStylesChecked ? "checked" : ""}>
<label for="bake-styles">Bake computed styles into HTML (Samsung TV compatibility)</label>
</div>
<div class="hint" style="margin-top:-8px;margin-bottom:16px;">Fixes styling issues on browsers with limited CSS support (e.g., DuckDuckGo). Increases HTML size.</div>
</div>
<div class="field">
<label>Cache TTL (seconds)</label>
<input type="number" id="view-cache-ttl" value="${cacheTtl}" min="0" max="86400" required>
@@ -862,14 +889,19 @@
</form>`;
}
function buildUrlRowHTML(url, durationSec, index, total) {
function buildUrlRowHTML(url, durationSec, index, total, forceDarkMode) {
const isSingle = total <= 1;
const disabledAttr = isSingle ? "disabled" : "";
const grayStyle = isSingle ? 'style="opacity:0.4;"' : "";
const darkChecked = forceDarkMode ? "checked" : "";
return `
<div class="url-row" data-index="${index}">
<input type="url" placeholder="https://example.com/slideshow" value="${esc(url || "")}" class="url-input" required>
<input type="number" placeholder="Seconds" value="${durationSec || 30}" min="1" max="86400" class="duration-input" ${disabledAttr} ${grayStyle} required>
<label class="dark-toggle" title="Force dark mode">
<input type="checkbox" class="dark-mode-input" ${darkChecked}>
🌙
</label>
<button type="button" class="btn btn-icon btn-danger btn-sm" onclick="removeUrlRow(this)" ${isSingle ? "disabled" : ""}>✕</button>
</div>`;
}
@@ -882,7 +914,7 @@
const container = document.getElementById("url-rows");
const rows = container.querySelectorAll(".url-row");
const div = document.createElement("div");
div.innerHTML = buildUrlRowHTML("", 30, rows.length, rows.length + 1);
div.innerHTML = buildUrlRowHTML("", 30, rows.length, rows.length + 1, false);
container.appendChild(div.firstElementChild);
updateUrlRowStates();
}
@@ -914,6 +946,7 @@
function onMethodChange() {
const method = document.getElementById("view-method").value;
document.getElementById("meta-section").style.display = method === "ssr" ? "" : "none";
document.getElementById("bake-section").style.display = method === "ssr" ? "" : "none";
}
// ═══════════════════════════════════════════════
@@ -929,6 +962,7 @@
const name = document.getElementById("view-name").value.trim();
const method = document.getElementById("view-method").value;
const metaRefreshEnabled = document.getElementById("meta-refresh").checked;
const bakeStyles = document.getElementById("bake-styles")?.checked ?? false;
const cacheTtlSec = parseInt(document.getElementById("view-cache-ttl").value) || 60;
const viewportWidth = parseInt(document.getElementById("viewport-width").value) || 1920;
const viewportHeight = parseInt(document.getElementById("viewport-height").value) || 1080;
@@ -937,9 +971,11 @@
for (const row of document.querySelectorAll("#url-rows .url-row")) {
const urlVal = row.querySelector(".url-input").value.trim();
if (!urlVal) continue;
const forceDarkMode = row.querySelector(".dark-mode-input")?.checked ?? false;
urls.push({
url: urlVal,
durationSec: parseInt(row.querySelector(".duration-input").value) || 30,
forceDarkMode,
});
}
@@ -953,7 +989,10 @@
}
const body = { name, urls, method, cacheTtlSec, viewportWidth, viewportHeight };
if (method === "ssr") body.metaRefreshEnabled = metaRefreshEnabled;
if (method === "ssr") {
body.metaRefreshEnabled = metaRefreshEnabled;
body.bakeStyles = bakeStyles;
}
try {
if (isEdit) {