Compare commits
4 commits
4f654dd95d
...
9b417bf4f2
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b417bf4f2 | |||
| 8bb8f9d96a | |||
| ccee337522 | |||
| a29255c848 |
7 changed files with 252 additions and 30 deletions
|
|
@ -75,6 +75,18 @@
|
||||||
box-shadow: 0 8px 32px rgba(59, 130, 246, 0.3);
|
box-shadow: 0 8px 32px rgba(59, 130, 246, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-logo img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-logo:has(img[src]) {
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
.login-title {
|
.login-title {
|
||||||
font-size: 1.75rem;
|
font-size: 1.75rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|
@ -585,9 +597,12 @@
|
||||||
<body>
|
<body>
|
||||||
<div class="login-container">
|
<div class="login-container">
|
||||||
<div class="login-header">
|
<div class="login-header">
|
||||||
<div class="login-logo">🤖</div>
|
<div class="login-logo" id="login-logo">
|
||||||
<h1 class="login-title">Welcome Back</h1>
|
<img id="login-logo-img" src="" alt="Logo" style="display:none; width:100%; height:100%; object-fit:contain;">
|
||||||
<p class="login-subtitle">
|
<span id="login-logo-default">🤖</span>
|
||||||
|
</div>
|
||||||
|
<h1 class="login-title" id="login-title">Welcome Back</h1>
|
||||||
|
<p class="login-subtitle" id="login-subtitle">
|
||||||
Sign in to your General Bots account
|
Sign in to your General Bots account
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1018,6 +1033,55 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// Load bot config and apply logo/title
|
||||||
|
async function loadBotConfig() {
|
||||||
|
try {
|
||||||
|
// Get bot name from URL path
|
||||||
|
const pathParts = window.location.pathname.split('/');
|
||||||
|
const botName = pathParts[1] || 'default';
|
||||||
|
|
||||||
|
// Fetch bot config
|
||||||
|
const response = await fetch(`/api/bot/config?bot_name=${botName}`);
|
||||||
|
if (response.ok) {
|
||||||
|
const config = await response.json();
|
||||||
|
|
||||||
|
// Apply logo if provided
|
||||||
|
const logo = config.theme_logo || config["theme-logo"] || "";
|
||||||
|
if (logo) {
|
||||||
|
const logoImg = document.getElementById("login-logo-img");
|
||||||
|
const logoDefault = document.getElementById("login-logo-default");
|
||||||
|
const logoContainer = document.getElementById("login-logo");
|
||||||
|
|
||||||
|
if (logoImg) {
|
||||||
|
logoImg.src = logo;
|
||||||
|
logoImg.style.display = "block";
|
||||||
|
}
|
||||||
|
if (logoDefault) {
|
||||||
|
logoDefault.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply title if provided
|
||||||
|
const title = config.theme_title || config["theme-title"] || "";
|
||||||
|
if (title) {
|
||||||
|
const titleEl = document.getElementById("login-title");
|
||||||
|
if (titleEl) {
|
||||||
|
titleEl.textContent = title;
|
||||||
|
}
|
||||||
|
const subtitleEl = document.getElementById("login-subtitle");
|
||||||
|
if (subtitleEl) {
|
||||||
|
subtitleEl.textContent = "Sign in to your account";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Could not load bot config:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load bot config on page load
|
||||||
|
loadBotConfig();
|
||||||
|
|
||||||
// Password visibility toggle
|
// Password visibility toggle
|
||||||
function togglePassword() {
|
function togglePassword() {
|
||||||
const passwordInput = document.getElementById("password");
|
const passwordInput = document.getElementById("password");
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en" data-theme="sentient">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,6 @@
|
||||||
autofocus
|
autofocus
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
id="voiceBtn"
|
|
||||||
title="Voice"
|
|
||||||
data-i18n-title="chat-voice"
|
|
||||||
style="display: none"
|
|
||||||
>
|
|
||||||
🎤
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
id="sendBtn"
|
id="sendBtn"
|
||||||
|
|
@ -166,6 +157,7 @@
|
||||||
var currentStreamingContent = "";
|
var currentStreamingContent = "";
|
||||||
var reconnectAttempts = 0;
|
var reconnectAttempts = 0;
|
||||||
var maxReconnectAttempts = 5;
|
var maxReconnectAttempts = 5;
|
||||||
|
var disconnectNotified = false;
|
||||||
var isUserScrolling = false;
|
var isUserScrolling = false;
|
||||||
|
|
||||||
var mentionState = {
|
var mentionState = {
|
||||||
|
|
@ -898,6 +890,7 @@
|
||||||
ws.onopen = function () {
|
ws.onopen = function () {
|
||||||
console.log("WebSocket connected");
|
console.log("WebSocket connected");
|
||||||
reconnectAttempts = 0;
|
reconnectAttempts = 0;
|
||||||
|
disconnectNotified = false;
|
||||||
updateConnectionStatus("connected");
|
updateConnectionStatus("connected");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -944,7 +937,10 @@
|
||||||
|
|
||||||
ws.onclose = function () {
|
ws.onclose = function () {
|
||||||
updateConnectionStatus("disconnected");
|
updateConnectionStatus("disconnected");
|
||||||
notify("Disconnected from chat server", "error");
|
if (!disconnectNotified) {
|
||||||
|
notify("Disconnected from chat server", "error");
|
||||||
|
disconnectNotified = true;
|
||||||
|
}
|
||||||
if (reconnectAttempts < maxReconnectAttempts) {
|
if (reconnectAttempts < maxReconnectAttempts) {
|
||||||
reconnectAttempts++;
|
reconnectAttempts++;
|
||||||
updateConnectionStatus("connecting");
|
updateConnectionStatus("connecting");
|
||||||
|
|
@ -995,22 +991,64 @@
|
||||||
.then(function(config) {
|
.then(function(config) {
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
|
|
||||||
|
// Get the theme manager's theme for this bot to check if user selected a different theme
|
||||||
|
var botId = botName.toLowerCase();
|
||||||
|
var botTheme = window.ThemeManager ? (
|
||||||
|
// Get bot-specific theme from theme manager's mapping
|
||||||
|
(window.ThemeManager.getAvailableThemes &&
|
||||||
|
window.ThemeManager.getAvailableThemes().find(t => t.id === botId)) ||
|
||||||
|
// Fallback to localStorage
|
||||||
|
localStorage.getItem("gb-theme")
|
||||||
|
) : localStorage.getItem("gb-theme");
|
||||||
|
|
||||||
|
// Check if bot config has a theme-base setting
|
||||||
|
var configThemeBase = config.theme_base || config["theme-base"] || "light";
|
||||||
|
|
||||||
|
// Only use bot config colors if:
|
||||||
|
// 1. No theme has been explicitly selected by user (localStorage empty or default)
|
||||||
|
// 2. AND the bot config's theme-base matches the current theme
|
||||||
|
var localStorageTheme = localStorage.getItem("gb-theme");
|
||||||
|
var useBotConfigColors = !localStorageTheme ||
|
||||||
|
localStorageTheme === "default" ||
|
||||||
|
localStorageTheme === configThemeBase;
|
||||||
|
|
||||||
// Apply colors from config (API returns snake_case)
|
// Apply colors from config (API returns snake_case)
|
||||||
var color1 = config.theme_color1 || config["theme-color1"] || config["Theme Color"] || "#3b82f6";
|
var color1 = config.theme_color1 || config["theme-color1"] || config["Theme Color"] || "#3b82f6";
|
||||||
var color2 = config.theme_color2 || config["theme-color2"] || "#f5deb3";
|
var color2 = config.theme_color2 || config["theme-color2"] || "#f5deb3";
|
||||||
var title = config.theme_title || config["theme-title"] || botName;
|
var title = config.theme_title || config["theme-title"] || botName;
|
||||||
|
var logo = config.theme_logo || config["theme-logo"] || "";
|
||||||
|
|
||||||
// Set CSS variables for colors on document element
|
// Only set bot config colors if user hasn't selected a different theme
|
||||||
document.documentElement.style.setProperty("--chat-color1", color1);
|
if (useBotConfigColors) {
|
||||||
document.documentElement.style.setProperty("--chat-color2", color2);
|
document.documentElement.style.setProperty("--chat-color1", color1);
|
||||||
document.documentElement.style.setProperty("--suggestion-color", color1);
|
document.documentElement.style.setProperty("--chat-color2", color2);
|
||||||
document.documentElement.style.setProperty("--suggestion-bg", color2);
|
document.documentElement.style.setProperty("--suggestion-color", color1);
|
||||||
document.documentElement.style.setProperty("--color1", color1);
|
document.documentElement.style.setProperty("--suggestion-bg", color2);
|
||||||
document.documentElement.style.setProperty("--color2", color2);
|
document.documentElement.style.setProperty("--color1", color1);
|
||||||
document.documentElement.style.setProperty("--primary", color1);
|
document.documentElement.style.setProperty("--color2", color2);
|
||||||
document.documentElement.style.setProperty("--accent", color1);
|
document.documentElement.style.setProperty("--primary", color1);
|
||||||
|
document.documentElement.style.setProperty("--accent", color1);
|
||||||
|
console.log("Bot config colors applied:", {color1: color1, color2: color2});
|
||||||
|
} else {
|
||||||
|
console.log("Bot config colors skipped - user selected custom theme:", localStorageTheme);
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Bot config loaded:", { color1: color1, color2: color2, title: title });
|
// Update logo if provided
|
||||||
|
if (logo) {
|
||||||
|
var logoImg = document.querySelector(".logo-icon-img");
|
||||||
|
if (logoImg) {
|
||||||
|
logoImg.src = logo;
|
||||||
|
logoImg.alt = title || botName;
|
||||||
|
logoImg.style.display = "block";
|
||||||
|
}
|
||||||
|
// Hide the SVG logo when image logo is used
|
||||||
|
var logoSvg = document.querySelector(".logo-icon-svg");
|
||||||
|
if (logoSvg) {
|
||||||
|
logoSvg.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Bot config loaded:", { color1: color1, color2: color2, title: title, logo: logo });
|
||||||
})
|
})
|
||||||
.catch(function(e) {
|
.catch(function(e) {
|
||||||
console.log("Could not load bot config:", e);
|
console.log("Could not load bot config:", e);
|
||||||
|
|
|
||||||
|
|
@ -1096,6 +1096,10 @@ body {
|
||||||
/* Theme logo image - when src is set, show as image */
|
/* Theme logo image - when src is set, show as image */
|
||||||
.logo-icon.logo-icon-img[src:not=""]] {
|
.logo-icon.logo-icon-img[src:not=""]] {
|
||||||
display: block;
|
display: block;
|
||||||
|
width: auto;
|
||||||
|
height: 40px;
|
||||||
|
object-fit: contain;
|
||||||
|
filter: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide SVG when logo image is shown */
|
/* Hide SVG when logo image is shown */
|
||||||
|
|
|
||||||
|
|
@ -72,10 +72,10 @@
|
||||||
|
|
||||||
<!-- SECURITY BOOTSTRAP - MUST load immediately after HTMX -->
|
<!-- SECURITY BOOTSTRAP - MUST load immediately after HTMX -->
|
||||||
<!-- This provides centralized auth for ALL apps: HTMX, fetch, XHR -->
|
<!-- This provides centralized auth for ALL apps: HTMX, fetch, XHR -->
|
||||||
<script src="suite/js/security-bootstrap.js?v=20260207b"></script>
|
<script src="suite/js/security-bootstrap.js?v=20260215a"></script>
|
||||||
|
|
||||||
<!-- ERROR REPORTER - Captures JS errors and sends to server log -->
|
<!-- ERROR REPORTER - Captures JS errors and sends to server log -->
|
||||||
<script src="suite/js/error-reporter.js?v=20260207c"></script>
|
<script src="suite/js/error-reporter.js?v=20260215a"></script>
|
||||||
|
|
||||||
<!-- i18n -->
|
<!-- i18n -->
|
||||||
<script src="suite/js/i18n.js"></script>
|
<script src="suite/js/i18n.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -1286,6 +1286,21 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
if (settingsBtn) settingsBtn.style.display = "";
|
if (settingsBtn) settingsBtn.style.display = "";
|
||||||
if (appsButton) appsButton.style.display = "";
|
if (appsButton) appsButton.style.display = "";
|
||||||
if (notificationsBtn) notificationsBtn.style.display = "";
|
if (notificationsBtn) notificationsBtn.style.display = "";
|
||||||
|
|
||||||
|
// Show omnibox (search bar) when signed in
|
||||||
|
const omnibox = document.getElementById("omnibox");
|
||||||
|
if (omnibox) omnibox.style.display = "";
|
||||||
|
|
||||||
|
// Show Drive, Tasks, CRM, and Calendar navigation when signed in (all instances)
|
||||||
|
const driveTabs = document.querySelectorAll('[data-section="drive"]');
|
||||||
|
const tasksTabs = document.querySelectorAll('[data-section="tasks"]');
|
||||||
|
const crmTabs = document.querySelectorAll('[data-section="crm"]');
|
||||||
|
const calendarTabs = document.querySelectorAll('[data-section="calendar"]');
|
||||||
|
|
||||||
|
driveTabs.forEach(tab => tab.style.display = "");
|
||||||
|
tasksTabs.forEach(tab => tab.style.display = "");
|
||||||
|
crmTabs.forEach(tab => tab.style.display = "");
|
||||||
|
calendarTabs.forEach(tab => tab.style.display = "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadUserProfile() {
|
function loadUserProfile() {
|
||||||
|
|
@ -1360,6 +1375,21 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
if (settingsBtn) settingsBtn.style.display = "none";
|
if (settingsBtn) settingsBtn.style.display = "none";
|
||||||
if (appsButton) appsButton.style.display = "none";
|
if (appsButton) appsButton.style.display = "none";
|
||||||
if (notificationsBtn) notificationsBtn.style.display = "none";
|
if (notificationsBtn) notificationsBtn.style.display = "none";
|
||||||
|
|
||||||
|
// Hide omnibox (search bar) when signed out
|
||||||
|
const omnibox = document.getElementById("omnibox");
|
||||||
|
if (omnibox) omnibox.style.display = "none";
|
||||||
|
|
||||||
|
// Hide Drive, Tasks, CRM, and Calendar navigation when signed out (all instances)
|
||||||
|
const driveTabs = document.querySelectorAll('[data-section="drive"]');
|
||||||
|
const tasksTabs = document.querySelectorAll('[data-section="tasks"]');
|
||||||
|
const crmTabs = document.querySelectorAll('[data-section="crm"]');
|
||||||
|
const calendarTabs = document.querySelectorAll('[data-section="calendar"]');
|
||||||
|
|
||||||
|
driveTabs.forEach(tab => tab.style.display = "none");
|
||||||
|
tasksTabs.forEach(tab => tab.style.display = "none");
|
||||||
|
crmTabs.forEach(tab => tab.style.display = "none");
|
||||||
|
calendarTabs.forEach(tab => tab.style.display = "none");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to load cached user first
|
// Try to load cached user first
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ const ThemeManager = (() => {
|
||||||
const botThemeMap = {
|
const botThemeMap = {
|
||||||
// Default bot uses light theme with brown accents
|
// Default bot uses light theme with brown accents
|
||||||
"default": "light",
|
"default": "light",
|
||||||
// Cristo bot uses mellowgold theme with earth tones
|
// Cristo bot uses mellowgold theme with earth tones (perfect for sanctuary/church)
|
||||||
"cristo": "mellowgold",
|
"cristo": "mellowgold",
|
||||||
// Salesianos bot uses light theme with blue accents
|
// Salesianos bot uses light theme with blue accents
|
||||||
"salesianos": "light",
|
"salesianos": "light",
|
||||||
|
|
@ -68,6 +68,8 @@ const ThemeManager = (() => {
|
||||||
if (!theme.file) {
|
if (!theme.file) {
|
||||||
currentThemeId = "default";
|
currentThemeId = "default";
|
||||||
localStorage.setItem("gb-theme", "default");
|
localStorage.setItem("gb-theme", "default");
|
||||||
|
// Re-enable sentient theme for default
|
||||||
|
document.documentElement.setAttribute("data-theme", "sentient");
|
||||||
updateDropdown();
|
updateDropdown();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -75,13 +77,92 @@ const ThemeManager = (() => {
|
||||||
const link = document.createElement("link");
|
const link = document.createElement("link");
|
||||||
link.id = "theme-css";
|
link.id = "theme-css";
|
||||||
link.rel = "stylesheet";
|
link.rel = "stylesheet";
|
||||||
link.href = `/public/themes/${theme.file}`;
|
link.href = `/suite/public/themes/${theme.file}`;
|
||||||
link.onload = () => {
|
link.onload = () => {
|
||||||
console.log("✓ Theme loaded:", theme.name);
|
console.log("✓ Theme loaded:", theme.name);
|
||||||
currentThemeId = id;
|
currentThemeId = id;
|
||||||
localStorage.setItem("gb-theme", id);
|
localStorage.setItem("gb-theme", id);
|
||||||
updateDropdown();
|
|
||||||
subscribers.forEach((cb) => cb({ themeId: id, themeName: theme.name }));
|
// Keep data-theme="sentient" on html so CSS selectors work
|
||||||
|
// The inline styles will override the colors
|
||||||
|
if (!document.documentElement.getAttribute("data-theme")) {
|
||||||
|
document.documentElement.setAttribute("data-theme", "sentient");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove data-theme from body to prevent base.css theme rules from overriding
|
||||||
|
document.body.removeAttribute("data-theme");
|
||||||
|
|
||||||
|
// Small delay to ensure CSS variables are applied
|
||||||
|
setTimeout(() => {
|
||||||
|
// Get the theme's colors from CSS variables
|
||||||
|
const rootStyle = getComputedStyle(document.documentElement);
|
||||||
|
const primary = rootStyle.getPropertyValue("--primary")?.trim() || "#3b82f6";
|
||||||
|
const background = rootStyle.getPropertyValue("--background")?.trim() || "0 0% 100%";
|
||||||
|
const foreground = rootStyle.getPropertyValue("--foreground")?.trim() || "222 47% 11%";
|
||||||
|
const card = rootStyle.getPropertyValue("--card")?.trim() || "0 0% 98%";
|
||||||
|
const border = rootStyle.getPropertyValue("--border")?.trim() || "214 32% 91%";
|
||||||
|
|
||||||
|
// Convert HSL values to hex format for app compatibility
|
||||||
|
const hslToHex = (h, s, l) => {
|
||||||
|
l /= 100;
|
||||||
|
const a = s * Math.min(l, 1 - l) / 100;
|
||||||
|
const f = n => {
|
||||||
|
const k = (n + h / 30) % 12;
|
||||||
|
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||||
|
return Math.round(255 * color).toString(16).padStart(2, '0');
|
||||||
|
};
|
||||||
|
return `#${f(0)}${f(8)}${f(4)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseHsl = (hslStr) => {
|
||||||
|
const match = hslStr.match(/(\d+)\s+(\d+)%\s+(\d+)%/);
|
||||||
|
if (match) {
|
||||||
|
return [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const bgHsl = parseHsl(background);
|
||||||
|
const fgHsl = parseHsl(foreground);
|
||||||
|
const cardHsl = parseHsl(card);
|
||||||
|
const borderHsl = parseHsl(border);
|
||||||
|
|
||||||
|
// Update the app's CSS variables with the theme colors
|
||||||
|
// These inline styles override the theme-sentient.css values
|
||||||
|
if (bgHsl) {
|
||||||
|
const bgHex = hslToHex(...bgHsl);
|
||||||
|
document.documentElement.style.setProperty("--bg", bgHex);
|
||||||
|
document.documentElement.style.setProperty("--primary-bg", `hsl(${background})`);
|
||||||
|
document.documentElement.style.setProperty("--header-bg", bgHex);
|
||||||
|
}
|
||||||
|
if (fgHsl) {
|
||||||
|
const textHex = hslToHex(...fgHsl);
|
||||||
|
document.documentElement.style.setProperty("--text", textHex);
|
||||||
|
document.documentElement.style.setProperty("--primary-fg", `hsl(${foreground})`);
|
||||||
|
}
|
||||||
|
if (cardHsl) {
|
||||||
|
const surfaceHex = hslToHex(...cardHsl);
|
||||||
|
document.documentElement.style.setProperty("--surface", surfaceHex);
|
||||||
|
document.documentElement.style.setProperty("--card-bg", surfaceHex);
|
||||||
|
}
|
||||||
|
if (borderHsl) {
|
||||||
|
const borderHex = hslToHex(...borderHsl);
|
||||||
|
document.documentElement.style.setProperty("--border", borderHex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update ALL color-related CSS variables to match the theme
|
||||||
|
// This overrides any bot config colors
|
||||||
|
document.documentElement.style.setProperty("--chat-color1", `hsl(${primary})`);
|
||||||
|
document.documentElement.style.setProperty("--chat-color2", `hsl(${card})`);
|
||||||
|
document.documentElement.style.setProperty("--suggestion-color", `hsl(${primary})`);
|
||||||
|
document.documentElement.style.setProperty("--suggestion-bg", `hsl(${card})`);
|
||||||
|
document.documentElement.style.setProperty("--color1", `hsl(${primary})`);
|
||||||
|
document.documentElement.style.setProperty("--color2", `hsl(${card})`);
|
||||||
|
|
||||||
|
console.log("✓ Theme colors applied:", { bg: background, primary: primary });
|
||||||
|
updateDropdown();
|
||||||
|
subscribers.forEach((cb) => cb({ themeId: id, themeName: theme.name }));
|
||||||
|
}, 50);
|
||||||
};
|
};
|
||||||
link.onerror = () => console.error("✗ Failed:", theme.name);
|
link.onerror = () => console.error("✗ Failed:", theme.name);
|
||||||
document.head.appendChild(link);
|
document.head.appendChild(link);
|
||||||
|
|
@ -108,6 +189,11 @@ const ThemeManager = (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
// Ensure data-theme is set on html element so CSS selectors work
|
||||||
|
if (!document.documentElement.getAttribute("data-theme")) {
|
||||||
|
document.documentElement.setAttribute("data-theme", "sentient");
|
||||||
|
}
|
||||||
|
|
||||||
// First, load saved bot theme from config.csv (if available)
|
// First, load saved bot theme from config.csv (if available)
|
||||||
loadSavedTheme();
|
loadSavedTheme();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue