mirror of
https://github.com/epstein-docs/epstein-docs.github.io.git
synced 2026-04-16 21:55:41 -05:00
clean up site
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.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.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-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>
|
||||
|
||||
106
src/index.njk
106
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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
});
|
||||
});
|
||||
|
||||
sections.forEach(section => {
|
||||
const title = section.querySelector('h2').textContent.toLowerCase();
|
||||
section.style.display = title.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 = '';
|
||||
|
||||
// Filter items
|
||||
allItems.forEach(item => {
|
||||
if (letter === 'all') {
|
||||
item.style.display = 'block';
|
||||
} else {
|
||||
item.style.display = item.dataset.letter === letter ? 'block' : 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user