[PATCH v1 1/2] add mobile drawer navigation with a compact sticky top bar

Rito Rhymes <[email protected]> Mon, 3 Aug 2026 11:35:01 -0400
Newsgroups org.kernel.linux.tools
Message-ID <[email protected]>
On small screens the navigation wraps into a dense block of links that
is hard to scan and gives closely spaced tap targets, the banner
consumes significant vertical space, and because content reflows into a
single narrow column, returning to the navigation at the top of the page
is a much longer scroll than on desktop.

At the mobile breakpoint, hide the desktop banner/navigation and replace
it with a compact sticky top bar carrying the same site title and logo.
Add a hamburger control that opens a drawer menu with overlay, so
navigation is reachable at any scroll position without returning to the
top. It is driven by a checkbox/label toggle, so no JavaScript is
required. All rules are scoped to the 848px media query; the desktop
layout is unchanged.

The checkbox is the only keyboard-operable control, so it is hidden with
clip-path rather than display:none to keep it focusable, and Space
toggles the drawer. The labels are pointer affordances only, marked
aria-hidden and given no tabindex: a label does not respond to Enter or
Space, so making it focusable would present a control that cannot be
operated. Focus rings are drawn on whichever affordance is visible,
moving to the close icon once the open drawer paints over the hamburger.
The closed drawer is set visibility:hidden so its off-screen links stay
out of the tab order.

Signed-off-by: Rito Rhymes <[email protected]>
Assisted-by: LLM [codegen, review]
---
 korgi/static/css/main.css | 164 ++++++++++++++++++++++++++++++++++++++
 korgi/templates/base.html |  30 +++++++
 2 files changed, 194 insertions(+)

diff --git a/korgi/static/css/main.css b/korgi/static/css/main.css
index 95b6326..60538d9 100644
--- a/korgi/static/css/main.css
+++ b/korgi/static/css/main.css
@@ -339,7 +339,171 @@ dt {
     padding-bottom: 22px;
 }
 
+/* Mobile nav: hidden on desktop. The toggle, overlay and drawer all
+   live inside #mobile-banner, so hiding it hides the whole control. */
+#mobile-banner { display: none; }
+
 @media screen and (max-width: 848px) {
+    #banner {
+        display: none;
+    }
+    /* The checkbox is the actual control, so it stays reachable by
+       keyboard; only its rendering is suppressed. The labels are
+       pointer affordances and are hidden from assistive tech. */
+    .mobile-nav-checkbox {
+        display: block;
+        position: absolute;
+        width: 1px;
+        height: 1px;
+        margin: 0;
+        padding: 0;
+        border: 0;
+        overflow: hidden;
+        clip-path: inset(50%);
+    }
+    .mobile-nav-checkbox:focus-visible ~ .hamburger-btn,
+    .mobile-nav-checkbox:focus-visible:checked ~ #mobile-drawer .close-btn svg {
+        outline: 2px solid #3357D6;
+        outline-offset: 2px;
+        border-radius: 0.2em;
+    }
+    /* While open the drawer paints over the hamburger, so the ring
+       would be hidden; show it on the close control instead. */
+    .mobile-nav-checkbox:focus-visible:checked ~ .hamburger-btn {
+        outline: none;
+    }
+    #mobile-banner {
+        display: flex;
+        align-items: center;
+        position: sticky;
+        top: 0;
+        z-index: 1000;
+        background: #FFFFFF;
+        padding: 0.5em 1em;
+        border-bottom: thin solid #CCC8B8;
+        box-shadow: 0 0.1em 0.3em #CCC8B8;
+    }
+    #mobile-tux-gear {
+        width: 2.5em;
+        height: 3em;
+        background-image: url('../images/logos/tux.png');
+        background-position: center;
+        background-repeat: no-repeat;
+        background-size: contain;
+        flex-shrink: 0;
+    }
+    #mobile-banner .mobile-site-title {
+        flex: 1;
+        margin: 0 0.5em;
+        font-family: oswald,helvetica,sans-serif;
+        font-weight: bold;
+        font-size: 1.4em;
+        line-height: 1.1;
+        text-shadow: 2px 2px 0 #F8F4EE;
+    }
+    #mobile-banner .mobile-site-title > a {
+        color: #2C1D00;
+        transition: none;
+    }
+    .hamburger-btn {
+        cursor: pointer;
+        flex-shrink: 0;
+        padding: 0.25em;
+    }
+    .hamburger-btn svg {
+        display: block;
+    }
+    #mobile-drawer {
+        display: block;
+        position: fixed;
+        top: 0;
+        right: 0;
+        width: 200px;
+        max-width: 75vw;
+        height: 100%;
+        background: #FFFFFF;
+        z-index: 1002;
+        transform: translateX(100%);
+        /* visibility keeps the off-screen links out of the tab order
+           while closed; it is delayed so the slide-out still animates. */
+        visibility: hidden;
+        transition: transform 0.3s ease, visibility 0s linear 0.3s;
+        overflow-y: auto;
+        box-shadow: -2px 0 8px rgba(0, 0, 0, 0.2);
+    }
+    #mobile-drawer ul {
+        list-style: none;
+        padding: 1em;
+        margin: 0;
+    }
+    #mobile-drawer li {
+        padding: 0.75em 0;
+        border-bottom: thin solid #F0EDE3;
+    }
+    #mobile-drawer li:last-child {
+        border-bottom: none;
+    }
+    #mobile-drawer li > a {
+        font-family: oswald, helvetica, sans-serif;
+        font-size: 1.1em;
+        color: #B39223;
+    }
+    #mobile-drawer li > a:hover,
+    #mobile-drawer li > a:active {
+        color: #4C3D00;
+    }
+    #drawer-tux {
+        width: 4em;
+        height: 5em;
+        margin: 1.5em auto 1em;
+        background-image: url('../images/logos/tux.png');
+        background-position: center;
+        background-repeat: no-repeat;
+        background-size: contain;
+    }
+    .close-btn {
+        display: block;
+        cursor: pointer;
+        text-align: right;
+        padding: 1em;
+    }
+    .close-btn svg {
+        display: inline-block;
+    }
+    .nav-overlay {
+        display: block;
+        position: fixed;
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        background: rgba(0, 0, 0, 0.5);
+        z-index: 1001;
+        opacity: 0;
+        visibility: hidden;
+        transition: opacity 0.3s ease, visibility 0s linear 0.3s;
+    }
+    #mobile-nav-toggle:checked ~ #mobile-drawer {
+        transform: translateX(0);
+        visibility: visible;
+        transition: transform 0.3s ease, visibility 0s;
+    }
+    #mobile-nav-toggle:checked ~ .nav-overlay {
+        opacity: 1;
+        visibility: visible;
+        transition: opacity 0.3s ease, visibility 0s;
+    }
+    /* Drop the slide and fade, but keep the visibility swap so the
+       closed drawer stays out of the tab order. */
+    @media (prefers-reduced-motion: reduce) {
+        #mobile-drawer,
+        #mobile-nav-toggle:checked ~ #mobile-drawer,
+        .nav-overlay,
+        #mobile-nav-toggle:checked ~ .nav-overlay {
+            transition: none;
+        }
+    }
+
     #banner {
         width: auto;
         margin: 0;
