Contents
Document Popups — One Enriched Viewer
Every local exhibit (exhibits/…pdf) opens the single enriched pdf.js viewer. This viewer is scaffolded by chrome.ts pdfModal() and lives at #pdf-modal — its markup is emitted once per page and never duplicated. app.ts initViewer() wires all open/close/page-turn behaviour at boot.
Routing rule. Former .stepdetail local-doc anchors route to the enriched viewer via app.ts openStep(). Only remote or CORS-blocked documents (SEC EDGAR full-submission TXTs, external registrar pages) fall through to the #info-modal iframe. Do not open a local exhibit in the iframe — it bypasses enrichment and breaks deep-link state.
Deep-link. Appending #doc=<url> to any page URL causes the viewer to auto-open that document on load. Scheme pages and the complaint generate these links for every cited exhibit.
- Correct:
openStep(url)→#pdf-modalopens, enrichment loads. - Incorrect: opening a local PDF in
#info-modal, or emitting a second<div id="pdf-modal">.
Enriched Viewer Layout
The enriched viewer (#pdf-modal) has a two-zone body: a summary band and a split panel.
Desktop layout (≥ 900 px)
- Summary band —
#pdf-enrichfull-width, rendered above the split panel. Contains the AI-generated one-paragraph summary of the document. - Split panel — two columns inside
.pdf-body:- Left cell —
#pdf-enrich-more: numbered bullet points drawn from the enrichment JSON. Item #1 top-aligns to the PDF page top (CSSalign-self: start; padding-top: 0). - Right cell —
.pdf-stage: the rendered PDF canvas pages.
- Left cell —
- Toolbar —
flex-columnon the right edge of the panel: navigation controls at the top, document title (#pdf-title) below. Controls: prev/next page (), zoom in/out, open in new tab (), view on SEC.gov (), close (×).
Mobile layout (< 900 px)
Stacks vertically: summary band → numbered points → PDF stage. The toolbar collapses to a minimal strip at the top of the panel.
Deep-link
#doc=<url> on any page automatically opens the viewer for that URL, sets the title, and triggers enrichment load. This is the canonical way to share a document reference.
Enrichment Data
Each exhibit's enrichment lives in doc-enrich/<docSlug(url)>.json, generated by scripts/enrich.ts.
Generation
All enrichment requests route exclusively through the gpumon ingress at 192.168.1.211:4010. Never call a public LLM endpoint directly from enrichment scripts — the ingress handles model routing, rotation, and attribution.
Triples convention
- Acronyms — rendered ALL-CAPS: SEC, DTC, CEDE, FBAR, RICO, LOM, SDI, CIK, CUSIP, EDGAR, DTCC, FINRA, OTC, PPS, REG S, SOX.
- Organisation names — Title Case (Davi Skin Inc., Tele-Lawyer, Dynamic Associates).
- Relator reference — "the relator" (never "Mark Phillips" outside case captions; never "M.P." in enrichment prose, only in formal captions).
JSON shape
{
"summary": "One-paragraph plain-language summary.",
"points": [
{ "n": 1, "text": "First key finding." },
{ "n": 2, "text": "Second key finding." }
]
}
The viewer checks for this file at boot; if absent it shows the summary band as hidden and the split panel renders full-width.
Narration
Narration is the primary accessibility and engagement feature. The conventions below are locked and must not be varied between pages.
Animated head
- Exactly one talking head per page — mounted by
tts.tsat playback start, not at idle. The gear icon activates the head when the user presses play, not when the panel opens. - Wiretap popups (citation footnotes opened via a source link) have no head.
Page / scheme narration flow
- The "Play narration" button (
[data-narrate]) appears as a CTA a line below the scheme or page intro text, rendered viachrome.ts narrateBadges(page). - Clicking it opens the karaoke popup with live word-highlighting. The choice modal shows: glyph + title + about blurb + duration per mode.
- The gear panel ( settings) exposes speed, auto-advance, and talking-head placement controls. It does not duplicate the play CTA.
Karaoke popup
The popup handles its own head instance. It is opened by narrate.ts, not by the gear. Do not wire [data-narrate] in tts.ts — the two systems must remain decoupled.
Audio file types
- Per-chapter clips —
synth-cache/<page>/af_heart/<id>.mp3, one per narration chapter. The TTS endpoint caps a single request at ~27 s, sorender-narration-audio.tssplits each chapter into ≤350-char pieces and ffmpeg-concats them (never send a whole chapter in one call). - Stitched reading —
_full.mp3in the same folder: all chapters concatenated for a continuous "full version" / podcast enclosure. - Highlights vs full — some readings have both a short overview (
complaint,…-highlights) and a full reading (complaint-full, the full briefs). The §341 and depositions ship a real(highlights).mp3+(audio).mp3pair. - Real recordings — wiretaps and the §341 exam are non-TTS MP3s under
exhibits/; multi-voice depositions/trials are single stitched MP3s referenced bycourtroom/<slug>.json.
Transcripts & visage files
- Narration JSON —
narration/<page>.json: chaptertitle+text; the build injects per-chapterdur+ readingtotalDurfromdurations.jsonso the popup can show chapter x/y · playlist elapsed/total. - Word timings —
narration-transcripts/<page>/<id>.words.json(Whisper) drive precise karaoke; when absent/stale (audio re-rendered) the players fall back to a proportional highlight (elapsed/duration). - Courtroom turns —
courtroom/<slug>.jsoncarries speakerturnswith start/end ms for the multi-voice per-turn highlight. - Visage envelopes —
<id>.visage.json(rms/fps) were the lip-sync source; players are now analyser-driven off the audio element, and durations come from ffprobe, so visage is optional (deleted on re-render).
The players (five)
- Settings / gear TTS (
tts.ts) — the header gear; readsnarration/<page>.jsonwith the floating head + voice/speed settings. - Narrated walk (
walk.ts,[data-walk]) — report/primer/precrime; a floating bar that scrolls the page, floating head, karaoke box (with a "read" toggle that expands to the full transcript in-place). - Courtroom popup (
courtroom.ts,[data-media-all]/[data-courtroom]/[data-media-full]) — the gold-standard popup: source PDF beside a chaptered player with read-along prose highlight, the floating cane head, chapter list + durations, and a reciprocal highlights⇄full toggle stating the other reading's runtime. - Narrate modal (
narrate.ts,[data-narrate]) — the standalone chaptered karaoke popup. - Wiretap popup (
wiretap.ts,[data-wt-audio]) — word-synced intercept transcript with seek + download; no head.
PDF viewer
The enriched source viewer (viewer.ts + chrome.ts pdfModal()) opens any [data-doc] / citation link: page nav, zoom, open-in-tab, and an enrichment panel (title, gist, doc-type badge, entity triples, statutes). When a document has associated audio (doc-audio.ts) it surfaces a "play narration" chip. Inside the courtroom popup the PDF sits in a split panel beside the player.
Podcast
The podcast page (podcast.html) surfaces three ordered sections:
- Site narration — full-page readings of complaint + scheme pages.
- Forensic-brief deep-dives — topic-focused audio episodes.
- Source pleadings / trial / §341 / depositions — primary-record recordings.
Per-item popup
Each item has a single button that opens a popup (never inline) with exactly four actions:
- listen — play audio in the popup player.
- karaoke — open the karaoke reader.
- view doc — open the source document in the enriched viewer.
- download — download the MP3.
No other actions appear in the popup. Do not embed a player directly on the page list — always use the popup pattern.
Design Tokens & Chrome
Color tokens (CSS custom properties)
| Token | Light | Dark | Purpose |
|---|---|---|---|
--bg | #fafafa | #0b0c0e | Page background |
--panel | #ffffff | #131518 | Card / modal surface |
--panel-2 | #f6f6f4 | #0f1114 | Secondary surface, hero background |
--ink | #1a1a1a | #e9e7df | Primary text |
--dim | #6b6b6b | #8c8e86 | Muted / secondary text |
--faint | #9b9b96 | #5c5e58 | Build stamps, fine print |
--line | #e7e7e4 | #262a2f | Borders, dividers |
--line-2 | #d0d0d0 | #3a3e45 | Heavier borders |
--accent | #b3791a | #e0a23a | Primary accent (amber) |
--accent-2 | #4a72b0 | #5fb4b4 | Secondary accent (blue/teal) |
--accent-3 | #8b6fbf | #c486ad | Tertiary accent / error (violet/rose) |
Typography
--mono: 'JetBrains Mono', ui-monospace, Menlo, monospace — code, labels, build stamps, the disclaimer bar, nav items.--sans: 'Space Grotesk', system-ui, sans-serif — body text, headings, prose.- Body weight 450 (Space Grotesk), line-height 1.5.
Nerd Font glyphs (nf())
All icons are Nerd Font codepoints rendered via the bundled nerd-icons.woff2 subset through the .nf CSS class. Call nf(name) (from src/data/glyphs.ts) — never paste raw Unicode or use Font Awesome CDN.
| Key | Glyph | Use |
|---|---|---|
link | | Citation / source link |
external | | Opens on SEC.gov in new tab |
pdf | | Source PDF → opens modal viewer |
moon | | Theme toggle (currently light) |
sun | | Theme toggle (currently dark) |
chevronDown | | Scroll cue / back arrow |
scales | | Statutes / law / notice |
fileText | | Document / transcript |
download | | Download MP3 or file |
headphones | | Listen / full audiobook |
gavel | | Litigation / court |
shield | | Concealment |
Standard page shell
Every standalone page follows this structure (see primer.ts renderPrimerPage() as the canonical template):
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<!-- charset, viewport, Google Fonts (JetBrains Mono + Space Grotesk), main.css -->
</head>
<body class="<pagename>page">
<div class="disclaimer-bar"> … allegation notice … </div>
<header class="hero-bar">
<a class="brand backlink" href="index.html">back to the report</a>
<span class="brand brand-r"> … page subtitle … </span>
<button id="themeToggle" class="toggle"> … moon/sun glyph … </button>
</header>
<div class="fig-intro">
<span class="eyebrow"> … section label … </span>
<h1 class="fig-h1"> … page title … </h1>
<p class="dek"> … one-sentence description … </p>
</div>
<main class="wrap <page>-wrap"> … sections … </main>
<!-- pdfModal() scaffold, required even if no exhibits are cited -->
<script type="module" src="${opts.jsHref}"></script>
</body>
</html>Deploy
Deployment uses deploy.ts and is keyed to a slug: the first 8 hex chars of the SHA-256 of the packaged exhibit corpus.
bun run build --gateway # quick verify; IPFS gateway links, no local exhibits
bun run build # full; packages referenced exhibits into dist/
bun run deploy.ts --slug <first-8-of-sha256>
Build modes
--gateway— asset URLs resolve viaatsignhandle.xyz/ipfs/<cid>; no exhibit files copied to dist. Fast verify iteration.--relative— exhibit files bundled into dist; no CID fallback links.- (default) — hybrid: CID gateway links + referenced exhibits copied to dist.
Version stamp
Every .html file in dist receives a <footer> version stamp — slug + ISO date UTC — injected just before </body>. Do not remove or override this.
Asset cache-busting
Shared CSS and JS are referenced as main.css?v=<timestamp-base36> and app.js?v=<timestamp-base36>. The version token changes on every build; browsers always load fresh assets on a new deploy.
Component Gallery
Live, styled examples of the real components below — rendered with the site's own main.css classes, not mockups. If a component looks different here than on a page, the page has drifted from convention.
Buttons
.pod-listen — the play/listen chip used on podcast items and courtroom rows. .narr-dur is the duration chip appended after a label.
.litidx-btn — the compact mono action buttons on case-index rows (preview / open / play).
.figbtn — the outlined accent button used under exhibit figures.
Badges & tags
.lit-tag reads its border/text color from the inline --tag CSS custom property, set per-entity from the ENTITY_TAGS palette in litigation.ts. A tag with a data-cap attribute shows the full Bluebook caption (case number + jurisdiction) as a hover tooltip — try hovering the chips below.
RICO ENTERPRISE DiScala Dynamic / LATI MW Medical Davi Skin SDI / Galaxy Tarapaski SuperPAC Hearn Wallace (personal) MOD / PHILLIPS
A full case-index row (.litidx-row) as it renders in the chronological index, badge + name + action buttons:
Color-token swatches
Rendered chips for the palette table in §6 — actual computed background from each CSS custom property.
| Token | Swatch | Purpose |
|---|---|---|
--bg | Page background | |
--panel | Card / modal surface | |
--panel-2 | Secondary surface | |
--accent | Primary accent (amber) | |
--accent-2 | Secondary accent (blue/teal) | |
--accent-3 | Tertiary accent (violet/rose) | |
--line | Borders, dividers |
Source note + quote pair
.pod-src-note is the plain lead-in sentence; .pod-src-quote is the pulled verbatim quote, with .pod-src-qcite for the attribution line and .pod-src-qtime highlighting the timecode within it.
The witness is asked directly whether the note was backdated.
"I don't recall drafting that note on that date."§341 Meeting of Creditors — 12:27
Cards
.plead-card — the bordered pleading card used in key-documents rows; .kd-pane is its flex-column layout wrapper inside a .kd-row split.
Charges the RICO enterprise's core defendants with securities fraud, money laundering, and obstruction across the shell-issuance scheme.
read the filing