From 3dd9eb5a3eb86da97ef28cdd9f7510d64ec08d8a Mon Sep 17 00:00:00 2001 From: serversdown Date: Mon, 22 Jun 2026 19:39:55 +0000 Subject: [PATCH] feat(mobile): Thoughts in the mobile menu + full nav drawer on secondary pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Chat page: add "💭 Thoughts" to the mobile slide-out menu (with /thoughts handler), grouped with Journal. Thoughts was the one page mobile couldn't reach. - nav.js: on mobile, secondary pages (Thoughts/Journal/Mind/Session/History/Hands/ Logs) now get a ☰ slide-in drawer with the full nav + Settings — matching the desktop sidebar. Gated to pages without their own mobile menu, so the chat page's tailored hamburger/tab-bar is left untouched. Shared ITEMS list = one source of truth. Static-only (no server change). 77 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) --- lyra/web/static/index.html | 6 ++- lyra/web/static/nav.js | 86 ++++++++++++++++++++++++++------------ 2 files changed, 64 insertions(+), 28 deletions(-) diff --git a/lyra/web/static/index.html b/lyra/web/static/index.html index 94a471c..4f60a17 100644 --- a/lyra/web/static/index.html +++ b/lyra/web/static/index.html @@ -41,9 +41,10 @@

Actions

