Internal reference. Living document — update this page whenever a UX convention is added or changed.
back to the report site conventions · ux & design spec
Site Reference

UX & Design Conventions

Locked layout and interaction rules for the Cane enterprise interactive report. Consult this page before adding a new scheme, page, or component — every convention here is active and must be preserved across builds.

0

Contents

1

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-modal opens, enrichment loads.
  • Incorrect: opening a local PDF in #info-modal, or emitting a second <div id="pdf-modal">.
2

Enriched Viewer Layout

The enriched viewer (#pdf-modal) has a two-zone body: a summary band and a split panel.

Desktop layout (≥ 900 px)

  1. Summary band#pdf-enrich full-width, rendered above the split panel. Contains the AI-generated one-paragraph summary of the document.
  2. 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 (CSS align-self: start; padding-top: 0).
    • Right cell.pdf-stage: the rendered PDF canvas pages.
  3. Toolbarflex-column on 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.

3

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.

4

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.ts at 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

  1. The "Play narration" button ([data-narrate]) appears as a CTA a line below the scheme or page intro text, rendered via chrome.ts narrateBadges(page).
  2. Clicking it opens the karaoke popup with live word-highlighting. The choice modal shows: glyph + title + about blurb + duration per mode.
  3. 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 clipssynth-cache/<page>/af_heart/<id>.mp3, one per narration chapter. The TTS endpoint caps a single request at ~27 s, so render-narration-audio.ts splits each chapter into ≤350-char pieces and ffmpeg-concats them (never send a whole chapter in one call).
  • Stitched reading_full.mp3 in 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).mp3 pair.
  • Real recordings — wiretaps and the §341 exam are non-TTS MP3s under exhibits/; multi-voice depositions/trials are single stitched MP3s referenced by courtroom/<slug>.json.

Transcripts & visage files

  • Narration JSONnarration/<page>.json: chapter title + text; the build injects per-chapter dur + reading totalDur from durations.json so the popup can show chapter x/y · playlist elapsed/total.
  • Word timingsnarration-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 turnscourtroom/<slug>.json carries speaker turns with 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; reads narration/<page>.json with 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.

5

Podcast

The podcast page (podcast.html) surfaces three ordered sections:

  1. Site narration — full-page readings of complaint + scheme pages.
  2. Forensic-brief deep-dives — topic-focused audio episodes.
  3. 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.

6

Design Tokens & Chrome

Color tokens (CSS custom properties)

TokenLightDarkPurpose
--bg#fafafa#0b0c0ePage background
--panel#ffffff#131518Card / modal surface
--panel-2#f6f6f4#0f1114Secondary surface, hero background
--ink#1a1a1a#e9e7dfPrimary text
--dim#6b6b6b#8c8e86Muted / secondary text
--faint#9b9b96#5c5e58Build stamps, fine print
--line#e7e7e4#262a2fBorders, dividers
--line-2#d0d0d0#3a3e45Heavier borders
--accent#b3791a#e0a23aPrimary accent (amber)
--accent-2#4a72b0#5fb4b4Secondary accent (blue/teal)
--accent-3#8b6fbf#c486adTertiary 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.

KeyGlyphUse
linkCitation / source link
externalOpens on SEC.gov in new tab
pdfSource PDF → opens modal viewer
moonTheme toggle (currently light)
sunTheme toggle (currently dark)
chevronDownScroll cue / back arrow
scalesStatutes / law / notice
fileTextDocument / transcript
downloadDownload MP3 or file
headphonesListen / full audiobook
gavelLitigation / court
shieldConcealment

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>
7

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 via atsignhandle.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.