setExpanded(!expanded)}>
+
+ {character.name}
+ {badge.label}
+
+
+ {v ? (
+
+
+
+
+
+ ) : (
+
Awaiting vitals...
+ )}
+
+
+
+ {t?.kills_per_hour ?? '--'}
+ kills/hr
+
+
+ {t?.kills?.toLocaleString() ?? '--'}
+ kills
+
+
+ {t?.deaths ?? '0'}
+ deaths
+
+
+ {t?.onlinetime?.replace(/^00\./, '') ?? '--'}
+ uptime
+
+
+
+ {t && (
+
+ {t.ns?.toFixed(1)}N, {t.ew?.toFixed(1)}E
+
+ )}
+
+ {expanded && (
+
+ {v?.vitae ?
Vitae: {v.vitae}%
: null}
+
+ Prismatics: {t?.prismatic_taper_count ?? '--'}
+ Total Deaths: {t?.total_deaths ?? '--'}
+
+ {c?.session && (
+
+ Session Dmg: {c.session.total_damage_given?.toLocaleString()}
+ Session Kills: {c.session.total_kills}
+
+ )}
+
+ RAM: {t?.mem_mb ? (t.mem_mb / 1048576).toFixed(0) + ' MB' : '--'}
+ CPU: {t?.cpu_pct?.toFixed(1) ?? '--'}%
+
+
+ )}
+
+ );
+});
+
+CharacterCard.displayName = 'CharacterCard';
diff --git a/frontend/src/components/CharacterGrid.tsx b/frontend/src/components/CharacterGrid.tsx
new file mode 100644
index 00000000..36d78c80
--- /dev/null
+++ b/frontend/src/components/CharacterGrid.tsx
@@ -0,0 +1,27 @@
+import React, { useMemo } from 'react';
+import { CharacterCard } from './CharacterCard';
+import type { CharacterState } from '../types';
+
+interface Props {
+ characters: Map