A platform for Bosnian language puzzle games.
/client/- Frontend application (React + TypeScript + Vite)- (Future)
/api/- Backend API services - (Future)
/shared/- Shared types and utilities
Connections-style word puzzle game in Bosnian. Find groups of four words that share a common category.
- Daily puzzles with four difficulty levels
- Completion tracking via localStorage
- Archive of historical puzzles
- Puzzle submission by community members
- Node.js 20+
- A Supabase project with the
connection_puzzlesandconnection_puzzle_submissionstables (see schema below)
Create client/.env:
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
All commands run from client/:
npm run dev # Start dev server
npm run build # TypeScript compile + Vite build
npm run preview # Preview production buildRun in the Supabase SQL editor:
CREATE TABLE connection_puzzles (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
date text NOT NULL UNIQUE,
authors jsonb NOT NULL DEFAULT '[]',
categories jsonb NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
published_by uuid REFERENCES auth.users(id)
);
CREATE TABLE connection_puzzle_submissions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
submitted_at timestamptz NOT NULL DEFAULT now(),
submitted_by uuid NOT NULL REFERENCES auth.users(id),
proposed_date text NOT NULL,
authors jsonb NOT NULL DEFAULT '[]',
categories jsonb NOT NULL,
reviewed_at timestamptz,
reviewed_by uuid REFERENCES auth.users(id),
rejection_notes text
);
-- Indexes
CREATE INDEX ON connection_puzzle_submissions (submitted_by);
CREATE INDEX ON connection_puzzle_submissions (reviewed_at) WHERE reviewed_at IS NULL;
-- RLS
ALTER TABLE connection_puzzles ENABLE ROW LEVEL SECURITY;
ALTER TABLE connection_puzzle_submissions ENABLE ROW LEVEL SECURITY;
CREATE POLICY "public_read_puzzles" ON connection_puzzles FOR SELECT USING (true);
CREATE POLICY "admin_write_puzzles" ON connection_puzzles FOR ALL
USING ((auth.jwt() -> 'app_metadata' ->> 'role') = 'admin');
CREATE POLICY "auth_insert_submission" ON connection_puzzle_submissions FOR INSERT
TO authenticated WITH CHECK (submitted_by = auth.uid());
CREATE POLICY "auth_read_own_submission" ON connection_puzzle_submissions FOR SELECT
TO authenticated USING (submitted_by = auth.uid());
CREATE POLICY "admin_all_submissions" ON connection_puzzle_submissions FOR ALL
USING ((auth.jwt() -> 'app_metadata' ->> 'role') = 'admin');Puzzles are managed through the Supabase dashboard or via the /kreiraj page in the app. Each puzzle requires exactly 4 categories, one of each difficulty (1–4), with 4 words each.
Puzzle structure:
{
"date": "YYYY-MM-DD",
"authors": [{ "name": "Author Name" }],
"categories": [
{
"name": "Category Name",
"words": ["Word1", "Word2", "Word3", "Word4"],
"difficulty": 1
}
]
}Rules:
- One puzzle per day (
dateis unique) - Exactly 4 categories with difficulties 1, 2, 3, 4
- All 16 words must be unique
- Use title case for Bosnian words
- React 19 + TypeScript + Vite
- Supabase — puzzle storage and (future) auth
- Tailwind CSS v4 — styling via CSS custom properties
- Vercel Analytics
Puzzle Loading:
getPuzzleByDate(date)queriesconnection_puzzlesin Supabase- Results are cached in a module-level
Mapto avoid redundant fetches - Archive dates fetched once from
SELECT date FROM connection_puzzles
Game State:
- Lives in
Game.tsxwith local React state - In-progress state persisted to localStorage after every guess
- Completion data saved to localStorage when game ends
Directory Structure:
client/src/
├── games/
│ └── connections/
│ ├── components/ # Game UI components
│ ├── utils/
│ │ ├── gameLogic.ts # Core game mechanics
│ │ ├── puzzleUtils.ts # Puzzle fetching (Supabase)
│ │ ├── supabasePuzzleUtils.ts # Supabase queries
│ │ ├── storageUtils.ts # localStorage persistence
│ │ └── colors.ts # Difficulty colors
│ ├── types/
│ │ └── game.ts
│ └── index.ts
├── components/
│ ├── Header.tsx
│ └── Footer.tsx
├── context/
│ └── AuthContext.tsx # Supabase auth (wired up, not yet exposed in UI)
├── lib/
│ └── supabase.ts # Supabase client singleton
├── config/
│ └── gameConfig.ts
├── hooks/
│ └── useDarkMode.ts
└── App.tsx
Tailwind CSS v4 with CSS custom properties for theming:
- Dark mode via
.darkclass on<html>, managed byuseDarkModehook - Use
bg-[var(--header-bg)]style arbitrary values, notdark:variants - Difficulty colors: yellow (1), green (2), blue (3), purple (4)
konekcije_completion_{date}— written when game endskonekcije_progress_{date}— written after every guess, cleared on game endtheme— light/dark preference
GitHub Actions (.github/workflows/ci.yml) runs on push/PR to main:
- Node 20.x and 22.x
npm ci→npm run build- Build artifacts uploaded (Node 20.x only)