# Wood Paneling & Full-Width Layout Implementation ## Problem Statement The current "wood themes" were implemented as color themes with CSS gradients, but they should be wood paneling textures applied to the dashboard bookshelf background only. Additionally, the app uses constrained width containers (`max-w-7xl`) which limit screen real estate on large monitors, unlike modern bookshelf apps like Audiobookshelf. ## Root Cause Analysis ### Current Implementation Issues 1. **Wood themes are misimplemented:** - Wood themes (`wood-light`, `wood-dark`, `wood-mahogany`) are in the theme dropdown alongside color themes - Applied via CSS gradients in `header.ts` `changeThemeTo()` - Gradients applied to entire `` instead of just bookshelf background - Not integrated with `theme.ts` `applyTheme()` function 2. **Constrained layout:** - Header uses `max-w-7xl mx-auto` container - Dashboard content uses `max-w-7xl mx-auto` container - Other pages use `max-w-7xl mx-auto` containers - Wastes horizontal space on large monitors 3. **No active indicators:** - Theme dropdown doesn't show which theme/wood option is currently active - Users can't tell what's selected without remembering ## Solution Strategy ### Approach: Separate Concerns **Principle:** Wood paneling is a visual treatment for the bookshelf, not a color theme. It should be: - Separate from color themes - Applied only to `#collections-container` on dashboard - Stored in localStorage (not database) - User-selectable from theme dropdown (but sectioned separately) **Layout principle:** Full-width layout for modern app feel, better space utilization. ## Implementation Plan ### Phase 0: Download Wood Textures **Source URLs:** 1. **Light Wood:** https://opengameart.org/content/light-wood-1024x1024 - Author: qubodup - License: CC0 (no attribution required) - File: qubodup-light_wood.png (1.9 MB) - Resolution: 1024x1024 2. **Dark & Mahogany Wood:** https://opengameart.org/content/5-wood-textures - Author: Luke.RUSTLTD - License: CC0 (no attribution required) - File: wood_0.zip (6.5 MB) - contains 5 textures - Select 2 best: darkest and medium/reddish - Resolution: Likely 512x512 or 1024x1024 **Download Steps:** ```bash # Create textures directory mkdir -p web/static/textures # Download light wood cd web/static/textures curl -O https://opengameart.org/sites/default/files/qubodup-light_wood.png mv qubodup-light_wood.png wood-light.png # Download texture pack (dark & mahogany) cd /tmp curl -O https://opengameart.org/sites/default/files/wood_0.zip unzip wood_0.zip # Select and copy best 2 textures (you'll need to review and choose) # Copy selected ones to web/static/textures/wood-dark.png # Copy selected ones to web/static/textures/wood-mahogany.png ``` **Manual Steps:** 1. Extract `wood_0.zip` 2. Review the 5 textures 3. Select darkest → rename to `wood-dark.png` 4. Select medium/reddish → rename to `wood-mahogany.png` 5. Move both to `web/static/textures/` **Optional Optimization:** ```bash # Use ImageOptim (Mac) or FileOptimizer (Windows/Linux) # Or use online: TinyPNG.com # Goal: Keep each under 500 KB for fast loading ``` **Final Files:** - `web/static/textures/wood-light.png` - `web/static/textures/wood-dark.png` - `web/static/textures/wood-mahogany.png` --- ### Phase 1: Remove Wood Themes from Theme System **Goal:** Remove wood themes as color themes from the dropdown #### Files to Modify: **1. `templates/header.templ`** - Remove wood theme buttons (lines 86-99) - This section will be replaced with wood paneling section in Phase 3 **2. `tailwind.config.ts`** - Remove `theme-wood-light`, `theme-wood-dark`, `theme-wood-mahogany` from safelist (lines 21-23) **Before:** ```typescript safelist: [ 'theme-wood-light', 'theme-wood-dark', 'theme-wood-mahogany', // ... other themes ] ``` **After:** ```typescript safelist: [ // Remove wood theme entries // Keep only color themes ] ``` **3. `web/src/header.ts`** - Remove wood theme handling from `changeThemeTo()` function (lines 31-51) - Keep only regular theme handling **Before:** ```typescript const changeThemeTo = (theme: string): void => { if (theme.startsWith('wood-')) { // Apply wood background theme document.body.className = `theme-${theme}`; // Set wood background style let woodGradient = ''; switch (theme) { case 'wood-light': woodGradient = 'linear-gradient(135deg, #deb887 0%, #d2a679 50%, #c9975b 100%)'; break; case 'wood-dark': woodGradient = 'linear-gradient(135deg, #8b7355 0%, #6b5344 50%, #5a4636 100%)'; break; case 'wood-mahogany': woodGradient = 'linear-gradient(135deg, #a0522d 0%, #8b4513 50%, #7a3c10 100%)'; break; } document.body.style.background = woodGradient; document.body.style.backgroundSize = 'cover'; document.body.style.backgroundAttachment = 'fixed'; } else { // Apply regular theme document.body.className = `theme-${theme}`; document.body.style.background = ''; document.body.style.backgroundSize = ''; document.body.style.backgroundAttachment = ''; } // ... rest of function }; ``` **After:** ```typescript const changeThemeTo = (theme: string): void => { // Apply regular theme only document.body.className = `theme-${theme}`; document.body.style.background = ''; document.body.style.backgroundSize = ''; document.body.style.backgroundAttachment = ''; // Save to localStorage localStorage.setItem('theme', theme); // Save to server if logged in const token = localStorage.getItem('token'); if (token) { fetch('/api/auth/theme', { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ theme }) }).catch(err => console.log('Theme save failed', err)); } // Close dropdown const dropdown = document.getElementById('theme-dropdown'); if (dropdown) { dropdown.classList.add('hidden'); } }; ``` --- ### Phase 2: Create Wood Paneling System **Goal:** Create separate wood paneling preference system (localStorage only) #### New File: `web/src/woodPaneling.ts` ```typescript // Wood paneling management functionality type WoodPanelingType = 'none' | 'wood-light' | 'wood-dark' | 'wood-mahogany'; const WOOD_STORAGE_KEY = 'wood-paneling'; // Apply wood paneling to collections container const applyWoodPaneling = (paneling: WoodPanelingType): void => { const container = document.getElementById('collections-container'); if (!container) return; // Remove all wood background classes container.classList.remove('bg-wood-light', 'bg-wood-dark', 'bg-wood-mahogany'); if (paneling !== 'none') { // Add selected wood background class container.classList.add(`bg-${paneling}`); } // Save to localStorage localStorage.setItem(WOOD_STORAGE_KEY, paneling); }; // Load wood paneling from localStorage on page load const loadWoodPaneling = (): void => { const stored = localStorage.getItem(WOOD_STORAGE_KEY) as WoodPanelingType | null; if (stored) { applyWoodPaneling(stored); } else { // Default to none applyWoodPaneling('none'); } }; // Change wood paneling (called from theme dropdown) const changeWoodPaneling = (paneling: WoodPanelingType): void => { applyWoodPaneling(paneling); // Update active indicators updateWoodPanelingIndicators(); // Close dropdown const dropdown = document.getElementById('theme-dropdown'); if (dropdown) { dropdown.classList.add('hidden'); } }; // Update visual indicators for wood paneling buttons const updateWoodPanelingIndicators = (): void => { const currentWood = localStorage.getItem(WOOD_STORAGE_KEY) || 'none'; // Update wood paneling buttons document.querySelectorAll('.wood-paneling-btn').forEach(btn => { const wood = btn.getAttribute('data-wood'); if (wood === currentWood) { // Active state - darker background btn.style.backgroundColor = 'var(--bg-primary)'; } else { // Inactive state btn.style.backgroundColor = 'var(--bg-secondary)'; } }); }; // Make functions available globally (window as any).changeWoodPaneling = changeWoodPaneling; (window as any).loadWoodPaneling = loadWoodPaneling; (window as any).updateWoodPanelingIndicators = updateWoodPanelingIndicators; // Auto-initialize when DOM is ready if (typeof document !== 'undefined') { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { loadWoodPaneling(); updateWoodPanelingIndicators(); }); } else { loadWoodPaneling(); updateWoodPanelingIndicators(); } } ``` **Build:** Add to build system (will be compiled to `web/static/woodPaneling.js`) --- ### Phase 3: Update Header with Wood Paneling Section **Goal:** Replace wood theme buttons with wood paneling section #### File: `templates/header.templ` **Replace wood theme section (lines 86-99) with:** ```templ

