openvibers/openvibe.live Commits
Remove the superseded navbar: Live's own menu toggles, dropdown/overflow/drawer helpers, the auth-state writes to markup that no longer exists and 138 CSS rules for it (the shared navbar has replaced all of it since 2026-09-18); -22 KB style.css
Changes
@@@ -957,19 +957,7 @@ main#app { position: relative; z-index: 1; }}.nav-brand[data-brand-tooltip]:hover::after { opacity: 1; }--.nav-links-wrap {-- display: flex; align-items: center; flex: 1; min-width: 0;-- position: relative;--}--.nav-links {-- display: flex; gap: 4px; flex: 1; min-width: 0;-- overflow-x: auto; overflow-y: visible;-- align-items: center;-- scrollbar-width: none;-- scroll-behavior: smooth;--}--.nav-links::-webkit-scrollbar { display: none; }.nav-link {display: flex; align-items: center; gap: 6px;padding: 8px 14px; border-radius: var(--radius);@@@ -990,97 +978,16 @@ main#app { position: relative; z-index: 1; }color: var(--accent-light);}--/* ── Nav Dropdown (Game → OpenVibeGame / Canvas) ──────────────────── */--.nav-dropdown {-- position: relative;-- flex-shrink: 0;--}--.nav-dropdown .nav-link {-- user-select: none;--}--.nav-dd-caret {-- font-size: 0.6rem;-- margin-left: 2px;-- transition: transform 0.2s;--}--.nav-dropdown:hover .nav-dd-caret,--.nav-dropdown.open .nav-dd-caret {-- transform: rotate(180deg);--}--.nav-dropdown-menu {-- display: none;-- position: fixed;-- min-width: 180px;-- background: var(--bg-tertiary, #1a1a2e);-- border: 1px solid var(--border, rgba(255,255,255,0.08));-- border-radius: var(--radius, 8px);-- box-shadow: var(--shadow-lg, 0 12px 28px rgba(0,0,0,0.4));-- z-index: 1002;-- overflow: hidden;-- padding: 4px 0;--}--/* Open on hover (desktop) or click — JS positions the fixed menu */--.nav-dropdown:hover .nav-dropdown-menu,--.nav-dropdown.open .nav-dropdown-menu {-- display: block;--}--.nav-dropdown-menu a {-- display: flex;-- align-items: center;-- gap: 10px;-- padding: 10px 16px;-- color: var(--text-primary, #e8e6e3);-- font-size: 0.9rem;-- cursor: pointer;-- transition: background 0.15s;-- white-space: nowrap;--}--.nav-dropdown-menu a:hover {-- background: var(--bg-hover, rgba(255,255,255,0.06));-- color: var(--accent-light, var(--accent-light));--}--.nav-dropdown-menu a:hover i {-- color: var(--accent, var(--accent));--}--.nav-dropdown-menu a i {-- width: 18px;-- text-align: center;-- color: var(--text-secondary, #9a9a9a);-- transition: color 0.15s;--}--/* ── Nav Scroll Arrows (visible when nav links overflow) ────── */--.nav-scroll-btn {-- display: none;-- width: 28px;-- min-width: 28px;-- height: 28px;-- align-items: center;-- justify-content: center;-- background: var(--bg-tertiary, #1a1a2e);-- border: 1px solid rgba(255,255,255,0.12);-- border-radius: 50%;-- color: var(--text-secondary);-- font-size: 0.75rem;-- cursor: pointer;-- z-index: 10;-- transition: background 0.15s, color 0.15s;-- flex-shrink: 0;--}--.nav-scroll-btn:hover {-- background: var(--accent, var(--accent));-- color: #fff;--}--.nav-scroll-btn.visible { display: flex; }/* Live indicator on Go Live nav link */.nav-live-dot {@@@ -1109,12 +1016,6 @@ main#app { position: relative; z-index: 1; }display: flex; align-items: center; gap: 12px;flex-shrink: 0; white-space: nowrap;}--#nav-auth-anon,--#nav-auth-user {-- display: flex;-- align-items: center;-- gap: 10px;--}.nav-balance {display: flex; align-items: center; gap: 4px;padding: 4px 10px; border-radius: 12px;@@@ -1123,50 +1024,8 @@ main#app { position: relative; z-index: 1; }color: var(--accent); font-size: 0.85rem; font-weight: 600;cursor: pointer;}--.nav-avatar-wrap {-- display: flex; align-items: center; gap: 8px;-- cursor: pointer; position: relative;-- padding: 4px 8px; border-radius: var(--radius);--}--.nav-avatar-wrap:hover { background: var(--bg-hover); }--.nav-avatar {-- width: 32px; height: 32px; border-radius: 50%;-- background: var(--accent); color: #000; font-weight: 700;-- display: flex; align-items: center; justify-content: center;-- font-size: 0.85rem;--}--#nav-username { font-size: 0.9rem; color: var(--text-primary); }--.nav-avatar-wrap { position: relative; }--.user-dropdown {-- display: none; position: absolute; top: 100%; right: 0;-- background: var(--bg-tertiary); border: 1px solid var(--border);-- border-radius: var(--radius); min-width: 200px;-- box-shadow: var(--shadow-lg); z-index: 1001;-- overflow: hidden;--}--.user-dropdown.show { display: block; }--.user-dropdown a {-- display: flex; align-items: center; gap: 10px;-- padding: 10px 16px; color: var(--text-primary);-- font-size: 0.9rem; cursor: pointer;--}--.user-dropdown a:hover { background: var(--bg-hover); }--.user-dropdown hr { border: none; border-top: 1px solid var(--border); margin: 4px 0; }--.nav-hamburger {-- display: none; background: none; border: none;-- color: var(--text-primary); font-size: 1.3rem;-- padding: 8px; cursor: pointer;-- border-radius: 10px;-- transition: background 0.15s, color 0.15s;-- flex-shrink: 0;--}--.nav-hamburger:hover { background: rgba(255,255,255,0.06); }--.nav-hamburger.open {-- background: color-mix(in srgb, var(--accent) 12%, transparent);-- color: var(--accent);--}/* ═══════════════════════════════════════════════════════════════BUTTONS@@@ -4913,21 +4772,9 @@ textarea.form-input { resize: vertical; min-height: 60px; }.navbar { gap: 12px; padding: 0 12px; }.nav-link { padding: 8px 10px; gap: 5px; font-size: 0.85rem; }.nav-right { gap: 8px; }-- #nav-username { display: none; }-- .nav-avatar-wrap { gap: 4px; padding: 4px; }.nav-balance, .nav-coins { padding: 4px 8px; font-size: 0.8rem; }-- #nav-auth-anon, #nav-auth-user { gap: 8px; }}--/* 1024px: Hamburger menu + hide nav links (catches Steam Deck & small laptops) */--/* Phones and small tablets: the hamburger replaces the link row. Between 820 and 1024px the-- links fit (or scroll), so showing a menu button there only hid navigation that had room. */--@media (max-width: 820px) {-- .nav-links { display: none; }-- .nav-links-wrap { display: contents; }-- .nav-links-wrap .nav-scroll-btn { display: none !important; }-- .nav-hamburger { display: inline-flex; }--}@media (max-width: 1024px) {.navbar { gap: 8px; padding: 0 10px; }.nav-brand { font-size: 1.1rem; }@@@ -4936,51 +4783,6 @@ textarea.form-input { resize: vertical; min-height: 60px; }/* (nav links stay visible here — the row scrolls when it overflows; the hamburger onlytakes over below 820px, see the block right after this one.) */-- /* ── Mobile nav menu ──────────────────────────────────────────────────────-- This was a full-bleed sheet pinned to both edges of the screen, with the items centred-- individually so every pill was a different width and the column read as ragged. It is now-- a panel that belongs to the button that opened it: anchored under the hamburger at the-- right edge, sized to its content rather than the viewport, and scaled up from its top-right-- corner so the motion starts where the finger did.---- Glass here is a layered one — a translucent card tint over a blur with a little saturation,-- a hairline, and an inner top highlight — so it sits above the page instead of washing it-- out. All of it is theme-derived; nothing is a fixed colour. */-- .nav-links.show {-- display: flex; flex-direction: column; align-items: stretch;-- position: fixed; top: calc(var(--navbar-h) + 7px);-- right: 8px; left: auto; bottom: auto;-- width: max-content;-- min-width: 208px;-- max-width: min(280px, calc(100vw - 16px));-- padding: 7px; gap: 3px; z-index: 1001;-- border-radius: 16px;-- border: 1px solid color-mix(in srgb, var(--border-light) 85%, transparent);-- background:-- linear-gradient(180deg,-- color-mix(in srgb, var(--bg-card) 88%, transparent),-- color-mix(in srgb, var(--bg-secondary) 94%, transparent));-- box-shadow:-- 0 22px 48px rgba(0, 0, 0, 0.45),-- 0 3px 10px rgba(0, 0, 0, 0.28),-- inset 0 1px 0 rgba(255, 255, 255, 0.07);-- backdrop-filter: blur(20px) saturate(1.6);-- -webkit-backdrop-filter: blur(20px) saturate(1.6);-- overflow-y: auto; overscroll-behavior: contain;-- max-height: calc(100vh - var(--navbar-h) - 22px);-- max-height: calc(100dvh - var(--navbar-h) - 22px);-- transform-origin: top right;-- animation: nav-drawer-in 0.26s cubic-bezier(.2, 1, .3, 1) both;-- }-- /* A small tongue pointing back at the hamburger, so the panel reads as attached to it. */-- .nav-links.show::before {-- content: ''; position: absolute; top: -5px; right: 17px;-- width: 11px; height: 11px; transform: rotate(45deg);-- background: color-mix(in srgb, var(--bg-card) 88%, transparent);-- border-left: 1px solid color-mix(in srgb, var(--border-light) 85%, transparent);-- border-top: 1px solid color-mix(in srgb, var(--border-light) 85%, transparent);-- border-radius: 2px 0 0 0;-- }@keyframes nav-drawer-in {from { opacity: 0; transform: translateY(-8px) scale(0.93); }to { opacity: 1; transform: none; }@@@ -4991,109 +4793,16 @@ textarea.form-input { resize: vertical; min-height: 60px; }from { opacity: 0; transform: translateX(7px); }to { opacity: 1; transform: none; }}-- .nav-links.show .nav-link,-- .nav-links.show .nav-dropdown-menu a {-- animation: nav-item-in 0.26s ease both;-- animation-delay: calc(60ms + var(--nav-i, 0) * 26ms);-- }-- .nav-links.show > :nth-child(1) { --nav-i: 0; }-- .nav-links.show > :nth-child(2) { --nav-i: 1; }-- .nav-links.show > :nth-child(3) { --nav-i: 2; }-- .nav-links.show > :nth-child(4) { --nav-i: 3; }-- .nav-links.show > :nth-child(5) { --nav-i: 4; }-- .nav-links.show > :nth-child(n+6) { --nav-i: 5; }---- /* Restore labels & lay out vertically in dropdown */-- .nav-links.show .nav-link {-- flex-shrink: 0;-- display: flex; align-items: center; justify-content: flex-start; gap: 11px;-- width: 100%;-- padding: 10px 12px;-- border: 1px solid transparent;-- background: transparent;-- border-radius: 11px;-- font-size: 0.92rem; font-weight: 600;-- white-space: nowrap;-- transition: background-color 0.16s ease, border-color 0.16s ease, color 0.16s ease, transform 0.12s ease;-- }-- /* A fixed icon column so the labels start on one line instead of wherever each glyph ends. */-- .nav-links.show .nav-link > i,-- .nav-links.show .nav-dropdown-menu a > i {-- width: 19px; flex: none; text-align: center; font-size: 0.94rem;-- color: var(--text-secondary); transition: color 0.16s ease;-- }-- .nav-links.show .nav-link span { display: inline; }-- .nav-links.show .nav-link[data-tooltip]::after { display: none; }-- .nav-links.show .nav-link:hover,-- .nav-links.show .nav-dropdown-menu a:hover {-- background: color-mix(in srgb, var(--accent) 12%, transparent);-- border-color: color-mix(in srgb, var(--accent) 24%, transparent);-- color: var(--text-primary);-- }-- .nav-links.show .nav-link:hover > i,-- .nav-links.show .nav-dropdown-menu a:hover > i { color: var(--accent); }-- .nav-links.show .nav-link:active,-- .nav-links.show .nav-dropdown-menu a:active { transform: scale(0.982); }-- .nav-links.show .nav-link.active {-- background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 20%, transparent), color-mix(in srgb, var(--accent) 9%, transparent));-- border-color: color-mix(in srgb, var(--accent) 34%, transparent);-- color: var(--text-primary);-- }-- .nav-links.show .nav-link.active > i { color: var(--accent); }-- /* Nav dropdown in mobile: flatten into the same single column as everything else. */-- .nav-links.show .nav-dropdown {-- display: flex; flex-direction: column; align-items: stretch; gap: 3px;-- }-- .nav-links.show .nav-dropdown .nav-dd-caret { display: none; }-- .nav-links.show .nav-dropdown-menu {-- display: flex;-- flex-direction: column;-- align-items: stretch;-- position: static;-- background: none;-- border: none;-- box-shadow: none;-- padding: 0;-- gap: 3px;-- margin-top: 0;-- width: 100%;-- }-- .nav-links.show .nav-dropdown-menu a {-- display: flex; align-items: center; gap: 11px;-- padding: 10px 12px;-- border-radius: 11px;-- border: 1px solid transparent;-- background: transparent;-- font-size: 0.92rem; font-weight: 600;-- white-space: nowrap;-- transition: background-color 0.16s ease, border-color 0.16s ease, color 0.16s ease, transform 0.12s ease;-- }-- @media (prefers-reduced-motion: reduce) {-- .nav-links.show,-- .nav-links.show .nav-link,-- .nav-links.show .nav-dropdown-menu a { animation: none !important; }-- }++/* "Go Live" is the only remaining nav dropdown — keep its trigger visible. *//* Nav-right stays visible, pushed to end before hamburger */.nav-right { margin-left: auto; gap: 6px; }/* Compact currency badges */.nav-balance, .nav-coins { padding: 4px 7px; font-size: 0.75rem; gap: 3px; border-radius: 10px; }-- /* Smaller avatar, hide caret */-- .nav-avatar-wrap { gap: 0; padding: 2px; }-- .nav-avatar-wrap > .fa-caret-down { display: none; }-- .nav-avatar { width: 30px; height: 30px; font-size: 0.78rem; }---- /* Wider hamburger tap target + a gap from the avatar so they're not mis-tapped. */-- .nav-hamburger {-- padding: 8px 14px; min-width: 48px;-- justify-content: center;-- margin-left: 6px;-- }-- /* User dropdown needs to not overflow right edge */-- .user-dropdown { right: -8px; min-width: 190px; }++}/* 900px: Compact currency, hide brand text (keep "OpenVibe" on mobile) */@@@ -5104,7 +4813,6 @@ textarea.form-input { resize: vertical; min-height: 60px; }.nav-balance, .nav-coins { padding: 4px 8px; min-width: 0; font-size: 0.78rem; }.nav-right .btn span { display: none; }.nav-right .btn { padding: 6px 10px; font-size: 0.85rem; gap: 0; }-- #nav-auth-anon, #nav-auth-user { gap: 6px; }/* Chat replay sidebar stacks below video */}@@@ -5144,33 +4852,11 @@ textarea.form-input { resize: vertical; min-height: 60px; }.nav-brand { gap: 6px; }.nav-brand i { font-size: 1.1rem; }.brand-text { font-size: 0.92rem; }-- .nav-hamburger { padding: 6px; font-size: 1.2rem; }.nav-right { gap: 4px; }.nav-balance, .nav-coins { padding: 3px 6px; font-size: 0.72rem; gap: 2px; }-- .nav-avatar { width: 28px; height: 28px; font-size: 0.72rem; }-- .nav-avatar-wrap { padding: 2px; }---- /* Sign in button compact */-- #nav-auth-anon .btn { padding: 6px 10px; font-size: 0.82rem; }---- /* User dropdown full-width on mobile */-- /* Anchored under the avatar and sized to itself, like the nav panel — but its own width and-- its own entrance, because it is its own thing. */-- .user-dropdown {-- position: fixed; top: calc(var(--navbar-h) + 7px); left: auto; right: 8px;-- width: min(300px, calc(100vw - 16px)); min-width: 250px;-- max-height: calc(100vh - var(--navbar-h) - 22px);-- max-height: calc(100dvh - var(--navbar-h) - 22px);-- overflow-y: auto; overscroll-behavior: contain;-- border-radius: 16px;-- padding-bottom: env(safe-area-inset-bottom, 0px);-- }-- .user-dropdown .ud-group a { padding: 10px 11px; font-size: 0.9rem; }-- .ud-head { padding: 14px 14px 12px; }-- /* Nav drawer tighter */-- /* Only the outer inset tightens on a very small screen; row size is set with the panel. */-- .nav-links.show { right: 6px; max-width: calc(100vw - 12px); }++++.hero h1 { font-size: 1.6rem; }.hero { padding-left: 16px; padding-right: 16px; }@@@ -5319,13 +5005,8 @@ textarea.form-input { resize: vertical; min-height: 60px; }.nav-brand { gap: 5px; }.nav-brand i { font-size: 1rem; }.brand-text { font-size: 0.88rem; }-- .nav-hamburger { padding: 6px; font-size: 1.15rem; }.nav-balance, .nav-coins { padding: 2px 5px; font-size: 0.68rem; }-- .nav-avatar { width: 26px; height: 26px; font-size: 0.68rem; }-- /* Sign in button — icon only on tiny screens */-- #nav-auth-anon .btn span { display: none; }-- #nav-auth-anon .btn { padding: 6px 10px; font-size: 0.82rem; gap: 0; }.stream-card { border-radius: 16px; }.modal { width: 95%; border-radius: var(--radius); }@@@ -5406,9 +5087,6 @@ textarea.form-input { resize: vertical; min-height: 60px; }.brand-text { font-size: 0.82rem; }/* Hide currency on tiny screens to save space */.nav-balance, .nav-coins { display: none; }-- .nav-avatar { width: 24px; height: 24px; font-size: 0.65rem; }-- .nav-hamburger { padding: 5px; font-size: 1.1rem; }-- #nav-auth-anon .btn { padding: 5px 8px; font-size: 0.78rem; }.hero h1 { font-size: 1.3rem; }.hero p { font-size: 0.85rem; }@@@ -6221,9 +5899,6 @@ body.theme-light .btn-outline {body.theme-light .btn-outline:hover {background: var(--bg-hover);}--body.theme-light .user-dropdown {-- border: 1px solid var(--border);--}/* ── Page Header ────────────────────────────────────────────── */.page-header {@@@ -11479,7 +11154,6 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg.nav-brand .brand-text, .nav-brand > span:not(.brand-mark) { display: none; }.nav-right { gap: 6px; min-width: 0; }.nav-coins, .nav-balance { max-width: 88px; overflow: hidden; }-- .nav-avatar-wrap .nav-username { display: none; }}/* ── The stat board's backdrop blur ──────────────────────────────────────────@@@ -11503,7 +11177,6 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srgto its own bell rather than floating over the row, and the username truncates instead ofpushing. */.nav-right { gap: clamp(5px, 1.2vw, 10px); align-items: center; }--#nav-auth-user { gap: clamp(5px, 1.2vw, 9px); align-items: center; }.nav-right .nav-coins,.nav-right .nav-balance {height: 32px; display: inline-flex; align-items: center; gap: 5px;@@@ -11512,61 +11185,14 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg}#openvibe-bell-mount { height: 32px; display: inline-flex; align-items: center; position: relative; }#openvibe-bell-mount > * { position: relative; }--.nav-avatar-wrap {-- height: 34px; padding: 0 6px 0 3px; gap: 7px; border-radius: 999px;-- max-width: min(40vw, 190px); min-width: 0;--}--.nav-avatar { width: 28px; height: 28px; font-size: 0.78rem; flex: none; }--#nav-username {-- font-size: clamp(0.75rem, 1.8vw, 0.9rem); font-weight: 650;-- overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;--}--/* Size only — whether it shows is decided by the ≤820px block (it was display:inline-flex here,-- which put a menu button on every desktop navbar next to the full link row). */--.nav-hamburger { height: 34px; width: 34px; align-items: center; justify-content: center; flex: none; }----/* The menu itself: anchored to the avatar, never wider than the screen, and scrollable rather-- than running off the bottom on a short screen or a landscape phone. */--.user-dropdown {-- top: calc(100% + 7px); right: 0;-- /* Wide enough for the identity header's name and both wallet chips on one line. */-- min-width: 262px; max-width: min(312px, calc(100vw - 20px));-- max-height: calc(100vh - var(--navbar-h) - 20px); overflow-y: auto;-- border-radius: 16px; padding: 0;-- box-shadow: 0 22px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.05);-- overscroll-behavior: contain;--}--.user-dropdown a {-- border-radius: 9px; padding: 9px 11px; gap: 11px;-- font-size: 0.86rem; line-height: 1.25;--}--.user-dropdown a > i:first-child { width: 17px; text-align: center; flex: none; opacity: 0.85; }--.user-dropdown hr { margin: 5px 7px; border: 0; border-top: 1px solid var(--border); }--@media (max-width: 560px) {-- /* Anchor to the bar rather than the avatar, so a wide menu can't hang off the right edge. */-- .user-dropdown { position: fixed; top: calc(var(--navbar-h) + 6px); right: 8px; left: auto; min-width: 0; width: min(290px, calc(100vw - 16px)); }--}--@media (max-height: 520px) and (orientation: landscape) {-- .user-dropdown { max-height: calc(100vh - var(--navbar-h) - 12px); }-- .user-dropdown a { padding: 7px 10px; font-size: 0.82rem; }--}--/* Signed-out: the sign-in button should match the cluster's height, not tower over it. */--#nav-auth-anon .btn { height: 34px; padding: 0 clamp(10px, 2vw, 15px); font-size: clamp(0.76rem, 1.8vw, 0.88rem); border-radius: 11px; }++/* Belt and braces for the About section's cards: whatever happens with animations or deferredrendering, a card is never allowed to end up as an empty outline. */.home-cta-feature > i, .home-cta-feature > strong, .home-cta-feature > span { opacity: 1; }--/* The user menu at tablet widths: anchored to the avatar it could still reach past the right-- edge, so the fixed anchoring now covers everything below 900px rather than below 560. */--@media (max-width: 900px) {-- .user-dropdown {-- position: fixed; top: calc(var(--navbar-h) + 6px); right: 8px; left: auto;-- min-width: 0; width: min(290px, calc(100vw - 16px));-- }--}/* ── About section sub-headings ──────────────────────────────────────────────Three sub-sections inside one long panel, all introduced by the same small grey line of caps —@@@ -11684,52 +11310,8 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg--/* ═══════════════════════════════════════════════════════════════-- The account menu.---- The nav menu beside it is a list of places, so it is a compact column of equal rows. This one-- answers a different question — who am I signed in as, and what am I carrying — so it opens with-- that and groups everything under it by what the row is for. Same glass family as the nav panel-- so they belong to the same navbar, but its own shape: a tinted identity header, labelled-- sections instead of bare rules, and a logout row that reads as the way out.-- ═══════════════════════════════════════════════════════════════ */--.user-dropdown {-- padding: 0;-- border: 1px solid color-mix(in srgb, var(--accent) 26%, var(--border));-- background:-- linear-gradient(180deg,-- color-mix(in srgb, var(--accent) 10%, var(--bg-card)) 0,-- var(--bg-secondary) 92px),-- var(--bg-secondary);-- box-shadow:-- 0 24px 60px rgba(0, 0, 0, 0.5),-- 0 3px 12px rgba(0, 0, 0, 0.3),-- inset 0 1px 0 rgba(255, 255, 255, 0.07);-- backdrop-filter: blur(22px) saturate(1.6);-- -webkit-backdrop-filter: blur(22px) saturate(1.6);-- transform-origin: top right;--}--.user-dropdown.show { animation: udIn 0.24s cubic-bezier(.2, 1, .3, 1) both; }@keyframes udIn { from { opacity: 0; transform: translateY(-8px) scale(0.95); } to { opacity: 1; transform: none; } }--/* ── Identity header ── */--.ud-head {-- display: grid; grid-template-columns: auto 1fr; gap: 3px 11px;-- align-items: center; padding: 13px 14px 11px;-- border-bottom: 1px solid color-mix(in srgb, var(--accent) 16%, var(--border));--}--.ud-avatar {-- grid-row: span 2; width: 40px; height: 40px; border-radius: 50%;-- display: grid; place-items: center; overflow: hidden;-- font-weight: 900; font-size: 1rem; color: #fff;-- background: var(--accent);-- border: 2px solid color-mix(in srgb, var(--accent) 55%, transparent);-- box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);--}--.ud-avatar img { width: 100%; height: 100%; object-fit: cover; }--.ud-who { min-width: 0; display: grid; gap: 1px; }--.ud-who b { font-size: 0.94rem; font-weight: 850; letter-spacing: -0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }--.ud-who small { font-size: 0.73rem; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.ud-wallet { grid-column: 2; display: flex; gap: 6px; margin-top: 4px; flex-wrap: wrap; }.ud-wallet span {display: inline-flex; align-items: center; gap: 5px;@@@ -11737,56 +11319,18 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srgfont-size: 0.72rem; font-weight: 800; font-variant-numeric: tabular-nums;border: 1px solid transparent;}--.ud-coin { color: #f6c945; background: color-mix(in srgb, #f6c945 13%, transparent); border-color: color-mix(in srgb, #f6c945 30%, transparent) !important; }--.ud-vibe { color: #4ade80; background: color-mix(in srgb, #4ade80 13%, transparent); border-color: color-mix(in srgb, #4ade80 30%, transparent) !important; }.ud-wallet i { font-size: 0.86em; opacity: 0.9; }--/* ── Groups ── */--.ud-group { padding: 6px 5px; }--.ud-group + .ud-group { border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); }.ud-label {display: block; padding: 3px 9px 5px;font-size: 0.6rem; font-weight: 800; letter-spacing: 0.13em; text-transform: uppercase;color: var(--text-muted);}--.user-dropdown .ud-group a {-- border-radius: 9px; padding: 8px 9px; gap: 11px;-- font-size: 0.86rem; font-weight: 600;-- border: 1px solid transparent;-- transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.12s ease;--}--.user-dropdown .ud-group a > i:first-child {-- width: 18px; flex: none; text-align: center; opacity: 0.8;-- transition: color 0.15s ease, opacity 0.15s ease;--}--.user-dropdown .ud-group a:hover {-- background: color-mix(in srgb, var(--accent) 12%, transparent);-- border-color: color-mix(in srgb, var(--accent) 24%, transparent);-- transform: translateX(2px);--}--.user-dropdown .ud-group a:hover > i:first-child { color: var(--accent); opacity: 1; }--.user-dropdown .ud-group a:active { transform: translateX(2px) scale(0.985); }--/* Leaving is a different kind of action from going somewhere. */--.ud-logout { color: var(--danger, #e74c3c) !important; }--.user-dropdown .ud-group a.ud-logout:hover {-- background: color-mix(in srgb, var(--danger, #e74c3c) 14%, transparent);-- border-color: color-mix(in srgb, var(--danger, #e74c3c) 30%, transparent);--}--.user-dropdown .ud-group a.ud-logout:hover > i:first-child { color: var(--danger, #e74c3c); }/* Rows arrive just behind the panel, capped so a long menu never feels slow. */@keyframes udRow { from { opacity: 0; transform: translateX(6px); } to { opacity: 1; transform: none; } }--.user-dropdown.show .ud-group a { animation: udRow 0.22s ease both; animation-delay: calc(70ms + var(--ud-i, 0) * 18ms); }--.user-dropdown.show .ud-group:nth-of-type(1) a { --ud-i: 0; }--.user-dropdown.show .ud-group:nth-of-type(2) a { --ud-i: 1; }--.user-dropdown.show .ud-group:nth-of-type(3) a { --ud-i: 2; }--.user-dropdown.show .ud-group:nth-of-type(n+4) a { --ud-i: 3; }--@media (prefers-reduced-motion: reduce) {-- .user-dropdown.show, .user-dropdown.show .ud-group a { animation: none !important; }-- .user-dropdown .ud-group a:hover { transform: none; }--}/* ═══════════════════════════════════════════════════════════════About section ("Streaming the way it should be") — layout pass.@@@ -11848,31 +11392,6 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg.ov-conn-pill .ov-conn-dot { animation: none !important; }}--/* ── Dropdowns never leave the screen ─────────────────────────────────────────-- One rule for every navbar panel (user menu, nav dropdowns, mobile nav drawer). 100vh on phones-- includes the strip hidden behind the browser's address bar, which is why the bottom of a long menu-- used to fall off-screen and could not be scrolled to: dvh is the visible height. The cap also-- leaves room for the home indicator, and the panel scrolls inside itself without dragging the page. */--.user-dropdown, .nav-dropdown-menu, .nav-links.show {-- max-height: calc(100vh - var(--navbar-h) - 16px);-- max-height: calc(100dvh - var(--navbar-h) - 16px - env(safe-area-inset-bottom, 0px));-- overflow-y: auto; overflow-x: hidden;-- overscroll-behavior: contain; -webkit-overflow-scrolling: touch;-- scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--accent) 40%, transparent) transparent;--}--.user-dropdown { max-width: calc(100vw - 16px); }--/* Below tablet width the user menu is a sheet pinned under the bar, as wide as the phone allows. */--@media (max-width: 900px) {-- .user-dropdown { position: fixed; top: calc(var(--navbar-h) + 6px); right: 8px; left: auto; min-width: 0; width: min(340px, calc(100vw - 16px)); }-- /* The floating chat button steps aside while a navbar panel is open (body.ov-panel-open), so the menu-- needs no dead space at its end to clear it. */-- .user-dropdown > :last-child { padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px)); }--}--/* Short screens (landscape phones, split view): tighter rows so more of the menu is visible at once. */--@media (max-height: 560px) {-- .user-dropdown .ud-group a, .nav-links.show .nav-link { padding-top: 7px; padding-bottom: 7px; font-size: 0.86rem; }-- .user-dropdown .ud-label { padding-top: 8px; padding-bottom: 2px; }--}body.ov-panel-open #floating-chat-fab { opacity: 0; pointer-events: none; transform: scale(.85); transition: opacity .15s, transform .15s; }@@@ -747,48 +747,7 @@ function onAuthChange() {if (currentUser) _enableHandoff(); else if (window.OpenVibeSSO) { try { window.OpenVibeSSO.handoffLinks({ signedIn: false }); } catch { /* */ } }// The navbar is the network's shared component; tell it who is here (js/ov-navbar-live.js).if (window.LiveNav) window.LiveNav.sync(currentUser || null);-- const anon = document.getElementById('nav-auth-anon');-- const user = document.getElementById('nav-auth-user');-- const admin = document.getElementById('nav-admin');---- // The nav chrome doesn't exist in every document that loads app.js (e.g. the-- // popout chat window), so guard it — the auth-changed event below must still fire.-- if (anon && user) {-- if (currentUser) {-- anon.style.display = 'none';-- user.style.display = 'flex';-- if (admin) admin.style.display = (currentUser.capabilities?.admin_panel || hasCapability('can_access_staff_console')) ? '' : 'none';-- // Admin Panel link in the user dropdown — admins/owners only (mods can't access it).-- const ddAdmin = document.getElementById('user-dropdown-admin');-- if (ddAdmin) ddAdmin.style.display = (currentUser.role === 'admin' || currentUser.capabilities?.is_owner) ? '' : 'none';-- const navAv = document.getElementById('nav-avatar');-- if (navAv) navAv.innerHTML = _avatarInner(currentUser.avatar_url, currentUser.username);-- const navUn = document.getElementById('nav-username');-- if (navUn) navUn.textContent = currentUser.display_name || currentUser.username;-- // The account menu opens with the same identity the navbar shows, so it does not need-- // a second source of truth — it is filled from here.-- const udAv = document.getElementById('ud-avatar');-- if (udAv) udAv.innerHTML = _avatarInner(currentUser.avatar_url, currentUser.username);-- const udName = document.getElementById('ud-name');-- if (udName) udName.textContent = currentUser.display_name || currentUser.username;-- const udHandle = document.getElementById('ud-handle');-- if (udHandle) udHandle.textContent = '@' + (currentUser.username || '');-- loadBalance();-- } else {-- anon.style.display = '';-- user.style.display = 'none';-- if (admin) admin.style.display = 'none';-- }-- } else if (currentUser) {-- loadBalance();-- }-- // Go Live nav is always visible (logged out → prompts sign-up); its Dashboard-- // sub-menu + caret only apply when logged in.-- const goliveMenu = document.getElementById('nav-golive-menu');-- if (goliveMenu) goliveMenu.style.display = currentUser ? '' : 'none';-- const goliveCaret = document.querySelector('#nav-golive-dropdown .nav-dd-caret');-- if (goliveCaret) goliveCaret.style.display = currentUser ? '' : 'none';-- document.getElementById('user-dropdown')?.classList.remove('show');++ if (currentUser) loadBalance();// Sync canvas auth state if canvas page is loadedif (typeof syncCanvasAuthState === 'function') syncCanvasAuthState();@@@ -843,25 +802,6 @@ function openChannelRewards() {if (typeof toggleRewardsPanel === 'function') toggleRewardsPanel();}--function toggleUserMenu() {-- const dd = document.getElementById('user-dropdown');-- const open = dd.classList.toggle('show');-- if (open && !window.OpenVibePanels) closeMobileNav(); // the shared controller does this once it has loaded--}----function closeMobileNav() {-- document.querySelector('.nav-links')?.classList.remove('show');-- document.querySelector('.nav-hamburger')?.classList.remove('open');--}----function toggleMobileNav() {-- const navLinks = document.querySelector('.nav-links');-- const hamburger = document.querySelector('.nav-hamburger');-- navLinks.classList.toggle('show');-- hamburger?.classList.toggle('open', navLinks.classList.contains('show'));-- if (navLinks.classList.contains('show') && !window.OpenVibePanels) document.getElementById('user-dropdown')?.classList.remove('show');--}--function isModifiedLinkClick(event) {return !!(event && (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey));}@@@ -920,13 +860,6 @@ function handleLinkClick(event, urlPath, replace = false) {return false;}--function handleDropdownLinkClick(event, dropdownId) {-- if (isModifiedLinkClick(event)) return true;-- event?.preventDefault?.();-- toggleNavDropdown(dropdownId);-- return false;--}--// Go Live nav button — always visible. Logged in → the broadcast workspace;// logged out → prompt to sign up ("begin your streaming journey").function goLiveNav(event) {@@@ -940,7 +873,6 @@ function goLiveNav(event) {function dashNav(event) {if (isModifiedLinkClick(event)) return true;event?.preventDefault?.();-- if (typeof closeNavDropdowns === 'function') closeNavDropdowns();if (currentUser) navigate('/dashboard');else if (typeof showModal === 'function') showModal('register');return false;@@@ -1015,7 +947,6 @@ function teardownRoute(nextPath) {}function navigate(urlPath, replace = false) {-- closeMobileNav();teardownRoute(urlPath);// Normalize path@@@ -1125,7 +1056,6 @@ function routeFromURL() {// Hide all pagesdocument.querySelectorAll('.page').forEach(p => p.classList.remove('active'));document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));-- document.querySelector('.nav-links')?.classList.remove('show');window.scrollTo(0, 0);@@@ -1420,115 +1350,6 @@ let _navScrollRaf = 0;window.addEventListener('scroll', () => { if (_navScrollRaf) return; _navScrollRaf = requestAnimationFrame(() => { _navScrollRaf = 0; updateNavHeroTransparency(); }); }, { passive: true });window.addEventListener('resize', updateNavHeroTransparency, { passive: true });--/* ── Nav Dropdown Helpers ──────────────────────────────────────── */--function toggleNavDropdown(id) {-- const dd = document.getElementById(id);-- if (!dd) return;-- const wasOpen = dd.classList.contains('open');-- closeNavDropdowns();-- if (!wasOpen) dd.classList.add('open');--}----function closeNavDropdowns() {-- document.querySelectorAll('.nav-dropdown.open').forEach(d => d.classList.remove('open'));--}----// Close nav dropdowns when clicking outside--document.addEventListener('click', (e) => {-- if (!e.target.closest('.nav-dropdown')) closeNavDropdowns();--});----/* ── Nav Scroll Overflow Detection ─────────────────────────────── */--function checkNavOverflow() {-- const nl = document.querySelector('.nav-links');-- if (!nl) return;-- const hasOverflow = nl.scrollWidth > nl.clientWidth + 2;-- const left = document.getElementById('nav-scroll-left');-- const right = document.getElementById('nav-scroll-right');-- const atStart = nl.scrollLeft <= 1;-- const atEnd = nl.scrollLeft >= nl.scrollWidth - nl.clientWidth - 1;-- if (left) left.classList.toggle('visible', hasOverflow && !atStart);-- if (right) right.classList.toggle('visible', hasOverflow && !atEnd);--}----function scrollNavLinks(dir) {-- const nl = document.querySelector('.nav-links');-- if (!nl) return;-- nl.scrollBy({ left: dir * 160, behavior: 'smooth' });-- // Poll until scroll settles (smooth scroll can take 300-600ms)-- let checks = 0;-- let lastPos = nl.scrollLeft;-- const poll = setInterval(() => {-- checkNavOverflow();-- if (nl.scrollLeft === lastPos || ++checks > 12) clearInterval(poll);-- lastPos = nl.scrollLeft;-- }, 60);--}----// Position fixed dropdown menus below their triggers--function positionNavDropdownMenu(dropdown) {-- const menu = dropdown?.querySelector('.nav-dropdown-menu');-- const trigger = dropdown?.querySelector('.nav-link');-- if (!menu || !trigger) return;-- if (getComputedStyle(menu).position !== 'fixed') { menu.style.top = menu.style.left = menu.style.maxHeight = ''; return; } // inside the mobile drawer it flows inline-- const rect = trigger.getBoundingClientRect();-- const vw = document.documentElement.clientWidth, vh = window.visualViewport ? window.visualViewport.height : window.innerHeight;-- const width = menu.offsetWidth || 200, pad = 8;-- // Keep the whole menu on screen: slide left when it would cross the right edge, cap its height to the space below.-- menu.style.left = `${Math.max(pad, Math.min(rect.left, vw - width - pad))}px`;-- menu.style.top = `${rect.bottom}px`;-- menu.style.maxHeight = `${Math.max(120, vh - rect.bottom - pad)}px`;--}----// Live's menus are coordinated by the network's shared panel controller (openvibe-shared/panels.js, the same--// one the shared navbar uses on every other site): one panel open at a time, Escape and rotation close them, and--// <body class="ov-panel-open"> lets the floating chat button step aside. Live keeps its own markup and toggles.--{-- const register = () => {-- const P = window.OpenVibePanels; if (!P) return;-- const dd = document.getElementById('user-dropdown'); if (dd) P.register({ el: dd, openClass: 'show', id: 'user-menu' });-- const nl = document.querySelector('.nav-links'); if (nl) P.register({ el: nl, openClass: 'show', id: 'nav-drawer', close: () => closeMobileNav() });-- document.querySelectorAll('.nav-dropdown').forEach((d, i) => P.register({ el: d, openClass: 'open', id: 'nav-dropdown-' + i }));-- };-- const load = () => {-- if (window.OpenVibePanels) return register();-- const sc = document.createElement('script'); sc.src = '/shared/panels.js'; sc.async = true; sc.onload = register; document.head.appendChild(sc);-- };-- if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', load); else load();--}----/** Close every navbar panel (used when the viewport changes under an open menu). */--function closeNavPanels() {-- document.getElementById('user-dropdown')?.classList.remove('show');-- closeNavDropdowns();-- closeMobileNav();--}--// A rotated phone, a resized window or an opened keyboard invalidates a fixed menu's position: reposition--// what is open, and close panels on orientation change. Escape closes them too.--{-- let raf = 0;-- const reflow = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => document.querySelectorAll('.nav-dropdown.open, .nav-dropdown:hover').forEach(positionNavDropdownMenu)); };-- window.addEventListener('resize', reflow, { passive: true });-- if (window.visualViewport) window.visualViewport.addEventListener('resize', reflow, { passive: true });--}----// Observe hover/open to position dropdown menus--document.querySelectorAll('.nav-dropdown').forEach(dd => {-- dd.addEventListener('mouseenter', () => positionNavDropdownMenu(dd));-- dd.addEventListener('click', () => positionNavDropdownMenu(dd));--});----// Listen for scroll and resize to update nav overflow arrows--{-- const nl = document.querySelector('.nav-links');-- if (nl) {-- nl.addEventListener('scroll', checkNavOverflow, { passive: true });-- new ResizeObserver(checkNavOverflow).observe(nl);-- }-- // Also check on login (new nav items may appear)-- window.addEventListener('load', () => setTimeout(checkNavOverflow, 500));--}--/* ── Channel Page (/:username) ────────────────────────────────── */let currentChannelUsername = null; // is the current channel's user owner-rank?let _activeChannelUserId = null;@@@ -2785,14 +2606,3 @@ window.addEventListener('popstate', () => {// If auth hasn't loaded yet, DOMContentLoaded handler will call routeFromURL()});--// Intercept link clicks to use SPA navigation--document.addEventListener('click', (e) => {-- // Close user dropdown-- if (!e.target.closest('.nav-avatar-wrap') && !e.target.closest('.user-dropdown')) {-- document.getElementById('user-dropdown')?.classList.remove('show');-- }---- if (!e.target.closest('.nav-links') && !e.target.closest('.nav-hamburger')) {-- closeMobileNav();-- }--});ddeleted file mode 100644@@@ -1,11 +0,0 @@--'use strict';--// Regression: a desktop-dropdown "click outside" handler must never close the mobile drawer, or the hamburger--// opens and closes it in the same click (shipped once, 2026-09-18).--const assert = require('assert');--const fs = require('fs'); const path = require('path');--const src = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'app.js'), 'utf8');--const body = (name) => { const i = src.indexOf(`function ${name}(`); assert.ok(i >= 0, `${name} exists`); let d = 0, j = src.indexOf('{', i); const s = j; for (; j < src.length; j++) { if (src[j] === '{') d++; else if (src[j] === '}' && --d === 0) break; } return src.slice(s, j + 1); };--assert.ok(!/closeMobileNav\s*\(/.test(body('closeNavDropdowns')), 'closeNavDropdowns leaves the mobile drawer alone');--assert.ok(/closeMobileNav\s*\(/.test(body('closeNavPanels')) && /closeNavDropdowns\s*\(/.test(body('closeNavPanels')), 'closeNavPanels closes everything');--assert.ok(/classList\.toggle\('show'\)/.test(body('toggleMobileNav')), 'hamburger toggles the drawer');--console.log('nav panels: all checks passed');
@@ -957,19 +957,7 @@ main#app { position: relative; z-index: 1; }
@@ -957,19 +957,7 @@ main#app { position: relative; z-index: 1; }
}
}
.nav-brand[data-brand-tooltip]:hover::after { opacity: 1; }
.nav-brand[data-brand-tooltip]:hover::after { opacity: 1; }
-.nav-links-wrap {
- display: flex; align-items: center; flex: 1; min-width: 0;
- position: relative;
-}
-.nav-links {
- display: flex; gap: 4px; flex: 1; min-width: 0;
- overflow-x: auto; overflow-y: visible;
- align-items: center;
- scrollbar-width: none;
- scroll-behavior: smooth;
-}
-.nav-links::-webkit-scrollbar { display: none; }
.nav-link {
.nav-link {
display: flex; align-items: center; gap: 6px;
display: flex; align-items: center; gap: 6px;
padding: 8px 14px; border-radius: var(--radius);
padding: 8px 14px; border-radius: var(--radius);
@@ -990,97 +978,16 @@ main#app { position: relative; z-index: 1; }
@@ -990,97 +978,16 @@ main#app { position: relative; z-index: 1; }
color: var(--accent-light);
color: var(--accent-light);
}
}
-/* ── Nav Dropdown (Game → OpenVibeGame / Canvas) ──────────────────── */
-.nav-dropdown {
- position: relative;
- flex-shrink: 0;
-}
-.nav-dropdown .nav-link {
- user-select: none;
-}
-.nav-dd-caret {
- font-size: 0.6rem;
- margin-left: 2px;
- transition: transform 0.2s;
-}
-.nav-dropdown:hover .nav-dd-caret,
-.nav-dropdown.open .nav-dd-caret {
- transform: rotate(180deg);
-}
-.nav-dropdown-menu {
- display: none;
- position: fixed;
- min-width: 180px;
- background: var(--bg-tertiary, #1a1a2e);
- border: 1px solid var(--border, rgba(255,255,255,0.08));
- border-radius: var(--radius, 8px);
- box-shadow: var(--shadow-lg, 0 12px 28px rgba(0,0,0,0.4));
- z-index: 1002;
- overflow: hidden;
- padding: 4px 0;
-}
-/* Open on hover (desktop) or click — JS positions the fixed menu */
-.nav-dropdown:hover .nav-dropdown-menu,
-.nav-dropdown.open .nav-dropdown-menu {
- display: block;
-}
-.nav-dropdown-menu a {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 10px 16px;
- color: var(--text-primary, #e8e6e3);
- font-size: 0.9rem;
- cursor: pointer;
- transition: background 0.15s;
- white-space: nowrap;
-}
-.nav-dropdown-menu a:hover {
- background: var(--bg-hover, rgba(255,255,255,0.06));
- color: var(--accent-light, var(--accent-light));
-}
-.nav-dropdown-menu a:hover i {
- color: var(--accent, var(--accent));
-}
-.nav-dropdown-menu a i {
- width: 18px;
- text-align: center;
- color: var(--text-secondary, #9a9a9a);
- transition: color 0.15s;
-}
-/* ── Nav Scroll Arrows (visible when nav links overflow) ────── */
-.nav-scroll-btn {
- display: none;
- width: 28px;
- min-width: 28px;
- height: 28px;
- align-items: center;
- justify-content: center;
- background: var(--bg-tertiary, #1a1a2e);
- border: 1px solid rgba(255,255,255,0.12);
- border-radius: 50%;
- color: var(--text-secondary);
- font-size: 0.75rem;
- cursor: pointer;
- z-index: 10;
- transition: background 0.15s, color 0.15s;
- flex-shrink: 0;
-}
-.nav-scroll-btn:hover {
- background: var(--accent, var(--accent));
- color: #fff;
-}
-.nav-scroll-btn.visible { display: flex; }
/* Live indicator on Go Live nav link */
/* Live indicator on Go Live nav link */
.nav-live-dot {
.nav-live-dot {
@@ -1109,12 +1016,6 @@ main#app { position: relative; z-index: 1; }
@@ -1109,12 +1016,6 @@ main#app { position: relative; z-index: 1; }
display: flex; align-items: center; gap: 12px;
display: flex; align-items: center; gap: 12px;
flex-shrink: 0; white-space: nowrap;
flex-shrink: 0; white-space: nowrap;
}
}
-#nav-auth-anon,
-#nav-auth-user {
- display: flex;
- align-items: center;
- gap: 10px;
-}
.nav-balance {
.nav-balance {
display: flex; align-items: center; gap: 4px;
display: flex; align-items: center; gap: 4px;
padding: 4px 10px; border-radius: 12px;
padding: 4px 10px; border-radius: 12px;
@@ -1123,50 +1024,8 @@ main#app { position: relative; z-index: 1; }
@@ -1123,50 +1024,8 @@ main#app { position: relative; z-index: 1; }
color: var(--accent); font-size: 0.85rem; font-weight: 600;
color: var(--accent); font-size: 0.85rem; font-weight: 600;
cursor: pointer;
cursor: pointer;
}
}
-.nav-avatar-wrap {
- display: flex; align-items: center; gap: 8px;
- cursor: pointer; position: relative;
- padding: 4px 8px; border-radius: var(--radius);
-}
-.nav-avatar-wrap:hover { background: var(--bg-hover); }
-.nav-avatar {
- width: 32px; height: 32px; border-radius: 50%;
- background: var(--accent); color: #000; font-weight: 700;
- display: flex; align-items: center; justify-content: center;
- font-size: 0.85rem;
-}
-#nav-username { font-size: 0.9rem; color: var(--text-primary); }
-.nav-avatar-wrap { position: relative; }
-.user-dropdown {
- display: none; position: absolute; top: 100%; right: 0;
- background: var(--bg-tertiary); border: 1px solid var(--border);
- border-radius: var(--radius); min-width: 200px;
- box-shadow: var(--shadow-lg); z-index: 1001;
- overflow: hidden;
-}
-.user-dropdown.show { display: block; }
-.user-dropdown a {
- display: flex; align-items: center; gap: 10px;
- padding: 10px 16px; color: var(--text-primary);
- font-size: 0.9rem; cursor: pointer;
-}
-.user-dropdown a:hover { background: var(--bg-hover); }
-.user-dropdown hr { border: none; border-top: 1px solid var(--border); margin: 4px 0; }
-.nav-hamburger {
- display: none; background: none; border: none;
- color: var(--text-primary); font-size: 1.3rem;
- padding: 8px; cursor: pointer;
- border-radius: 10px;
- transition: background 0.15s, color 0.15s;
- flex-shrink: 0;
-}
-.nav-hamburger:hover { background: rgba(255,255,255,0.06); }
-.nav-hamburger.open {
- background: color-mix(in srgb, var(--accent) 12%, transparent);
- color: var(--accent);
-}
/* ═══════════════════════════════════════════════════════════════
/* ═══════════════════════════════════════════════════════════════
BUTTONS
BUTTONS
@@ -4913,21 +4772,9 @@ textarea.form-input { resize: vertical; min-height: 60px; }
@@ -4913,21 +4772,9 @@ textarea.form-input { resize: vertical; min-height: 60px; }
.navbar { gap: 12px; padding: 0 12px; }
.navbar { gap: 12px; padding: 0 12px; }
.nav-link { padding: 8px 10px; gap: 5px; font-size: 0.85rem; }
.nav-link { padding: 8px 10px; gap: 5px; font-size: 0.85rem; }
.nav-right { gap: 8px; }
.nav-right { gap: 8px; }
- #nav-username { display: none; }
- .nav-avatar-wrap { gap: 4px; padding: 4px; }
.nav-balance, .nav-coins { padding: 4px 8px; font-size: 0.8rem; }
.nav-balance, .nav-coins { padding: 4px 8px; font-size: 0.8rem; }
- #nav-auth-anon, #nav-auth-user { gap: 8px; }
}
}
-/* 1024px: Hamburger menu + hide nav links (catches Steam Deck & small laptops) */
-/* Phones and small tablets: the hamburger replaces the link row. Between 820 and 1024px the
- links fit (or scroll), so showing a menu button there only hid navigation that had room. */
-@media (max-width: 820px) {
- .nav-links { display: none; }
- .nav-links-wrap { display: contents; }
- .nav-links-wrap .nav-scroll-btn { display: none !important; }
- .nav-hamburger { display: inline-flex; }
-}
@media (max-width: 1024px) {
@media (max-width: 1024px) {
.navbar { gap: 8px; padding: 0 10px; }
.navbar { gap: 8px; padding: 0 10px; }
.nav-brand { font-size: 1.1rem; }
.nav-brand { font-size: 1.1rem; }
@@ -4936,51 +4783,6 @@ textarea.form-input { resize: vertical; min-height: 60px; }
@@ -4936,51 +4783,6 @@ textarea.form-input { resize: vertical; min-height: 60px; }
/* (nav links stay visible here — the row scrolls when it overflows; the hamburger only
/* (nav links stay visible here — the row scrolls when it overflows; the hamburger only
takes over below 820px, see the block right after this one.) */
takes over below 820px, see the block right after this one.) */
- /* ── Mobile nav menu ──────────────────────────────────────────────────────
- This was a full-bleed sheet pinned to both edges of the screen, with the items centred
- individually so every pill was a different width and the column read as ragged. It is now
- a panel that belongs to the button that opened it: anchored under the hamburger at the
- right edge, sized to its content rather than the viewport, and scaled up from its top-right
- corner so the motion starts where the finger did.
-
- Glass here is a layered one — a translucent card tint over a blur with a little saturation,
- a hairline, and an inner top highlight — so it sits above the page instead of washing it
- out. All of it is theme-derived; nothing is a fixed colour. */
- .nav-links.show {
- display: flex; flex-direction: column; align-items: stretch;
- position: fixed; top: calc(var(--navbar-h) + 7px);
- right: 8px; left: auto; bottom: auto;
- width: max-content;
- min-width: 208px;
- max-width: min(280px, calc(100vw - 16px));
- padding: 7px; gap: 3px; z-index: 1001;
- border-radius: 16px;
- border: 1px solid color-mix(in srgb, var(--border-light) 85%, transparent);
- background:
- linear-gradient(180deg,
- color-mix(in srgb, var(--bg-card) 88%, transparent),
- color-mix(in srgb, var(--bg-secondary) 94%, transparent));
- box-shadow:
- 0 22px 48px rgba(0, 0, 0, 0.45),
- 0 3px 10px rgba(0, 0, 0, 0.28),
- inset 0 1px 0 rgba(255, 255, 255, 0.07);
- backdrop-filter: blur(20px) saturate(1.6);
- -webkit-backdrop-filter: blur(20px) saturate(1.6);
- overflow-y: auto; overscroll-behavior: contain;
- max-height: calc(100vh - var(--navbar-h) - 22px);
- max-height: calc(100dvh - var(--navbar-h) - 22px);
- transform-origin: top right;
- animation: nav-drawer-in 0.26s cubic-bezier(.2, 1, .3, 1) both;
- }
- /* A small tongue pointing back at the hamburger, so the panel reads as attached to it. */
- .nav-links.show::before {
- content: ''; position: absolute; top: -5px; right: 17px;
- width: 11px; height: 11px; transform: rotate(45deg);
- background: color-mix(in srgb, var(--bg-card) 88%, transparent);
- border-left: 1px solid color-mix(in srgb, var(--border-light) 85%, transparent);
- border-top: 1px solid color-mix(in srgb, var(--border-light) 85%, transparent);
- border-radius: 2px 0 0 0;
- }
@keyframes nav-drawer-in {
@keyframes nav-drawer-in {
from { opacity: 0; transform: translateY(-8px) scale(0.93); }
from { opacity: 0; transform: translateY(-8px) scale(0.93); }
to { opacity: 1; transform: none; }
to { opacity: 1; transform: none; }
@@ -4991,109 +4793,16 @@ textarea.form-input { resize: vertical; min-height: 60px; }
@@ -4991,109 +4793,16 @@ textarea.form-input { resize: vertical; min-height: 60px; }
from { opacity: 0; transform: translateX(7px); }
from { opacity: 0; transform: translateX(7px); }
to { opacity: 1; transform: none; }
to { opacity: 1; transform: none; }
}
}
- .nav-links.show .nav-link,
- .nav-links.show .nav-dropdown-menu a {
- animation: nav-item-in 0.26s ease both;
- animation-delay: calc(60ms + var(--nav-i, 0) * 26ms);
- }
- .nav-links.show > :nth-child(1) { --nav-i: 0; }
- .nav-links.show > :nth-child(2) { --nav-i: 1; }
- .nav-links.show > :nth-child(3) { --nav-i: 2; }
- .nav-links.show > :nth-child(4) { --nav-i: 3; }
- .nav-links.show > :nth-child(5) { --nav-i: 4; }
- .nav-links.show > :nth-child(n+6) { --nav-i: 5; }
-
- /* Restore labels & lay out vertically in dropdown */
- .nav-links.show .nav-link {
- flex-shrink: 0;
- display: flex; align-items: center; justify-content: flex-start; gap: 11px;
- width: 100%;
- padding: 10px 12px;
- border: 1px solid transparent;
- background: transparent;
- border-radius: 11px;
- font-size: 0.92rem; font-weight: 600;
- white-space: nowrap;
- transition: background-color 0.16s ease, border-color 0.16s ease, color 0.16s ease, transform 0.12s ease;
- }
- /* A fixed icon column so the labels start on one line instead of wherever each glyph ends. */
- .nav-links.show .nav-link > i,
- .nav-links.show .nav-dropdown-menu a > i {
- width: 19px; flex: none; text-align: center; font-size: 0.94rem;
- color: var(--text-secondary); transition: color 0.16s ease;
- }
- .nav-links.show .nav-link span { display: inline; }
- .nav-links.show .nav-link[data-tooltip]::after { display: none; }
- .nav-links.show .nav-link:hover,
- .nav-links.show .nav-dropdown-menu a:hover {
- background: color-mix(in srgb, var(--accent) 12%, transparent);
- border-color: color-mix(in srgb, var(--accent) 24%, transparent);
- color: var(--text-primary);
- }
- .nav-links.show .nav-link:hover > i,
- .nav-links.show .nav-dropdown-menu a:hover > i { color: var(--accent); }
- .nav-links.show .nav-link:active,
- .nav-links.show .nav-dropdown-menu a:active { transform: scale(0.982); }
- .nav-links.show .nav-link.active {
- background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 20%, transparent), color-mix(in srgb, var(--accent) 9%, transparent));
- border-color: color-mix(in srgb, var(--accent) 34%, transparent);
- color: var(--text-primary);
- }
- .nav-links.show .nav-link.active > i { color: var(--accent); }
- /* Nav dropdown in mobile: flatten into the same single column as everything else. */
- .nav-links.show .nav-dropdown {
- display: flex; flex-direction: column; align-items: stretch; gap: 3px;
- }
- .nav-links.show .nav-dropdown .nav-dd-caret { display: none; }
- .nav-links.show .nav-dropdown-menu {
- display: flex;
- flex-direction: column;
- align-items: stretch;
- position: static;
- background: none;
- border: none;
- box-shadow: none;
- padding: 0;
- gap: 3px;
- margin-top: 0;
- width: 100%;
- }
- .nav-links.show .nav-dropdown-menu a {
- display: flex; align-items: center; gap: 11px;
- padding: 10px 12px;
- border-radius: 11px;
- border: 1px solid transparent;
- background: transparent;
- font-size: 0.92rem; font-weight: 600;
- white-space: nowrap;
- transition: background-color 0.16s ease, border-color 0.16s ease, color 0.16s ease, transform 0.12s ease;
- }
- @media (prefers-reduced-motion: reduce) {
- .nav-links.show,
- .nav-links.show .nav-link,
- .nav-links.show .nav-dropdown-menu a { animation: none !important; }
- }
+
/* "Go Live" is the only remaining nav dropdown — keep its trigger visible. */
/* "Go Live" is the only remaining nav dropdown — keep its trigger visible. */
/* Nav-right stays visible, pushed to end before hamburger */
/* Nav-right stays visible, pushed to end before hamburger */
.nav-right { margin-left: auto; gap: 6px; }
.nav-right { margin-left: auto; gap: 6px; }
/* Compact currency badges */
/* Compact currency badges */
.nav-balance, .nav-coins { padding: 4px 7px; font-size: 0.75rem; gap: 3px; border-radius: 10px; }
.nav-balance, .nav-coins { padding: 4px 7px; font-size: 0.75rem; gap: 3px; border-radius: 10px; }
- /* Smaller avatar, hide caret */
- .nav-avatar-wrap { gap: 0; padding: 2px; }
- .nav-avatar-wrap > .fa-caret-down { display: none; }
- .nav-avatar { width: 30px; height: 30px; font-size: 0.78rem; }
-
- /* Wider hamburger tap target + a gap from the avatar so they're not mis-tapped. */
- .nav-hamburger {
- padding: 8px 14px; min-width: 48px;
- justify-content: center;
- margin-left: 6px;
- }
- /* User dropdown needs to not overflow right edge */
- .user-dropdown { right: -8px; min-width: 190px; }
+
}
}
/* 900px: Compact currency, hide brand text (keep "OpenVibe" on mobile) */
/* 900px: Compact currency, hide brand text (keep "OpenVibe" on mobile) */
@@ -5104,7 +4813,6 @@ textarea.form-input { resize: vertical; min-height: 60px; }
@@ -5104,7 +4813,6 @@ textarea.form-input { resize: vertical; min-height: 60px; }
.nav-balance, .nav-coins { padding: 4px 8px; min-width: 0; font-size: 0.78rem; }
.nav-balance, .nav-coins { padding: 4px 8px; min-width: 0; font-size: 0.78rem; }
.nav-right .btn span { display: none; }
.nav-right .btn span { display: none; }
.nav-right .btn { padding: 6px 10px; font-size: 0.85rem; gap: 0; }
.nav-right .btn { padding: 6px 10px; font-size: 0.85rem; gap: 0; }
- #nav-auth-anon, #nav-auth-user { gap: 6px; }
/* Chat replay sidebar stacks below video */
/* Chat replay sidebar stacks below video */
}
}
@@ -5144,33 +4852,11 @@ textarea.form-input { resize: vertical; min-height: 60px; }
@@ -5144,33 +4852,11 @@ textarea.form-input { resize: vertical; min-height: 60px; }
.nav-brand { gap: 6px; }
.nav-brand { gap: 6px; }
.nav-brand i { font-size: 1.1rem; }
.nav-brand i { font-size: 1.1rem; }
.brand-text { font-size: 0.92rem; }
.brand-text { font-size: 0.92rem; }
- .nav-hamburger { padding: 6px; font-size: 1.2rem; }
.nav-right { gap: 4px; }
.nav-right { gap: 4px; }
.nav-balance, .nav-coins { padding: 3px 6px; font-size: 0.72rem; gap: 2px; }
.nav-balance, .nav-coins { padding: 3px 6px; font-size: 0.72rem; gap: 2px; }
- .nav-avatar { width: 28px; height: 28px; font-size: 0.72rem; }
- .nav-avatar-wrap { padding: 2px; }
-
- /* Sign in button compact */
- #nav-auth-anon .btn { padding: 6px 10px; font-size: 0.82rem; }
-
- /* User dropdown full-width on mobile */
- /* Anchored under the avatar and sized to itself, like the nav panel — but its own width and
- its own entrance, because it is its own thing. */
- .user-dropdown {
- position: fixed; top: calc(var(--navbar-h) + 7px); left: auto; right: 8px;
- width: min(300px, calc(100vw - 16px)); min-width: 250px;
- max-height: calc(100vh - var(--navbar-h) - 22px);
- max-height: calc(100dvh - var(--navbar-h) - 22px);
- overflow-y: auto; overscroll-behavior: contain;
- border-radius: 16px;
- padding-bottom: env(safe-area-inset-bottom, 0px);
- }
- .user-dropdown .ud-group a { padding: 10px 11px; font-size: 0.9rem; }
- .ud-head { padding: 14px 14px 12px; }
- /* Nav drawer tighter */
- /* Only the outer inset tightens on a very small screen; row size is set with the panel. */
- .nav-links.show { right: 6px; max-width: calc(100vw - 12px); }
+
+
.hero h1 { font-size: 1.6rem; }
.hero h1 { font-size: 1.6rem; }
.hero { padding-left: 16px; padding-right: 16px; }
.hero { padding-left: 16px; padding-right: 16px; }
@@ -5319,13 +5005,8 @@ textarea.form-input { resize: vertical; min-height: 60px; }
@@ -5319,13 +5005,8 @@ textarea.form-input { resize: vertical; min-height: 60px; }
.nav-brand { gap: 5px; }
.nav-brand { gap: 5px; }
.nav-brand i { font-size: 1rem; }
.nav-brand i { font-size: 1rem; }
.brand-text { font-size: 0.88rem; }
.brand-text { font-size: 0.88rem; }
- .nav-hamburger { padding: 6px; font-size: 1.15rem; }
.nav-balance, .nav-coins { padding: 2px 5px; font-size: 0.68rem; }
.nav-balance, .nav-coins { padding: 2px 5px; font-size: 0.68rem; }
- .nav-avatar { width: 26px; height: 26px; font-size: 0.68rem; }
- /* Sign in button — icon only on tiny screens */
- #nav-auth-anon .btn span { display: none; }
- #nav-auth-anon .btn { padding: 6px 10px; font-size: 0.82rem; gap: 0; }
.stream-card { border-radius: 16px; }
.stream-card { border-radius: 16px; }
.modal { width: 95%; border-radius: var(--radius); }
.modal { width: 95%; border-radius: var(--radius); }
@@ -5406,9 +5087,6 @@ textarea.form-input { resize: vertical; min-height: 60px; }
@@ -5406,9 +5087,6 @@ textarea.form-input { resize: vertical; min-height: 60px; }
.brand-text { font-size: 0.82rem; }
.brand-text { font-size: 0.82rem; }
/* Hide currency on tiny screens to save space */
/* Hide currency on tiny screens to save space */
.nav-balance, .nav-coins { display: none; }
.nav-balance, .nav-coins { display: none; }
- .nav-avatar { width: 24px; height: 24px; font-size: 0.65rem; }
- .nav-hamburger { padding: 5px; font-size: 1.1rem; }
- #nav-auth-anon .btn { padding: 5px 8px; font-size: 0.78rem; }
.hero h1 { font-size: 1.3rem; }
.hero h1 { font-size: 1.3rem; }
.hero p { font-size: 0.85rem; }
.hero p { font-size: 0.85rem; }
@@ -6221,9 +5899,6 @@ body.theme-light .btn-outline {
@@ -6221,9 +5899,6 @@ body.theme-light .btn-outline {
body.theme-light .btn-outline:hover {
body.theme-light .btn-outline:hover {
background: var(--bg-hover);
background: var(--bg-hover);
}
}
-body.theme-light .user-dropdown {
- border: 1px solid var(--border);
-}
/* ── Page Header ────────────────────────────────────────────── */
/* ── Page Header ────────────────────────────────────────────── */
.page-header {
.page-header {
@@ -11479,7 +11154,6 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
@@ -11479,7 +11154,6 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
.nav-brand .brand-text, .nav-brand > span:not(.brand-mark) { display: none; }
.nav-brand .brand-text, .nav-brand > span:not(.brand-mark) { display: none; }
.nav-right { gap: 6px; min-width: 0; }
.nav-right { gap: 6px; min-width: 0; }
.nav-coins, .nav-balance { max-width: 88px; overflow: hidden; }
.nav-coins, .nav-balance { max-width: 88px; overflow: hidden; }
- .nav-avatar-wrap .nav-username { display: none; }
}
}
/* ── The stat board's backdrop blur ──────────────────────────────────────────
/* ── The stat board's backdrop blur ──────────────────────────────────────────
@@ -11503,7 +11177,6 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
@@ -11503,7 +11177,6 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
to its own bell rather than floating over the row, and the username truncates instead of
to its own bell rather than floating over the row, and the username truncates instead of
pushing. */
pushing. */
.nav-right { gap: clamp(5px, 1.2vw, 10px); align-items: center; }
.nav-right { gap: clamp(5px, 1.2vw, 10px); align-items: center; }
-#nav-auth-user { gap: clamp(5px, 1.2vw, 9px); align-items: center; }
.nav-right .nav-coins,
.nav-right .nav-coins,
.nav-right .nav-balance {
.nav-right .nav-balance {
height: 32px; display: inline-flex; align-items: center; gap: 5px;
height: 32px; display: inline-flex; align-items: center; gap: 5px;
@@ -11512,61 +11185,14 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
@@ -11512,61 +11185,14 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
}
}
#openvibe-bell-mount { height: 32px; display: inline-flex; align-items: center; position: relative; }
#openvibe-bell-mount { height: 32px; display: inline-flex; align-items: center; position: relative; }
#openvibe-bell-mount > * { position: relative; }
#openvibe-bell-mount > * { position: relative; }
-.nav-avatar-wrap {
- height: 34px; padding: 0 6px 0 3px; gap: 7px; border-radius: 999px;
- max-width: min(40vw, 190px); min-width: 0;
-}
-.nav-avatar { width: 28px; height: 28px; font-size: 0.78rem; flex: none; }
-#nav-username {
- font-size: clamp(0.75rem, 1.8vw, 0.9rem); font-weight: 650;
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
-}
-/* Size only — whether it shows is decided by the ≤820px block (it was display:inline-flex here,
- which put a menu button on every desktop navbar next to the full link row). */
-.nav-hamburger { height: 34px; width: 34px; align-items: center; justify-content: center; flex: none; }
-
-/* The menu itself: anchored to the avatar, never wider than the screen, and scrollable rather
- than running off the bottom on a short screen or a landscape phone. */
-.user-dropdown {
- top: calc(100% + 7px); right: 0;
- /* Wide enough for the identity header's name and both wallet chips on one line. */
- min-width: 262px; max-width: min(312px, calc(100vw - 20px));
- max-height: calc(100vh - var(--navbar-h) - 20px); overflow-y: auto;
- border-radius: 16px; padding: 0;
- box-shadow: 0 22px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.05);
- overscroll-behavior: contain;
-}
-.user-dropdown a {
- border-radius: 9px; padding: 9px 11px; gap: 11px;
- font-size: 0.86rem; line-height: 1.25;
-}
-.user-dropdown a > i:first-child { width: 17px; text-align: center; flex: none; opacity: 0.85; }
-.user-dropdown hr { margin: 5px 7px; border: 0; border-top: 1px solid var(--border); }
-@media (max-width: 560px) {
- /* Anchor to the bar rather than the avatar, so a wide menu can't hang off the right edge. */
- .user-dropdown { position: fixed; top: calc(var(--navbar-h) + 6px); right: 8px; left: auto; min-width: 0; width: min(290px, calc(100vw - 16px)); }
-}
-@media (max-height: 520px) and (orientation: landscape) {
- .user-dropdown { max-height: calc(100vh - var(--navbar-h) - 12px); }
- .user-dropdown a { padding: 7px 10px; font-size: 0.82rem; }
-}
-/* Signed-out: the sign-in button should match the cluster's height, not tower over it. */
-#nav-auth-anon .btn { height: 34px; padding: 0 clamp(10px, 2vw, 15px); font-size: clamp(0.76rem, 1.8vw, 0.88rem); border-radius: 11px; }
+
/* Belt and braces for the About section's cards: whatever happens with animations or deferred
/* Belt and braces for the About section's cards: whatever happens with animations or deferred
rendering, a card is never allowed to end up as an empty outline. */
rendering, a card is never allowed to end up as an empty outline. */
.home-cta-feature > i, .home-cta-feature > strong, .home-cta-feature > span { opacity: 1; }
.home-cta-feature > i, .home-cta-feature > strong, .home-cta-feature > span { opacity: 1; }
-/* The user menu at tablet widths: anchored to the avatar it could still reach past the right
- edge, so the fixed anchoring now covers everything below 900px rather than below 560. */
-@media (max-width: 900px) {
- .user-dropdown {
- position: fixed; top: calc(var(--navbar-h) + 6px); right: 8px; left: auto;
- min-width: 0; width: min(290px, calc(100vw - 16px));
- }
-}
/* ── About section sub-headings ──────────────────────────────────────────────
/* ── About section sub-headings ──────────────────────────────────────────────
Three sub-sections inside one long panel, all introduced by the same small grey line of caps —
Three sub-sections inside one long panel, all introduced by the same small grey line of caps —
@@ -11684,52 +11310,8 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
@@ -11684,52 +11310,8 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
-/* ═══════════════════════════════════════════════════════════════
- The account menu.
-
- The nav menu beside it is a list of places, so it is a compact column of equal rows. This one
- answers a different question — who am I signed in as, and what am I carrying — so it opens with
- that and groups everything under it by what the row is for. Same glass family as the nav panel
- so they belong to the same navbar, but its own shape: a tinted identity header, labelled
- sections instead of bare rules, and a logout row that reads as the way out.
- ═══════════════════════════════════════════════════════════════ */
-.user-dropdown {
- padding: 0;
- border: 1px solid color-mix(in srgb, var(--accent) 26%, var(--border));
- background:
- linear-gradient(180deg,
- color-mix(in srgb, var(--accent) 10%, var(--bg-card)) 0,
- var(--bg-secondary) 92px),
- var(--bg-secondary);
- box-shadow:
- 0 24px 60px rgba(0, 0, 0, 0.5),
- 0 3px 12px rgba(0, 0, 0, 0.3),
- inset 0 1px 0 rgba(255, 255, 255, 0.07);
- backdrop-filter: blur(22px) saturate(1.6);
- -webkit-backdrop-filter: blur(22px) saturate(1.6);
- transform-origin: top right;
-}
-.user-dropdown.show { animation: udIn 0.24s cubic-bezier(.2, 1, .3, 1) both; }
@keyframes udIn { from { opacity: 0; transform: translateY(-8px) scale(0.95); } to { opacity: 1; transform: none; } }
@keyframes udIn { from { opacity: 0; transform: translateY(-8px) scale(0.95); } to { opacity: 1; transform: none; } }
-/* ── Identity header ── */
-.ud-head {
- display: grid; grid-template-columns: auto 1fr; gap: 3px 11px;
- align-items: center; padding: 13px 14px 11px;
- border-bottom: 1px solid color-mix(in srgb, var(--accent) 16%, var(--border));
-}
-.ud-avatar {
- grid-row: span 2; width: 40px; height: 40px; border-radius: 50%;
- display: grid; place-items: center; overflow: hidden;
- font-weight: 900; font-size: 1rem; color: #fff;
- background: var(--accent);
- border: 2px solid color-mix(in srgb, var(--accent) 55%, transparent);
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
-}
-.ud-avatar img { width: 100%; height: 100%; object-fit: cover; }
-.ud-who { min-width: 0; display: grid; gap: 1px; }
-.ud-who b { font-size: 0.94rem; font-weight: 850; letter-spacing: -0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.ud-who small { font-size: 0.73rem; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ud-wallet { grid-column: 2; display: flex; gap: 6px; margin-top: 4px; flex-wrap: wrap; }
.ud-wallet { grid-column: 2; display: flex; gap: 6px; margin-top: 4px; flex-wrap: wrap; }
.ud-wallet span {
.ud-wallet span {
display: inline-flex; align-items: center; gap: 5px;
display: inline-flex; align-items: center; gap: 5px;
@@ -11737,56 +11319,18 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
@@ -11737,56 +11319,18 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
font-size: 0.72rem; font-weight: 800; font-variant-numeric: tabular-nums;
font-size: 0.72rem; font-weight: 800; font-variant-numeric: tabular-nums;
border: 1px solid transparent;
border: 1px solid transparent;
}
}
-.ud-coin { color: #f6c945; background: color-mix(in srgb, #f6c945 13%, transparent); border-color: color-mix(in srgb, #f6c945 30%, transparent) !important; }
-.ud-vibe { color: #4ade80; background: color-mix(in srgb, #4ade80 13%, transparent); border-color: color-mix(in srgb, #4ade80 30%, transparent) !important; }
.ud-wallet i { font-size: 0.86em; opacity: 0.9; }
.ud-wallet i { font-size: 0.86em; opacity: 0.9; }
-/* ── Groups ── */
-.ud-group { padding: 6px 5px; }
-.ud-group + .ud-group { border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); }
.ud-label {
.ud-label {
display: block; padding: 3px 9px 5px;
display: block; padding: 3px 9px 5px;
font-size: 0.6rem; font-weight: 800; letter-spacing: 0.13em; text-transform: uppercase;
font-size: 0.6rem; font-weight: 800; letter-spacing: 0.13em; text-transform: uppercase;
color: var(--text-muted);
color: var(--text-muted);
}
}
-.user-dropdown .ud-group a {
- border-radius: 9px; padding: 8px 9px; gap: 11px;
- font-size: 0.86rem; font-weight: 600;
- border: 1px solid transparent;
- transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.12s ease;
-}
-.user-dropdown .ud-group a > i:first-child {
- width: 18px; flex: none; text-align: center; opacity: 0.8;
- transition: color 0.15s ease, opacity 0.15s ease;
-}
-.user-dropdown .ud-group a:hover {
- background: color-mix(in srgb, var(--accent) 12%, transparent);
- border-color: color-mix(in srgb, var(--accent) 24%, transparent);
- transform: translateX(2px);
-}
-.user-dropdown .ud-group a:hover > i:first-child { color: var(--accent); opacity: 1; }
-.user-dropdown .ud-group a:active { transform: translateX(2px) scale(0.985); }
-/* Leaving is a different kind of action from going somewhere. */
-.ud-logout { color: var(--danger, #e74c3c) !important; }
-.user-dropdown .ud-group a.ud-logout:hover {
- background: color-mix(in srgb, var(--danger, #e74c3c) 14%, transparent);
- border-color: color-mix(in srgb, var(--danger, #e74c3c) 30%, transparent);
-}
-.user-dropdown .ud-group a.ud-logout:hover > i:first-child { color: var(--danger, #e74c3c); }
/* Rows arrive just behind the panel, capped so a long menu never feels slow. */
/* Rows arrive just behind the panel, capped so a long menu never feels slow. */
@keyframes udRow { from { opacity: 0; transform: translateX(6px); } to { opacity: 1; transform: none; } }
@keyframes udRow { from { opacity: 0; transform: translateX(6px); } to { opacity: 1; transform: none; } }
-.user-dropdown.show .ud-group a { animation: udRow 0.22s ease both; animation-delay: calc(70ms + var(--ud-i, 0) * 18ms); }
-.user-dropdown.show .ud-group:nth-of-type(1) a { --ud-i: 0; }
-.user-dropdown.show .ud-group:nth-of-type(2) a { --ud-i: 1; }
-.user-dropdown.show .ud-group:nth-of-type(3) a { --ud-i: 2; }
-.user-dropdown.show .ud-group:nth-of-type(n+4) a { --ud-i: 3; }
-@media (prefers-reduced-motion: reduce) {
- .user-dropdown.show, .user-dropdown.show .ud-group a { animation: none !important; }
- .user-dropdown .ud-group a:hover { transform: none; }
-}
/* ═══════════════════════════════════════════════════════════════
/* ═══════════════════════════════════════════════════════════════
About section ("Streaming the way it should be") — layout pass.
About section ("Streaming the way it should be") — layout pass.
@@ -11848,31 +11392,6 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
@@ -11848,31 +11392,6 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
.ov-conn-pill .ov-conn-dot { animation: none !important; }
.ov-conn-pill .ov-conn-dot { animation: none !important; }
}
}
-/* ── Dropdowns never leave the screen ─────────────────────────────────────────
- One rule for every navbar panel (user menu, nav dropdowns, mobile nav drawer). 100vh on phones
- includes the strip hidden behind the browser's address bar, which is why the bottom of a long menu
- used to fall off-screen and could not be scrolled to: dvh is the visible height. The cap also
- leaves room for the home indicator, and the panel scrolls inside itself without dragging the page. */
-.user-dropdown, .nav-dropdown-menu, .nav-links.show {
- max-height: calc(100vh - var(--navbar-h) - 16px);
- max-height: calc(100dvh - var(--navbar-h) - 16px - env(safe-area-inset-bottom, 0px));
- overflow-y: auto; overflow-x: hidden;
- overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
- scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--accent) 40%, transparent) transparent;
-}
-.user-dropdown { max-width: calc(100vw - 16px); }
-/* Below tablet width the user menu is a sheet pinned under the bar, as wide as the phone allows. */
-@media (max-width: 900px) {
- .user-dropdown { position: fixed; top: calc(var(--navbar-h) + 6px); right: 8px; left: auto; min-width: 0; width: min(340px, calc(100vw - 16px)); }
- /* The floating chat button steps aside while a navbar panel is open (body.ov-panel-open), so the menu
- needs no dead space at its end to clear it. */
- .user-dropdown > :last-child { padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px)); }
-}
-/* Short screens (landscape phones, split view): tighter rows so more of the menu is visible at once. */
-@media (max-height: 560px) {
- .user-dropdown .ud-group a, .nav-links.show .nav-link { padding-top: 7px; padding-bottom: 7px; font-size: 0.86rem; }
- .user-dropdown .ud-label { padding-top: 8px; padding-bottom: 2px; }
-}
body.ov-panel-open #floating-chat-fab { opacity: 0; pointer-events: none; transform: scale(.85); transition: opacity .15s, transform .15s; }
body.ov-panel-open #floating-chat-fab { opacity: 0; pointer-events: none; transform: scale(.85); transition: opacity .15s, transform .15s; }
@@ -747,48 +747,7 @@ function onAuthChange() {
@@ -747,48 +747,7 @@ function onAuthChange() {
if (currentUser) _enableHandoff(); else if (window.OpenVibeSSO) { try { window.OpenVibeSSO.handoffLinks({ signedIn: false }); } catch { /* */ } }
if (currentUser) _enableHandoff(); else if (window.OpenVibeSSO) { try { window.OpenVibeSSO.handoffLinks({ signedIn: false }); } catch { /* */ } }
// The navbar is the network's shared component; tell it who is here (js/ov-navbar-live.js).
// The navbar is the network's shared component; tell it who is here (js/ov-navbar-live.js).
if (window.LiveNav) window.LiveNav.sync(currentUser || null);
if (window.LiveNav) window.LiveNav.sync(currentUser || null);
- const anon = document.getElementById('nav-auth-anon');
- const user = document.getElementById('nav-auth-user');
- const admin = document.getElementById('nav-admin');
-
- // The nav chrome doesn't exist in every document that loads app.js (e.g. the
- // popout chat window), so guard it — the auth-changed event below must still fire.
- if (anon && user) {
- if (currentUser) {
- anon.style.display = 'none';
- user.style.display = 'flex';
- if (admin) admin.style.display = (currentUser.capabilities?.admin_panel || hasCapability('can_access_staff_console')) ? '' : 'none';
- // Admin Panel link in the user dropdown — admins/owners only (mods can't access it).
- const ddAdmin = document.getElementById('user-dropdown-admin');
- if (ddAdmin) ddAdmin.style.display = (currentUser.role === 'admin' || currentUser.capabilities?.is_owner) ? '' : 'none';
- const navAv = document.getElementById('nav-avatar');
- if (navAv) navAv.innerHTML = _avatarInner(currentUser.avatar_url, currentUser.username);
- const navUn = document.getElementById('nav-username');
- if (navUn) navUn.textContent = currentUser.display_name || currentUser.username;
- // The account menu opens with the same identity the navbar shows, so it does not need
- // a second source of truth — it is filled from here.
- const udAv = document.getElementById('ud-avatar');
- if (udAv) udAv.innerHTML = _avatarInner(currentUser.avatar_url, currentUser.username);
- const udName = document.getElementById('ud-name');
- if (udName) udName.textContent = currentUser.display_name || currentUser.username;
- const udHandle = document.getElementById('ud-handle');
- if (udHandle) udHandle.textContent = '@' + (currentUser.username || '');
- loadBalance();
- } else {
- anon.style.display = '';
- user.style.display = 'none';
- if (admin) admin.style.display = 'none';
- }
- } else if (currentUser) {
- loadBalance();
- }
- // Go Live nav is always visible (logged out → prompts sign-up); its Dashboard
- // sub-menu + caret only apply when logged in.
- const goliveMenu = document.getElementById('nav-golive-menu');
- if (goliveMenu) goliveMenu.style.display = currentUser ? '' : 'none';
- const goliveCaret = document.querySelector('#nav-golive-dropdown .nav-dd-caret');
- if (goliveCaret) goliveCaret.style.display = currentUser ? '' : 'none';
- document.getElementById('user-dropdown')?.classList.remove('show');
+ if (currentUser) loadBalance();
// Sync canvas auth state if canvas page is loaded
// Sync canvas auth state if canvas page is loaded
if (typeof syncCanvasAuthState === 'function') syncCanvasAuthState();
if (typeof syncCanvasAuthState === 'function') syncCanvasAuthState();
@@ -843,25 +802,6 @@ function openChannelRewards() {
@@ -843,25 +802,6 @@ function openChannelRewards() {
if (typeof toggleRewardsPanel === 'function') toggleRewardsPanel();
if (typeof toggleRewardsPanel === 'function') toggleRewardsPanel();
}
}
-function toggleUserMenu() {
- const dd = document.getElementById('user-dropdown');
- const open = dd.classList.toggle('show');
- if (open && !window.OpenVibePanels) closeMobileNav(); // the shared controller does this once it has loaded
-}
-
-function closeMobileNav() {
- document.querySelector('.nav-links')?.classList.remove('show');
- document.querySelector('.nav-hamburger')?.classList.remove('open');
-}
-
-function toggleMobileNav() {
- const navLinks = document.querySelector('.nav-links');
- const hamburger = document.querySelector('.nav-hamburger');
- navLinks.classList.toggle('show');
- hamburger?.classList.toggle('open', navLinks.classList.contains('show'));
- if (navLinks.classList.contains('show') && !window.OpenVibePanels) document.getElementById('user-dropdown')?.classList.remove('show');
-}
-
function isModifiedLinkClick(event) {
function isModifiedLinkClick(event) {
return !!(event && (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey));
return !!(event && (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey));
}
}
@@ -920,13 +860,6 @@ function handleLinkClick(event, urlPath, replace = false) {
@@ -920,13 +860,6 @@ function handleLinkClick(event, urlPath, replace = false) {
return false;
return false;
}
}
-function handleDropdownLinkClick(event, dropdownId) {
- if (isModifiedLinkClick(event)) return true;
- event?.preventDefault?.();
- toggleNavDropdown(dropdownId);
- return false;
-}
-
// Go Live nav button — always visible. Logged in → the broadcast workspace;
// Go Live nav button — always visible. Logged in → the broadcast workspace;
// logged out → prompt to sign up ("begin your streaming journey").
// logged out → prompt to sign up ("begin your streaming journey").
function goLiveNav(event) {
function goLiveNav(event) {
@@ -940,7 +873,6 @@ function goLiveNav(event) {
@@ -940,7 +873,6 @@ function goLiveNav(event) {
function dashNav(event) {
function dashNav(event) {
if (isModifiedLinkClick(event)) return true;
if (isModifiedLinkClick(event)) return true;
event?.preventDefault?.();
event?.preventDefault?.();
- if (typeof closeNavDropdowns === 'function') closeNavDropdowns();
if (currentUser) navigate('/dashboard');
if (currentUser) navigate('/dashboard');
else if (typeof showModal === 'function') showModal('register');
else if (typeof showModal === 'function') showModal('register');
return false;
return false;
@@ -1015,7 +947,6 @@ function teardownRoute(nextPath) {
@@ -1015,7 +947,6 @@ function teardownRoute(nextPath) {
}
}
function navigate(urlPath, replace = false) {
function navigate(urlPath, replace = false) {
- closeMobileNav();
teardownRoute(urlPath);
teardownRoute(urlPath);
// Normalize path
// Normalize path
@@ -1125,7 +1056,6 @@ function routeFromURL() {
@@ -1125,7 +1056,6 @@ function routeFromURL() {
// Hide all pages
// Hide all pages
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
- document.querySelector('.nav-links')?.classList.remove('show');
window.scrollTo(0, 0);
window.scrollTo(0, 0);
@@ -1420,115 +1350,6 @@ let _navScrollRaf = 0;
@@ -1420,115 +1350,6 @@ let _navScrollRaf = 0;
window.addEventListener('scroll', () => { if (_navScrollRaf) return; _navScrollRaf = requestAnimationFrame(() => { _navScrollRaf = 0; updateNavHeroTransparency(); }); }, { passive: true });
window.addEventListener('scroll', () => { if (_navScrollRaf) return; _navScrollRaf = requestAnimationFrame(() => { _navScrollRaf = 0; updateNavHeroTransparency(); }); }, { passive: true });
window.addEventListener('resize', updateNavHeroTransparency, { passive: true });
window.addEventListener('resize', updateNavHeroTransparency, { passive: true });
-/* ── Nav Dropdown Helpers ──────────────────────────────────────── */
-function toggleNavDropdown(id) {
- const dd = document.getElementById(id);
- if (!dd) return;
- const wasOpen = dd.classList.contains('open');
- closeNavDropdowns();
- if (!wasOpen) dd.classList.add('open');
-}
-
-function closeNavDropdowns() {
- document.querySelectorAll('.nav-dropdown.open').forEach(d => d.classList.remove('open'));
-}
-
-// Close nav dropdowns when clicking outside
-document.addEventListener('click', (e) => {
- if (!e.target.closest('.nav-dropdown')) closeNavDropdowns();
-});
-
-/* ── Nav Scroll Overflow Detection ─────────────────────────────── */
-function checkNavOverflow() {
- const nl = document.querySelector('.nav-links');
- if (!nl) return;
- const hasOverflow = nl.scrollWidth > nl.clientWidth + 2;
- const left = document.getElementById('nav-scroll-left');
- const right = document.getElementById('nav-scroll-right');
- const atStart = nl.scrollLeft <= 1;
- const atEnd = nl.scrollLeft >= nl.scrollWidth - nl.clientWidth - 1;
- if (left) left.classList.toggle('visible', hasOverflow && !atStart);
- if (right) right.classList.toggle('visible', hasOverflow && !atEnd);
-}
-
-function scrollNavLinks(dir) {
- const nl = document.querySelector('.nav-links');
- if (!nl) return;
- nl.scrollBy({ left: dir * 160, behavior: 'smooth' });
- // Poll until scroll settles (smooth scroll can take 300-600ms)
- let checks = 0;
- let lastPos = nl.scrollLeft;
- const poll = setInterval(() => {
- checkNavOverflow();
- if (nl.scrollLeft === lastPos || ++checks > 12) clearInterval(poll);
- lastPos = nl.scrollLeft;
- }, 60);
-}
-
-// Position fixed dropdown menus below their triggers
-function positionNavDropdownMenu(dropdown) {
- const menu = dropdown?.querySelector('.nav-dropdown-menu');
- const trigger = dropdown?.querySelector('.nav-link');
- if (!menu || !trigger) return;
- if (getComputedStyle(menu).position !== 'fixed') { menu.style.top = menu.style.left = menu.style.maxHeight = ''; return; } // inside the mobile drawer it flows inline
- const rect = trigger.getBoundingClientRect();
- const vw = document.documentElement.clientWidth, vh = window.visualViewport ? window.visualViewport.height : window.innerHeight;
- const width = menu.offsetWidth || 200, pad = 8;
- // Keep the whole menu on screen: slide left when it would cross the right edge, cap its height to the space below.
- menu.style.left = `${Math.max(pad, Math.min(rect.left, vw - width - pad))}px`;
- menu.style.top = `${rect.bottom}px`;
- menu.style.maxHeight = `${Math.max(120, vh - rect.bottom - pad)}px`;
-}
-
-// Live's menus are coordinated by the network's shared panel controller (openvibe-shared/panels.js, the same
-// one the shared navbar uses on every other site): one panel open at a time, Escape and rotation close them, and
-// <body class="ov-panel-open"> lets the floating chat button step aside. Live keeps its own markup and toggles.
-{
- const register = () => {
- const P = window.OpenVibePanels; if (!P) return;
- const dd = document.getElementById('user-dropdown'); if (dd) P.register({ el: dd, openClass: 'show', id: 'user-menu' });
- const nl = document.querySelector('.nav-links'); if (nl) P.register({ el: nl, openClass: 'show', id: 'nav-drawer', close: () => closeMobileNav() });
- document.querySelectorAll('.nav-dropdown').forEach((d, i) => P.register({ el: d, openClass: 'open', id: 'nav-dropdown-' + i }));
- };
- const load = () => {
- if (window.OpenVibePanels) return register();
- const sc = document.createElement('script'); sc.src = '/shared/panels.js'; sc.async = true; sc.onload = register; document.head.appendChild(sc);
- };
- if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', load); else load();
-}
-
-/** Close every navbar panel (used when the viewport changes under an open menu). */
-function closeNavPanels() {
- document.getElementById('user-dropdown')?.classList.remove('show');
- closeNavDropdowns();
- closeMobileNav();
-}
-// A rotated phone, a resized window or an opened keyboard invalidates a fixed menu's position: reposition
-// what is open, and close panels on orientation change. Escape closes them too.
-{
- let raf = 0;
- const reflow = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => document.querySelectorAll('.nav-dropdown.open, .nav-dropdown:hover').forEach(positionNavDropdownMenu)); };
- window.addEventListener('resize', reflow, { passive: true });
- if (window.visualViewport) window.visualViewport.addEventListener('resize', reflow, { passive: true });
-}
-
-// Observe hover/open to position dropdown menus
-document.querySelectorAll('.nav-dropdown').forEach(dd => {
- dd.addEventListener('mouseenter', () => positionNavDropdownMenu(dd));
- dd.addEventListener('click', () => positionNavDropdownMenu(dd));
-});
-
-// Listen for scroll and resize to update nav overflow arrows
-{
- const nl = document.querySelector('.nav-links');
- if (nl) {
- nl.addEventListener('scroll', checkNavOverflow, { passive: true });
- new ResizeObserver(checkNavOverflow).observe(nl);
- }
- // Also check on login (new nav items may appear)
- window.addEventListener('load', () => setTimeout(checkNavOverflow, 500));
-}
-
/* ── Channel Page (/:username) ────────────────────────────────── */
/* ── Channel Page (/:username) ────────────────────────────────── */
let currentChannelUsername = null; // is the current channel's user owner-rank?
let currentChannelUsername = null; // is the current channel's user owner-rank?
let _activeChannelUserId = null;
let _activeChannelUserId = null;
@@ -2785,14 +2606,3 @@ window.addEventListener('popstate', () => {
@@ -2785,14 +2606,3 @@ window.addEventListener('popstate', () => {
// If auth hasn't loaded yet, DOMContentLoaded handler will call routeFromURL()
// If auth hasn't loaded yet, DOMContentLoaded handler will call routeFromURL()
});
});
-// Intercept link clicks to use SPA navigation
-document.addEventListener('click', (e) => {
- // Close user dropdown
- if (!e.target.closest('.nav-avatar-wrap') && !e.target.closest('.user-dropdown')) {
- document.getElementById('user-dropdown')?.classList.remove('show');
- }
-
- if (!e.target.closest('.nav-links') && !e.target.closest('.nav-hamburger')) {
- closeMobileNav();
- }
-});
deleted file mode 100644
deleted file mode 100644
@@ -1,11 +0,0 @@
@@ -1,11 +0,0 @@
-'use strict';
-// Regression: a desktop-dropdown "click outside" handler must never close the mobile drawer, or the hamburger
-// opens and closes it in the same click (shipped once, 2026-09-18).
-const assert = require('assert');
-const fs = require('fs'); const path = require('path');
-const src = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'app.js'), 'utf8');
-const body = (name) => { const i = src.indexOf(`function ${name}(`); assert.ok(i >= 0, `${name} exists`); let d = 0, j = src.indexOf('{', i); const s = j; for (; j < src.length; j++) { if (src[j] === '{') d++; else if (src[j] === '}' && --d === 0) break; } return src.slice(s, j + 1); };
-assert.ok(!/closeMobileNav\s*\(/.test(body('closeNavDropdowns')), 'closeNavDropdowns leaves the mobile drawer alone');
-assert.ok(/closeMobileNav\s*\(/.test(body('closeNavPanels')) && /closeNavDropdowns\s*\(/.test(body('closeNavPanels')), 'closeNavPanels closes everything');
-assert.ok(/classList\.toggle\('show'\)/.test(body('toggleMobileNav')), 'hamburger toggles the drawer');
-console.log('nav panels: all checks passed');