atproto utils for zig zat.dev
atproto sdk zig
26
fork

Configure Feed

Select the types of activity you want to include in your feed.

freshen docs site: inline nav, theme toggle, updated roadmap

- remove sidebar/hamburger, put nav inline in header
- add dark/light theme toggle with localStorage
- improve typography, details, blockquotes, tables
- update roadmap for v0.3.0-alpha
- fix repo link to tangled.org

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+280 -241
+31 -17
docs/roadmap.md
··· 1 1 # roadmap 2 2 3 - zat started as a small set of string primitives for AT Protocol - the types everyone reimplements (`Tid`, `Did`, `Handle`, `Nsid`, `Rkey`, `AtUri`). the scope grew based on real usage. 3 + zat started as a small set of string primitives for AT Protocol — the types everyone reimplements (`Tid`, `Did`, `Handle`, `Nsid`, `Rkey`, `AtUri`). the scope grew based on real usage. 4 4 5 - ## history 5 + ## current status 6 6 7 - **initial scope** - string primitives with parsing and validation. the philosophy: primitives not frameworks, layered design, zig idioms, minimal scope. 7 + **v0.3.0-alpha** — zig 0.16, `std.Io` throughout. 8 + 9 + the v0.3.0 migration replaced all networking and concurrency primitives with zig 0.16's [`std.Io`](https://ziglang.org/documentation/master/std/#std.Io) interface. the API change is mechanical: every networking type takes `io: std.Io` as its first parameter. streaming clients moved from `connect()` + `next()` loops to `subscribe(handler)` with automatic reconnection, backoff, and host rotation. 10 + 11 + the library is stable and tested. the alpha tag reflects that downstream consumers (zlay, labelz, pollz) are still validating in production. 12 + 13 + ## history 8 14 9 15 **what grew from usage:** 10 - - DID/handle resolution — real projects needed it, so `DidResolver`, `DidDocument`, `HandleResolver` got added 11 - - XRPC client and JSON helpers — same story 16 + - string primitives with parsing and validation — the initial scope 17 + - DID/handle resolution — `DidResolver`, `DidDocument`, `HandleResolver` 18 + - XRPC client and JSON helpers 12 19 - JWT verification for service auth 13 - - jetstream client — typed JSON event stream with reconnection (0.1.3) 14 - - firehose client — raw CBOR event stream, DAG-CBOR codec, CAR codec, CID creation (0.1.4) 15 - - MST, ECDSA signing, `did:key` construction, multibase encoding (0.1.9) 16 - - full repo verification — end-to-end trust chain from handle to MST root CID match (0.2.0) 17 - - CID hash verification in CAR parser (0.2.1), size limits (0.2.2) 20 + - jetstream client — typed JSON event stream with reconnection (v0.1.3) 21 + - firehose client — raw CBOR event stream, DAG-CBOR codec, CAR codec, CID creation (v0.1.4) 22 + - MST, ECDSA signing, `did:key` construction, multibase encoding (v0.1.9) 23 + - full repo verification — end-to-end trust chain from handle to MST root CID match (v0.2.0) 24 + - CID hash verification in CAR parser (v0.2.1), size limits (v0.2.2) 25 + - OAuth 2.1 DPoP client (v0.2.14) 26 + - configurable keep-alive, transport options (v0.2.12–v0.2.18) 27 + - `std.Io` migration, `subscribe(handler)` streaming API (v0.3.0-alpha) 18 28 19 - this pattern - start minimal, expand based on real pain - continues. 29 + this pattern — start minimal, expand based on real pain — continues. 20 30 21 - ## now 31 + ## what's next 22 32 23 - the library covers the full AT Protocol verification pipeline: identity resolution, repo parsing, signature verification, and MST validation. benchmarked against Go (indigo) and Rust (rsky) in [atproto-bench](https://tangled.sh/@zzstoatzz.io/atproto-bench). 33 + the library covers the full AT Protocol verification pipeline: identity resolution, repo parsing, signature verification, and MST validation. benchmarked against Go (indigo) and Rust (rsky) in [atproto-bench](https://tangled.org/zzstoatzz.io/atproto-bench). 34 + 35 + near-term: 36 + - promote to v0.3.0-beta once production consumers stabilize 37 + - cut v0.3.0 when the API surface is confirmed 24 38 25 39 what's missing will show up when people build things. until then, no speculative features. 26 40 ··· 28 42 29 43 these stay out of scope unless real demand emerges: 30 44 31 - - lexicon codegen - probably a separate project 32 - - higher-level clients/frameworks - too opinionated 33 - - token refresh/session management - app-specific 34 - - feed generator scaffolding - each feed is unique 45 + - lexicon codegen — probably a separate project 46 + - higher-level clients/frameworks — too opinionated 47 + - token refresh/session management — app-specific 48 + - feed generator scaffolding — each feed is unique 35 49 36 50 ## non-goals 37 51
+16 -17
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"); 3 + const themeToggle = document.querySelector(".theme-toggle"); 4 + 5 + // Theme toggle 6 + function getPreferredTheme() { 7 + const stored = localStorage.getItem("theme"); 8 + if (stored) return stored; 9 + return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark"; 10 + } 6 11 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" : ""; 12 + function applyTheme(theme) { 13 + document.documentElement.setAttribute("data-theme", theme); 14 + localStorage.setItem("theme", theme); 13 15 } 14 16 15 - menuToggle?.addEventListener("click", () => toggleMenu()); 16 - overlay?.addEventListener("click", () => toggleMenu(false)); 17 + applyTheme(getPreferredTheme()); 17 18 18 - // Close menu when nav link clicked (mobile) 19 - navEl?.addEventListener("click", (e) => { 20 - if (e.target.closest("a")) toggleMenu(false); 19 + themeToggle?.addEventListener("click", () => { 20 + const current = document.documentElement.getAttribute("data-theme") || getPreferredTheme(); 21 + applyTheme(current === "dark" ? "light" : "dark"); 21 22 }); 22 23 23 24 const buildId = new URL(import.meta.url).searchParams.get("v") || ""; ··· 110 111 111 112 async function main() { 112 113 // SPA fallback: convert pathname to hash route so deep links work 113 - // e.g. /roadmap -> #roadmap.md, /devlog/001 -> #devlog/001.md 114 114 if (location.pathname !== "/" && !location.hash) { 115 115 const path = location.pathname.replace(/^\/+/, ""); 116 116 if (path) { ··· 144 144 renderNav(pages, activePath); 145 145 146 146 if (!activePath) { 147 - contentEl.innerHTML = `<p class="empty">No docs yet. Add markdown files under <code>zat/docs/</code> and push to <code>main</code>.</p>`; 147 + contentEl.innerHTML = `<p class="empty">No docs yet.</p>`; 148 148 return; 149 149 } 150 150 ··· 153 153 const html = globalThis.marked.parse(md); 154 154 contentEl.innerHTML = html; 155 155 156 - // Update current marker after navigation re-render. 157 156 for (const a of navEl.querySelectorAll("a")) { 158 157 const href = decodeURIComponent((a.getAttribute("href") || "").slice(1)); 159 158 a.toggleAttribute("aria-current", normalizeDocPath(href) === activePath);
+14 -24
site/index.html
··· 5 5 <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 6 <base href="/" /> 7 7 <title>zat.dev</title> 8 - <meta name="description" content="zat documentation" /> 8 + <meta name="description" content="AT Protocol building blocks for zig" /> 9 9 <link rel="icon" href="./favicon.svg" type="image/svg+xml" /> 10 10 <link rel="stylesheet" href="./style.css" /> 11 11 </head> 12 12 <body> 13 13 <div class="app"> 14 14 <header class="header"> 15 - <button class="menu-toggle" aria-label="Toggle navigation" aria-expanded="false"> 16 - <span></span> 17 - </button> 18 15 <a class="brand" href="./">zat.dev</a> 19 - <div class="header-links"> 16 + <nav id="nav" class="nav"></nav> 17 + <div class="header-right"> 20 18 <a class="header-link" href="#devlog/index.md">devlog</a> 21 - <a class="header-link" href="https://tangled.sh/zat.dev/zat" target="_blank" rel="noopener noreferrer">repo</a> 19 + <a class="header-link" href="https://tangled.org/zat.dev/zat" target="_blank" rel="noopener noreferrer">repo</a> 20 + <button class="theme-toggle" aria-label="Toggle theme" title="Toggle theme"> 21 + <svg class="icon-sun" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg> 22 + <svg class="icon-moon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg> 23 + </button> 22 24 </div> 23 25 </header> 24 26 25 - <div class="overlay"></div> 26 - <div class="layout"> 27 - <nav class="sidebar"> 28 - <div id="nav" class="nav"></div> 29 - </nav> 30 - 31 - <main class="main"> 32 - <article id="content" class="content"> 33 - <noscript>This docs site requires JavaScript.</noscript> 34 - </article> 35 - </main> 36 - </div> 27 + <main class="main"> 28 + <article id="content" class="content"> 29 + <noscript>This docs site requires JavaScript.</noscript> 30 + </article> 31 + </main> 37 32 38 33 <footer class="site-footer"> 39 - <a 40 - class="footer-link" 41 - href="https://wisp.place" 42 - target="_blank" 43 - rel="noopener noreferrer" 44 - > 34 + <a class="footer-link" href="https://wisp.place" target="_blank" rel="noopener noreferrer"> 45 35 powered by wisp.place 46 36 </a> 47 37 </footer>
+219 -183
site/style.css
··· 1 1 :root { 2 - color-scheme: light dark; 2 + color-scheme: dark; 3 3 --bg: #0b0b0f; 4 - --panel: #10101a; 5 - --text: #f3f4f6; 6 - --muted: #a1a1aa; 7 - --border: rgba(255, 255, 255, 0.08); 8 - --link: #93c5fd; 9 - --codebg: rgba(255, 255, 255, 0.06); 10 - --shadow: rgba(0, 0, 0, 0.35); 11 - --max: 900px; 12 - --radius: 12px; 4 + --panel: #12121e; 5 + --text: #e4e4eb; 6 + --muted: #8b8b9e; 7 + --border: rgba(255, 255, 255, 0.07); 8 + --link: #7db4f5; 9 + --codebg: rgba(255, 255, 255, 0.05); 10 + --shadow: rgba(0, 0, 0, 0.4); 11 + --accent: rgba(125, 180, 245, 0.08); 12 + --radius: 10px; 13 13 --gutter: 16px; 14 14 --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", 15 15 "Courier New", monospace; 16 - --sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, 17 - Arial, "Apple Color Emoji", "Segoe UI Emoji"; 16 + --sans: -apple-system, system-ui, "Segoe UI", Roboto, Helvetica, Arial, 17 + sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; 18 18 } 19 19 20 20 @media (prefers-color-scheme: light) { 21 - :root { 22 - --bg: #fafafa; 21 + :root:not([data-theme="dark"]) { 22 + color-scheme: light; 23 + --bg: #f8f8fa; 23 24 --panel: #ffffff; 24 - --text: #0b0b0f; 25 - --muted: #52525b; 25 + --text: #1a1a2e; 26 + --muted: #64648a; 26 27 --border: rgba(0, 0, 0, 0.08); 27 28 --link: #2563eb; 28 29 --codebg: rgba(0, 0, 0, 0.04); 29 - --shadow: rgba(0, 0, 0, 0.08); 30 + --shadow: rgba(0, 0, 0, 0.06); 31 + --accent: rgba(37, 99, 235, 0.06); 30 32 } 33 + } 34 + 35 + :root[data-theme="light"] { 36 + color-scheme: light; 37 + --bg: #f8f8fa; 38 + --panel: #ffffff; 39 + --text: #1a1a2e; 40 + --muted: #64648a; 41 + --border: rgba(0, 0, 0, 0.08); 42 + --link: #2563eb; 43 + --codebg: rgba(0, 0, 0, 0.04); 44 + --shadow: rgba(0, 0, 0, 0.06); 45 + --accent: rgba(37, 99, 235, 0.06); 31 46 } 32 47 33 48 * { ··· 68 83 z-index: 20; 69 84 display: flex; 70 85 align-items: center; 71 - gap: 12px; 72 - padding: 12px var(--gutter); 86 + gap: 8px; 87 + padding: 0 var(--gutter); 88 + height: 48px; 73 89 border-bottom: 1px solid var(--border); 74 - background: color-mix(in srgb, var(--panel) 92%, transparent); 75 - backdrop-filter: blur(10px); 76 - } 77 - 78 - .menu-toggle { 79 - display: none; 80 - align-items: center; 81 - justify-content: center; 82 - width: 36px; 83 - height: 36px; 84 - padding: 0; 85 - background: transparent; 86 - border: 1px solid var(--border); 87 - border-radius: 8px; 88 - cursor: pointer; 89 - flex-shrink: 0; 90 - } 91 - .menu-toggle span { 92 - display: block; 93 - width: 16px; 94 - height: 2px; 95 - background: var(--text); 96 - border-radius: 1px; 97 - position: relative; 98 - } 99 - .menu-toggle span::before, 100 - .menu-toggle span::after { 101 - content: ""; 102 - position: absolute; 103 - left: 0; 104 - width: 16px; 105 - height: 2px; 106 - background: var(--text); 107 - border-radius: 1px; 108 - transition: transform 0.2s; 109 - } 110 - .menu-toggle span::before { 111 - top: -5px; 112 - } 113 - .menu-toggle span::after { 114 - top: 5px; 115 - } 116 - .menu-toggle[aria-expanded="true"] span { 117 - background: transparent; 118 - } 119 - .menu-toggle[aria-expanded="true"] span::before { 120 - transform: translateY(5px) rotate(45deg); 121 - } 122 - .menu-toggle[aria-expanded="true"] span::after { 123 - transform: translateY(-5px) rotate(-45deg); 90 + background: color-mix(in srgb, var(--bg) 85%, transparent); 91 + backdrop-filter: blur(12px); 92 + -webkit-backdrop-filter: blur(12px); 93 + max-width: 100%; 124 94 } 125 95 126 96 .brand { 97 + font-family: var(--mono); 127 98 font-weight: 700; 128 - font-size: 15px; 99 + font-size: 14px; 129 100 color: var(--text); 130 - padding: 6px 0; 101 + letter-spacing: -0.02em; 102 + white-space: nowrap; 103 + flex-shrink: 0; 131 104 } 132 105 .brand:hover { 133 106 text-decoration: none; 134 107 opacity: 0.8; 135 108 } 136 109 137 - .header-links { 110 + /* Nav — inline in header */ 111 + .nav { 138 112 display: flex; 139 - gap: 8px; 140 - margin-left: auto; 113 + gap: 2px; 114 + margin-left: 12px; 115 + overflow-x: auto; 116 + -webkit-overflow-scrolling: touch; 117 + scrollbar-width: none; 118 + } 119 + .nav::-webkit-scrollbar { 120 + display: none; 141 121 } 142 122 143 - .header-link { 144 - padding: 6px 12px; 145 - font-size: 14px; 146 - border-radius: 8px; 147 - border: 1px solid var(--border); 123 + .nav a { 124 + padding: 6px 10px; 125 + border-radius: 6px; 126 + color: var(--muted); 127 + font-size: 13px; 128 + white-space: nowrap; 129 + transition: color 0.1s, background 0.1s; 130 + } 131 + .nav a:hover { 148 132 color: var(--text); 149 - } 150 - .header-link:hover { 151 133 background: var(--codebg); 152 134 text-decoration: none; 153 135 } 154 - 155 - /* Overlay */ 156 - .overlay { 157 - display: none; 158 - position: fixed; 159 - inset: 0; 160 - z-index: 15; 161 - background: rgba(0, 0, 0, 0.5); 162 - } 163 - .overlay.open { 164 - display: block; 136 + .nav a[aria-current="page"] { 137 + color: var(--text); 138 + background: var(--accent); 165 139 } 166 140 167 - /* Layout */ 168 - .layout { 141 + /* Header right */ 142 + .header-right { 169 143 display: flex; 170 - gap: var(--gutter); 171 - padding: var(--gutter); 172 - flex: 1; 173 - max-width: 1200px; 174 - margin: 0 auto; 175 - width: 100%; 144 + align-items: center; 145 + gap: 6px; 146 + margin-left: auto; 147 + flex-shrink: 0; 176 148 } 177 149 178 - /* Sidebar */ 179 - .sidebar { 180 - width: 240px; 181 - flex-shrink: 0; 182 - position: sticky; 183 - top: 72px; 184 - align-self: flex-start; 185 - max-height: calc(100vh - 88px); 186 - overflow-y: auto; 187 - border: 1px solid var(--border); 188 - border-radius: var(--radius); 189 - background: var(--panel); 150 + .header-link { 151 + padding: 5px 10px; 152 + font-size: 13px; 153 + border-radius: 6px; 154 + color: var(--muted); 155 + white-space: nowrap; 156 + } 157 + .header-link:hover { 158 + color: var(--text); 159 + background: var(--codebg); 160 + text-decoration: none; 190 161 } 191 162 192 - .nav { 193 - padding: 8px; 163 + /* Theme toggle */ 164 + .theme-toggle { 194 165 display: flex; 195 - flex-direction: column; 196 - gap: 2px; 166 + align-items: center; 167 + justify-content: center; 168 + width: 32px; 169 + height: 32px; 170 + padding: 0; 171 + background: transparent; 172 + border: 1px solid var(--border); 173 + border-radius: 6px; 174 + cursor: pointer; 175 + color: var(--muted); 176 + transition: color 0.15s, border-color 0.15s; 197 177 } 198 - 199 - .nav a { 178 + .theme-toggle:hover { 179 + color: var(--text); 180 + border-color: var(--muted); 181 + } 182 + .theme-toggle .icon-moon { 183 + display: none; 184 + } 185 + :root[data-theme="dark"] .theme-toggle .icon-sun { 186 + display: none; 187 + } 188 + :root[data-theme="dark"] .theme-toggle .icon-moon { 200 189 display: block; 201 - padding: 10px 12px; 202 - border-radius: 8px; 203 - color: var(--text); 204 - font-size: 14px; 205 190 } 206 - .nav a:hover { 207 - background: var(--codebg); 208 - text-decoration: none; 191 + :root[data-theme="light"] .theme-toggle .icon-sun { 192 + display: block; 209 193 } 210 - .nav a[aria-current="page"] { 211 - background: color-mix(in srgb, var(--link) 15%, transparent); 194 + :root[data-theme="light"] .theme-toggle .icon-moon { 195 + display: none; 212 196 } 213 197 214 198 /* Main content */ 215 199 .main { 216 200 flex: 1; 217 - min-width: 0; 201 + width: 100%; 202 + max-width: 720px; 203 + margin: 0 auto; 204 + padding: var(--gutter); 218 205 } 219 206 220 207 .content { 221 208 border: 1px solid var(--border); 222 209 border-radius: var(--radius); 223 210 background: var(--panel); 224 - padding: 24px; 211 + padding: 28px 32px; 225 212 } 226 213 227 214 /* Footer */ ··· 232 219 } 233 220 234 221 .footer-link { 235 - font-size: 13px; 222 + font-size: 12px; 236 223 color: var(--muted); 237 224 } 238 225 .footer-link:hover { ··· 244 231 .content h1, 245 232 .content h2, 246 233 .content h3 { 247 - scroll-margin-top: 80px; 234 + scroll-margin-top: 64px; 235 + letter-spacing: -0.01em; 248 236 } 249 237 250 238 .content h1 { 251 239 margin-top: 0; 252 - font-size: 28px; 240 + font-size: 24px; 241 + font-weight: 700; 253 242 } 254 243 255 244 .content h2 { 256 - font-size: 20px; 257 - margin-top: 32px; 245 + font-size: 17px; 246 + font-weight: 600; 247 + margin-top: 36px; 248 + padding-top: 16px; 249 + border-top: 1px solid var(--border); 250 + } 251 + 252 + .content h2:first-of-type { 253 + border-top: none; 254 + padding-top: 0; 258 255 } 259 256 260 257 .content h3 { 261 - font-size: 16px; 258 + font-size: 14px; 259 + font-weight: 600; 262 260 margin-top: 24px; 261 + color: var(--muted); 263 262 } 264 263 265 264 .content p, 266 265 .content li { 267 - line-height: 1.65; 266 + line-height: 1.7; 267 + font-size: 14px; 268 268 } 269 269 270 270 .content code { 271 271 font-family: var(--mono); 272 - font-size: 0.9em; 272 + font-size: 0.85em; 273 273 background: var(--codebg); 274 - padding: 2px 6px; 275 - border-radius: 6px; 274 + padding: 2px 5px; 275 + border-radius: 4px; 276 + border: 1px solid var(--border); 276 277 } 277 278 278 279 .content pre { 279 280 overflow-x: auto; 280 - padding: 16px; 281 - border-radius: 10px; 281 + padding: 14px 16px; 282 + border-radius: 8px; 282 283 background: var(--codebg); 283 284 border: 1px solid var(--border); 284 - font-size: 14px; 285 - line-height: 1.5; 285 + font-size: 13px; 286 + line-height: 1.6; 286 287 } 287 288 288 289 .content pre code { 289 290 background: transparent; 290 291 padding: 0; 292 + border: none; 293 + font-size: inherit; 291 294 } 292 295 293 296 .content details { 294 - margin: 16px 0; 297 + margin: 12px 0; 298 + border: 1px solid var(--border); 299 + border-radius: 8px; 295 300 } 296 301 297 302 .content details summary { 298 303 cursor: pointer; 299 - padding: 8px 0; 304 + padding: 10px 14px; 305 + font-size: 14px; 306 + border-radius: 8px; 307 + } 308 + 309 + .content details summary:hover { 310 + background: var(--accent); 311 + } 312 + 313 + .content details[open] summary { 314 + border-bottom: 1px solid var(--border); 315 + border-radius: 8px 8px 0 0; 316 + } 317 + 318 + .content details > :not(summary) { 319 + padding: 0 14px; 320 + } 321 + 322 + .content hr { 323 + border: none; 324 + border-top: 1px solid var(--border); 325 + margin: 28px 0; 326 + } 327 + 328 + .content blockquote { 329 + border-left: 3px solid var(--link); 330 + margin: 16px 0; 331 + padding: 4px 16px; 332 + color: var(--muted); 333 + background: var(--accent); 334 + border-radius: 0 6px 6px 0; 335 + } 336 + 337 + .content table { 338 + width: 100%; 339 + border-collapse: collapse; 340 + font-size: 13px; 341 + margin: 16px 0; 342 + } 343 + 344 + .content th, 345 + .content td { 346 + padding: 8px 12px; 347 + border: 1px solid var(--border); 348 + text-align: left; 349 + } 350 + 351 + .content th { 352 + background: var(--codebg); 353 + font-weight: 600; 300 354 } 301 355 302 356 .empty { ··· 304 358 } 305 359 306 360 /* Mobile */ 307 - @media (max-width: 768px) { 308 - :root { 309 - --gutter: 16px; 310 - } 311 - 312 - .menu-toggle { 313 - display: flex; 361 + @media (max-width: 640px) { 362 + .header { 363 + gap: 4px; 364 + padding: 0 12px; 314 365 } 315 366 316 - .layout { 317 - flex-direction: column; 367 + .nav { 368 + margin-left: 8px; 369 + gap: 0; 318 370 } 319 371 320 - .sidebar { 321 - position: fixed; 322 - top: 0; 323 - left: 0; 324 - bottom: 0; 325 - width: 280px; 326 - max-width: 80vw; 327 - z-index: 16; 328 - border: none; 329 - border-radius: 0; 330 - border-right: 1px solid var(--border); 331 - max-height: none; 332 - padding-top: 60px; 333 - transform: translateX(-100%); 334 - transition: transform 0.2s ease-out; 372 + .nav a { 373 + padding: 6px 8px; 374 + font-size: 12px; 335 375 } 336 376 337 - .sidebar.open { 338 - transform: translateX(0); 377 + .header-link { 378 + font-size: 12px; 379 + padding: 5px 8px; 339 380 } 340 381 341 - .nav { 382 + .main { 342 383 padding: 12px; 343 - } 344 - 345 - .nav a { 346 - padding: 12px 14px; 347 - font-size: 15px; 348 384 } 349 385 350 386 .content { 351 - padding: 20px; 352 - border-radius: 10px; 387 + padding: 20px 16px; 388 + border-radius: 8px; 353 389 } 354 390 355 391 .content h1 { 356 - font-size: 24px; 392 + font-size: 20px; 357 393 } 358 394 359 395 .content h2 { 360 - font-size: 18px; 396 + font-size: 16px; 361 397 } 362 398 363 399 .content pre { 364 - font-size: 13px; 365 - padding: 14px; 400 + font-size: 12px; 401 + padding: 12px; 366 402 } 367 403 }