Bläddra i källkod

clean up site

nickp 3 månader sedan
förälder
incheckning
acebe92a6a
7 ändrade filer med 883 tillägg och 127 borttagningar
  1. 420 2
      src/_includes/base.njk
  2. 121 5
      src/all-documents.njk
  3. 104 67
      src/documents.njk
  4. 104 2
      src/index.njk
  5. 42 17
      src/locations.njk
  6. 42 17
      src/organizations.njk
  7. 50 17
      src/people.njk

+ 420 - 2
src/_includes/base.njk

@@ -3,7 +3,7 @@
 <head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>{{ title }} - Document Explorer</title>
+  <title>{{ title }} - Epstein Archive</title>
   <style>
     * {
       margin: 0;
@@ -275,12 +275,430 @@
       outline: none;
       border-color: #3498db;
     }
+
+    /* Alphabet Navigation - used by entity pages */
+    .alphabet-nav {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 0.5rem;
+      margin: 1.5rem 0;
+      padding: 1rem;
+      background: white;
+      border-radius: 8px;
+      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+    }
+
+    .letter-btn {
+      padding: 0.5rem 0.75rem;
+      border: 1px solid #ddd;
+      background: white;
+      cursor: pointer;
+      border-radius: 4px;
+      font-weight: 500;
+      transition: all 0.2s;
+    }
+
+    .letter-btn:hover {
+      background: #f0f0f0;
+      border-color: #3498db;
+    }
+
+    .letter-btn.active {
+      background: #3498db;
+      color: white;
+      border-color: #3498db;
+    }
+
+    /* Entity Items (collapsible) - used by people/organizations/locations */
+    .entity-item {
+      background: white;
+      border: 1px solid #e0e0e0;
+      border-radius: 8px;
+      margin-bottom: 0.75rem;
+      transition: all 0.2s;
+    }
+
+    .entity-item:hover {
+      border-color: #3498db;
+      box-shadow: 0 2px 8px rgba(52, 152, 219, 0.1);
+    }
+
+    .entity-summary {
+      padding: 1rem 1.5rem;
+      cursor: pointer;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      font-size: 1.1rem;
+      user-select: none;
+    }
+
+    .entity-summary::-webkit-details-marker {
+      display: none;
+    }
+
+    .entity-summary::before {
+      content: '▶';
+      margin-right: 0.75rem;
+      color: #666;
+      transition: transform 0.2s;
+    }
+
+    details[open] .entity-summary::before {
+      transform: rotate(90deg);
+    }
+
+    .entity-name {
+      font-weight: 600;
+      color: #2c3e50;
+    }
+
+    .entity-count {
+      font-size: 0.9rem;
+      color: #666;
+      background: #f0f0f0;
+      padding: 0.25rem 0.75rem;
+      border-radius: 12px;
+    }
+
+    .entity-content {
+      padding: 0 1.5rem 1.5rem 3.25rem;
+      display: grid;
+      gap: 0.75rem;
+    }
+
+    /* Compact Document Cards - used in entity pages */
+    .document-card-compact {
+      padding: 0.75rem;
+      background: #f8f9fa;
+      border-radius: 4px;
+      border-left: 3px solid #3498db;
+    }
+
+    .document-card-compact:hover {
+      background: #e9ecef;
+    }
+
+    .doc-link {
+      color: #3498db;
+      text-decoration: none;
+      font-size: 1rem;
+    }
+
+    .doc-link:hover {
+      text-decoration: underline;
+    }
+
+    .meta-compact {
+      font-size: 0.85rem;
+      color: #666;
+      margin-top: 0.25rem;
+    }
+
+    /* Pagination Controls - used by all-documents page */
+    .pagination-info {
+      margin: 1rem 0;
+      text-align: center;
+      color: #666;
+    }
+
+    .pagination-controls {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      gap: 1rem;
+      margin: 2rem 0;
+      flex-wrap: wrap;
+    }
+
+    .page-btn {
+      padding: 0.5rem 1rem;
+      border: 1px solid #ddd;
+      background: white;
+      cursor: pointer;
+      border-radius: 4px;
+      font-weight: 500;
+      transition: all 0.2s;
+    }
+
+    .page-btn:hover:not(:disabled) {
+      background: #f0f0f0;
+      border-color: #3498db;
+    }
+
+    .page-btn:disabled {
+      opacity: 0.5;
+      cursor: not-allowed;
+    }
+
+    .page-num {
+      padding: 0.5rem 0.75rem;
+      border: 1px solid #ddd;
+      background: white;
+      cursor: pointer;
+      border-radius: 4px;
+      transition: all 0.2s;
+      min-width: 40px;
+      text-align: center;
+    }
+
+    .page-num:hover {
+      background: #f0f0f0;
+      border-color: #3498db;
+    }
+
+    .page-num.active {
+      background: #3498db;
+      color: white;
+      border-color: #3498db;
+    }
+
+    /* Document Detail Page Layout */
+    .doc-layout {
+      display: grid;
+      grid-template-columns: 250px 1fr;
+      gap: 2rem;
+      margin-top: 2rem;
+    }
+
+    .doc-sidebar {
+      position: sticky;
+      top: 20px;
+      height: fit-content;
+    }
+
+    .doc-main {
+      max-width: 800px;
+    }
+
+    .doc-toc {
+      background: white;
+      padding: 1.5rem;
+      border-radius: 8px;
+      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+      margin-bottom: 1.5rem;
+    }
+
+    .doc-toc h3 {
+      font-size: 0.9rem;
+      text-transform: uppercase;
+      color: #666;
+      margin-bottom: 1rem;
+    }
+
+    .toc-list {
+      list-style: none;
+    }
+
+    .toc-list li {
+      margin-bottom: 0.5rem;
+    }
+
+    .toc-list a {
+      color: #3498db;
+      text-decoration: none;
+      font-size: 0.9rem;
+    }
+
+    .toc-list a:hover {
+      text-decoration: underline;
+    }
+
+    .doc-entities {
+      background: white;
+      padding: 1.5rem;
+      border-radius: 8px;
+      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+    }
+
+    .doc-entities h3 {
+      font-size: 0.9rem;
+      text-transform: uppercase;
+      color: #666;
+      margin-bottom: 1rem;
+    }
+
+    .entity-section {
+      margin-bottom: 1.5rem;
+    }
+
+    .entity-section h4 {
+      font-size: 0.85rem;
+      color: #888;
+      margin-bottom: 0.5rem;
+    }
+
+    .entity-section ul {
+      list-style: none;
+      margin: 0;
+      padding: 0;
+    }
+
+    .entity-section li {
+      padding: 0.25rem 0;
+      font-size: 0.9rem;
+    }
+
+    .entity-section a {
+      color: #3498db;
+      text-decoration: none;
+    }
+
+    .entity-section a:hover {
+      text-decoration: underline;
+    }
+
+    .metadata-box {
+      background: #f8f9fa;
+      padding: 1.5rem;
+      border-radius: 8px;
+      margin-bottom: 2rem;
+      border-left: 4px solid #3498db;
+    }
+
+    .metadata-item strong {
+      display: block;
+      font-size: 0.75rem;
+      text-transform: uppercase;
+      color: #666;
+      margin-bottom: 0.25rem;
+    }
+
+    .full-text-container {
+      background: white;
+      padding: 2rem;
+      border-radius: 8px;
+      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+      margin-bottom: 2rem;
+    }
+
+    .full-text-container h2 {
+      margin-bottom: 1.5rem;
+      color: #2c3e50;
+    }
+
+    .full-text-container .full-text {
+      font-family: Georgia, serif;
+      font-size: 1.05rem;
+      line-height: 1.8;
+      white-space: pre-wrap;
+      color: #333;
+    }
+
+    .page-details {
+      margin-top: 2rem;
+    }
+
+    .page-detail {
+      margin-bottom: 1rem;
+    }
+
+    .page-summary {
+      padding: 1rem 1.5rem;
+      background: #f8f9fa;
+      border-radius: 4px;
+      cursor: pointer;
+      font-weight: 500;
+    }
+
+    .page-summary:hover {
+      background: #e9ecef;
+    }
+
+    .page-content {
+      padding: 1.5rem;
+      background: white;
+      border: 1px solid #e0e0e0;
+      border-top: none;
+      border-radius: 0 0 4px 4px;
+    }
+
+    /* Responsive Design */
+    @media (max-width: 968px) {
+      .doc-layout {
+        grid-template-columns: 1fr;
+      }
+
+      .doc-sidebar {
+        position: static;
+      }
+
+      nav .container {
+        flex-direction: column;
+        gap: 1rem;
+      }
+
+      .browse-grid {
+        grid-template-columns: 1fr;
+      }
+
+      .stats {
+        grid-template-columns: repeat(2, 1fr);
+      }
+    }
+
+    @media (max-width: 600px) {
+      .hero h1 {
+        font-size: 2rem;
+      }
+
+      .stats {
+        grid-template-columns: 1fr;
+      }
+
+      .alphabet-nav {
+        justify-content: center;
+      }
+
+      .entity-summary {
+        font-size: 1rem;
+        padding: 0.75rem 1rem;
+      }
+
+      .entity-content {
+        padding: 0 1rem 1rem 2.5rem;
+      }
+    }
+
+    /* Typography improvements */
+    h1, h2, h3, h4, h5, h6 {
+      font-weight: 600;
+      line-height: 1.3;
+    }
+
+    h1 {
+      font-size: 2.5rem;
+      margin-bottom: 1rem;
+    }
+
+    h2 {
+      font-size: 2rem;
+      margin-bottom: 0.75rem;
+    }
+
+    h3 {
+      font-size: 1.5rem;
+      margin-bottom: 0.5rem;
+    }
+
+    h4 {
+      font-size: 1.25rem;
+      margin-bottom: 0.5rem;
+    }
+
+    p {
+      margin-bottom: 1rem;
+    }
+
+    a {
+      transition: all 0.2s;
+    }
   </style>
 </head>
 <body>
   <nav>
     <div class="container">
