diff --git a/index.html b/index.html index 82cd2ef..87c8717 100644 --- a/index.html +++ b/index.html @@ -41,8 +41,14 @@ } .boot-loader { - min-height: 100vh; + position: fixed; + inset: 0; + z-index: 2147483646; + width: 100%; + min-height: 100%; + min-height: 100dvh; display: flex; + flex-direction: column; align-items: center; justify-content: center; direction: rtl; @@ -53,71 +59,36 @@ "Segoe UI", sans-serif; background: - radial-gradient(circle at top, var(--boot-orb), transparent 38%), - linear-gradient( - 135deg, - var(--boot-bg) 0%, - var(--boot-bg-soft) 100% - ); + radial-gradient(ellipse 70% 45% at 50% 28%, var(--boot-orb), transparent 70%), + linear-gradient(180deg, var(--boot-bg) 0%, var(--boot-bg-soft) 100%); color: var(--boot-text); overflow: hidden; } - .boot-loader__card { - position: relative; - width: min(340px, calc(100vw - 48px)); - padding: 30px 22px 28px; - border-radius: 24px; - text-align: center; - background: var(--boot-card); - border: 1px solid var(--boot-border); - box-shadow: 0 18px 48px var(--boot-shadow); - backdrop-filter: blur(10px); + .boot-loader__logo { + height: min(20vh, 160px); + width: auto; + max-width: min(70vw, 280px); + object-fit: contain; + opacity: 0; + transition: opacity 0.28s ease; } - .boot-loader__orb { - position: absolute; - top: -72px; - left: 50%; - width: 150px; - height: 150px; - transform: translateX(-50%); - border-radius: 999px; - background: var(--boot-orb); - filter: blur(8px); - pointer-events: none; + .boot-loader__logo.is-ready { + opacity: 1; } .boot-loader__spinner { - position: relative; - z-index: 1; - width: 56px; - height: 56px; - margin: 0 auto 20px; + position: absolute; + bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px)); + width: 26px; + height: 26px; border-radius: 999px; - border: 4px solid var(--boot-spinner-track); + border: 3px solid var(--boot-spinner-track); border-top-color: var(--boot-accent); animation: boot-spin 0.9s linear infinite; } - .boot-loader__title { - position: relative; - z-index: 1; - margin: 0; - font-size: 19px; - font-weight: 800; - letter-spacing: -0.02em; - } - - .boot-loader__text { - position: relative; - z-index: 1; - margin: 10px 0 0; - color: var(--boot-muted); - font-size: 14px; - line-height: 2; - } - @keyframes boot-spin { to { transform: rotate(360deg); @@ -143,16 +114,25 @@ try { var rawCookie = getCookie(CONFIG_COOKIE_NAME) - if (!rawCookie) return null - - var decoded = decodeURIComponent(rawCookie) - var payload = JSON.parse(decoded) - - return payload && payload.config ? payload.config : null + if (rawCookie) { + var decoded = decodeURIComponent(rawCookie) + var payload = JSON.parse(decoded) + if (payload && payload.config) return payload.config + } } catch (error) { console.warn('Boot theme: failed to read cached config', error) - return null } + + try { + var rawLs = localStorage.getItem('APP_CONFIG_LS_CACHE') + if (!rawLs) return null + var lsPayload = JSON.parse(rawLs) + if (lsPayload && lsPayload.config) return lsPayload.config + } catch (error) { + console.warn('Boot theme: failed to read LS config cache', error) + } + + return null } function normalizeHex(hex) { @@ -238,6 +218,123 @@ document.documentElement.style.setProperty(name, value) } + var BOOT_LOGO_KEY = 'BOOT_LOGO_URL' + var BOOT_LOGO_VERSION_KEY = 'BOOT_LOGO_VERSION' + + function pickAvatarRaw(value) { + if (value == null) return '' + if (typeof value === 'string') return String(value).trim() + if (typeof value === 'object') { + return String(value.full_src || value.url || value.src || value.avatar || '').trim() + } + return '' + } + + function logoCacheValid() { + try { + var v = String(localStorage.getItem('version') || '').trim() + var lv = String(localStorage.getItem(BOOT_LOGO_VERSION_KEY) || '').trim() + if (v && lv && v !== lv) return false + return true + } catch (e) { + return false + } + } + + function readCachedLogoUrl() { + if (!logoCacheValid()) return '' + try { + return String(localStorage.getItem(BOOT_LOGO_KEY) || '').trim() + } catch (e) { + return '' + } + } + + function resolveAbsoluteLogoUrl(raw) { + if (!raw) return '' + if (/^(https?:\/\/|data:|blob:)/i.test(raw)) { + return window.location.protocol === 'https:' ? raw.replace(/^http:\/\//i, 'https://') : raw + } + var path = raw.replace(/^\/+/, '') + return window.location.origin + '/server/' + path + } + + function resolveBootLogoUrl(config) { + var cached = readCachedLogoUrl() + if (cached && /^(data:|blob:)/i.test(cached)) return cached + if (cached && /^(https?:\/\/)/i.test(cached)) return resolveAbsoluteLogoUrl(cached) + + var nested = config && config.user && typeof config.user === 'object' ? config.user : {} + var root = config && typeof config === 'object' ? config : {} + var raw = pickAvatarRaw(nested.avatar) || pickAvatarRaw(root.avatar) || cached + return resolveAbsoluteLogoUrl(raw) + } + + function persistLogoCache(logoUrl) { + try { + var v = String(localStorage.getItem('version') || '').trim() + if (v) localStorage.setItem(BOOT_LOGO_VERSION_KEY, v) + if (!logoUrl) return + localStorage.setItem(BOOT_LOGO_KEY, logoUrl) + if (logoUrl.indexOf('data:') === 0 || typeof fetch !== 'function') return + fetch(logoUrl, { mode: 'cors', credentials: 'omit' }) + .then(function (res) { + return res && res.ok ? res.blob() : Promise.reject() + }) + .then(function (blob) { + if (!blob || blob.size > 450000) return + var reader = new FileReader() + reader.onloadend = function () { + try { + if (typeof reader.result === 'string' && reader.result.indexOf('data:') === 0) { + localStorage.setItem(BOOT_LOGO_KEY, reader.result) + } + } catch (e) {} + } + reader.readAsDataURL(blob) + }) + .catch(function () {}) + } catch (e) {} + } + + function applyBootLogo(config) { + var img = document.getElementById('boot-logo') + if (!img) return + + var logoUrl = resolveBootLogoUrl(config || {}) + if (!logoUrl) { + img.hidden = true + img.removeAttribute('src') + img.classList.remove('is-ready') + return + } + + function markReady() { + img.hidden = false + img.removeAttribute('hidden') + img.classList.add('is-ready') + persistLogoCache(logoUrl) + } + + img.onload = markReady + img.onerror = function () { + img.hidden = true + img.classList.remove('is-ready') + } + + if (img.getAttribute('src') === logoUrl && img.complete && img.naturalWidth > 0) { + markReady() + return + } + + img.hidden = false + img.removeAttribute('hidden') + img.src = logoUrl + if (logoUrl.indexOf('data:') === 0) markReady() + } + + window.__APPLY_BOOT_BRANDING__ = applyBootLogo + function applyBootTheme() { var config = readCachedConfig() var user = config && config.user ? config.user : null @@ -289,6 +386,7 @@ } applyBootTheme() + applyBootLogo(readCachedConfig()) })() @@ -296,15 +394,15 @@
لطفاً چند لحظه صبر کنید...
-