atproto utils for zig zat.dev
atproto sdk zig

fix: responsive mobile experience

- hamburger menu toggle (hidden on desktop)
- sidebar slides in from left on mobile
- overlay backdrop when menu open
- touch-friendly nav sizing (44px+ tap targets)
- responsive padding at breakpoints
- code blocks edge-to-edge on small screens

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+145 -5
+19
site/app.js
··· 1 1 const navEl = document.getElementById("nav"); 2 2 const contentEl = document.getElementById("content"); 3 + const menuToggle = document.querySelector(".menu-toggle"); 4 + const sidebar = document.querySelector(".sidebar"); 5 + const overlay = document.querySelector(".overlay"); 6 + 7 + function toggleMenu(open) { 8 + const isOpen = open ?? !sidebar.classList.contains("open"); 9 + sidebar.classList.toggle("open", isOpen); 10 + overlay?.classList.toggle("open", isOpen); 11 + menuToggle?.setAttribute("aria-expanded", isOpen); 12 + document.body.style.overflow = isOpen ? "hidden" : ""; 13 + } 14 + 15 + menuToggle?.addEventListener("click", () => toggleMenu()); 16 + overlay?.addEventListener("click", () => toggleMenu(false)); 17 + 18 + // Close menu when nav link clicked (mobile) 19 + navEl?.addEventListener("click", (e) => { 20 + if (e.target.closest("a")) toggleMenu(false); 21 + }); 3 22 4 23 const buildId = new URL(import.meta.url).searchParams.get("v") || ""; 5 24
+7 -1
site/index.html
··· 11 11 <body> 12 12 <div class="app"> 13 13 <header class="header"> 14 + <button class="menu-toggle" aria-label="Toggle navigation" aria-expanded="false"> 15 + <span></span> 16 + <span></span> 17 + <span></span> 18 + </button> 14 19 <a class="brand" href="./">zat.dev</a> 15 20 <a 16 21 class="header-link" 17 - href="https://tangled.org/zat.dev/zat" 22 + href="https://tangled.sh/zat.dev/zat" 18 23 target="_blank" 19 24 rel="noopener noreferrer" 20 25 > ··· 22 27 </a> 23 28 </header> 24 29 30 + <div class="overlay"></div> 25 31 <div class="layout"> 26 32 <nav class="sidebar"> 27 33 <div id="nav" class="nav"></div>
+119 -4
site/style.css
··· 58 58 .header { 59 59 position: sticky; 60 60 top: 0; 61 - z-index: 5; 61 + z-index: 20; 62 62 display: flex; 63 63 gap: 12px; 64 64 align-items: center; ··· 68 68 backdrop-filter: blur(10px); 69 69 } 70 70 71 + .menu-toggle { 72 + display: none; 73 + flex-direction: column; 74 + justify-content: center; 75 + gap: 5px; 76 + width: 40px; 77 + height: 40px; 78 + padding: 8px; 79 + background: transparent; 80 + border: 1px solid var(--border); 81 + border-radius: 10px; 82 + cursor: pointer; 83 + } 84 + .menu-toggle span { 85 + display: block; 86 + width: 100%; 87 + height: 2px; 88 + background: var(--text); 89 + border-radius: 1px; 90 + transition: transform 0.2s, opacity 0.2s; 91 + } 92 + .menu-toggle[aria-expanded="true"] span:nth-child(1) { 93 + transform: translateY(7px) rotate(45deg); 94 + } 95 + .menu-toggle[aria-expanded="true"] span:nth-child(2) { 96 + opacity: 0; 97 + } 98 + .menu-toggle[aria-expanded="true"] span:nth-child(3) { 99 + transform: translateY(-7px) rotate(-45deg); 100 + } 101 + 71 102 .brand { 72 103 font-weight: 700; 73 104 letter-spacing: 0.2px; ··· 93 124 opacity: 1; 94 125 } 95 126 127 + .overlay { 128 + display: none; 129 + position: fixed; 130 + inset: 0; 131 + z-index: 9; 132 + background: rgba(0, 0, 0, 0.5); 133 + backdrop-filter: blur(2px); 134 + } 135 + .overlay.open { 136 + display: block; 137 + } 138 + 96 139 .layout { 97 140 display: grid; 98 141 grid-template-columns: 280px 1fr; ··· 101 144 flex: 1; 102 145 } 103 146 104 - @media (max-width: 980px) { 147 + @media (max-width: 768px) { 148 + .menu-toggle { 149 + display: flex; 150 + } 151 + 105 152 .layout { 106 153 grid-template-columns: 1fr; 154 + padding: 12px; 155 + gap: 12px; 107 156 } 157 + 108 158 .sidebar { 109 - position: relative; 110 - top: auto; 159 + position: fixed; 160 + top: 0; 161 + left: 0; 162 + right: 0; 163 + bottom: 0; 164 + z-index: 10; 111 165 max-height: none; 166 + border: none; 167 + border-radius: 0; 168 + transform: translateX(-100%); 169 + transition: transform 0.25s ease; 170 + padding-top: 64px; 171 + } 172 + .sidebar.open { 173 + transform: translateX(0); 174 + } 175 + 176 + .nav { 177 + padding: 16px; 178 + } 179 + 180 + .nav a { 181 + padding: 14px 16px; 182 + font-size: 16px; 183 + } 184 + 185 + .content { 186 + padding: 16px; 187 + } 188 + 189 + .content h1 { 190 + font-size: 26px; 191 + } 192 + 193 + .content pre { 194 + padding: 12px; 195 + font-size: 13px; 196 + } 197 + } 198 + 199 + @media (max-width: 480px) { 200 + .header { 201 + padding: 10px 12px; 202 + } 203 + 204 + .layout { 205 + padding: 8px; 206 + } 207 + 208 + .content { 209 + padding: 14px; 210 + border-radius: 10px; 211 + } 212 + 213 + .content h1 { 214 + font-size: 22px; 215 + } 216 + 217 + .content h2 { 218 + font-size: 18px; 219 + } 220 + 221 + .content pre { 222 + margin-left: -14px; 223 + margin-right: -14px; 224 + border-radius: 0; 225 + border-left: none; 226 + border-right: none; 112 227 } 113 228 } 114 229