-      <a href="/" style="font-weight: bold; font-size: 1.2rem;">Document Archive</a>
+      <a href="/" style="font-weight: bold; font-size: 1.2rem;">Epstein Archive</a>
       <div>
         <a href="/people/">People</a>
         <a href="/organizations/">Organizations</a>

+ 121 - 5
src/all-documents.njk

@@ -10,9 +10,13 @@ title: All Documents
   <input type="text" id="search" placeholder="Search documents...">
 </div>
 
+<div class="pagination-info" style="margin: 1rem 0; text-align: center; color: #666;">
+  Showing <span id="showing-start">1</span>-<span id="showing-end">50</span> of <span id="total-count">{{ documents.length }}</span>
+</div>
+
 <div id="results">
   {% for doc in documents %}
-  <div class="document-card">
+  <div class="document-card" data-index="{{ loop.index0 }}">
     <h4>Document {{ doc.document_number }}</h4>
     <div class="meta">
       {% if doc.document_metadata.document_type %}Type: {{ doc.document_metadata.document_type }} | {% endif %}
@@ -33,17 +37,129 @@ title: All Documents
   {% endfor %}
 </div>
 
+<div class="pagination-controls" style="display: flex; justify-content: center; align-items: center; gap: 1rem; margin: 2rem 0; flex-wrap: wrap;">
+  <button id="prev-btn" class="page-btn">← Previous</button>
+  <div id="page-numbers" style="display: flex; gap: 0.5rem; flex-wrap: wrap;"></div>
+  <button id="next-btn" class="page-btn">Next →</button>
+</div>
+
 <script>
   const search = document.getElementById('search');
   const results = document.getElementById('results');
