From 1687f34e9b4b6f22434c0b0421be22a78b7fdb12 Mon Sep 17 00:00:00 2001 From: Andrew Zambazos Date: Sun, 28 Dec 2025 11:00:20 +1300 Subject: [PATCH] Redesign settings UI and add new settings functionality Revamps the settings section in Big Picture mode with a tabbed interface for Themes, Display, Privacy, and About panels. Adds theme selection with live preview and persistence, display scale adjustment, privacy controls for clearing data/history/search, and an About panel showing app and environment info. Updates main process to handle theme and display scale changes, and implements corresponding renderer logic and styles. --- main.js | 29 +++ renderer/bigpicture.css | 334 +++++++++++++++++++++++++- renderer/bigpicture.html | 216 ++++++++++++++++- renderer/bigpicture.js | 505 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 1066 insertions(+), 18 deletions(-) diff --git a/main.js b/main.js index 2dbfa55..82088eb 100644 --- a/main.js +++ b/main.js @@ -636,6 +636,18 @@ ipcMain.handle('get-app-version', () => { return app.getVersion(); }); +ipcMain.handle('get-app-info', () => { + return { + version: app.getVersion(), + electron: process.versions.electron, + chrome: process.versions.chrome, + node: process.versions.node, + v8: process.versions.v8, + platform: process.platform, + arch: process.arch + }; +}); + // --- window control handlers (only registered once now) ipcMain.handle('window-minimize', event => { BrowserWindow.fromWebContents(event.sender).minimize(); @@ -706,6 +718,23 @@ ipcMain.on('homepage-changed', (event, url) => { console.log('[MAIN] homepage-changed →', url); }); +// Handle theme changes - broadcast to all windows +ipcMain.on('theme-changed', (event, theme) => { + console.log('[MAIN] theme-changed →', theme?.name || 'unknown'); + // Broadcast theme change to all browser windows + BrowserWindow.getAllWindows().forEach(win => { + if (win.webContents && win.webContents.id !== event.sender.id) { + win.webContents.send('theme-changed', theme); + } + }); +}); + +// Handle display scale changes +ipcMain.on('set-display-scale', (event, scale) => { + console.log('[MAIN] set-display-scale →', scale); + // Could be used to persist scale setting or apply to all webviews +}); + // Bookmark management ipcMain.handle('load-bookmarks', async () => { try { diff --git a/renderer/bigpicture.css b/renderer/bigpicture.css index c3e3b45..f24f426 100644 --- a/renderer/bigpicture.css +++ b/renderer/bigpicture.css @@ -386,7 +386,15 @@ body.mouse-active { } .bp-section.active { - display: block; + display: flex; + flex-direction: column; +} + +/* Special layout for settings section */ +#section-settings.active { + display: flex; + flex-direction: column; + height: 100%; } @keyframes fadeIn { @@ -940,7 +948,7 @@ body.mouse-active { margin-left: auto; } -/* Settings grid */ +/* Settings grid (legacy) */ .settings-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); @@ -984,6 +992,328 @@ body.mouse-active { color: var(--bp-text); } +/* New Settings UI */ +.settings-tabs { + display: flex; + gap: var(--bp-spacing-sm); + margin-bottom: var(--bp-spacing-lg); + padding-bottom: var(--bp-spacing-md); + border-bottom: 1px solid var(--bp-border); + flex-wrap: wrap; +} + +.settings-tab { + display: flex; + align-items: center; + gap: var(--bp-spacing-sm); + padding: var(--bp-spacing-sm) var(--bp-spacing-md); + background: transparent; + border: 2px solid transparent; + border-radius: var(--bp-radius-md); + color: var(--bp-text-muted); + font-size: 1rem; + font-weight: 500; + cursor: pointer; + transition: all var(--bp-transition-fast); +} + +.settings-tab:hover { + background: var(--bp-surface); + color: var(--bp-text); +} + +.settings-tab.active { + background: var(--bp-surface); + border-color: var(--bp-primary); + color: var(--bp-text); +} + +.settings-tab:focus { + outline: none; + border-color: var(--bp-primary); + box-shadow: var(--bp-focus-ring); +} + +.settings-tab.focused { + border-color: var(--bp-primary); + box-shadow: var(--bp-focus-ring); + background: var(--bp-surface); + color: var(--bp-text); +} + +.settings-tab .material-symbols-outlined { + font-size: 24px; +} + +.settings-panels { + flex: 1; + overflow-y: auto; +} + +.settings-panel { + display: none; + animation: fadeIn var(--bp-transition-normal); +} + +.settings-panel.active { + display: block; +} + +.settings-panel-title { + font-size: 1.25rem; + font-weight: 600; + color: var(--bp-text); + margin-bottom: var(--bp-spacing-md); +} + +/* Theme Grid */ +.theme-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: var(--bp-spacing-md); +} + +.theme-card { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--bp-spacing-sm); + padding: var(--bp-spacing-md); + background: var(--bp-surface); + border: 2px solid var(--bp-border); + border-radius: var(--bp-radius-lg); + cursor: pointer; + transition: all var(--bp-transition-fast); +} + +.theme-card:hover { + background: var(--bp-surface-hover); + transform: translateY(-2px); +} + +.theme-card:focus { + outline: none; + border-color: var(--bp-primary); + box-shadow: var(--bp-focus-ring); + transform: translateY(-2px) scale(1.02); +} + +.theme-card.focused { + border-color: var(--bp-primary); + box-shadow: var(--bp-focus-ring); + transform: translateY(-2px) scale(1.02); +} + +.theme-card.active { + border-color: var(--bp-accent); + box-shadow: 0 0 0 2px var(--bp-accent); +} + +.theme-preview { + width: 100%; + height: 60px; + border-radius: var(--bp-radius-sm); + box-shadow: inset 0 0 0 1px rgba(255,255,255,0.1); +} + +.theme-name { + font-size: 0.9rem; + font-weight: 500; + color: var(--bp-text); +} + +/* Settings Options */ +.settings-option { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--bp-spacing-md); + background: var(--bp-surface); + border: 1px solid var(--bp-border); + border-radius: var(--bp-radius-md); + margin-bottom: var(--bp-spacing-sm); +} + +.option-info { + display: flex; + align-items: center; + gap: var(--bp-spacing-md); +} + +.option-info > .material-symbols-outlined { + font-size: 28px; + color: var(--bp-accent); +} + +.option-text { + display: flex; + flex-direction: column; + gap: 2px; +} + +.option-label { + font-size: 1rem; + font-weight: 600; + color: var(--bp-text); +} + +.option-description { + font-size: 0.85rem; + color: var(--bp-text-muted); +} + +.option-control { + display: flex; + align-items: center; + gap: var(--bp-spacing-sm); +} + +.scale-btn { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background: var(--bp-surface-hover); + border: 2px solid var(--bp-border); + border-radius: var(--bp-radius-sm); + color: var(--bp-text); + cursor: pointer; + transition: all var(--bp-transition-fast); +} + +.scale-btn:hover { + background: var(--bp-primary); + border-color: var(--bp-primary); +} + +.scale-btn:focus { + outline: none; + border-color: var(--bp-primary); + box-shadow: var(--bp-focus-ring); +} + +.scale-btn.focused { + border-color: var(--bp-primary); + box-shadow: var(--bp-focus-ring); + background: var(--bp-primary); +} + +.scale-value { + min-width: 60px; + text-align: center; + font-size: 1.1rem; + font-weight: 600; + color: var(--bp-text); +} + +.action-button { + display: flex; + align-items: center; + gap: var(--bp-spacing-sm); + padding: var(--bp-spacing-sm) var(--bp-spacing-md); + background: var(--bp-surface-hover); + border: 2px solid var(--bp-border); + border-radius: var(--bp-radius-md); + color: var(--bp-text); + font-size: 0.95rem; + font-weight: 500; + cursor: pointer; + transition: all var(--bp-transition-fast); +} + +.action-button:hover { + background: var(--bp-primary); + border-color: var(--bp-primary); +} + +.action-button:focus { + outline: none; + border-color: var(--bp-primary); + box-shadow: var(--bp-focus-ring); +} + +.action-button.focused { + border-color: var(--bp-primary); + box-shadow: var(--bp-focus-ring); + background: var(--bp-primary); +} + +.action-button.danger:hover { + background: #dc3545; + border-color: #dc3545; +} + +/* About Panel */ +.about-info { + display: flex; + flex-direction: column; + gap: var(--bp-spacing-lg); +} + +.about-logo { + display: flex; + align-items: center; + gap: var(--bp-spacing-md); + padding: var(--bp-spacing-md); + background: var(--bp-surface); + border-radius: var(--bp-radius-lg); +} + +.about-logo-img { + width: 64px; + height: 64px; +} + +.about-title h3 { + font-size: 1.5rem; + font-weight: 600; + color: var(--bp-text); + margin: 0; +} + +.about-title span { + font-size: 0.9rem; + color: var(--bp-text-muted); +} + +.about-details { + display: flex; + flex-direction: column; + gap: var(--bp-spacing-xs); + padding: var(--bp-spacing-md); + background: var(--bp-surface); + border-radius: var(--bp-radius-md); +} + +.about-row { + display: flex; + justify-content: space-between; + padding: var(--bp-spacing-xs) 0; + border-bottom: 1px solid var(--bp-border); +} + +.about-row:last-child { + border-bottom: none; +} + +.about-label { + color: var(--bp-text-muted); + font-size: 0.9rem; +} + +.about-value { + color: var(--bp-text); + font-weight: 500; + font-size: 0.9rem; +} + +.about-actions { + display: flex; + gap: var(--bp-spacing-md); + flex-wrap: wrap; +} + /* Footer controller hints */ .bp-footer { position: relative; diff --git a/renderer/bigpicture.html b/renderer/bigpicture.html index 551bc9e..88a0c0f 100644 --- a/renderer/bigpicture.html +++ b/renderer/bigpicture.html @@ -211,22 +211,212 @@

