feat(crimescene): expand Zodiac Killer with subcollections — letters, ciphers, police reports

- Restructured Zodiac Killer into 4 subcollections: FBI Investigation Files, Zodiac Letters & Cards, The Zodiac Ciphers, Police Reports
- Added 11 letter PDFs (original correspondence 1969-1974)
- Added 3 cipher PDFs (Z408, Z340, Z32) plus Z340 academic solution paper
- Each document has unique historical description
- Path bug fixed: documents now route through country code (US) via subcollections
- JS/CSS already support subcollection accordion rendering
This commit is contained in:
jae 2026-04-15 22:08:28 +00:00
parent f1e58f132f
commit b3db877db4
3 changed files with 428 additions and 72 deletions

View file

@ -40,67 +40,246 @@
{
"id": "zodiac-killer",
"name": "The Zodiac Killer",
"description": "Unidentified serial killer active in Northern California in the late 1960s and early 1970s. Taunted police and newspapers with cryptic letters and ciphers, some of which remain unsolved. At least five confirmed victims killed and two survivors. Claimed responsibility for 37 murders. Despite extensive FBI and local law enforcement investigations — including high-profile suspect Arthur Leigh Allen — the case remains officially open. Files released under FOIA in 2007.",
"documents": [
"year": "19681974",
"source": "FBI Vault / zodiackiller.com / Archive.org",
"description": "Unidentified serial killer active in Northern California in the late 1960s and early 1970s. Taunted police and newspapers with cryptic letters and ciphers, some of which remain unsolved. At least five confirmed victims killed and two survivors. Claimed responsibility for 37 murders. Despite extensive FBI and local law enforcement investigations — including high-profile suspect Arthur Leigh Allen — the case remains officially open.",
"subcollections": [
{
"id": "zodiac-fbi-part-01",
"title": "FBI Investigation File — Part 01 of 06",
"filename": "zodiac-fbi-part-01.pdf",
"pages": 83,
"size_mb": 1.6,
"date_released": "2007",
"classification": "DECLASSIFIED",
"description": "Focuses primarily on the FBI's investigation of prime suspect Arthur Leigh Allen. Contains records of multi-day meetings between FBI agents and Vallejo Police Department investigators, totalling approximately ten hours. Documents interview strategies, Allen's denial of being the Zodiac, and subsequent follow-up interrogations. Includes inter-agency memoranda between the San Francisco field office and FBI headquarters regarding the Allen investigation."
"id": "fbi-investigation",
"name": "FBI Investigation Files",
"description": "Complete FBI FOIA release comprising six parts totalling over 1,100 pages. Covers the Bureau's involvement from 1969 through the 2000s, including suspect investigations, forensic analysis, cipher decryption attempts, inter-agency coordination, and public tip processing. Released under the Freedom of Information Act in 2007.",
"documents": [
{
"id": "zodiac-fbi-part-01",
"title": "FBI Investigation File — Part 01 of 06",
"filename": "zodiac-fbi-part-01.pdf",
"pages": 83,
"size_mb": 1.6,
"date_released": "2007",
"classification": "DECLASSIFIED",
"description": "Focuses primarily on the FBI's investigation of prime suspect Arthur Leigh Allen. Contains records of multi-day meetings between FBI agents and Vallejo Police Department investigators, totalling approximately ten hours. Documents interview strategies, Allen's denial of being the Zodiac, and subsequent follow-up interrogations. Includes inter-agency memoranda between the San Francisco field office and FBI headquarters regarding the Allen investigation."
},
{
"id": "zodiac-fbi-part-02",
"title": "FBI Investigation File — Part 02 of 06",
"filename": "zodiac-fbi-part-02.pdf",
"pages": 88,
"size_mb": 2.9,
"date_released": "2007",
"classification": "DECLASSIFIED",
"description": "Contains the early FBI case file opened under Bureau file number 9-HQ-49911. Includes FOIPA processing sheets, deleted page notifications citing exemptions under Section 552, and initial Bureau of Investigation reports from late 1969. Documents the FBI's first formal involvement after the Zodiac's threatening letters to San Francisco newspapers and the extortion classification of the case."
},
{
"id": "zodiac-fbi-part-03",
"title": "FBI Investigation File — Part 03 of 06",
"filename": "zodiac-fbi-part-03.pdf",
"pages": 108,
"size_mb": 1.7,
"date_released": "2007",
"classification": "DECLASSIFIED",
"description": "Centres on the Zodiac's coded communications and cipher analysis. Contains FBI teletypes between the San Francisco field office and Director's office regarding the newspaper cipher that appeared to identify the killer. Includes latent fingerprint comparison reports from the FBI Identification Division, attempting to match prints recovered from Zodiac crime scenes and letters against known individuals. Documents tips from the public claiming to have solved the cipher."
},
{
"id": "zodiac-fbi-part-04",
"title": "FBI Investigation File — Part 04 of 06",
"filename": "zodiac-fbi-part-04.pdf",
"pages": 258,
"size_mb": 11.7,
"date_released": "2007",
"classification": "DECLASSIFIED",
"description": "The largest section of the released files, containing extensive FBI Laboratory Division analysis and Identification Division records. Includes forensic examination reports on physical evidence from Zodiac crime scenes, handwriting analysis of the Zodiac's letters, and ballistics reports. Contains inter-office airmail correspondence between Sacramento, San Francisco field offices and FBI headquarters directing investigative priorities."
},
{
"id": "zodiac-fbi-part-05",
"title": "FBI Investigation File — Part 05 of 06",
"filename": "zodiac-fbi-part-05.pdf",
"pages": 207,
"size_mb": 7.8,
"date_released": "2007",
"classification": "DECLASSIFIED",
"description": "Covers the FBI's sustained investigation through the 1970s including follow-up on named suspects, witness re-interviews, and inter-agency intelligence sharing. Contains reports on individuals who confessed to or were accused of being the Zodiac, along with elimination documentation. Includes correspondence with the Napa County Sheriff's Office and San Francisco Police Department regarding the Lake Berryessa and Presidio Heights attacks."
},
{
"id": "zodiac-fbi-part-06",
"title": "FBI Investigation File — Part 06 of 06 (Final)",
"filename": "zodiac-fbi-part-06.pdf",
"pages": 372,
"size_mb": 8.4,
"date_released": "2007",
"classification": "DECLASSIFIED",
"description": "The final and most voluminous section spanning decades of continued investigation from the late 1970s through the 2000s. Contains hundreds of public tips and leads forwarded to the FBI, suspect referrals from other law enforcement agencies, and periodic case reviews. Includes materials related to DNA analysis attempts on Zodiac letter stamps and envelopes, as well as the Bureau's assessment of various Zodiac suspects proposed by researchers and the public."
}
]
},
{
"id": "zodiac-fbi-part-02",
"title": "FBI Investigation File — Part 02 of 06",
"filename": "zodiac-fbi-part-02.pdf",
"pages": 88,
"size_mb": 2.9,
"date_released": "2007",
"classification": "DECLASSIFIED",
"description": "Contains the early FBI case file opened under Bureau file number 9-HQ-49911. Includes FOIPA (Freedom of Information/Privacy Acts) processing sheets, deleted page notifications citing exemptions under Section 552, and initial Bureau of Investigation reports from late 1969. Documents the FBI's first formal involvement after the Zodiac's threatening letters to San Francisco newspapers and the extortion classification of the case."
"id": "letters-and-cards",
"name": "Zodiac Letters & Cards",
"description": "High-resolution colour scans of the original letters, cards, and envelopes sent by the Zodiac Killer to Bay Area newspapers and other recipients between 1969 and 1974. These communications were the Zodiac's primary method of taunting law enforcement and the public, often containing ciphers, threats, and claims of additional victims. Sourced from zodiackiller.com's authenticated archive of original documents.",
"documents": [
{
"id": "letter-sf-chronicle-1969-07-31",
"title": "SF Chronicle Three-Part Cipher Letter — 31 July 1969",
"filename": "zodiac-letter-sf-chronicle-1969-07-31.pdf",
"pages": 4,
"size_mb": 0.8,
"date_released": "1969",
"classification": "ORIGINAL CORRESPONDENCE",
"description": "One of three near-identical letters sent simultaneously to the San Francisco Chronicle, SF Examiner, and Vallejo Times-Herald on 31 July 1969. Each letter contained one-third of the 408-character cipher and demanded front-page publication. The Chronicle version includes two pages of handwritten text claiming credit for the murders of David Faraday, Betty Lou Jensen, Darlene Ferrin, and Michael Mageau, plus envelope front and back with handwritten address to the SF Chronicle editor."
},
{
"id": "letter-sf-examiner-1969-07-31",
"title": "SF Examiner Three-Part Cipher Letter — 31 July 1969",
"filename": "zodiac-letter-sf-examiner-1969-07-31.pdf",
"pages": 3,
"size_mb": 1.1,
"date_released": "1969",
"classification": "ORIGINAL CORRESPONDENCE",
"description": "The San Francisco Examiner's portion of the three-part cipher mailing. Contains two pages of handwritten text with the killer's account of the Lake Herman Road and Blue Rock Springs attacks, plus the Examiner's one-third section of the Z408 cipher. Includes the original envelope addressed to the editor. The letter threatened to go on a killing spree if the cipher was not published on the front page."
},
{
"id": "letter-debut-1969-08-07",
"title": "Zodiac Debut Letter — \"Dear Editor, This is the Zodiac speaking\" — 7 August 1969",
"filename": "zodiac-letter-debut-1969-08-07.pdf",
"pages": 3,
"size_mb": 1.5,
"date_released": "1969",
"classification": "ORIGINAL CORRESPONDENCE",
"description": "The letter in which the killer first identified himself as 'the Zodiac' — a name that would define the case. Three pages sent to the San Francisco Examiner on 7 August 1969, opening with the now-iconic phrase 'Dear Editor, This is the Zodiac speaking.' Provides specific details about the Blue Rock Springs shooting that only the perpetrator could have known, including the type of ammunition used and the position of victims in the vehicle. Established the pattern of direct communication with the press."
},
{
"id": "letter-stine-1969-10-13",
"title": "Stine Letter with Bloody Shirt Swatch — 13 October 1969",
"filename": "zodiac-letter-stine-1969-10-13.pdf",
"pages": 1,
"size_mb": 0.6,
"date_released": "1969",
"classification": "ORIGINAL CORRESPONDENCE",
"description": "One of the most chilling Zodiac communications. Sent two days after the murder of taxi driver Paul Stine in the Presidio Heights neighbourhood of San Francisco. Contains a swatch of Stine's blood-soaked shirt as proof of the killing. The letter taunts police for their inability to catch him despite being stopped by officers near the crime scene. High-resolution colour scan showing the actual fabric swatch attached to the letter."
},
{
"id": "letter-bus-bomb-1969-11-09",
"title": "Bus Bomb Letter — 9 November 1969",
"filename": "zodiac-letter-bus-bomb-1969-11-09.pdf",
"pages": 6,
"size_mb": 3.4,
"date_released": "1969",
"classification": "ORIGINAL CORRESPONDENCE",
"description": "A six-page letter sent to the San Francisco Chronicle containing the famous 340-character cipher (Z340) that would remain unsolved for 51 years until December 2020. Includes a detailed bomb diagram with instructions for constructing a device to blow up a school bus, complete with technical specifications. The letter threatens to shoot out the tyres of a school bus and pick off the children as they come bouncing out. Also contains the Zodiac's crosshair symbol and score-keeping tally."
},
{
"id": "letter-belli-1969-12-20",
"title": "Belli Letter — 20 December 1969",
"filename": "zodiac-letter-belli-1969-12-20.pdf",
"pages": 3,
"size_mb": 1.1,
"date_released": "1969",
"classification": "ORIGINAL CORRESPONDENCE",
"description": "An unusual departure from the Zodiac's typically boastful tone. Addressed directly to celebrity attorney Melvin Belli rather than a newspaper, this letter takes on a desperate, almost pleading quality. The writer claims he cannot control his urge to kill and asks Belli for help. Some investigators believe this letter may be a hoax, though handwriting analysis was inconclusive. Includes original envelope with address to Belli's San Francisco law office."
},
{
"id": "letter-dragon-card-1970-04-28",
"title": "Dragon Card — 28 April 1970",
"filename": "zodiac-letter-dragon-card-1970-04-28.pdf",
"pages": 1,
"size_mb": 0.4,
"date_released": "1970",
"classification": "ORIGINAL CORRESPONDENCE",
"description": "A commercial greeting card depicting a dragon, sent to the San Francisco Chronicle. The Zodiac added his own handwritten text and a cipher. This communication is from the period when the Zodiac began sending cards rather than letters, possibly to reduce the amount of handwriting available for forensic analysis. The card is associated with the 'My name is' cipher era and the bomb threat escalation period."
},
{
"id": "letter-phillips66-1970-06-26",
"title": "Phillips 66 Map Letter — 26 June 1970",
"filename": "zodiac-letter-phillips66-1970-06-26.pdf",
"pages": 3,
"size_mb": 1.5,
"date_released": "1970",
"classification": "ORIGINAL CORRESPONDENCE",
"description": "Contains the Z32 'map code' cipher — a 32-character cryptogram alongside a section of a Phillips 66 road map of the San Francisco Bay Area with a specific location marked. The Zodiac claimed that combining the cipher solution with the map coordinates would reveal the location of a bomb he had planted. The map cipher remains unsolved. Includes the letter text, the annotated Phillips 66 map section, and the original envelope."
},
{
"id": "letter-little-list-1970-07-26",
"title": "Little List / Mikado Letter — 26 July 1970",
"filename": "zodiac-letter-little-list-1970-07-26.pdf",
"pages": 5,
"size_mb": 2.9,
"date_released": "1970",
"classification": "ORIGINAL CORRESPONDENCE",
"description": "A five-page letter referencing Gilbert and Sullivan's operetta 'The Mikado,' specifically the song 'I've Got a Little List' about people who 'never would be missed.' The Zodiac modified the lyrics to reflect his own targets and grievances. This is one of the longer Zodiac communications and demonstrates the killer's theatrical flair and literary awareness. Contains continued threats and updates to his claimed victim count."
},
{
"id": "letter-exorcist-1974-01-29",
"title": "Exorcist Letter (Last Confirmed) — 29 January 1974",
"filename": "zodiac-letter-exorcist-1974-01-29.pdf",
"pages": 2,
"size_mb": 0.7,
"date_released": "1974",
"classification": "ORIGINAL CORRESPONDENCE",
"description": "The last letter widely accepted as authentic Zodiac correspondence. References the horror film 'The Exorcist,' which was in cinemas at the time, calling it 'the best saterical comidy [sic] that I have ever seen.' Updates his victim count to 37 and rates the film. The letter's handwriting and phrasing are consistent with earlier confirmed Zodiac communications. After this letter, the Zodiac went permanently silent — no further authenticated communications were ever received."
},
{
"id": "letter-citizen-card-1974-05-08",
"title": "Citizen Card — 8 May 1974",
"filename": "zodiac-letter-citizen-card-1974-05-08.pdf",
"pages": 2,
"size_mb": 0.8,
"date_released": "1974",
"classification": "DISPUTED CORRESPONDENCE",
"description": "A card received by the San Francisco Chronicle in May 1974, shortly after the Exorcist letter. Its authenticity is disputed among Zodiac researchers — some consider it the final Zodiac communication whilst others believe it to be a hoax. The card references a local newspaper columnist and contains the Zodiac's characteristic crosshair symbol. If authentic, it would extend the timeline of Zodiac communications by several months beyond the Exorcist letter."
}
]
},
{
"id": "zodiac-fbi-part-03",
"title": "FBI Investigation File — Part 03 of 06",
"filename": "zodiac-fbi-part-03.pdf",
"pages": 108,
"size_mb": 1.7,
"date_released": "2007",
"classification": "DECLASSIFIED",
"description": "Centres on the Zodiac's coded communications and cipher analysis. Contains FBI teletypes between the San Francisco field office and Director's office (OctoberNovember 1969) regarding the newspaper cipher that appeared to identify the killer. Includes latent fingerprint comparison reports from the FBI Identification Division, attempting to match prints recovered from Zodiac crime scenes and letters against known individuals. Documents tips from the public claiming to have solved the cipher."
"id": "ciphers",
"name": "The Zodiac Ciphers",
"description": "The four known ciphers created by the Zodiac Killer, ranging from the solved Z408 (cracked within a week of publication in 1969) to the Z340 (unsolved for 51 years until a breakthrough in December 2020) to the still-unsolved Z13 and Z32. These cryptograms represent some of the most famous unsolved puzzles in criminal history and have attracted amateur and professional codebreakers for over five decades.",
"documents": [
{
"id": "cipher-z408",
"title": "Z408 Cipher — Three-Part Cipher (Solved 1969)",
"filename": "zodiac-cipher-z408-three-parts.pdf",
"pages": 2,
"size_mb": 0.8,
"date_released": "1969",
"classification": "CIPHER — SOLVED",
"description": "The first and most famous Zodiac cipher, split across three letters sent to Bay Area newspapers on 31 July 1969. The 408-character substitution cipher was solved within a week by schoolteacher Donald Harden and his wife Bettye of Salinas, California. The decoded message reads: 'I like killing people because it is so much fun… the most thrilling experience… even better than killing wild game in the forrest [sic].' The solution revealed the killer's motivation but not his identity, as the final 18 characters remain garbled."
},
{
"id": "cipher-z340",
"title": "Z340 Cipher — 340-Character Cipher (Solved 2020)",
"filename": "zodiac-cipher-z340.pdf",
"pages": 1,
"size_mb": 0.6,
"date_released": "1969",
"classification": "CIPHER — SOLVED",
"description": "The 340-character cipher sent with the Bus Bomb letter on 9 November 1969. Resisted all decryption attempts for 51 years until December 2020, when Australian mathematician Sam Blake, Belgian programmer Jarl Van Eycke, and American codebreaker David Oranchak cracked it using computational analysis. The solution revealed the message: 'I hope you are having lots of fun in trying to catch me… I am not afraid of the gas chamber because it will send me to paradice [sic].' The cipher used a complex combination of substitution and transposition."
},
{
"id": "cipher-z340-solution",
"title": "Z340 Solution — Academic Paper (Oranchak, Blake & Van Eycke, 2024)",
"filename": "zodiac-cipher-z340-solution-oranchak-2024.pdf",
"pages": 38,
"size_mb": 16.6,
"date_released": "2024",
"classification": "ACADEMIC PUBLICATION",
"description": "The peer-reviewed academic paper detailing the complete solution methodology for the Z340 cipher. Published on arXiv by David Oranchak, Sam Blake, and Jarl Van Eycke — the team that cracked the cipher in December 2020 after 51 years. Describes the computational approach combining homophonic substitution analysis with transposition pattern detection, the AZdecrypt software tool, and the step-by-step decryption process. Includes full cipher analysis, key tables, and comparison with the earlier Z408 cipher structure."
},
{
"id": "cipher-z32",
"title": "Z32 Map Code Cipher (Unsolved)",
"filename": "zodiac-cipher-z32-map-code.pdf",
"pages": 1,
"size_mb": 0.7,
"date_released": "1970",
"classification": "CIPHER — UNSOLVED",
"description": "The 32-character 'map code' cipher sent alongside a Phillips 66 road map on 26 June 1970. The Zodiac claimed that solving the cipher and combining it with the map coordinates would reveal the location of a bomb. Unlike the Z408 and Z340 ciphers which were substitution-based, the Z32's brevity makes frequency analysis virtually impossible. The cipher remains unsolved and represents one of the enduring mysteries of the case. This document contains the high-resolution map section with the Zodiac's annotations and markings."
}
]
},
{
"id": "zodiac-fbi-part-04",
"title": "FBI Investigation File — Part 04 of 06",
"filename": "zodiac-fbi-part-04.pdf",
"pages": 258,
"size_mb": 11.7,
"date_released": "2007",
"classification": "DECLASSIFIED",
"description": "The largest section of the released files, containing extensive FBI Laboratory Division analysis and Identification Division records. Includes forensic examination reports on physical evidence from Zodiac crime scenes, handwriting analysis of the Zodiac's letters, and ballistics reports. Contains inter-office airmail correspondence (NovemberDecember 1969) between Sacramento, San Francisco field offices and FBI headquarters directing investigative priorities. Documents coordination with California Department of Justice and local law enforcement agencies."
},
{
"id": "zodiac-fbi-part-05",
"title": "FBI Investigation File — Part 05 of 06",
"filename": "zodiac-fbi-part-05.pdf",
"pages": 207,
"size_mb": 7.8,
"date_released": "2007",
"classification": "DECLASSIFIED",
"description": "Covers the FBI's sustained investigation through the 1970s including follow-up on named suspects, witness re-interviews, and inter-agency intelligence sharing. Contains reports on individuals who confessed to or were accused of being the Zodiac, along with elimination documentation. Includes correspondence with the Napa County Sheriff's Office and San Francisco Police Department regarding the Lake Berryessa and Presidio Heights attacks. Documents the gradual transition of the case from active to cold status."
},
{
"id": "zodiac-fbi-part-06",
"title": "FBI Investigation File — Part 06 of 06 (Final)",
"filename": "zodiac-fbi-part-06.pdf",
"pages": 372,
"size_mb": 8.4,
"date_released": "2007",
"classification": "DECLASSIFIED",
"description": "The final and most voluminous section spanning decades of continued investigation from the late 1970s through the 2000s. Contains hundreds of public tips and leads forwarded to the FBI, suspect referrals from other law enforcement agencies, and periodic case reviews. Documents the investigation of theorist Gareth Penn and his suspect Michael O'Hare. Includes materials related to DNA analysis attempts on Zodiac letter stamps and envelopes, as well as the Bureau's assessment of various Zodiac suspects proposed by researchers and the public over the years."
"id": "police-reports",
"name": "Police Reports & Crime Scene Documents",
"description": "Original law enforcement reports, crime scene documentation, and investigative files from the four confirmed Zodiac attacks. These reports were filed by the Benicia Police Department, Vallejo Police Department, Napa County Sheriff's Office, and San Francisco Police Department between December 1968 and October 1969. This sub-collection is under active expansion as documents are sourced from public archives.",
"documents": []
}
]
}

View file

@ -1084,3 +1084,80 @@ mark.ur-highlight {
font-size: 0.78rem;
}
}
/* ═══ Subcollection Accordion ═══ */
.prop-subcollection {
border: 1px solid rgba(255, 45, 45, 0.15);
border-radius: 2px;
margin-bottom: 0.75rem;
background: rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.prop-subcollection-header {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.8rem 1rem;
cursor: pointer;
background: rgba(255, 45, 45, 0.04);
border-bottom: 1px solid rgba(255, 45, 45, 0.1);
transition: background 0.2s;
user-select: none;
}
.prop-subcollection-header:hover {
background: rgba(255, 45, 45, 0.08);
}
.prop-subcollection-toggle {
font-size: 1rem;
color: #ff2d2d;
transition: transform 0.25s;
display: inline-block;
}
.prop-subcollection.collapsed .prop-subcollection-toggle {
transform: rotate(-90deg);
}
.prop-subcollection.collapsed .prop-doc-list,
.prop-subcollection.collapsed .prop-subcollection-desc,
.prop-subcollection.collapsed .prop-empty {
display: none;
}
.prop-subcollection-name {
font-family: 'JetBrains Mono', monospace;
font-size: 0.82rem;
font-weight: 700;
color: #ff4444;
letter-spacing: 0.06em;
text-transform: uppercase;
flex: 1;
}
.prop-subcollection-count {
font-family: 'JetBrains Mono', monospace;
font-size: 0.68rem;
color: #888;
letter-spacing: 0.05em;
}
.prop-subcollection-desc {
font-family: 'JetBrains Mono', monospace;
font-size: 0.72rem;
color: #999;
padding: 0.6rem 1rem;
line-height: 1.5;
letter-spacing: 0.02em;
border-bottom: 1px solid rgba(255, 45, 45, 0.06);
}
.prop-subcollection .prop-doc-list {
padding: 0.4rem 0;
}
.prop-subcollection .prop-doc-item {
margin: 0 0.5rem;
}

View file

@ -29,14 +29,18 @@
// ─── Helpers ──────────────────────────────────────────
function countDocuments(obj) {
if (!obj) return 0;
if (Array.isArray(obj.documents)) return obj.documents.length;
let count = 0;
if (Array.isArray(obj.documents)) count += obj.documents.length;
if (Array.isArray(obj.subcollections)) {
count += obj.subcollections.reduce((s, sc) => s + (sc.documents ? sc.documents.length : 0), 0);
}
if (Array.isArray(obj.collections)) {
return obj.collections.reduce((s, c) => s + (c.documents ? c.documents.length : 0), 0);
count += obj.collections.reduce((s, c) => s + countDocuments(c), 0);
}
if (Array.isArray(obj.countries)) {
return obj.countries.reduce((s, cn) => s + countDocuments(cn), 0);
count += obj.countries.reduce((s, cn) => s + countDocuments(cn), 0);
}
return 0;
return count;
}
function countCollections(obj) {
@ -63,6 +67,9 @@
(cn.collections || []).forEach(col => {
collections++;
docs += (col.documents || []).length;
(col.subcollections || []).forEach(sc => {
docs += (sc.documents || []).length;
});
});
});
});
@ -241,7 +248,8 @@
}
function renderCollectionCard(catId, countryCode, col, flag) {
const nDocs = col.documents ? col.documents.length : 0;
let nDocs = col.documents ? col.documents.length : 0;
if (col.subcollections) nDocs += col.subcollections.reduce((s, sc) => s + (sc.documents ? sc.documents.length : 0), 0);
return `<div class="prop-collection-card" onclick="location.hash='collection/${catId}/${countryCode}/${col.id}'">
<div class="prop-collection-name">${flag ? flag + ' ' : ''}${esc(col.name)}</div>
<div class="prop-collection-year">${esc(col.year)}</div>
@ -300,36 +308,90 @@
</div>`;
});
html += '</div>';
} else {
} else if (!col.subcollections || col.subcollections.length === 0) {
html += renderEmpty('NO DOCUMENTS UPLOADED YET', 'This collection has been catalogued but documents have not yet been uploaded to the archive.');
}
// Subcollections (accordion sections)
if (col.subcollections && col.subcollections.length > 0) {
html += `<div class="prop-section-label">SUB-COLLECTIONS [${col.subcollections.length}]</div>`;
col.subcollections.forEach(sc => {
const nScDocs = sc.documents ? sc.documents.length : 0;
html += `<div class="prop-subcollection">`;
html += `<div class="prop-subcollection-header" onclick="this.parentElement.classList.toggle('collapsed')">`;
html += `<span class="prop-subcollection-toggle">▾</span>`;
html += `<span class="prop-subcollection-name">${esc(sc.name)}</span>`;
html += `<span class="prop-subcollection-count">[${nScDocs} DOC${nScDocs === 1 ? '' : 'S'}]</span>`;
html += `</div>`;
if (sc.description) {
html += `<div class="prop-subcollection-desc">${esc(sc.description)}</div>`;
}
if (sc.documents && sc.documents.length > 0) {
html += '<div class="prop-doc-list">';
sc.documents.forEach(doc => {
html += `<div class="prop-doc-item" onclick="location.hash='doc/${catId}/${countryCode}/${colId}/${sc.id}/${doc.id}'">`;
html += `<div class="prop-doc-icon">📄</div>`;
html += `<div class="prop-doc-info">`;
html += `<div class="prop-doc-title">${esc(doc.title)}</div>`;
html += `<div class="prop-doc-desc">${esc(doc.description)}</div>`;
html += `<div class="prop-doc-meta">`;
html += `<span>PAGES: ${doc.pages || '?'}</span>`;
html += `<span>RELEASED: ${esc(doc.date_released || 'UNKNOWN')}</span>`;
html += `<span>${esc(doc.filename)}</span>`;
html += `</div></div>`;
html += `<div class="prop-doc-badge">VIEW</div>`;
html += `</div>`;
});
html += '</div>';
} else {
html += renderEmpty('NO DOCUMENTS YET', 'Documents for this sub-section have not yet been uploaded.');
}
html += '</div>';
});
}
ROOT.innerHTML = html;
}
// ─── View: Document (PDF Viewer) ──────────────────────
function renderDocument(catId, countryCode, colId, docId) {
function renderDocument(catId, countryCode, colId, subOrDocId, maybeDocId) {
const cat = findCategory(catId);
if (!cat) return renderNotFound('Category not found');
const country = findCountry(cat, countryCode);
if (!country) return renderNotFound('Country not found');
const col = findCollection(country, colId);
if (!col) return renderNotFound('Collection not found');
const doc = findDocument(col, docId);
if (!doc) return renderNotFound('Document not found');
const pdfUrl = `${PDF_BASE}/${catId}/${countryCode}/${colId}/${doc.filename}`;
let doc, subcol, docId, pdfUrl;
if (maybeDocId) {
// 6-part route: subcollection doc
const scId = subOrDocId;
docId = maybeDocId;
subcol = (col.subcollections || []).find(sc => sc.id === scId);
if (!subcol) return renderNotFound('Sub-collection not found');
doc = findDocument(subcol, docId);
if (!doc) return renderNotFound('Document not found');
pdfUrl = `${PDF_BASE}/${catId}/${countryCode}/${colId}/${scId}/${doc.filename}`;
} else {
// 5-part route: direct collection doc
docId = subOrDocId;
doc = findDocument(col, docId);
if (!doc) return renderNotFound('Document not found');
pdfUrl = `${PDF_BASE}/${catId}/${countryCode}/${colId}/${doc.filename}`;
}
let html = '';
html += `<div class="prop-classification">CASE FILE // ${esc(doc.title).toUpperCase()}</div>`;
html += breadcrumb([
const bcParts = [
{ label: 'CRIME SCENE', hash: '' },
{ label: cat.name, hash: `category/${catId}` },
{ label: `${country.flag || ''} ${country.name}`, hash: `country/${catId}/${countryCode}` },
{ label: col.name, hash: `collection/${catId}/${countryCode}/${colId}` },
{ label: doc.title }
]);
{ label: col.name, hash: `collection/${catId}/${countryCode}/${colId}` }
];
if (subcol) bcParts.push({ label: subcol.name });
bcParts.push({ label: doc.title });
html += breadcrumb(bcParts);
// Viewer
html += `<div class="prop-viewer-container" id="pdfViewerContainer">
@ -876,6 +938,37 @@
}
});
// Index subcollection documents
(col.subcollections || []).forEach(sc => {
(sc.documents || []).forEach(doc => {
const scFields = [
doc.title || '',
doc.description || '',
sc.name || '',
col.name || '',
cat.name || '',
cn.name || '',
doc.year ? String(doc.year) : '',
doc.classification || ''
];
const scCombined = scFields.join(' ').toLowerCase();
if (scCombined.includes(q)) {
results.push({
doc,
catName: cat.name,
catId: cat.id,
catIcon: cat.icon || '📁',
countryName: cn.name,
countryCode: cn.code,
countryFlag: cn.flag || '',
colName: sc.name,
colId: col.id,
subcolId: sc.id
});
}
});
});
// Also match collection-level (even if no docs match)
const colFields = [col.name || '', col.description || '', cat.name || '', cn.name || ''].join(' ').toLowerCase();
if (colFields.includes(q) && !results.find(r => r.colId === col.id && r.catId === cat.id && r.countryCode === cn.code && !r.doc.id)) {
@ -915,9 +1008,14 @@
results.forEach(r => {
const title = highlightMatch(esc(r.doc.title || 'Untitled'), q);
const desc = r.doc.description ? highlightMatch(esc(truncateText(r.doc.description, 160)), q) : '';
const hash = r.isCollection
? `country/${r.catId}/${r.countryCode}`
: `doc/${r.catId}/${r.countryCode}/${r.colId}/${r.doc.id}`;
let hash;
if (r.isCollection) {
hash = `country/${r.catId}/${r.countryCode}`;
} else if (r.subcolId) {
hash = `doc/${r.catId}/${r.countryCode}/${r.colId}/${r.subcolId}/${r.doc.id}`;
} else {
hash = `doc/${r.catId}/${r.countryCode}/${r.colId}/${r.doc.id}`;
}
html += `<div class="ur-result-card" onclick="location.hash='${hash}'">
<div class="ur-result-header">
@ -994,6 +1092,8 @@
renderCategory(parts[1], parts[2]);
} else if (parts[0] === 'collection' && parts.length >= 4) {
renderCollection(parts[1], parts[2], parts[3]);
} else if (parts[0] === 'doc' && parts.length >= 6) {
renderDocument(parts[1], parts[2], parts[3], parts[4], parts[5]);
} else if (parts[0] === 'doc' && parts.length >= 5) {
renderDocument(parts[1], parts[2], parts[3], parts[4]);
} else {