patx/mongokv

Improve navigation and styling in index.html

Commit 8c0065f · patx · 2025-12-28T21:46:04-05:00

Changeset
8c0065fa52febc56786655d1eee4ea804ee6534a
Parents
3d26742e1079071afd8a9677bfbe51dff36bc5c7

View source at this commit

Improve navigation and styling in index.html

Enhance navigation and styling for better user experience.

Comments

No comments yet.

Log in to comment

Diff

diff --git a/docs/index.html b/docs/index.html
index 5081abc..4eb3c8a 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -4,26 +4,34 @@
   <meta charset="UTF-8">
   <title>mongoKV – Tiny Redis-style Key-Value Store for MongoDB (Python)</title>
   <meta name="viewport" content="width=device-width, initial-scale=1">
+
   <link rel="preconnect" href="https://fonts.googleapis.com">
   <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
   <link href="https://fonts.googleapis.com/css2?family=Pacifico&display=swap" rel="stylesheet">
+
   <!-- highlight.js -->
   <link rel="stylesheet"
-      href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/github.min.css">
+        href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/github.min.css">
   <script defer
-        src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/highlight.min.js"></script>
+          src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/highlight.min.js"></script>
   <script defer
-        src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/languages/python.min.js"></script>
+          src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/languages/python.min.js"></script>
   <script defer
-        src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/languages/bash.min.js"></script>
+          src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/languages/bash.min.js"></script>
   <script defer
-        src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/languages/json.min.js"></script>
+          src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/languages/json.min.js"></script>
+
+  <script defer>
+    document.addEventListener("DOMContentLoaded", () => {
+      document.querySelectorAll("pre code").forEach(el => hljs.highlightElement(el));
+
+      const nav = document.getElementById("topnav");
+      const onScroll = () => nav.classList.toggle("scrolled", window.scrollY > 2);
+      onScroll();
+      window.addEventListener("scroll", onScroll, { passive: true });
+    });
+  </script>
 
-<script defer>
-  document.addEventListener("DOMContentLoaded", () => {
-    document.querySelectorAll("pre code").forEach(el => hljs.highlightElement(el));
-  });
-</script>
   <style>
     :root {
       --bg: #f5f5f5;
@@ -37,6 +45,8 @@
       --sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
     }
 
+    html { scroll-behavior: smooth; }
+
     body {
       margin: 0;
       padding: 0;
@@ -67,13 +77,13 @@
       font-size: 4rem;
       margin: 0 0 4px;
     }
-    
+
     .logo {
-     font-family: 'Pacifico', cursive;
-     color: var(--accent);
-     letter-spacing: 3px;
+      font-family: 'Pacifico', cursive;
+      color: var(--accent);
+      letter-spacing: 3px;
     }
-    
+
     .subtitle {
       color: var(--muted);
       margin: 0 0 12px;
@@ -86,18 +96,10 @@
       white-space: nowrap;
     }
 
-    a {
-      color: var(--accent);
-      text-decoration: none;
-    }
-
-    a:hover {
-      text-decoration: underline;
-    }
+    a { color: var(--accent); text-decoration: none; }
+    a:hover { text-decoration: underline; }
 
-    p {
-      margin: 0 0 1rem;
-    }
+    p { margin: 0 0 1rem; }
 
     ul, ol {
       margin: 0 0 1rem 1.5rem;
@@ -165,28 +167,167 @@
       color: var(--accent);
     }
 
