Add Nebula Browser app, UI and assets

Add initial Nebula Browser project skeleton: CMakeLists to configure and link CEF (including post-build steps to copy runtime and UI files), a Windows CEF-based entry (app/main.cpp) that initializes CEF and loads the bundled UI, and a full ui/ and assets/ tree (HTML, CSS, JS, fonts, icons, and branding images). Update .gitignore to ignore build/out, thirdparty/cef, IDE and common OS artifacts.
This commit is contained in:
Andrew Zambazos
2026-05-13 22:17:58 +12:00
parent 79565f2ef3
commit 207a849f06
52 changed files with 13906 additions and 109 deletions
+89
View File
@@ -0,0 +1,89 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>404 - Page Not Found</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
:root {
--bg:#121212; --panel:#1e1e1e; --warn:#d97706; --danger:#dc2626; --text:#f5f5f5; --muted:#9ca3af; --accent:#6366f1;
color-scheme: dark;
}
body { margin:0; font-family: system-ui,-apple-system,Segoe UI,Roboto,Inter,Ubuntu,sans-serif; background:var(--bg); color:var(--text); display:flex; min-height:100vh; align-items:center; justify-content:center; padding:32px; }
.card { max-width:780px; width:100%; background:linear-gradient(145deg,#1c1c1c,#242424); border:1px solid #2c2c2c; border-radius:20px; padding:40px 46px 48px; box-shadow:0 8px 28px -6px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,0.04); position:relative; overflow:hidden; }
.card:before { content:""; position:absolute; inset:0; background:radial-gradient(circle at 18% 15%,rgba(255,255,255,.08),transparent 55%), radial-gradient(circle at 82% 78%,rgba(255,255,255,.05),transparent 60%); pointer-events:none; }
h1 { font-size: clamp(1.9rem, 2.6vw, 2.6rem); margin:0 0 12px; letter-spacing:-.5px; display:flex; align-items:center; gap:.6rem; }
h1 svg { width:28px; height:28px; flex-shrink:0; }
h1 span.badge { font-size:12px; letter-spacing:1px; padding:4px 8px; border:1px solid var(--danger); color:var(--danger); border-radius:999px; text-transform:uppercase; background:rgba(220,38,38,0.1); }
p.lede { font-size:1.05rem; line-height:1.55; margin:0 0 22px; color:var(--muted); }
code { background:#252525; padding:3px 6px; border-radius:6px; font-size:.9rem; color:#e0e0e0; }
.url-box { font-family:monospace; font-size:.92rem; padding:10px 12px; background:#181818; border:1px solid #2a2a2a; border-radius:10px; word-break:break-all; margin:0 0 22px; display:flex; align-items:center; gap:.75rem; }
.url-box svg { flex:0 0 auto; width:22px; height:22px; stroke:var(--danger); }
ul { margin:0 0 26px 1.1rem; padding:0; line-height:1.5; color:var(--muted); }
ul li { margin-bottom:6px; }
.actions { display:flex; flex-wrap:wrap; gap:14px; }
button { cursor:pointer; font-size:.95rem; letter-spacing:.4px; font-weight:500; border-radius:12px; padding:14px 26px; border:1px solid transparent; background:linear-gradient(135deg,#303030,#252525); color:#fff; position:relative; overflow:hidden; transition:.25s; }
button.primary { background:linear-gradient(135deg,#6366f1,#5145cd); box-shadow:0 4px 18px -4px rgba(99,102,241,.5); }
button.outline { background:transparent; border-color:#444; }
button:hover { filter:brightness(1.12); transform:translateY(-2px); }
button:active { transform:translateY(0); filter:brightness(.9); }
.mini { font-size:.75rem; text-transform:uppercase; letter-spacing:1px; opacity:.8; margin-top:24px; }
.fade-in { animation:fade .5s ease .05s both; }
@keyframes fade { from { opacity:0; transform: translateY(6px); } to { opacity:1; transform:none; } }
.grid { display:grid; gap:40px; }
@media (max-width:760px){ .card{padding:34px 28px 40px;} h1{font-size:2rem;} }
</style>
</head>
<body>
<div class="card fade-in">
<h1>
<svg viewBox="0 0 24 24" fill="none" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg>
Page Not Found <span class="badge">404</span>
</h1>
<p class="lede">The page you're looking for doesn't exist or has been moved. You've warped into an unknown sector of the web.</p>
<div class="url-box" id="targetBox" title="Attempted URL"></div>
<ul>
<li>The URL might be typed incorrectly.</li>
<li>The page may have been removed or relocated.</li>
<li>The link you followed could be outdated or broken.</li>
<li>Try going back or navigate to the home page.</li>
</ul>
<div class="actions">
<button id="backBtn" class="outline" aria-label="Go Back">Go Back</button>
<button id="homeBtn" class="primary" aria-label="Go to Home">Go to Home</button>
<button id="settingsBtn" aria-label="Open Settings">Open Settings</button>
</div>
<div class="mini">Nebula Navigation Error</div>
</div>
<script>
(function(){
const params = new URLSearchParams(location.search);
const attemptedUrl = params.get('url');
const box = document.getElementById('targetBox');
if (attemptedUrl) {
box.textContent = decodeURIComponent(attemptedUrl);
} else {
box.textContent = 'Unknown URL';
}
function sendNavigate(url, opts){
if (window.electronAPI && window.electronAPI.sendToHost){
window.electronAPI.sendToHost('navigate', url, opts||{});
} else if (window.parent && window.parent !== window) {
window.parent.postMessage({ type:'navigate', url, opts }, '*');
} else if (url === 'nebula://home') {
window.location.href = 'home.html';
} else if (url === 'nebula://settings') {
window.location.href = 'settings.html';
} else {
window.location.href = url;
}
}
document.getElementById('backBtn').onclick = () => history.length > 1 ? history.back() : sendNavigate('nebula://home');
document.getElementById('homeBtn').onclick = () => sendNavigate('nebula://home');
document.getElementById('settingsBtn').onclick = () => window.location.href = "settings.html";
})();
</script>
</body>
</html>
+529
View File
@@ -0,0 +1,529 @@
<!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/branding/Nebula-Icon.svg" type="image/svg+xml">
<link rel="stylesheet" href="../css/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/branding/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="section-actions">
<button class="action-btn" id="addBookmarkBtn" data-focusable tabindex="0">
<span class="material-symbols-outlined">bookmark_add</span>
<span>Add Bookmark</span>
</button>
<button class="action-btn" id="addCurrentBookmarkBtn" data-focusable tabindex="0">
<span class="material-symbols-outlined">bookmark</span>
<span>Add Current Page</span>
</button>
</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="section-actions">
<button class="action-btn" id="clearHistoryBtn" data-focusable tabindex="0">
<span class="material-symbols-outlined">delete_sweep</span>
<span>Clear History</span>
</button>
<button class="action-btn" id="refreshHistoryBtn" data-focusable tabindex="0">
<span class="material-symbols-outlined">refresh</span>
<span>Refresh</span>
</button>
</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>
<!-- Settings categories navigation -->
<div class="settings-tabs">
<button class="settings-tab active" data-settings-tab="themes" data-focusable tabindex="0">
<span class="material-symbols-outlined">palette</span>
<span>Themes</span>
</button>
<button class="settings-tab" data-settings-tab="display" data-focusable tabindex="0">
<span class="material-symbols-outlined">display_settings</span>
<span>Display</span>
</button>
<button class="settings-tab" data-settings-tab="privacy" data-focusable tabindex="0">
<span class="material-symbols-outlined">shield</span>
<span>Privacy</span>
</button>
<button class="settings-tab" data-settings-tab="about" data-focusable tabindex="0">
<span class="material-symbols-outlined">info</span>
<span>About</span>
</button>
</div>
<!-- Settings panels -->
<div class="settings-panels">
<!-- Themes Panel -->
<div class="settings-panel active" id="settings-panel-themes">
<h2 class="settings-panel-title">Theme Presets</h2>
<div class="theme-grid" id="bp-theme-grid">
<button class="theme-card" data-theme="default" data-focusable tabindex="0">
<div class="theme-preview" style="background: linear-gradient(145deg, #121418, #1B1035);"></div>
<span class="theme-name">Default</span>
</button>
<button class="theme-card" data-theme="ocean" data-focusable tabindex="0">
<div class="theme-preview" style="background: linear-gradient(145deg, #1a365d, #2c5282);"></div>
<span class="theme-name">Ocean</span>
</button>
<button class="theme-card" data-theme="forest" data-focusable tabindex="0">
<div class="theme-preview" style="background: linear-gradient(145deg, #1a202c, #2d3748);"></div>
<span class="theme-name">Forest</span>
</button>
<button class="theme-card" data-theme="sunset" data-focusable tabindex="0">
<div class="theme-preview" style="background: linear-gradient(145deg, #744210, #c05621);"></div>
<span class="theme-name">Sunset</span>
</button>
<button class="theme-card" data-theme="cyberpunk" data-focusable tabindex="0">
<div class="theme-preview" style="background: linear-gradient(145deg, #0a0a0a, #2a0a3a);"></div>
<span class="theme-name">Cyberpunk</span>
</button>
<button class="theme-card" data-theme="midnight-rose" data-focusable tabindex="0">
<div class="theme-preview" style="background: linear-gradient(145deg, #1c1820, #3d3046);"></div>
<span class="theme-name">Midnight Rose</span>
</button>
<button class="theme-card" data-theme="arctic-ice" data-focusable tabindex="0">
<div class="theme-preview" style="background: linear-gradient(145deg, #f0f8ff, #d1e7ff);"></div>
<span class="theme-name">Arctic Ice</span>
</button>
<button class="theme-card" data-theme="cherry-blossom" data-focusable tabindex="0">
<div class="theme-preview" style="background: linear-gradient(145deg, #fff5f8, #ffd4db);"></div>
<span class="theme-name">Cherry Blossom</span>
</button>
<button class="theme-card" data-theme="cosmic-purple" data-focusable tabindex="0">
<div class="theme-preview" style="background: linear-gradient(145deg, #0f0524, #2d1b69);"></div>
<span class="theme-name">Cosmic Purple</span>
</button>
<button class="theme-card" data-theme="emerald-dream" data-focusable tabindex="0">
<div class="theme-preview" style="background: linear-gradient(145deg, #0d2818, #2d5a44);"></div>
<span class="theme-name">Emerald Dream</span>
</button>
<button class="theme-card" data-theme="mocha-coffee" data-focusable tabindex="0">
<div class="theme-preview" style="background: linear-gradient(145deg, #3c2414, #5d3a26);"></div>
<span class="theme-name">Mocha Coffee</span>
</button>
<button class="theme-card" data-theme="lavender-fields" data-focusable tabindex="0">
<div class="theme-preview" style="background: linear-gradient(145deg, #f8f4ff, #e6d8ff);"></div>
<span class="theme-name">Lavender Fields</span>
</button>
</div>
</div>
<!-- Display Panel -->
<div class="settings-panel" id="settings-panel-display">
<h2 class="settings-panel-title">Display Settings</h2>
<div class="settings-option">
<div class="option-info">
<span class="material-symbols-outlined">zoom_in</span>
<div class="option-text">
<span class="option-label">Display Scale</span>
<span class="option-description">Adjust the default zoom level</span>
</div>
</div>
<div class="option-control">
<button class="scale-btn" id="bp-scale-down" data-focusable tabindex="0">
<span class="material-symbols-outlined">remove</span>
</button>
<span class="scale-value" id="bp-scale-value">100%</span>
<button class="scale-btn" id="bp-scale-up" data-focusable tabindex="0">
<span class="material-symbols-outlined">add</span>
</button>
</div>
</div>
<div class="settings-option">
<div class="option-info">
<span class="material-symbols-outlined">desktop_windows</span>
<div class="option-text">
<span class="option-label">Exit Big Picture Mode</span>
<span class="option-description">Return to standard desktop interface</span>
</div>
</div>
<div class="option-control">
<button class="action-button" id="bp-exit-desktop" data-focusable tabindex="0">
<span class="material-symbols-outlined">logout</span>
<span>Exit</span>
</button>
</div>
</div>
</div>
<!-- Privacy Panel -->
<div class="settings-panel" id="settings-panel-privacy">
<h2 class="settings-panel-title">Privacy & Data</h2>
<div class="settings-option">
<div class="option-info">
<span class="material-symbols-outlined">delete_sweep</span>
<div class="option-text">
<span class="option-label">Clear Browsing Data</span>
<span class="option-description">Delete cookies, cache, and site data</span>
</div>
</div>
<div class="option-control">
<button class="action-button danger" id="bp-clear-data" data-focusable tabindex="0">
<span class="material-symbols-outlined">delete</span>
<span>Clear All</span>
</button>
</div>
</div>
<div class="settings-option">
<div class="option-info">
<span class="material-symbols-outlined">history</span>
<div class="option-text">
<span class="option-label">Clear History</span>
<span class="option-description">Delete browsing history</span>
</div>
</div>
<div class="option-control">
<button class="action-button" id="bp-clear-history" data-focusable tabindex="0">
<span class="material-symbols-outlined">delete</span>
<span>Clear</span>
</button>
</div>
</div>
<div class="settings-option">
<div class="option-info">
<span class="material-symbols-outlined">search_off</span>
<div class="option-text">
<span class="option-label">Clear Search History</span>
<span class="option-description">Delete search query history</span>
</div>
</div>
<div class="option-control">
<button class="action-button" id="bp-clear-search" data-focusable tabindex="0">
<span class="material-symbols-outlined">delete</span>
<span>Clear</span>
</button>
</div>
</div>
</div>
<!-- About Panel -->
<div class="settings-panel" id="settings-panel-about">
<h2 class="settings-panel-title">About Nebula Browser</h2>
<div class="about-info">
<div class="about-logo">
<img src="../assets/images/branding/Nebula-Icon.svg" alt="Nebula" class="about-logo-img">
<div class="about-title">
<h3>Nebula Browser</h3>
<span id="bp-version">Version loading...</span>
</div>
</div>
<div class="about-details">
<div class="about-row">
<span class="about-label">CEF</span>
<span class="about-value" id="bp-cef-version">--</span>
</div>
<div class="about-row">
<span class="about-label">Chromium</span>
<span class="about-value" id="bp-chromium-version">--</span>
</div>
<div class="about-row">
<span class="about-label">Node.js</span>
<span class="about-value" id="bp-node-version">--</span>
</div>
<div class="about-row">
<span class="about-label">Platform</span>
<span class="about-value" id="bp-platform">--</span>
</div>
</div>
<div class="about-actions">
<button class="action-button" id="bp-github-link" data-focusable tabindex="0">
<span class="ri-github-fill" style="font-size: 20px;"></span>
<span>GitHub</span>
</button>
<button class="action-button" id="bp-copy-diagnostics" data-focusable tabindex="0">
<span class="material-symbols-outlined">content_copy</span>
<span>Copy Info</span>
</button>
</div>
</div>
</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-title">
<span class="material-symbols-outlined">keyboard</span>
<span id="osk-label">Enter text</span>
</div>
<div class="osk-header">
<div class="osk-input-wrapper">
<input type="text" id="osk-input" class="osk-text-input" placeholder="Your text appears here..." readonly>
<span id="osk-cursor" class="osk-cursor"></span>
<span id="osk-text-measure" class="osk-text-measure"></span>
</div>
<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="../js/bigpicture.js"></script>
</body>
</html>
+147
View File
@@ -0,0 +1,147 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Downloads</title>
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/performance.css" />
<style>
body { font-family: system-ui, Segoe UI, Roboto, sans-serif; margin: 16px; color: #eee; background: #121212; }
h1 { font-size: 20px; margin: 0 0 12px; }
.download-list { display: flex; flex-direction: column; gap: 10px; }
.download-item { background: #1e1e1e; border: 1px solid #2a2a2a; border-radius: 8px; padding: 10px 12px; display: grid; grid-template-columns: 1fr auto; gap: 6px 12px; align-items: center; }
.file { font-weight: 600; color: #fafafa; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.meta { font-size: 12px; color: #bbb; }
.progress { height: 6px; background: #2a2a2a; border-radius: 4px; overflow: hidden; grid-column: 1 / -1; }
.bar { height: 100%; background: #3b82f6; width: 0%; transition: width .15s linear; }
.actions { display: flex; gap: 6px; }
button { background: #2b2b2b; border: 1px solid #3a3a3a; color: #eee; border-radius: 6px; padding: 6px 10px; cursor: pointer; }
button:hover { background: #333; }
.state { font-size: 12px; color: #aaa; }
.row { display: flex; gap: 12px; justify-content: space-between; align-items: center; }
.empty { color: #888; font-style: italic; padding: 20px; text-align: center; }
.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.scan { font-size: 12px; }
.scan.bad { color: #f87171; }
.scan.good { color: #34d399; }
.scan.pending { color: #fbbf24; }
</style>
</head>
<body>
<div class="toolbar">
<h1>Downloads</h1>
<div>
<button id="clear-completed">Clear Completed</button>
</div>
</div>
<div id="list" class="download-list"></div>
<div id="empty" class="empty" style="display:none;">No downloads yet</div>
<script>
const api = window.downloadsAPI || null;
const listEl = document.getElementById('list');
const emptyEl = document.getElementById('empty');
const clearBtn = document.getElementById('clear-completed');
function fmtBytes(n) {
if (!n || n <= 0) return '0 B';
const u = ['B','KB','MB','GB','TB'];
const i = Math.floor(Math.log(n)/Math.log(1024));
return (n/Math.pow(1024,i)).toFixed( i===0 ? 0 : 1 ) + ' ' + u[i];
}
function esc(s) {
return (s || '').replace(/[&<>"']/g, (c) => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
})[c]);
}
function rowHtml(d){
const pct = d.totalBytes > 0 ? Math.min(100, Math.round((d.receivedBytes||0) * 100 / d.totalBytes)) : 0;
const scan = d.scan || { status: 'unavailable' };
const isInfected = scan.status === 'infected';
const isScanning = scan.status === 'scanning';
const scanCls = scan.status === 'infected' ? 'scan bad' : (scan.status === 'clean' ? 'scan good' : (scan.status==='scanning'?'scan pending':'scan'));
const scanText = scan.status === 'infected' ? `Threat detected (${scan.engine||''})` :
scan.status === 'clean' ? `Scanned clean (${scan.engine||''})` :
scan.status === 'scanning' ? `Scanning... (${scan.engine||''})` :
scan.status === 'pending' ? `Queued for scan (${scan.engine||''})` :
scan.status === 'error' ? `Scan error${scan.details?': '+esc(scan.details):''}` :
'Scan unavailable';
return `
<div class="download-item" id="dl-${d.id}">
<div class="file" title="${d.filename}">${d.filename}</div>
<div class="actions">
${d.state==='in-progress' ? `
<button data-act="${d.paused?'resume':'pause'}" data-id="${d.id}">${d.paused?'Resume':'Pause'}</button>
<button data-act="cancel" data-id="${d.id}">Cancel</button>
` : `
<button data-act="open-file" data-id="${d.id}" ${(d.state!=='completed'||isInfected)?'disabled':''}>Open</button>
<button data-act="show-in-folder" data-id="${d.id}">Show in Folder</button>
${isInfected ? `<button data-act="delete-file" data-id="${d.id}">Delete</button>` : ''}
${d.state!=='in-progress' ? `<button data-act="rescan" data-id="${d.id}" ${isScanning?'disabled':''}>Rescan</button>` : ''}
`}
</div>
<div class="meta">
<span class="state">${d.state}</span>
· ${fmtBytes(d.receivedBytes||0)} / ${fmtBytes(d.totalBytes||0)}
· <span class="${scanCls}">${scanText}</span>
</div>
<div class="progress"><div class="bar" style="width:${pct}%"></div></div>
</div>
`;
}
function render(entries){
listEl.innerHTML = entries.map(rowHtml).join('');
emptyEl.style.display = entries.length ? 'none' : 'block';
}
async function refresh(){
if (!api || typeof api.list !== 'function') {
render([]);
emptyEl.textContent = 'Downloads are managed by CEF in this build';
return;
}
const all = await api.list();
// Newest first by start time
all.sort((a,b)=> (b.startedAt||0)-(a.startedAt||0));
render(all);
}
listEl.addEventListener('click', async (e)=>{
const btn = e.target.closest('button');
if (!btn) return;
const id = btn.getAttribute('data-id');
const act = btn.getAttribute('data-act');
if (!id || !act) return;
if (!api || typeof api.action !== 'function') return;
await api.action(id, act);
if (act==='cancel') refresh();
});
clearBtn.addEventListener('click', async ()=>{
if (!api || typeof api.clearCompleted !== 'function') return;
await api.clearCompleted();
refresh();
});
api?.onStarted?.(()=> refresh());
api?.onUpdated?.(()=> {
// Partial update: just patch progress bar and bytes if present
// For simplicity now, refresh list
refresh();
});
api?.onDone?.(()=> refresh());
api?.onScanStarted?.(()=> refresh());
api?.onScanResult?.(()=> refresh());
api?.onCleared?.(()=> refresh());
refresh();
</script>
</body>
</html>
+231
View File
@@ -0,0 +1,231 @@
<!DOCTYPE html>
<html>
<head>
<title>GPU Diagnostics - Nebula Browser</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.status {
padding: 10px;
margin: 10px 0;
border-radius: 4px;
}
.status.good { background: #d4edda; color: #155724; }
.status.warning { background: #fff3cd; color: #856404; }
.status.error { background: #f8d7da; color: #721c24; }
button {
background: #007bff;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:hover { background: #0056b3; }
pre {
background: #f8f9fa;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
font-size: 12px;
}
.canvas-test {
border: 1px solid #ccc;
margin: 10px 0;
}
</style>
</head>
<body>
<div class="container">
<h1>GPU Diagnostics</h1>
<div id="gpu-status" class="status">
<h3>GPU Status</h3>
<p>Loading GPU information...</p>
</div>
<div class="status">
<h3>WebGL Test</h3>
<canvas id="webgl-canvas" class="canvas-test" width="300" height="150"></canvas>
<p id="webgl-status">Testing WebGL...</p>
</div>
<div class="status">
<h3>Canvas 2D Acceleration Test</h3>
<canvas id="canvas2d" class="canvas-test" width="300" height="150"></canvas>
<p id="canvas2d-status">Testing Canvas 2D...</p>
</div>
<div>
<h3>Actions</h3>
<button onclick="refreshGPUInfo()">Refresh GPU Info</button>
<button onclick="forceGC()">Force Garbage Collection</button>
<button onclick="applyFallback(1)">Apply GPU Fallback Level 1</button>
<button onclick="applyFallback(2)">Apply GPU Fallback Level 2</button>
</div>
<div>
<h3>Detailed GPU Information</h3>
<pre id="gpu-details">Loading...</pre>
</div>
</div>
<script>
async function refreshGPUInfo() {
try {
if (!window.electronAPI?.invoke) {
const statusDiv = document.getElementById('gpu-status');
const detailsDiv = document.getElementById('gpu-details');
statusDiv.className = 'status warning';
statusDiv.innerHTML = '<h3>GPU Status</h3><p>Native GPU diagnostics are not exposed to this CEF page.</p>';
detailsDiv.textContent = navigator.userAgent;
return;
}
const gpuInfo = await window.electronAPI.invoke('get-gpu-info');
const statusDiv = document.getElementById('gpu-status');
const detailsDiv = document.getElementById('gpu-details');
if (gpuInfo.error) {
statusDiv.className = 'status error';
statusDiv.innerHTML = `<h3>GPU Status</h3><p>Error: ${gpuInfo.error}</p>`;
} else {
const isGPUWorking = checkGPUFeatures(gpuInfo.featureStatus);
statusDiv.className = `status ${isGPUWorking ? 'good' : 'warning'}`;
statusDiv.innerHTML = `
<h3>GPU Status</h3>
<p><strong>Hardware Acceleration:</strong> ${isGPUWorking ? 'Enabled' : 'Disabled/Limited'}</p>
<p><strong>Fallback Level:</strong> ${gpuInfo.fallbackStatus?.fallbackLevel || 0}</p>
<p><strong>GPU Enabled:</strong> ${gpuInfo.fallbackStatus?.gpuEnabled ? 'Yes' : 'No'}</p>
`;
}
detailsDiv.textContent = JSON.stringify(gpuInfo, null, 2);
} catch (err) {
console.error('Failed to get GPU info:', err);
document.getElementById('gpu-status').innerHTML = `<h3>GPU Status</h3><p>Error: ${err.message}</p>`;
}
}
function checkGPUFeatures(features) {
const criticalFeatures = ['gpu_compositing', 'webgl', 'webgl2'];
return criticalFeatures.some(feature =>
features[feature] && !features[feature].includes('disabled')
);
}
async function forceGC() {
try {
if (!window.electronAPI?.invoke) {
alert('Garbage collection is managed by CEF in this build.');
return;
}
await window.electronAPI.invoke('force-gc');
alert('Garbage collection completed');
} catch (err) {
alert('Failed to force GC: ' + err.message);
}
}
async function applyFallback(level) {
try {
if (!window.electronAPI?.invoke) {
alert('GPU fallback settings are managed by the native CEF app.');
return;
}
const result = await window.electronAPI.invoke('apply-gpu-fallback', level);
if (result.success) {
alert(`Applied GPU fallback level ${level}. App restart may be required.`);
} else {
alert('Failed to apply fallback: ' + result.error);
}
} catch (err) {
alert('Failed to apply fallback: ' + err.message);
}
}
// Test WebGL
function testWebGL() {
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
const status = document.getElementById('webgl-status');
if (gl) {
// Draw a simple triangle
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
`);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, `
precision mediump float;
void main() {
gl_Color = vec4(0.0, 1.0, 0.0, 1.0);
}
`);
gl.compileShader(fragmentShader);
status.textContent = 'WebGL: Available ✓';
status.parentElement.className = 'status good';
// Clear with green color to show it's working
gl.clearColor(0.0, 0.8, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
} else {
status.textContent = 'WebGL: Not Available ✗';
status.parentElement.className = 'status error';
}
}
// Test Canvas 2D
function testCanvas2D() {
const canvas = document.getElementById('canvas2d');
const ctx = canvas.getContext('2d');
const status = document.getElementById('canvas2d-status');
try {
// Draw some graphics to test acceleration
const gradient = ctx.createLinearGradient(0, 0, 300, 0);
gradient.addColorStop(0, '#ff0000');
gradient.addColorStop(1, '#0000ff');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 300, 150);
ctx.fillStyle = 'white';
ctx.font = '20px Arial';
ctx.fillText('Canvas 2D Working!', 50, 80);
status.textContent = 'Canvas 2D: Working ✓';
status.parentElement.className = 'status good';
} catch (err) {
status.textContent = 'Canvas 2D: Error - ' + err.message;
status.parentElement.className = 'status error';
}
}
// Initialize tests
window.addEventListener('DOMContentLoaded', () => {
refreshGPUInfo();
testWebGL();
testCanvas2D();
});
</script>
</body>
</html>
+181
View File
@@ -0,0 +1,181 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>New Tab</title>
<link rel="icon" href="../assets/images/branding/Nebula-Icon.svg" type="image/svg+xml">
<link rel="stylesheet" href="../css/home.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">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1/font/bootstrap-icons.css" rel="stylesheet">
</head>
<body>
<div class="home-container">
<button id="editLayoutBtn" class="edit-btn" aria-pressed="false" title="Edit layout">Edit</button>
<!-- Dynamic greeting replaces the large logo for a cleaner hero look -->
<h1 id="greeting" class="greeting-title">Welcome</h1>
<!-- Retain logo for branding but keep it subtle/optional -->
<div class="logo" aria-hidden="true" style="display:none">
<img src="../assets/images/branding/Nebula-Logo.svg" class="logo-img">
<div class="logo-text">Nebula Browser</div>
</div>
<div class="search-container">
<div class="search-engine-selector">
<button id="searchEngineBtn" class="search-engine-btn">
<img id="searchEngineLogo" src="../assets/icons/searchengines/google.svg" alt="Search Engine">
</button>
<div id="searchEngineDropdown" class="search-engine-dropdown hidden">
<div class="search-engine-option" data-engine="google">
<img src="../assets/icons/searchengines/google.svg" alt="Google">
</div>
<div class="search-engine-option" data-engine="bing">
<img src="../assets/icons/searchengines/Bing.svg" alt="Bing">
</div>
<div class="search-engine-option" data-engine="duckduckgo">
<img src="../assets/icons/searchengines/duckduckgo.svg" alt="DuckDuckGo">
</div>
</div>
</div>
<div class="search-bar">
<input type="text" id="searchInput" class="search-input" placeholder="Search">
<button id="searchBtn" class="search-btn">
<span class="material-symbols-outlined">search</span>
</button>
</div>
</div>
<!-- Top Sites card -->
<section class="top-sites-card">
<header class="top-sites-header">
<h2>Bookmarks</h2>
<button id="resetTopSites" class="link-btn" title="Clear bookmarks">Reset</button>
</header>
<div class="bookmarks" id="bookmarkList">
<!-- Bookmarks dynamically inserted here -->
</div>
</section>
</div>
<!-- At a glance widget -->
<aside class="glance" aria-live="polite">
<div class="glance-card">
<div class="glance-title">At A Glance</div>
<div class="glance-grid">
<div class="glance-tile">
<div class="glance-label">Time</div>
<div id="clock" class="glance-value">--:--</div>
</div>
<div class="glance-tile">
<div class="glance-label">Weather</div>
<div id="weather" class="glance-value">Fetching…</div>
</div>
</div>
</div>
</aside>
<!-- Edit mode toolbar -->
<div id="editToolbar" class="edit-toolbar" hidden>
<label style="display:flex;align-items:center;gap:6px;margin-right:8px;">
<input type="checkbox" id="toggleShowGreeting" checked>
<span>Show greeting</span>
</label>
<label style="display:flex;align-items:center;gap:6px;margin-right:8px;">
<input type="checkbox" id="toggleShowBookmarks" checked>
<span>Show bookmarks</span>
</label>
<label style="display:flex;align-items:center;gap:6px;margin-right:12px;">
<input type="checkbox" id="toggleShowGlance" checked>
<span>Show At a Glance</span>
</label>
<button id="cancelEditBtn" class="btn secondary" aria-label="Cancel layout edits">Cancel</button>
<button id="saveEditBtn" class="btn primary" aria-label="Save layout edits">Save</button>
</div>
<!-- Popup for adding a bookmark -->
<div id="addPopup" class="popup hidden">
<div class="popup-inner">
<h2>Add New Bookmark</h2>
<!-- Title field -->
<label for="titleInput">Title</label>
<input type="text" id="titleInput" placeholder="Enter title">
<!-- URL field -->
<label for="urlInput">URL</label>
<input type="url" id="urlInput" placeholder="https://example.com">
<!-- Icon picker -->
<div class="icon-picker-layout">
<nav class="icon-side-nav" id="iconCategoryNav" aria-label="Icon Categories">
<!-- Category nav buttons injected here -->
</nav>
<div class="icon-main">
<div class="icon-header">
<label for="iconFilter" class="icon-filter-label">Icon</label>
<div class="favicon-toggle">
<input type="checkbox" id="useFavicon" class="favicon-checkbox">
<label for="useFavicon" class="favicon-label">Use site favicon</label>
</div>
</div>
<input type="text" id="iconFilter" class="icon-filter"
placeholder="Search for icon, enter emoji, or type 'favicon'">
<div id="iconGrid" class="icon-grid" tabindex="0" aria-label="Icon selection list"></div>
<input type="hidden" id="selectedIcon">
</div>
</div>
<!-- action buttons -->
<div class="popup-buttons">
<button id="cancelBtn">Cancel</button>
<button id="saveBookmarkBtn">Add</button>
</div>
</div>
</div>
<!-- Theme loader script -->
<script src="../js/customization.js"></script>
<script>
// Apply saved theme on page load and listen for updates
document.addEventListener('DOMContentLoaded', () => {
BrowserCustomizer.applyThemeToPage();
// Function to update logo and title based on theme
function updateLogoAndTitle(theme) {
const logoText = document.querySelector('.logo-text');
const logoImg = document.querySelector('.logo-img');
if (logoText) {
logoText.textContent = theme.customTitle || 'Nebula Browser';
}
if (logoImg) {
logoImg.style.display = theme.showLogo ? 'block' : 'none';
}
}
// Listen for theme updates via postMessage fallback
window.addEventListener('message', (event) => {
if (event.data.type === 'theme-update') {
const theme = event.data.theme;
localStorage.setItem('currentTheme', JSON.stringify(theme));
BrowserCustomizer.applyThemeToPage();
updateLogoAndTitle(theme);
}
});
// Listen for theme updates when a native bridge provides them.
if (window.electronAPI && typeof window.electronAPI.on === 'function') {
window.electronAPI.on('theme-update', (theme) => {
localStorage.setItem('currentTheme', JSON.stringify(theme));
BrowserCustomizer.applyThemeToPage();
updateLogoAndTitle(theme);
});
}
});
</script>
<!-- make this a module so we can import icons -->
<script type="module" src="../js/home.js"></script>
</body>
</html>
+32
View File
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nebula Browser</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body class="cef-shell">
<main class="cef-start">
<section class="cef-card">
<p class="eyebrow">Nebula Browser</p>
<h1>Browse with CEF</h1>
<p class="lede">This page is now a lightweight start surface. Tabs, windows, and page hosting are handled by the CEF application.</p>
<form id="start-form" class="start-search" autocomplete="off">
<label class="sr-only" for="start-url">Search or enter address</label>
<input id="start-url" type="text" placeholder="Search or enter address" autofocus>
<button type="submit">Go</button>
</form>
<nav class="quick-links" aria-label="Nebula pages">
<a href="home.html">Home</a>
<a href="settings.html">Settings</a>
<a href="downloads.html">Downloads</a>
</nav>
</section>
</main>
<script src="../js/script.js"></script>
</body>
</html>
+88
View File
@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Connection Not Secure</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
:root {
--bg:#121212; --panel:#1e1e1e; --warn:#d97706; --danger:#dc2626; --text:#f5f5f5; --muted:#9ca3af; --accent:#6366f1;
color-scheme: dark;
}
body { margin:0; font-family: system-ui,-apple-system,Segoe UI,Roboto,Inter,Ubuntu,sans-serif; background:var(--bg); color:var(--text); display:flex; min-height:100vh; align-items:center; justify-content:center; padding:32px; }
.card { max-width:780px; width:100%; background:linear-gradient(145deg,#1c1c1c,#242424); border:1px solid #2c2c2c; border-radius:20px; padding:40px 46px 48px; box-shadow:0 8px 28px -6px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,0.04); position:relative; overflow:hidden; }
.card:before { content:""; position:absolute; inset:0; background:radial-gradient(circle at 18% 15%,rgba(255,255,255,.08),transparent 55%), radial-gradient(circle at 82% 78%,rgba(255,255,255,.05),transparent 60%); pointer-events:none; }
h1 { font-size: clamp(1.9rem, 2.6vw, 2.6rem); margin:0 0 12px; letter-spacing:-.5px; display:flex; align-items:center; gap:.6rem; }
h1 span.badge { font-size:12px; letter-spacing:1px; padding:4px 8px; border:1px solid var(--warn); color:var(--warn); border-radius:999px; text-transform:uppercase; background:rgba(217,119,6,0.1); }
p.lede { font-size:1.05rem; line-height:1.55; margin:0 0 22px; color:var(--muted); }
code { background:#252525; padding:3px 6px; border-radius:6px; font-size:.9rem; color:#e0e0e0; }
.url-box { font-family:monospace; font-size:.92rem; padding:10px 12px; background:#181818; border:1px solid #2a2a2a; border-radius:10px; word-break:break-all; margin:0 0 22px; display:flex; align-items:center; gap:.75rem; }
.url-box svg { flex:0 0 auto; width:22px; height:22px; stroke:var(--warn); }
ul { margin:0 0 26px 1.1rem; padding:0; line-height:1.5; color:var(--muted); }
ul li { margin-bottom:6px; }
.actions { display:flex; flex-wrap:wrap; gap:14px; }
button { cursor:pointer; font-size:.95rem; letter-spacing:.4px; font-weight:500; border-radius:12px; padding:14px 26px; border:1px solid transparent; background:linear-gradient(135deg,#303030,#252525); color:#fff; position:relative; overflow:hidden; transition:.25s; }
button.primary { background:linear-gradient(135deg,#6366f1,#5145cd); box-shadow:0 4px 18px -4px rgba(99,102,241,.5); }
button.danger { background:linear-gradient(135deg,#b91c1c,#7f1d1d); border-color:#dc2626; }
button.outline { background:transparent; border-color:#444; }
button:hover { filter:brightness(1.12); transform:translateY(-2px); }
button:active { transform:translateY(0); filter:brightness(.9); }
.mini { font-size:.75rem; text-transform:uppercase; letter-spacing:1px; opacity:.8; margin-top:24px; }
.fade-in { animation:fade .5s ease .05s both; }
@keyframes fade { from { opacity:0; transform: translateY(6px); } to { opacity:1; transform:none; } }
.grid { display:grid; gap:40px; }
@media (max-width:760px){ .card{padding:34px 28px 40px;} h1{font-size:2rem;} }
</style>
</head>
<body>
<div class="card fade-in">
<h1>
<svg viewBox="0 0 24 24" fill="none" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4"/><path d="M12 17h.01"/><path d="M12 2 2 7l10 5 10-5-10-5Z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
Connection Not Secure <span class="badge">http</span>
</h1>
<p class="lede">Youre about to visit a page using <strong>HTTP (unencrypted)</strong>. Information you send or view can potentially be intercepted or modified. If this is a site you trust and you understand the risks, you can continue anyway.</p>
<div class="url-box" id="targetBox" title="Target URL"></div>
<ul>
<li>No TLS encryption data (including passwords or forms) travels in plain text.</li>
<li>Attackers on the same network (café WiFi, school, workplace) could tamper with or read content.</li>
<li>The site might support HTTPS. Try manually changing to <code>https://</code> first.</li>
<li>Proceed only if necessary and you have a reason to trust this destination.</li>
</ul>
<div class="actions">
<button id="backBtn" class="outline" aria-label="Go Back">Go Back</button>
<button id="tryHttps" class="primary" aria-label="Retry with HTTPS">Try HTTPS</button>
<button id="continueBtn" class="danger" aria-label="Continue (HTTP)">Continue Anyway</button>
</div>
<div class="mini">Nebula Secure Navigation Interstitial</div>
</div>
<script>
(function(){
const params = new URLSearchParams(location.search);
const target = params.get('target');
const box = document.getElementById('targetBox');
if (target) box.textContent = target;
function sendNavigate(url, opts){
if (window.electronAPI && window.electronAPI.sendToHost){
window.electronAPI.sendToHost('navigate', url, opts||{});
} else if (window.parent && window.parent !== window) {
window.parent.postMessage({ type:'navigate', url, opts }, '*');
} else if (url === 'nebula://home') {
window.location.href = 'home.html';
} else {
window.location.href = url;
}
}
document.getElementById('backBtn').onclick = () => history.length > 1 ? history.back() : sendNavigate('nebula://home');
document.getElementById('tryHttps').onclick = () => {
if (!target) return; try {
const u = new URL(target.replace(/^http:/,'https:'));
sendNavigate(u.href);
} catch { sendNavigate(target.replace(/^http:/,'https:')); }
};
document.getElementById('continueBtn').onclick = () => {
if (!target) return; sendNavigate(target, { insecureBypass:true });
};
})();
</script>
</body>
</html>
+23
View File
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Menu</title>
<link rel="stylesheet" href="../css/menu-popup.css" />
</head>
<body>
<div id="menu-popup" role="menu">
<button data-cmd="open-settings" role="menuitem">Settings</button>
<button data-cmd="big-picture" role="menuitem">🎮 Big Picture Mode</button>
<button data-cmd="toggle-devtools" role="menuitem">Toggle Developer Tools</button>
<div class="zoom-controls" role="group" aria-label="Zoom controls">
<button data-cmd="zoom-out" aria-label="Zoom out">-</button>
<span id="zoom-percent">100%</span>
<button data-cmd="zoom-in" aria-label="Zoom in">+</button>
</div>
<button data-cmd="hard-reload" role="menuitem">Hard Reload (Ignore Cache)</button>
<button data-cmd="fresh-reload" role="menuitem">Reload Fresh (Add Cache-Buster)</button>
</div>
<script src="../js/menu-popup.js"></script>
</body>
</html>
+59
View File
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Nebot</title>
<link rel="stylesheet" href="../plugins/nebot/page.css" onerror="this.remove()"/>
<style>
body { margin:0; font-family: system-ui,-apple-system,Segoe UI,Roboto,sans-serif; background:#12141c; color:#e6e8ef; }
.fallback { max-width:620px; margin:60px auto; padding:32px 36px; background:#1d222e; border:1px solid rgba(255,255,255,0.08); border-radius:18px; }
.fallback h1 { margin:0 0 12px; font-size:28px; background:linear-gradient(90deg,#a48bff,#6cb6ff); -webkit-background-clip:text; color:transparent; }
.fallback p { line-height:1.55; }
.tip { background:#232b38; padding:10px 14px; border-radius:10px; font-size:13px; margin-top:18px; border:1px solid rgba(255,255,255,0.08); }
.err { color:#ff6d7d; font-weight:600; }
#mount { min-height:400px; }
</style>
</head>
<body>
<div id="mount"></div>
<script>
(async function(){
// Wait a tick so plugin preload (renderer-preload) runs and exposes window.ollamaChat
const mount = document.getElementById('mount');
function showFallback(reason){
mount.innerHTML = `<div class="fallback">`+
`<h1>Nebot</h1>`+
`<p>The Nebot plugin page could not load automatically.</p>`+
(reason?`<p class='err'>${reason}</p>`:'')+
`<div class="tip"><strong>How to fix</strong><br/>1. Ensure the Nebot plugin folder exists at plugins/nebot.<br/>2. Confirm plugin is enabled (manifest enabled: true).<br/>3. Restart the app so the plugin manager registers pages.</div>`+
`</div>`;
}
try {
// Try to fetch plugin page HTML directly
const res = await fetch('../plugins/nebot/page.html');
if(!res.ok){ showFallback('Missing page.html (status '+res.status+').'); return; }
const html = await res.text();
// Simple sandboxed injection
mount.innerHTML = html;
// The injected page expects its CSS & JS relative to itself; adjust asset paths
const fixLinks = mount.querySelectorAll('link[rel="stylesheet"], script[src]');
fixLinks.forEach(el=>{
const attr = el.tagName==='SCRIPT'?'src':'href';
if(el.getAttribute(attr) && !/plugins\/nebot\//.test(el.getAttribute(attr))){
el.setAttribute(attr,'../plugins/nebot/'+el.getAttribute(attr));
}
});
// Inject JS if not already present
if(!mount.querySelector('script[data-nebot-page]')){
const s=document.createElement('script'); s.dataset.nebotPage='1';
// Pass the current URL hash to the page script for debug mode
s.src='../plugins/nebot/page.js' + window.location.hash;
mount.appendChild(s);
}
} catch(e){
showFallback(e.message||'Unknown error');
}
})();
</script>
</body>
</html>
+596
View File
@@ -0,0 +1,596 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Settings</title>
<link rel="stylesheet" href="../css/settings.css" />
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚙️</text></svg>">
<!-- Inline styles removed; styles now live in settings.css for a cleaner, modern look. -->
</head>
<body>
<div class="container" role="application">
<aside class="sidebar" aria-label="Settings categories">
<h1>Settings</h1>
<nav class="tabs" role="tablist">
<button class="tab-link active" role="tab" aria-selected="true" aria-controls="panel-general" id="tab-general" data-tab="general">General</button>
<button class="tab-link" role="tab" aria-selected="false" aria-controls="panel-appearance" id="tab-appearance" data-tab="appearance">Appearance</button>
<button class="tab-link" role="tab" aria-selected="false" aria-controls="panel-history" id="tab-history" data-tab="history">History</button>
<button class="tab-link" role="tab" aria-selected="false" aria-controls="panel-plugins" id="tab-plugins" data-tab="plugins">Plugins</button>
<button class="tab-link" role="tab" aria-selected="false" aria-controls="panel-about" id="tab-about" data-tab="about">About</button>
</nav>
</aside>
<main class="content">
<!-- General Panel -->
<section class="tab-panel active" id="panel-general" role="tabpanel" aria-labelledby="tab-general">
<h2>General</h2>
<div class="setting-group">
<h3>Data Management</h3>
<p class="note">Clear all cookies, cache, and browsing data stored locally on this device.</p>
<div class="setting-row">
<button id="clear-data-btn">Clear All Data</button>
</div>
</div>
<!-- Big Picture Mode -->
<div class="setting-group">
<h3>Big Picture Mode</h3>
<p class="note">A controller-friendly UI designed for handheld devices (e.g., Steam Deck).</p>
<div class="setting-row">
<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 Display</h3>
<p class="note">Choose how temperature is displayed on the Home page weather card.</p>
<fieldset class="weather-units settings-fieldset">
<legend>Temperature units</legend>
<label style="display: block; margin-bottom: 8px;"><input type="radio" name="weather-unit" id="weather-unit-auto" value="auto" checked> Auto (based on locale)</label>
<label style="display: block; margin-bottom: 8px;"><input type="radio" name="weather-unit" id="weather-unit-c" value="c"> Celsius (°C)</label>
<label style="display: block; margin-bottom: 8px;"><input type="radio" name="weather-unit" id="weather-unit-f" value="f"> Fahrenheit (°F)</label>
</fieldset>
</div>
<div class="setting-group">
<h3>System Information</h3>
<div class="debug-info" id="debug-info">Loading debug info...</div>
</div>
</section>
<!-- Appearance Panel -->
<section class="tab-panel" id="panel-appearance" role="tabpanel" aria-labelledby="tab-appearance">
<h2>Appearance</h2>
<!-- Theme Selection -->
<div class="customization-group">
<h3>Theme Presets</h3>
<div class="theme-selector">
<button id="theme-default" class="theme-btn" data-theme="default">
<div class="theme-preview" style="background: linear-gradient(145deg, #121418, #1B1035);"></div>
<span>Default</span>
</button>
<button id="theme-ocean" class="theme-btn" data-theme="ocean">
<div class="theme-preview" style="background: linear-gradient(145deg, #1a365d, #2c5282);"></div>
<span>Ocean</span>
</button>
<button id="theme-forest" class="theme-btn" data-theme="forest">
<div class="theme-preview" style="background: linear-gradient(145deg, #1a202c, #2d3748);"></div>
<span>Forest</span>
</button>
<button id="theme-sunset" class="theme-btn" data-theme="sunset">
<div class="theme-preview" style="background: linear-gradient(145deg, #744210, #c05621);"></div>
<span>Sunset</span>
</button>
<button id="theme-cyberpunk" class="theme-btn" data-theme="cyberpunk">
<div class="theme-preview" style="background: linear-gradient(145deg, #0a0a0a, #2a0a3a, #1a0520);"></div>
<span>Cyberpunk</span>
</button>
<button id="theme-midnight-rose" class="theme-btn" data-theme="midnight-rose">
<div class="theme-preview" style="background: linear-gradient(145deg, #1c1820, #3d3046);"></div>
<span>Midnight Rose</span>
</button>
<button id="theme-arctic-ice" class="theme-btn" data-theme="arctic-ice">
<div class="theme-preview" style="background: linear-gradient(145deg, #f0f8ff, #d1e7ff);"></div>
<span>Arctic Ice</span>
</button>
<button id="theme-cherry-blossom" class="theme-btn" data-theme="cherry-blossom">
<div class="theme-preview" style="background: linear-gradient(145deg, #fff5f8, #ffd4db);"></div>
<span>Cherry Blossom</span>
</button>
<button id="theme-cosmic-purple" class="theme-btn" data-theme="cosmic-purple">
<div class="theme-preview" style="background: linear-gradient(145deg, #0f0524, #2d1b69, #4b0082);"></div>
<span>Cosmic Purple</span>
</button>
<button id="theme-emerald-dream" class="theme-btn" data-theme="emerald-dream">
<div class="theme-preview" style="background: linear-gradient(145deg, #0d2818, #2d5a44);"></div>
<span>Emerald Dream</span>
</button>
<button id="theme-mocha-coffee" class="theme-btn" data-theme="mocha-coffee">
<div class="theme-preview" style="background: linear-gradient(145deg, #3c2414, #5d3a26);"></div>
<span>Mocha Coffee</span>
</button>
<button id="theme-lavender-fields" class="theme-btn" data-theme="lavender-fields">
<div class="theme-preview" style="background: linear-gradient(145deg, #f8f4ff, #e6d8ff);"></div>
<span>Lavender Fields</span>
</button>
<button id="theme-custom" class="theme-btn custom-theme-btn" data-theme="custom" style="display: none;">
<div class="theme-preview" style="background: linear-gradient(145deg, var(--bg), var(--gradient-color));"></div>
<span>Custom</span>
</button>
</div>
</div>
<!-- Display Scale -->
<div class="customization-group">
<h3>Display Scale</h3>
<p class="note">Adjust the zoom level for this window. Changes apply immediately.</p>
<div class="zoom-controls">
<button class="zoom-btn" id="zoom-decrease" title="Decrease zoom"></button>
<span id="display-scale-value" class="zoom-value">100%</span>
<button class="zoom-btn" id="zoom-increase" title="Increase zoom">+</button>
</div>
<div class="zoom-presets">
<button class="zoom-preset-btn" data-zoom="60">60%</button>
<button class="zoom-preset-btn" data-zoom="70">70%</button>
<button class="zoom-preset-btn" data-zoom="80">80%</button>
<button class="zoom-preset-btn" data-zoom="90">90%</button>
<button class="zoom-preset-btn" data-zoom="100">100%</button>
<button class="zoom-preset-btn" data-zoom="110">110%</button>
<button class="zoom-preset-btn" data-zoom="120">120%</button>
<button class="zoom-preset-btn" data-zoom="130">130%</button>
</div>
</div>
<!-- Color Customization -->
<div class="customization-group">
<h3>Custom Colors</h3>
<div class="color-controls">
<div class="color-group">
<label for="bg-color">Background:</label>
<input type="color" id="bg-color" value="#121418">
</div>
<div class="color-group">
<label for="gradient-color">Gradient End:</label>
<input type="color" id="gradient-color" value="#1B1035">
</div>
<div class="color-group">
<label for="accent-color">Accent:</label>
<input type="color" id="accent-color" value="#7B2EFF">
</div>
<div class="color-group">
<label for="secondary-color">Secondary:</label>
<input type="color" id="secondary-color" value="#00C6FF">
</div>
<div class="color-group">
<label for="text-color">Text:</label>
<input type="color" id="text-color" value="#E0E0E0">
</div>
</div>
</div>
<!-- Logo Customization -->
<div class="customization-group">
<h3>Logo & Branding</h3>
<div class="logo-options">
<label for="show-logo">
<input type="checkbox" id="show-logo" checked>
Show Nebula Logo
</label>
<label for="custom-title">
Custom Title:
<input type="text" id="custom-title" placeholder="Nebula Browser" maxlength="50">
</label>
</div>
</div>
<!-- Theme Management -->
<div class="customization-group">
<h3>Theme Management</h3>
<div class="theme-management">
<button id="save-custom-theme">Save Current as Custom Theme</button>
<button id="export-theme">Export Theme</button>
<button id="import-theme">Import Theme</button>
<input type="file" id="theme-file-input" accept=".json" style="display: none;">
<button id="reset-to-default">Reset to Default</button>
</div>
</div>
<!-- Live Preview -->
<div class="customization-group">
<h3>Preview</h3>
<div class="preview-container" id="preview-container">
<div class="preview-home">
<div class="preview-logo" id="preview-logo">../assets/images/branding/Nebula-Logo.svg</div>
<div class="preview-text">Nebula</div>
<div class="preview-search"></div>
<div class="preview-bookmarks">
<div class="preview-bookmark"></div>
<div class="preview-bookmark"></div>
<div class="preview-bookmark"></div>
</div>
</div>
</div>
</div>
</section>
<!-- History Panel -->
<section class="tab-panel" id="panel-history" role="tabpanel" aria-labelledby="tab-history">
<h2>History</h2>
<div class="customization-group">
<h3>Search History</h3>
<ul id="search-history-list"></ul>
<button id="clear-search-history-btn" style="margin-top: 10px;">Clear Search History</button>
</div>
<div class="customization-group">
<h3>Site History</h3>
<ul id="site-history-list"></ul>
<div class="button-row" style="margin-top:10px;">
<button id="clear-site-history-btn">Clear Site History</button>
</div>
</div>
</section>
<!-- Plugins Panel -->
<section class="tab-panel" id="panel-plugins" role="tabpanel" aria-labelledby="tab-plugins">
<h2>Plugins</h2>
<div class="customization-group">
<div class="button-row">
<button id="plugins-reload-all">Reload Plugins</button>
<span class="note">Changes to renderer preloads may require app restart.</span>
</div>
</div>
<div class="customization-group">
<h3>Installed</h3>
<div id="plugins-list" class="plugins-list" role="list"></div>
</div>
</section>
<!-- About Panel -->
<section class="tab-panel" id="panel-about" role="tabpanel" aria-labelledby="tab-about">
<h2>About</h2>
<div class="customization-group">
<h3>Application</h3>
<ul id="about-app">
<li><strong>Name:</strong> <span id="about-app-name">Loading...</span></li>
<li><strong>Version:</strong> <span id="about-app-version">Loading...</span></li>
<li><strong>Packaged:</strong> <span id="about-packaged">Loading...</span></li>
<li><strong>User data:</strong> <span id="about-userdata" style="word-break: break-all;">Loading...</span></li>
</ul>
</div>
<div class="customization-group">
<h3>Runtime</h3>
<ul id="about-runtime">
<li><strong>CEF:</strong> <span id="about-cef">Chromium Embedded Framework</span></li>
<li><strong>Chromium:</strong> <span id="about-chrome">Loading...</span></li>
<li><strong>Node.js:</strong> <span id="about-node">Loading...</span></li>
<li><strong>V8:</strong> <span id="about-v8">Loading...</span></li>
</ul>
</div>
<div class="customization-group">
<h3>System</h3>
<ul id="about-system">
<li><strong>OS:</strong> <span id="about-os">Loading...</span></li>
<li><strong>CPU:</strong> <span id="about-cpu">Loading...</span></li>
<li><strong>Architecture:</strong> <span id="about-arch">Loading...</span></li>
<li><strong>Memory:</strong> <span id="about-mem">Loading...</span></li>
</ul>
</div>
<div class="customization-group">
<h3>Runtime Updates</h3>
<p class="note">Nebula Browser now runs on CEF. Runtime updates are handled by the native application build.</p>
<p><strong>Nebula Browser:</strong> <span id="about-app-version-copy">Loading...</span></p>
</div>
<div class="customization-group about-actions">
<button id="copy-about-btn">Copy diagnostics</button>
<a id="github-link" href="https://github.com/Bobbybear007/NebulaBrowser" class="github-btn" rel="noopener noreferrer">
<!-- GitHub mark (Octicons) MIT License -->
<svg viewBox="0 0 16 16" aria-hidden="true" focusable="false" role="img">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.01.08-2.11 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.91.08 2.11.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
</svg>
<span>GitHub</span>
</a>
<a id="help-link" href="https://nebula.zambazosmedia.group" class="help-btn" rel="noopener noreferrer">
<!-- Help icon -->
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" role="img">
<path d="M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm0 15a1.25 1.25 0 1 1 0 2.5A1.25 1.25 0 0 1 12 17zm-.02-1.9c-.6 0-1.02-.42-1.02-.98 0-1.97 2.76-1.95 2.76-3.62 0-.77-.66-1.4-1.72-1.4-1 0-1.67.5-2.06 1.22-.3.54-.95.73-1.45.44-.54-.31-.72-1-.42-1.53C7.7 7.7 9.06 6.5 12 6.5c2.26 0 3.98 1.3 3.98 3.35 0 2.74-2.96 2.77-3 3.83-.03.6-.43 1.02-1 1.02z"/>
</svg>
<span>Help</span>
</a>
</div>
</section>
</main>
</div>
<!-- status overlay moved outside of .container -->
<div id="status" class="status hidden">
<div class="spinner"></div>
<span id="status-text"></span>
</div>
<script src="../js/settings.js"></script>
<script src="../js/customization.js"></script>
<script>
// Apply saved theme immediately when page loads
document.addEventListener('DOMContentLoaded', () => {
BrowserCustomizer.applyThemeToPage();
});
// Update debug info
function updateDebugInfo() {
const debugDiv = document.getElementById('debug-info');
const localStorage = window.localStorage;
const hasNativeBridge = !!window.electronAPI;
const siteHistory = localStorage ? localStorage.getItem('siteHistory') : 'N/A';
debugDiv.innerHTML = `
localStorage available: ${!!localStorage}<br>
native bridge available: ${hasNativeBridge}<br>
siteHistory in localStorage: ${siteHistory || 'null'}<br>
Current domain: ${window.location.hostname}<br>
Current protocol: ${window.location.protocol}
`;
}
// Get site history from localStorage in this webview context
function getSiteHistoryFromLocalStorage() {
try {
const history = localStorage.getItem('siteHistory');
console.log('[SETTINGS DEBUG] localStorage siteHistory:', history);
return history ? JSON.parse(history) : [];
} catch (err) {
console.error('[SETTINGS DEBUG] Error reading from localStorage:', err);
return [];
}
}
// Get search history from localStorage in this webview context
function getSearchHistoryFromLocalStorage() {
try {
const history = localStorage.getItem('searchHistory');
console.log('[SETTINGS DEBUG] localStorage searchHistory:', history);
return history ? JSON.parse(history) : [];
} catch (err) {
console.error('[SETTINGS DEBUG] Error reading search history from localStorage:', err);
return [];
}
}
// Sync site history from main browser's localStorage to this webview's localStorage
function syncSiteHistoryFromMain() {
try {
// Try to get the parent window's localStorage
if (window.parent && window.parent !== window) {
const parentHistory = window.parent.localStorage.getItem('siteHistory');
if (parentHistory) {
localStorage.setItem('siteHistory', parentHistory);
console.log('[SETTINGS DEBUG] Synced history from parent window');
return JSON.parse(parentHistory);
}
}
return [];
} catch (err) {
console.log('[SETTINGS DEBUG] Could not sync from parent:', err.message);
return [];
}
}
// Sync search history from main browser's localStorage to this webview's localStorage
function syncSearchHistoryFromMain() {
try {
// Try to get the parent window's localStorage
if (window.parent && window.parent !== window) {
const parentHistory = window.parent.localStorage.getItem('searchHistory');
if (parentHistory) {
localStorage.setItem('searchHistory', parentHistory);
console.log('[SETTINGS DEBUG] Synced search history from parent window');
return JSON.parse(parentHistory);
}
}
return [];
} catch (err) {
console.log('[SETTINGS DEBUG] Could not sync search history from parent:', err.message);
return [];
}
}
function addTestHistory() {
const testSites = [
'https://www.google.com',
'https://github.com',
'https://stackoverflow.com',
'https://developer.mozilla.org',
'https://www.wikipedia.org'
];
testSites.forEach(site => {
try {
let history = getSiteHistoryFromLocalStorage();
history = history.filter(item => item !== site);
history.unshift(site);
localStorage.setItem('siteHistory', JSON.stringify(history));
console.log('[SETTINGS DEBUG] Added test site:', site);
} catch (err) {
console.error('Error adding test history:', err);
}
});
loadHistories();
}
async function loadHistories() {
try {
console.log('[SETTINGS DEBUG] Loading histories...');
updateDebugInfo();
// First try to sync from parent window
let siteHistory = syncSiteHistoryFromMain();
// If that didn't work, get from local storage
if (siteHistory.length === 0) {
siteHistory = getSiteHistoryFromLocalStorage();
}
// Try to get search history from parent or localStorage
let searchHistory = syncSearchHistoryFromMain();
if (searchHistory.length === 0) {
searchHistory = getSearchHistoryFromLocalStorage();
}
const searchList = document.getElementById('search-history-list');
const siteList = document.getElementById('site-history-list');
// Clear existing content
searchList.innerHTML = '';
siteList.innerHTML = '';
// Populate search history
if (searchHistory && searchHistory.length > 0) {
console.log('[SETTINGS DEBUG] Displaying', searchHistory.length, 'search history items');
searchHistory.forEach(query => {
const li = document.createElement('li');
li.style.wordBreak = 'break-all';
li.textContent = query;
li.style.color = 'var(--text)';
searchList.appendChild(li);
});
} else {
const searchLi = document.createElement('li');
searchLi.textContent = 'No search history yet';
searchLi.style.fontStyle = 'italic';
searchLi.style.color = '#666';
searchList.appendChild(searchLi);
}
// Populate site history
if (siteHistory && siteHistory.length > 0) {
console.log('[SETTINGS DEBUG] Displaying', siteHistory.length, 'site history items');
siteHistory.forEach(item => {
const li = document.createElement('li');
li.style.wordBreak = 'break-all';
// Create a clickable link that asks host to navigate in a new tab
const a = document.createElement('a');
a.href = item;
a.textContent = item;
a.target = '_blank';
a.rel = 'noopener noreferrer';
a.style.color = 'var(--accent)';
a.style.textDecoration = 'none';
a.addEventListener('click', (e) => {
e.preventDefault();
try {
if (window.electronAPI && typeof window.electronAPI.sendToHost === 'function') {
// Ask the host to open this URL in a new tab to keep Settings open
window.electronAPI.sendToHost('navigate', item, { newTab: true });
} else if (window.parent && window.parent !== window) {
// Fallback: postMessage to parent if available
window.parent.postMessage({ type: 'navigate', url: item }, '*');
} else {
// Last resort: open in this webview
window.location.href = item;
}
} catch (err) {
console.error('[SETTINGS DEBUG] Failed to trigger navigation for', item, err);
}
});
li.appendChild(a);
siteList.appendChild(li);
});
} else {
console.log('[SETTINGS DEBUG] No site history to display');
const li = document.createElement('li');
li.textContent = 'No browsing history found. Browse some websites first!';
li.style.fontStyle = 'italic';
li.style.color = '#666';
siteList.appendChild(li);
}
} catch(err) {
console.error('[SETTINGS DEBUG] Error loading histories:', err);
const searchList = document.getElementById('search-history-list');
const siteList = document.getElementById('site-history-list');
const errorLi1 = document.createElement('li');
errorLi1.textContent = 'Error loading search history: ' + err.message;
errorLi1.style.color = 'red';
searchList.appendChild(errorLi1);
const errorLi2 = document.createElement('li');
errorLi2.textContent = 'Error loading site history: ' + err.message;
errorLi2.style.color = 'red';
siteList.appendChild(errorLi2);
}
}
async function clearSiteHistory() {
try {
// Clear from localStorage
localStorage.removeItem('siteHistory');
console.log('[SETTINGS DEBUG] Cleared site history from localStorage');
loadHistories(); // Refresh the display
} catch (err) {
console.error('Error clearing site history:', err);
}
}
async function clearSearchHistory() {
try {
// Clear from localStorage
localStorage.removeItem('searchHistory');
console.log('[SETTINGS DEBUG] Cleared search history from localStorage');
// Also clear in parent window if accessible
if (window.parent && window.parent !== window) {
try {
window.parent.localStorage.removeItem('searchHistory');
console.log('[SETTINGS DEBUG] Cleared search history from parent');
} catch (e) {
console.log('[SETTINGS DEBUG] Could not clear parent search history:', e.message);
}
}
loadHistories(); // Refresh the display
} catch (err) {
console.error('Error clearing search history:', err);
}
}
// Load histories on page load
window.addEventListener('DOMContentLoaded', () => {
console.log('[SETTINGS DEBUG] DOM loaded, window.electronAPI:', !!window.electronAPI);
loadHistories();
// Refresh history every few seconds to pick up new browsing
setInterval(loadHistories, 3000);
// Add test history button functionality
const addTestBtn = document.getElementById('add-test-history-btn');
if (addTestBtn) {
addTestBtn.addEventListener('click', addTestHistory);
}
// Add clear buttons functionality
const clearSiteBtn = document.getElementById('clear-site-history-btn');
const clearSearchBtn = document.getElementById('clear-search-history-btn');
if (clearSiteBtn) {
clearSiteBtn.addEventListener('click', clearSiteHistory);
}
if (clearSearchBtn) {
clearSearchBtn.addEventListener('click', clearSearchHistory);
}
});
</script>
</body>
</html>
+134
View File
@@ -0,0 +1,134 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome to Nebula</title>
<link rel="stylesheet" href="../css/setup.css">
</head>
<body>
<div class="setup-container">
<!-- Progress indicator -->
<div class="progress-bar">
<div class="progress-step active" data-step="1">
<div class="step-circle">1</div>
<div class="step-label">Welcome</div>
</div>
<div class="progress-line"></div>
<div class="progress-step" data-step="2">
<div class="step-circle">2</div>
<div class="step-label">Theme</div>
</div>
<div class="progress-line"></div>
<div class="progress-step" data-step="3">
<div class="step-circle">3</div>
<div class="step-label">Default Browser</div>
</div>
<div class="progress-line"></div>
<div class="progress-step" data-step="4">
<div class="step-circle">4</div>
<div class="step-label">Complete</div>
</div>
</div>
<!-- Step 1: Welcome -->
<div class="setup-step active" data-step="1">
<div class="step-content">
<h1 class="setup-title">Welcome to Nebula</h1>
<p class="setup-subtitle">Let's personalize your browsing experience</p>
<div class="feature-grid">
<div class="feature-item">
<div class="feature-icon">🎨</div>
<h3>Beautiful Themes</h3>
<p>Choose from stunning themes or create your own</p>
</div>
<div class="feature-item">
<div class="feature-icon">🚀</div>
<h3>Lightning Fast</h3>
<p>Built for speed and performance</p>
</div>
<div class="feature-item">
<div class="feature-icon">🎮</div>
<h3>Steam Deck Ready</h3>
<p>Optimized for gaming handhelds</p>
</div>
<div class="feature-item">
<div class="feature-icon">🔒</div>
<h3>Privacy First</h3>
<p>Your data stays yours</p>
</div>
</div>
</div>
<div class="step-actions">
<button class="btn btn-primary" id="btn-start">Get Started</button>
<button class="btn btn-secondary" id="btn-skip-all">Skip Setup</button>
</div>
</div>
<!-- Step 2: Theme Selection -->
<div class="setup-step" data-step="2">
<div class="step-content">
<h1 class="setup-title">Choose Your Theme</h1>
<p class="setup-subtitle">Pick a color scheme that suits your style</p>
<div class="theme-grid" id="theme-grid">
<!-- Themes will be dynamically loaded here -->
</div>
</div>
<div class="step-actions">
<button class="btn btn-secondary" id="btn-back-2">Back</button>
<button class="btn btn-primary" id="btn-next-2">Continue</button>
</div>
</div>
<!-- Step 3: Default Browser -->
<div class="setup-step" data-step="3">
<div class="step-content">
<h1 class="setup-title">Set as Default Browser</h1>
<p class="setup-subtitle">Make Nebula your go-to browser for all links</p>
<div class="default-browser-section">
<div class="default-browser-card">
<div class="browser-icon">🌐</div>
<h3>Quick Access</h3>
<p>Open all web links automatically with Nebula</p>
</div>
<div class="default-browser-status" id="default-status">
<div class="status-icon"></div>
<p class="status-text">Checking default browser status...</p>
</div>
<div class="default-browser-actions">
<button class="btn btn-large btn-primary" id="btn-set-default">
<span class="btn-icon"></span>
Set as Default Browser
</button>
<p class="help-text">You can always change this later in settings</p>
</div>
</div>
</div>
<div class="step-actions">
<button class="btn btn-secondary" id="btn-back-3">Back</button>
<button class="btn btn-primary" id="btn-skip-3">Skip for Now</button>
</div>
</div>
<!-- Step 4: Complete -->
<div class="setup-step" data-step="4">
<div class="step-content">
<div class="success-icon"></div>
<h1 class="setup-title">All Set!</h1>
<p class="setup-subtitle">You're ready to explore the web with Nebula</p>
<div class="completion-summary" id="completion-summary">
<!-- Summary will be populated dynamically -->
</div>
</div>
<div class="step-actions">
<button class="btn btn-primary btn-large" id="btn-finish">Start Browsing</button>
</div>
</div>
</div>
<script src="../js/setup.js"></script>
</body>
</html>