redesign docs page to be more modern and easxier to read. even added a few emojis

Commit f1e0a23 · patx · 2025-12-14T23:32:30-05:00

Changeset
f1e0a23d5ad014a5d7998ec863172ec62713e74d
Parents
5ba583a1c5dd5b1922362b742f48aa7a679f8942

View source at this commit

Comments

No comments yet.

Log in to comment

Diff

diff --git a/__pycache__/pickledb.cpython-312.pyc b/__pycache__/pickledb.cpython-312.pyc
new file mode 100644
index 0000000..9f5ebc2
Binary files /dev/null and b/__pycache__/pickledb.cpython-312.pyc differ
diff --git a/docs/index.html b/docs/index.html
index c57ae82..a63a359 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -2,449 +2,1038 @@
 <html lang="en">
 <head>
   <meta charset="UTF-8">
-  <title>pickleDB · Lightweight Key-Value Store for Python</title>
+  <title>pickleDB · Lightning-Fast Key-Value Store for 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=Fira+Code:wght@400;500;600&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet">
+  
   <style>
     :root {
-      --bg: #fafafa;
+      --bg-primary: #f8fdf9;
+      --bg-secondary: #f0f9f2;
       --bg-card: #ffffff;
-      --border: #e5e7eb;
-      --text: #111827;
-      --muted: #6b7280;
-      --accent: #2563eb;
-      --code-bg: #111827;
-      --code-fg: #e5e7eb;
+      --text-primary: #1a1f1b;
+      --text-secondary: #4a5c4d;
+      --text-muted: #7a8a7d;
+      --accent-pickle: #5fc45f;
+      --accent-lime: #8fd460;
+      --accent-forest: #3a9943;
+      --accent-sage: #6b9b6e;
+      --accent-mint: #4ecdc4;
+      --border: #dfe8e0;
+      --border-dark: #c5d4c7;
+      --shadow: rgba(26, 31, 27, 0.08);
+      --shadow-strong: rgba(26, 31, 27, 0.15);
+      --mono: 'Fira Code', 'Courier New', monospace;
+      --sans: 'Outfit', system-ui, sans-serif;
     }
 
-    * { box-sizing: border-box; }
+    * {
+      margin: 0;
+      padding: 0;
+      box-sizing: border-box;
+    }
 
     body {
-      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
-      line-height: 1.6;
-      margin: 0;
-      padding: 2rem 1rem 3rem;
-      max-width: 900px;
-      margin-inline: auto;
-      background: var(--bg);
-      color: var(--text);
+      font-family: var(--sans);
+      background: var(--bg-primary);
+      color: var(--text-primary);
+      line-height: 1.7;
+      overflow-x: hidden;
     }
 
