|
3 | 3 | ## Overview |
4 | 4 | Sopra Steria CASSA landing page with interactive 3D police knowledge graph ("Operation Hydra"). All UI text is **German**. Domain: law enforcement, legal references (StPO, StGB, BtMG, GwG, BSIG, NIS2, DSGVO). |
5 | 5 |
|
| 6 | +Deployed at: `https://<user>.github.io/cassa/` (base path `/cassa/`) |
| 7 | + |
6 | 8 | ## Tech Stack |
7 | | -- **React 19** + **TypeScript 5.7** + **Vite 7** (SWC plugin) |
| 9 | +- **React 19** + **TypeScript 5.7** + **Vite 7** (SWC plugin, `@vitejs/plugin-react-swc`) |
8 | 10 | - **Tailwind CSS v4** (`@tailwindcss/vite`, oklch color space, `@theme inline`) |
9 | | -- **shadcn/ui** ("new-york" style, `@/components/ui/*`) with Radix UI primitives |
10 | | -- **react-force-graph-3d** + **three.js** + **three-spritetext** for 3D graph |
11 | | -- **framer-motion** for animations |
12 | | -- **@github/spark** platform — do not remove `sparkPlugin()` or icon proxy from `vite.config.ts` |
| 11 | +- **shadcn/ui** ("new-york" style, `@/components/ui/*`) with 14 Radix UI primitives |
| 12 | +- **react-force-graph-3d** ^1.29 + **three.js** ^0.175 + **three-spritetext** ^1.10 for 3D graph |
| 13 | +- **framer-motion** ^12.6 for animations |
| 14 | +- **d3** ^7.9 (force layout engine used by react-force-graph) |
| 15 | +- **@github/spark** platform — **never** remove `sparkPlugin()` or `createIconImportProxy()` from `vite.config.ts` |
| 16 | +- **react-error-boundary** ^6.0 wrapping `<App />` in `main.tsx` |
13 | 17 |
|
14 | 18 | ## Build & Dev |
15 | 19 | ```sh |
16 | | -npm run dev # Vite dev server (port 5000) |
| 20 | +npm run dev # Vite dev server (default port 5000) |
17 | 21 | npm run build # tsc -b --noCheck && vite build |
18 | 22 | npm run lint # eslint . |
19 | 23 | npm run preview # Preview production build |
| 24 | +npm run kill # fuser -k 5000/tcp |
| 25 | +npm run optimize # vite optimize (pre-bundle deps) |
| 26 | +``` |
| 27 | + |
| 28 | +## CI/CD |
| 29 | +- **GitHub Actions**: `.github/workflows/deploy.yml` — Node 22, `npm ci` → `npm run build` → deploy `dist/` to GitHub Pages |
| 30 | +- **Dependabot**: `.github/dependabot.yml` — npm daily, devcontainers weekly |
| 31 | + |
| 32 | +## Project Structure |
| 33 | + |
20 | 34 | ``` |
| 35 | +├── .github/ |
| 36 | +│ ├── copilot-instructions.md # This file |
| 37 | +│ ├── dependabot.yml |
| 38 | +│ └── workflows/deploy.yml # GitHub Pages deploy |
| 39 | +├── input/ # Graph source data (JSON, CSV, Cypher) |
| 40 | +│ ├── hydra_graph_data (1).json # Enriched: 88 nodes, 113 rels, STIX/XPolizei/standards |
| 41 | +│ ├── hydra_graph_data.json # Original graph data |
| 42 | +│ ├── hydra_neo4j_import*.cypher # Neo4j import scripts |
| 43 | +│ ├── hydra_nodes*.csv # Node CSVs (original + enriched) |
| 44 | +│ └── hydra_relationships*.csv # Relationship CSVs (original + enriched) |
| 45 | +├── public/audio/ # Static audio assets |
| 46 | +│ ├── hydra_briefing.mp3 # ElevenLabs narration (Otto voice, warm German male, current) |
| 47 | +│ ├── hydra_erklaerung.mp3 # Legacy narration (unused) |
| 48 | +│ └── hydra_narration.mp3 # Legacy narration (unused) |
| 49 | +├── scripts/ # Python helper scripts (see below) |
| 50 | +├── src/ |
| 51 | +│ ├── App.tsx # Main SPA (~1271 lines), all scroll sections + narration |
| 52 | +│ ├── ErrorFallback.tsx # Error boundary fallback UI |
| 53 | +│ ├── main.tsx # createRoot, ErrorBoundary, CSS imports |
| 54 | +│ ├── main.css # Tailwind v4 entry, @theme inline, design tokens |
| 55 | +│ ├── index.css # Custom oklch colors, hero-pattern, network-pattern |
| 56 | +│ ├── vite-end.d.ts # Vite + Spark runtime type declarations |
| 57 | +│ ├── components/ |
| 58 | +│ │ ├── PoliceKnowledgeGraph3D.tsx # 3D force-graph (~1002 lines) |
| 59 | +│ │ └── ui/ # 45 shadcn/ui components (accordion → tooltip) |
| 60 | +│ ├── hooks/ |
| 61 | +│ │ └── use-mobile.ts # useIsMobile() — breakpoint 768px |
| 62 | +│ ├── lib/ |
| 63 | +│ │ └── utils.ts # cn() = twMerge(clsx(...)) |
| 64 | +│ └── styles/ |
| 65 | +│ └── theme.css # Radix color scales, Spark theme vars |
| 66 | +├── check-console.mjs # Playwright: console error/warning logger |
| 67 | +├── test-graph.mjs # Playwright: headless screenshot + pixel analysis |
| 68 | +├── components.json # shadcn/ui config |
| 69 | +├── runtime.config.json # Spark app ID |
| 70 | +├── spark.meta.json # Spark metadata |
| 71 | +├── tailwind.config.js # Radix color scale integration, spacing |
| 72 | +├── theme.json # Empty (theming via CSS) |
| 73 | +├── vite.config.ts # base: '/cassa/', plugins, path alias |
| 74 | +└── tsconfig.json # ES2020, strictNullChecks, bundler resolution |
| 75 | +``` |
| 76 | + |
| 77 | +## Scripts & Tools |
| 78 | + |
| 79 | +### Python Scripts (`scripts/`) |
| 80 | +All scripts use Python 3 and `urllib` (no external deps unless noted). API key from `.env` (`ELEVENLABS_API_KEY`). |
| 81 | + |
| 82 | +| Script | Purpose | Usage | |
| 83 | +|--------|---------|-------| |
| 84 | +| `find_voices.py` | Query ElevenLabs API for German voices | `python3 scripts/find_voices.py` | |
| 85 | +| `generate_narration.py` | Generate narration MP3 with ElevenLabs "Lucius" voice | `python3 scripts/generate_narration.py` | |
| 86 | +| `generate_hydra_voice.py` | Extended TTS generation with `--login` flag | `python3 scripts/generate_hydra_voice.py` | |
| 87 | +| `generate_graph_code.py` | Convert enriched JSON → TypeScript `buildCaseData()` code | `python3 scripts/generate_graph_code.py` | |
| 88 | + |
| 89 | +### Playwright Test Scripts (root) |
| 90 | +Require `@playwright/test` + Chromium: `npx playwright install chromium` |
| 91 | + |
| 92 | +| Script | Purpose | Usage | |
| 93 | +|--------|---------|-------| |
| 94 | +| `check-console.mjs` | Launch browser, log console errors/warnings/network failures | `node check-console.mjs` | |
| 95 | +| `test-graph.mjs` | Headless screenshot of 3D graph canvas, pixel analysis | `node test-graph.mjs` | |
21 | 96 |
|
22 | 97 | ## Architecture |
23 | | -- **Single-page app** — no router. One `App.tsx` (~762 lines) with scroll-based sections |
24 | | -- **`PoliceKnowledgeGraph3D.tsx`** — main feature component (~950 lines): self-contained data, types, 3D rendering, detail panel |
25 | | -- **State**: React hooks only (`useState`, `useEffect`, `useMemo`, `useCallback`) — no external state library |
26 | | -- **Path alias**: `@/*` → `./src/*` |
27 | | -- **Utility**: `cn()` in `src/lib/utils.ts` (clsx + tailwind-merge) |
| 98 | + |
| 99 | +### Single-Page App |
| 100 | +- **No router** — one `App.tsx` with scroll-based sections, using `scrollToSection(id)` helper |
| 101 | +- **Sections** (in order): Hero → Architecture (4-Layer Ontology) → Features → Knowledge Graph (3D) → Scenarios → Standards & Compliance → Best Practices → Cross-Border Cooperation → CTA |
| 102 | +- **Narration**: `HTMLAudioElement` playing `public/audio/hydra_briefing.mp3` (ElevenLabs "Lucius" German male voice) |
| 103 | + |
| 104 | +### State Management |
| 105 | +React hooks only (`useState`, `useEffect`, `useMemo`, `useCallback`, `useRef`) — no external state library. |
| 106 | + |
| 107 | +Key state in `App.tsx`: |
| 108 | +- `selectedLayer: number | null` — 4-layer architecture highlighting |
| 109 | +- `activeScenario: number` — scenario tab selection |
| 110 | +- `showIntroGuide: boolean` — intro overlay |
| 111 | +- `isPlayingNarration: boolean` — audio toggle |
| 112 | + |
| 113 | +### Path Alias |
| 114 | +`@/*` → `./src/*` (configured in both `vite.config.ts` and `tsconfig.json`) |
| 115 | + |
| 116 | +### CSS Import Order |
| 117 | +`main.tsx` imports: `main.css` → `theme.css` → `index.css` |
28 | 118 |
|
29 | 119 | ## Code Style |
30 | 120 | - Functional components with named exports for features, default export for `App` |
31 | 121 | - Section dividers: `// ────────────` with section labels |
32 | 122 | - Types at file top: `type NodeType = 'suspect' | 'victim' | ...` union pattern |
33 | 123 | - Node data uses `Record<string, string>` for flexible `details` and optional `timestamp`, `score` fields |
34 | | -- Icons from `lucide-react` — no other icon library in components |
| 124 | +- Icons from `lucide-react` — primary icon library in components |
| 125 | +- Also available: `@heroicons/react`, `@phosphor-icons/react` (Spark icon proxy) |
| 126 | + |
| 127 | +## Graph Component Conventions (`PoliceKnowledgeGraph3D.tsx`) |
| 128 | + |
| 129 | +### 18 Node Types |
| 130 | +`suspect`, `victim`, `witness`, `case`, `evidence`, `location`, `communication`, `law`, `organization`, `account`, `vehicle`, `weapon`, `drug`, `digital`, `regulation`, `process`, `sop`, `anzeige` |
35 | 131 |
|
36 | | -## Graph Component Conventions |
37 | | -- **18 node types** (see `NodeType`): suspect, victim, witness, case, evidence, location, communication, law, organization, account, vehicle, weapon, drug, digital, regulation, process, sop, anzeige |
38 | | -- Each type needs entries in `NODE_COLORS` and `NODE_LABELS` (emoji + German label) |
39 | | -- Node data built in `buildCaseData()` — returns `{ nodes, links }` |
40 | | -- Links use `{ source, target, type, description? }` — source/target are node IDs |
| 132 | +### Data Architecture |
| 133 | +- Each type needs entries in `NODE_COLORS` (oklch) and `NODE_LABELS` (emoji + German label) |
| 134 | +- `SOURCE_REGISTRY` maps source keys to URLs |
| 135 | +- Node data built in `buildCaseData()` → returns `{ nodes: GraphNode[], links: GraphLink[] }` |
| 136 | +- **88 nodes** and **113 relationships** in current dataset |
| 137 | +- Links: `{ source, target, type, description? }` — source/target are string node IDs |
41 | 138 | - Detail panel groups relationships by connected node type: law → regulation → process → sop → anzeige → other |
42 | | -- Demo data uses German legal formats: Aktenzeichen, Asservat-Nr, paragraph references (§100a StPO) |
43 | | -- Addresses are redacted with `XXX` — never use real addresses |
| 139 | + |
| 140 | +### Graph Data Enrichment |
| 141 | +Every node in the enriched JSON (`input/hydra_graph_data (1).json`) carries: |
| 142 | +- `sources[]` — keys referencing `metadata.sources` (OFAC, TRM, DOJ, ELLIPTIC, etc.) |
| 143 | +- `stix_type` — STIX 2.1 SDO mapping (e.g., `threat-actor`, `infrastructure`, `identity`) |
| 144 | +- `xpolizei_type` — XPolizei 2.0 type (optional) |
| 145 | +- `applicable_standards[]` — ISO 27037, ISO 27042, NIST 800-86, EO 13694, etc. |
| 146 | + |
| 147 | +### German Legal Formats |
| 148 | +- Aktenzeichen: `Az. XXXX Js XXXXX/22` |
| 149 | +- Asservat-Nr: `ASS-2022-XXXX-XXXX` |
| 150 | +- Paragraph references: `§100a StPO`, `§261 StGB` |
| 151 | +- Addresses redacted with `XXX` — never use real addresses |
44 | 152 |
|
45 | 153 | ## CSS & Theming |
46 | | -- Tailwind v4 with `@theme inline` in `src/main.css` — defines all design tokens |
47 | | -- Custom oklch colors in `src/index.css` (Deep Navy primary, Signal Red accent) |
48 | | -- Dark mode via `.dark` selector and `@custom-variant dark` |
49 | | -- Two fonts: **Space Grotesk** (headings), **Inter** (body) |
50 | | -- Radix color scales imported in `src/styles/theme.css` |
| 154 | +- **Tailwind v4** with `@theme inline` in `src/main.css` — all design tokens as CSS custom properties |
| 155 | +- **oklch color space** throughout: Deep Navy primary (`oklch(0.25 0.05 250)`), Signal Red accent (`oklch(0.55 0.22 25)`) |
| 156 | +- **Dark mode** via `.dark` selector and `@custom-variant dark (&:is(.dark *))` |
| 157 | +- **Fonts**: **Space Grotesk** (headings), **Inter** (body) — loaded via Google Fonts in `index.html` |
| 158 | +- **Radix color scales**: All scales imported in `src/styles/theme.css` (262 lines) |
| 159 | +- **Spark theme vars** in `#spark-app` selector — spacing, radius, neutral/accent mapping but mostly dark/light bg colors |
| 160 | +- `tailwind.config.js` (147 lines): extends defaultTheme with Radix CSS variable mappings |
| 161 | + |
| 162 | +## Environment Variables |
| 163 | + |
| 164 | +| Variable | File | Purpose | |
| 165 | +|----------|------|---------| |
| 166 | +| `ELEVENLABS_API_KEY` | `.env` | ElevenLabs TTS API key (for `scripts/*.py`) | |
| 167 | + |
| 168 | +`.env` is in `.gitignore` — never commit API keys. |
| 169 | + |
| 170 | +## Narration / Audio |
| 171 | +- **Always use ElevenLabs** for text-to-speech generation — **never** use the Web Speech API (`SpeechSynthesis`) |
| 172 | +- Voice: **male, warm friendly tone, native German** (currently ElevenLabs voice ID `FTNCalFNG5bRnkkaP5Ug` "Otto") |
| 173 | +- Model: `eleven_multilingual_v2` |
| 174 | +- API key is read from `.env` (`ELEVENLABS_API_KEY`) — never hardcode it |
| 175 | +- Generate script: `python3 scripts/generate_narration.py` → outputs `public/audio/hydra_briefing.mp3` |
| 176 | +- The **player in `App.tsx` always uses the stored MP3 file** (`HTMLAudioElement` with `public/audio/hydra_briefing.mp3`) — never inline `SpeechSynthesisUtterance` |
| 177 | +- When the narration text changes, **regenerate the MP3** by running the generate script before committing |
51 | 178 |
|
52 | 179 | ## Security |
53 | 180 | - Report vulnerabilities via `opensource-security@github.com`, not public issues |
54 | 181 | - All case data is fictional — keep addresses redacted, use fake Aktenzeichen |
55 | 182 | - DSGVO/NIS2 references must be legally accurate when added |
| 183 | +- Never commit `.env` or API keys |
| 184 | +- ElevenLabs API key is only used by Python scripts at build time, not at runtime |
| 185 | + |
| 186 | +## Key Dependencies (34 production, 10 dev) |
| 187 | + |
| 188 | +### Core |
| 189 | +`react` ^19, `react-dom` ^19, `react-error-boundary` ^6, `framer-motion` ^12.6 |
| 190 | + |
| 191 | +### 3D Visualization |
| 192 | +`react-force-graph-3d` ^1.29, `three` ^0.175, `three-spritetext` ^1.10, `d3` ^7.9 |
| 193 | + |
| 194 | +### UI Components |
| 195 | +14 `@radix-ui/react-*` packages, `class-variance-authority`, `clsx`, `tailwind-merge`, `cmdk`, `vaul`, `sonner`, `embla-carousel-react`, `react-day-picker`, `react-resizable-panels`, `input-otp` |
| 196 | + |
| 197 | +### Icons |
| 198 | +`lucide-react` ^0.484, `@heroicons/react` ^2.2, `@phosphor-icons/react` ^2.1 |
| 199 | + |
| 200 | +### Data & Forms |
| 201 | +`@tanstack/react-query` ^5.83, `react-hook-form` ^7.54, `@hookform/resolvers` ^4.1, `zod` ^3.25, `recharts` ^2.15 |
| 202 | + |
| 203 | +### GitHub/Spark |
| 204 | +`@github/spark` >=0.43.1, `octokit` ^4.1, `@octokit/core` ^6.1 |
| 205 | + |
| 206 | +### Utilities |
| 207 | +`date-fns` ^3.6, `marked` ^15.0, `next-themes` ^0.4, `uuid` ^11.1 |
| 208 | + |
| 209 | +### Dev |
| 210 | +`vite` ^7.2, `tailwindcss` ^4.1, `typescript` ~5.7, `eslint` ^9.28, `@playwright/test` ^1.58 |
| 211 | + |
| 212 | +## TypeScript Configuration |
| 213 | +- Target: `ES2020`, Module: `ESNext`, Resolution: `bundler` |
| 214 | +- `strictNullChecks: true` (but not full `strict` mode) |
| 215 | +- `noEmit: true`, `skipLibCheck: true`, `jsx: react-jsx` |
| 216 | +- `isolatedModules: true`, `noFallthroughCasesInSwitch: true` |
| 217 | + |
| 218 | +## Vite Configuration |
| 219 | +- **Base**: `/cassa/` (subpath deployment) |
| 220 | +- **Plugins**: react-swc, tailwindcss, Spark icon proxy, Spark plugin |
| 221 | +- **Alias**: `@` → `src/` |
| 222 | +- Known build warnings: CSS `@media` Tailwind v4 artifacts (harmless), bundle >500KB (expected for three.js) |
0 commit comments