+  const allCards = Array.from(results.querySelectorAll('.document-card'));
+  const prevBtn = document.getElementById('prev-btn');
+  const nextBtn = document.getElementById('next-btn');
+  const pageNumbersContainer = document.getElementById('page-numbers');
+  const showingStart = document.getElementById('showing-start');
+  const showingEnd = document.getElementById('showing-end');
+  const totalCount = document.getElementById('total-count');
+
+  const itemsPerPage = 50;
+  let currentPage = 1;
+  let filteredCards = allCards;
+
+  function updatePagination() {
+    const totalPages = Math.ceil(filteredCards.length / itemsPerPage);
+    const startIndex = (currentPage - 1) * itemsPerPage;
+    const endIndex = Math.min(startIndex + itemsPerPage, filteredCards.length);
+
+    // Hide all cards
+    allCards.forEach(card => card.style.display = 'none');
+
+    // Show current page cards
+    filteredCards.slice(startIndex, endIndex).forEach(card => card.style.display = 'block');
+
+    // Update info
+    showingStart.textContent = filteredCards.length > 0 ? startIndex + 1 : 0;
+    showingEnd.textContent = endIndex;
+    totalCount.textContent = filteredCards.length;
+
+    // Update buttons
+    prevBtn.disabled = currentPage === 1;
+    nextBtn.disabled = currentPage === totalPages || filteredCards.length === 0;
+
+    // Update page numbers
+    pageNumbersContainer.innerHTML = '';
+    const maxPageButtons = 7;
+    let startPage = Math.max(1, currentPage - Math.floor(maxPageButtons / 2));
+    let endPage = Math.min(totalPages, startPage + maxPageButtons - 1);
+
+    if (endPage - startPage < maxPageButtons - 1) {
+      startPage = Math.max(1, endPage - maxPageButtons + 1);
+    }
 
+    if (startPage > 1) {
+      const btn = createPageButton(1);
+      pageNumbersContainer.appendChild(btn);
+      if (startPage > 2) {
+        const ellipsis = document.createElement('span');
+        ellipsis.textContent = '...';
+        ellipsis.style.padding = '0.5rem';
+        pageNumbersContainer.appendChild(ellipsis);
+      }
+    }
+
+    for (let i = startPage; i <= endPage; i++) {
+      const btn = createPageButton(i);
+      pageNumbersContainer.appendChild(btn);
+    }
+
+    if (endPage < totalPages) {
+      if (endPage < totalPages - 1) {
+        const ellipsis = document.createElement('span');
+        ellipsis.textContent = '...';
+        ellipsis.style.padding = '0.5rem';
+        pageNumbersContainer.appendChild(ellipsis);
+      }
+      const btn = createPageButton(totalPages);
+      pageNumbersContainer.appendChild(btn);
+    }
+  }
+
+  function createPageButton(page) {
+    const btn = document.createElement('button');
+    btn.textContent = page;
+    btn.className = 'page-num';
+    if (page === currentPage) {
+      btn.classList.add('active');
+    }
+    btn.addEventListener('click', () => {
+      currentPage = page;
+      updatePagination();
+      window.scrollTo({ top: 0, behavior: 'smooth' });
+    });
+    return btn;
+  }
+
+  // Search functionality
   search.addEventListener('input', (e) => {
     const query = e.target.value.toLowerCase();
-    const cards = results.querySelectorAll('.document-card');
-
-    cards.forEach(card => {
+    filteredCards = allCards.filter(card => {
       const text = card.textContent.toLowerCase();
-      card.style.display = text.includes(query) ? 'block' : 'none';
+      return text.includes(query);
     });
+    currentPage = 1;
+    updatePagination();
   });
+
+  // Navigation buttons
+  prevBtn.addEventListener('click', () => {
+    if (currentPage > 1) {
+      currentPage--;
+      updatePagination();
+      window.scrollTo({ top: 0, behavior: 'smooth' });
+    }
+  });
+
+  nextBtn.addEventListener('click', () => {
+    const totalPages = Math.ceil(filteredCards.length / itemsPerPage);
+    if (currentPage < totalPages) {
+      currentPage++;
+      updatePagination();
+      window.scrollTo({ top: 0, behavior: 'smooth' });
+    }
+  });
+
+  // Initial render
+  updatePagination();
 </script>

+ 104 - 67
src/documents.njk

@@ -14,9 +14,9 @@ eleventyComputed:
     <a href="/" style="color: #3498db; text-decoration: none;">← Back to home</a>
   </div>
 
-  <h1>Document {{ doc.document_number }}</h1>
+  <h1 style="margin-bottom: 1rem;">Document {{ doc.document_number }}</h1>
 
-  <div class="metadata">
+  <div class="metadata-box">
     <div class="metadata-grid">
       <div class="metadata-item">
         <strong>Document Type</strong>
@@ -49,76 +49,113 @@ eleventyComputed:
     </div>
   </div>
 
-  <h2>Full Text</h2>
-  <div class="full-text">{{ doc.full_text }}</div>
+  <div class="doc-layout">
+    <aside class="doc-sidebar">
+      {% if doc.page_count > 1 %}
+      <nav class="doc-toc">
+        <h3>Table of Contents</h3>
+        <ul class="toc-list">
+          <li><a href="#full-text">Full Document</a></li>
+          {% for page in doc.pages %}
+          <li><a href="#page-{{ loop.index }}">Page {{ page.document_metadata.page_number or loop.index }}</a></li>
+          {% endfor %}
+        </ul>
+      </nav>
+      {% endif %}
 
-  <h2>Referenced Entities</h2>
-  <div class="entities">
-    {% if doc.entities.people.length > 0 %}
-    <div class="entity-group">
-      <h4>People ({{ doc.entities.people.length }})</h4>
-      <ul>
-        {% for person in doc.entities.people %}
-        <li><a href="/people/#{{ person | slugify }}">{{ person }}</a></li>
-        {% endfor %}
-      </ul>
-    </div>
-    {% endif %}
+      <div class="doc-entities">
+        <h3>Referenced Entities</h3>
 
-    {% if doc.entities.organizations.length > 0 %}
-    <div class="entity-group">
-      <h4>Organizations ({{ doc.entities.organizations.length }})</h4>
-      <ul>
-        {% for org in doc.entities.organizations %}
-        <li><a href="/organizations/#{{ org | slugify }}">{{ org }}</a></li>
-        {% endfor %}
-      </ul>
-    </div>
-    {% endif %}
+        {% if doc.entities.people.length > 0 %}
+        <div class="entity-section">
+          <h4>People ({{ doc.entities.people.length }})</h4>
+          <ul>
+            {% for person in doc.entities.people | slice(0, 10) %}
+            <li><a href="/people/#{{ person | slugify }}">{{ person }}</a></li>
+            {% endfor %}
+            {% if doc.entities.people.length > 10 %}
+            <li style="color: #888; font-style: italic;">+{{ doc.entities.people.length - 10 }} more</li>
+            {% endif %}
+          </ul>
+        </div>
+        {% endif %}
 
-    {% if doc.entities.locations.length > 0 %}
-    <div class="entity-group">
-      <h4>Locations ({{ doc.entities.locations.length }})</h4>
-      <ul>
-        {% for loc in doc.entities.locations %}
-        <li><a href="/locations/#{{ loc | slugify }}">{{ loc }}</a></li>
-        {% endfor %}
-      </ul>
-    </div>
-    {% endif %}
+        {% if doc.entities.organizations.length > 0 %}
+        <div class="entity-section">
+          <h4>Organizations ({{ doc.entities.organizations.length }})</h4>
+          <ul>
+            {% for org in doc.entities.organizations | slice(0, 10) %}
+            <li><a href="/organizations/#{{ org | slugify }}">{{ org }}</a></li>
+            {% endfor %}
+            {% if doc.entities.organizations.length > 10 %}
+            <li style="color: #888; font-style: italic;">+{{ doc.entities.organizations.length - 10 }} more</li>
+            {% endif %}
+          </ul>
+        </div>
+        {% endif %}
 
-    {% if doc.entities.dates.length > 0 %}
-    <div class="entity-group">
-      <h4>Dates ({{ doc.entities.dates.length }})</h4>
-      <ul>
-        {% for date in doc.entities.dates %}
-        <li>{{ date }}</li>
-        {% endfor %}
-      </ul>
-    </div>
-    {% endif %}
+        {% if doc.entities.locations.length > 0 %}
+        <div class="entity-section">
+          <h4>Locations ({{ doc.entities.locations.length }})</h4>
+          <ul>
+            {% for loc in doc.entities.locations %}
+            <li><a href="/locations/#{{ loc | slugify }}">{{ loc }}</a></li>
+            {% endfor %}
+          </ul>
+        </div>
+        {% endif %}
+
+        {% if doc.entities.dates.length > 0 %}
+        <div class="entity-section">
+          <h4>Dates ({{ doc.entities.dates.length }})</h4>
+          <ul>
+            {% for date in doc.entities.dates | slice(0, 5) %}
+            <li>{{ date }}</li>
+            {% endfor %}
+            {% if doc.entities.dates.length > 5 %}
+            <li style="color: #888; font-style: italic;">+{{ doc.entities.dates.length - 5 }} more</li>
+            {% endif %}
+          </ul>
+        </div>
+        {% endif %}
 
-    {% if doc.entities.reference_numbers.length > 0 %}
-    <div class="entity-group">
-      <h4>Reference Numbers ({{ doc.entities.reference_numbers.length }})</h4>
-      <ul>
-        {% for ref in doc.entities.reference_numbers %}
-        <li>{{ ref }}</li>
+        {% if doc.entities.reference_numbers.length > 0 %}
+        <div class="entity-section">
+          <h4>Reference Numbers</h4>
+          <ul>
+            {% for ref in doc.entities.reference_numbers | slice(0, 5) %}
+            <li>{{ ref }}</li>
+            {% endfor %}
+            {% if doc.entities.reference_numbers.length > 5 %}
+            <li style="color: #888; font-style: italic;">+{{ doc.entities.reference_numbers.length - 5 }} more</li>
+            {% endif %}
+          </ul>
+        </div>
+        {% endif %}
+      </div>
+    </aside>
+
+    <main class="doc-main">
+      <div id="full-text" class="full-text-container">
+        <h2>Full Text</h2>
+        <div class="full-text">{{ doc.full_text }}</div>
+      </div>
+
+      {% if doc.page_count > 1 %}
+      <div class="page-details">
+        <h2 style="margin-bottom: 1.5rem;">Individual Pages</h2>
+        {% for page in doc.pages %}
+        <details class="page-detail" id="page-{{ loop.index }}">
+          <summary class="page-summary">
+            Page {{ page.document_metadata.page_number or loop.index }} - {{ page.filename }}
+          </summary>
+          <div class="page-content">
+            <div class="full-text">{{ page.full_text }}</div>
+          </div>
+        </details>
         {% endfor %}
-      </ul>
-    </div>
-    {% endif %}
+      </div>
+      {% endif %}
+    </main>
   </div>
-
-  <h2>Page Details</h2>
-  {% for page in doc.pages %}
-  <details style="margin-bottom: 1rem;">
-    <summary style="cursor: pointer; padding: 1rem; background: #f8f9fa; border-radius: 4px;">
-      Page {{ page.document_metadata.page_number }} - {{ page.filename }}
-    </summary>
-    <div style="padding: 1rem; background: white; border: 1px solid #e0e0e0; border-top: none;">
-      <div class="full-text">{{ page.full_text }}</div>
-    </div>
-  </details>
-  {% endfor %}
 </div>

+ 104 - 2
src/index.njk

@@ -1,11 +1,17 @@
 ---
 layout: base.njk
-title: Document Explorer
+title: Home
 ---
 
 <div class="hero">
-  <h1>Document Archive</h1>
+  <h1>Epstein Archive</h1>
   <p class="subtitle">Browse {{ documents.length }} processed documents</p>
+
+  <div class="search-box" style="margin-top: 2rem; max-width: 600px; margin-left: auto; margin-right: auto;">
+    <input type="text" id="global-search" placeholder="Search people, organizations, locations...">
+  </div>
+
+  <div id="search-results" style="display: none; margin-top: 1.5rem; text-align: left; max-width: 600px; margin-left: auto; margin-right: auto;"></div>
 </div>
 
 <div class="stats">
@@ -82,3 +88,99 @@ title: Document Explorer
   </ul>
   <a href="/organizations/" class="view-all">View all organizations →</a>
 </div>
+
+<script>
+  const globalSearch = document.getElementById('global-search');
+  const searchResults = document.getElementById('search-results');
+
+  // Build lightweight search index (name and count only)
+  const searchIndex = {
+    people: [
+      {% for person in indices.people %}
+      { name: {{ person.name | dump | safe }}, count: {{ person.count }} }{{ "," if not loop.last else "" }}
+      {% endfor %}
+    ],
+    organizations: [
+      {% for org in indices.organizations %}
+      { name: {{ org.name | dump | safe }}, count: {{ org.count }} }{{ "," if not loop.last else "" }}
+      {% endfor %}
+    ],
+    locations: [
+      {% for loc in indices.locations %}
+      { name: {{ loc.name | dump | safe }}, count: {{ loc.count }} }{{ "," if not loop.last else "" }}
+      {% endfor %}
+    ]
+  };
+
+  globalSearch.addEventListener('input', (e) => {
+    const query = e.target.value.toLowerCase().trim();
+
+    if (query.length === 0) {
+      searchResults.style.display = 'none';
+      return;
+    }
+
+    const results = {
+      people: searchIndex.people.filter(p => p.name.toLowerCase().includes(query)).slice(0, 5),
+      organizations: searchIndex.organizations.filter(o => o.name.toLowerCase().includes(query)).slice(0, 5),
+      locations: searchIndex.locations.filter(l => l.name.toLowerCase().includes(query)).slice(0, 5)
+    };
+
+    const totalResults = results.people.length + results.organizations.length + results.locations.length;
+
+    if (totalResults === 0) {
+      searchResults.innerHTML = '<div style="background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); color: #666;">No results found</div>';
+      searchResults.style.display = 'block';
+      return;
+    }
+
+    let html = '<div style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">';
+
+    if (results.people.length > 0) {
+      html += '<div style="margin-bottom: 1.5rem;"><h4 style="color: #666; font-size: 0.85rem; text-transform: uppercase; margin-bottom: 0.75rem;">People</h4><div style="display: grid; gap: 0.5rem;">';
+      results.people.forEach(person => {
+        html += `<a href="/people/#${slugify(person.name)}" style="display: flex; justify-content: space-between; padding: 0.5rem; background: #f8f9fa; border-radius: 4px; text-decoration: none; color: #3498db; border-left: 3px solid #3498db;">
+          <span>${person.name}</span>
+          <span style="color: #999; font-size: 0.85rem;">${person.count} docs</span>
+        </a>`;
+      });
+      html += '</div></div>';
+    }
+
+    if (results.organizations.length > 0) {
+      html += '<div style="margin-bottom: 1.5rem;"><h4 style="color: #666; font-size: 0.85rem; text-transform: uppercase; margin-bottom: 0.75rem;">Organizations</h4><div style="display: grid; gap: 0.5rem;">';
+      results.organizations.forEach(org => {
+        html += `<a href="/organizations/#${slugify(org.name)}" style="display: flex; justify-content: space-between; padding: 0.5rem; background: #f8f9fa; border-radius: 4px; text-decoration: none; color: #3498db; border-left: 3px solid #3498db;">
+          <span>${org.name}</span>
+          <span style="color: #999; font-size: 0.85rem;">${org.count} docs</span>
+        </a>`;
+      });
+      html += '</div></div>';
+    }
+
+    if (results.locations.length > 0) {
+      html += '<div><h4 style="color: #666; font-size: 0.85rem; text-transform: uppercase; margin-bottom: 0.75rem;">Locations</h4><div style="display: grid; gap: 0.5rem;">';
+      results.locations.forEach(loc => {
+        html += `<a href="/locations/#${slugify(loc.name)}" style="display: flex; justify-content: space-between; padding: 0.5rem; background: #f8f9fa; border-radius: 4px; text-decoration: none; color: #3498db; border-left: 3px solid #3498db;">
+          <span>${loc.name}</span>
+          <span style="color: #999; font-size: 0.85rem;">${loc.count} docs</span>
+        </a>`;
+      });
+      html += '</div></div>';
+    }
+
+    html += '</div>';
+    searchResults.innerHTML = html;
+    searchResults.style.display = 'block';
+  });
+
+  // Simple slugify function matching 11ty's slugify filter
+  function slugify(str) {
+    return str.toString().toLowerCase()
+      .replace(/\s+/g, '-')
+      .replace(/[^\w\-]+/g, '')
+      .replace(/\-\-+/g, '-')
+      .replace(/^-+/, '')
+      .replace(/-+$/, '');
+  }
+</script>

+ 42 - 17
src/locations.njk

@@ -10,40 +10,65 @@ title: Locations
   <input type="text" id="search" placeholder="Search locations...">
 </div>
 
+<div class="alphabet-nav">
+  <button class="letter-btn active" data-letter="all">All</button>
+  {% for letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' %}
+  <button class="letter-btn" data-letter="{{ letter }}">{{ letter }}</button>
+  {% endfor %}
+</div>
+
 <div id="results">
   {% for loc in indices.locations %}
-  <div class="section" id="{{ loc.name | slugify }}">
-    <h2>{{ loc.name }}</h2>
-    <p>Mentioned in {{ loc.count }} documents</p>
-
-    <div style="margin-top: 1rem;">
+  <details class="entity-item" data-name="{{ loc.name }}" data-letter="{{ loc.name[0] | upper }}" id="{{ loc.name | slugify }}">
+    <summary class="entity-summary">
+      <span class="entity-name">{{ loc.name }}</span>
+      <span class="entity-count">{{ loc.count }} {{ "document" if loc.count == 1 else "documents" }}</span>
+    </summary>
+    <div class="entity-content">
       {% for doc in loc.docs %}
-      <div class="document-card">
-        <h4>Document {{ doc.document_number }}</h4>
-        <div class="meta">
-          {% if doc.document_metadata.document_type %}Type: {{ doc.document_metadata.document_type }} | {% endif %}
-          {% if doc.document_metadata.date %}Date: {{ doc.document_metadata.date }} | {% endif %}
-          Pages: {{ doc.page_count }}
+      <div class="document-card-compact">
+        <a href="/document/{{ doc.unique_id | slugify }}/" class="doc-link">
+          <strong>Document {{ doc.document_number }}</strong>
+        </a>
+        <div class="meta-compact">
+          {% if doc.document_metadata.document_type %}{{ doc.document_metadata.document_type }}{% endif %}
+          {% if doc.document_metadata.date %} · {{ doc.document_metadata.date }}{% endif %}
+          · {{ doc.page_count }} {{ "page" if doc.page_count == 1 else "pages" }}
         </div>
-        <a href="/document/{{ doc.unique_id | slugify }}/" style="font-size: 0.9rem;">View document →</a>
       </div>
       {% endfor %}
     </div>
-  </div>
+  </details>
   {% endfor %}
 </div>
 
 <script>
   const search = document.getElementById('search');
   const results = document.getElementById('results');
+  const letterBtns = document.querySelectorAll('.letter-btn');
+  const allItems = results.querySelectorAll('.entity-item');
 
   search.addEventListener('input', (e) => {
     const query = e.target.value.toLowerCase();
-    const sections = results.querySelectorAll('.section');
+    allItems.forEach(item => {
+      const name = item.dataset.name.toLowerCase();
+      item.style.display = name.includes(query) ? 'block' : 'none';
+    });
+  });
 
-    sections.forEach(section => {
-      const title = section.querySelector('h2').textContent.toLowerCase();
-      section.style.display = title.includes(query) ? 'block' : 'none';
+  letterBtns.forEach(btn => {
+    btn.addEventListener('click', () => {
+      const letter = btn.dataset.letter;
+      letterBtns.forEach(b => b.classList.remove('active'));
+      btn.classList.add('active');
+      search.value = '';
+      allItems.forEach(item => {
+        if (letter === 'all') {
+          item.style.display = 'block';
+        } else {
+          item.style.display = item.dataset.letter === letter ? 'block' : 'none';
+        }
+      });
     });
   });
 </script>

+ 42 - 17
src/organizations.njk

@@ -10,40 +10,65 @@ title: Organizations
   <input type="text" id="search" placeholder="Search organizations...">
 </div>
 
+<div class="alphabet-nav">
+  <button class="letter-btn active" data-letter="all">All</button>
+  {% for letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' %}
+  <button class="letter-btn" data-letter="{{ letter }}">{{ letter }}</button>
+  {% endfor %}
+</div>
+
 <div id="results">
   {% for org in indices.organizations %}
-  <div class="section" id="{{ org.name | slugify }}">
-    <h2>{{ org.name }}</h2>
-    <p>Mentioned in {{ org.count }} documents</p>
-
-    <div style="margin-top: 1rem;">
+  <details class="entity-item" data-name="{{ org.name }}" data-letter="{{ org.name[0] | upper }}" id="{{ org.name | slugify }}">
+    <summary class="entity-summary">
+      <span class="entity-name">{{ org.name }}</span>
+      <span class="entity-count">{{ org.count }} {{ "document" if org.count == 1 else "documents" }}</span>
+    </summary>
+    <div class="entity-content">
       {% for doc in org.docs %}
-      <div class="document-card">
-        <h4>Document {{ doc.document_number }}</h4>
-        <div class="meta">
-          {% if doc.document_metadata.document_type %}Type: {{ doc.document_metadata.document_type }} | {% endif %}
-          {% if doc.document_metadata.date %}Date: {{ doc.document_metadata.date }} | {% endif %}
-          Pages: {{ doc.page_count }}
+      <div class="document-card-compact">
+        <a href="/document/{{ doc.unique_id | slugify }}/" class="doc-link">
+          <strong>Document {{ doc.document_number }}</strong>
+        </a>
+        <div class="meta-compact">
+          {% if doc.document_metadata.document_type %}{{ doc.document_metadata.document_type }}{% endif %}
+          {% if doc.document_metadata.date %} · {{ doc.document_metadata.date }}{% endif %}
+          · {{ doc.page_count }} {{ "page" if doc.page_count == 1 else "pages" }}
         </div>
-        <a href="/document/{{ doc.unique_id | slugify }}/" style="font-size: 0.9rem;">View document →</a>
       </div>
       {% endfor %}
     </div>
-  </div>
+  </details>
   {% endfor %}
 </div>
 
 <script>
   const search = document.getElementById('search');
   const results = document.getElementById('results');
+  const letterBtns = document.querySelectorAll('.letter-btn');
+  const allItems = results.querySelectorAll('.entity-item');
 
   search.addEventListener('input', (e) => {
     const query = e.target.value.toLowerCase();
-    const sections = results.querySelectorAll('.section');
+    allItems.forEach(item => {
+      const name = item.dataset.name.toLowerCase();
+      item.style.display = name.includes(query) ? 'block' : 'none';
+    });
+  });
 
-    sections.forEach(section => {
-      const title = section.querySelector('h2').textContent.toLowerCase();
-      section.style.display = title.includes(query) ? 'block' : 'none';
+  letterBtns.forEach(btn => {
+    btn.addEventListener('click', () => {
+      const letter = btn.dataset.letter;
+      letterBtns.forEach(b => b.classList.remove('active'));
+      btn.classList.add('active');
+      search.value = '';
+      allItems.forEach(item => {
+        if (letter === 'all') {
+          item.style.display = 'block';
+        } else {
+          item.style.display = item.dataset.letter === letter ? 'block' : 'none';
+        }
+      });
     });
   });
 </script>

+ 50 - 17
src/people.njk

@@ -10,40 +10,73 @@ title: People
   <input type="text" id="search" placeholder="Search people...">
 </div>
 
+<div class="alphabet-nav">
+  <button class="letter-btn active" data-letter="all">All</button>
+  {% for letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' %}
+  <button class="letter-btn" data-letter="{{ letter }}">{{ letter }}</button>
+  {% endfor %}
+</div>
+
 <div id="results">
   {% for person in indices.people %}
-  <div class="section" id="{{ person.name | slugify }}">
-    <h2>{{ person.name }}</h2>
-    <p>Mentioned in {{ person.count }} documents</p>
-
-    <div style="margin-top: 1rem;">
+  <details class="entity-item" data-name="{{ person.name }}" data-letter="{{ person.name[0] | upper }}" id="{{ person.name | slugify }}">
+    <summary class="entity-summary">
+      <span class="entity-name">{{ person.name }}</span>
+      <span class="entity-count">{{ person.count }} {{ "document" if person.count == 1 else "documents" }}</span>
+    </summary>
+    <div class="entity-content">
       {% for doc in person.docs %}
-      <div class="document-card">
-        <h4>Document {{ doc.document_number }}</h4>
-        <div class="meta">
-          {% if doc.document_metadata.document_type %}Type: {{ doc.document_metadata.document_type }} | {% endif %}
-          {% if doc.document_metadata.date %}Date: {{ doc.document_metadata.date }} | {% endif %}
-          Pages: {{ doc.page_count }}
+      <div class="document-card-compact">
+        <a href="/document/{{ doc.unique_id | slugify }}/" class="doc-link">
+          <strong>Document {{ doc.document_number }}</strong>
+        </a>
+        <div class="meta-compact">
+          {% if doc.document_metadata.document_type %}{{ doc.document_metadata.document_type }}{% endif %}
+          {% if doc.document_metadata.date %} · {{ doc.document_metadata.date }}{% endif %}
+          · {{ doc.page_count }} {{ "page" if doc.page_count == 1 else "pages" }}
         </div>
-        <a href="/document/{{ doc.unique_id | slugify }}/" style="font-size: 0.9rem;">View document →</a>
       </div>
       {% endfor %}
     </div>
-  </div>
+  </details>
   {% endfor %}
 </div>
 
 <script>
   const search = document.getElementById('search');
   const results = document.getElementById('results');
+  const letterBtns = document.querySelectorAll('.letter-btn');
+  const allItems = results.querySelectorAll('.entity-item');
 
+  // Search functionality
   search.addEventListener('input', (e) => {
     const query = e.target.value.toLowerCase();
-    const sections = results.querySelectorAll('.section');
+    allItems.forEach(item => {
+      const name = item.dataset.name.toLowerCase();
+      item.style.display = name.includes(query) ? 'block' : 'none';
+    });
+  });
+
+  // Alphabet filter
+  letterBtns.forEach(btn => {
+    btn.addEventListener('click', () => {
+      const letter = btn.dataset.letter;
+
+      // Update active state
+      letterBtns.forEach(b => b.classList.remove('active'));
+      btn.classList.add('active');
+
+      // Clear search
+      search.value = '';
 
-    sections.forEach(section => {
-      const title = section.querySelector('h2').textContent.toLowerCase();
-      section.style.display = title.includes(query) ? 'block' : 'none';
+      // Filter items
+      allItems.forEach(item => {
+        if (letter === 'all') {
+          item.style.display = 'block';
+        } else {
+          item.style.display = item.dataset.letter === letter ? 'block' : 'none';
+        }
+      });
     });
   });
 </script>