+ + - @@ -1203,6 +1204,9 @@ document.getElementById("mobileHistoryBtn").addEventListener("click", () => { closeMobileMenu(); window.location.href = "/history"; }); + document.getElementById("mobileThoughtsBtn").addEventListener("click", () => { + closeMobileMenu(); window.location.href = "/thoughts"; + }); // Connect to the global live log on page load. connectThinkingStream(); diff --git a/lyra/web/static/nav.js b/lyra/web/static/nav.js index fbd1de0..349b7e9 100644 --- a/lyra/web/static/nav.js +++ b/lyra/web/static/nav.js @@ -1,6 +1,7 @@ /* Shared app navigation — one source of truth across all pages (no build step). - Injects a left sidebar on desktop (>=769px) with active-page highlighting; stays - out of the way on mobile, where each page keeps its bottom bar / back-links. */ + Desktop (>=769px): a fixed left sidebar. Mobile (<=768px): a slide-in drawer + behind a ☰ button — but ONLY on pages that don't already ship their own mobile + menu (the chat page has its own hamburger + tab bar, so we leave it alone). */ (function () { const ITEMS = [ { href: "/", icon: "💬", label: "Chat" }, @@ -21,34 +22,45 @@ return path === href || path.indexOf(href + "/") === 0; } + // Visual styling (all sizes); positioning differs per breakpoint below. const css = ` - #app-nav { display: none; } + #app-nav { display: none; flex-direction: column; gap: 2px; box-sizing: border-box; + padding: 14px 10px; background: #0b0b0b; border-right: 1px solid #2a1d12; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } + #app-nav .brand { display: flex; align-items: center; gap: 8px; text-decoration: none; + color: #ff7a00; font-weight: 700; font-size: 1.15rem; letter-spacing: .5px; padding: 6px 11px 14px; } + #app-nav .brand .dot { width: 8px; height: 8px; border-radius: 50%; + background: #8fd694; box-shadow: 0 0 8px rgba(143,214,148,.6); } + #app-nav .navitem { display: flex; align-items: center; gap: 11px; width: 100%; text-align: left; + padding: 9px 11px; border-radius: 9px; border: none; background: none; color: #cfcfcf; + text-decoration: none; font-size: .95rem; cursor: pointer; font-family: inherit; + -webkit-tap-highlight-color: transparent; } + #app-nav .navitem .i { font-size: 1.05rem; width: 20px; text-align: center; filter: grayscale(.3); } + #app-nav .navitem:hover { background: rgba(255,122,0,.08); color: #fff; } + #app-nav .navitem.active { background: rgba(255,122,0,.14); color: #ff7a00; } + #app-nav .navitem.active .i { filter: none; } + #app-nav .spacer { flex: 1; } + #app-nav-burger { display: none; } + #app-nav-scrim { display: none; } + @media screen and (min-width: 769px) { body { padding-left: 212px; } - #app-nav { - position: fixed; left: 0; top: 0; bottom: 0; width: 212px; z-index: 1000; - display: flex; flex-direction: column; gap: 2px; box-sizing: border-box; - padding: 14px 10px; background: #0b0b0b; border-right: 1px solid #2a1d12; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - } - #app-nav .brand { - display: flex; align-items: center; gap: 8px; text-decoration: none; - color: #ff7a00; font-weight: 700; font-size: 1.15rem; letter-spacing: .5px; - padding: 6px 11px 14px; - } - #app-nav .brand .dot { width: 8px; height: 8px; border-radius: 50%; - background: #8fd694; box-shadow: 0 0 8px rgba(143,214,148,.6); } - #app-nav .navitem { - display: flex; align-items: center; gap: 11px; width: 100%; text-align: left; - padding: 9px 11px; border-radius: 9px; border: none; background: none; - color: #cfcfcf; text-decoration: none; font-size: .95rem; cursor: pointer; - font-family: inherit; -webkit-tap-highlight-color: transparent; - } - #app-nav .navitem .i { font-size: 1.05rem; width: 20px; text-align: center; filter: grayscale(.3); } - #app-nav .navitem:hover { background: rgba(255,122,0,.08); color: #fff; } - #app-nav .navitem.active { background: rgba(255,122,0,.14); color: #ff7a00; } - #app-nav .navitem.active .i { filter: none; } - #app-nav .spacer { flex: 1; } + #app-nav { display: flex; position: fixed; left: 0; top: 0; bottom: 0; width: 212px; z-index: 1000; } + } + + @media screen and (max-width: 768px) { + body.lyra-nav-mobile #app-nav-burger { display: flex; align-items: center; justify-content: center; + position: fixed; top: calc(env(safe-area-inset-top) + 8px); right: 10px; z-index: 1301; + width: 40px; height: 40px; border-radius: 10px; border: 1px solid #2a1d12; + background: rgba(14,14,14,.92); color: #ff7a00; font-size: 1.2rem; cursor: pointer; + -webkit-tap-highlight-color: transparent; backdrop-filter: blur(4px); } + body.lyra-nav-mobile #app-nav { display: flex; position: fixed; left: 0; top: 0; bottom: 0; + width: 240px; max-width: 80vw; transform: translateX(-100%); transition: transform .22s ease; + z-index: 1310; padding-top: calc(env(safe-area-inset-top) + 14px); overflow-y: auto; } + body.lyra-nav-mobile #app-nav.open { transform: translateX(0); } + body.lyra-nav-mobile #app-nav-scrim.show { display: block; position: fixed; inset: 0; + background: rgba(0,0,0,.5); z-index: 1305; } + #app-nav .navitem { padding: 12px 11px; font-size: 1rem; } }`; const style = document.createElement("style"); @@ -74,4 +86,24 @@ if (btn) btn.click(); else location.href = "/?settings=1"; }); + + // Mobile drawer — only on pages without their own mobile menu (i.e., not the chat page). + if (!document.getElementById("hamburgerMenu")) { + document.body.classList.add("lyra-nav-mobile"); + const burger = document.createElement("button"); + burger.id = "app-nav-burger"; + burger.type = "button"; + burger.setAttribute("aria-label", "Menu"); + burger.textContent = "☰"; + const scrim = document.createElement("div"); + scrim.id = "app-nav-scrim"; + document.body.appendChild(burger); + document.body.appendChild(scrim); + const close = function () { nav.classList.remove("open"); scrim.classList.remove("show"); }; + burger.addEventListener("click", function () { + nav.classList.toggle("open"); scrim.classList.toggle("show"); + }); + scrim.addEventListener("click", close); + nav.addEventListener("click", function (e) { if (e.target.closest("a")) close(); }); + } })();