From fecb978a5fd44de5bde653ed6701c006afde819f Mon Sep 17 00:00:00 2001 From: buenosairesam Date: Tue, 27 Jan 2026 00:06:29 -0300 Subject: [PATCH] updated sidebar --- build.py | 22 +- soleprint/index.html | 576 +++++++++++++----- soleprint/run.py | 91 ++- soleprint/station/tools/sbwrapper/sidebar.css | 427 +++++-------- soleprint/station/tools/sbwrapper/sidebar.js | 440 ++++++------- 5 files changed, 842 insertions(+), 714 deletions(-) diff --git a/build.py b/build.py index 323413c..09e5b67 100644 --- a/build.py +++ b/build.py @@ -179,10 +179,20 @@ def build_managed(output_dir: Path, cfg_name: str, config: dict): ): copy_path(item, managed_dir / item.name) - # Scripts from ctrl/ -> managed/ctrl/ + # Copy managed app config from cfg/// (e.g., .env, dumps/) + room_managed_cfg = room_cfg / managed_name + if room_managed_cfg.exists(): + log.info(f" Copying {managed_name} config...") + for item in room_managed_cfg.iterdir(): + if item.is_file(): + copy_path(item, managed_dir / item.name, quiet=True) + elif item.is_dir(): + copy_path(item, managed_dir / item.name) + + # Scripts from ctrl/ -> output_dir/ctrl/ (sibling of managed, link, soleprint) room_ctrl = room_cfg / "ctrl" if room_ctrl.exists(): - ctrl_dir = managed_dir / "ctrl" + ctrl_dir = output_dir / "ctrl" ensure_dir(ctrl_dir) for item in room_ctrl.iterdir(): if item.is_file(): @@ -307,13 +317,19 @@ def build_soleprint(output_dir: Path, room: str): log.warning("Model generation failed") -def build(output_dir: Path, cfg_name: str | None = None): +def build(output_dir: Path, cfg_name: str | None = None, clean: bool = True): """Build complete room instance.""" room = cfg_name or "standalone" config = load_config(cfg_name) managed = config.get("managed") log.info(f"\n=== Building {room} ===") + + # Clean output directory first + if clean and output_dir.exists(): + log.info(f"Cleaning {output_dir}...") + shutil.rmtree(output_dir) + ensure_dir(output_dir) if managed: diff --git a/soleprint/index.html b/soleprint/index.html index 06f95b9..20b36a1 100644 --- a/soleprint/index.html +++ b/soleprint/index.html @@ -1,174 +1,426 @@ - + - - - - soleprint - - - - -
- - -

soleprint

-
-

Cada paso deja huella