Bookshelf Background

``` **Add script includes at bottom of header (before closing script tag):** ```templ ``` --- ### Phase 4: Add Active Indicators for Themes **Goal:** Show which theme and wood paneling option is active #### New File: `web/src/themeDropdown.ts` ```typescript // Theme dropdown active indicator management // Update visual indicators for theme buttons const updateThemeIndicators = (): void => { const currentTheme = localStorage.getItem('theme') || 'tokyo-night'; // Update theme buttons (all buttons with changeThemeTo onclick) document.querySelectorAll('[onclick^="changeThemeTo"]').forEach(btn => { const onclick = btn.getAttribute('onclick') || ''; const match = onclick.match(/changeThemeTo\('(.+?)'\)/); if (match) { const theme = match[1]; if (theme === currentTheme) { // Active state - darker background btn.style.backgroundColor = 'var(--bg-primary)'; } else { // Inactive state btn.style.backgroundColor = 'var(--bg-secondary)'; } } }); }; // Make function available globally (window as any).updateThemeIndicators = updateThemeIndicators; // Update on dropdown toggle const originalToggleThemeDropdown = (window as any).toggleThemeDropdown; if (originalToggleThemeDropdown) { (window as any).toggleThemeDropdown = () => { originalToggleThemeDropdown(); updateThemeIndicators(); (window as any).updateWoodPanelingIndicators?.(); }; } // Update after theme changes const originalChangeThemeTo = (window as any).changeThemeTo; if (originalChangeThemeTo) { (window as any).changeThemeTo = (...args: unknown[]) => { originalChangeThemeTo(...args); updateThemeIndicators(); }; } // Auto-initialize when DOM is ready if (typeof document !== 'undefined') { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', updateThemeIndicators); } else { updateThemeIndicators(); } } ``` **Add script include to `templates/header.templ`:** ```templ ``` --- ### Phase 5: Update Tailwind Config for Wood Backgrounds **Goal:** Add Tailwind utility classes for wood textures #### File: `tailwind.config.ts` **Add to `theme.extend`:** ```typescript export default { // ... existing config theme: { extend: { // ... existing extensions backgroundImage: { 'wood-light': "url('/static/textures/wood-light.png')", 'wood-dark': "url('/static/textures/wood-dark.png')", 'wood-mahogany': "url('/static/textures/wood-mahogany.png')", }, }, }, }; ``` **Regenerate CSS:** ```bash npm run build:css # or whatever the build command is ``` --- ### Phase 6: Apply Full-Width Layout Globally **Goal:** Remove `max-w-7xl` containers from header and all pages #### Files to Modify: **1. `templates/header.templ`** **Line 5 - Remove `max-w-7xl`:** ```templ
``` **2. `templates/dashboard.templ`** **Line 27 - Library selector:** ```templ
``` **Line 67 - Collections container:** ```templ
``` **3. Other pages (remove `max-w-7xl mx-auto` from all content containers):** - `templates/collections.templ` - `templates/progress.templ` - `templates/queue.templ` - `templates/devices.templ` - `templates/analytics.templ` - `templates/conflicts.templ` - `templates/unlinked_books.templ` - `templates/bookshelf.templ` - `templates/profile.templ` - `templates/admin.templ` (see note below) - `templates/admin_library.templ` (see note below) - `templates/admin_users.templ` (see note below) - `templates/docs.templ` **Pattern:** Replace `class="max-w-7xl mx-auto px-4 ..."` with `class="w-full px-4 ..."` **Note:** Keep padding (`px-4`, `py-8`, etc.) for readability. **Special note for ALL admin templates (admin.templ, admin_library.templ, admin_users.templ):** These files were already modified by ADMIN_SIDEBAR_PLAN.md to add sidebar layout wrappers. The current structure for all three is: ```templ
@AdminSidebar(user, currentPath)
(or
in some cases) ``` To apply full-width layout, change the inner div in all three: ```templ
(or
)
``` **Do NOT** modify the `main` element or the flex wrapper in any admin template - these are part of the admin sidebar layout. --- ### Phase 7: Add Inline Script for Dashboard Initialization **Goal:** Ensure wood paneling is applied immediately on dashboard load #### File: `templates/dashboard.templ` **Add inline script after existing scripts (around line 20):** ```templ ``` **This ensures:** - Wood paneling applied before external woodPaneling.js loads - No flash of wrong background - Progressive enhancement: works even if JS fails --- ## Testing Checklist After implementation: ### Wood Paneling - [ ] Wood paneling options appear in theme dropdown under "Bookshelf Background" section - [ ] "None" option removes wood texture - [ ] Selecting "Wood Light" applies light wood texture to dashboard background - [ ] Selecting "Wood Dark" applies dark wood texture to dashboard background - [ ] Selecting "Wood Mahogany" applies mahogany texture to dashboard background - [ ] Texture covers only `#collections-container` area (not header or sidebar) - [ ] Texture looks seamless when repeated - [ ] Wood preference persists across page refreshes (localStorage) - [ ] Active wood option has darker background highlight - [ ] Switching between wood options updates immediately ### Full-Width Layout - [ ] Header uses full width on all pages - [ ] Dashboard uses full width - [ ] Collections page uses full width - [ ] Bookshelf page uses full width - [ ] All other pages use full width - [ ] Content still has appropriate padding for readability - [ ] Layout responsive on mobile devices ### Theme System - [ ] Wood themes removed from theme dropdown - [ ] Color themes still work correctly - [ ] Active color theme has darker background highlight - [ ] Theme switching still saves to server - [ ] No wood theme options in color theme section ### Performance - [ ] Wood textures load quickly (no visible delay on reasonable connection) - [ ] No flash of wrong background on page load - [ ] Texture files are optimized (< 500 KB each) --- ## Files Summary ### New Files Created - `web/src/woodPaneling.ts` - Wood paneling management - `web/src/themeDropdown.ts` - Active indicator management - `web/static/textures/wood-light.png` - Light wood texture - `web/static/textures/wood-dark.png` - Dark wood texture - `web/static/textures/wood-mahogany.png` - Mahogany texture ### Files Modified - `templates/header.templ` - Remove wood themes, add wood paneling section, remove max-width - `tailwind.config.ts` - Remove theme-wood-* from safelist, add wood background images - `web/src/header.ts` - Remove wood theme logic from `changeThemeTo()` - `templates/dashboard.templ` - Remove max-width, add inline initialization script - `templates/*.templ` - Remove `max-w-7xl` from all page templates --- ## Migration Notes ### Breaking Changes - Wood themes removed from theme system - Users with wood theme selected will see tokyo-night (or their last color theme) - Wood paneling preference starts as "none" (users must opt-in) ### Backward Compatibility - Color themes unaffected - localStorage theme preference still works - Server-side theme sync still works ### Database Dependencies - None (wood paneling is localStorage only) ### Performance - Wood textures only loaded when selected - "None" option = no texture load (fastest) - Texture size optimized to ~200-500 KB each ### Progressive Enhancement - Pages work without wood paneling (default "none") - Wood paneling degrades gracefully if JS fails - Color themes work independently of wood paneling --- ## Rollback Plan If issues arise: 1. Remove wood paneling section from header 2. Restore `max-w-7xl` containers in templates 3. Delete wood texture files 4. Revert header.ts to include wood theme logic 5. Wood paneling preference in localStorage will be ignored (harmless) --- ## Future Improvements 1. **Per-page wood paneling:** Allow wood on bookshelf page, not just dashboard 2. **Texture variety:** Add more wood options (oak, pine, walnut) 3. **Texture intensity:** Add opacity slider for subtler effect 4. **Custom textures:** Allow users to upload their own backgrounds 5. **Server-side sync:** Store wood paneling preference in database for cross-device sync 6. **Preview mode:** Show texture preview before applying 7. **High-DPI textures:** Provide 2x versions for retina displays --- ## Attribution All wood textures are CC0 licensed (no attribution required): - **Light wood:** qubodup - https://opengameart.org/content/light-wood-1024x1024 - **Dark/Mahogany wood:** Luke.RUSTLTD - https://opengameart.org/content/5-wood-textures **Optional:** Add to README.md or about page for documentation purposes.