diff --git a/.project/designs/app/index.html b/.project/designs/app/index.html
new file mode 100644
index 00000000..83a4350f
--- /dev/null
+++ b/.project/designs/app/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+ Age of Dwarves — Design System
+
+
+
+
+
+
+
+
+
diff --git a/.project/designs/app/package.json b/.project/designs/app/package.json
new file mode 100644
index 00000000..406f4d1c
--- /dev/null
+++ b/.project/designs/app/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "@magic-civ/designs",
+ "private": true,
+ "version": "0.0.1",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0",
+ "react-router-dom": "^7.5.3",
+ "styled-components": "^6.1.18"
+ },
+ "devDependencies": {
+ "@types/react": "^19.1.2",
+ "@types/react-dom": "^19.1.2",
+ "@types/styled-components": "^5.1.34",
+ "@vitejs/plugin-react": "^4.4.1",
+ "typescript": "^5.8.3",
+ "vite": "^6.3.3"
+ }
+}
diff --git a/.project/designs/app/src/data/units.ts b/.project/designs/app/src/data/units.ts
new file mode 100644
index 00000000..761471da
--- /dev/null
+++ b/.project/designs/app/src/data/units.ts
@@ -0,0 +1,88 @@
+export type AttackType = "blade" | "pierce" | "crush" | "trample" | "siege";
+export type ArmorType = "unarmored" | "light" | "medium" | "armored" | "heavy" | "plate" | "fortified";
+
+export interface Unit {
+ id: string;
+ name: string;
+ tier: number;
+ archetype: string;
+ attackType: AttackType;
+ armor: ArmorType;
+ hp: number;
+ attack: number;
+ defense: number;
+ rangedAttack: number;
+ range: number;
+ movement: number;
+ keywords: string[];
+ race?: string;
+ unique?: boolean;
+}
+
+// Sourced from public/games/age-of-dwarves/data/units/*.json
+export const UNITS: Record = {
+ warrior: {
+ id: "warrior", name: "Warrior", tier: 1, archetype: "Light Melee",
+ attackType: "blade", armor: "light",
+ hp: 80, attack: 14, defense: 8, rangedAttack: 0, range: 0, movement: 2,
+ keywords: [],
+ },
+ berserker: {
+ id: "berserker", name: "Berserker", tier: 2, archetype: "Light Melee",
+ attackType: "blade", armor: "light",
+ hp: 90, attack: 20, defense: 6, rangedAttack: 0, range: 0, movement: 2,
+ keywords: ["rage", "no_shield"],
+ race: "dwarf", unique: true,
+ },
+ ironwarden: {
+ id: "ironwarden", name: "Ironwarden", tier: 4, archetype: "Heavy Melee",
+ attackType: "blade", armor: "heavy",
+ hp: 110, attack: 22, defense: 18, rangedAttack: 0, range: 0, movement: 2,
+ keywords: ["formation", "shield_wall"],
+ race: "dwarf", unique: true,
+ },
+ spearmen: {
+ id: "spearmen", name: "Spearmen", tier: 1, archetype: "Anti-Cavalry",
+ attackType: "pierce", armor: "medium",
+ hp: 60, attack: 8, defense: 8, rangedAttack: 0, range: 0, movement: 1,
+ keywords: ["reach"],
+ },
+ pikeman: {
+ id: "pikeman", name: "Pikeman", tier: 2, archetype: "Anti-Cavalry",
+ attackType: "pierce", armor: "armored",
+ hp: 100, attack: 10, defense: 14, rangedAttack: 0, range: 0, movement: 2,
+ keywords: ["zoc", "anti_cavalry"],
+ },
+ cavalry: {
+ id: "cavalry", name: "Cavalry", tier: 3, archetype: "Cavalry",
+ attackType: "blade", armor: "light",
+ hp: 70, attack: 16, defense: 6, rangedAttack: 0, range: 0, movement: 4,
+ keywords: ["fast", "flanking"],
+ },
+ archer: {
+ id: "archer", name: "Archer", tier: 1, archetype: "Ranged",
+ attackType: "pierce", armor: "light",
+ hp: 50, attack: 10, defense: 4, rangedAttack: 12, range: 2, movement: 2,
+ keywords: ["ranged"],
+ },
+ runesmith: {
+ id: "runesmith", name: "Runesmith", tier: 2, archetype: "Ranged",
+ attackType: "pierce", armor: "armored",
+ hp: 70, attack: 8, defense: 12, rangedAttack: 18, range: 2, movement: 1,
+ keywords: ["ranged", "heavy_armor"],
+ race: "dwarf", unique: true,
+ },
+};
+
+// From COMBAT_SYSTEM.md
+export const DAMAGE_MATRIX: Record> = {
+ blade: { unarmored: 1.25, light: 1.25, medium: 1.00, armored: 0.75, heavy: 0.75, plate: 0.50, fortified: 0.25 },
+ pierce: { unarmored: 1.00, light: 1.00, medium: 1.25, armored: 1.00, heavy: 1.00, plate: 0.75, fortified: 0.50 },
+ crush: { unarmored: 0.75, light: 0.75, medium: 1.00, armored: 1.25, heavy: 1.25, plate: 1.50, fortified: 1.75 },
+ trample: { unarmored: 2.00, light: 1.50, medium: 1.00, armored: 0.50, heavy: 0.50, plate: 0.25, fortified: 0.00 },
+ siege: { unarmored: 0.50, light: 0.50, medium: 0.75, armored: 1.00, heavy: 1.00, plate: 1.00, fortified: 2.00 },
+};
+
+export function matrixMultiplier(atk: AttackType, armor: ArmorType): number {
+ return DAMAGE_MATRIX[atk][armor];
+}
diff --git a/.project/designs/app/src/theme.ts b/.project/designs/app/src/theme.ts
new file mode 100644
index 00000000..c3db9831
--- /dev/null
+++ b/.project/designs/app/src/theme.ts
@@ -0,0 +1,56 @@
+export const t = {
+ bg: {
+ deepest: "#171219",
+ panel: "#17121e",
+ surface: "#221a14",
+ raised: "#2a2018",
+ list: "#120e1e",
+ listSel: "#3f2d0d",
+ menu: "#0e0a17",
+ btnNormal: "#1f1733",
+ btnHover: "#331a0d",
+ btnPressed: "#472f0f",
+ },
+ text: {
+ title: "#f2d973",
+ primary: "#e0d8c8",
+ secondary: "#bfb7a6",
+ muted: "#b2b2b2",
+ disabled: "#80806699",
+ btn: "#e0d199",
+ btnHover: "#ffeb80",
+ btnPressed: "#ffffb3",
+ },
+ accent: {
+ gold: "#d9a020",
+ goldBright: "#d9b33f",
+ goldPress: "#ffd14d",
+ goldRes: "#f2d133",
+ science: "#66bfff",
+ sage: "#66b866",
+ ping: "#ffd973",
+ },
+ sem: {
+ positive: "#66e666",
+ goldenAge: "#ffeb66",
+ negative: "#d95940",
+ warning: "#e69933",
+ diplomacy: "#e68c73",
+ trade: "#ccbf73",
+ },
+ border: {
+ panel: "#73591fcc",
+ focus: "#d9b340",
+ listSel: "#d9b340cc",
+ divider: "#73591f33",
+ },
+ font: {
+ heading: "'Grenze Gotisch', serif",
+ body: "'Bitter', serif",
+ mono: "monospace",
+ },
+ radius: { panel: "4px", btn: "3px", list: "2px" },
+ space: { 1: "4px", 2: "8px", 3: "12px", 4: "14px", 5: "18px", 6: "24px" },
+} as const;
+
+export type Theme = typeof t;
diff --git a/.project/designs/app/tsconfig.json b/.project/designs/app/tsconfig.json
new file mode 100644
index 00000000..efdb14c6
--- /dev/null
+++ b/.project/designs/app/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "skipLibCheck": true
+ },
+ "include": ["src"]
+}
diff --git a/.project/designs/app/vite.config.ts b/.project/designs/app/vite.config.ts
new file mode 100644
index 00000000..36307958
--- /dev/null
+++ b/.project/designs/app/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [react()],
+ server: { port: 7777 },
+});