Handoff: Riprap Findings Region
Current target version: v0.4.5 (polish on v0.4.4 β now applied in Riprap Stone-Grouped UI v0.4.5.html). See V0.4.5_SPEC.md for the nine deltas. This README is the v0.4.4 specification and remains the reference for everything not changing in v0.4.5.
Read order for the implementer:
CLAUDE_CODE_PROMPT.mdβ paste into Claude CodeV0.4.5_SPEC.mdβ the nine fixes (now realized inv0.4.5.html)README.md(this file) β v0.4.4 referencedesign_files/β prototypes (both v0.4.4 and v0.4.5 HTMLs are present)
Overview
Riprap is a citation-grounded Flood Exposure Briefing tool for New York City. A user enters an address, neighborhood, or proposed development; Riprap returns a written briefing where every numeric claim links to its primary public-record source (FEMA, USGS, NYC DOITT, FloodNet, NYC OpenData, etc.).
This handoff covers the Findings region β the structured-data sibling of the briefing prose. It groups model outputs into five named "Stones" (cognitive roles), each rendered as a card stack with explicit epistemic tiering, smart provenance traces, and cross-linking to the map.
The target codebase is SvelteKit (Svelte 5 with runes). The files in design_files/ are React-based prototypes β design references, not production code. Recreate them as Svelte components.
About the Design Files
The files in design_files/ are design references created in HTML + React β high-fidelity prototypes showing the intended look and behavior. They are not production code to copy directly.
Your task is to recreate these designs in the existing Svelte codebase, using its established patterns (Svelte 5 runes, scoped styles, the project's existing route structure and data layer). Lift visual values, copy, and interaction logic from the references; do not transpile JSX to Svelte.
File pairing (each prototype area has one or two source files):
Riprap Stone-Grouped UI v0.4.4.htmlβ main prototype, the v0.4.4 Findings region in contextfindings.jsxβ Findings region: stones, cards, run-health, grammar reference (the centerpiece of this handoff)briefing.jsxβ long-form briefing prose with inline citationsmap.jsxβ mini-map with FEMA AE / HWM / FloodNet / 311 / address layers and link highlightingtrace.jsx,stones-trace.jsx,stone-evidence.jsxβ provenance trace variantsshell.jsxβ app header, footer, cold-start stateglyphs.jsxβ four tier glyphs as inline SVGtokens.cssβ design tokens (colors, type, spacing). Port verbatim.styles.cssβ component CSS. Reference only; rewrite as scoped Svelte styles.tweaks-panel.jsx,design-canvas.jsx,landing-variants.*β prototype-time tooling. Ignore.
Fidelity
High-fidelity (hifi). Pixel-perfect mockups with final colors, typography, spacing, glyphs, and interactions. Recreate the UI pixel-perfectly using the codebase's existing libraries and patterns. The tier color values, the IBM Plex font stack, the 4/8/12/16/24/32/48/64/96 spacing scale, and the four-tier glyph system are all final.
The Findings region in particular has been through several iterations (v0.4.0 β v0.4.4) and is settled. Don't redesign card layouts; port them as-is.
Screens / Views
The product is a single-page app with two states: cold-start (no query) and briefing (query active).
1. Cold-start
Empty state with the wordmark, a deck explaining what Riprap is, a query input, three sample-query buttons, and a "How Riprap is built" trust band. See shell.jsx β <ColdStart>.
- Layout: centered single column, max-width ~720px, paper background (
--paper) - Wordmark:
ripraplowercase, IBM Plex Mono 14px / 600, with a 0.85em accent barβprefix - Deck: serif paragraph, 18px, line-height 1.55, ink-secondary
- Query input: full width, 1px ink border, mono placeholder, 16px
- Submit: ink fill, paper text, mono caps, 13px
- Sample queries: three buttons in a column, each shows mode (caps mono) / query (sans 16px) / sub (mono 12px tertiary)
- Trust band: section-label heading, italic-serif "Cornerstone remembers. Keystone tallies. Touchstone watches. Lodestone projects. Capstone writes it all down with citations." then a bullet list
2. Briefing (active query)
Three-region layout, vertical stack:
- App header (sticky top): wordmark Β· context Β· query pill Β· methodology / export PDF / live status
- Briefing prose region: long-form text with inline
[N]citations, three side-by-side panes (excerpt Β· evidence cards Β· mini map) - Findings region: the v0.4.4 Stone-grouped card stack (this handoff's focus)
- Footer: tier legend + build line
The Findings region is the substantial new surface. Everything below documents it.
Findings Region (v0.4.4) β detailed spec
Composition
<FindingsRegion>
<RunHealthStrip /> β 1 row, top, summarizes all 25 model calls
<StoneRegion stone="cornerstone" />
<StoneRegion stone="keystone" />
<StoneRegion stone="touchstone" />
<StoneRegion stone="lodestone" />
<StoneRegion stone="capstone" />
<CardGrammarReference /> β optional, on by default; one stub per variant
</FindingsRegion>
Stones
Five fixed roles, in order:
| key | name | role | tag |
|---|---|---|---|
cornerstone |
Cornerstone | the hazard reader | what NYC's ground remembers |
keystone |
Keystone | the asset register | what's exposed |
touchstone |
Touchstone | the present-tense witness | what's happening now |
lodestone |
Lodestone | the future-pointer | what's coming |
capstone |
Capstone | the writer | what we say, with citations |
Each <StoneRegion> has:
- Header: Stone name (IBM Plex Serif 26px italic for the name, sans for the role) Β· role tagline Β·
<StoneTally>chip showingN/M cards firedΒ· provenance toggle button - Provenance trace: smart-default expansion (see below). Renders specialist tree with status pips.
- Card grid: 12-column grid, each card spans 4 cols by default (3 per row); register/timeseries/raster cards may span 6.
Card data schema
type Card = {
stone: "cornerstone" | "keystone" | "touchstone" | "lodestone" | "capstone";
tier: "empirical" | "modeled" | "proxy" | "synthetic";
variant: CardVariant; // see below
source: string; // short label, e.g. "FEMA"
agency: string; // long form, e.g. "FEMA preliminary FIRM, panel 36047C..."
vintage: string; // e.g. "2024-Q3" or "2007βpresent"
title: string; // card title
// variant-specific fields:
headline?: string; subhead?: string;
columns?: string[]; rows?: (string | number)[][];
scalars?: { label: string; value: string; unit?: string }[];
spark?: number[]; histogram?: number[];
timeseries?: { hours: number[]; values: number[]; threshold?: number };
forecast?: { years: number[]; p10: number[]; p50: number[]; p90: number[] };
raster?: "stormwater" | "fema-ae" | "hwm" | "floodnet-density" | ...;
register?: { tag: string; label: string; sourceId: string; detail: string }[];
comparison?: { left: ScalarSet; right: ScalarSet; delta: string };
meta?: Record<string, string>;
// citation fan-out:
cites?: { id: string; label: string; href?: string }[];
// map link:
mapLayer?: "fema-ae" | "hwm" | "floodnet" | "nycha" | "address" | ...;
};
See findings.jsx lines 12β230 for the canonical CARDS table populated for the Red Hook query.
Card grammar (12 variants)
Every card uses the same chrome β title row (sans 14/600), source Β· agency Β· vintage row (mono 11/tertiary), a body block, and a footer with the tier badge (3-letter caps mono) and a "cite" button. The body block is one of:
| variant | shape | use |
|---|---|---|
headline |
one big number/label, scenario-tagged subhead | single-fact cards: "Zone AE" |
tabular |
small NΓ3 table, mono | observation lists: HWM marks |
scalars |
2β3 labeled scalars in a row | "1.2 m Β· 0.18 mi Β· 2012" |
spark |
60Γ24 inline sparkline, no axes | trend at a glance |
histogram |
8β12 bar histogram, mono labels | distributions |
timeseries |
240Γ84 line chart with threshold rule | hourly water level, 311 calls |
forecast |
240Γ88 fan chart (p10/p50/p90) | 2050/2080 SLR, surge |
raster |
240Γ120 stylized raster thumbnail | FEMA AE polygon, stormwater extent, HWM contour |
raster-pred |
same shape with dashed top-rule (synthetic tier) | TerraMind 2050 prediction |
register |
3-col dense list (tag Β· label Β· sourceId), detail in title= tooltip |
NYCHA buildings, schools |
comparison |
side-by-side scalar columns with delta | FEMA-AE vs Prithvi-2050 |
meta |
definition list of run metadata | model id, prompt hash, latency |
Synthetic tier cards (TerraMind predictions, prior-only Lodestone outputs) get a dashed top-rule (1px dashed --tier-synthetic-line) to telegraph "no observed data here." Comparison cards always render synthetic.
The <CardGrammarReference> region renders one stub per variant in findings.jsx lines 529β581 β a visual catalog the design team uses to spot-check fidelity. Keep it, gate it on showGrammar prop (default true in dev, false in prod).
Tier system
Four epistemic tiers, encoded redundantly:
| tier | color | glyph | badge | meaning |
|---|---|---|---|---|
empirical |
#0B5394 (8.59:1) |
filled square | EMP | observed, ground-truth |
modeled |
#2A6FA8 (5.41:1) |
open square | MOD | computed from observations |
proxy |
#6B6B6B (5.74:1) |
dotted ring | PRX | indirect signal, e.g. 311 calls |
synthetic |
#2A6FA8 + dashed |
hatched square | SYN | model prior, no observation |
Glyphs are inline SVG, 12Γ12, black-stroke. See glyphs.jsx for the four shapes.
Accessibility: tier is always encoded by color + glyph + label, never color alone. Modeled and synthetic share a hue; the dashed top-rule and glyph carry the difference.
Provenance trace
Every Stone has a tree of specialists ("CORN-001: pull FEMA NFHL β CORN-002: parse panel index β ..."). Each specialist has a status: ok / warn / error / silent.
Smart-default rule (provenanceMode = "smart"):
- All-
okStone β collapsed by default, single-line summary "12/12 specialists fired clean" - Any
warnorerrorβ expanded, full tree visible - All
silentβ collapsed, single-line "no firings (section omitted from briefing)"
provenanceMode = "expanded" forces all expanded; "collapsed" forces all collapsed (warn/error still get a count chip).
The trace UI: indented tree, mono ids, status pip (β/β²/β /β) in tier-color or warn/error colors. Specialist names italic-serif. Hovering a specialist row dims the rest. See trace.jsx for the leaf and stones-trace.jsx for the per-Stone composition.
Run-health strip
Single row above the Stones, full-width. Shows:
- Total specialists fired / total specialists registered (e.g.
83/100) - Per-tier breakdown as four chips:
EMP 41 Β· MOD 28 Β· PRX 12 Β· SYN 2 - Total runtime (e.g.
3.4s) - Cache-hit ratio (e.g.
92%)
Mono throughout. Background --paper-deep, 1px ink-soft top + bottom rule.
Hover linking
Every card with a mapLayer is hoverable. On hover (or focus), the card's key becomes the page-level linkedKey. The briefing's map frame reads linkedKey and:
- Adds
is-link-{layer}class to its root, which lights up that layer (seemap.jsxfor the CSS rules) - Renders a small label badge bottom-right: "linked: {layer}"
The same applies in reverse: hovering a layer in the map sets linkedKey to the corresponding card key, which gets a 2px accent outline.
In Svelte: lift linkedKey to +page.svelte as let linkedKey = $state(null), pass it down both branches, and have the cards / layers update it on pointerenter / focus / pointerleave / blur.
Density
density: "compact" | "comfortable" (default comfortable).
- Comfortable: 16px card padding, 14px line-height multiplier 1.4
- Compact: 10px card padding, 12px line-height multiplier 1.25, register-card row height 18px (vs 24px)
Pass through to all card bodies; only register/tabular/meta visibly change.
Interactions & Behavior
- Card hover: 200ms
background-colortransition to--paper-deep, 2px accent outline if linked - Cite button: opens a small popover with the full citation list (
cites[]), each row a link to the source PDF/page - Provenance toggle: button with
aria-expanded, animates the tree open/closed viadetails/summaryor scopedmax-heighttransition (β€200ms) - Map layer hover: sets
linkedKey, 100ms layer fill opacity transition - Reduced motion: all transitions become 0.01ms via the global rule in
tokens.css. Don't add motion on top. - Keyboard: every card is
tabindex=0witharia-label="{tier} card Β· {title} Β· {source}". Cite button is real<button>. Provenance toggle is real<button>. - Loading: cards show a 1px ink-soft skeleton (no spinner); replace with content when the specialist returns
- Error / silent: cards with status
errorrender a 1-line "specialist failed: {reason}" in tier-error color and stay; cards with statussilentare omitted entirely (silence over confabulation β design tenet)
State Management
Svelte 5 runes; lift to +page.svelte:
<script>
let query = $state(null);
let density = $state("comfortable");
let provenanceMode = $state("smart");
let showComparison = $state(false);
let showGrammar = $state(false); // dev-only toggle
let linkedKey = $state(null);
// Data: load once per query, hydrate from server
let cardsByStone = $derived(loadCards(query));
let runHealth = $derived(summarize(cardsByStone));
</script>
Children take props via $props(). No Svelte stores unless cross-route.
Data fetching: assume the existing codebase has a +page.server.ts load function that runs the 25 specialists and returns the Card[] payload. This handoff doesn't change the data layer; it changes the rendering.
Design Tokens
Port tokens.css verbatim. Key values:
Tier colors (all WCAG AA on white):
--tier-empirical: #0B5394(8.59:1)--tier-modeled: #2A6FA8(5.41:1)--tier-proxy: #6B6B6B(5.74:1)--tier-synthetic: #2A6FA8(pattern-differentiated)
Neutrals:
--paper: #FAFAF7(warm near-white, USGS-report register)--paper-deep: #F2F2EE--ink: #1A1A1AΒ·--ink-secondary: #4A4A4AΒ·--ink-tertiary: #6B6B6B--rule: #1A1A1AΒ·--rule-soft: #C9C9C5
Accent:
--accent: #B8620A(for text, AA)--accent-graphical: #D17C00(for shapes/lines, β₯3:1)
Type:
--font-sans: "IBM Plex Sans"(UI, body)--font-serif: "IBM Plex Serif"(Stone names, hero italic emphasis, oversized stone numerals)--font-mono: "IBM Plex Mono"(labels, source ids, badges, table cells)
All three are SIL OFL / Apache; self-host or load from Google Fonts. No system fallbacks for branding β always specify the Plex stack, fall through only on load failure.
Spacing (--s-1 through --s-9): 4 / 8 / 12 / 16 / 24 / 32 / 48 / 64 / 96 px. Use these tokens; don't hand-roll values.
Type scale (suggested, not enforced):
- 11px mono labels (
section-label) - 12β13px mono row text
- 14px sans card titles (600)
- 16px sans body
- 18px sans deck text
- 26px serif italic Stone names
- 36β52px serif headlines (italic for emphasis)
Radius: 0 throughout (this is a civic-tech-clean, USGS-report register; no rounded corners except 1px on focus rings).
Shadows: none. Differentiation by 1px rules and --paper-deep fills only.
Assets
- Fonts: IBM Plex Sans / Serif / Mono. Self-host woff2 or Google Fonts.
- Wordmark: text + accent bar
βprefix, no logo file - Tier glyphs: inline SVG, see
glyphs.jsx - Map raster thumbnails: hand-drawn SVG approximations using each layer's conventional palette. See
findings.jsxβRasterThumb. Replace with real raster previews if/when MapLibre tile snapshots are wired up. - Real map: the production map should use MapLibre GL with a custom
style.jsonthat respects the tier palette. Style fragments are sketched inshell.jsxcomments. - No icon library. No Lucide, no Heroicons. SVG glyphs for tiers, mono characters (β, β, β) for chrome.
- No emoji.
Files in this bundle
design_handoff_riprap_findings/
βββ CLAUDE_CODE_PROMPT.md β paste this into Claude Code first
βββ README.md β you are here
βββ design_files/
βββ Riprap Stone-Grouped UI v0.4.4.html β main prototype, open in browser
βββ Riprap Landing.html
βββ Riprap Landing Variants.html
βββ tokens.css β port verbatim
βββ styles.css β reference only
βββ findings.jsx β Findings region (this handoff's centerpiece)
βββ briefing.jsx β long-form prose
βββ evidence.jsx β evidence card stack used in briefing
βββ map.jsx β mini-map with link highlighting
βββ trace.jsx
βββ stones-trace.jsx
βββ stone-evidence.jsx
βββ shell.jsx β header, footer, cold-start
βββ glyphs.jsx β four tier glyphs
βββ tweaks-panel.jsx β prototype tooling, ignore
βββ design-canvas.jsx β prototype tooling, ignore
βββ landing-variants.css β marketing-page exploration, ignore unless asked
βββ landing-variants.jsx β marketing-page exploration, ignore unless asked
Scope
In scope for this handoff:
- The Findings region (5 Stones, 12 card variants, run-health strip, smart provenance, hover linking, card grammar reference)
- Briefing prose and map, to the extent they connect to Findings via
linkedKey - The marketing landing page (see Β§"Landing page" below)
- v0.4.5 deltas in
V0.4.5_SPEC.md
Out of scope: the methodology PDF, the export-PDF flow.
Landing page
The landing page is the public-facing entry point at / (separate from the app's cold-start state at /app). Two design files in design_files/:
Riprap Landing.htmlβ final shipping landing pageRiprap Landing Variants.htmlβ three exploratory variants on a design canvas (kept for reference; do not port)
Landing structure (port Riprap Landing.html)
Four-section vertical scroll, max-width 1200px, paper background:
Hero
- Wordmark top-left (with
βaccent prefix) - Headline (52px serif, italic emphasis on any place, line-broken into two lines)
- Deck (18px sans, max 70ch, ink-secondary)
- Big query box: "Try:" label + cycling example queries on a fixed dotted-underline rail (rail width pinned, ellipsis fallback for overflow)
- Submit button (ink fill, paper text, mono caps)
- Wordmark top-left (with
"What you'll get back" preview β 3-pane grid (1.4fr / 1fr / 1fr), bottoms equalized:
- Excerpt pane: serif briefing snippet with inline
[N]citation pins; compact source list at bottom (tier-coded) - Evidence cards pane: 2x2 grid of compact evidence cards, each with tier-coded left rule (2px), claim, source
- Map pane: mini SVG map (240x200, 8px paper-grain grid texture) with FEMA-AE polygon fill, HWM contour, FloodNet sensor pin, 311 cluster, address pin, and a compact tier-legend overlay bottom-edge
- Excerpt pane: serif briefing snippet with inline
Five Stones strip β explanation grid: 5 columns (
repeat(5, 1fr)), one cell per Stone:- Oversized italic-serif numerals
01..05top-right of each cell (in--rule-soft, decorative) - Stone name (serif 22px / 500)
- Role tagline (sans 13px / ink-secondary)
- Italic-serif tag ("what NYC's ground remembers" etc.)
- Dashed rule + mono source list at bottom
- Oversized italic-serif numerals
Footer β earns its keep:
- Tier legend (4 swatches: empirical / modeled / proxy / synthetic)
- Build line (mono, ink-tertiary)
Landing β what's settled (don't redesign)
- The hero dropped the redundant "Riprap" eyebrow; the wordmark already says it. Headline carries the lede.
- Cycling examples ride a single fixed dotted underline (no jump on length change). Pin the rail width; ellipsis-truncate overflow.
- Pane heights are equalized via a flex-1 spacer in the cards pane and
flex: 1on the map pane. - Map texture is a subtle 8px paper-grain grid behind the colored layers β gives it a map register, not a diagram register.
- Stones strip uses oversized italic numerals as a typographic register (decorative, in soft-rule color).
- Italic serif is intentional: hero emphasis + Stone tags + Stone numerals. Reads as a deliberate third voice.
Landing β em-dashes
All user-facing em-dashes have been purged from Riprap Landing.html, landing-variants.css, and landing-variants.jsx. Replacement convention: , (comma-space) for em-dashes used as parenthetical breaks. Maintain this convention when porting copy. Em-dashes in source-code comments (non-rendered) are fine.
Landing β out of scope variants
Riprap Landing Variants.html contains three earlier explorations on a design canvas: v1 (minimal pushed harder), v2 (example gallery), v3 (methodology-forward). Kept for reference only; do not port. The shipping landing page is Riprap Landing.html (a refinement of v1).
Open questions for the design team
These are deliberately not resolved in the prototype; raise them with the designer before locking implementation:
- Should
registercards paginate when N > 20, or stay scrollable in a fixed-height card? - The
comparisoncard is currently always synthetic (FEMA-AE vs Prithvi-2050). Will there be empirical-vs-empirical comparisons (e.g. FEMA-AE vs HWM observed)? - Run-health cache-hit ratio β is this surfaced to end users, or is it dev-mode only?
- Provenance trace expansion: should expanded state survive across query changes, or reset?