diff --git a/404.html b/404.html index e12cdb2..e73ada5 100644 --- a/404.html +++ b/404.html @@ -1,63 +1,63 @@ - - - - - - - - - - - 404 - Virtual Clicker - - - - - - - - - - - - -
-

404 Not Found

-
- - - -
- -
- - - + + + + + + + + + + + 404 - Virtual Clicker + + + + + + + + + + + +
+ +
+

Virtual Clicker

+ +
+
+
+

404 Not Found

+
+ + + +
+ +
+ + + \ No newline at end of file diff --git a/index.html b/index.html index 10c5a7d..3db782d 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,7 @@ + @@ -23,7 +24,7 @@ - -
+

000

-
+
@@ -118,7 +119,7 @@

Announcement

-
+
@@ -156,7 +157,7 @@

Announcement

-
+
@@ -174,12 +175,12 @@

Announcement

- - + +
- - + +
@@ -194,7 +195,7 @@

Announcement

-
+
@@ -279,6 +280,9 @@

4

+
@@ -540,7 +544,7 @@

000

-
+
-
+
'; + document.querySelector('[data-answer-mode="set"] .button-grid:has([data-set-input])').innerHTML = ''; if (document.querySelector("[data-add-set-input]")) { document.querySelector("[data-add-set-input]").addEventListener("click", addSet); } @@ -1214,6 +1214,7 @@ try { var newColumn = document.createElement('input'); newColumn.setAttribute('type', 'text'); newColumn.setAttribute('autocomplete', 'off'); + newColumn.classList.add('matrix-column'); newColumn.setAttribute('data-matrix-column', row.children.length + 1); row.appendChild(newColumn); }); @@ -1248,6 +1249,7 @@ try { var newColumn = document.createElement('input'); newColumn.setAttribute('type', 'text'); newColumn.setAttribute('autocomplete', 'off'); + newColumn.classList.add('matrix-column'); newColumn.setAttribute('data-matrix-column', column.getAttribute('data-matrix-column')); newRow.appendChild(newColumn); }); @@ -1273,7 +1275,7 @@ try { function resetMatrix() { var matrix = document.getElementById('matrix'); - matrix.innerHTML = '
'; + matrix.innerHTML = '
'; document.querySelectorAll('[data-answer-mode="matrix"] .button-grid')[1].innerHTML = ''; document.querySelectorAll('[data-answer-mode="matrix"] .button-grid')[2].innerHTML = ''; if (document.querySelector("[data-add-matrix-column]")) document.querySelector("[data-add-matrix-column]").addEventListener("click", addColumn); diff --git a/src/design.css b/src/design.css index 1ffdfb0..b5448bf 100644 --- a/src/design.css +++ b/src/design.css @@ -70,7 +70,7 @@ body.enable-transitions { --easing: cubic-bezier(0.32, 0, 0.67, 0); } -body:has(#header) > *:not([id]):not(dialog) { +body:has(.header) > *:not([id]):not(.header):not(.clicker):not(.controls-container):not(.loader):not(dialog) { position: fixed; } @@ -460,7 +460,7 @@ div.ML__keyboard { } } -#clicker button { +.clicker button { width: inherit; min-width: 36px; min-height: 36px; @@ -548,7 +548,7 @@ div.ML__keyboard { transition: 0.25s; } -#clicker:has([data-value="frq"][aria-selected="true"]) #answer-container { +.clicker:has([data-value="frq"][aria-selected="true"]) #answer-container { overflow: visible !important; } @@ -593,11 +593,11 @@ div.ML__keyboard { min-width: fit-content !important; } -#clicker:not(:has(.section)):not(:has(#submit-button)) { +.clicker:not(:has(.section)):not(:has(#submit-button)) { align-items: center; } -#clicker:not(:has(.section)):not(:has(#submit-button)) *:not(ul):not(ol):not(li) { +.clicker:not(:has(.section)):not(:has(#submit-button)) *:not(ul):not(ol):not(li) { text-align: center; } diff --git a/src/layout.css b/src/layout.css index feb13df..0c71bf3 100644 --- a/src/layout.css +++ b/src/layout.css @@ -62,7 +62,7 @@ div.spacer { gap: 0.75rem; } -#header { +.header { align-self: center; display: flex; flex-direction: column; @@ -70,30 +70,30 @@ div.spacer { gap: 10px; } -#header h1 { +.header h1 { font-size: 1.5rem; } -#header h6, -#header a { +.header h6, +.header a { font-size: 1.25rem; font-weight: 700; text-decoration: none; line-height: 1.5; } -#header .info { +.header .info { display: flex; flex-direction: column; align-items: center; } -#header .menu { +.header .menu { margin-top: 5px; border-radius: 10px; } -#clicker { +.clicker { flex: 1; align-self: center; display: flex; @@ -275,7 +275,7 @@ div.spacer { transition: 0.25s; } -#insert-row { +.insert-row { flex: 1; overflow: hidden; } diff --git a/src/modules/auth.js b/src/modules/auth.js index e697499..31ca582 100644 --- a/src/modules/auth.js +++ b/src/modules/auth.js @@ -150,7 +150,7 @@ export async function sync(hideWelcome = true, returnFunction = null) { ))); console.log(`${settingsIsSynced ? '🟢' : '🟡'} Settings is ${!settingsIsSynced ? 'not ' : ''}synced!`); if (settingsIsSynced) { - if (document.getElementById('clicker')) document.getElementById('clicker').classList = r.settings['layout'] || ''; + if (document.querySelector(".clicker")) document.querySelector(".clicker").classList = 'clicker ' + (r.settings['layout'] || ''); if (returnFunction) returnFunction(); return; } @@ -195,7 +195,7 @@ export async function sync(hideWelcome = true, returnFunction = null) { ui.reportBugModal(null, String(error.stack)); } }); - if (document.getElementById('clicker')) document.getElementById('clicker').classList = r.settings['layout'] || ''; + if (document.querySelector(".clicker")) document.querySelector(".clicker").classList = 'clicker ' + (r.settings['layout'] || ''); } window.location.reload(); }) @@ -737,4 +737,10 @@ export async function refundThemes(themes = []) { console.error(e); if (!e.message || (e.message && (e.message.includes('NetworkError') || !e.message.includes(".")))) ui.view("api-fail"); }); +} + +export function getDailyThemeImageUrl(imageUrl) { + const match = /^https:\/\/img\.peapix\.com\/([a-f0-9]{32})_640\.jpg$/.exec(imageUrl); + if (!match) throw new Error("Invalid daily theme thumbnail URL"); + return `${domain}/daily_theme_image/${match[1]}`; } \ No newline at end of file diff --git a/src/modules/pwa-install.js b/src/modules/pwa-install.js new file mode 100644 index 0000000..c06264e --- /dev/null +++ b/src/modules/pwa-install.js @@ -0,0 +1,38 @@ +const { alert } = await import("./ui.js"); + +const appDisplayModes = ["standalone", "minimal-ui", "fullscreen", "window-controls-overlay"].map((mode) => window.matchMedia(`(display-mode: ${mode})`)); +const isAppWindow = () => (navigator.standalone === true) || appDisplayModes.some((mode) => mode.matches); +const button = document.querySelector("[data-install-pwa]"); +let installPrompt = null; +let installedThisSession = false; + +window.addEventListener("beforeinstallprompt", (event) => { + button?.removeAttribute('hidden'); + event.preventDefault(); + installPrompt = event; + installedThisSession = false; +}); + +window.addEventListener("appinstalled", () => { + installPrompt = null; + installedThisSession = true; +}); + +button?.addEventListener("click", async () => { + if (isAppWindow()) return; + if (!installPrompt) return; + const prompt = installPrompt; + installPrompt = null; + button.disabled = true; + try { + await prompt.prompt(); + await prompt.userChoice; + } catch (error) { + console.error("PWA installation prompt failed", error); + const appleMobile = /iPad|iPhone|iPod/.test(navigator.userAgent) || ((navigator.platform === "MacIntel") && (navigator.maxTouchPoints > 1)); + if (installedThisSession && !appleMobile) button?.setAttribute('hidden', 'true'); + if (appleMobile) alert("Install App", "To install, open this page in Safari, tap Share, then Add to Home Screen. If already installed, open the app from your home screen."); + } finally { + button.disabled = false; + } +}); diff --git a/src/modules/service-worker.js b/src/modules/service-worker.js index 2be917b..8872cdb 100644 --- a/src/modules/service-worker.js +++ b/src/modules/service-worker.js @@ -1,14 +1,10 @@ -const MANIFEST_URL = "/manifest.webmanifest"; const DEFAULT_THEME_COLOR = "#fafafa"; -let manifestTemplatePromise = null; -let manifestObjectUrl = null; - export function registerServiceWorker(version) { - if (!import.meta.env.PROD || !("serviceWorker" in navigator)) return; + if (!("serviceWorker" in navigator) || !window.isSecureContext) return; const register = () => { - navigator.serviceWorker.register(`/service-worker.js?v=${encodeURIComponent(version)}`, { scope: "/" }).catch((error) => { + navigator.serviceWorker.register(`/service-worker.js?v=${encodeURIComponent(version)}${import.meta.env.DEV ? "&dev=1" : ""}`, { scope: "/" }).catch((error) => { console.error("Service worker registration failed", error); }); syncPwaTheme().catch((error) => { @@ -28,7 +24,6 @@ export async function syncPwaTheme() { const backgroundColor = getThemeBackgroundColor(); setThemeColorMeta(backgroundColor); - await setManifestTheme(backgroundColor); } function getThemeBackgroundColor() { @@ -49,42 +44,3 @@ function setThemeColorMeta(color) { meta.setAttribute("content", color); } -async function setManifestTheme(color) { - const manifest = await getManifestTemplate(); - if (!manifest) return; - - const updatedManifest = { - ...manifest, - theme_color: color, - background_color: color, - }; - - const link = ensureManifestLink(); - const blob = new Blob([JSON.stringify(updatedManifest)], { type: "application/manifest+json" }); - - if (manifestObjectUrl) { - URL.revokeObjectURL(manifestObjectUrl); - } - manifestObjectUrl = URL.createObjectURL(blob); - link.setAttribute("href", manifestObjectUrl); -} - -async function getManifestTemplate() { - if (!manifestTemplatePromise) { - manifestTemplatePromise = fetch(MANIFEST_URL, { cache: "no-store" }) - .then((response) => (response.ok ? response.json() : null)) - .catch(() => null); - } - return manifestTemplatePromise; -} - -function ensureManifestLink() { - let link = document.querySelector('link[rel="manifest"]'); - if (!link) { - link = document.createElement("link"); - link.setAttribute("rel", "manifest"); - link.setAttribute("href", MANIFEST_URL); - document.head.appendChild(link); - } - return link; -} \ No newline at end of file diff --git a/src/modules/ui.css b/src/modules/ui.css index 2600032..43a1d06 100644 --- a/src/modules/ui.css +++ b/src/modules/ui.css @@ -208,7 +208,7 @@ div[data-color-input] > div[data-color-preview]:hover { } } -#loader { +.loader { position: fixed; top: 0; left: 0; @@ -223,11 +223,11 @@ div[data-color-input] > div[data-color-preview]:hover { transition: 0.5s opacity; } -#loader > * { +.loader > * { animation: fadeIn 1s ease-in-out infinite alternate; } -#loader.active { +.loader.active { display: flex; opacity: 1; transition: 0.5s opacity; diff --git a/src/modules/ui.js b/src/modules/ui.js index 3d0aaee..52f0d63 100644 --- a/src/modules/ui.js +++ b/src/modules/ui.js @@ -741,12 +741,12 @@ export function toast(message, duration = 3000, type = "info", icon = null) { } export function startLoader() { - const loader = document.getElementById("loader"); + const loader = document.querySelector(".loader"); if (loader) loader.classList.add("active"); } export function stopLoader() { - const loader = document.getElementById("loader"); + const loader = document.querySelector(".loader"); if (loader) loader.classList.remove("active"); } diff --git a/src/themes/butterfly/butterfly.css b/src/themes/butterfly/butterfly.css index 90c290a..a5dfdb0 100644 --- a/src/themes/butterfly/butterfly.css +++ b/src/themes/butterfly/butterfly.css @@ -29,6 +29,9 @@ body[data-theme="butterfly"] { background-image: url("./background.png"); +} + +body[data-theme="butterfly"] .clicker { font-family: "typerwriter"; font-weight: normal; } @@ -37,11 +40,11 @@ body[data-theme="butterfly"] #virtual-clicker-butterfly { display: unset; } -body[data-theme="butterfly"] #header > .virtual-clicker-logo { +body[data-theme="butterfly"] .header > .virtual-clicker-logo { display: none; } -body[data-theme="butterfly"] :is(#header, #clicker) { +body[data-theme="butterfly"] :is(.header, .clicker) { background-color: transparent; } @@ -49,17 +52,17 @@ body[data-theme="butterfly"] [data-button-select] { background-color: var(--surface-color); } -body[data-theme="butterfly"] :is(#header, #clicker > h1, #clicker > label) { +body[data-theme="butterfly"] :is(.header, .clicker > h1, .clicker > label) { color: #ffffff; } -body[data-theme="butterfly"] #header h1 { +body[data-theme="butterfly"] .header h1 { font-family: "ultinoid"; font-size: 2.5rem; font-weight: normal; } -body[data-theme="butterfly"] #clicker > h1 { +body[data-theme="butterfly"] .clicker > h1 { animation: glow 1.5s linear infinite alternate; } diff --git a/src/themes/themes.css b/src/themes/themes.css index 9bda763..d2f12c1 100644 --- a/src/themes/themes.css +++ b/src/themes/themes.css @@ -1,3 +1,43 @@ +[data-theme="daily"] { + color-scheme: var(--daily-theme-color-scheme, dark); + --text-color: var(--daily-theme-text-color, #ffffff); + --background-color: var(--daily-theme-background-color, #243447); + --surface-color: var(--daily-theme-surface-color, #182330); + --accent-color: var(--daily-theme-accent-color, #91c8ef); + --accent-text-color: var(--daily-theme-accent-text-color, #182330); + --error-color: var(--daily-theme-error-color, #eb324b); + background: var(--daily-theme-full, none) center / cover no-repeat !important; +} + +[data-modal-page="store"] .theme-item[data-theme="daily"] { + grid-area: 1 / 1 / 3 / 2; +} + +[data-modal-page="store"] :is(.theme-item, .promo)[data-theme="daily"] .daily-theme-copyright, +.daily-theme-wallpaper-copyright { + color: #ffffff; + background: #000c; + padding: 4px 8px; + border-radius: 4px; + font-size: 11px; + line-height: 1.4; + text-shadow: none; +} + +.daily-theme-wallpaper-copyright { + display: none; + position: fixed; + bottom: max(8px, env(safe-area-inset-bottom)); + right: max(8px, env(safe-area-inset-right)); + max-width: min(80vw, 480px); + z-index: -1; + pointer-events: none; +} + +body[data-theme="daily"] > .daily-theme-wallpaper-copyright { + display: block; +} + [data-theme="classic"] { color-scheme: dark; --text-color: #ffffff; @@ -312,7 +352,7 @@ body[data-theme="dune-2"] #atreides-logo { display: unset; } -body[data-theme="dune-2"] #header>.virtual-clicker-logo { +body[data-theme="dune-2"] .header > .virtual-clicker-logo { display: none; } @@ -326,7 +366,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #054dc6; --accent-text-color: #7bace5; --error-color: #eb324b; - background: url(/store/backdrop/jellyfish.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/jellyfish.png) !important; } [data-theme="jellyfish-2"] { @@ -337,7 +377,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #575ed3; --accent-text-color: #ffffff; --error-color: #eb324b; - background: url(/store/backdrop/jellyfish-2.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/jellyfish-2.png) !important; } [data-theme="jellyfish-pro"] { @@ -348,7 +388,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #575ed3; --accent-text-color: #ffffff; --error-color: #eb324b; - background: url(/store/backdrop/jellyfish-pro.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/jellyfish-pro.png) !important; } [data-theme="black-sand"] { @@ -359,7 +399,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #444d58; --accent-text-color: #ffffff; --error-color: #eb324b; - background: url(/store/backdrop/black-sand.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/black-sand.png) !important; } [data-theme="blizzard-pro"] { @@ -370,7 +410,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #71b2d3; --accent-text-color: #2c2c2c; --error-color: #eb324b; - background: url(/store/backdrop/blizzard-pro.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/blizzard-pro.png) !important; } [data-theme="sage-pro"] { @@ -381,7 +421,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #5c8a5b; --accent-text-color: #2c2c2c; --error-color: #eb324b; - background: url(/store/backdrop/sage-pro.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/sage-pro.png) !important; } [data-theme="sandy-beach"] { @@ -392,7 +432,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #a58d6b; --accent-text-color: #2c2c2c; --error-color: #eb324b; - background: url(/store/backdrop/sandy-beach.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/sandy-beach.png) !important; } [data-theme="rose-pro"] { @@ -403,7 +443,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #ff6767; --accent-text-color: #ffffff; --error-color: #eb324b; - background: url(/store/backdrop/rose-pro.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/rose-pro.png) !important; } [data-theme="lavender-pro"] { @@ -414,7 +454,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #866ed5; --accent-text-color: #d7d7d7; --error-color: #eb324b; - background: url(/store/backdrop/lavender-pro.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/lavender-pro.png) !important; } [data-theme="stealth-2"] { @@ -425,7 +465,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #4c4746; --accent-text-color: #f2f2f2; --error-color: #eb324b; - background: url(/store/backdrop/stealth-2.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/stealth-2.png) !important; } [data-theme="synthwave-pro"] { @@ -437,7 +477,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-text-color: #2de2e6; --error-color: #ea1b2d; --backlight-color: #ff00ec; - background: url(/store/backdrop/synthwave-pro.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/synthwave-pro.png) !important; } [data-theme="aeroplane"] { @@ -449,7 +489,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-text-color: #1c1c1c; --error-color: #eb324b; --backlight-color: #141414; - background: url(/store/backdrop/aeroplane.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/aeroplane.png) !important; } [data-theme="lavender-2"] { @@ -460,7 +500,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #825895; --accent-text-color: #e4e4e4; --error-color: #eb324b; - background: url(/store/backdrop/lavender-2.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/lavender-2.png) !important; } [data-theme="rainy-day"] { @@ -471,7 +511,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #2d8ba5; --accent-text-color: #e4e4e4; --error-color: #eb324b; - background: url(/store/backdrop/rainy-day.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/rainy-day.png) !important; } [data-theme="snow"] { @@ -482,7 +522,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #9cb7d2; --accent-text-color: #474747; --error-color: #eb324b; - background: url(/store/backdrop/snow.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/snow.png) !important; } [data-theme="shells"] { @@ -493,7 +533,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #b28766; --accent-text-color: #2c2c2c; --error-color: #eb324b; - background: url(/store/backdrop/shells.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/shells.png) !important; } [data-theme="tides"] { @@ -504,7 +544,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #2e9993; --accent-text-color: #fafafa; --error-color: #eb324b; - background: url(/store/backdrop/tides.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/tides.png) !important; } [data-theme="northern-lights"] { @@ -515,7 +555,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #1e644a; --accent-text-color: #e4e4e4; --error-color: #eb324b; - background: url(/store/backdrop/northern-lights.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/northern-lights.png) !important; } [data-theme="galaxy"] { @@ -526,7 +566,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #6269af; --accent-text-color: #dee3fc; --error-color: #eb324b; - background: url(/store/backdrop/galaxy.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/galaxy.png) !important; } [data-theme="galaxy-2"] { @@ -537,7 +577,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #bb9890; --accent-text-color: #e5e4cd; --error-color: #eb324b; - background: url(/store/backdrop/galaxy-2.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/galaxy-2.png) !important; } [data-theme="milky-way"] { @@ -548,7 +588,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #a5978e; --accent-text-color: #171719; --error-color: #eb324b; - background: url(/store/backdrop/milky-way.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/milky-way.png) !important; } [data-theme="siege"] { @@ -559,7 +599,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #b57c49; --accent-text-color: #ede5a9; --error-color: #cb5a4f; - background: url(/store/backdrop/siege.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/siege.png) !important; } [data-theme="snowflakes"] { @@ -570,7 +610,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #73b9dc; --accent-text-color: #e7e4ec; --error-color: #eb324b; - background: url(/store/backdrop/snowflakes.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/snowflakes.png) !important; } [data-theme="chocolate-hearts"] { @@ -582,7 +622,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-text-color: #ffffff; --error-color: #b90024; --backlight-color: #d50035; - background: url(/store/backdrop/chocolate-hearts.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/chocolate-hearts.png) !important; } [data-theme="chocolate-hearts-2"] { @@ -593,7 +633,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #cc0039; --accent-text-color: #ffffff; --error-color: #b90024; - background: url(/store/backdrop/chocolate-hearts-2.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/chocolate-hearts-2.png) !important; } [data-theme="rocket-league"] { @@ -604,7 +644,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #eb9100; --accent-text-color: #ffffff; --error-color: #b90024; - background: url(/store/backdrop/rocket-league.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/rocket-league.png) !important; } [data-theme="rocket-league-2"] { @@ -615,7 +655,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #2567ff; --accent-text-color: #ffffff; --error-color: #b90024; - background: url(/store/backdrop/rocket-league-2.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/rocket-league-2.png) !important; } [data-theme="rocket-league-ice"] { @@ -626,7 +666,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #db131f; --accent-text-color: #2c2c2c; --error-color: #db131f; - background: url(/store/backdrop/rocket-league-ice.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/rocket-league-ice.png) !important; } [data-theme="rocket-league-synthwave"] { @@ -637,7 +677,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #811b80; --accent-text-color: #2de2e6; --error-color: #ea1b2d; - background: url(/store/backdrop/rocket-league-synthwave.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/rocket-league-synthwave.png) !important; } [data-theme="friends"] { @@ -648,7 +688,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #660940; --accent-text-color: #f8f3d9; --error-color: #ea1b2d; - background: url(/store/backdrop/friends.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/friends.png) !important; } [data-theme="lebron"] { @@ -660,7 +700,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-text-color: #000; --error-color: #e21b00; --backlight-color: #6a6665; - background: url(/store/backdrop/lebron.jpg) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/lebron.jpg) !important; } [data-theme="lebron-pro"] { @@ -671,7 +711,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #e21b00; --accent-text-color: #000; --error-color: #e21b00; - background: url(/store/backdrop/lebron-pro.jpg) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/lebron-pro.jpg) !important; } [data-theme="messi"] { @@ -683,7 +723,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-text-color: #000; --error-color: #ee1c36; --backlight-color: #276cf9; - background: url(/store/backdrop/messi.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/messi.png) !important; } [data-theme="messi-neymar"] { @@ -694,7 +734,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #f3dc15; --accent-text-color: #70161d; --error-color: #a21b2a; - background: url(/store/backdrop/messi-neymar.jpg) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/messi-neymar.jpg) !important; } [data-theme="one-piece"] { @@ -705,7 +745,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #9d5053; --accent-text-color: #fff; --error-color: #9d5053; - background: url(/store/backdrop/one-piece.jpg) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/one-piece.jpg) !important; } [data-theme="super-mario"] { @@ -716,7 +756,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #d42404; --accent-text-color: #fff; --error-color: #d42404; - background: url(/store/backdrop/super-mario.jpg) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/super-mario.jpg) !important; } [data-theme="black-ice"] { @@ -728,7 +768,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-text-color: #00110f; --error-color: #eb324b; --backlight-color: #00988a; - background: url(/store/backdrop/black-ice.jpg) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/black-ice.jpg) !important; } [data-theme="mountains"] { @@ -739,7 +779,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #1881c2; --accent-text-color: #fff; --error-color: #eb324b; - background: url(/store/backdrop/mountains.jpg) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/mountains.jpg) !important; } [data-theme="cherry-blossoms"] { @@ -750,7 +790,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #c673a9; --accent-text-color: #ffffff; --error-color: #9e3b39; - background: url(/store/backdrop/cherry-blossoms.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/cherry-blossoms.png) !important; } [data-theme="cherry-blossoms-2"] { @@ -761,7 +801,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #c85b73; --accent-text-color: #ffffff; --error-color: #c32b28; - background: url(/store/backdrop/cherry-blossoms-2.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/cherry-blossoms-2.png) !important; } [data-theme="fruit"] { @@ -772,7 +812,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #7fb92c; --accent-text-color: #ffffff; --error-color: #f27606; - background: url(/store/backdrop/fruit.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/fruit.png) !important; } [data-theme="apple"] { @@ -783,7 +823,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #c4060d; --accent-text-color: #ffffff; --error-color: #fd3135; - background: url(/store/backdrop/apple.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/apple.png) !important; } [data-theme="avocado"] { @@ -794,7 +834,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #202e09; --accent-text-color: #ffffff; --error-color: #eb324b; - background: url(/store/backdrop/avocado.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/avocado.png) !important; } [data-theme="blueberry"] { @@ -805,7 +845,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #719ed7; --accent-text-color: #16213d; --error-color: #eb324b; - background: url(/store/backdrop/blueberry.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/blueberry.png) !important; } [data-theme="banana"] { @@ -816,7 +856,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #c88e00; --accent-text-color: #ffffff; --error-color: #eb324b; - background: url(/store/backdrop/banana.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/banana.png) !important; } [data-theme="coconut"] { @@ -827,7 +867,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #965a38; --accent-text-color: #ffffff; --error-color: #eb324b; - background: url(/store/backdrop/coconut.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/coconut.png) !important; } [data-theme="dragon-fruit"] { @@ -838,7 +878,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #fa4899; --accent-text-color: #ffffff; --error-color: #cc3559; - background: url(/store/backdrop/dragon-fruit.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/dragon-fruit.png) !important; } [data-theme="grape"] { @@ -849,7 +889,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #b9bc01; --accent-text-color: #ffffff; --error-color: #eb324b; - background: url(/store/backdrop/grape.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/grape.png) !important; } [data-theme="kiwi"] { @@ -860,7 +900,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #7a1; --accent-text-color: #ffffff; --error-color: #eb324b; - background: url(/store/backdrop/kiwi.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/kiwi.png) !important; } [data-theme="kumquat"] { @@ -871,7 +911,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #dfd5ab; --accent-text-color: #db8820; --error-color: #eb324b; - background: url(/store/backdrop/kumquat.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/kumquat.png) !important; } [data-theme="lemon"] { @@ -882,7 +922,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #b08a0c; --accent-text-color: #ffffff; --error-color: #eb324b; - background: url(/store/backdrop/lemon.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/lemon.png) !important; } [data-theme="lime"] { @@ -893,7 +933,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #429119; --accent-text-color: #ffffff; --error-color: #eb324b; - background: url(/store/backdrop/lime.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/lime.png) !important; } [data-theme="mango"] { @@ -904,7 +944,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #eca200; --accent-text-color: #ffffff; --error-color: #eb324b; - background: url(/store/backdrop/mango.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/mango.png) !important; } [data-theme="orange"] { @@ -915,7 +955,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #fe5504; --accent-text-color: #ffffff; --error-color: #eb324b; - background: url(/store/backdrop/orange.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/orange.png) !important; } [data-theme="peach"] { @@ -926,7 +966,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #e8c54c; --accent-text-color: #0d0007; --error-color: #eb324b; - background: url(/store/backdrop/peach.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/peach.png) !important; } [data-theme="pear"] { @@ -937,7 +977,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #da9e7a; --accent-text-color: #1d2d13; --error-color: #eb324b; - background: url(/store/backdrop/pear.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/pear.png) !important; } [data-theme="pineapple"] { @@ -948,7 +988,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #e58532; --accent-text-color: #ffffff; --error-color: #97390e; - background: url(/store/backdrop/pineapple.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/pineapple.png) !important; } [data-theme="pomegranate"] { @@ -959,7 +999,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #2a1115; --accent-text-color: #ffffff; --error-color: #901115; - background: url(/store/backdrop/pomegranate.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/pomegranate.png) !important; } [data-theme="raspberry"] { @@ -970,7 +1010,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #650c0f; --accent-text-color: #ffffff; --error-color: #eb324b; - background: url(/store/backdrop/raspberry.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/raspberry.png) !important; } [data-theme="raspberry-blueberry"] { @@ -981,7 +1021,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #1b3550; --accent-text-color: #ffffff; --error-color: #d7011b; - background: url(/store/backdrop/raspberry-blueberry.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/raspberry-blueberry.png) !important; } [data-theme="strawberry-pro"] { @@ -992,7 +1032,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #5a8527; --accent-text-color: #ffffff; --error-color: #c80000; - background: url(/store/backdrop/strawberry-pro.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/strawberry-pro.png) !important; } [data-theme="watermelon"] { @@ -1003,7 +1043,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #efd25b; --accent-text-color: #070500; --error-color: #eb324b; - background: url(/store/backdrop/watermelon.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/watermelon.png) !important; } [data-theme="watermelon-2"] { @@ -1014,7 +1054,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #063857; --accent-text-color: #fff; --error-color: #eb324b; - background: url(/store/backdrop/watermelon-2.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/watermelon-2.png) !important; } [data-theme="watermelon-pro"] { @@ -1025,7 +1065,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #d3190d; --accent-text-color: #fff; --error-color: #eb324b; - background: url(/store/backdrop/watermelon-pro.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/watermelon-pro.png) !important; } [data-theme="coconut-tree"] { @@ -1036,7 +1076,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #5f962b; --accent-text-color: #343427; --error-color: #eb324b; - background: url(/store/backdrop/coconut-tree.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/coconut-tree.png) !important; } [data-theme="calm-palm"] { @@ -1047,7 +1087,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #3c5104; --accent-text-color: #fff; --error-color: #eb324b; - background: url(/store/backdrop/calm-palm.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/calm-palm.png) !important; } [data-theme="japan"] { @@ -1058,7 +1098,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #865e23; --accent-text-color: #fff; --error-color: #eb324b; - background: url(/store/backdrop/japan.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/japan.png) !important; } [data-theme="wood"] { @@ -1069,7 +1109,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #9b5236; --accent-text-color: #f7c080; --error-color: #eb324b; - background: url(/store/backdrop/wood.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/wood.png) !important; } [data-theme="sugar-bites"] { @@ -1080,7 +1120,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #97245d; --accent-text-color: #fff; --error-color: #eb324b; - background: url(/store/backdrop/sugar-bites.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/sugar-bites.png) !important; } [data-theme="cookies"]:not(.force) { @@ -1091,7 +1131,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #d17121; --accent-text-color: #fff; --error-color: #eb324b; - background: url(/store/backdrop/cookies.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/cookies.png) !important; background-size: 100px !important; background-repeat: repeat !important; background-attachment: fixed !important; @@ -1106,7 +1146,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-text-color: #000; --error-color: #eb324b; --backlight-color: #318060; - background: url(/store/backdrop/pandas.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/pandas.png) !important; background-size: 300px !important; background-repeat: repeat !important; background-attachment: fixed !important; @@ -1121,7 +1161,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-text-color: #2b4e1f; --error-color: #eb324b; --backlight-color: #4f9230; - background: url(/store/backdrop/balloon.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/balloon.png) !important; } [data-theme="balloon-pro"] { @@ -1132,7 +1172,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #6fa9f9; --accent-text-color: #111956; --error-color: #eb324b; - background: url(/store/backdrop/balloon-pro.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/balloon-pro.png) !important; } [data-theme="balloons"]:not(.force) { @@ -1143,7 +1183,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #fd62a3; --accent-text-color: #ffcdea; --error-color: #eb324b; - background: url(/store/backdrop/balloons.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/balloons.png) !important; background-size: 300px !important; background-repeat: repeat !important; background-attachment: fixed !important; @@ -1159,7 +1199,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #78cab8; --accent-text-color: #14302a; --error-color: #eb324b; - background: url(/store/backdrop/waterfall.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/waterfall.png) !important; } [data-theme="water"] { @@ -1170,7 +1210,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #2b333e; --accent-text-color: #fff; --error-color: #eb324b; - background: url(/store/backdrop/water.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/water.png) !important; } [data-theme="snowstorm"] { @@ -1181,7 +1221,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #367ec2; --accent-text-color: #fff; --error-color: #eb324b; - background: url(/store/backdrop/snowstorm.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/snowstorm.png) !important; } [data-theme="usa"] { @@ -1193,7 +1233,7 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-text-color: #fff; --error-color: #9f244a; --backlight-color: #9f244a; - background: url(/store/backdrop/usa.png) !important; + background: url(https://assets.vssfalcons.com/store/backdrop/usa.png) !important; } [data-theme="synthwave-terrain"] { @@ -1204,5 +1244,5 @@ body[data-theme="dune-2"] #header>.virtual-clicker-logo { --accent-color: #e68450; --accent-text-color: #fff; --error-color: #c66180; - background: url(/store/backdrop/synthwave-terrain.png) !important; -} \ No newline at end of file + background: url(https://assets.vssfalcons.com/store/backdrop/synthwave-terrain.png) !important; +} diff --git a/src/themes/themes.js b/src/themes/themes.js index 95e61fd..b4c7786 100644 --- a/src/themes/themes.js +++ b/src/themes/themes.js @@ -31,6 +31,145 @@ const customTheme = Object.assign({}, storage.get("custom-theme") || defaultThem const colorizeTrigger = document.querySelector('[data-modal-view="colorize"]'); const colorizeRange = document.getElementById('colorize'); +let dailyImage = null; +let pendingRequest = null; + +function addDailyThemeCopyright(parent) { + const copyright = document.createElement("small"); + copyright.className = "daily-theme-copyright"; + copyright.textContent = dailyImage?.copyright || "Daily image currently unavailable."; + parent.appendChild(copyright); + return copyright; +} + +function dailyColorLuminance(rgb) { + const linear = rgb.map(channel => { + const value = channel / 255; + return (value <= 0.04045) ? (value / 12.92) : (((value + 0.055) / 1.055) ** 2.4); + }); + return (linear[0] * 0.2126) + (linear[1] * 0.7152) + (linear[2] * 0.0722); +} + +function dailyThemePaletteFromPixels({ data, width, height }) { + const buckets = new Map(); + const distance = (a, b) => a.reduce((sum, value, index) => sum + ((value - b[index]) ** 2), 0); + const hex = rgb => "#" + rgb.map(value => Math.round(value).toString(16).padStart(2, "0")).join(""); + const textColor = rgb => { + const luminance = dailyColorLuminance(rgb); + return (((luminance + 0.05) / 0.05) >= (1.05 / (luminance + 0.05))) ? "#000000" : "#ffffff"; + }; + for (let i = 0; i < data.length; i += 4) { + if (data[i + 3] < 128) continue; + const rgb = [data[i], data[i + 1], data[i + 2]]; + const key = rgb.map(value => value >> 5).join(","); + const bucket = buckets.get(key) || { sum: [0, 0, 0], count: 0, edges: 0 }; + rgb.forEach((value, channel) => { bucket.sum[channel] += value; }); + bucket.count++; + const x = (i / 4) % width; + const y = Math.floor((i / 4) / width); + if ((x < (width * 0.1)) || (x >= (width * 0.9)) || (y < (height * 0.1)) || (y >= (height * 0.9))) bucket.edges++; + buckets.set(key, bucket); + } + const colors = [...buckets.values()].map(bucket => ({ + ...bucket, rgb: bucket.sum.map(value => value / bucket.count), + })).sort((a, b) => b.count - a.count); + if (!colors.length) throw new Error("Daily theme image has no opaque pixels"); + const clusters = []; + for (const color of colors) { + const nearest = clusters.reduce((best, cluster) => (!best || (distance(color.rgb, cluster.rgb) < distance(color.rgb, best.rgb))) ? cluster : best, null); + if (nearest && distance(color.rgb, nearest.rgb) < 48 ** 2) { + nearest.rgb = nearest.rgb.map((value, channel) => ((value * nearest.count) + (color.rgb[channel] * color.count)) / (nearest.count + color.count)); + nearest.count += color.count; + nearest.edges += color.edges; + } else { + clusters.push({ rgb: [...color.rgb], count: color.count, edges: color.edges }); + } + } + clusters.sort((a, b) => b.count - a.count); + const accent = clusters[0].rgb.map(Math.round); + const background = [...clusters].sort((a, b) => b.edges - a.edges)[0].rgb.map(Math.round); + const secondary = clusters.find(color => (color.count >= (width * height * 0.03)) && (distance(color.rgb, accent) >= (48 ** 2)))?.rgb || background; + const lightSurface = dailyColorLuminance(background) > 0.45; + const mix = (rgb, target, amount) => rgb.map(value => Math.round(value * (1 - amount) + (target * amount))); + let surface = mix(secondary, lightSurface ? 255 : 0, lightSurface ? 0.8 : 0.65); + if (distance(surface, accent) < (60 ** 2)) surface = mix(secondary, (dailyColorLuminance(accent) > 0.179) ? 0 : 255, 0.8); + const red = colors.find(({ rgb: [r, g, b] }) => (r >= 80) && (r > (g * 1.3)) && (r > (b * 1.15)) && ((r - Math.max(g, b)) > 35)); + const text = textColor(surface); + return { + "text-color": text, + "background-color": hex(background), + "surface-color": hex(surface), + "accent-color": hex(accent), + "accent-text-color": textColor(accent), + "error-color": red ? hex(red.rgb) : "#eb324b", + "color-scheme": (text === "#000000") ? "light" : "dark", + }; +} + +async function extractDailyThemePalette(url) { + const response = await fetch(auth.getDailyThemeImageUrl(url), { signal: AbortSignal.timeout(10000) }); + if (!response.ok) throw new Error("Daily theme image unavailable"); + const bitmap = await createImageBitmap(await response.blob()); + try { + const canvas = document.createElement("canvas"); + const scale = 64 / Math.max(bitmap.width, bitmap.height); + canvas.width = Math.max(1, Math.round(bitmap.width * scale)); + canvas.height = Math.max(1, Math.round(bitmap.height * scale)); + const context = canvas.getContext("2d", { willReadFrequently: true }); + if (!context) throw new Error("Daily theme canvas unavailable"); + context.drawImage(bitmap, 0, 0, canvas.width, canvas.height); + return dailyThemePaletteFromPixels(context.getImageData(0, 0, canvas.width, canvas.height)); + } finally { + bitmap.close(); + } +} + +function refreshDailyTheme() { + if (pendingRequest) return pendingRequest; + pendingRequest = (async () => { + try { + const response = await fetch("https://peapix.com/bing/feed", { + cache: "no-store", + signal: AbortSignal.timeout(10000), + }); + if (!response.ok) throw new Error("Daily theme feed unavailable"); + const feed = await response.json(); + const first = Array.isArray(feed) ? feed[0] : null; + if (!first || (typeof first.copyright !== "string") || !first.copyright.trim()) { + throw new Error("Daily theme attribution missing"); + } + const image = { copyright: first.copyright }; + for (const key of ["fullUrl", "thumbUrl"]) { + const url = new URL(first[key]); + if (url.protocol !== "https:") throw new Error("Invalid daily theme image URL"); + image[key] = url.href; + } + image.palette = ((dailyImage?.fullUrl === image.fullUrl) && (dailyImage?.thumbUrl === image.thumbUrl) && dailyImage.palette) ? dailyImage.palette : (await extractDailyThemePalette(image.thumbUrl).catch(() => null)); + for (const key of ["text-color", "background-color", "surface-color", "accent-color", "accent-text-color", "error-color", "color-scheme"]) { + if (image.palette) { + document.documentElement.style.setProperty(`--daily-theme-${key}`, image.palette[key]); + } else { + document.documentElement.style.removeProperty(`--daily-theme-${key}`); + } + } + dailyImage = image; + document.documentElement.style.setProperty("--daily-theme-full", `url(${JSON.stringify(image.fullUrl)})`); + document.documentElement.style.setProperty("--daily-theme-thumb", `url(${JSON.stringify(image.thumbUrl)})`); + if (!document.querySelector(".daily-theme-wallpaper-copyright")) { + const copyright = addDailyThemeCopyright(document.body); + copyright.classList.add("daily-theme-wallpaper-copyright"); + } + document.querySelectorAll(".daily-theme-copyright").forEach(copyright => { + copyright.textContent = image.copyright; + }); + if (document.body.getAttribute("data-theme") === "daily") await syncPwaTheme().catch(() => null); + } finally { + pendingRequest = null; + } + })(); + return pendingRequest; +} + export function resetTheme() { disableTransitions(); document.body.removeAttribute("data-theme"); @@ -53,6 +192,7 @@ export function enableTransitions() { export async function syncTheme() { const value = storage.get("theme"); + if (value === "daily") refreshDailyTheme(); disableTransitions(); if (value === "custom") { applyCustomTheme(); @@ -191,6 +331,7 @@ export async function renderStore(domain) { const store = document.querySelector(`[data-modal-page="store"]`); if (!store) return; store.innerHTML = ""; + refreshDailyTheme(); await storage.idbReady; var initialTheme = storage.get("theme") || "default"; var checks = (await storage.idbGet("cache"))?.checksCount || 0; @@ -230,6 +371,7 @@ export async function renderStore(domain) { promoInner.classList = 'promo-inner'; promoInner.innerHTML = `${featuredTheme[1] || featuredTheme[0]}`; promo.appendChild(promoInner); + if (featuredTheme[0] === "daily") addDailyThemeCopyright(promo); const promoButton = document.createElement("button"); promoButton.textContent = ownedThemes.includes(featuredTheme[0]) ? "Owned" : "Preview Theme"; promoButton.addEventListener("mouseover", () => { @@ -356,13 +498,14 @@ export async function renderStore(domain) { themeItem.setAttribute("data-theme", value); if (theme[3]) { themeItem.setAttribute('tooltip', `${checks}/${theme[3]} Check${theme[3] == 1 ? '' : 's'}${theme[4].filter(t => !ownedThemes.includes(t[0])) && theme[4].filter(t => !ownedThemes.includes(t[0])).length ? `. You need: ${theme[4].filter(t => !ownedThemes.includes(t[0])).map(t => themes.find(th => th[0] == t)[1] || t).join(', ')}` : ''}`); - if (theme[7]) { - themeItem.setAttribute('style', `background: url('/store/thumb/${theme[0]}.png') center / 100px repeat !important;`); - } else { - themeItem.setAttribute('style', `background: url('/store/thumb/${theme[0]}.png') center / cover no-repeat !important;`); + if ((value !== "daily") && theme[7]) { + themeItem.setAttribute('style', `background: url('https://assets.vssfalcons.com/store/thumb/${theme[0]}.png') center / 100px repeat !important;`); + } else if (value !== "daily") { + themeItem.setAttribute('style', `background: url('https://assets.vssfalcons.com/store/thumb/${theme[0]}.png') center / cover no-repeat !important;`); } } themeItem.innerHTML = `${theme[2] ? `` : ''}${theme[5] ? `` : ''}${theme[6] ? `` : ''}${theme[7] ? `` : ''}${theme[8] ? `` : ''}
${name}

