Complete WIP: Architecture refactor.

Mount JSX server side templating for blog posts. Send AppShell conditionally. Maintain support for HMR via HTMLbundles using Bun's native fullstack dev server under an /hmr path. This is only mounted in development and is supported by the onImport Bun plugin. Add DB creation on startup and load pages based on those records.
This commit is contained in:
2026-01-08 05:13:48 -08:00
parent 3abd97702d
commit f46f4667a1
32 changed files with 2779 additions and 353 deletions

View File

@@ -0,0 +1,165 @@
// Theme switching functionality
const LIGHT_THEMES = ['latte', 'solarized-light', 'gruvbox-light'];
const DARK_THEMES = ['frappe', 'macchiato', 'mocha', 'solarized-dark', 'gruvbox-dark', 'nord', 'dracula', 'one-dark', 'tokyo-night'];
const THEME_NAMES = {
latte: 'Catppuccin Latte',
frappe: 'Catppuccin Frappe',
macchiato: 'Catppuccin Macchiato',
mocha: 'Catppuccin Mocha',
'solarized-light': 'Solarized Light',
'solarized-dark': 'Solarized Dark',
'gruvbox-light': 'Gruvbox Light',
'gruvbox-dark': 'Gruvbox Dark',
nord: 'Nord',
dracula: 'Dracula',
'one-dark': 'One Dark',
'tokyo-night': 'Tokyo Night'
};
let currentMode = '';
let THEMES = { light: 'latte', dark: 'mocha' };
function initializeTheme() {
const savedLightTheme = localStorage.getItem('theme-light') || 'latte';
const savedDarkTheme = localStorage.getItem('theme-dark') || 'mocha';
const savedMode = localStorage.getItem('theme-mode');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
THEMES.light = savedLightTheme;
THEMES.dark = savedDarkTheme;
currentMode = savedMode || (prefersDark ? 'dark' : 'light');
updateModeToggle();
updateThemeDropdown();
applyTheme();
}
function applyTheme() {
const theme = THEMES[currentMode];
document.documentElement.setAttribute('data-theme', theme);
updateCurrentThemeDisplay();
}
function updateThemeDropdown() {
const menu = document.getElementById('themeDropdownMenu');
const themes = currentMode === 'light' ? LIGHT_THEMES : DARK_THEMES;
if (menu) {
menu.innerHTML = themes.map(theme =>
'<div class="theme-option" data-theme="' + theme + '">' +
' <div class="theme-name">' + THEME_NAMES[theme] + '</div>' +
' <button class="theme-selector-btn" data-theme="' + theme + '"></button>' +
'</div>'
).join('');
attachThemeOptionListeners();
updateUI();
}
}
function attachThemeOptionListeners() {
document.querySelectorAll('.theme-option').forEach(option => {
option.addEventListener('click', function(e) {
const theme = this.getAttribute('data-theme');
if (theme) selectTheme(theme);
});
});
}
function selectTheme(theme: string) {
THEMES[currentMode] = theme;
localStorage.setItem('theme-' + currentMode, theme);
updateUI();
applyTheme();
}
function updateCurrentThemeDisplay() {
const theme = THEMES[currentMode];
const themeName = THEME_NAMES[theme];
const display = document.getElementById('currentThemeDisplay');
if (display) {
display.textContent = themeName;
}
}
function updateUI() {
const currentTheme = THEMES[currentMode];
document.querySelectorAll('.theme-selector-btn').forEach(function(btn) {
const theme = btn.getAttribute('data-theme');
if (theme) {
btn.classList.toggle('selected', currentTheme === theme);
}
});
}
function updateModeToggle() {
const lightBtn = document.getElementById('lightModeBtn');
const darkBtn = document.getElementById('darkModeBtn');
if (lightBtn) lightBtn.classList.toggle('active', currentMode === 'light');
if (darkBtn) darkBtn.classList.toggle('active', currentMode === 'dark');
}
// Event listeners
function setupEventListeners() {
const lightBtn = document.getElementById('lightModeBtn');
const darkBtn = document.getElementById('darkModeBtn');
const trigger = document.getElementById('themeDropdownTrigger');
const menu = document.getElementById('themeDropdownMenu');
if (lightBtn) {
lightBtn.addEventListener('click', function() {
currentMode = 'light';
localStorage.setItem('theme-mode', 'light');
updateModeToggle();
updateThemeDropdown();
applyTheme();
});
}
if (darkBtn) {
darkBtn.addEventListener('click', function() {
currentMode = 'dark';
localStorage.setItem('theme-mode', 'dark');
updateModeToggle();
updateThemeDropdown();
applyTheme();
});
}
if (trigger && menu) {
trigger.addEventListener('click', function() {
trigger.classList.toggle('open');
menu.classList.toggle('open');
});
}
document.addEventListener('click', function(e) {
const target = e.target as Element;
if (!target.closest('.theme-dropdown-wrapper')) {
if (trigger) trigger.classList.remove('open');
if (menu) menu.classList.remove('open');
}
});
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
if (localStorage.getItem('theme-mode') !== 'light' && localStorage.getItem('theme-mode') !== 'dark') {
currentMode = e.matches ? 'dark' : 'light';
updateModeToggle();
updateThemeDropdown();
applyTheme();
}
});
}
// Initialize on page load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
initializeTheme();
setupEventListeners();
});
} else {
initializeTheme();
setupEventListeners();
}