From aef9b816dbc97e93a65110d8f37c140c22dcdfe5 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 29 Jul 2025 11:53:30 +1200 Subject: [PATCH] Add theme customization system and theme manager Introduces a full browser customization system with support for theme presets, live editing, import/export, and non-destructive design. Adds new documentation for customization, updates the features list, and implements a ThemeManager for loading and managing themes at the application level. Updates home and settings pages to support live theming and preview, and adds four built-in themes (default, forest, ocean, sunset). --- README.md | 1 + documentation/Customization.md | 68 +++++ documentation/FEATURES.md | 13 + renderer/customization.js | 455 +++++++++++++++++++++++++++++++++ renderer/home.css | 16 +- renderer/home.html | 41 ++- renderer/settings.html | 307 ++++++++++++++++++++++ site-history.json | 1 + theme-manager.js | 213 +++++++++++++++ themes/default.json | 17 ++ themes/forest.json | 17 ++ themes/ocean.json | 17 ++ themes/sunset.json | 17 ++ 13 files changed, 1176 insertions(+), 7 deletions(-) create mode 100644 documentation/Customization.md create mode 100644 renderer/customization.js create mode 100644 theme-manager.js create mode 100644 themes/default.json create mode 100644 themes/forest.json create mode 100644 themes/ocean.json create mode 100644 themes/sunset.json diff --git a/README.md b/README.md index 317152d..3e40946 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ This project is licensed under the MIT License. [Read More](documentation/MIT.md * [MIT Licese](documentation/MIT.md) * [GPU Fix](documentation/GPU-FIX-README.md) * [Features](documentation/FEATURES.md) +* [Customization](documentation/Customization.md) * [Project Structure](documentation/PROJECT_STRUCTURE.md) * [Core Concepts](documentation/CORE_CONCEPTS.md) * [Contributing Guide](documentation/CONTRIBUTING.md) diff --git a/documentation/Customization.md b/documentation/Customization.md new file mode 100644 index 0000000..5f07135 --- /dev/null +++ b/documentation/Customization.md @@ -0,0 +1,68 @@ +# Nebula Browser Themes + +This directory contains theme files for the Nebula Browser customization system. + +## Theme Structure + +Each theme is a JSON file with the following structure: + +```json +{ + "name": "Theme Name", + "colors": { + "bg": "#121418", + "darkBlue": "#0B1C2B", + "darkPurple": "#1B1035", + "primary": "#7B2EFF", + "accent": "#00C6FF", + "text": "#E0E0E0" + }, + "layout": "centered", + "showLogo": true, + "customTitle": "Nebula Browser", + "gradient": "linear-gradient(145deg, #121418 0%, #1B1035 100%)", + "version": "1.0", + "description": "Theme description" +} +``` + +## Color Properties + +- `bg`: Main background color +- `darkBlue`: Secondary dark blue accent +- `darkPurple`: Secondary dark purple accent +- `primary`: Primary accent color (used for buttons, logos) +- `accent`: Secondary accent color (used for highlights) +- `text`: Main text color + +## Layout Options + +- `centered`: Default centered layout +- `sidebar`: Sidebar navigation layout +- `compact`: Compact view layout + +## Directories + +- `/downloaded/`: Themes downloaded from the community +- `/user/`: User-created custom themes + +## Usage + +1. **Import Theme**: Go to Settings > Customization > Import Theme +2. **Export Theme**: Create your custom theme and export it +3. **Share Themes**: Share your exported .json files with other users + +## Creating Custom Themes + +1. Go to Settings > Browser Customization +2. Adjust colors and settings using the controls +3. Use the live preview to see changes +4. Save as custom theme or export to share + +## Community Themes + +Place downloaded community themes in the `/downloaded/` folder. The browser will automatically detect and make them available in the theme selector. + +## Non-Destructive Design + +All theme changes are stored separately and can be reset to default at any time. Your customizations never modify the original browser files. diff --git a/documentation/FEATURES.md b/documentation/FEATURES.md index dbf511a..dcee111 100644 --- a/documentation/FEATURES.md +++ b/documentation/FEATURES.md @@ -46,6 +46,19 @@ For advanced users, Nebula provides tools to manage GPU acceleration. - **GPU Diagnostics:** View detailed information about your system's GPU and its status. - **GPU Fallback:** If you experience rendering issues, you can apply a GPU fallback to use a more stable rendering path. This can help resolve visual glitches or crashes. +### Custom Themes & Customization + +Nebula offers extensive customization options to personalize your browsing experience. + +- **Theme System:** Choose from built-in themes (default, forest, ocean, sunset) or create your own custom themes. +- **Live Theme Editor:** Modify colors, gradients, and layout options with real-time preview in the settings. +- **Import/Export Themes:** Share custom themes with the community or use themes created by other users. +- **Non-Destructive Design:** All customizations are stored separately and can be reset to default at any time. +- **Layout Options:** Switch between centered, sidebar, and compact view layouts. +- **Custom Branding:** Personalize the browser title and logo visibility. + +For detailed information about creating and managing themes, see the [Customization Guide](Customization.md). + ### Cross-Platform Nebula is built with Electron, allowing it to run on multiple operating systems. diff --git a/renderer/customization.js b/renderer/customization.js new file mode 100644 index 0000000..bc60fc1 --- /dev/null +++ b/renderer/customization.js @@ -0,0 +1,455 @@ +/** + * Browser Customization System + * Allows users to customize themes, colors, and layouts non-destructively + */ + +class BrowserCustomizer { + constructor() { + this.defaultTheme = { + name: 'Default', + colors: { + bg: '#121418', + darkBlue: '#0B1C2B', + darkPurple: '#1B1035', + primary: '#7B2EFF', + accent: '#00C6FF', + text: '#E0E0E0' + }, + layout: 'centered', + showLogo: true, + customTitle: 'Nebula Browser', + gradient: 'linear-gradient(145deg, #121418 0%, #1B1035 100%)' + }; + + this.predefinedThemes = { + default: this.defaultTheme, + ocean: { + name: 'Ocean', + colors: { + bg: '#1a365d', + darkBlue: '#2a4365', + darkPurple: '#2c5282', + primary: '#3182ce', + accent: '#00d9ff', + text: '#e2e8f0' + }, + layout: 'centered', + showLogo: true, + customTitle: 'Nebula Browser', + gradient: 'linear-gradient(145deg, #1a365d 0%, #2c5282 100%)' + }, + forest: { + name: 'Forest', + colors: { + bg: '#1a202c', + darkBlue: '#2d3748', + darkPurple: '#4a5568', + primary: '#68d391', + accent: '#9ae6b4', + text: '#f7fafc' + }, + layout: 'centered', + showLogo: true, + customTitle: 'Nebula Browser', + gradient: 'linear-gradient(145deg, #1a202c 0%, #2d3748 100%)' + }, + sunset: { + name: 'Sunset', + colors: { + bg: '#744210', + darkBlue: '#975a16', + darkPurple: '#c05621', + primary: '#ed8936', + accent: '#fbb040', + text: '#fffaf0' + }, + layout: 'centered', + showLogo: true, + customTitle: 'Nebula Browser', + gradient: 'linear-gradient(145deg, #744210 0%, #c05621 100%)' + } + }; + + this.currentTheme = this.loadTheme(); + this.init(); + } + + init() { + this.setupEventListeners(); + this.loadCurrentTheme(); + this.updatePreview(); + } + + setupEventListeners() { + // Theme preset buttons + document.querySelectorAll('.theme-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + const themeName = e.currentTarget.dataset.theme; + this.applyPredefinedTheme(themeName); + }); + }); + + // Color inputs + const colorInputs = ['bg-color', 'gradient-color', 'accent-color', 'secondary-color', 'text-color']; + colorInputs.forEach(inputId => { + const input = document.getElementById(inputId); + if (input) { + input.addEventListener('input', (e) => { + this.updateColorFromInput(inputId, e.target.value); + }); + } + }); + + // Layout options + document.querySelectorAll('input[name="layout"]').forEach(input => { + input.addEventListener('change', (e) => { + this.currentTheme.layout = e.target.value; + this.saveTheme(); + this.updatePreview(); + this.applyThemeToPages(); + }); + }); + + // Logo options + const showLogoInput = document.getElementById('show-logo'); + if (showLogoInput) { + showLogoInput.addEventListener('change', (e) => { + this.currentTheme.showLogo = e.target.checked; + this.saveTheme(); + this.updatePreview(); + this.applyThemeToPages(); + }); + } + + const customTitleInput = document.getElementById('custom-title'); + if (customTitleInput) { + customTitleInput.addEventListener('input', (e) => { + this.currentTheme.customTitle = e.target.value || 'Nebula Browser'; + this.saveTheme(); + this.updatePreview(); + this.applyThemeToPages(); + }); + } + + // Theme management buttons + this.setupThemeManagementButtons(); + } + + setupThemeManagementButtons() { + const saveBtn = document.getElementById('save-custom-theme'); + const exportBtn = document.getElementById('export-theme'); + const importBtn = document.getElementById('import-theme'); + const resetBtn = document.getElementById('reset-to-default'); + const fileInput = document.getElementById('theme-file-input'); + + if (saveBtn) { + saveBtn.addEventListener('click', () => this.saveCustomTheme()); + } + + if (exportBtn) { + exportBtn.addEventListener('click', () => this.exportTheme()); + } + + if (importBtn) { + importBtn.addEventListener('click', () => fileInput.click()); + } + + if (fileInput) { + fileInput.addEventListener('change', (e) => this.importTheme(e)); + } + + if (resetBtn) { + resetBtn.addEventListener('click', () => this.resetToDefault()); + } + } + + updateColorFromInput(inputId, color) { + const colorMap = { + 'bg-color': 'bg', + 'gradient-color': 'darkPurple', + 'accent-color': 'primary', + 'secondary-color': 'accent', + 'text-color': 'text' + }; + + const colorKey = colorMap[inputId]; + if (colorKey) { + this.currentTheme.colors[colorKey] = color; + + // Update gradient for background or gradient changes + if (colorKey === 'bg' || colorKey === 'darkPurple') { + this.currentTheme.gradient = `linear-gradient(145deg, ${this.currentTheme.colors.bg} 0%, ${this.currentTheme.colors.darkPurple} 100%)`; + } + + this.saveTheme(); + this.updatePreview(); + this.applyThemeToPages(); + } + } + + applyPredefinedTheme(themeName) { + if (this.predefinedThemes[themeName]) { + this.currentTheme = { ...this.predefinedThemes[themeName] }; + this.saveTheme(); + this.loadCurrentTheme(); + this.updatePreview(); + this.applyThemeToCurrentPage(); + this.applyThemeToPages(); + this.updateThemeButtons(themeName); + } + } + + updateThemeButtons(activeTheme) { + document.querySelectorAll('.theme-btn').forEach(btn => { + btn.classList.remove('active'); + if (btn.dataset.theme === activeTheme) { + btn.classList.add('active'); + } + }); + } + + loadCurrentTheme() { + // Update color inputs + document.getElementById('bg-color').value = this.currentTheme.colors.bg; + document.getElementById('gradient-color').value = this.currentTheme.colors.darkPurple; + document.getElementById('accent-color').value = this.currentTheme.colors.primary; + document.getElementById('secondary-color').value = this.currentTheme.colors.accent; + document.getElementById('text-color').value = this.currentTheme.colors.text; + + // Update layout radio + const layoutInput = document.querySelector(`input[name="layout"][value="${this.currentTheme.layout}"]`); + if (layoutInput) layoutInput.checked = true; + + // Update logo options + document.getElementById('show-logo').checked = this.currentTheme.showLogo; + document.getElementById('custom-title').value = this.currentTheme.customTitle; + } + + updatePreview() { + const preview = document.getElementById('preview-container'); + const previewHome = preview.querySelector('.preview-home'); + const previewLogo = preview.querySelector('.preview-logo'); + + // Apply colors to preview + previewHome.style.background = this.currentTheme.gradient; + previewLogo.style.color = this.currentTheme.colors.primary; + previewLogo.textContent = this.currentTheme.showLogo ? + `🌌 ${this.currentTheme.customTitle}` : this.currentTheme.customTitle; + + // Update CSS custom properties for live preview + this.applyThemeToCurrentPage(); + } + + applyThemeToCurrentPage() { + const root = document.documentElement; + root.style.setProperty('--bg', this.currentTheme.colors.bg); + root.style.setProperty('--dark-blue', this.currentTheme.colors.darkBlue); + root.style.setProperty('--dark-purple', this.currentTheme.colors.darkPurple); + root.style.setProperty('--primary', this.currentTheme.colors.primary); + root.style.setProperty('--accent', this.currentTheme.colors.accent); + root.style.setProperty('--text', this.currentTheme.colors.text); + + // Apply gradient to body if it exists + const body = document.body; + if (body && this.currentTheme.gradient) { + body.style.background = this.currentTheme.gradient; + console.log('[THEME] Applied gradient:', this.currentTheme.gradient); + } + } + + applyThemeToPages() { + // This will be called to apply theme to home.html and other pages + // We'll store the theme and let other pages load it + this.saveTheme(); + + // If we have access to other windows/frames, apply there too + try { + if (window.parent && window.parent !== window) { + window.parent.postMessage({ + type: 'theme-update', + theme: this.currentTheme + }, '*'); + } + } catch (e) { + console.log('Could not send theme update to parent window'); + } + } + + saveCustomTheme() { + const themeName = prompt('Enter a name for your custom theme:', 'My Custom Theme'); + if (themeName) { + const customThemes = this.getCustomThemes(); + customThemes[themeName.toLowerCase().replace(/\s+/g, '-')] = { + ...this.currentTheme, + name: themeName + }; + localStorage.setItem('customThemes', JSON.stringify(customThemes)); + + // Show success message + this.showMessage('Custom theme saved successfully!', 'success'); + } + } + + exportTheme() { + const themeData = { + ...this.currentTheme, + exportedAt: new Date().toISOString(), + version: '1.0' + }; + + const blob = new Blob([JSON.stringify(themeData, null, 2)], { + type: 'application/json' + }); + + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `nebula-theme-${themeData.name.toLowerCase().replace(/\s+/g, '-')}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + this.showMessage('Theme exported successfully!', 'success'); + } + + importTheme(event) { + const file = event.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (e) => { + try { + const themeData = JSON.parse(e.target.result); + + // Validate theme structure + if (this.validateTheme(themeData)) { + this.currentTheme = themeData; + this.saveTheme(); + this.loadCurrentTheme(); + this.updatePreview(); + this.applyThemeToCurrentPage(); + this.applyThemeToPages(); + this.showMessage('Theme imported successfully!', 'success'); + } else { + this.showMessage('Invalid theme file format.', 'error'); + } + } catch (error) { + this.showMessage('Error reading theme file.', 'error'); + } + }; + reader.readAsText(file); + } + + validateTheme(theme) { + return theme && + theme.colors && + theme.colors.bg && + theme.colors.primary && + theme.colors.accent && + theme.colors.text; + } + + resetToDefault() { + if (confirm('Are you sure you want to reset to the default theme? This will lose your current customizations.')) { + this.currentTheme = { ...this.defaultTheme }; + this.saveTheme(); + this.loadCurrentTheme(); + this.updatePreview(); + this.applyThemeToCurrentPage(); + this.applyThemeToPages(); + this.updateThemeButtons('default'); + this.showMessage('Theme reset to default.', 'success'); + } + } + + saveTheme() { + localStorage.setItem('currentTheme', JSON.stringify(this.currentTheme)); + } + + loadTheme() { + const savedTheme = localStorage.getItem('currentTheme'); + return savedTheme ? JSON.parse(savedTheme) : { ...this.defaultTheme }; + } + + getCustomThemes() { + const customThemes = localStorage.getItem('customThemes'); + return customThemes ? JSON.parse(customThemes) : {}; + } + + showMessage(message, type = 'info') { + const messageDiv = document.createElement('div'); + messageDiv.className = `message message-${type}`; + messageDiv.textContent = message; + messageDiv.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + padding: 12px 20px; + border-radius: 4px; + color: white; + font-weight: 500; + z-index: 10000; + animation: slideIn 0.3s ease; + background: ${type === 'success' ? '#48bb78' : type === 'error' ? '#e53e3e' : '#4299e1'}; + `; + + document.body.appendChild(messageDiv); + + setTimeout(() => { + messageDiv.style.animation = 'slideOut 0.3s ease'; + setTimeout(() => { + if (messageDiv.parentNode) { + messageDiv.parentNode.removeChild(messageDiv); + } + }, 300); + }, 3000); + } + + // Static method to apply theme to any page + static applyThemeToPage() { + const savedTheme = localStorage.getItem('currentTheme'); + if (savedTheme) { + const theme = JSON.parse(savedTheme); + const root = document.documentElement; + + root.style.setProperty('--bg', theme.colors.bg); + root.style.setProperty('--dark-blue', theme.colors.darkBlue); + root.style.setProperty('--dark-purple', theme.colors.darkPurple); + root.style.setProperty('--primary', theme.colors.primary); + root.style.setProperty('--accent', theme.colors.accent); + root.style.setProperty('--text', theme.colors.text); + + // Apply gradient to body if it exists + const body = document.body; + if (body && theme.gradient) { + body.style.background = theme.gradient; + console.log('[THEME] Applied gradient from storage:', theme.gradient); + } + + return theme; + } + return null; + } +} + +// Auto-initialize on settings page +if (window.location.pathname.includes('settings.html')) { + document.addEventListener('DOMContentLoaded', () => { + window.browserCustomizer = new BrowserCustomizer(); + }); +} + +// Add keyframe animations for messages +const style = document.createElement('style'); +style.textContent = ` + @keyframes slideIn { + from { transform: translateX(100%); opacity: 0; } + to { transform: translateX(0); opacity: 1; } + } + @keyframes slideOut { + from { transform: translateX(0); opacity: 1; } + to { transform: translateX(100%); opacity: 0; } + } +`; +document.head.appendChild(style); diff --git a/renderer/home.css b/renderer/home.css index 7da1636..3dbad4d 100644 --- a/renderer/home.css +++ b/renderer/home.css @@ -6,6 +6,16 @@ font-display: swap; } +/* CSS Custom Properties for Theming */ +:root { + --bg: #121418; + --dark-blue: #0B1C2B; + --dark-purple: #1B1035; + --primary: #7B2EFF; + --accent: #00C6FF; + --text: #E0E0E0; +} + /* Base reset */ * { margin: 0; @@ -13,13 +23,12 @@ box-sizing: border-box; } - body, html { - /* replace solid bg with a subtle gradient */ + /* Use CSS custom properties for theming */ margin: 0; padding: 0; height: 100%; - background: linear-gradient(145deg, #121418 0%, #1B1035 100%); + background: linear-gradient(145deg, var(--bg) 0%, var(--dark-purple) 100%); color: var(--text); overflow: hidden; font-family: 'InterVariable', sans-serif; @@ -57,6 +66,7 @@ body, html { .logo-text { font-size: 2rem; font-weight: bold; + color: var(--primary); } /* Search bar container */ diff --git a/renderer/home.html b/renderer/home.html index de72e05..35e505d 100644 --- a/renderer/home.html +++ b/renderer/home.html @@ -73,10 +73,43 @@ - - - - + + + + diff --git a/renderer/settings.html b/renderer/settings.html index 0a2e3a4..5b874ab 100644 --- a/renderer/settings.html +++ b/renderer/settings.html @@ -9,9 +9,193 @@ body { font-family: sans-serif; padding: 20px; } section { margin-bottom: 30px; } h2 { border-bottom: 1px solid #ccc; padding-bottom: 5px; } + h3 { margin: 15px 0 10px 0; color: var(--accent); font-size: 1.1rem; } ul { list-style: none; padding-left: 0; } li { padding: 5px 0; border-bottom: 1px solid #eee; } .debug-info { background: #f0f0f0; padding: 10px; margin: 10px 0; font-family: monospace; font-size: 12px; } + + /* Customization Styles */ + .customization-group { + margin-bottom: 25px; + padding: 15px; + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; + } + + .theme-selector { + display: flex; + gap: 10px; + flex-wrap: wrap; + } + + .theme-btn { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 10px; + background: transparent; + border: 2px solid transparent; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s ease; + color: var(--text); + } + + .theme-btn:hover { + border-color: var(--accent); + } + + .theme-btn.active { + border-color: var(--primary); + background: rgba(123, 46, 255, 0.1); + } + + .theme-preview { + width: 60px; + height: 40px; + border-radius: 4px; + border: 1px solid rgba(255, 255, 255, 0.2); + } + + .color-controls { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 15px; + } + + .color-group { + display: flex; + flex-direction: column; + gap: 5px; + } + + .color-group label { + font-size: 0.9rem; + color: var(--text); + } + + .color-group input[type="color"] { + width: 100%; + height: 40px; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + background: transparent; + cursor: pointer; + } + + .layout-options { + display: flex; + flex-direction: column; + gap: 10px; + } + + .layout-options label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + padding: 8px; + border-radius: 4px; + transition: background 0.2s ease; + } + + .layout-options label:hover { + background: rgba(255, 255, 255, 0.05); + } + + .logo-options { + display: flex; + flex-direction: column; + gap: 12px; + } + + .logo-options label { + display: flex; + align-items: center; + gap: 8px; + } + + .logo-options input[type="text"] { + flex: 1; + padding: 8px; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + color: var(--text); + } + + .theme-management { + display: flex; + flex-wrap: wrap; + gap: 10px; + } + + .theme-management button { + padding: 8px 16px; + background: var(--primary); + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + transition: background 0.2s ease; + } + + .theme-management button:hover { + background: var(--accent); + } + + .theme-management button:last-child { + background: #e53e3e; + } + + .theme-management button:last-child:hover { + background: #c53030; + } + + .preview-container { + background: var(--dark-blue); + border-radius: 8px; + padding: 20px; + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .preview-home { + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; + padding: 20px; + background: var(--bg); + border-radius: 8px; + min-height: 200px; + } + + .preview-logo { + font-size: 1.5rem; + font-weight: bold; + color: var(--primary); + } + + .preview-search { + width: 60%; + height: 40px; + background: rgba(255, 255, 255, 0.1); + border-radius: 20px; + border: 1px solid rgba(255, 255, 255, 0.2); + } + + .preview-bookmarks { + display: flex; + gap: 10px; + } + + .preview-bookmark { + width: 50px; + height: 50px; + background: var(--accent); + border-radius: 8px; + } @@ -27,6 +211,123 @@
Loading debug info...
+ +
+

🎨 Browser Customization

+ + +
+

Theme Presets

+
+ + + + +
+
+ + +
+

Custom Colors

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+

Home Page Layout

+
+ + + +
+
+ + +
+

Logo & Branding

+
+ + +
+
+ + +
+

Theme Management

+
+ + + + + +
+
+ + +
+

Preview

+
+
+ + +
+
+
+
+
+
+
+
+
+

Search History

@@ -48,7 +349,13 @@ +