+ footer { + margin-top: 3rem; + padding-top: 1.5rem; + border-top: 1px solid #333; + font-size: 0.85rem; + color: #666; + } + + + + {% if managed %} + + {% endif %} + +
+ + -
-

Artery

-

Todo lo vital

-
- +

soleprint

+
+

Cada paso deja huella

- - - - - - - - - - - -
-

Atlas

-

Documentación accionable

-
-
+ - - - - - - - - - - - - - - - - -
-

Station

-

Monitores, Entornos y Herramientas

-
-
- + + +
soleprint
+ diff --git a/soleprint/run.py b/soleprint/run.py index 8ceb9e5..1d26421 100644 --- a/soleprint/run.py +++ b/soleprint/run.py @@ -512,20 +512,109 @@ def station_route(path: str): return {"system": "station", "path": path} +# === Sidebar Wrapper (served at /spr/* when proxied) === + + +@app.get("/sidebar.css") +def sidebar_css(): + """Serve sidebar CSS for injection.""" + css_path = SPR_ROOT / "station" / "tools" / "sbwrapper" / "sidebar.css" + if css_path.exists(): + from fastapi.responses import Response + + return Response(content=css_path.read_text(), media_type="text/css") + return Response(content="/* sidebar.css not found */", media_type="text/css") + + +@app.get("/sidebar.js") +def sidebar_js(): + """Serve sidebar JS for injection.""" + js_path = SPR_ROOT / "station" / "tools" / "sbwrapper" / "sidebar.js" + if js_path.exists(): + from fastapi.responses import Response + + return Response( + content=js_path.read_text(), media_type="application/javascript" + ) + return Response( + content="/* sidebar.js not found */", media_type="application/javascript" + ) + + +@app.get("/api/sidebar/config") +def sidebar_config(request: Request): + """Return sidebar configuration for the current room.""" + config = load_config() + managed = config.get("managed", {}) + auth = config.get("auth", {}) + + # Get soleprint URL (where tools are) + host = request.headers.get("host", "localhost") + host_no_port = host.split(":")[0] + scheme = request.headers.get("x-forwarded-proto", "http") + + # Soleprint tools are at /spr/* when accessed via .spr. + soleprint_base = "/spr" + + return { + "room": managed.get("name", "standalone"), + "soleprint_base": soleprint_base, + "auth_enabled": auth.get("enabled", False), + "tools": { + "artery": f"{soleprint_base}/artery", + "atlas": f"{soleprint_base}/atlas", + "station": f"{soleprint_base}/station", + }, + "auth": { + "login_url": f"{soleprint_base}/artery/google/oauth/login", + "status_url": f"{soleprint_base}/artery/google/oauth/status", + "logout_url": f"{soleprint_base}/artery/google/oauth/logout", + }, + } + + # === Main === +def get_managed_url(request: Request, managed: dict) -> str | None: + """ + Derive managed app URL from current host. + + Pattern: .spr. -> . + localhost:port -> None (no managed URL for direct port access) + """ + if not managed: + return None + + host = request.headers.get("host", "localhost") + host_no_port = host.split(":")[0] + + # Check if host matches pattern: .spr. + if ".spr." in host_no_port: + # Replace .spr. with . to get managed app domain + managed_host = host_no_port.replace(".spr.", ".") + scheme = request.headers.get("x-forwarded-proto", "http") + return f"{scheme}://{managed_host}" + + return None + + @app.get("/") def index(request: Request): """Landing page with links to subsystems.""" + config = load_config() + managed = config.get("managed", {}) + managed_url = get_managed_url(request, managed) + return templates.TemplateResponse( "index.html", { "request": request, - # In bare-metal mode, all routes are internal "artery": "/artery", "atlas": "/atlas", "station": "/station", + "managed": managed, + "managed_url": managed_url, }, ) diff --git a/soleprint/station/tools/sbwrapper/sidebar.css b/soleprint/station/tools/sbwrapper/sidebar.css index ea656d1..2d321d2 100755 --- a/soleprint/station/tools/sbwrapper/sidebar.css +++ b/soleprint/station/tools/sbwrapper/sidebar.css @@ -1,296 +1,161 @@ -/* Pawprint Wrapper - Sidebar Styles */ +/* Soleprint Sidebar - Injected Styles */ :root { - --sidebar-width: 320px; - --sidebar-bg: #1e1e1e; - --sidebar-text: #e0e0e0; - --sidebar-accent: #007acc; - --sidebar-border: #333; - --sidebar-shadow: 0 0 20px rgba(0,0,0,0.5); - --card-bg: #2a2a2a; - --card-hover: #3a3a3a; - --success: #4caf50; - --error: #f44336; -} - -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - margin: 0; - padding: 0; + --spr-sidebar-width: 60px; + --spr-sidebar-width-expanded: 280px; + --spr-sidebar-bg: #1a1a1a; + --spr-sidebar-text: #e5e5e5; + --spr-sidebar-text-muted: #a3a3a3; + --spr-sidebar-border: #333; + --spr-sidebar-accent: #d4a574; + --spr-sidebar-hover: #2a2a2a; } /* Sidebar Container */ -#pawprint-sidebar { - position: fixed; - right: 0; - top: 0; - width: var(--sidebar-width); - height: 100vh; - background: var(--sidebar-bg); - color: var(--sidebar-text); - box-shadow: var(--sidebar-shadow); - transform: translateX(100%); - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); - z-index: 9999; - overflow-y: auto; - overflow-x: hidden; - display: flex; - flex-direction: column; +#spr-sidebar { + position: fixed; + left: 0; + top: 0; + width: var(--spr-sidebar-width); + height: 100vh; + background: var(--spr-sidebar-bg); + border-right: 1px solid var(--spr-sidebar-border); + display: flex; + flex-direction: column; + z-index: 99999; + transition: width 0.2s ease; + font-family: + system-ui, + -apple-system, + sans-serif; } -#pawprint-sidebar.expanded { - transform: translateX(0); +#spr-sidebar.expanded { + width: var(--spr-sidebar-width-expanded); } -/* Toggle Button */ -#sidebar-toggle { - position: fixed; - right: 0; - top: 50%; - transform: translateY(-50%); - background: var(--sidebar-bg); - color: var(--sidebar-text); - border: 1px solid var(--sidebar-border); - border-right: none; - border-radius: 8px 0 0 8px; - padding: 12px 8px; - cursor: pointer; - z-index: 10000; - font-size: 16px; - transition: background 0.2s; - box-shadow: -2px 0 8px rgba(0,0,0,0.3); +/* Push page content */ +body.spr-sidebar-active { + margin-left: var(--spr-sidebar-width) !important; + transition: margin-left 0.2s ease; } -#sidebar-toggle:hover { - background: var(--card-hover); +body.spr-sidebar-active.spr-sidebar-expanded { + margin-left: var(--spr-sidebar-width-expanded) !important; } -#sidebar-toggle .icon { - display: block; - transition: transform 0.3s; -} - -#pawprint-sidebar.expanded ~ #sidebar-toggle .icon { - transform: scaleX(-1); -} - -/* Header */ -.sidebar-header { - padding: 20px; - border-bottom: 1px solid var(--sidebar-border); - background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%); -} - -.sidebar-header h2 { - font-size: 18px; - font-weight: 600; - margin-bottom: 4px; - color: var(--sidebar-accent); -} - -.sidebar-header .nest-name { - font-size: 12px; - opacity: 0.7; - text-transform: uppercase; - letter-spacing: 1px; -} - -/* Content */ -.sidebar-content { - flex: 1; - padding: 20px; - overflow-y: auto; -} - -/* Panel */ -.panel { - margin-bottom: 24px; - padding: 16px; - background: var(--card-bg); - border-radius: 8px; - border: 1px solid var(--sidebar-border); -} - -.panel h3 { - font-size: 14px; - font-weight: 600; - margin-bottom: 12px; - color: var(--sidebar-accent); - display: flex; - align-items: center; - gap: 8px; -} - -/* Current User Display */ -.current-user { - padding: 12px; - background: rgba(76, 175, 80, 0.1); - border: 1px solid rgba(76, 175, 80, 0.3); - border-radius: 6px; - margin-bottom: 16px; - font-size: 13px; -} - -.current-user strong { - color: var(--success); - font-weight: 600; -} - -.current-user .logout-btn { - margin-top: 8px; - padding: 6px 12px; - background: rgba(244, 67, 54, 0.1); - border: 1px solid rgba(244, 67, 54, 0.3); - color: var(--error); - border-radius: 4px; - cursor: pointer; - font-size: 12px; - transition: all 0.2s; - width: 100%; -} - -.current-user .logout-btn:hover { - background: rgba(244, 67, 54, 0.2); -} - -/* User Cards */ -.user-cards { - display: flex; - flex-direction: column; - gap: 8px; -} - -.user-card { - display: flex; - align-items: center; - gap: 12px; - padding: 12px; - background: var(--card-bg); - border: 1px solid var(--sidebar-border); - border-radius: 6px; - cursor: pointer; - transition: all 0.2s; -} - -.user-card:hover { - background: var(--card-hover); - border-color: var(--sidebar-accent); - transform: translateX(-2px); -} - -.user-card.active { - background: rgba(0, 122, 204, 0.2); - border-color: var(--sidebar-accent); -} - -.user-card .icon { - font-size: 24px; - width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; - background: rgba(255,255,255,0.05); - border-radius: 50%; -} - -.user-card .info { - flex: 1; -} - -.user-card .label { - display: block; - font-size: 14px; - font-weight: 600; - margin-bottom: 2px; -} - -.user-card .role { - display: block; - font-size: 11px; - opacity: 0.6; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -/* Status Messages */ -.status-message { - padding: 12px; - border-radius: 6px; - font-size: 13px; - margin-bottom: 16px; - border: 1px solid; -} - -.status-message.success { - background: rgba(76, 175, 80, 0.1); - border-color: rgba(76, 175, 80, 0.3); - color: var(--success); -} - -.status-message.error { - background: rgba(244, 67, 54, 0.1); - border-color: rgba(244, 67, 54, 0.3); - color: var(--error); -} - -.status-message.info { - background: rgba(0, 122, 204, 0.1); - border-color: rgba(0, 122, 204, 0.3); - color: var(--sidebar-accent); -} - -/* Loading Spinner */ -.loading { - display: inline-block; - width: 14px; - height: 14px; - border: 2px solid rgba(255,255,255,0.1); - border-top-color: var(--sidebar-accent); - border-radius: 50%; - animation: spin 0.8s linear infinite; -} - -@keyframes spin { - to { transform: rotate(360deg); } -} - -/* Scrollbar */ -#pawprint-sidebar::-webkit-scrollbar { - width: 8px; -} - -#pawprint-sidebar::-webkit-scrollbar-track { - background: #1a1a1a; -} - -#pawprint-sidebar::-webkit-scrollbar-thumb { - background: #444; - border-radius: 4px; -} - -#pawprint-sidebar::-webkit-scrollbar-thumb:hover { - background: #555; -} - -/* Footer */ -.sidebar-footer { - padding: 16px 20px; - border-top: 1px solid var(--sidebar-border); - font-size: 11px; - opacity: 0.5; - text-align: center; -} - -/* Responsive */ -@media (max-width: 768px) { - #pawprint-sidebar { +/* Sidebar Items */ +.spr-sidebar-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 18px; + color: var(--spr-sidebar-text-muted); + text-decoration: none; + transition: all 0.15s; + cursor: pointer; + border: none; + background: none; width: 100%; - } + text-align: left; + font-size: 14px; +} + +.spr-sidebar-item:hover { + background: var(--spr-sidebar-hover); + color: var(--spr-sidebar-text); +} + +.spr-sidebar-item.active { + background: var(--spr-sidebar-accent); + color: #0a0a0a; +} + +.spr-sidebar-item svg { + width: 24px; + height: 24px; + flex-shrink: 0; +} + +.spr-sidebar-item .label { + display: none; + white-space: nowrap; +} + +#spr-sidebar.expanded .spr-sidebar-item .label { + display: inline; +} + +/* Divider */ +.spr-sidebar-divider { + height: 1px; + background: var(--spr-sidebar-border); + margin: 8px 16px; +} + +/* Spacer */ +.spr-sidebar-spacer { + flex: 1; +} + +/* User Section */ +.spr-sidebar-user { + padding: 12px 16px; + border-top: 1px solid var(--spr-sidebar-border); + font-size: 12px; + color: var(--spr-sidebar-text-muted); +} + +.spr-sidebar-user .email { + display: none; + margin-top: 4px; + color: var(--spr-sidebar-text); +} + +#spr-sidebar.expanded .spr-sidebar-user .email { + display: block; +} + +/* Login Button */ +.spr-login-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: #4285f4; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 13px; + width: 100%; + justify-content: center; +} + +.spr-login-btn:hover { + background: #3367d6; +} + +.spr-login-btn svg { + width: 18px; + height: 18px; +} + +/* Header with room name */ +.spr-sidebar-header { + padding: 16px; + border-bottom: 1px solid var(--spr-sidebar-border); +} + +.spr-sidebar-header .room-name { + display: none; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 1px; + color: var(--spr-sidebar-accent); + margin-top: 8px; +} + +#spr-sidebar.expanded .spr-sidebar-header .room-name { + display: block; } diff --git a/soleprint/station/tools/sbwrapper/sidebar.js b/soleprint/station/tools/sbwrapper/sidebar.js index 6cc038c..588e87a 100755 --- a/soleprint/station/tools/sbwrapper/sidebar.js +++ b/soleprint/station/tools/sbwrapper/sidebar.js @@ -1,295 +1,201 @@ -// Soleprint Wrapper - Sidebar Logic +// Soleprint Sidebar - Self-Injecting Script +// This script creates and manages the soleprint sidebar when injected into a managed app -class SoleprintSidebar { - constructor() { - this.config = null; - this.currentUser = null; - this.sidebar = null; - this.toggleBtn = null; - } +(function () { + "use strict"; - async init() { - // Load configuration - await this.loadConfig(); + const SPR_BASE = "/spr"; - // Create sidebar elements - this.createSidebar(); - this.createToggleButton(); + let config = null; + let user = null; + let expanded = localStorage.getItem("spr_sidebar_expanded") === "true"; - // Setup event listeners - this.setupEventListeners(); + // Icons as SVG strings + const icons = { + toggle: ` + + `, + home: ` + + `, + soleprint: ` + + + + + `, + artery: ` + + + `, + atlas: ` + + + `, + station: ` + + + + `, + google: ` + + + + + `, + user: ` + + + `, + logout: ` + + `, + }; - // Check if user is already logged in - this.checkCurrentUser(); - - // Load saved sidebar state - this.loadSidebarState(); - } - - async loadConfig() { + async function init() { try { - const response = await fetch("/wrapper/config.json"); - this.config = await response.json(); - console.log("[Soleprint] Config loaded:", this.config.nest_name); - } catch (error) { - console.error("[Soleprint] Failed to load config:", error); - // Use default config - this.config = { - nest_name: "default", - wrapper: { - environment: { - backend_url: "http://localhost:8000", - frontend_url: "http://localhost:3000", - }, - users: [], - }, - }; - } - } + // Load config + const response = await fetch(`${SPR_BASE}/api/sidebar/config`); + config = await response.json(); - createSidebar() { - const sidebar = document.createElement("div"); - sidebar.id = "pawprint-sidebar"; - sidebar.innerHTML = this.getSidebarHTML(); - document.body.appendChild(sidebar); - this.sidebar = sidebar; - } + // Check auth status + await checkAuth(); - createToggleButton() { - const button = document.createElement("button"); - button.id = "sidebar-toggle"; - button.innerHTML = ''; - button.title = "Toggle Soleprint Sidebar (Ctrl+Shift+P)"; - document.body.appendChild(button); - this.toggleBtn = button; - } + // Create sidebar + createSidebar(); - getSidebarHTML() { - const users = this.config.wrapper.users || []; - - return ` - - - - - - `; - } - - setupEventListeners() { - // Toggle button - this.toggleBtn.addEventListener("click", () => this.toggle()); - - // Keyboard shortcut: Ctrl+Shift+P - document.addEventListener("keydown", (e) => { - if (e.ctrlKey && e.shiftKey && e.key === "P") { - e.preventDefault(); - this.toggle(); - } - }); - } - - toggle() { - this.sidebar.classList.toggle("expanded"); - this.saveSidebarState(); - } - - saveSidebarState() { - const isExpanded = this.sidebar.classList.contains("expanded"); - localStorage.setItem("pawprint_sidebar_expanded", isExpanded); - } - - loadSidebarState() { - const isExpanded = - localStorage.getItem("pawprint_sidebar_expanded") === "true"; - if (isExpanded) { - this.sidebar.classList.add("expanded"); - } - } - - showStatus(message, type = "info") { - const container = document.getElementById("status-container"); - const statusDiv = document.createElement("div"); - statusDiv.className = `status-message ${type}`; - statusDiv.textContent = message; - container.innerHTML = ""; - container.appendChild(statusDiv); - - // Auto-remove after 5 seconds - setTimeout(() => { - statusDiv.remove(); - }, 5000); - } - - async loginAs(userId) { - const user = this.config.wrapper.users.find((u) => u.id === userId); - if (!user) return; - - this.showStatus(`Logging in as ${user.label}... ⏳`, "info"); - - try { - const backendUrl = this.config.wrapper.environment.backend_url; - const response = await fetch(`${backendUrl}/api/token/`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - username: user.username, - password: user.password, - }), + // Setup keyboard shortcut + document.addEventListener("keydown", (e) => { + if (e.ctrlKey && e.shiftKey && e.key === "S") { + e.preventDefault(); + toggleSidebar(); + } }); - if (!response.ok) { - throw new Error(`Login failed: ${response.status}`); - } + console.log("[Soleprint] Sidebar initialized for room:", config.room); + } catch (error) { + console.error("[Soleprint] Failed to initialize sidebar:", error); + } + } + async function checkAuth() { + if (!config.auth_enabled) return; + + try { + const response = await fetch(`${SPR_BASE}/artery/google/oauth/status`, { + credentials: "include", + }); const data = await response.json(); - - // Store tokens - localStorage.setItem("access_token", data.access); - localStorage.setItem("refresh_token", data.refresh); - - // Store user info - localStorage.setItem( - "user_info", - JSON.stringify({ - username: user.username, - label: user.label, - role: data.details?.role || user.role, - }), - ); - - this.showStatus(`✓ Logged in as ${user.label}`, "success"); - this.currentUser = user; - this.updateCurrentUserDisplay(); - - // Reload page after short delay - setTimeout(() => { - window.location.reload(); - }, 1000); + if (data.authenticated) { + user = data.user; + } } catch (error) { - console.error("[Soleprint] Login error:", error); - this.showStatus(`✗ Login failed: ${error.message}`, "error"); + console.log("[Soleprint] Auth check failed:", error); } } - logout() { - localStorage.removeItem("access_token"); - localStorage.removeItem("refresh_token"); - localStorage.removeItem("user_info"); - - this.showStatus("✓ Logged out", "success"); - this.currentUser = null; - this.updateCurrentUserDisplay(); - - // Reload page after short delay - setTimeout(() => { - window.location.reload(); - }, 1000); - } - - checkCurrentUser() { - const userInfo = localStorage.getItem("user_info"); - if (userInfo) { - try { - this.currentUser = JSON.parse(userInfo); - this.updateCurrentUserDisplay(); - } catch (error) { - console.error("[Soleprint] Failed to parse user info:", error); - } + function createSidebar() { + // Add body class + document.body.classList.add("spr-sidebar-active"); + if (expanded) { + document.body.classList.add("spr-sidebar-expanded"); } + + const sidebar = document.createElement("div"); + sidebar.id = "spr-sidebar"; + if (expanded) sidebar.classList.add("expanded"); + + sidebar.innerHTML = ` +
+ +
${config.room}
+
+ + + ${icons.home} + Home + + +
+ + + ${icons.soleprint} + Soleprint + + + + ${icons.artery} + Artery + + + + ${icons.atlas} + Atlas + + + + ${icons.station} + Station + + +
+ + ${config.auth_enabled ? renderAuthSection() : ""} + `; + + document.body.appendChild(sidebar); } - updateCurrentUserDisplay() { - const display = document.getElementById("current-user-display"); - const username = document.getElementById("current-username"); - - if (this.currentUser) { - display.style.display = "block"; - username.textContent = this.currentUser.username; - - // Highlight active user card - document.querySelectorAll(".user-card").forEach((card) => { - card.classList.remove("active"); - }); - - const activeCard = document.querySelector( - `.user-card[data-user-id="${this.getUserIdByUsername(this.currentUser.username)}"]`, - ); - if (activeCard) { - activeCard.classList.add("active"); - } + function renderAuthSection() { + if (user) { + return ` +
+
+ ${icons.user} + ${user.email} +
+ + ${icons.logout} + Logout + +
+ `; } else { - display.style.display = "none"; + return ` + + `; } } - getUserIdByUsername(username) { - const user = this.config.wrapper.users.find((u) => u.username === username); - return user ? user.id : null; + function toggleSidebar() { + const sidebar = document.getElementById("spr-sidebar"); + expanded = !expanded; + + sidebar.classList.toggle("expanded", expanded); + document.body.classList.toggle("spr-sidebar-expanded", expanded); + + localStorage.setItem("spr_sidebar_expanded", expanded); } -} -// Initialize sidebar when DOM is ready -const soleprintSidebar = new SoleprintSidebar(); + // Expose to global scope for onclick handlers + window.sprSidebar = { + toggle: toggleSidebar, + }; -if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", () => soleprintSidebar.init()); -} else { - soleprintSidebar.init(); -} + // Initialize when DOM is ready + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } -console.log("[Soleprint] Sidebar script loaded"); + console.log("[Soleprint] Sidebar script loaded"); +})();