Add Big Picture Mode for Steam Deck and controllers

Introduces a new Big Picture Mode with a controller-friendly, console-style UI optimized for Steam Deck and handheld devices. Adds new renderer files (HTML, CSS, JS) for the mode, updates main and preload scripts to support window management and IPC for Big Picture Mode, and documents features and usage in BIG_PICTURE_MODE.md. Updates settings and navigation to allow launching and exiting Big Picture Mode.
This commit is contained in:
2025-12-27 23:09:45 +13:00
parent 43ebed0ade
commit 5c837aecd8
12 changed files with 3573 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
# Big Picture Mode - Steam Deck & Controller UI
Nebula Browser includes a **Big Picture Mode** - a controller-friendly, console-style interface designed for Steam Deck, handheld devices, and living room setups.
## Features
### 🎮 Controller Support
- **Full gamepad navigation** - Use D-pad or left stick to navigate
- **Button mapping**:
- **A / Cross** - Select/Activate
- **B / Circle** - Go Back
- **Y / Triangle** - Quick Search
- **Start** - Toggle Settings
- **Audio feedback** for navigation
### 📱 Optimized for Steam Deck
- **1280x800 native resolution** support
- Automatic detection of Steam Deck screens
- Large touch-friendly UI elements
- Fullscreen immersive experience
### 🎨 Modern Console-Style UI
- Inspired by Steam OS Big Picture and Xbox Dashboard
- Smooth animations and transitions
- Glowing focus indicators
- Dark theme optimized for OLED displays
### ⌨️ On-Screen Keyboard
- Built-in virtual keyboard for controller input
- URL and search input support
- Special keys for common domains (.com, .org, etc.)
## How to Access
### From Desktop Mode
1. **Menu Button (☰)** → Click **"🎮 Big Picture Mode"**
2. **Settings****General** → Click **"Launch Big Picture Mode"**
### Keyboard Shortcut
- Press `F11` while in Big Picture Mode to toggle fullscreen
### Automatic Detection
If Nebula detects a Steam Deck-sized display (1280x800), it will suggest Big Picture Mode in settings.
## Navigation Sections
| Section | Description |
|---------|-------------|
| **Home** | Quick access sites, search, and recent browsing |
| **Bookmarks** | Your saved websites in a tile grid |
| **History** | Recently visited sites |
| **Downloads** | Downloaded files |
| **NeBot AI** | Launch the AI assistant |
| **Settings** | Theme, privacy, and display options |
## Controller Button Reference
| Button | Action |
|--------|--------|
| D-Pad / Left Stick | Navigate between elements |
| A / Cross | Select focused element |
| B / Circle | Go back / Close menu |
| Y / Triangle | Open search (on-screen keyboard) |
| Start | Open/Close settings |
| LB/RB | Scroll horizontally |
## Exiting Big Picture Mode
- Press the **Exit** button in the top-right corner
- Go to **Settings****Desktop Mode**
- Press `Escape` key multiple times
## Technical Details
### Files
- `renderer/bigpicture.html` - Main HTML structure
- `renderer/bigpicture.css` - Console-optimized styles
- `renderer/bigpicture.js` - Controller handling and navigation
### Screen Detection
Big Picture Mode is suggested for displays matching:
- Steam Deck resolution: 1280×800
- Screens smaller than 1366px width
- 16:10 or 16:9 aspect ratios
### API
```javascript
// Check if Big Picture Mode is recommended
const suggested = await window.bigPictureAPI.isSuggested();
// Get screen information
const info = await window.bigPictureAPI.getScreenInfo();
// Launch Big Picture Mode
await window.bigPictureAPI.launch();
// Exit Big Picture Mode
await window.bigPictureAPI.exit();
```
## Customization
The Big Picture Mode respects your theme settings. Colors are applied from your selected theme:
- Background colors
- Accent and primary colors
- Text colors
## Known Limitations
- Some complex web forms may be difficult to navigate with controller only
- Video players use native controls
- Right-click context menus require mouse/touch
## Future Improvements
- [ ] Rumble/haptic feedback for compatible controllers
- [ ] Voice search integration with NeBot
- [ ] Picture-in-picture mode for videos
- [ ] Game overlay mode
- [ ] Custom controller mappings
+137
View File
@@ -68,6 +68,143 @@ ipcMain.removeHandler('window-minimize');
ipcMain.removeHandler('window-maximize');
ipcMain.removeHandler('window-close');
// =============================================================================
// BIG PICTURE MODE - Steam Deck / Console UI
// =============================================================================
// Steam Deck screen dimensions: 1280x800
const STEAM_DECK_WIDTH = 1280;
const STEAM_DECK_HEIGHT = 800;
const HANDHELD_THRESHOLD = 1366; // Consider screens smaller than this as "handheld"
let bigPictureWindow = null;
/**
* Check if the current display is likely a Steam Deck or similar handheld
*/
function isSteamDeckDisplay() {
const primaryDisplay = screen.getPrimaryDisplay();
const { width, height } = primaryDisplay.size;
// Check for Steam Deck resolution or similar small screens
const isSteamDeckRes = width === STEAM_DECK_WIDTH && height === STEAM_DECK_HEIGHT;
const isSmallScreen = width <= HANDHELD_THRESHOLD;
// Also check for certain aspect ratios common in handhelds (16:10, 16:9)
const aspectRatio = width / height;
const isHandheldAspect = aspectRatio >= 1.5 && aspectRatio <= 1.8;
return isSteamDeckRes || (isSmallScreen && isHandheldAspect);
}
/**
* Get screen info for UI decisions
*/
function getScreenInfo() {
const primaryDisplay = screen.getPrimaryDisplay();
const { width, height } = primaryDisplay.size;
const { scaleFactor } = primaryDisplay;
return {
width,
height,
scaleFactor,
isSteamDeck: width === STEAM_DECK_WIDTH && height === STEAM_DECK_HEIGHT,
isSmallScreen: width <= HANDHELD_THRESHOLD,
aspectRatio: width / height,
suggestBigPicture: isSteamDeckDisplay()
};
}
/**
* Create Big Picture Mode window
*/
function createBigPictureWindow() {
if (bigPictureWindow && !bigPictureWindow.isDestroyed()) {
bigPictureWindow.focus();
return bigPictureWindow;
}
const primaryDisplay = screen.getPrimaryDisplay();
const { width, height } = primaryDisplay.workAreaSize;
bigPictureWindow = new BrowserWindow({
width: width,
height: height,
fullscreen: true,
frame: false,
show: false,
backgroundColor: '#0a0a0f',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true,
webviewTag: true,
spellcheck: false,
webSecurity: true,
},
icon: process.platform === 'darwin'
? path.join(__dirname, 'assets/images/Logos/Nebula-Favicon.icns')
: path.join(__dirname, 'assets/images/Logos/Nebula-favicon.png'),
title: 'Nebula - Big Picture Mode'
});
bigPictureWindow.loadFile('renderer/bigpicture.html');
bigPictureWindow.once('ready-to-show', () => {
bigPictureWindow.show();
console.log('[BigPicture] Window ready');
});
bigPictureWindow.on('closed', () => {
bigPictureWindow = null;
console.log('[BigPicture] Window closed');
});
return bigPictureWindow;
}
/**
* Exit Big Picture Mode and return to desktop UI
*/
function exitBigPictureMode() {
if (bigPictureWindow && !bigPictureWindow.isDestroyed()) {
bigPictureWindow.close();
bigPictureWindow = null;
}
// Ensure main window exists and is focused
const windows = BrowserWindow.getAllWindows();
const mainWindow = windows.find(w => w !== bigPictureWindow);
if (mainWindow) {
mainWindow.focus();
} else if (windows.length === 0) {
createWindow();
}
}
// IPC handlers for Big Picture Mode
ipcMain.handle('get-screen-info', () => getScreenInfo());
ipcMain.handle('launch-bigpicture', () => {
createBigPictureWindow();
return { success: true };
});
ipcMain.handle('exit-bigpicture', () => {
exitBigPictureMode();
return { success: true };
});
ipcMain.handle('is-bigpicture-suggested', () => {
return isSteamDeckDisplay();
});
ipcMain.on('exit-bigpicture', () => {
exitBigPictureMode();
});
// =============================================================================
function createWindow(startUrl) {
+14
View File
@@ -116,6 +116,20 @@ contextBridge.exposeInMainWorld('aboutAPI', {
getInfo: () => ipcRenderer.invoke('get-about-info')
});
// Big Picture Mode API - Steam Deck / Console UI
contextBridge.exposeInMainWorld('bigPictureAPI', {
// Get screen info to determine if Big Picture Mode is recommended
getScreenInfo: () => ipcRenderer.invoke('get-screen-info'),
// Check if device is likely a Steam Deck or handheld
isSuggested: () => ipcRenderer.invoke('is-bigpicture-suggested'),
// Launch Big Picture Mode
launch: () => ipcRenderer.invoke('launch-bigpicture'),
// Exit Big Picture Mode
exit: () => ipcRenderer.invoke('exit-bigpicture'),
// Navigate to URL (from Big Picture Mode)
navigate: (url) => ipcRenderer.send('bigpicture-navigate', url)
});
// Relay context-menu commands from main to active renderer context (open new tabs etc.)
ipcRenderer.on('context-menu-command', (event, payload) => {
window.dispatchEvent(new CustomEvent('nebula-context-command', { detail: payload }));
File diff suppressed because it is too large Load Diff
+311
View File
@@ -0,0 +1,311 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nebula - Big Picture Mode</title>
<link rel="icon" href="../assets/images/Logos/Nebula-Icon.svg" type="image/svg+xml">
<link rel="stylesheet" href="bigpicture.css">
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/remixicon@3/fonts/remixicon.css" rel="stylesheet">
</head>
<body>
<!-- Audio feedback for navigation -->
<audio id="navSound" preload="auto">
<source src="data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQ==" type="audio/wav">
</audio>
<audio id="selectSound" preload="auto">
<source src="data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQ==" type="audio/wav">
</audio>
<!-- Main container with ambient background -->
<div class="bp-container">
<!-- Animated background -->
<div class="bp-background">
<div class="bg-gradient"></div>
<div class="bg-particles"></div>
<div class="bg-glow"></div>
</div>
<!-- Top header bar -->
<header class="bp-header">
<div class="header-left">
<img src="../assets/images/Logos/Nebula-Icon.svg" alt="Nebula" class="bp-logo">
<span class="bp-title">Nebula</span>
</div>
<div class="header-center">
<div class="clock-widget">
<span id="bp-time" class="time">--:--</span>
<span id="bp-date" class="date">---</span>
</div>
</div>
<div class="header-right">
<div class="status-icons">
<span id="bp-wifi" class="status-icon" title="Connected">
<span class="material-symbols-outlined">wifi</span>
</span>
<span id="bp-battery" class="status-icon" title="Battery">
<span class="material-symbols-outlined">battery_full</span>
</span>
</div>
<button id="exitBigPicture" class="bp-exit-btn" data-focusable tabindex="0">
<span class="material-symbols-outlined">close</span>
<span class="btn-label">Exit</span>
</button>
</div>
</header>
<!-- Main navigation area -->
<main class="bp-main">
<!-- Left sidebar navigation -->
<nav class="bp-sidebar">
<div class="nav-items">
<button class="nav-item active" data-section="home" data-focusable tabindex="0">
<span class="material-symbols-outlined">home</span>
<span class="nav-label">Home</span>
</button>
<button class="nav-item" data-section="browse" data-focusable tabindex="0">
<span class="material-symbols-outlined">language</span>
<span class="nav-label">Browse</span>
</button>
<button class="nav-item" data-section="bookmarks" data-focusable tabindex="0">
<span class="material-symbols-outlined">bookmarks</span>
<span class="nav-label">Bookmarks</span>
</button>
<button class="nav-item" data-section="history" data-focusable tabindex="0">
<span class="material-symbols-outlined">history</span>
<span class="nav-label">History</span>
</button>
<button class="nav-item" data-section="downloads" data-focusable tabindex="0">
<span class="material-symbols-outlined">download</span>
<span class="nav-label">Downloads</span>
</button>
</div>
<div class="nav-footer">
<button class="nav-item" data-section="settings" data-focusable tabindex="0">
<span class="material-symbols-outlined">settings</span>
<span class="nav-label">Settings</span>
</button>
</div>
</nav>
<!-- Content area -->
<div class="bp-content">
<!-- Webview container for browsing -->
<div id="webview-container" class="webview-container hidden"></div>
<!-- Home section -->
<section id="section-home" class="bp-section active">
<div class="section-header">
<h1 class="section-title">
<span id="greeting-text">Welcome back</span>
</h1>
<p class="section-subtitle">What would you like to browse today?</p>
</div>
<!-- Search card -->
<div class="search-card" data-focusable tabindex="0">
<div class="search-icon">
<span class="material-symbols-outlined">search</span>
</div>
<input type="text" id="bp-search" class="search-input" placeholder="Search the web or enter URL..." autocomplete="off">
<div class="search-hint">
<span class="key-hint">A</span> Search
</div>
</div>
<!-- Quick access grid -->
<div class="quick-access">
<h2 class="subsection-title">Quick Access</h2>
<div class="tile-grid" id="quickAccessGrid">
<!-- Tiles will be populated dynamically -->
</div>
</div>
<!-- Recent sites -->
<div class="recent-sites">
<h2 class="subsection-title">Continue Browsing</h2>
<div class="horizontal-scroll" id="recentSitesScroll">
<!-- Recent sites will be populated dynamically -->
</div>
</div>
</section>
<!-- Browse section (for webview) -->
<section id="section-browse" class="bp-section">
<!-- Webview container is outside sections -->
</section>
<!-- Bookmarks section -->
<section id="section-bookmarks" class="bp-section">
<div class="section-header">
<h1 class="section-title">Bookmarks</h1>
<p class="section-subtitle">Your saved websites</p>
</div>
<div class="tile-grid large" id="bookmarksGrid">
<!-- Bookmarks will be populated dynamically -->
</div>
</section>
<!-- History section -->
<section id="section-history" class="bp-section">
<div class="section-header">
<h1 class="section-title">History</h1>
<p class="section-subtitle">Recently visited sites</p>
</div>
<div class="list-container" id="historyList">
<!-- History will be populated dynamically -->
</div>
</section>
<!-- Downloads section -->
<section id="section-downloads" class="bp-section">
<div class="section-header">
<h1 class="section-title">Downloads</h1>
<p class="section-subtitle">Your downloaded files</p>
</div>
<div class="list-container" id="downloadsList">
<div class="empty-state">
<span class="material-symbols-outlined">folder_open</span>
<p>No recent downloads</p>
</div>
</div>
</section>
<!-- NeBot AI section -->
<section id="section-nebot" class="bp-section">
<div class="section-header">
<h1 class="section-title">NeBot AI Assistant</h1>
<p class="section-subtitle">Your AI-powered browsing companion</p>
</div>
<div class="nebot-launch">
<div class="nebot-card" data-focusable tabindex="0" id="launchNebot">
<div class="nebot-icon">
<span class="material-symbols-outlined">smart_toy</span>
</div>
<div class="nebot-info">
<h3>Start Conversation</h3>
<p>Ask questions, get summaries, and more</p>
</div>
<div class="nebot-action">
<span class="key-hint">A</span>
</div>
</div>
</div>
</section>
<!-- Settings section -->
<section id="section-settings" class="bp-section">
<div class="section-header">
<h1 class="section-title">Settings</h1>
<p class="section-subtitle">Configure your browser</p>
</div>
<div class="settings-grid">
<div class="settings-card" data-focusable tabindex="0" data-action="theme">
<span class="material-symbols-outlined">palette</span>
<span class="settings-label">Themes</span>
</div>
<div class="settings-card" data-focusable tabindex="0" data-action="privacy">
<span class="material-symbols-outlined">shield</span>
<span class="settings-label">Privacy</span>
</div>
<div class="settings-card" data-focusable tabindex="0" data-action="display">
<span class="material-symbols-outlined">display_settings</span>
<span class="settings-label">Display</span>
</div>
<div class="settings-card" data-focusable tabindex="0" data-action="exit-bigpicture">
<span class="material-symbols-outlined">desktop_windows</span>
<span class="settings-label">Desktop Mode</span>
</div>
</div>
</section>
</div>
</main>
<!-- Bottom controller hints -->
<footer class="bp-footer">
<div class="controller-hints">
<div class="hint">
<span class="controller-btn dpad">
<span class="material-symbols-outlined">gamepad</span>
</span>
<span>Navigate</span>
</div>
<div class="hint">
<span class="controller-btn a-btn">A</span>
<span>Select</span>
</div>
<div class="hint">
<span class="controller-btn b-btn">B</span>
<span>Back</span>
</div>
<div class="hint">
<span class="controller-btn y-btn">Y</span>
<span>Search</span>
</div>
<div class="hint">
<span class="controller-btn menu-btn"></span>
<span>Menu</span>
</div>
</div>
</footer>
<!-- On-screen keyboard (for controller input) -->
<div id="osk-overlay" class="osk-overlay hidden">
<div class="osk-container">
<div class="osk-header">
<input type="text" id="osk-input" class="osk-text-input" placeholder="Type with D-pad + A" readonly>
<button class="osk-close" data-focusable tabindex="0">
<span class="material-symbols-outlined">close</span>
</button>
</div>
<div class="osk-keyboard" id="osk-keyboard">
<!-- Keyboard rows will be generated by JS -->
</div>
<div class="osk-actions">
<button class="osk-action-btn" id="osk-space" data-focusable tabindex="0">
<span class="btn-hint">Y</span> Space
</button>
<button class="osk-action-btn" id="osk-backspace" data-focusable tabindex="0">
<span class="btn-hint">X</span>
<span class="material-symbols-outlined">backspace</span>
</button>
<button class="osk-action-btn" id="osk-clear" data-focusable tabindex="0">
<span class="btn-hint">LB</span> Clear
</button>
<button class="osk-action-btn primary" id="osk-submit" data-focusable tabindex="0">
<span class="btn-hint">RB</span> Go
</button>
</div>
<div class="osk-hints">
<span><b>A</b> Type</span>
<span><b>X</b> Backspace</span>
<span><b>Y</b> Space</span>
<span><b>B</b> Close</span>
<span><b>LB</b> Clear All</span>
<span><b>RB</b> Submit</span>
</div>
</div>
</div>
<!-- Context menu -->
<div id="context-menu" class="context-menu hidden">
<button class="context-item" data-action="open" data-focusable tabindex="0">
<span class="material-symbols-outlined">open_in_new</span>
<span>Open</span>
</button>
<button class="context-item" data-action="edit" data-focusable tabindex="0">
<span class="material-symbols-outlined">edit</span>
<span>Edit</span>
</button>
<button class="context-item" data-action="delete" data-focusable tabindex="0">
<span class="material-symbols-outlined">delete</span>
<span>Delete</span>
</button>
</div>
</div>
<script src="bigpicture.js"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
+1
View File
@@ -62,6 +62,7 @@
<div id="menu-popup" class="hidden">
<button onclick="openSettings()">Settings</button>
<!-- You can add more options here -->
<button id="bigpicture-btn" title="Launch Big Picture Mode (Controller/Steam Deck UI)">🎮 Big Picture Mode</button>
<button id="devtools-btn" title="Open / Close Developer Tools">Toggle Developer Tools</button>
<div class="zoom-controls">
<button id="zoom-out-btn">-</button>
+14
View File
@@ -1189,6 +1189,20 @@ window.addEventListener('DOMContentLoaded', () => {
});
}
// Big Picture Mode button
const bigPictureBtn = document.getElementById('bigpicture-btn');
if (bigPictureBtn && window.bigPictureAPI && window.bigPictureAPI.launch) {
bigPictureBtn.addEventListener('click', async () => {
try {
await window.bigPictureAPI.launch();
// Close the menu popup
if (menuPopup) menuPopup.classList.add('hidden');
} catch (e) {
console.error('Failed to launch Big Picture Mode:', e);
}
});
}
// wire up back/forward buttons
const backBtn = document.querySelector('.nav-left button:nth-child(1)');
const forwardBtn = document.querySelector('.nav-left button:nth-child(2)');
+29
View File
@@ -217,6 +217,35 @@ button:active {
transform: translateY(1px);
}
/* Primary button style (e.g., Big Picture Mode) */
.primary-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
font-size: 1rem;
font-weight: 600;
background: linear-gradient(135deg, #7B2EFF 0%, #5a1fd4 50%, #00C6FF 100%);
background-size: 200% 100%;
color: #fff;
border: 1px solid rgba(255,255,255,0.15);
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 20px rgba(123, 46, 255, 0.3);
}
.primary-btn:hover {
background-position: 100% 0;
box-shadow: 0 6px 30px rgba(123, 46, 255, 0.45), 0 0 20px rgba(0, 198, 255, 0.2);
transform: translateY(-2px);
}
.primary-btn:active {
transform: translateY(0);
box-shadow: 0 2px 10px rgba(123, 46, 255, 0.3);
}
.note {
font-size: 0.8rem;
color: #aaa;
+12
View File
@@ -30,6 +30,18 @@
</div>
<p class="note">Settings are stored locally on this device.</p>
<!-- Big Picture Mode -->
<div class="setting-group">
<h3>🎮 Big Picture Mode</h3>
<p class="note">A controller-friendly UI designed for Steam Deck and handheld devices.</p>
<div style="display: flex; align-items: center; gap: 12px; margin-top: 10px;">
<button id="launch-bigpicture-btn" class="primary-btn">
<span style="font-size: 18px;">🎮</span> Launch Big Picture Mode
</button>
<span id="bigpicture-status" class="note"></span>
</div>
</div>
<div class="setting-group">
<h3>Weather</h3>
<fieldset class="weather-units" style="display:flex; flex-direction:column; gap:8px; border:1px solid rgba(255,255,255,0.15); padding:10px; border-radius:8px;">
+43
View File
@@ -189,6 +189,49 @@ window.addEventListener('DOMContentLoaded', () => {
});
}
} catch (e) { console.warn('Display scale setup failed', e); }
// Big Picture Mode controls
try {
const bigPictureBtn = document.getElementById('launch-bigpicture-btn');
const bigPictureStatus = document.getElementById('bigpicture-status');
// Check if Big Picture Mode is recommended for this display
if (window.bigPictureAPI && typeof window.bigPictureAPI.isSuggested === 'function') {
window.bigPictureAPI.isSuggested().then(suggested => {
if (suggested && bigPictureStatus) {
bigPictureStatus.textContent = '✓ Recommended for your display';
bigPictureStatus.style.color = '#4ade80';
}
}).catch(() => {});
// Get screen info for display
window.bigPictureAPI.getScreenInfo().then(info => {
if (info && bigPictureStatus) {
const hint = info.isSteamDeck ? 'Steam Deck detected' :
info.isSmallScreen ? 'Small screen detected' : '';
if (hint && !bigPictureStatus.textContent) {
bigPictureStatus.textContent = hint;
}
}
}).catch(() => {});
}
if (bigPictureBtn) {
bigPictureBtn.addEventListener('click', async () => {
try {
if (window.bigPictureAPI && typeof window.bigPictureAPI.launch === 'function') {
showStatus('Launching Big Picture Mode...');
await window.bigPictureAPI.launch();
} else {
showStatus('Big Picture Mode not available');
}
} catch (e) {
console.error('Big Picture Mode launch error:', e);
showStatus('Failed to launch Big Picture Mode');
}
});
}
} catch (e) { console.warn('Big Picture Mode setup failed', e); }
});
// Tabs: simple controller
+12
View File
@@ -291,6 +291,18 @@ body:has(#home-container.active) #downloads-btn {
background: rgba(255,255,255,0.06);
}
/* Big Picture Mode button special style */
#bigpicture-btn {
background: linear-gradient(135deg, rgba(123, 46, 255, 0.15) 0%, rgba(0, 198, 255, 0.1) 100%) !important;
border: 1px solid rgba(123, 46, 255, 0.3) !important;
margin: 4px 0;
}
#bigpicture-btn:hover {
background: linear-gradient(135deg, rgba(123, 46, 255, 0.25) 0%, rgba(0, 198, 255, 0.15) 100%) !important;
border-color: rgba(123, 46, 255, 0.5) !important;
}
.hidden {
display: none;
}