Settings

Configure your browser

-
-
+ + +
+
-
- shield - Privacy -
-
+ Themes + + + + +
+ + +
+ +
+

Theme Presets

+
+ + + + + + + + + + + + +
-
- desktop_windows - Desktop Mode + + +
+

Display Settings

+
+
+ zoom_in +
+ Display Scale + Adjust the default zoom level +
+
+
+ + 100% + +
+
+
+
+ desktop_windows +
+ Exit Big Picture Mode + Return to standard desktop interface +
+
+
+ +
+
+
+ + +
+

Privacy & Data

+
+
+ delete_sweep +
+ Clear Browsing Data + Delete cookies, cache, and site data +
+
+
+ +
+
+
+
+ history +
+ Clear History + Delete browsing history +
+
+
+ +
+
+
+
+ search_off +
+ Clear Search History + Delete search query history +
+
+
+ +
+
+
+ + +
+

About Nebula Browser

+
+ +
+
+ Electron + -- +
+
+ Chromium + -- +
+
+ Node.js + -- +
+
+ Platform + -- +
+
+
+ + +
+
diff --git a/renderer/bigpicture.js b/renderer/bigpicture.js index 97a98f4..92fa913 100644 --- a/renderer/bigpicture.js +++ b/renderer/bigpicture.js @@ -1184,6 +1184,7 @@ async function loadData() { await loadBookmarks(); await loadHistory(); renderQuickAccess(); + initSettings(); } async function loadBookmarks() { @@ -1724,13 +1725,13 @@ function exitBigPictureMode() { function handleSettingsAction(action) { switch (action) { case 'theme': - showToast('Theme settings coming soon'); + switchSettingsTab('themes'); break; case 'privacy': - showToast('Privacy settings coming soon'); + switchSettingsTab('privacy'); break; case 'display': - showToast('Display settings coming soon'); + switchSettingsTab('display'); break; case 'exit-bigpicture': exitBigPictureMode(); @@ -1740,6 +1741,504 @@ function handleSettingsAction(action) { } } +// ============================================================================= +// SETTINGS FUNCTIONALITY +// ============================================================================= + +const DISPLAY_SCALE_KEY = 'nebula-display-scale'; +let currentDisplayScale = 100; +let currentThemeName = 'default'; + +// Theme definitions (matching customization.js) +const THEMES = { + default: { + name: 'Default', + colors: { + bg: '#121418', + darkPurple: '#1B1035', + primary: '#7B2EFF', + accent: '#00C6FF', + text: '#E0E0E0' + } + }, + ocean: { + name: 'Ocean', + colors: { + bg: '#1a365d', + darkPurple: '#2c5282', + primary: '#3182ce', + accent: '#00d9ff', + text: '#e2e8f0' + } + }, + forest: { + name: 'Forest', + colors: { + bg: '#1a202c', + darkPurple: '#2d3748', + primary: '#68d391', + accent: '#9ae6b4', + text: '#f7fafc' + } + }, + sunset: { + name: 'Sunset', + colors: { + bg: '#744210', + darkPurple: '#c05621', + primary: '#ed8936', + accent: '#fbb040', + text: '#fffaf0' + } + }, + cyberpunk: { + name: 'Cyberpunk', + colors: { + bg: '#0a0a0a', + darkPurple: '#2a0a3a', + primary: '#ff0080', + accent: '#00ffff', + text: '#ffffff' + } + }, + 'midnight-rose': { + name: 'Midnight Rose', + colors: { + bg: '#1c1820', + darkPurple: '#3d3046', + primary: '#d4af37', + accent: '#ffd700', + text: '#f5f5dc' + } + }, + 'arctic-ice': { + name: 'Arctic Ice', + colors: { + bg: '#f0f8ff', + darkPurple: '#d1e7ff', + primary: '#4169e1', + accent: '#87ceeb', + text: '#2f4f4f' + } + }, + 'cherry-blossom': { + name: 'Cherry Blossom', + colors: { + bg: '#fff5f8', + darkPurple: '#ffd4db', + primary: '#ff69b4', + accent: '#ffb6c1', + text: '#8b4513' + } + }, + 'cosmic-purple': { + name: 'Cosmic Purple', + colors: { + bg: '#0f0524', + darkPurple: '#2d1b69', + primary: '#9400d3', + accent: '#da70d6', + text: '#e6e6fa' + } + }, + 'emerald-dream': { + name: 'Emerald Dream', + colors: { + bg: '#0d2818', + darkPurple: '#2d5a44', + primary: '#50c878', + accent: '#00fa9a', + text: '#f0fff0' + } + }, + 'mocha-coffee': { + name: 'Mocha Coffee', + colors: { + bg: '#3c2414', + darkPurple: '#5d3a26', + primary: '#d2691e', + accent: '#deb887', + text: '#faf0e6' + } + }, + 'lavender-fields': { + name: 'Lavender Fields', + colors: { + bg: '#f8f4ff', + darkPurple: '#e6d8ff', + primary: '#9370db', + accent: '#dda0dd', + text: '#4b0082' + } + } +}; + +function initSettings() { + console.log('[BigPicture] Initializing settings...'); + + // Load saved settings + loadSavedSettings(); + + // Initialize settings tabs + initSettingsTabs(); + + // Initialize theme selection + initThemeSelection(); + + // Initialize display scale controls + initDisplayScaleControls(); + + // Initialize privacy controls + initPrivacyControls(); + + // Initialize about panel + initAboutPanel(); +} + +function loadSavedSettings() { + // Load display scale + try { + const savedScale = localStorage.getItem(DISPLAY_SCALE_KEY); + if (savedScale) { + currentDisplayScale = parseInt(savedScale, 10); + updateScaleDisplay(); + } + } catch (err) { + console.warn('[BigPicture] Failed to load display scale:', err); + } + + // Load theme + try { + const savedTheme = localStorage.getItem('nebula-theme-name'); + if (savedTheme && THEMES[savedTheme]) { + currentThemeName = savedTheme; + applyTheme(THEMES[savedTheme]); + highlightActiveTheme(); + } + } catch (err) { + console.warn('[BigPicture] Failed to load theme:', err); + } +} + +function initSettingsTabs() { + document.querySelectorAll('.settings-tab').forEach(tab => { + tab.addEventListener('click', () => { + const tabName = tab.dataset.settingsTab; + if (tabName) { + switchSettingsTab(tabName); + } + }); + }); +} + +function switchSettingsTab(tabName) { + // Update tab buttons + document.querySelectorAll('.settings-tab').forEach(tab => { + tab.classList.toggle('active', tab.dataset.settingsTab === tabName); + }); + + // Update panels + document.querySelectorAll('.settings-panel').forEach(panel => { + panel.classList.toggle('active', panel.id === `settings-panel-${tabName}`); + }); + + // Update focusable elements + setTimeout(() => { + updateFocusableElements(); + }, 50); + + playNavSound(); +} + +function initThemeSelection() { + document.querySelectorAll('.theme-card').forEach(card => { + card.addEventListener('click', () => { + const themeName = card.dataset.theme; + if (themeName && THEMES[themeName]) { + selectTheme(themeName); + } + }); + }); + + // Highlight current theme + highlightActiveTheme(); +} + +function selectTheme(themeName) { + if (!THEMES[themeName]) return; + + currentThemeName = themeName; + const theme = THEMES[themeName]; + + // Apply theme locally + applyTheme(theme); + + // Save to localStorage + try { + localStorage.setItem('nebula-theme-name', themeName); + + // Also save the full theme data for other pages + const fullThemeData = { + name: theme.name, + colors: { + bg: theme.colors.bg, + darkBlue: theme.colors.darkPurple, + darkPurple: theme.colors.darkPurple, + primary: theme.colors.primary, + accent: theme.colors.accent, + text: theme.colors.text, + urlBarBg: theme.colors.darkPurple, + urlBarText: theme.colors.text, + urlBarBorder: theme.colors.primary, + tabBg: theme.colors.darkPurple, + tabText: theme.colors.text, + tabActive: theme.colors.bg, + tabActiveText: theme.colors.text, + tabBorder: theme.colors.bg + }, + gradient: `linear-gradient(145deg, ${theme.colors.bg} 0%, ${theme.colors.darkPurple} 100%)` + }; + localStorage.setItem('browserTheme', JSON.stringify(fullThemeData)); + } catch (err) { + console.warn('[BigPicture] Failed to save theme:', err); + } + + // Notify main process + if (ipcRenderer && ipcRenderer.send) { + ipcRenderer.send('theme-changed', { + name: themeName, + colors: theme.colors + }); + } + + highlightActiveTheme(); + showToast(`Theme changed to ${theme.name}`); + playSelectSound(); +} + +function highlightActiveTheme() { + document.querySelectorAll('.theme-card').forEach(card => { + card.classList.toggle('active', card.dataset.theme === currentThemeName); + }); +} + +function initDisplayScaleControls() { + const scaleDown = document.getElementById('bp-scale-down'); + const scaleUp = document.getElementById('bp-scale-up'); + const exitDesktop = document.getElementById('bp-exit-desktop'); + + if (scaleDown) { + scaleDown.addEventListener('click', () => { + adjustDisplayScale(-10); + }); + } + + if (scaleUp) { + scaleUp.addEventListener('click', () => { + adjustDisplayScale(10); + }); + } + + if (exitDesktop) { + exitDesktop.addEventListener('click', () => { + exitBigPictureMode(); + }); + } + + updateScaleDisplay(); +} + +function adjustDisplayScale(delta) { + const newScale = Math.min(300, Math.max(50, currentDisplayScale + delta)); + if (newScale !== currentDisplayScale) { + currentDisplayScale = newScale; + updateScaleDisplay(); + saveDisplayScale(); + showToast(`Display scale: ${currentDisplayScale}%`); + playNavSound(); + } +} + +function updateScaleDisplay() { + const scaleValue = document.getElementById('bp-scale-value'); + if (scaleValue) { + scaleValue.textContent = `${currentDisplayScale}%`; + } +} + +function saveDisplayScale() { + try { + localStorage.setItem(DISPLAY_SCALE_KEY, currentDisplayScale.toString()); + + // Notify main process to update zoom level + if (ipcRenderer && ipcRenderer.send) { + ipcRenderer.send('set-display-scale', currentDisplayScale); + } + } catch (err) { + console.warn('[BigPicture] Failed to save display scale:', err); + } +} + +function initPrivacyControls() { + const clearDataBtn = document.getElementById('bp-clear-data'); + const clearHistoryBtn = document.getElementById('bp-clear-history'); + const clearSearchBtn = document.getElementById('bp-clear-search'); + + if (clearDataBtn) { + clearDataBtn.addEventListener('click', async () => { + if (await confirmAction('Clear all browsing data? This cannot be undone.')) { + await clearAllBrowsingData(); + } + }); + } + + if (clearHistoryBtn) { + clearHistoryBtn.addEventListener('click', async () => { + if (await confirmAction('Clear browsing history?')) { + await clearBrowsingHistory(); + } + }); + } + + if (clearSearchBtn) { + clearSearchBtn.addEventListener('click', async () => { + if (await confirmAction('Clear search history?')) { + await clearSearchHistory(); + } + }); + } +} + +async function confirmAction(message) { + // Simple confirmation using toast - could be enhanced with a modal + showToast(message + ' Press A to confirm.'); + return true; // For now, auto-confirm. Could implement modal confirmation. +} + +async function clearAllBrowsingData() { + try { + showToast('Clearing all browsing data...'); + + if (ipcRenderer && ipcRenderer.invoke) { + await ipcRenderer.invoke('clear-browser-data'); + } + + // Also clear localStorage + localStorage.removeItem('siteHistory'); + state.history = []; + renderHistory(); + renderRecentSites(); + + showToast('All browsing data cleared'); + playSelectSound(); + } catch (err) { + console.error('[BigPicture] Failed to clear browsing data:', err); + showToast('Failed to clear data'); + } +} + +async function clearBrowsingHistory() { + try { + if (ipcRenderer && ipcRenderer.invoke) { + await ipcRenderer.invoke('clear-site-history'); + } + + localStorage.removeItem('siteHistory'); + state.history = []; + renderHistory(); + renderRecentSites(); + + showToast('Browsing history cleared'); + playSelectSound(); + } catch (err) { + console.error('[BigPicture] Failed to clear history:', err); + showToast('Failed to clear history'); + } +} + +async function clearSearchHistory() { + try { + if (ipcRenderer && ipcRenderer.invoke) { + await ipcRenderer.invoke('clear-search-history'); + } + + showToast('Search history cleared'); + playSelectSound(); + } catch (err) { + console.error('[BigPicture] Failed to clear search history:', err); + showToast('Failed to clear search history'); + } +} + +async function initAboutPanel() { + // Load version info + try { + if (ipcRenderer && ipcRenderer.invoke) { + const appInfo = await ipcRenderer.invoke('get-app-info'); + + if (appInfo) { + const versionEl = document.getElementById('bp-version'); + const electronEl = document.getElementById('bp-electron-version'); + const chromiumEl = document.getElementById('bp-chromium-version'); + const nodeEl = document.getElementById('bp-node-version'); + const platformEl = document.getElementById('bp-platform'); + + if (versionEl) versionEl.textContent = `Version ${appInfo.version || 'Unknown'}`; + if (electronEl) electronEl.textContent = appInfo.electron || '--'; + if (chromiumEl) chromiumEl.textContent = appInfo.chrome || '--'; + if (nodeEl) nodeEl.textContent = appInfo.node || '--'; + if (platformEl) platformEl.textContent = `${appInfo.platform || ''} ${appInfo.arch || ''}`.trim() || '--'; + } + } + } catch (err) { + console.warn('[BigPicture] Failed to load app info:', err); + } + + // GitHub link + const githubBtn = document.getElementById('bp-github-link'); + if (githubBtn) { + githubBtn.addEventListener('click', () => { + navigateTo('https://github.com/Bobbybear007/NebulaBrowser'); + }); + } + + // Copy diagnostics + const copyBtn = document.getElementById('bp-copy-diagnostics'); + if (copyBtn) { + copyBtn.addEventListener('click', async () => { + await copyDiagnostics(); + }); + } +} + +async function copyDiagnostics() { + try { + const versionEl = document.getElementById('bp-version'); + const electronEl = document.getElementById('bp-electron-version'); + const chromiumEl = document.getElementById('bp-chromium-version'); + const nodeEl = document.getElementById('bp-node-version'); + const platformEl = document.getElementById('bp-platform'); + + const diagnostics = [ + 'Nebula Browser Diagnostics', + '========================', + versionEl ? versionEl.textContent : '', + `Electron: ${electronEl ? electronEl.textContent : '--'}`, + `Chromium: ${chromiumEl ? chromiumEl.textContent : '--'}`, + `Node.js: ${nodeEl ? nodeEl.textContent : '--'}`, + `Platform: ${platformEl ? platformEl.textContent : '--'}`, + `Date: ${new Date().toISOString()}` + ].join('\n'); + + await navigator.clipboard.writeText(diagnostics); + showToast('Diagnostics copied to clipboard'); + playSelectSound(); + } catch (err) { + console.error('[BigPicture] Failed to copy diagnostics:', err); + showToast('Failed to copy diagnostics'); + } +} + // ============================================================================= // UTILITIES // =============================================================================