-    .callout-list li {
-      margin-bottom: 0.25rem;
+    .callout-list li { margin-bottom: 0.25rem; }
+
+    .header-content {
+      display: flex;
+      justify-content: space-between;
+      align-items: flex-start;
+      flex-wrap: wrap;
+      gap: 24px;
+    }
+
+    .header-content nav ul {
+      list-style: none;
+      margin: 0;
+      padding: 0;
+      display: flex;
+      gap: 24px;
+    }
+
+    .header-content nav a {
+      color: var(--fg);
+      text-decoration: none;
+      font-weight: 500;
+    }
+
+    .header-content nav a:hover {
+      color: var(--accent);
+      text-decoration: underline;
+    }
+
+    .navbar{
+      position: sticky;
+      top: 0;
+      z-index: 9999;
+
+      background: rgba(245,245,245,.82);
+      backdrop-filter: blur(10px);
+      -webkit-backdrop-filter: blur(10px);
+
+      border-bottom: 1px solid var(--border);
+    }
+
+    .nav-inner{
+      max-width: 840px;
+      margin: 0 auto;
+      padding: 10px 16px;
+
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      gap: 14px;
+    }
+
+    .brand{
+      display: inline-flex;
+      align-items: baseline;
+      gap: 10px;
+      text-decoration: none;
+    }
+
+    .brand-logo{
+      font-family: 'Pacifico', cursive;
+      color: var(--accent);
+      letter-spacing: 2px;
+      font-size: 1.25rem;
+      line-height: 1;
+    }
+
+    .nav-links{
+      list-style: none;
+      margin: 0;
+      padding: 0;
+
+      display: flex;
+      align-items: center;
+      gap: 10px;
+      flex-wrap: wrap;
+      justify-content: flex-end;
+    }
+
+    .nav-links a{
+      display: inline-flex;
+      align-items: center;
+      height: 36px;
+      padding: 0 12px;
+
+      border-radius: 999px;
+      color: var(--fg);
+      text-decoration: none;
+      font-weight: 600;
+      font-size: 0.95rem;
+
+      transition: transform .15s ease, background .15s ease, color .15s ease, box-shadow .15s ease;
+    }
+
+    .nav-links a:hover{
+      background: rgba(36,102,60,.10);
+      color: var(--accent);
+      box-shadow: 0 4px 14px rgba(0,0,0,.06);
+      transform: translateY(-1px);
+    }
+
+    /* NEW: active section highlight */
+    .nav-links a.active{
+      background: rgba(36,102,60,.16);
+      color: var(--accent);
+      box-shadow: 0 8px 18px rgba(0,0,0,.08);
+    }
+
+    .navbar.scrolled{
+      box-shadow: 0 10px 24px rgba(0,0,0,.08);
+    }
+    /* Hide brand logo until scrolled past header */
+    .brand-logo {
+      opacity: 0;
+      transform: translateY(-4px);
+      pointer-events: none;
+      transition: opacity .2s ease, transform .2s ease;
+    }
+
+    /* Show brand logo after scrolling */
+    .navbar.show-logo .brand-logo {
+      opacity: 1;
+      transform: translateY(0);
+      pointer-events: auto;
     }
   </style>
 </head>
+
 <body>
-  <div class="page">
+  <nav class="navbar" id="topnav">
+    <div class="nav-inner">
+      <a class="brand" href="#top">
+        <span class="brand-logo">mongoKV</span>
+      </a>
+
+      <ul class="nav-links">
+        <li><a href="#guide" data-spy="guide">Guide</a></li>
+        <li><a href="#api" data-spy="api">API</a></li>
+        <li><a href="https://github.com/patx/mongokv" target="_blank" rel="noreferrer">GitHub</a></li>
+        <li><a href="https://pypi.org/project/mongokv/" target="_blank" rel="noreferrer">PyPI</a></li>
+      </ul>
+    </div>
+  </nav>
+
+  <div class="page" id="top">
     <header>
-      <h1><span class="logo">mongoKV</span> <span class="pronounce">/ˈmɑːŋɡoʊ kiː ˈvæljuː/</span></h1>
-      <p class="subtitle">Tiny async/sync key–value store on top of PyMongo.</p>
-      <p class="tagline">
-        <a href="https://github.com/patx/mongokv" target="_blank" rel="noopener noreferrer">GitHub</a>
-        &middot;
-        <a href="https://pypi.org/project/mongokv/" target="_blank" rel="noopener noreferrer">PyPI</a>
-        &middot;
-        <a href="https://harrisonerd.com/" target="_blank" rel="noopener noreferrer">Author</a>
+      <div class="header-content">
+        <div>
+          <h1><span class="logo">mongoKV</span> <span class="pronounce">/ˈmɑːŋɡoʊ kiː ˈvæljuː/</span></h1>
+          <p class="subtitle">Tiny async/sync key–value store on top of PyMongo.</p>
+          <p class="tagline">
+        <a href="https://harrisonerd.com/" target="_blank" rel="noopener noreferrer">Harrison Erd</a>
         &middot; BSD 3-Clause
-      </p>
+        </p>
+        </div>
+
+      </div>
     </header>
 
     <section class="section" style="border: 0px;">
-      <h2>User Guide</h2>
+      <h2 id="guide">User Guide</h2>
       <p><code>mongoKV</code> is a tiny key–value store wrapper around MongoDB using PyMongo.</p>
       <ul class="callout-list">
         <li>One collection per instance, one document per key</li>
@@ -302,7 +443,7 @@ asyncio.run(main())</code></pre>
     </section>
 
     <section class="section api-heading" style="border: 0px;">
-      <h2>API Reference</h2>
+      <h2 id="api">API Reference</h2>
     </section>
 
     <section class="section">
@@ -503,6 +644,9 @@ print(keys)  # -&gt; ["user:1", "note:abc", ...]</code></pre>
           </ul>
         </li>
       </ul>
+      
+      <h4>Returns</h4>
+      <p><code>None</code></p>
 
       <h4>Examples (sync)</h4>
       <pre><code class="language-python">db.close()</code></pre>
@@ -547,6 +691,98 @@ except PyMongoError as e:
     </section>
 
   </div>
+  
+<script defer>
+  document.addEventListener("DOMContentLoaded", () => {
+    document.querySelectorAll("pre code").forEach(el => hljs.highlightElement(el));
+
+    const nav = document.getElementById("topnav");
+    const header = document.querySelector("header");
+
+    const guideEl = document.getElementById("guide");
+    const apiEl   = document.getElementById("api");
+
+    const guideLink = document.querySelector('.nav-links a[href="#guide"]');
+    const apiLink   = document.querySelector('.nav-links a[href="#api"]');
+
+    const clearActive = () => {
+      [guideLink, apiLink].forEach(a => a && a.classList.remove("active"));
+    };
+
+    const setActive = (which) => {
+      clearActive();
+      if (which === "guide" && guideLink) guideLink.classList.add("active");
+      if (which === "api" && apiLink) apiLink.classList.add("active");
+    };
+
+    // Decide which section should be active RIGHT NOW (when spy is enabled)
+    const computeActiveSection = () => {
+      if (!guideEl || !apiEl) return null;
+
+      const y = 90; // "reading line" just below sticky nav
+      const g = guideEl.getBoundingClientRect();
+      const a = apiEl.getBoundingClientRect();
+
+      const guidePassed = g.top <= y;
+      const apiPassed   = a.top <= y;
+
+      if (apiPassed) return "api";
+
+      if (guidePassed) return "guide";
+
+      return null;
+    };
+
+    let spyEnabled = false;
+
+    const syncNavState = () => {
+      const headerBottom = header.getBoundingClientRect().bottom;
+      const shouldEnable = headerBottom <= 0;
+
+      nav.classList.toggle("scrolled", window.scrollY > 2);
+      nav.classList.toggle("show-logo", shouldEnable);
+
+      if (!shouldEnable) {
+        spyEnabled = false;
+        clearActive();
+        return;
+      }
+
+      spyEnabled = true;
+
+      const which = computeActiveSection();
+      if (which) setActive(which);
+      else clearActive();
+    };
+
+    syncNavState();
+    window.addEventListener("scroll", syncNavState, { passive: true });
+    window.addEventListener("resize", syncNavState, { passive: true });
+
+    const onNavClick = (which) => (e) => {
+      if (!spyEnabled) return;
+      setActive(which);
+    };
+    if (guideLink) guideLink.addEventListener("click", onNavClick("guide"));
+    if (apiLink) apiLink.addEventListener("click", onNavClick("api"));
+
+    if ("IntersectionObserver" in window && guideEl && apiEl) {
+      const observer = new IntersectionObserver(() => {
+        if (!spyEnabled) return;
+        const which = computeActiveSection();
+        if (which) setActive(which);
+        else clearActive();
+      }, {
+        root: null,
+        rootMargin: "-72px 0px -55% 0px",
+        threshold: [0, 0.1, 0.25, 0.5]
+      });
+
+      observer.observe(guideEl);
+      observer.observe(apiEl);
+    }
+  });
+</script>
+
 </body>
 </html>
-