-    a {
-      color: var(--accent);
-      text-decoration: none;
+    /* Animated background pattern */
+    body::before {
+      content: '';
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background-image: 
+        radial-gradient(circle at 10% 20%, rgba(95, 196, 95, 0.08) 0%, transparent 50%),
+        radial-gradient(circle at 90% 80%, rgba(143, 212, 96, 0.08) 0%, transparent 50%),
+        radial-gradient(circle at 50% 50%, rgba(78, 205, 196, 0.05) 0%, transparent 50%);
+      pointer-events: none;
+      z-index: 0;
+      animation: patternShift 30s ease-in-out infinite alternate;
+    }
+
+    @keyframes patternShift {
+      0% { transform: scale(1) rotate(0deg); }
+      100% { transform: scale(1.1) rotate(5deg); }
+    }
+
+    .page {
+      position: relative;
+      max-width: 1140px;
+      margin: 0 auto;
+      padding: 0 24px 100px;
+      z-index: 1;
     }
-    a:hover {
-      text-decoration: underline;
+
+    /* Hero Section */
+    header {
+      padding: 60px 0 80px;
+      text-align: center;
+      position: relative;
+    }
+
+    .badges {
+      display: flex;
+      justify-content: center;
+      gap: 12px;
+      margin-bottom: 32px;
+      animation: fadeIn 0.8s ease 0.2s both;
     }
 
-    h1, h2, h3 {
-      color: var(--text);
+    .badges img {
+      height: 28px;
+      transition: transform 0.2s ease;
+    }
+
+    .badges img:hover {
+      transform: scale(1.05);
     }
 
     h1 {
+      font-size: clamp(3.5rem, 10vw, 6.5rem);
+      font-weight: 800;
+      letter-spacing: -0.04em;
+      margin-bottom: 24px;
+      position: relative;
+      display: inline-block;
+      animation: slideUp 0.8s cubic-bezier(0.16, 1, 0.3, 1);
+    }
+
+    @keyframes slideUp {
+      from {
+        opacity: 0;
+        transform: translateY(30px);
+      }
+      to {
+        opacity: 1;
+        transform: translateY(0);
+      }
+    }
+
+    h1::before {
+      content: '';
+      position: absolute;
+      bottom: -12px;
+      left: 10%;
+      right: 10%;
+      height: 12px;
+      background: linear-gradient(90deg, 
+        var(--accent-forest), 
+        var(--accent-pickle), 
+        var(--accent-lime));
+      border-radius: 6px;
+      z-index: -1;
+      animation: expandBar 1s cubic-bezier(0.16, 1, 0.3, 1) 0.4s both;
+    }
+
+    @keyframes expandBar {
+      from { transform: scaleX(0); }
+      to { transform: scaleX(1); }
+    }
+
+    .tagline {
+      font-size: clamp(1.25rem, 3vw, 1.75rem);
+      color: var(--text-secondary);
+      margin-bottom: 32px;
+      font-weight: 500;
+      animation: fadeIn 0.8s ease 0.6s both;
+    }
+
+    @keyframes fadeIn {
+      from { opacity: 0; }
+      to { opacity: 1; }
+    }
+
+    .tagline strong {
+      color: var(--accent-forest);
+      font-weight: 700;
+    }
+
+    .license-note {
+      color: var(--text-muted);
+      font-size: 0.9375rem;
+      animation: fadeIn 0.8s ease 0.8s both;
+    }
+
+    /* Feature Badges */
+    .feature-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+      gap: 16px;
+      margin: 48px 0 80px;
+      animation: fadeIn 0.8s ease 1s both;
+    }
+
+    .feature-badge {
+      background: var(--bg-card);
+      border: 2px solid var(--border);
+      border-radius: 16px;
+      padding: 20px;
+      text-align: center;
+      transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
+      position: relative;
+      overflow: hidden;
+    }
+
+    .feature-badge::before {
+      content: '';
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 3px;
+      background: linear-gradient(90deg, var(--accent-forest), var(--accent-pickle));
+      transform: scaleX(0);
+      transition: transform 0.3s ease;
+    }
+
+    .feature-badge:hover {
+      transform: translateY(-4px);
+      box-shadow: 0 12px 32px var(--shadow-strong);
+      border-color: var(--accent-pickle);
+    }
+
+    .feature-badge:hover::before {
+      transform: scaleX(1);
+    }
+
+    .feature-icon {
       font-size: 2rem;
-      margin: 0 0 0.5rem;
+      margin-bottom: 8px;
+    }
+
+    .feature-label {
+      font-weight: 600;
+      color: var(--text-primary);
+      font-size: 0.9375rem;
+    }
+
+    /* Section Styles */
+    .section {
+      margin-bottom: 80px;
+      opacity: 0;
+      transform: translateY(30px);
+      transition: opacity 0.8s ease, transform 0.8s ease;
+    }
+
+    .section.visible {
+      opacity: 1;
+      transform: translateY(0);
     }
 
     h2 {
-      font-size: 1.35rem;
-      margin-top: 2rem;
-      border-bottom: 1px solid var(--border);
-      padding-bottom: 0.35rem;
+      font-size: clamp(2.25rem, 5vw, 3rem);
+      font-weight: 700;
+      letter-spacing: -0.02em;
+      margin-bottom: 32px;
+      color: var(--text-primary);
+      position: relative;
+      padding-left: 24px;
+    }
+
+    h2::before {
+      content: '';
+      position: absolute;
+      left: 0;
+      top: 0;
+      bottom: 0;
+      width: 6px;
+      background: linear-gradient(180deg, var(--accent-pickle), var(--accent-sage));
+      border-radius: 3px;
     }
 
     h3 {
-      font-size: 1.1rem;
-      margin-top: 1.2rem;
+      font-size: clamp(1.5rem, 3vw, 1.875rem);
+      font-weight: 600;
+      margin: 48px 0 20px;
+      color: var(--text-primary);
+      letter-spacing: -0.01em;
+    }
+
+    h4 {
+      font-size: 1rem;
+      font-weight: 600;
+      margin: 24px 0 12px;
+      color: var(--accent-forest);
+      text-transform: uppercase;
+      letter-spacing: 0.08em;
+      font-size: 0.875rem;
     }
 
     p {
-      margin: 0.35rem 0 0.6rem;
+      color: var(--text-secondary);
+      margin-bottom: 20px;
+      font-size: 1.0625rem;
     }
 
-    ul {
-      padding-left: 1.25rem;
-      margin: 0.25rem 0 0.6rem;
+    /* Code Blocks */
+    pre {
+      margin: 24px 0;
+      padding: 28px;
+      background: linear-gradient(135deg, #2d2a2e 0%, #1f1d21 100%);
+      border: 2px solid var(--border);
+      border-radius: 16px;
+      overflow-x: auto;
+      position: relative;
+      box-shadow: 0 8px 24px var(--shadow-strong);
+      transition: all 0.3s ease;
     }
 
-    img {
-      max-width: 100%;
-      height: auto;
+    pre:hover {
+      transform: translateY(-2px);
+      box-shadow: 0 12px 32px var(--shadow-strong);
     }
 
-    pre {
-      background: var(--code-bg);
-      color: var(--code-fg);
-      padding: 0.9rem 1rem;
-      border-radius: 8px;
-      overflow: auto;
+    pre::before {
+      content: '';
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      height: 3px;
+      background: linear-gradient(90deg, var(--accent-forest), var(--accent-pickle), var(--accent-lime));
+    }
+
+    pre code {
+      font-family: var(--mono);
       font-size: 0.9rem;
-      margin: 0.6rem 0 1rem;
+      line-height: 1.8;
+      color: #e8e6e3;
     }
 
     code {
-      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
-      background: #f3f4f6;
-      padding: 0.1em 0.35em;
-      border-radius: 4px;
-      font-size: 0.92em;
+      font-family: var(--mono);
+      font-size: 0.9em;
+      background: var(--bg-secondary);
+      padding: 3px 8px;
+      border-radius: 6px;
+      color: var(--accent-forest);
+      border: 1px solid var(--border);
+      font-weight: 500;
     }
 
     pre code {
       background: transparent;
       padding: 0;
+      border: none;
+    }
+
+    /* Syntax highlighting for code */
+    .comment { color: #75715e; }
+    .keyword { color: #ff6b9d; font-weight: 600; }
+    .string { color: #a9dc76; }
+    .function { color: #78dce8; }
+    .number { color: #ab9df2; }
+
+    /* Lists */
+    ul, ol {
+      margin: 20px 0 20px 32px;
+      color: var(--text-secondary);
+    }
+
+    li {
+      margin-bottom: 12px;
+      padding-left: 8px;
+      line-height: 1.8;
+    }
+
+    li::marker {
+      color: var(--accent-pickle);
+      font-weight: 700;
+    }
+
+    /* Cards */
+    .card {
+      background: var(--bg-card);
+      border: 2px solid var(--border);
+      border-radius: 20px;
+      padding: 40px;
+      margin: 40px 0;
+      position: relative;
+      overflow: hidden;
+      box-shadow: 0 4px 16px var(--shadow);
+      transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
+    }
+
+    .card::before {
+      content: '';
+      position: absolute;
+      top: -2px;
+      left: -2px;
+      right: -2px;
+      height: 4px;
+      background: linear-gradient(90deg, var(--accent-forest), var(--accent-pickle), var(--accent-lime));
+    }
+
+    .card:hover {
+      transform: translateY(-4px);
+      box-shadow: 0 16px 48px var(--shadow-strong);
     }
 
+    /* Install Banner */
+    .install-banner {
+      background: linear-gradient(135deg, var(--accent-pickle), var(--accent-lime));
+      color: white;
+      border-radius: 20px;
+      padding: 48px;
+      margin: 56px 0;
+      text-align: center;
+      position: relative;
+      overflow: hidden;
+      box-shadow: 0 12px 40px rgba(95, 196, 95, 0.3);
+    }
+
+    .install-banner::before {
+      content: '';
+      position: absolute;
+      top: -50%;
+      left: -50%;
+      width: 200%;
+      height: 200%;
+      background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
+      animation: rotate 20s linear infinite;
+    }
+
+    @keyframes rotate {
+      0% { transform: rotate(0deg); }
+      100% { transform: rotate(360deg); }
+    }
+
+    .install-banner h3 {
+      margin: 0 0 24px;
+      font-size: 2rem;
+      color: white;
+      position: relative;
+      z-index: 1;
+    }
+
+    .install-banner pre {
+      background: rgba(0, 0, 0, 0.3);
+      border: 2px solid rgba(255, 255, 255, 0.2);
+      display: inline-block;
+      margin: 0;
+      position: relative;
+      z-index: 1;
+      backdrop-filter: blur(10px);
+    }
+
+    .install-banner pre code {
+      color: white;
+      font-weight: 600;
+    }
+
+    /* Table */
     table {
-      border-collapse: collapse;
       width: 100%;
-      margin: 0.75rem 0 1rem;
-      font-size: 0.95rem;
+      border-collapse: separate;
+      border-spacing: 0;
+      margin: 32px 0;
       background: var(--bg-card);
+      border-radius: 16px;
+      overflow: hidden;
+      box-shadow: 0 4px 16px var(--shadow);
+      border: 2px solid var(--border);
     }
 
-    th, td {
-      border: 1px solid var(--border);
-      padding: 0.45rem 0.6rem;
+    thead {
+      background: linear-gradient(135deg, var(--accent-pickle), var(--accent-lime));
+      color: white;
+    }
+
+    th {
+      padding: 16px 20px;
       text-align: left;
+      font-weight: 700;
+      text-transform: uppercase;
+      letter-spacing: 0.05em;
+      font-size: 0.875rem;
     }
 
-    thead {
-      background: #f3f4f6;
-      font-weight: 600;
+    td {
+      padding: 16px 20px;
+      border-bottom: 1px solid var(--border);
+      color: var(--text-secondary);
     }
 
-    tbody tr:nth-child(even) {
-      background: #f9fafb;
+    tbody tr:last-child td {
+      border-bottom: none;
     }
 
-    .badges {
-      display: flex;
-      flex-wrap: wrap;
-      gap: 0.75rem;
-      align-items: center;
-      margin-bottom: 1.25rem;
+    tbody tr {
+      transition: background 0.2s ease;
     }
 
-    .muted {
-      color: var(--muted);
-      font-size: 0.9rem;
+    tbody tr:hover {
+      background: var(--bg-secondary);
     }
 
-    .note {
-      font-size: 0.9rem;
-      color: var(--muted);
-      border-left: 3px solid var(--border);
-      padding-left: 0.75rem;
-      margin: 0.3rem 0 0.7rem;
+    td code {
+      font-size: 0.875rem;
     }
 
-    .pill-note {
-      display: inline-block;
-      font-size: 0.9rem;
-      padding: 0.3rem 0.7rem;
-      border-radius: 999px;
-      background: #eff6ff;
-      color: #1d4ed8;
-      border: 1px solid #bfdbfe;
-      margin: 0.35rem 0 0.75rem;
+    /* Callout Boxes */
+    .callout {
+      background: var(--bg-secondary);
+      border-left: 6px solid var(--accent-mint);
+      border-radius: 0 12px 12px 0;
+      padding: 24px 28px;
+      margin: 32px 0;
+      box-shadow: 0 2px 8px var(--shadow);
     }
 
-    .section-intro {
-      margin-bottom: 0.4rem;
+    .callout-warning {
+      border-left-color: var(--accent-lime);
+      background: #fefce8;
     }
-  </style>
-</head>
-<body>
 
-  <div class="badges">
-    <a href="https://patx.github.io/pickledb">
-      <img src="https://patx.github.io/pickledb/logo.png" alt="pickleDB Logo">
-    </a>
-    <a href="https://pepy.tech/projects/pickledb">
-      <img src="https://static.pepy.tech/badge/pickledb" alt="PyPI Downloads">
-    </a>
-    <a href="https://github.com/patx/pickledb">
-      <img src="https://img.shields.io/github/stars/patx/pickledb?style=flat&logo=github" alt="GitHub Stars">
-    </a>
-  </div>
+    .callout-success {
+      border-left-color: var(--accent-pickle);
+      background: #f0fdf4;
+    }
 
-  <h1>pickleDB</h1>
-  <p class="section-intro">
-    <code>pickleDB</code> is a lightweight, JSON-backed key-value store for Python, designed for
-    <strong>simple APIs</strong>, <strong>fast in-memory operations</strong>, and a
-    <strong>unified sync/async interface</strong>. BSD 3-Clause License · © Harrison Erd.
-  </p>
+    .callout p {
+      margin: 0;
+      color: var(--text-primary);
+    }
 
-  <h2>Quick Start</h2>
+    .callout strong {
+      color: var(--accent-mint);
+      font-weight: 700;
+    }
 
-  <h3>Install</h3>
-  <pre><code class="language-bash">pip install pickledb
-</code></pre>
+    .callout-warning strong {
+      color: #84a617;
+    }
 
-  <h3>Sync Example</h3>
-  <pre><code class="language-python">from pickledb import PickleDB
+    .callout-success strong {
+      color: var(--accent-forest);
+    }
 
-# Bind to a JSON file; no I/O yet
-db = PickleDB("data.json").load()  # load from disk into memory (if file exists)
+    /* Performance Table Highlight */
+    .perf-table {
+      border: 3px solid var(--accent-pickle);
+    }
 
-db.set("username", "alice")
-db.set("theme", {"color": "blue", "font": "sans-serif"})
+    .perf-table thead {
+      background: linear-gradient(135deg, var(--accent-pickle), var(--accent-lime));
+    }
 
-print(db.get("username"))  # → "alice"
+    /* Quote */
+    .quote-block {
+      font-size: 1.5rem;
+      font-style: italic;
+      color: var(--text-muted);
+      text-align: center;
+      margin: 64px 0;
+      padding: 40px;
+      position: relative;
+    }
 
-db.save()  # atomically write in-memory DB back to data.json
-</code></pre>
+    .quote-block::before {
+      content: '"';
+      position: absolute;
+      top: -20px;
+      left: 50%;
+      transform: translateX(-50%);
+      font-size: 6rem;
+      color: var(--accent-lime);
+      opacity: 0.3;
+      font-family: Georgia, serif;
+      line-height: 1;
+    }
 
-  <h3>Async Example</h3>
-  <pre><code class="language-python">import asyncio
-from pickledb import PickleDB
+    /* Links */
+    a {
+      color: var(--accent-forest);
+      text-decoration: none;
+      font-weight: 500;
+      transition: all 0.2s ease;
+      position: relative;
+    }
 
-async def main():
-    db = await PickleDB("data.json").load()
+    a::after {
+      content: '';
+      position: absolute;
+      bottom: -2px;
+      left: 0;
+      width: 0;
+      height: 2px;
+      background: var(--accent-pickle);
+      transition: width 0.2s ease;
+    }
 
-    await db.set("score", 42)
-    value = await db.get("score")
-    print(value)  # → 42
+    a:hover::after {
+      width: 100%;
+    }
 
-    await db.save()
+    /* Context Manager Showcase */
+    .context-showcase {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
+      gap: 32px;
+      margin: 40px 0;
+    }
 
-asyncio.run(main())
-</code></pre>
+    .context-card {
+      background: var(--bg-card);
+      border: 2px solid var(--border);
+      border-radius: 16px;
+      padding: 32px;
+      box-shadow: 0 4px 16px var(--shadow);
+      transition: all 0.3s ease;
+    }
 
-  <div class="note">
-    <strong>Explicit I/O:</strong> the constructor <code>PickleDB("data.json")</code> never touches disk.
-    <code>load()</code> and <code>save()</code> are explicit (or handled by context managers).
-  </div>
+    .context-card:hover {
+      transform: translateY(-4px);
+      box-shadow: 0 12px 32px var(--shadow-strong);
+    }
+
+    .context-card h4 {
+      margin-top: 0;
+      margin-bottom: 20px;
+      font-size: 1.25rem;
+      color: var(--text-primary);
+      text-transform: none;
+      letter-spacing: -0.01em;
+    }
+
+    /* When Not To Use Section */
+    .warning-list {
+      background: #fefce8;
+      border: 2px solid var(--accent-lime);
+      border-radius: 16px;
+      padding: 32px;
+      margin: 32px 0;
+    }
+
+    .warning-list h3 {
+      margin-top: 0;
+      color: #84a617;
+    }
+
+    .warning-list ul {
+      margin-bottom: 0;
+    }
+
+    .warning-list li {
+      color: #4d5a0f;
+    }
+
+    /* Footer */
+    .footer-note {
+      text-align: center;
+      color: var(--text-muted);
+      font-size: 0.9375rem;
+      margin-top: 80px;
+      padding-top: 40px;
+      border-top: 2px solid var(--border);
+    }
+
+    /* Responsive */
+    @media (max-width: 768px) {
+      .page {
+        padding: 0 16px 80px;
+      }
+
+      header {
+        padding: 40px 0 60px;
+      }
+
+      h2 {
+        padding-left: 16px;
+      }
 
-  <h2>Design & Behavior</h2>
-  <ul>
-    <li><strong>In-memory first:</strong> all data lives in memory while you work; JSON is used only for persistence.</li>
-    <li><strong>Method-only API:</strong> no dict syntax. Use <code>set()</code>, <code>get()</code>, <code>remove()</code>, etc.</li>
-    <li><strong>Unified sync/async:</strong> every core method works in both worlds via the same name.</li>
-    <li><strong>Atomic disk writes:</strong> uses a temp file and <code>os.replace</code> to avoid partial writes.</li>
-    <li><strong>No hidden autosave:</strong> nothing is written to disk unless you call <code>save()</code> or exit a context manager cleanly.</li>
-    <li><strong>Single file simplicity:</strong> because the database is saved to a single file pickleDB is not thread safe or process safe. If you need this and you want to use the same/similar API check out <a href="https://github.com/patx/mkvdb">mkvDB</a>.</li>
-  </ul>
-
-  <h2>Context Managers</h2>
-
-  <h3>Synchronous</h3>
-  <pre><code class="language-python">from pickledb import PickleDB
-
-with PickleDB("data.json") as db:
-    # On enter: db.load()
-    db.set("foo", "bar")
-    db.set("hello", "world")
-    # On successful exit: db.save()
-</code></pre>
-
-  <h3>Asynchronous</h3>
-  <pre><code class="language-python">import asyncio
-from pickledb import PickleDB
-
-async def main():
-    async with PickleDB("data.json") as db:
-        # On enter: await db.load()
-        await db.set("foo", "bar")
-        await db.set("hello", "world")
-        # On successful exit: await db.save()
-
-asyncio.run(main())
-</code></pre>
-
-  <h2>Core Methods</h2>
-
-  <p class="section-intro">
-    These are the only methods you really need to know. Every method works the same in sync and async code;
-    just add <code>await</code> when you’re in an async function. All of these methods are part of the <code>PickleDB</code> class.
-  </p>
-
-  <table>
-    <thead>
-      <tr>
-        <th>Method</th>
-        <th>Sync usage</th>
-        <th>Async usage</th>
-        <th>Description</th>
-      </tr>
-    </thead>
-    <tbody>
-      <tr>
-        <td><code>load()</code></td>
-        <td><code>db.load()</code></td>
-        <td><code>await db.load()</code></td>
-        <td>Load (or reload) the JSON file into memory. Returns the same <code>PickleDB</code> instance for chaining.</td>
-      </tr>
-      <tr>
-        <td><code>save()</code></td>
-        <td><code>db.save()</code></td>
-        <td><code>await db.save()</code></td>
-        <td>Atomically save the in-memory database back to disk.</td>
-      </tr>
-      <tr>
-        <td><code>set(key, value)</code></td>
-        <td><code>db.set(k, v)</code></td>
-        <td><code>await db.set(k, v)</code></td>
-        <td>Store a value under <code>key</code>. Keys are coerced to <code>str</code>; values must be JSON-serializable.</td>
-      </tr>
-      <tr>
-        <td><code>get(key, default=None)</code></td>
-        <td><code>db.get(k, d)</code></td>
-        <td><code>await db.get(k, d)</code></td>
-        <td>Retrieve the stored value, or <code>default</code> if the key doesn’t exist.</td>
-      </tr>
-      <tr>
-        <td><code>remove(key)</code></td>
-        <td><code>db.remove(k)</code></td>
-        <td><code>await db.remove(k)</code></td>
-        <td>Delete a key. Returns <code>True</code> if it existed and was removed.</td>
-      </tr>
-      <tr>
-        <td><code>all()</code></td>
-        <td><code>db.all()</code></td>
-        <td><code>await db.all()</code></td>
-        <td>Return a list of all keys in the in-memory database.</td>
-      </tr>
-      <tr>
-        <td><code>purge()</code></td>
-        <td><code>db.purge()</code></td>
-        <td><code>await db.purge()</code></td>
-        <td>Clear the in-memory database. Returns <code>True</code>.</td>
-      </tr>
-    </tbody>
-  </table>
-
-  <p class="note">
-    pickleDB is intentionally <strong>method-based only</strong>. Dict-style access like
-    <code>db["key"]</code> or <code>db["key"] = value</code> is not supported.
-  </p>
-
-  <h2>Common Patterns</h2>
-
-  <h3>Storing Complex Data</h3>
-  <pre><code class="language-python"># Store a dictionary
-db.set("user", {"name": "Alice", "age": 30})
-
-# Update it
-user = db.get("user")
-user["age"] += 1
-db.set("user", user)
-
-print(db.get("user"))
-# {'name': 'Alice', 'age': 31}
-</code></pre>
-
-  <h3>Lists / Simple Queues</h3>
-  <pre><code class="language-python">db.set("tasks", ["write", "test", "deploy"])
-
-tasks = db.get("tasks", [])
-tasks.append("celebrate")
-db.set("tasks", tasks)
-
-print(db.get("tasks"))
-# ['write', 'test', 'deploy', 'celebrate']
-</code></pre>
-
-  <h3>Namespace Keys</h3>
-  <pre><code class="language-python">db.set("user:1", {"name": "Alice"})
-db.set("user:2", {"name": "Bob"})
-
-def keys_with_prefix(db, prefix):
-    return [k for k in db.all() if k.startswith(prefix)]
-
-print(keys_with_prefix(db, "user:"))
-# ['user:1', 'user:2']
-</code></pre>
-
-  <h3>Basic TTL Pattern (App-Level)</h3>
-  <pre><code class="language-python">import time
-
-def set_with_ttl(db, key, value, ttl_seconds):
+      h2::before {
+        width: 4px;
+      }
+
+      .context-showcase {
+        grid-template-columns: 1fr;
+      }
+
+      .feature-grid {
+        grid-template-columns: 1fr;
+      }
+
+      .install-banner {
+        padding: 32px 24px;
+      }
+
+      .quote-block::before {
+        font-size: 4rem;
+      }
+    }
+
+    /* Smooth scrolling */
+    html {
+      scroll-behavior: smooth;
+    }
+
+    /* Selection */
+    ::selection {
+      background: var(--accent-pickle);
+      color: white;
+    }
+
+    /* Fade-in utility */
+    .fade-in {
+      opacity: 0;
+      transform: translateY(20px);
+      transition: opacity 0.6s ease, transform 0.6s ease;
+    }
+
+    .fade-in.visible {
+      opacity: 1;
+      transform: translateY(0);
+    }
+  </style>
+
+  <script>
+    document.addEventListener("DOMContentLoaded", () => {
+      // Intersection Observer for scroll animations
+      const observer = new IntersectionObserver((entries) => {
+        entries.forEach(entry => {
+          if (entry.isIntersecting) {
+            entry.target.classList.add('visible');
+          }
+        });
+      }, { threshold: 0.1 });
+      
+      document.querySelectorAll('.section, .fade-in').forEach(el => observer.observe(el));
+    });
+  </script>
+</head>
+<body>
+
+  <div class="page">
+    <header>
+      <div class="badges">
+        <a href="https://pepy.tech/projects/pickledb">
+          <img src="https://static.pepy.tech/badge/pickledb" alt="PyPI Downloads">
+        </a>
+        <a href="https://github.com/patx/pickledb">
+          <img src="https://img.shields.io/github/stars/patx/pickledb?style=flat&logo=github" alt="GitHub Stars">
+        </a>
+      </div>
+
+      <h1><img src="https://patx.github.io/pickledb/logo.png" alt="pickleDB"></h1>
+      <p class="tagline">
+        JSON-backed key-value store with <strong>simple APIs</strong>, <strong>fast in-memory operations</strong>, and a <strong>unified sync/async interface</strong>
+      </p>
+      <p class="license-note">BSD 3-Clause License · © Harrison Erd</p>
+
+      <div class="feature-grid">
+        <div class="feature-badge">
+          <div class="feature-icon">⚡</div>
+          <div class="feature-label">Lightning Fast</div>
+        </div>
+        <div class="feature-badge">
+          <div class="feature-icon">🎯</div>
+          <div class="feature-label">Simple API</div>
+        </div>
+        <div class="feature-badge">
+          <div class="feature-icon">🔄</div>
+          <div class="feature-label">Sync & Async</div>
+        </div>
+        <div class="feature-badge">
+          <div class="feature-icon">💾</div>
+          <div class="feature-label">JSON Native</div>
+        </div>
+      </div>
+    </header>
+
+    <section class="section">
+      <div class="install-banner">
+        <h3>Get Started in Seconds</h3>
+        <pre><code>pip install pickledb</code></pre>
+      </div>
+    </section>
+
+    <section class="section fade-in">
+      <h2>Quick Start</h2>
+
+      <div class="context-showcase">
+        <div class="context-card">
+          <h4>Synchronous Example</h4>
+          <pre><code><span class="keyword">from</span> pickledb <span class="keyword">import</span> PickleDB
+
+<span class="comment"># Bind to a JSON file; no I/O yet</span>
+db = PickleDB(<span class="string">"data.json"</span>).load()
+
+db.set(<span class="string">"username"</span>, <span class="string">"alice"</span>)
+db.set(<span class="string">"theme"</span>, {
+    <span class="string">"color"</span>: <span class="string">"blue"</span>,
+    <span class="string">"font"</span>: <span class="string">"sans-serif"</span>
+})
+
+<span class="function">print</span>(db.get(<span class="string">"username"</span>))  <span class="comment"># → "alice"</span>
+
+db.save()  <span class="comment"># atomically write to disk</span></code></pre>
+        </div>
+
+        <div class="context-card">
+          <h4>Asynchronous Example</h4>
+          <pre><code><span class="keyword">import</span> asyncio
+<span class="keyword">from</span> pickledb <span class="keyword">import</span> PickleDB
+
+<span class="keyword">async def</span> <span class="function">main</span>():
+    db = <span class="keyword">await</span> PickleDB(<span class="string">"data.json"</span>).load()
+
+    <span class="keyword">await</span> db.set(<span class="string">"score"</span>, <span class="number">42</span>)
+    value = <span class="keyword">await</span> db.get(<span class="string">"score"</span>)
+    <span class="function">print</span>(value)  <span class="comment"># → 42</span>
+
+    <span class="keyword">await</span> db.save()
+
+asyncio.run(main())</code></pre>
+        </div>
+      </div>
+
+      <div class="callout">
+        <p><strong>Explicit I/O:</strong> The constructor <code>PickleDB("data.json")</code> never touches disk. <code>load()</code> and <code>save()</code> are explicit (or handled by context managers).</p>
+      </div>
+    </section>
+
+    <section class="section fade-in">
+      <h2>Design Philosophy</h2>
+
+      <div class="card">
+        <ul>
+          <li><strong>In-memory first:</strong> All data lives in memory while you work; JSON is used only for persistence</li>
+          <li><strong>Method-only API:</strong> No dict syntax. Use <code>set()</code>, <code>get()</code>, <code>remove()</code>, etc.</li>
+          <li><strong>Unified sync/async:</strong> Every core method works in both worlds via the same name</li>
+          <li><strong>Atomic disk writes:</strong> Uses a temp file and <code>os.replace</code> to avoid partial writes</li>
+          <li><strong>No hidden autosave:</strong> Nothing is written to disk unless you call <code>save()</code> or exit a context manager cleanly</li>
+          <li><strong>Single file simplicity:</strong> Because the database is saved to a single file, pickleDB is not thread-safe or process-safe. If you need this, check out <a href="https://github.com/patx/mongokv">mongoKV</a></li>
+        </ul>
+      </div>
+    </section>
+
+    <section class="section fade-in">
+      <h2>Context Managers</h2>
+      <p>Automatically load and save your database with context managers:</p>
+
+      <div class="context-showcase">
+        <div class="context-card">
+          <h4>Synchronous Context</h4>
+          <pre><code><span class="keyword">from</span> pickledb <span class="keyword">import</span> PickleDB
+
+<span class="keyword">with</span> PickleDB(<span class="string">"data.json"</span>) <span class="keyword">as</span> db:
+    <span class="comment"># On enter: db.load()</span>
+    db.set(<span class="string">"foo"</span>, <span class="string">"bar"</span>)
+    db.set(<span class="string">"hello"</span>, <span class="string">"world"</span>)
+    <span class="comment"># On exit: db.save()</span></code></pre>
+        </div>
+
+        <div class="context-card">
+          <h4>Asynchronous Context</h4>
+          <pre><code><span class="keyword">import</span> asyncio
+<span class="keyword">from</span> pickledb <span class="keyword">import</span> PickleDB
+
+<span class="keyword">async def</span> <span class="function">main</span>():
+    <span class="keyword">async with</span> PickleDB(<span class="string">"data.json"</span>) <span class="keyword">as</span> db:
+        <span class="comment"># On enter: await db.load()</span>
+        <span class="keyword">await</span> db.set(<span class="string">"foo"</span>, <span class="string">"bar"</span>)
+        <span class="keyword">await</span> db.set(<span class="string">"hello"</span>, <span class="string">"world"</span>)
+        <span class="comment"># On exit: await db.save()</span>
+
+asyncio.run(main())</code></pre>
+        </div>
+      </div>
+    </section>
+
+    <section class="section fade-in">
+      <h2>Core Methods</h2>
+      <p>These are the only methods you need to know. Every method works the same in sync and async code — just add <code>await</code> when you're in an async function.</p>
+
+      <table>
+        <thead>
+          <tr>
+            <th>Method</th>
+            <th>Sync Usage</th>
+            <th>Async Usage</th>
+            <th>Description</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td><code>load()</code></td>
+            <td><code>db.load()</code></td>
+            <td><code>await db.load()</code></td>
+            <td>Load (or reload) the JSON file into memory. Returns the same instance for chaining.</td>
+          </tr>
+          <tr>
+            <td><code>save()</code></td>
+            <td><code>db.save()</code></td>
+            <td><code>await db.save()</code></td>
+            <td>Atomically save the in-memory database back to disk.</td>
+          </tr>
+          <tr>
+            <td><code>set(key, value)</code></td>
+            <td><code>db.set(k, v)</code></td>
+            <td><code>await db.set(k, v)</code></td>
+            <td>Store a value under <code>key</code>. Keys are coerced to <code>str</code>; values must be JSON-serializable.</td>
+          </tr>
+          <tr>
+            <td><code>get(key, default=None)</code></td>
+            <td><code>db.get(k, d)</code></td>
+            <td><code>await db.get(k, d)</code></td>
+            <td>Retrieve the stored value, or <code>default</code> if the key doesn't exist.</td>
+          </tr>
+          <tr>
+            <td><code>remove(key)</code></td>
+            <td><code>db.remove(k)</code></td>
+            <td><code>await db.remove(k)</code></td>
+            <td>Delete a key. Returns <code>True</code> if it existed and was removed.</td>
+          </tr>
+          <tr>
+            <td><code>all()</code></td>
+            <td><code>db.all()</code></td>
+            <td><code>await db.all()</code></td>
+            <td>Return a list of all keys in the in-memory database.</td>
+          </tr>
+          <tr>
+            <td><code>purge()</code></td>
+            <td><code>db.purge()</code></td>
+            <td><code>await db.purge()</code></td>
+            <td>Clear the in-memory database. Returns <code>True</code>.</td>
+          </tr>
+        </tbody>
+      </table>
+
+      <div class="callout-warning callout">
+        <p><strong>Important:</strong> pickleDB is intentionally <strong>method-based only</strong>. Dict-style access like <code>db["key"]</code> or <code>db["key"] = value</code> is not supported.</p>
+      </div>
+    </section>
+
+    <section class="section fade-in">
+      <h2>Common Patterns</h2>
+
+      <h3>Storing Complex Data</h3>
+      <pre><code><span class="comment"># Store a dictionary</span>
+db.set(<span class="string">"user"</span>, {<span class="string">"name"</span>: <span class="string">"Alice"</span>, <span class="string">"age"</span>: <span class="number">30</span>})
+
+<span class="comment"># Update it</span>
+user = db.get(<span class="string">"user"</span>)
+user[<span class="string">"age"</span>] += <span class="number">1</span>
+db.set(<span class="string">"user"</span>, user)
+
+<span class="function">print</span>(db.get(<span class="string">"user"</span>))
+<span class="comment"># {'name': 'Alice', 'age': 31}</span></code></pre>
+
+      <h3>Lists / Simple Queues</h3>
+      <pre><code>db.set(<span class="string">"tasks"</span>, [<span class="string">"write"</span>, <span class="string">"test"</span>, <span class="string">"deploy"</span>])
+
+tasks = db.get(<span class="string">"tasks"</span>, [])
+tasks.append(<span class="string">"celebrate"</span>)
+db.set(<span class="string">"tasks"</span>, tasks)
+
+<span class="function">print</span>(db.get(<span class="string">"tasks"</span>))
+<span class="comment"># ['write', 'test', 'deploy', 'celebrate']</span></code></pre>
+
+      <h3>Namespace Keys</h3>
+      <pre><code>db.set(<span class="string">"user:1"</span>, {<span class="string">"name"</span>: <span class="string">"Alice"</span>})
+db.set(<span class="string">"user:2"</span>, {<span class="string">"name"</span>: <span class="string">"Bob"</span>})
+
+<span class="keyword">def</span> <span class="function">keys_with_prefix</span>(db, prefix):
+    <span class="keyword">return</span> [k <span class="keyword">for</span> k <span class="keyword">in</span> db.all() <span class="keyword">if</span> k.startswith(prefix)]
+
+<span class="function">print</span>(keys_with_prefix(db, <span class="string">"user:"</span>))
+<span class="comment"># ['user:1', 'user:2']</span></code></pre>
+
+      <h3>Basic TTL Pattern</h3>
+      <pre><code><span class="keyword">import</span> time
+
+<span class="keyword">def</span> <span class="function">set_with_ttl</span>(db, key, value, ttl_seconds):
     db.set(key, {
-        "value": value,
-        "expires_at": time.time() + ttl_seconds,
+        <span class="string">"value"</span>: value,
+        <span class="string">"expires_at"</span>: time.time() + ttl_seconds,
     })
 
-def get_if_fresh(db, key):
+<span class="keyword">def</span> <span class="function">get_if_fresh</span>(db, key):
     data = db.get(key)
-    if not data:
-        return None
-    if time.time() &lt; data.get("expires_at", 0):
-        return data["value"]
+    <span class="keyword">if not</span> data:
+        <span class="keyword">return</span> <span class="keyword">None</span>
+    <span class="keyword">if</span> time.time() < data.get(<span class="string">"expires_at"</span>, <span class="number">0</span>):
+        <span class="keyword">return</span> data[<span class="string">"value"</span>]
     db.remove(key)
-    return None
-
-set_with_ttl(db, "session", "active", ttl_seconds=5)
-time.sleep(3)
-print(get_if_fresh(db, "session"))  # 'active'
-time.sleep(3)
-print(get_if_fresh(db, "session"))  # None
-</code></pre>
-
-  <h2>Performance Snapshot</h2>
-
-  <p class="section-intro">
-    Example timings loading, reading, and saving large JSON payloads using async mode on a Dell XPS 9350, Ubuntu 24.04:
-  </p>
-
-  <table>
-    <thead>
-      <tr>
-        <th>Entries</th>
-        <th>Load (into memory)</th>
-        <th>Bulk read</th>
-        <th>Save (to disk)</th>
-      </tr>
-    </thead>
-    <tbody>
-      <tr>
-        <td><strong>1M</strong></td>
-        <td>0.68 s</td>
-        <td>0.64 s</td>
-        <td>0.03 s</td>
-      </tr>
-      <tr>
-        <td><strong>10M</strong></td>
-        <td>7.48 s</td>
-        <td>7.27 s</td>
-        <td>0.22 s</td>
-      </tr>
-      <tr>
-        <td><strong>50M</strong></td>
-        <td>43.36 s</td>
-        <td>36.53 s</td>
-        <td>1.09 s</td>
-      </tr>
-    </tbody>
-  </table>
-
-  <p class="muted">
-    Full benchmark script: <a href="https://gist.github.com/patx/025ed3a10482459f35c738228ebd0721">available here</a>.
-  </p>
-
-  <h2>When Not to Use pickleDB</h2>
-  <ul>
-    <li>You need multi-process or multi-host concurrency.</li>
-    <li>ASGI web servers or frameworks. <small>unless your using a single worker ;)</small></li>
-    <li>Your dataset is too large to comfortably fit in memory.</li>
-    <li>You need rich querying, indexing, or joins.</li>
-  </ul>
-  <p class="muted">
-    In those cases, consider Redis, SQLite, PostgreSQL, MongoDB, or <a href="https://github.com/patx/mkvdb">mkvDB</a>.
-  </p>
-
-  <p class="muted">
-    Issues, questions, or ideas? Open an issue on
-    <a href="https://github.com/patx/pickledb/issues">GitHub</a>.
-  </p>
-
-    <div class="github-banner">
-        <a href="https://github.com/patx/pickledb">
-            <img style="position: fixed; top: 0; right: 0; border: 0;" src="https://github.blog/wp-content/uploads/2008/12/forkme_right_green_007200.png" alt="Fork me on GitHub">
-        </a>
+    <span class="keyword">return</span> <span class="keyword">None</span>
+
+set_with_ttl(db, <span class="string">"session"</span>, <span class="string">"active"</span>, ttl_seconds=<span class="number">5</span>)
+time.sleep(<span class="number">3</span>)
+<span class="function">print</span>(get_if_fresh(db, <span class="string">"session"</span>))  <span class="comment"># 'active'</span>
+time.sleep(<span class="number">3</span>)
+<span class="function">print</span>(get_if_fresh(db, <span class="string">"session"</span>))  <span class="comment"># None</span></code></pre>
+    </section>
+
+    <section class="section fade-in">
+      <h2>Performance Snapshot</h2>
+      <p>Example timings loading, reading, and saving large JSON payloads using async mode on a Dell XPS 9350, Ubuntu 24.04:</p>
+
+      <table class="perf-table">
+        <thead>
+          <tr>
+            <th>Entries</th>
+            <th>Load (into memory)</th>
+            <th>Bulk Read</th>
+            <th>Save (to disk)</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td><strong>1M</strong></td>
+            <td>0.68 s</td>
+            <td>0.64 s</td>
+            <td>0.03 s</td>
+          </tr>
+          <tr>
+            <td><strong>10M</strong></td>
+            <td>7.48 s</td>
+            <td>7.27 s</td>
+            <td>0.22 s</td>
+          </tr>
+          <tr>
+            <td><strong>50M</strong></td>
+            <td>43.36 s</td>
+            <td>36.53 s</td>
+            <td>1.09 s</td>
+          </tr>
+        </tbody>
+      </table>
+
+      <div class="callout-success callout">
+        <p><strong>Benchmark Details:</strong> Full benchmark script available at <a href="https://gist.github.com/patx/025ed3a10482459f35c738228ebd0721">this GitHub Gist</a>.</p>
+      </div>
+    </section>
+
+    <section class="section fade-in">
+      <div class="warning-list">
+        <h3>When Not to Use pickleDB</h3>
+        <ul>
+          <li>You need multi-process or multi-host concurrency</li>
+          <li>ASGI web servers or frameworks <small>(unless you're using a single worker)</small></li>
+          <li>Your dataset is too large to comfortably fit in memory</li>
+          <li>You need rich querying, indexing, or joins</li>
+        </ul>
+        <p style="color: var(--text-secondary); margin-top: 24px;">
+          In those cases, consider Redis, SQLite, PostgreSQL, MongoDB, or <a href="https://github.com/patx/mongokv">mongoKV</a>.
+        </p>
+      </div>
+    </section>
+
+    <div class="footer-note">
+      <p>Issues, questions, or ideas? Open an issue on <a href="https://github.com/patx/pickledb/issues">GitHub</a>.</p>
+      <p style="margin-top: 16px;">Made with 🍺 by <a href="https://harrisonerd.com/">Harrison Erd</a></p>
     </div>
-    
+  </div>
+
 </body>
 </html>
-
diff --git a/test.py b/test.py
new file mode 100644
index 0000000..e696a12
--- /dev/null
+++ b/test.py
@@ -0,0 +1,13 @@
+import asyncio
+from pickledb import PickleDB
+
+async def main():
+    db = await PickleDB("data.json").load()
+
+    await db.set("score", 42)
+    value = await db.get("score")
+    print(value)  # → 42
+
+    await db.save()
+
+asyncio.run(main())