diff --git a/.gitignore b/.gitignore
index c63d997..f66e33a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,7 @@ dist/
node_modules/
.DS_Store
*.local
+.mise.local.toml
+.env
+.env.local
+.env.*.local
diff --git a/mise.toml b/.mise.toml
similarity index 100%
rename from mise.toml
rename to .mise.toml
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..d0679d8
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,34 @@
+# impstr — Imposter Party Game
+
+Static PWA party game (like Werewolf/Mafia). No backend, no accounts — runs entirely in the browser.
+
+## Stack
+
+- **Frontend:** React 19, Vite, Tailwind CSS 4, TanStack Router, Zustand
+- **Linting:** Biome (tabs, 80 chars, double quotes)
+
+## Project Structure
+
+```
+src/
+├── features/ ← game logic, UI components
+├── routes/ ← TanStack Router file-based routes
+└── shared/ ← shared components, utilities
+```
+
+## Local Development
+
+```bash
+bun install
+bun run dev
+```
+
+## Deployment
+
+```bash
+./deploy.sh
+```
+
+Deploys to Uberspace (`serve.uber.space`):
+- Static files → `/var/www/virtual/serve/html/impstr/`
+- No backend, no database
diff --git a/biome.json b/biome.json
index 0d5cc8e..c01b2dc 100644
--- a/biome.json
+++ b/biome.json
@@ -5,7 +5,7 @@
},
"formatter": {
"indentStyle": "tab",
- "lineWidth": 100
+ "lineWidth": 80
},
"linter": {
"enabled": true,
diff --git a/src/features/round/flip-card.tsx b/src/features/round/flip-card.tsx
index b25570f..3a0d732 100644
--- a/src/features/round/flip-card.tsx
+++ b/src/features/round/flip-card.tsx
@@ -24,7 +24,11 @@ export function FlipCard({ word, flipped, onFlip }: FlipCardProps) {
return (
{
if (e.key === "Enter" || e.key === " ") onFlip();
diff --git a/src/features/setup/setup-screen.test.tsx b/src/features/setup/setup-screen.test.tsx
index 77e2641..c95913c 100644
--- a/src/features/setup/setup-screen.test.tsx
+++ b/src/features/setup/setup-screen.test.tsx
@@ -24,7 +24,9 @@ describe("SetupScreen", () => {
it("renders the setup screen", () => {
render(
);
expect(screen.getByText("Imposter")).toBeInTheDocument();
- expect(screen.getByPlaceholderText("Spieler hinzufügen…")).toBeInTheDocument();
+ expect(
+ screen.getByPlaceholderText("Spieler hinzufügen…"),
+ ).toBeInTheDocument();
});
it("adds a player via button click", () => {
diff --git a/src/features/setup/setup-screen.tsx b/src/features/setup/setup-screen.tsx
index 628d72b..efd81fc 100644
--- a/src/features/setup/setup-screen.tsx
+++ b/src/features/setup/setup-screen.tsx
@@ -39,7 +39,9 @@ export function SetupScreen() {
Partyspiel
Imposter
- Findet heraus, wer das geheime Wort nicht kennt!
+
+ Findet heraus, wer das geheime Wort nicht kennt!
+
@@ -83,14 +85,21 @@ export function SetupScreen() {
Alle löschen
)}
-
{!canStart && players.length > 0 && (
- Mindestens {MIN_PLAYERS} Spieler benötigt
+
+ Mindestens {MIN_PLAYERS} Spieler benötigt
+
)}
);
diff --git a/src/shared/components/ui/badge.tsx b/src/shared/components/ui/badge.tsx
index 07388f1..89c6f92 100644
--- a/src/shared/components/ui/badge.tsx
+++ b/src/shared/components/ui/badge.tsx
@@ -1,7 +1,10 @@
import { cn } from "@/shared/lib/utils";
import type { HTMLAttributes } from "react";
-export function Badge({ className, ...props }: HTMLAttributes) {
+export function Badge({
+ className,
+ ...props
+}: HTMLAttributes) {
return (
>(
- ({ className, ...props }, ref) => {
- return (
-
- );
- },
-);
+export const Input = forwardRef<
+ HTMLInputElement,
+ InputHTMLAttributes
+>(({ className, ...props }, ref) => {
+ return (
+
+ );
+});
Input.displayName = "Input";
diff --git a/src/shared/stores/game-store.test.ts b/src/shared/stores/game-store.test.ts
index d264b0a..f194f3c 100644
--- a/src/shared/stores/game-store.test.ts
+++ b/src/shared/stores/game-store.test.ts
@@ -27,7 +27,9 @@ describe("game-store", () => {
it("adds a player and persists to localStorage", () => {
getState().addPlayer("Alice");
expect(getState().players).toEqual(["Alice"]);
- expect(JSON.parse(localStorage.getItem("impstr-players") ?? "[]")).toEqual(["Alice"]);
+ expect(
+ JSON.parse(localStorage.getItem("impstr-players") ?? "[]"),
+ ).toEqual(["Alice"]);
});
it("trims whitespace", () => {
@@ -58,7 +60,9 @@ describe("game-store", () => {
getState().addPlayer("Bob");
getState().clearPlayers();
expect(getState().players).toEqual([]);
- expect(JSON.parse(localStorage.getItem("impstr-players") ?? "[]")).toEqual([]);
+ expect(
+ JSON.parse(localStorage.getItem("impstr-players") ?? "[]"),
+ ).toEqual([]);
});
});
@@ -76,7 +80,10 @@ describe("game-store", () => {
});
it("filters out non-string entries", () => {
- localStorage.setItem("impstr-players", JSON.stringify(["Alice", 42, null, "Bob"]));
+ localStorage.setItem(
+ "impstr-players",
+ JSON.stringify(["Alice", 42, null, "Bob"]),
+ );
getState().loadPlayers();
expect(getState().players).toEqual(["Alice", "Bob"]);
});
diff --git a/src/shared/stores/game-store.ts b/src/shared/stores/game-store.ts
index 2cb05ed..3d5cf9f 100644
--- a/src/shared/stores/game-store.ts
+++ b/src/shared/stores/game-store.ts
@@ -93,7 +93,9 @@ export const useGameStore = create((set, get) => ({
if (raw) {
const parsed = JSON.parse(raw);
if (Array.isArray(parsed)) {
- set({ players: parsed.filter((p): p is string => typeof p === "string") });
+ set({
+ players: parsed.filter((p): p is string => typeof p === "string"),
+ });
}
}
} catch {
diff --git a/src/test-setup.ts b/src/test-setup.ts
index 7e1502d..2dc78c1 100644
--- a/src/test-setup.ts
+++ b/src/test-setup.ts
@@ -20,4 +20,7 @@ const localStorageMock: Storage = {
key: (index: number) => [...store.keys()][index] ?? null,
};
-Object.defineProperty(globalThis, "localStorage", { value: localStorageMock, writable: true });
+Object.defineProperty(globalThis, "localStorage", {
+ value: localStorageMock,
+ writable: true,
+});