Files
projax/web/static
mAi bd600633c9 feat(layout): mobile bottom-nav + drawer
Phase 5g slice B. Fills the ≤767px gap left by slice A (sidebar
display:none on mobile) with a fixed-bottom 5-slot nav + a drawer for
overflow items. iOS PWA install respects safe-area-inset-bottom so the
nav clears the home indicator.

web/templates/layout.tmpl:
- New <nav class="projax-bottom-nav"> with five slots:
    Tree (/) → Dashboard (/dashboard) → +New (/new, raised circle)
    → Calendar (/calendar) → Menu (drawer).
- Center "+ New" slot is a raised .capture-circle (margin-top: -10px,
  44×44px, accent background) — mBrian's capture-button pattern, but
  pointing at /new because projax has no separate capture flow.
- Menu slot is a <details class="projax-mobile-drawer"> whose <summary>
  IS the bottom-nav-item. Tapping pops a drawer-sheet absolutely
  positioned 8px above the bottom-nav with overflow items: Timeline,
  Graph, Admin, theme toggle, sign-out. Browser-default <details>
  handles open/close + tap-outside-dismiss — no JS, no gesture wiring.
- Active class on bottom-nav-item + drawer-item via same .Path-driven
  server-side pattern slice A introduced.
- Theme toggle handler now binds to BOTH #theme-toggle (sidebar) AND
  #theme-toggle-drawer (drawer). Flipping either updates the icon on
  both buttons, sets data-theme on <html>, writes the cookie.

web/static/style.css:
- .projax-bottom-nav: fixed bottom, height = calc(56px +
  env(safe-area-inset-bottom, 0)), flex justify-around, z-index 1021.
- .bottom-nav-item: 44×44px min, column-flex, touch-action: none for the
  capture-button so iOS doesn't intercept the tap.
- .capture-circle: 44×44px raised circle, accent background.
- .projax-mobile-drawer .drawer-sheet: fixed, bottom-right anchored
  above the nav, min(260px, calc(100vw - 16px)) wide, slide-up animation
  via @keyframes projax-drawer-up (translateY 8→0, 160ms ease-out).
- @media (min-width: 768px): bottom-nav hidden.
- @media (max-width: 767px): main.projax-main gets padding-bottom =
  calc(56px + 1rem + env(safe-area-inset-bottom)) so rows aren't hidden
  behind the nav.

docs/design.md:
- New §18 (Layout: sidebar + bottom-nav, Phase 5g). Documents both
  surfaces' breakpoints, the .Path-driven active marker, the pre-paint
  localStorage restore, the theme-toggle dual-binding, and the four
  features I deliberately did not port from mBrian (resize handle,
  capture modal, quick-switcher/saved-searches/Today/Work, slide-up
  gesture).

Tests (web/layout_test.go):
- TestLayoutBottomNavMarkup: 5 slots present in documented order, +New
  is .capture-btn with .capture-circle, Menu is <details>, drawer holds
  Timeline/Graph/Admin/theme/sign-out.
- TestLayoutBottomNavActiveClass: /calendar render highlights Calendar
  slot only.
- TestLayoutThemeToggleBoundToBothButtons: handler enumerates both
  button ids so flipping either flips the theme.

All 10 layout tests pass (7 from slice A + 3 from slice B). Full web
suite green. No test source edits to pre-existing tests — the bottom-
nav is additive markup.
2026-05-25 16:40:14 +02:00
..