diff --git a/korgi/templates/base.html b/korgi/templates/base.html
index ce1d5fb..76448bc 100644
--- a/korgi/templates/base.html
+++ b/korgi/templates/base.html
@@ -46,6 +46,36 @@
                 {% endfor %}
                 </ul></nav>
         </header><!-- /#banner -->
+        <header id="mobile-banner" class="body">
+                <div id="mobile-tux-gear"></div>
+                <h1 class="mobile-site-title"><a href="{{ SITEURL }}/">{{ SITENAME }}</a></h1>
+                <input type="checkbox" id="mobile-nav-toggle" class="mobile-nav-checkbox" aria-label="Navigation menu" />
+                <label for="mobile-nav-toggle" class="hamburger-btn" aria-hidden="true">
+                    <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#2C1D00" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
+                </label>
+
+                <label for="mobile-nav-toggle" class="nav-overlay" aria-hidden="true"></label>
+
+                <nav id="mobile-drawer" role="navigation" aria-label="Mobile navigation">
+                <label for="mobile-nav-toggle" class="close-btn" aria-hidden="true">
+                    <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#2C1D00" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
+                </label>
+                <ul>
+                {% for title, link in MENUITEMS %}
+                    <li><a href="{{ link }}">{{ title }}</a></li>
+                {% endfor %}
+                {% if DISPLAY_PAGES_ON_MENU %}
+                {% for page in PAGES %}
+                    <li><a href="{{ SITEURL }}/{{ page.url }}">{{ page.title }}</a></li>
+                {% endfor %}
+                {% endif %}
+                {% for cat, null in categories %}
+                    <li><a href="{{ SITEURL }}/{{ cat.url }}">{{ cat }}</a></li>
+                {% endfor %}
+                </ul>
+                <a href="{{ SITEURL }}/"><div id="drawer-tux"></div></a>
+                </nav><!-- /#mobile-drawer -->
+        </header><!-- /#mobile-banner -->
         {% block content %}
         {% endblock %}
         <section id="extras" class="body">
-- 
2.51.0