v0.166.0: mobile nav off-canvas drawer + sidebar cleanup + versioned logo/favicon URLs

Mobile nav was broken — the <=768px block predated the v0.146.0 accordion and
flattened .nav-links into a horizontal overflow-x strip, clipping the accordion's
nested sub-lists. Replaced with a sticky top bar + off-canvas left drawer that
reuses the vertical sidebar (accordion untouched), plus a no-js static fallback.
Removed the sidebar customer-name span (kept on login). Added ?v={{.Version}}
cache-bust to logo/favicon URLs (Cloudflare 4h edge-cache; 0.126.1 failure mode).

Part 4 (outlined-logo constant swap) gated out per §3a: live felhom.eu main still
serves a logo.svg with live <text>/font-family; constants unchanged.

5 new tests via the real layout/CSS render; nav_accordion invariants unchanged.
This commit is contained in:
2026-07-24 13:26:22 +02:00
parent 1fd070615d
commit bf44216e79
8 changed files with 361 additions and 19 deletions
+36 -7
View File
@@ -1,17 +1,20 @@
{{define "layout_start"}}
<!DOCTYPE html>
<html lang="hu">
<html lang="hu" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}} — Felhom.eu</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
{{/* ?v= cache-bust: Cloudflare edge-caches /static/* for 4h — an unversioned URL keeps
serving the PREVIOUS release's CSS after a deploy (0.126.1 live QA hit this). The
controller version busts it automatically on every release. */}}
serving the PREVIOUS release's asset after a deploy (0.126.1 live QA hit this on CSS;
the logo/favicon share the failure mode). The controller version busts it every release. */}}
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v={{.Version}}">
<link rel="stylesheet" href="/static/style.css?v={{.Version}}">
<meta name="csrf-token" content="{{.CSRFToken}}">
<script>
// Progressive-enhancement hook (v0.166.0): flip no-js → js before any CSS-dependent paint so the
// mobile off-canvas drawer engages only when the drawer JS below can actually open it.
document.documentElement.className=document.documentElement.className.replace('no-js','js');
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
// Inline two-step confirm (drill F-11): the trigger swaps IN PLACE to "<question> Igen/Mégse".
// Never native confirm() — it is an OS-modal that blocks browser automation — and no overlay:
@@ -62,10 +65,16 @@
</head>
<body>
{{template "icon_sprite"}}
<nav class="sidebar">
<header class="mobile-topbar">
<a href="/" class="mobile-topbar-logo"><img src="/static/felhom-logo.svg?v={{.Version}}" alt="Felhom.eu"></a>
<button type="button" class="nav-burger" aria-expanded="false" aria-controls="sidebar" aria-label="Menü">
<svg class="ico"><use href="#i-menu"/></svg>
</button>
</header>
<div class="nav-backdrop" hidden></div>
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<img src="/static/felhom-logo.svg" alt="Felhom.eu" class="sidebar-logo">
<span class="customer-name">{{.CustomerName}}</span>
<img src="/static/felhom-logo.svg?v={{.Version}}" alt="Felhom.eu" class="sidebar-logo">
</div>
<ul class="nav-links">
<li><a href="/launcher" class="{{if eq .Page "launcher"}}active{{end}}"><svg class="ico"><use href="#i-rocket"/></svg>Indítópult</a></li>
@@ -139,6 +148,26 @@
{{define "layout_end"}}
</main>
<script>
// Mobile off-canvas drawer (v0.166.0). Reuses the existing sidebar as the drawer; the accordion
// handler in the head script is untouched and keeps working inside it. Only wires up on ≤768px
// chrome — on desktop the burger/backdrop are display:none so these listeners never fire.
(function(){
var burger=document.querySelector('.nav-burger');
var sidebar=document.getElementById('sidebar');
var backdrop=document.querySelector('.nav-backdrop');
if(!burger||!sidebar||!backdrop)return;
function setOpen(open){
sidebar.classList.toggle('is-open',open);
document.body.classList.toggle('nav-open',open);
backdrop.hidden=!open;
burger.setAttribute('aria-expanded',open?'true':'false');
}
burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));});
backdrop.addEventListener('click',function(){setOpen(false);});
document.addEventListener('keydown',function(e){
if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false);
});
})();
document.addEventListener('click', function(e) {
if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return;
var card = e.target.closest('[data-href]');