.meganote { font-style: italic; } /* example utility class */ #wiki-multipane { display: grid; grid-template-columns: minmax(25rem, 1fr) minmax(20rem, 1fr); /* main (min 600px) | source (400px) */ grid-template-rows: 1fr; gap: 1rem; } .megapage-grid { /* Remove grid properties - let Material handle its normal layout */ display: block; } /* Source pane positioning - fixed relative to viewport but below header */ #wiki-source-pane { border-left: 1px solid var(--md-default-fg-color--lightest); border-radius: 0.5rem 0 0 0.5rem; position: fixed; top: 2.5rem; /* Below Material's header */ right: 0rem; width: calc(40% - 1.5rem); /* Responsive width minus gap */ min-width: 20rem; height: calc(100vh - 2.5rem); /* Full height minus header and small margin */ padding: 0; background: var(--md-default-bg-color); z-index: 100; overflow: hidden; display: flex; flex-direction: column; } /* Source pane header with controls */ .source-pane-header { display: flex; align-items: center; justify-content: space-between; padding: 0rem; border-bottom: 1px solid var(--md-default-fg-color--lightest); background: var(--md-default-bg-color); flex-shrink: 0; position: absolute; width: 100%; min-width: 20rem; z-index: 1000; opacity: 1.0; } .source-pane-links { display: flex; gap: 0; align-items: center; } .source-pane-controls { display: flex; gap: 0.25rem; align-items: center; } .source-control-btn { background: var(--md-default-accent-fg-color); color: var(--md-default-fg-color); /* border: 1px solid var(--md-default-fg-color--light); */ font-size: 12px; cursor: pointer; padding: 0.25rem 0.5rem; border-radius: 0.25rem; text-decoration: none; display: inline-flex; align-items: center; gap: 0.25rem; transition: background-color 0.2s; } .source-control-btn:hover { background: var(--md-default-fg-color--lightest); text-decoration: none; } .source-control-btn .icon { font-size: 10px; } #source-frame { width: 100%; height: 100%; flex: 1; border: none; border-radius: 0; } /* Adjust main content margin when source pane is visible */ #wiki-multipane:has(#wiki-source-pane[style*="block"]) #wiki-main-pane { margin-right: 1rem; } /* Close button styling */ .close-btn { background: var(--md-accent-fg-color); color: white; border: none; font-size: 16px; cursor: pointer; padding: 0.25rem 0.5rem; border-radius: 0.25rem; line-height: 1; font-weight: bold; } .close-btn:hover { opacity: 0.8; } /* Hide the source pane when there's not enough space for both main (600px) + source (400px) + gap */ @media (max-width: 64rem) { /* 600px + 400px + gap ≈ 1024px */ #wiki-multipane { grid-template-columns: 1fr; } #wiki-source-pane { display: none !important; } #wiki-main-pane { margin-right: 0 !important; } } /* Dark mode adjustments */ @media (prefers-color-scheme: dark) { #wiki-source-pane { border-left-color: var(--md-default-fg-color--lightest); } } .md-footer { display: none; }ove source parameter from hash const currentHash = window.location.hash; const newHash = currentHash .replace(/[#&]source=[^&]*/, '') .replace(/^&/, '#') .replace(/&$/, ''); if (newHash !== currentHash) { history.pushState(null, '', newHash || '#'); } } async function discoverAndPreloadSources() { console.log('🔍 Discovering available source pages...'); // Find all source-link elements on the page const sourceLinks = document.querySelectorAll('a.source-link'); const discoveryPromises = []; for (const link of sourceLinks) { const originalUrl = link.href; const localPath = `/sources/${await slug(originalUrl)}`; // Check if local version exists (do this once during preload) discoveryPromises.push( fetch(localPath, { method: 'HEAD' }) .then(response => { if (response.ok) { sourceCache.registerSource(originalUrl, localPath); console.log(`📍 Registered: ${originalUrl} -> ${localPath}`); } }) .catch(() => { // Local version doesn't exist, that's fine }) ); } await Promise.all(discoveryPromises); console.log(`✅ Discovery complete. Found ${sourceCache.sourceRegistry.size} local sources.`); } async function preloadSource(url) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const content = await response.text(); sourceCache.set(url, content); return content; } catch (error) { console.warn(`Failed to preload source: ${url}`, error); return null; } } async function preloadAllSources() { if (sourceCache.preloadComplete) return; await discoverAndPreloadSources(); // Preload a few of the most recently discovered sources const localSources = Array.from(sourceCache.sourceRegistry.values()).slice(0, 10); // Limit to first 10 if (localSources.length > 0) { console.log(`📦 Preloading ${localSources.length} source pages...`); const preloadPromises = localSources.map(async (localPath) => { if (!sourceCache.has(localPath)) { await preloadSource(localPath); } }); await Promise.all(preloadPromises); console.log(`🚀 Preloaded ${sourceCache.size()} source pages`); } sourceCache.preloadComplete = true; } function createDataUrl(htmlContent) { // Create a data URL for the HTML content const blob = new Blob([htmlContent], { type: 'text/html' }); return URL.createObjectURL(blob); } function updateSourceControls() { const originalBtn = document.querySelector('.source-control-original'); const archiveBtn = document.querySelector('.source-control-archive'); if (!currentSourceUrl || !originalBtn || !archiveBtn) return; // Update original button originalBtn.href = currentSourceUrl; // Update archive button if (sourceCache.isAvailableLocally(currentSourceUrl)) { const localPath = sourceCache.getLocalPath(currentSourceUrl); archiveBtn.href = localPath; archiveBtn.style.display = 'inline-flex'; } else { archiveBtn.style.display = 'none'; } } async function showSourcePane(sourceUrl) { const pane = document.getElementById('wiki-source-pane'); const frame = document.getElementById('source-frame'); if (!hasSpaceForSourcePane()) { window.open(sourceUrl, '_blank'); return; } // Update current source URL for header controls currentSourceUrl = sourceUrl; // Show pane first for immediate feedback if (pane) pane.style.display = 'block'; // Update header controls updateSourceControls(); // Determine target without any network requests let target = sourceUrl; if (sourceCache.isAvailableLocally(sourceUrl)) { target = sourceCache.getLocalPath(sourceUrl); } // Check if we have cached content (instant load) if (sourceCache.has(target)) { const cachedContent = sourceCache.get(target); const dataUrl = createDataUrl(cachedContent); if (frame) frame.src = dataUrl; console.log(`⚡ Instant load from cache: ${target}`); } else { // Only make network request if not cached yet if (target.startsWith('/sources/')) { console.log(`📥 Loading and caching: ${target}`); const content = await preloadSource(target); if (content && frame) { const dataUrl = createDataUrl(content); frame.src = dataUrl; } else if (frame) { frame.src = target; // Fallback } } else { // External URL - load directly if (frame) frame.src = target; } } // Update URL setSourceUrlInHash(sourceUrl); } function hideSourcePane() { const pane = document.getElementById('wiki-source-pane'); if (pane) { pane.style.display = 'none'; currentSourceUrl = null; } removeSourceFromHash(); } function createSourcePaneHeader() { const header = document.createElement('div'); header.className = 'source-pane-header'; const title = document.createElement('span'); title.textContent = 'Source'; title.style.fontWeight = 'bold'; title.style.fontSize = '14px'; const controls = document.createElement('div'); controls.className = 'source-pane-controls'; // Original button const originalBtn = document.createElement('a'); originalBtn.className = 'source-control-btn source-control-original'; originalBtn.target = '_blank'; originalBtn.innerHTML = 'original'; // Archive button const archiveBtn = document.createElement('a'); archiveBtn.className = 'source-control-btn source-control-archive'; archiveBtn.target = '_blank'; archiveBtn.innerHTML = 'archive'; // Close button const closeBtn = document.createElement('button'); closeBtn.className = 'close-btn'; closeBtn.innerHTML = '×'; closeBtn.addEventListener('click', hideSourcePane); controls.appendChild(originalBtn); controls.appendChild(archiveBtn); controls.appendChild(closeBtn); header.appendChild(title); header.appendChild(controls); return header; } /* ---------- main ----------------------------------------------------- */ document.addEventListener('DOMContentLoaded', async () => { const pane = document.getElementById('wiki-source-pane'); // wrapper