Load layout before Alpine and adjust chat CSS

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-11-20 14:35:23 -03:00
parent 9f1d74b101
commit a823f319c3
3 changed files with 60 additions and 48 deletions

View file

@ -618,7 +618,7 @@ footer {
} }
/* Claude-style scrollbar - thin, subtle, always visible but more prominent on hover */ /* Claude-style scrollbar - thin, subtle, always visible but more prominent on hover */
#messages::-webkit-scrollbar { #messages::-webkit-scrollbar {
width: 8px; width: 12px;
} }
#messages::-webkit-scrollbar-track { #messages::-webkit-scrollbar-track {
background: transparent; background: transparent;
@ -646,7 +646,7 @@ footer {
/* Fallback for other elements */ /* Fallback for other elements */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 6px; width: 10px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background: transparent; background: transparent;

View file

@ -8,8 +8,6 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/livekit-client/dist/livekit-client.umd.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/livekit-client/dist/livekit-client.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script defer src="js/alpine.js"></script>
</head> </head>
<body> <body>
@ -46,7 +44,8 @@
<!-- Sections will be loaded dynamically --> <!-- Sections will be loaded dynamically -->
</div> </div>
<!-- Load Layout Script - module scripts are loaded dynamically --> <!-- Load Layout Script first, then Alpine - module scripts are loaded dynamically -->
<script src="js/layout.js"></script> <script src="js/layout.js"></script>
<script src="js/alpine.js"></script>
</body> </body>
</html> </html>

View file

@ -1,24 +1,24 @@
const sections = { const sections = {
drive: 'drive/drive.html', drive: "drive/drive.html",
tasks: 'tasks/tasks.html', tasks: "tasks/tasks.html",
mail: 'mail/mail.html', mail: "mail/mail.html",
chat: 'chat/chat.html', chat: "chat/chat.html",
}; };
const sectionCache = {}; const sectionCache = {};
function getBasePath() { function getBasePath() {
// All static assets (HTML, CSS, JS) are served from the site root. // All static assets (HTML, CSS, JS) are served from the site root.
// Returning a leading slash ensures URLs like "/drive/drive.html" resolve correctly // Returning a leading slash ensures URLs like "/drive/drive.html" resolve correctly
return '/'; return "/";
} }
// Preload chat CSS to avoid flash on first load // Preload chat CSS to avoid flash on first load
function preloadChatCSS() { function preloadChatCSS() {
const chatCssPath = getBasePath() + 'chat/chat.css'; const chatCssPath = getBasePath() + "chat/chat.css";
const existing = document.querySelector(`link[href="${chatCssPath}"]`); const existing = document.querySelector(`link[href="${chatCssPath}"]`);
if (!existing) { if (!existing) {
const link = document.createElement('link'); const link = document.createElement("link");
link.rel = 'stylesheet'; link.rel = "stylesheet";
link.href = chatCssPath; link.href = chatCssPath;
document.head.appendChild(link); document.head.appendChild(link);
} }
@ -27,43 +27,45 @@ function preloadChatCSS() {
async function loadSectionHTML(path) { async function loadSectionHTML(path) {
const fullPath = getBasePath() + path; const fullPath = getBasePath() + path;
const response = await fetch(fullPath); const response = await fetch(fullPath);
if (!response.ok) throw new Error('Failed to load section: ' + fullPath); if (!response.ok) throw new Error("Failed to load section: " + fullPath);
return await response.text(); return await response.text();
} }
async function switchSection(section) { async function switchSection(section) {
const mainContent = document.getElementById('main-content'); const mainContent = document.getElementById("main-content");
try { try {
const htmlPath = sections[section]; const htmlPath = sections[section];
console.log('Loading section:', section, 'from', htmlPath); console.log("Loading section:", section, "from", htmlPath);
// Resolve CSS path relative to the base directory. // Resolve CSS path relative to the base directory.
const cssPath = getBasePath() + htmlPath.replace('.html', '.css'); const cssPath = getBasePath() + htmlPath.replace(".html", ".css");
// Preload chat CSS if the target is chat // Preload chat CSS if the target is chat
if (section === 'chat') { if (section === "chat") {
preloadChatCSS(); preloadChatCSS();
} }
// Remove any existing section CSS // Remove any existing section CSS
document.querySelectorAll('link[data-section-css]').forEach(link => link.remove()); document
.querySelectorAll("link[data-section-css]")
.forEach((link) => link.remove());
// Load CSS first (skip if already loaded) // Load CSS first (skip if already loaded)
let cssLink = document.querySelector(`link[href="${cssPath}"]`); let cssLink = document.querySelector(`link[href="${cssPath}"]`);
if (!cssLink) { if (!cssLink) {
cssLink = document.createElement('link'); cssLink = document.createElement("link");
cssLink.rel = 'stylesheet'; cssLink.rel = "stylesheet";
cssLink.href = cssPath; cssLink.href = cssPath;
cssLink.setAttribute('data-section-css', 'true'); cssLink.setAttribute("data-section-css", "true");
document.head.appendChild(cssLink); document.head.appendChild(cssLink);
} }
// Hide previously loaded sections and show the requested one // Hide previously loaded sections and show the requested one
// Ensure a container exists for sections // Ensure a container exists for sections
let container = document.getElementById('section-container'); let container = document.getElementById("section-container");
if (!container) { if (!container) {
container = document.createElement('div'); container = document.createElement("div");
container.id = 'section-container'; container.id = "section-container";
mainContent.appendChild(container); mainContent.appendChild(container);
} }
@ -71,28 +73,28 @@ async function switchSection(section) {
if (targetDiv) { if (targetDiv) {
// Section already loaded: hide others, show this one // Section already loaded: hide others, show this one
container.querySelectorAll('.section').forEach(div => { container.querySelectorAll(".section").forEach((div) => {
div.style.display = 'none'; div.style.display = "none";
}); });
targetDiv.style.display = 'block'; targetDiv.style.display = "block";
} else { } else {
// Show loading placeholder inside the container // Show loading placeholder inside the container
const loadingDiv = document.createElement('div'); const loadingDiv = document.createElement("div");
loadingDiv.className = 'loading'; loadingDiv.className = "loading";
loadingDiv.textContent = 'Loading…'; loadingDiv.textContent = "Loading…";
container.appendChild(loadingDiv); container.appendChild(loadingDiv);
// Load HTML // Load HTML
const html = await loadSectionHTML(htmlPath); const html = await loadSectionHTML(htmlPath);
// Create wrapper for the new section // Create wrapper for the new section
const wrapper = document.createElement('div'); const wrapper = document.createElement("div");
wrapper.id = `section-${section}`; wrapper.id = `section-${section}`;
wrapper.className = 'section'; wrapper.className = "section";
wrapper.innerHTML = html; wrapper.innerHTML = html;
// Hide any existing sections // Hide any existing sections
container.querySelectorAll('.section').forEach(div => { container.querySelectorAll(".section").forEach((div) => {
div.style.display = 'none'; div.style.display = "none";
}); });
// Remove loading placeholder // Remove loading placeholder
@ -106,28 +108,37 @@ async function switchSection(section) {
gsap.fromTo( gsap.fromTo(
wrapper, wrapper,
{ opacity: 0 }, { opacity: 0 },
{ opacity: 1, duration: 0.15, ease: 'power2.out' } { opacity: 1, duration: 0.15, ease: "power2.out" },
); );
} }
// Then load JS after HTML is inserted (skip if already loaded) // Then load JS after HTML is inserted (skip if already loaded)
// Resolve JS path relative to the base directory. // Resolve JS path relative to the base directory.
const jsPath = getBasePath() + htmlPath.replace('.html', '.js'); const jsPath = getBasePath() + htmlPath.replace(".html", ".js");
const existingScript = document.querySelector(`script[src="${jsPath}"]`); const existingScript = document.querySelector(`script[src="${jsPath}"]`);
if (!existingScript) { if (!existingScript) {
const script = document.createElement('script'); // Create script and wait for it to load before initializing Alpine
const script = document.createElement("script");
script.src = jsPath; script.src = jsPath;
script.defer = true; script.defer = true;
document.body.appendChild(script);
// Wait for script to load before initializing Alpine
await new Promise((resolve, reject) => {
script.onload = resolve;
script.onerror = reject;
document.body.appendChild(script);
});
} }
window.history.pushState({}, '', `#${section}`);
window.history.pushState({}, "", `#${section}`);
Alpine.initTree(mainContent); Alpine.initTree(mainContent);
const inputEl = document.getElementById('messageInput'); const inputEl = document.getElementById("messageInput");
if (inputEl) { if (inputEl) {
inputEl.focus(); inputEl.focus();
} }
} catch (err) { } catch (err) {
console.error('Error loading section:', err); console.error("Error loading section:", err);
mainContent.innerHTML = `<div class="error">Failed to load ${section} section</div>`; mainContent.innerHTML = `<div class="error">Failed to load ${section} section</div>`;
} }
} }
@ -138,27 +149,29 @@ function getInitialSection() {
let section = window.location.hash.substring(1); let section = window.location.hash.substring(1);
// 2⃣ Fallback to pathname segments (e.g., /chat) // 2⃣ Fallback to pathname segments (e.g., /chat)
if (!section) { if (!section) {
const parts = window.location.pathname.split('/').filter(p => p); const parts = window.location.pathname.split("/").filter((p) => p);
const last = parts[parts.length - 1]; const last = parts[parts.length - 1];
if (['drive', 'tasks', 'mail', 'chat'].includes(last)) { if (["drive", "tasks", "mail", "chat"].includes(last)) {
section = last; section = last;
} }
} }
// 3⃣ As a last resort, inspect the full URL for known sections // 3⃣ As a last resort, inspect the full URL for known sections
if (!section) { if (!section) {
const match = window.location.href.match(/\/(drive|tasks|mail|chat)(?:\.html)?(?:[?#]|$)/i); const match = window.location.href.match(
/\/(drive|tasks|mail|chat)(?:\.html)?(?:[?#]|$)/i,
);
if (match) { if (match) {
section = match[1].toLowerCase(); section = match[1].toLowerCase();
} }
} }
// Default to chat if nothing matches // Default to chat if nothing matches
return section || 'chat'; return section || "chat";
} }
window.addEventListener('DOMContentLoaded', () => { window.addEventListener("DOMContentLoaded", () => {
switchSection(getInitialSection()); switchSection(getInitialSection());
}); });
// Handle browser back/forward navigation // Handle browser back/forward navigation
window.addEventListener('popstate', () => { window.addEventListener("popstate", () => {
switchSection(getInitialSection()); switchSection(getInitialSection());
}); });