File Explorer
Settings
Ginger Browser
Notes
About GingerOS
or inline after the HTML. */ (() => { /* ========================= HELPERS ========================== */ const qs = (s, p = document) => p.querySelector(s); const qsa = (s, p = document) => [...p.querySelectorAll(s)]; const clamp = (n, a, b) => Math.max(a, Math.min(b, n)); const STORAGE_KEYS = { NOTES: "ginger_notes", WIFI_PROFILES: "ginger_wifi_profiles", WIFI_ENABLED: "ginger_wifi_enabled", WIFI_LAST: "ginger_wifi_last", BROWSER_STATE: "ginger_browser_state" }; /* ========================= STATE ========================== */ const state = { z: 100, windows: {}, // { id: { el, minimized, maximized, prevRect } } activeWinId: null, notes: { content: localStorage.getItem(STORAGE_KEYS.NOTES) || "" }, wifi: { enabled: (localStorage.getItem(STORAGE_KEYS.WIFI_ENABLED) || "true") === "true", selected: null, connected: null, connecting: false, autoConnect: false, networks: [], profiles: safeParseJSON(localStorage.getItem(STORAGE_KEYS.WIFI_PROFILES), {}) // {ssid:{password,auto}} }, browser: { history: [], index: -1, current: null } }; function safeParseJSON(raw, fallback) { try { return raw ? JSON.parse(raw) : fallback; } catch { return fallback; } } function saveWifiProfiles() { localStorage.setItem(STORAGE_KEYS.WIFI_PROFILES, JSON.stringify(state.wifi.profiles)); } function saveWifiEnabled() { localStorage.setItem(STORAGE_KEYS.WIFI_ENABLED, String(state.wifi.enabled)); } /* ========================= ROOT ELEMENTS ========================== */ const overlay = qs("#overlay"); const startMenu = qs("#start-menu"); const actionCenter = qs("#action-center"); const wifiFlyout = qs("#wifi-flyout"); const desktop = qs("#desktop"); const btnStart = qs("#btn-start"); const trayWifiBtn = qs('[data-tray="wifi"]'); const trayActionBtn = qs('[data-tray="action"]'); /* ========================= INIT ========================== */ document.addEventListener("DOMContentLoaded", init); function init() { registerWindows(); bindOpeners(); bindStartMenu(); bindActionCenter(); bindWifi(); bindDesktopContextMenu(); bindGlobalShortcuts(); initClock(); initNotes(); initBrowser(); fakeScanNetworks(); restoreWifiLastConnection(); updateWifiUI(); } /* ========================= WINDOWS: open/close/min/max, focus, drag ========================== */ function registerWindows() { qsa(".win").forEach(win => { const id = win.dataset.app; state.windows[id] = { el: win, minimized: false, maximized: false, prevRect: null }; // baseline z-index win.style.zIndex = ++state.z; // Focus on click win.addEventListener("mousedown", () => activateWindow(id)); // Controls const closeBtn = qs("[data-win-close]", win); const minBtn = qs("[data-win-min]", win); const maxBtn = qs("[data-win-max]", win); closeBtn?.addEventListener("click", () => closeWindow(id)); minBtn?.addEventListener("click", () => minimizeWindow(id)); maxBtn?.addEventListener("click", () => toggleMaximize(id)); makeDraggable(win, id); }); } function openWindow(id) { const w = state.windows[id]; if (!w) return; w.el.classList.remove("hidden"); w.minimized = false; activateWindow(id); } function closeWindow(id) { const w = state.windows[id]; if (!w) return; w.el.classList.add("hidden"); w.minimized = false; if (state.activeWinId === id) state.activeWinId = null; } function minimizeWindow(id) { const w = state.windows[id]; if (!w) return; w.el.classList.add("hidden"); w.minimized = true; if (state.activeWinId === id) state.activeWinId = null; } function toggleMaximize(id) { const w = state.windows[id]; if (!w) return; w.maximized = !w.maximized; if (w.maximized) { // βœ… FIX: store actual computed position/size reliably w.prevRect = { top: w.el.offsetTop + "px", left: w.el.offsetLeft + "px", width: w.el.offsetWidth + "px", height: w.el.offsetHeight + "px" }; w.el.style.top = "0px"; w.el.style.left = "0px"; w.el.style.width = "100%"; w.el.style.height = "calc(100% - 44px)"; } else if (w.prevRect) { Object.assign(w.el.style, w.prevRect); } } function activateWindow(id) { const w = state.windows[id]; if (!w) return; qsa(".win").forEach(x => x.classList.remove("active")); w.el.classList.add("active"); w.el.style.zIndex = ++state.z; state.activeWinId = id; } function makeDraggable(win, id) { const bar = qs("[data-drag-handle]", win); if (!bar) return; let dragging = false; let offsetX = 0; let offsetY = 0; bar.addEventListener("mousedown", e => { const w = state.windows[id]; if (!w || w.maximized) return; dragging = true; activateWindow(id); offsetX = e.clientX - win.offsetLeft; offsetY = e.clientY - win.offsetTop; }); document.addEventListener("mousemove", e => { if (!dragging) return; const x = e.clientX - offsetX; const y = e.clientY - offsetY; const maxX = window.innerWidth - 120; const maxY = window.innerHeight - 120 - 44; // taskbar win.style.left = clamp(x, -80, maxX) + "px"; win.style.top = clamp(y, 0, maxY) + "px"; }); document.addEventListener("mouseup", () => (dragging = false)); // Double-click titlebar to maximize/restore bar.addEventListener("dblclick", () => toggleMaximize(id)); } /* ========================= OPENERS (desktop icons, pins, start items) ========================== */ function bindOpeners() { // Anything with data-open opens window and closes overlays qsa("[data-open]").forEach(btn => { btn.addEventListener("click", () => { openWindow(btn.dataset.open); closeAllMenus(); }); btn.addEventListener("keydown", e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); btn.click(); } }); }); // βœ… FIX: proper double-click for desktop icons (no recursion) qsa(".desk-icon").forEach(icon => { icon.addEventListener("dblclick", () => { const id = icon.dataset.open; if (id) { openWindow(id); closeAllMenus(); } }); }); } /* ========================= MENUS / OVERLAY ========================== */ function toggleMenu(el) { const isOpen = el.classList.contains("show"); closeAllMenus(); if (!isOpen) { el.classList.add("show"); overlay.classList.add("show"); updateAriaExpanded(); } } function closeAllMenus() { [startMenu, actionCenter, wifiFlyout].forEach(m => m.classList.remove("show")); overlay.classList.remove("show"); updateAriaExpanded(); } function updateAriaExpanded() { btnStart?.setAttribute("aria-expanded", startMenu.classList.contains("show") ? "true" : "false"); trayWifiBtn?.setAttribute("aria-expanded", wifiFlyout.classList.contains("show") ? "true" : "false"); trayActionBtn?.setAttribute("aria-expanded", actionCenter.classList.contains("show") ? "true" : "false"); } overlay.addEventListener("click", closeAllMenus); /* ========================= START MENU ========================== */ function bindStartMenu() { btnStart?.addEventListener("click", () => toggleMenu(startMenu)); // Power button (placeholder) qsa("[data-power]").forEach(btn => { btn.addEventListener("click", () => { pushNotif("Power", "Sleep / Shut down is simulated in GingerOS."); }); }); } /* ========================= ACTION CENTER ========================== */ function bindActionCenter() { trayActionBtn?.addEventListener("click", () => toggleMenu(actionCenter)); qs("[data-clear-notifs]")?.addEventListener("click", () => { // Keep the welcome notif (first) and template; remove others const body = qs(".ac-body"); if (!body) return; qsa(".notif", body).forEach((n, idx) => { if (n.id === "wifi-notif-template") return; if (idx === 0) return; n.remove(); }); }); // Quick settings toggles qsa("[data-qs]").forEach(btn => { btn.addEventListener("click", () => { const on = btn.classList.toggle("active"); btn.setAttribute("aria-pressed", on ? "true" : "false"); if (btn.dataset.qs === "wifi") { state.wifi.enabled = on; saveWifiEnabled(); if (!state.wifi.enabled) { if (state.wifi.connected) disconnectWifi(true); } updateWifiUI(); pushNotif("Wi-Fi", state.wifi.enabled ? "Wi-Fi turned on." : "Wi-Fi turned off."); } }); }); } function pushNotif(title, body) { const acBody = qs(".ac-body"); if (!acBody) return; const tmpl = qs("#wifi-notif-template"); let n; if (tmpl) { n = tmpl.cloneNode(true); n.hidden = false; n.removeAttribute("id"); qs(".nt", n).textContent = title; const b = qs("[data-wifi-notif-text]", n) || qs(".nb", n); if (b) b.textContent = body; } else { n = document.createElement("div"); n.className = "notif"; n.innerHTML = `
`; qs(".nt", n).textContent = title; qs(".nb", n).textContent = body; } acBody.prepend(n); } /* ========================= WI-FI (simulated) β€” WITH PASSWORD INPUT ========================== */ function bindWifi() { // open flyout qsa("[data-open-wifi-flyout]").forEach(b => b.addEventListener("click", () => toggleMenu(wifiFlyout))); trayWifiBtn?.addEventListener("click", () => toggleMenu(wifiFlyout)); // toggle wifi (buttons exist in both Settings + Flyout) qsa("[data-wifi-toggle]").forEach(btn => { btn.addEventListener("click", () => { state.wifi.enabled = !state.wifi.enabled; saveWifiEnabled(); if (!state.wifi.enabled && state.wifi.connected) disconnectWifi(true); updateWifiUI(); pushNotif("Wi-Fi", state.wifi.enabled ? "Wi-Fi is on." : "Wi-Fi is off."); }); }); // refresh networks qs("[data-wifi-refresh]")?.addEventListener("click", () => { fakeScanNetworks(); pushNotif("Wi-Fi", "Scanning for networks…"); }); // auto-connect checkbox qs("[data-auto-connect]")?.addEventListener("change", e => { state.wifi.autoConnect = !!e.target.checked; const sel = state.wifi.selected; if (sel && state.wifi.profiles[sel]) { state.wifi.profiles[sel].auto = state.wifi.autoConnect; saveWifiProfiles(); } }); // select network qs("#net-list")?.addEventListener("click", e => { const net = e.target.closest(".net"); if (!net) return; selectNetwork(net.dataset.ssid); }); // connect / disconnect buttons qsa("[data-connect]").forEach(b => b.addEventListener("click", () => connectWifi())); qsa("[data-disconnect]").forEach(b => b.addEventListener("click", () => disconnectWifi(false))); // password input should enable/disable connect when secured qs("[data-wifi-pass]")?.addEventListener("input", () => updateWifiUI()); } function selectNetwork(ssid) { state.wifi.selected = ssid; const net = state.wifi.networks.find(n => n.ssid === ssid); const secured = !!net?.secured; qsa("[data-selected-ssid]").forEach(el => (el.textContent = ssid || "β€”")); const passEl = qs("[data-wifi-pass]"); if (passEl) { passEl.value = state.wifi.profiles[ssid]?.password || ""; passEl.placeholder = secured ? "Network password" : "Not required (open network)"; passEl.disabled = !secured; } const auto = !!state.wifi.profiles[ssid]?.auto; state.wifi.autoConnect = auto; const autoEl = qs("[data-auto-connect]"); if (autoEl) autoEl.checked = auto; updateWifiUI(); } function fakeScanNetworks() { const base = [ { ssid: "GingerNet", secured: true, band: "2.4GHz" }, { ssid: "Cafe-WiFi", secured: false, band: "5GHz" }, { ssid: "DevLab_5G", secured: true, band: "5GHz" }, { ssid: "Public_Free", secured: false, band: "2.4GHz" }, { ssid: "Neighbours_5G", secured: true, band: "5GHz" }, { ssid: "Hidden Network", secured: true, band: "β€”" } ]; state.wifi.networks = base.map(n => ({ ...n, signal: clamp(Math.floor(Math.random() * 5) + 1, 1, 5) })); renderNetworks(); if (state.wifi.selected && state.wifi.networks.some(n => n.ssid === state.wifi.selected)) { selectNetwork(state.wifi.selected); } else { state.wifi.selected = null; qsa("[data-selected-ssid]").forEach(el => (el.textContent = "β€”")); updateWifiUI(); } } function renderNetworks() { const list = qs("#net-list"); if (!list) return; list.innerHTML = ""; state.wifi.networks.forEach(n => { const div = document.createElement("div"); div.className = "net"; div.dataset.ssid = n.ssid; div.tabIndex = 0; div.setAttribute("role", "button"); div.innerHTML = `
${escapeHtml(n.ssid)}
${n.secured ? "Secured" : "Open"}
Signal: ${"●".repeat(n.signal)}${"β—‹".repeat(5 - n.signal)} ${escapeHtml(n.band || "")}
`; div.addEventListener("keydown", e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); selectNetwork(n.ssid); } }); list.appendChild(div); }); } function escapeHtml(s) { return String(s) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function connectWifi() { if (!state.wifi.enabled) { pushNotif("Wi-Fi", "Turn Wi-Fi on to connect."); return; } if (!state.wifi.selected) return; if (state.wifi.connecting) return; const net = state.wifi.networks.find(n => n.ssid === state.wifi.selected); if (!net) return; const secured = !!net.secured; const passEl = qs("[data-wifi-pass]"); const password = passEl ? passEl.value : ""; if (secured && (!password || password.trim().length < 8)) { pushNotif("Wi-Fi", "Password required (min 8 characters) for secured networks."); setConnectionBadge("Password required"); updateWifiUI(); return; } state.wifi.connecting = true; setConnectionBadge("Connecting…"); updateWifiUI(); setTimeout(() => { state.wifi.connecting = false; state.wifi.connected = state.wifi.selected; if (secured) { state.wifi.profiles[state.wifi.connected] = { password, auto: !!state.wifi.autoConnect }; saveWifiProfiles(); } localStorage.setItem(STORAGE_KEYS.WIFI_LAST, state.wifi.connected); updateWifiUI(); setConnectionBadge("Connected"); pushNotif("Wi-Fi", `Connected to ${state.wifi.connected}.`); }, 1100); } function disconnectWifi(silent) { if (!state.wifi.connected) return; const name = state.wifi.connected; state.wifi.connected = null; localStorage.setItem(STORAGE_KEYS.WIFI_LAST, ""); updateWifiUI(); setConnectionBadge("Idle"); if (!silent) pushNotif("Wi-Fi", `Disconnected from ${name}.`); } function restoreWifiLastConnection() { const last = (localStorage.getItem(STORAGE_KEYS.WIFI_LAST) || "").trim(); if (!last) return; const profile = state.wifi.profiles[last]; if (state.wifi.enabled && profile && profile.auto) { state.wifi.selected = last; const passEl = qs("[data-wifi-pass]"); if (passEl) passEl.value = profile.password || ""; selectNetwork(last); connectWifi(); } } function setConnectionBadge(text) { qsa("[data-connection-state]").forEach(el => (el.textContent = text)); } function updateWifiUI() { const enabled = state.wifi.enabled; const connected = state.wifi.connected; const selected = state.wifi.selected; const connecting = state.wifi.connecting; qsa("[data-wifi-on]").forEach(el => (el.textContent = enabled ? "On" : "Off")); qsa("[data-wifi-toggle]").forEach(btn => { btn.setAttribute("aria-pressed", enabled ? "true" : "false"); if (btn.textContent.trim().toLowerCase().includes("turn")) { btn.textContent = enabled ? "Turn off" : "Turn on"; } else { btn.textContent = enabled ? "On" : "Off"; } }); const statusText = connected ? `Connected to ${connected}` : "Disconnected"; qsa("[data-wifi-status]").forEach(el => (el.textContent = statusText)); qsa("[data-current-ssid]").forEach(el => (el.textContent = connected || "β€”")); qsa("[data-ip]").forEach(el => (el.textContent = connected ? `192.168.0.${Math.floor(Math.random() * 200 + 20)}` : "β€”")); const sig = connected ? (state.wifi.networks.find(n => n.ssid === connected)?.signal ?? 3) : null; qsa("[data-signal]").forEach(el => (el.textContent = connected ? `${"●".repeat(sig)}${"β—‹".repeat(5 - sig)}` : "β€”")); const net = selected ? state.wifi.networks.find(n => n.ssid === selected) : null; const secured = !!net?.secured; const passEl = qs("[data-wifi-pass]"); const passwordOk = secured ? !!(passEl?.value && passEl.value.trim().length >= 8) : true; qsa("[data-connect]").forEach(btn => { btn.disabled = !enabled || !selected || connecting || (secured && !passwordOk); }); qsa("[data-disconnect]").forEach(btn => { btn.disabled = !enabled || !connected || connecting; }); if (passEl) { passEl.disabled = !enabled || !secured; // βœ… FIX: do NOT wipe password when switching to open networks // (leave existing value alone; selectNetwork() handles hydration) } const wifiQS = qs('[data-qs="wifi"]'); if (wifiQS) { wifiQS.classList.toggle("active", enabled); wifiQS.setAttribute("aria-pressed", enabled ? "true" : "false"); } if (trayWifiBtn) trayWifiBtn.classList.toggle("active", !!connected); } /* ========================= NOTES ========================== */ function initNotes() { const area = qs("[data-note-area]"); if (!area) return; area.value = state.notes.content; let t = null; area.addEventListener("input", () => { if (t) clearTimeout(t); t = setTimeout(() => { localStorage.setItem(STORAGE_KEYS.NOTES, area.value); }, 700); }); qs("[data-note-save]")?.addEventListener("click", () => { localStorage.setItem(STORAGE_KEYS.NOTES, area.value); pushNotif("Notes", "Saved locally."); }); qs("[data-note-new]")?.addEventListener("click", () => { area.value = ""; localStorage.setItem(STORAGE_KEYS.NOTES, ""); }); } /* ========================= CLOCK ========================== */ function initClock() { const t = qs("[data-time]"); const d = qs("[data-date]"); if (!t || !d) return; const pad2 = n => String(n).padStart(2, "0"); function update() { const now = new Date(); t.textContent = `${pad2(now.getHours())}:${pad2(now.getMinutes())}`; d.textContent = now.toLocaleDateString(); } update(); setInterval(update, 1000); qs("#clock")?.addEventListener("click", () => toggleMenu(actionCenter)); } /* ========================= DESKTOP CONTEXT MENU (basic) ========================== */ function bindDesktopContextMenu() { const menu = qs("#context-menu"); if (!menu) return; desktop?.addEventListener("contextmenu", e => { e.preventDefault(); // show first so it has a measurable size menu.classList.add("show"); // βœ… FIX: clamp within viewport const rect = menu.getBoundingClientRect(); const x = clamp(e.clientX, 0, window.innerWidth - rect.width); const y = clamp(e.clientY, 0, window.innerHeight - rect.height); menu.style.left = `${x}px`; menu.style.top = `${y}px`; closeAllMenus(); // context menu uses no overlay }); document.addEventListener("click", () => menu.classList.remove("show")); menu.addEventListener("click", e => { const item = e.target.closest("[data-cm]"); if (!item) return; const cmd = item.dataset.cm; menu.classList.remove("show"); if (cmd === "refresh") { fakeScanNetworks(); pushNotif("Desktop", "Refreshed."); } else if (cmd === "display") { openWindow("settings"); activateSettingsTab("network"); } else if (cmd === "personalize") { openWindow("settings"); activateSettingsTab("personalization"); } else { pushNotif("Desktop", `β€œ${cmd}” is a UI placeholder.`); } }); } /* ========================= SETTINGS TABS ========================== */ function activateSettingsTab(name) { const win = qs("#win-settings"); if (!win) return; qsa("[data-settings-tab]", win).forEach(n => n.classList.toggle("active", n.dataset.settingsTab === name)); qsa("[data-pane]", win).forEach(p => (p.hidden = p.dataset.pane !== name)); } document.addEventListener("click", e => { const nav = e.target.closest("[data-settings-tab]"); if (!nav) return; activateSettingsTab(nav.dataset.settingsTab); }); /* ========================= GLOBAL SHORTCUTS ========================== */ function bindGlobalShortcuts() { document.addEventListener("keydown", e => { if (e.key === "Escape") { closeAllMenus(); qs("#context-menu")?.classList.remove("show"); } // βœ… FIX: ignore repeats so holding Meta doesn’t spam-toggle if (e.key === "Meta" && !e.repeat) { e.preventDefault(); toggleMenu(startMenu); } if (e.ctrlKey && e.altKey && e.key.toLowerCase() === "w") { e.preventDefault(); toggleMenu(wifiFlyout); } if (e.ctrlKey && e.altKey && e.key.toLowerCase() === "a") { e.preventDefault(); toggleMenu(actionCenter); } if (e.altKey && e.key === "F4") { e.preventDefault(); if (state.activeWinId) closeWindow(state.activeWinId); } }); desktop?.addEventListener("mousedown", e => { if (e.target.closest(".desk-icon")) return; closeAllMenus(); }); } /* ========================= GINGER BROWSER ========================== */ function initBrowser() { const win = qs("#win-edge"); if (!win) return; const addr = qs("[data-addr]", win); const btnGo = qs("[data-go]", win); const btns = qsa(".btn", win).slice(0, 3); const [btnBack, btnFwd, btnRef] = btns; const contentHost = win.querySelector(".content > div:last-child") || win.querySelector(".content"); const viewport = document.createElement("div"); viewport.style.minHeight = "100%"; viewport.style.display = "block"; contentHost.innerHTML = ""; contentHost.appendChild(viewport); const toolbar = win.querySelector(".content > div:first-child"); if (toolbar) { const bm = document.createElement("div"); bm.style.display = "flex"; bm.style.gap = "8px"; bm.style.padding = "10px"; bm.style.borderBottom = "1px solid rgba(255,255,255,.10)"; bm.style.background = "rgba(10,11,13,.35)"; bm.innerHTML = ` Tip: external sites may open in a new tab (X-Frame-Options). `; toolbar.insertAdjacentElement("afterend", bm); bm.addEventListener("click", e => { const b = e.target.closest("[data-bm]"); if (!b) return; navigateTo(b.dataset.bm, { push: true }); if (addr) addr.value = normalizeUrl(b.dataset.bm); }); } const saved = safeParseJSON(localStorage.getItem(STORAGE_KEYS.BROWSER_STATE), null); if (saved?.history?.length) { state.browser.history = saved.history; state.browser.index = clamp(saved.index ?? (saved.history.length - 1), 0, saved.history.length - 1); const cur = state.browser.history[state.browser.index]; if (addr) addr.value = cur; navigateTo(cur, { push: false }); } else { navigateTo("ginger://home", { push: true }); if (addr) addr.value = "ginger://home"; } function saveBrowserState() { localStorage.setItem( STORAGE_KEYS.BROWSER_STATE, JSON.stringify({ history: state.browser.history, index: state.browser.index }) ); } function normalizeUrl(input) { const s = (input || "").trim(); if (!s) return "ginger://home"; if (s.startsWith("ginger://")) return s; if (/^https?:\/\//i.test(s)) return s; if (s.includes("://")) return s; if (s.includes(".") || s.includes("localhost")) return "https://" + s; return "ginger://search?q=" + encodeURIComponent(s); } function setNavButtons() { if (btnBack) btnBack.disabled = state.browser.index <= 0; if (btnFwd) btnFwd.disabled = state.browser.index >= state.browser.history.length - 1; } function pushHistory(url) { if (state.browser.index < state.browser.history.length - 1) { state.browser.history = state.browser.history.slice(0, state.browser.index + 1); } state.browser.history.push(url); state.browser.index = state.browser.history.length - 1; saveBrowserState(); setNavButtons(); } function renderBuiltInPage(url) { const u = new URL(url.replace("ginger://", "https://ginger.local/")); const path = u.pathname.replace(/^\//, ""); const q = u.searchParams; const page = document.createElement("div"); page.style.padding = "14px"; page.style.display = "grid"; page.style.gap = "10px"; const card = (title, body) => { const c = document.createElement("div"); c.className = "card"; c.innerHTML = `
${escapeHtml(title)}
${body}
`; return c; }; if (path === "" || path === "home") { page.appendChild(card("Ginger Browser β€” Home", `Welcome. Try ginger://apps or paste a URL.
External sites might open in a new tab if they block embedding.`)); const quick = document.createElement("div"); quick.className = "card"; quick.innerHTML = `
Quick links
`; quick.addEventListener("click", e => { const b = e.target.closest("[data-nav]"); if (!b) return; navigateTo(b.dataset.nav, { push: true }); if (addr) addr.value = b.dataset.nav; }); page.appendChild(quick); } else if (path === "apps") { page.appendChild(card("Apps", `Open GingerOS apps directly from the browser:`)); const grid = document.createElement("div"); grid.className = "card"; grid.innerHTML = `
`; grid.addEventListener("click", e => { const b = e.target.closest("[data-appopen]"); if (!b) return; openWindow(b.dataset.appopen); }); page.appendChild(grid); } else if (path === "wifi") { const status = state.wifi.connected ? `Connected to ${escapeHtml(state.wifi.connected)}` : "Disconnected"; page.appendChild(card("Wi-Fi", `Status: ${status}
Wi-Fi is ${state.wifi.enabled ? "On" : "Off"}.`)); const actions = document.createElement("div"); actions.className = "card"; actions.innerHTML = `
Connect from the Wi-Fi flyout to enter passwords.
`; actions.addEventListener("click", e => { if (e.target.closest("[data-openfly]")) toggleMenu(wifiFlyout); if (e.target.closest("[data-open-settings]")) { openWindow("settings"); activateSettingsTab("network"); } if (e.target.closest("[data-togglewifi]")) { state.wifi.enabled = !state.wifi.enabled; saveWifiEnabled(); if (!state.wifi.enabled && state.wifi.connected) disconnectWifi(true); updateWifiUI(); navigateTo("ginger://wifi", { push: false }); } }); page.appendChild(actions); } else if (path === "notes") { page.appendChild(card("Notes", `Your notes are stored locally in this browser.`)); const c = document.createElement("div"); c.className = "card"; c.innerHTML = `
Preview:
${escapeHtml(localStorage.getItem(STORAGE_KEYS.NOTES) || "") || "(empty)"}
`; c.addEventListener("click", e => { if (e.target.closest("[data-open-notes]")) openWindow("notes"); if (e.target.closest("[data-clear-notes]")) { localStorage.setItem(STORAGE_KEYS.NOTES, ""); const area = qs("[data-note-area]"); if (area) area.value = ""; navigateTo("ginger://notes", { push: false }); pushNotif("Notes", "Cleared."); } }); page.appendChild(c); } else if (path === "search") { const term = q.get("q") || ""; page.appendChild(card("Search", `Results for: ${escapeHtml(term)}`)); const res = document.createElement("div"); res.className = "card"; const items = [ { t: "GingerOS docs (built-in)", u: "ginger://home" }, { t: "Open Settings", u: "ginger://apps" }, { t: "Example.com (external)", u: "https://example.com" } ].filter(x => x.t.toLowerCase().includes(term.toLowerCase()) || !term); res.innerHTML = `
This is a built-in demo search (not web search).
${items.map(i => `
${escapeHtml(i.t)}
${escapeHtml(i.u)}
`).join("")}
`; res.addEventListener("click", e => { const b = e.target.closest("[data-openurl]"); if (!b) return; navigateTo(b.dataset.openurl, { push: true }); if (addr) addr.value = b.dataset.openurl; }); page.appendChild(res); } else { page.appendChild(card("Page not found", `No built-in page at ${escapeHtml("ginger://" + path)}.`)); } viewport.innerHTML = ""; viewport.appendChild(page); } function renderExternal(url) { viewport.innerHTML = ""; const wrap = document.createElement("div"); wrap.style.padding = "14px"; wrap.style.display = "grid"; wrap.style.gap = "10px"; const info = document.createElement("div"); info.className = "card"; info.innerHTML = `
External site
Many sites block being shown inside other pages (X-Frame-Options / CSP).
Ginger Browser will try to embed it; if it fails, use β€œOpen in new tab”.
`; wrap.appendChild(info); const frameCard = document.createElement("div"); frameCard.className = "card"; frameCard.innerHTML = `
Embedded view (may be blank if blocked):
If you see an error/blank page, use β€œOpen in new tab”.
`; wrap.appendChild(frameCard); const iframe = qs("[data-frame]", frameCard); function tryEmbed() { iframe.src = url; } info.addEventListener("click", e => { if (e.target.closest("[data-open-newtab]")) { window.open(url, "_blank", "noopener,noreferrer"); pushNotif("Ginger Browser", "Opened in a new tab."); } if (e.target.closest("[data-try-embed]")) { tryEmbed(); } }); viewport.appendChild(wrap); tryEmbed(); } function navigateTo(input, { push }) { const url = normalizeUrl(input); if (url.startsWith("ginger://")) { state.browser.current = url; if (push) pushHistory(url); else setNavButtons(); renderBuiltInPage(url); return; } state.browser.current = url; if (push) pushHistory(url); else setNavButtons(); renderExternal(url); } btnGo?.addEventListener("click", () => { const url = (addr?.value || "").trim(); navigateTo(url, { push: true }); if (addr) addr.value = normalizeUrl(url); }); addr?.addEventListener("keydown", e => { if (e.key === "Enter") { e.preventDefault(); btnGo?.click(); } }); btnBack?.addEventListener("click", () => { if (state.browser.index <= 0) return; state.browser.index--; const url = state.browser.history[state.browser.index]; if (addr) addr.value = url; navigateTo(url, { push: false }); localStorage.setItem(STORAGE_KEYS.BROWSER_STATE, JSON.stringify({ history: state.browser.history, index: state.browser.index })); setNavButtons(); }); btnFwd?.addEventListener("click", () => { if (state.browser.index >= state.browser.history.length - 1) return; state.browser.index++; const url = state.browser.history[state.browser.index]; if (addr) addr.value = url; navigateTo(url, { push: false }); localStorage.setItem(STORAGE_KEYS.BROWSER_STATE, JSON.stringify({ history: state.browser.history, index: state.browser.index })); setNavButtons(); }); btnRef?.addEventListener("click", () => { const cur = state.browser.current || (addr?.value || "ginger://home"); navigateTo(cur, { push: false }); pushNotif("Ginger Browser", "Refreshed."); }); const _ = 0; } /* ========================= MISC CLICK HELPERS ========================== */ document.addEventListener("click", e => { const t = e.target.closest("[data-run-troubleshooter]"); if (t) { pushNotif("Troubleshooter", "No issues found (simulated)."); return; } const sw = e.target.closest("[data-open-wifi-flyout]"); if (sw) { updateWifiUI(); return; } }); })();