${theme[3] ? `${theme[3]} Check${theme[3] == 1 ? '' : 's'}` : 'Free'}

${theme[4] && theme[4].length ? `Requires: ${theme[4].map(t => themes.find(th => th[0] == t)[1] || t).join(', ')}` : ''}`; + if (value === "daily") addDailyThemeCopyright(themeItem); if (value === initialTheme) themeItem.classList.add('selected'); const themeButton = document.createElement("button"); themeButton.textContent = (value === initialTheme) ? "Applied" : (ownedThemes.includes(theme[0]) ? "Owned" : "Preview"); @@ -631,6 +774,7 @@ try { } else { // Built-in theme const theme = storage.get("theme") || ""; + if (theme === "daily") refreshDailyTheme(); document.body.setAttribute("data-theme", theme); document.getElementById("theme-preview")?.setAttribute("data-theme", theme); selectedTheme = theme; @@ -804,7 +948,7 @@ try { animatedThemeVideo.controlsList = "nodownload"; document.body.appendChild(animatedThemeVideo); } - animatedThemeVideo.src = `/store/animated/${foundTheme[0]}.mp4`; + animatedThemeVideo.src = `https://assets.vssfalcons.com/store/animated/${foundTheme[0]}.mp4`; } else { if (animatedThemeVideo) { animatedThemeVideo.remove(); diff --git a/src/themes/themes.json b/src/themes/themes.json index c7456ec..9c9f8b7 100644 --- a/src/themes/themes.json +++ b/src/themes/themes.json @@ -24,6 +24,7 @@ ["synthwave-2077-solarized", "Synthwave 2077 Solarized", "", 0, [], false, false, false, false], ["casino", "Casino", "", 0, [], false, false, false, false], ["kfc", "KFC", "", 0, [], false, false, false, false], + ["daily", "Daily Theme", "image-alt", 100, [], true, false, false, false], ["butterfly", "GUTS", "stars", 150, [], false, false, false, false], ["sage-pro", "Sage Pro", "leaf", 60, [], false, false, false, false], ["rose-pro", "Rose Pro", "leaf", 60, [], false, false, false, false],