Files
bookhoard/WOOD_PANELING_PLAN.md
T
john-okeefe 79d10b5b37 docs: add theme fix and admin sidebar implementation plans
- Add THEME_FIX_PLAN.md: comprehensive plan for theme system consistency
- Add ADMIN_SIDEBAR_PLAN.md: reusable admin sidebar component plan
- Add WOOD_PANELING_PLAN.md: wood paneling and full-width layout plan
2026-02-24 10:24:05 -05:00

686 lines
21 KiB
Markdown

# 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 `<body>` 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
<div class="border-t pt-2 mt-2" style="border-color: var(--border);">
<p class="text-xs mb-2" style="color: var(--text-secondary)">Bookshelf Background</p>
<button onclick="changeWoodPaneling('none')"
class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
style="color: var(--text-primary);"
data-wood="none">
None
</button>
<button onclick="changeWoodPaneling('wood-light')"
class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
style="color: var(--text-primary);"
data-wood="wood-light">
<span class="inline-block w-4 h-4 rounded mr-2"
style="background: url('/static/textures/wood-light.png'); background-size: cover;"></span>
Wood Light
</button>
<button onclick="changeWoodPaneling('wood-dark')"
class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
style="color: var(--text-primary);"
data-wood="wood-dark">
<span class="inline-block w-4 h-4 rounded mr-2"
style="background: url('/static/textures/wood-dark.png'); background-size: cover;"></span>
Wood Dark
</button>
<button onclick="changeWoodPaneling('wood-mahogany')"
class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
style="color: var(--text-primary);"
data-wood="wood-mahogany">
<span class="inline-block w-4 h-4 rounded mr-2"
style="background: url('/static/textures/wood-mahogany.png'); background-size: cover;"></span>
Wood Mahogany
</button>
</div>
```
**Add script includes at bottom of header (before closing script tag):**
```templ
<script src="/static/woodPaneling.js"></script>
```
---
### 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
<script src="/static/themeDropdown.js"></script>
```
---
### 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
<!-- Before -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- After -->
<div class="w-full px-4 sm:px-6 lg:px-8">
```
**2. `templates/dashboard.templ`**
**Line 27 - Library selector:**
```templ
<!-- Before -->
<div class="max-w-7xl mx-auto px-4 py-3 flex items-center justify-between">
<!-- After -->
<div class="w-full px-4 py-3 flex items-center justify-between">
```
**Line 67 - Collections container:**
```templ
<!-- Before -->
<main id="collections-container" class="max-w-7xl mx-auto px-4 py-8">
<!-- After -->
<main id="collections-container" class="w-full px-4 py-8">
```
**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
<div class="flex min-h-screen" style="background-color: var(--bg-primary)">
@AdminSidebar(user, currentPath)
<main class="flex-1 p-8">
<div class="max-w-7xl">
(or <div class="max-w-6xl"> in some cases)
```
To apply full-width layout, change the inner div in all three:
```templ
<!-- Before -->
<div class="max-w-7xl">
(or <div class="max-w-6xl">)
<!-- After -->
<div class="w-full">
```
**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
<script>
// Apply wood paneling immediately (before DOM fully loads)
(function() {
const woodPaneling = localStorage.getItem('wood-paneling') || 'none';
if (woodPaneling !== 'none') {
document.addEventListener('DOMContentLoaded', function() {
const container = document.getElementById('collections-container');
if (container) {
container.classList.add('bg-' + woodPaneling);
}
});
}
})();
</script>
```
**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.