# Znakovni.hr - Proof of Concept Implementation Plan ## Executive Summary This document provides a complete implementation plan for a 1:1 functional and visual replica of the Znakovni.hr Croatian Sign Language web application. This is a proof-of-concept build optimized for 1-2 concurrent users with local storage. --- ## 🎯 CORE APPLICATION FUNCTIONALITY (Quick Reference) **The application is a Croatian Sign Language learning tool with THREE main interconnected screens:** ### 1️⃣ RIJEČI (Dictionary) - Browse & Add Words - **What it shows:** Grid of pre-recorded sign language words with icons - **What you do:** - Click **"Dodaj"** (Add) to add words to your sentence - Click **"Info"** to watch the sign video for any word - **Screenshot reference:** `1 dodaj rijeci.png` ### 2️⃣ ZNAKOPIS (Sentence Builder) - Order & Save - **What it shows:** All words you added, displayed as draggable tokens - **What you do:** - Drag and drop words to arrange them in correct sentence order - Create multiple sentences across multiple pages - Click **"Spremi"** (Save) to save document to cloud (stored under your user account) - **Screenshot reference:** `2 dodaj rijeci znakopis.png` ### 3️⃣ VIDEO REČENICA (Video Player) - Watch & Learn - **What it shows:** Video player + sentence list - **What you do:** - Click Play to watch all sign videos play in sequence - Current word being signed is **highlighted** in the sentence list - Control playback (pause, speed, navigate) - **Screenshot reference:** `3 dodaj rijeci recenica.png` ### πŸ”„ Complete User Journey: ``` RIJEČI (add words) β†’ ZNAKOPIS (order & save) β†’ VIDEO REČENICA (play & watch) ↓ OBLAK (cloud storage) (documents saved under user account) ``` **Key Technical Points:** - Words are pre-recorded in the database with associated sign videos - Sentence state persists across all three screens (global state management) - Documents are saved to database and associated with logged-in user - Video playback is synchronized with word highlighting - All Croatian text labels must match exactly --- ## πŸ“Š DATA FLOW & STATE MANAGEMENT ### How the Three Screens Work Together **1. Pre-recorded Words (Database)** ``` Terms Table: - id: uuid - wordText: "dobar" (good) - wordType: ADJECTIVE - cefrLevel: A1 - iconAssetId: "path/to/icon.png" TermMedia Table: - termId: (links to Term) - kind: VIDEO - url: "/uploads/videos/dobar.mp4" - durationMs: 2500 ``` **2. User Adds Words (Frontend State β†’ Database)** ``` User clicks "Dodaj" in Riječi ↓ Word added to sentenceStore (Zustand) ↓ User navigates to Znakopis ↓ sentenceStore displays tokens ↓ User reorders tokens via drag-and-drop ↓ User clicks "Spremi" (Save) ↓ API call: POST /api/documents ↓ Database creates: - Document (title, ownerUserId) - DocumentPage (documentId, pageIndex) - Sentence (documentPageId, sentenceIndex) - SentenceToken[] (sentenceId, termId, tokenIndex, displayText) ``` **3. User Plays Video (Database β†’ Frontend)** ``` User navigates to Video Rečenica ↓ Loads document from Oblak or current sentenceStore ↓ API call: GET /api/playlists/generate?sentenceId=xxx ↓ Backend resolves: Sentence β†’ SentenceTokens β†’ Terms β†’ TermMedia (videos) ↓ Returns playlist: [ { tokenId, termId, displayText, videoUrl, durationMs }, { tokenId, termId, displayText, videoUrl, durationMs }, ... ] ↓ Frontend plays videos sequentially ↓ Highlights current token in sentence list ``` ### State Management Strategy **Frontend State (Zustand stores):** - `sentenceStore`: Current sentence being built (tokens, order) - Used by: Riječi (add), Znakopis (edit), Video Rečenica (play) - Persists across navigation - Cleared when document is saved or loaded - `documentStore`: Current loaded document - Used by: Znakopis (edit), Oblak (list), Video Rečenica (play) - Contains: documentId, title, pages[], sentences[] - `authStore`: Current user session - Used by: All screens - Contains: userId, email, role, isAuthenticated **Backend Persistence:** - All saved documents stored in MySQL via Prisma - Documents linked to User via ownerUserId - Full hierarchy: Document β†’ DocumentPage β†’ Sentence β†’ SentenceToken β†’ Term --- ## 1. Technology Stack (PoC-Optimized) ### Frontend - **Framework**: React 18.2+ with TypeScript - **Build Tool**: Vite 5+ - **UI Framework**: Tailwind CSS 3+ (for exact pixel-perfect replication) - **Component Library**: shadcn/ui (customizable, matches screenshot aesthetics) - **State Management**: Zustand (lightweight sentence/document state) - **Video Player**: Plyr (clean, customizable controls) - **Drag & Drop**: @dnd-kit/core (sentence token reordering) - **Routing**: React Router v6 - **HTTP Client**: Axios - **Forms**: React Hook Form + Zod - **Icons**: Lucide React (modern, clean icons) ### Backend - **Runtime**: Node.js 20 LTS - **Framework**: Express.js with TypeScript - **Authentication**: Simple admin user + local user management (OAuth deferred to later phases) - **Session**: express-session with MySQL session store - **Validation**: Zod (shared with frontend) - **File Upload**: Multer (for video/image uploads) - **CORS**: cors middleware ### Database - **RDBMS**: MySQL 8.0+ - **ORM**: Prisma 5+ (type-safe, excellent MySQL support) - **Migrations**: Prisma Migrate - **Full-Text Search**: MySQL FULLTEXT indexes ### Media Storage - **Storage**: Local filesystem (`/uploads` directory) - **Structure**: `/uploads/videos/`, `/uploads/icons/`, `/uploads/documents/` - **Serving**: Express static middleware with proper MIME types - **Video Format**: MP4 (H.264) for maximum compatibility ### Development Tools - **Package Manager**: pnpm - **Monorepo Structure**: pnpm workspaces (frontend + backend) - **Linting**: ESLint + Prettier - **Type Checking**: TypeScript strict mode - **Environment**: dotenv - **Development**: Concurrently (run frontend + backend together) ### Deployment (PoC) - **Server**: Single VPS or local machine - **Process Manager**: PM2 - **Reverse Proxy**: nginx (optional, for production-like setup) - **Database**: MySQL server (local or managed) --- ## 2. Project Folder Structure ``` turkshop/ β”œβ”€β”€ packages/ β”‚ β”œβ”€β”€ frontend/ # React + Vite application β”‚ β”‚ β”œβ”€β”€ public/ β”‚ β”‚ β”‚ └── assets/ # Static assets β”‚ β”‚ β”œβ”€β”€ src/ β”‚ β”‚ β”‚ β”œβ”€β”€ components/ # Reusable UI components β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ ui/ # shadcn/ui base components β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ layout/ # Sidebar, Header, Layout β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ dictionary/ # WordCard, WordGrid, FilterBar β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ znakopis/ # SentenceBuilder, TokenTray β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ video/ # VideoPlayer, PlaylistControls β”‚ β”‚ β”‚ β”‚ └── cloud/ # DocumentBrowser, DocumentList β”‚ β”‚ β”‚ β”œβ”€β”€ pages/ # Route pages β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Home.tsx β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Dictionary.tsx β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Znakopis.tsx β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ VideoSentence.tsx β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Cloud.tsx β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Auth.tsx β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Help.tsx β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Community.tsx β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Comments.tsx β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BugReport.tsx β”‚ β”‚ β”‚ β”‚ └── admin/ β”‚ β”‚ β”‚ β”‚ └── UserManagement.tsx β”‚ β”‚ β”‚ β”œβ”€β”€ stores/ # Zustand stores β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ authStore.ts β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ sentenceStore.ts β”‚ β”‚ β”‚ β”‚ └── documentStore.ts β”‚ β”‚ β”‚ β”œβ”€β”€ lib/ # Utilities β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ api.ts # Axios instance β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ utils.ts β”‚ β”‚ β”‚ β”‚ └── constants.ts β”‚ β”‚ β”‚ β”œβ”€β”€ types/ # TypeScript types β”‚ β”‚ β”‚ β”‚ └── index.ts β”‚ β”‚ β”‚ β”œβ”€β”€ hooks/ # Custom React hooks β”‚ β”‚ β”‚ β”œβ”€β”€ App.tsx β”‚ β”‚ β”‚ β”œβ”€β”€ main.tsx β”‚ β”‚ β”‚ └── index.css # Tailwind imports β”‚ β”‚ β”œβ”€β”€ package.json β”‚ β”‚ β”œβ”€β”€ vite.config.ts β”‚ β”‚ β”œβ”€β”€ tailwind.config.js β”‚ β”‚ └── tsconfig.json β”‚ β”‚ β”‚ └── backend/ # Express + TypeScript API β”‚ β”œβ”€β”€ src/ β”‚ β”‚ β”œβ”€β”€ routes/ # API route handlers β”‚ β”‚ β”‚ β”œβ”€β”€ auth.ts β”‚ β”‚ β”‚ β”œβ”€β”€ admin.ts β”‚ β”‚ β”‚ β”œβ”€β”€ terms.ts β”‚ β”‚ β”‚ β”œβ”€β”€ documents.ts β”‚ β”‚ β”‚ β”œβ”€β”€ sentences.ts β”‚ β”‚ β”‚ β”œβ”€β”€ playlists.ts β”‚ β”‚ β”‚ β”œβ”€β”€ community.ts β”‚ β”‚ β”‚ └── index.ts β”‚ β”‚ β”œβ”€β”€ controllers/ # Business logic β”‚ β”‚ β”‚ β”œβ”€β”€ authController.ts β”‚ β”‚ β”‚ β”œβ”€β”€ adminController.ts β”‚ β”‚ β”‚ β”œβ”€β”€ termController.ts β”‚ β”‚ β”‚ β”œβ”€β”€ documentController.ts β”‚ β”‚ β”‚ └── sentenceController.ts β”‚ β”‚ β”œβ”€β”€ middleware/ # Express middleware β”‚ β”‚ β”‚ β”œβ”€β”€ auth.ts β”‚ β”‚ β”‚ β”œβ”€β”€ validation.ts β”‚ β”‚ β”‚ β”œβ”€β”€ errorHandler.ts β”‚ β”‚ β”‚ └── upload.ts β”‚ β”‚ β”œβ”€β”€ services/ # External services β”‚ β”‚ β”‚ β”œβ”€β”€ authService.ts β”‚ β”‚ β”‚ β”œβ”€β”€ storageService.ts β”‚ β”‚ β”‚ └── playlistService.ts β”‚ β”‚ β”œβ”€β”€ lib/ # Utilities β”‚ β”‚ β”‚ β”œβ”€β”€ prisma.ts # Prisma client β”‚ β”‚ β”‚ └── auth.ts # Auth utilities (bcrypt, session) β”‚ β”‚ β”œβ”€β”€ types/ # TypeScript types β”‚ β”‚ β”‚ └── index.ts β”‚ β”‚ β”œβ”€β”€ config/ # Configuration β”‚ β”‚ β”‚ └── index.ts β”‚ β”‚ └── server.ts # Express app entry β”‚ β”œβ”€β”€ prisma/ β”‚ β”‚ β”œβ”€β”€ schema.prisma # Database schema β”‚ β”‚ β”œβ”€β”€ migrations/ # Migration files β”‚ β”‚ └── seed.ts # Seed data β”‚ β”œβ”€β”€ uploads/ # Local file storage β”‚ β”‚ β”œβ”€β”€ videos/ β”‚ β”‚ β”œβ”€β”€ icons/ β”‚ β”‚ └── documents/ β”‚ β”œβ”€β”€ package.json β”‚ └── tsconfig.json β”‚ β”œβ”€β”€ shared/ # Shared types/schemas (optional) β”‚ β”œβ”€β”€ types/ β”‚ └── schemas/ β”‚ β”œβ”€β”€ package.json # Root package.json (workspace) β”œβ”€β”€ pnpm-workspace.yaml β”œβ”€β”€ .gitignore β”œβ”€β”€ README.md └── main-plan.md # This file ``` --- ## 3. Database Schema (Prisma + MySQL) ### Complete Prisma Schema ```prisma // prisma/schema.prisma generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } // ============================================ // AUTHENTICATION & USERS // ============================================ model User { id String @id @default(uuid()) email String @unique displayName String? @map("display_name") passwordHash String @map("password_hash") role UserRole @default(USER) isActive Boolean @default(true) @map("is_active") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") documents Document[] comments Comment[] bugReports BugReport[] @@map("users") } enum UserRole { ADMIN USER } // ============================================ // DICTIONARY / TERMS // ============================================ model Term { id String @id @default(uuid()) wordText String @map("word_text") @db.VarChar(255) normalizedText String @map("normalized_text") @db.VarChar(255) language String @default("hr") @db.VarChar(10) wordType WordType @map("word_type") cefrLevel CefrLevel @map("cefr_level") shortDescription String? @map("short_description") @db.Text tags String? @db.Text // JSON array stored as text iconAssetId String? @map("icon_asset_id") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") media TermMedia[] examples TermExample[] sentenceTokens SentenceToken[] @@index([normalizedText]) @@fulltext([wordText, normalizedText]) @@map("terms") } model TermMedia { id String @id @default(uuid()) termId String @map("term_id") kind MediaKind url String @db.VarChar(500) durationMs Int? @map("duration_ms") width Int? height Int? checksum String? @db.VarChar(64) createdAt DateTime @default(now()) @map("created_at") term Term @relation(fields: [termId], references: [id], onDelete: Cascade) @@index([termId]) @@map("term_media") } model TermExample { id String @id @default(uuid()) termId String @map("term_id") exampleText String @map("example_text") @db.Text notes String? @db.Text createdAt DateTime @default(now()) @map("created_at") term Term @relation(fields: [termId], references: [id], onDelete: Cascade) @@index([termId]) @@map("term_examples") } // ============================================ // DOCUMENTS & SENTENCES (ZNAKOPIS) // ============================================ model Document { id String @id @default(uuid()) ownerUserId String? @map("owner_user_id") title String @db.VarChar(255) description String? @db.Text visibility Visibility @default(PRIVATE) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") owner User? @relation(fields: [ownerUserId], references: [id], onDelete: SetNull) pages DocumentPage[] @@index([ownerUserId]) @@map("documents") } model DocumentPage { id String @id @default(uuid()) documentId String @map("document_id") pageIndex Int @map("page_index") title String? @db.VarChar(255) document Document @relation(fields: [documentId], references: [id], onDelete: Cascade) sentences Sentence[] @@unique([documentId, pageIndex]) @@index([documentId]) @@map("document_pages") } model Sentence { id String @id @default(uuid()) documentPageId String @map("document_page_id") sentenceIndex Int @map("sentence_index") rawText String? @map("raw_text") @db.Text documentPage DocumentPage @relation(fields: [documentPageId], references: [id], onDelete: Cascade) tokens SentenceToken[] @@unique([documentPageId, sentenceIndex]) @@index([documentPageId]) @@map("sentences") } model SentenceToken { id String @id @default(uuid()) sentenceId String @map("sentence_id") tokenIndex Int @map("token_index") termId String? @map("term_id") displayText String @map("display_text") @db.VarChar(255) isPunctuation Boolean @default(false) @map("is_punctuation") sentence Sentence @relation(fields: [sentenceId], references: [id], onDelete: Cascade) term Term? @relation(fields: [termId], references: [id], onDelete: SetNull) @@unique([sentenceId, tokenIndex]) @@index([sentenceId]) @@index([termId]) @@map("sentence_tokens") } // ============================================ // COMMUNITY & SUPPORT // ============================================ model Comment { id String @id @default(uuid()) userId String @map("user_id") content String @db.Text context String? @db.VarChar(255) // e.g., "term:uuid" or "general" createdAt DateTime @default(now()) @map("created_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@map("comments") } model BugReport { id String @id @default(uuid()) userId String? @map("user_id") title String @db.VarChar(255) description String @db.Text status BugStatus @default(OPEN) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([userId]) @@index([status]) @@map("bug_reports") } // ============================================ // ENUMS // ============================================ enum WordType { NOUN VERB ADJECTIVE ADVERB PRONOUN PREPOSITION CONJUNCTION INTERJECTION PHRASE OTHER } enum CefrLevel { A1 A2 B1 B2 C1 C2 } enum MediaKind { VIDEO IMAGE ILLUSTRATION } enum Visibility { PRIVATE SHARED PUBLIC } enum BugStatus { OPEN IN_PROGRESS RESOLVED CLOSED } ``` --- ## 4. API Routes & Endpoints ### Base URL - Development: `http://localhost:3000/api` - Production: `https://yourdomain.com/api` ### 4.1 Authentication Routes (`/api/auth`) | Method | Endpoint | Description | Auth Required | |--------|----------|-------------|---------------| | POST | `/auth/login` | Login with email/password | No | | POST | `/auth/logout` | Logout current user | Yes | | GET | `/auth/me` | Get current user info | Yes | ### 4.2 Admin User Management Routes (`/api/admin/users`) | Method | Endpoint | Description | Auth Required | |--------|----------|-------------|---------------| | GET | `/admin/users` | List all users | Yes (Admin) | | POST | `/admin/users` | Create new user | Yes (Admin) | | PUT | `/admin/users/:id` | Update user | Yes (Admin) | | DELETE | `/admin/users/:id` | Delete user | Yes (Admin) | | PUT | `/admin/users/:id/password` | Reset user password | Yes (Admin) | | PUT | `/admin/users/:id/activate` | Activate/deactivate user | Yes (Admin) | ### 4.3 Dictionary/Terms Routes (`/api/terms`) | Method | Endpoint | Description | Auth Required | |--------|----------|-------------|---------------| | GET | `/terms` | List/search terms with filters | No | | GET | `/terms/:id` | Get single term details | No | | GET | `/terms/:id/media` | Get all media for a term | No | | GET | `/terms/:id/examples` | Get examples for a term | No | | POST | `/terms` | Create new term (admin) | Yes (Admin) | | PUT | `/terms/:id` | Update term (admin) | Yes (Admin) | | DELETE | `/terms/:id` | Delete term (admin) | Yes (Admin) | **Query Parameters for GET `/terms`:** - `query` (string): Search text - `wordType` (enum): Filter by word type - `cefrLevel` (enum): Filter by CEFR level - `page` (number): Page number (default: 1) - `limit` (number): Items per page (default: 20) - `sortBy` (string): Sort field (default: wordText) - `sortOrder` (asc|desc): Sort direction (default: asc) ### 4.4 Document Routes (`/api/documents`) | Method | Endpoint | Description | Auth Required | |--------|----------|-------------|---------------| | GET | `/documents` | List user's documents | Yes | | GET | `/documents/:id` | Get full document with pages/sentences | Yes | | POST | `/documents` | Create new document | Yes | | PUT | `/documents/:id` | Update document metadata | Yes | | DELETE | `/documents/:id` | Delete document | Yes | | PUT | `/documents/:id/content` | Update document content (pages/sentences) | Yes | | POST | `/documents/:id/share` | Generate share link | Yes | ### 4.5 Sentence Routes (`/api/sentences`) | Method | Endpoint | Description | Auth Required | |--------|----------|-------------|---------------| | GET | `/sentences/:id` | Get sentence with tokens | Yes | | POST | `/sentences` | Create new sentence | Yes | | PUT | `/sentences/:id` | Update sentence | Yes | | DELETE | `/sentences/:id` | Delete sentence | Yes | | POST | `/sentences/:id/tokens` | Add token to sentence | Yes | | PUT | `/sentences/:id/tokens/:tokenId` | Update token | Yes | | DELETE | `/sentences/:id/tokens/:tokenId` | Remove token | Yes | | PUT | `/sentences/:id/reorder` | Reorder tokens | Yes | ### 4.6 Playlist Routes (`/api/playlists`) | Method | Endpoint | Description | Auth Required | |--------|----------|-------------|---------------| | POST | `/playlists/generate` | Generate video playlist from sentence | No | **Request Body:** ```json { "sentenceId": "uuid", "tokenIds": ["uuid1", "uuid2", "uuid3"] } ``` **Response:** ```json { "playlist": [ { "tokenId": "uuid1", "termId": "uuid", "displayText": "Dobar", "videoUrl": "/uploads/videos/dobar.mp4", "durationMs": 2500, "thumbnailUrl": "/uploads/videos/dobar-thumb.jpg" } ], "totalDuration": 7500 } ``` ### 4.7 Community Routes (`/api/community`) | Method | Endpoint | Description | Auth Required | |--------|----------|-------------|---------------| | GET | `/community/comments` | List comments | No | | POST | `/community/comments` | Create comment | Yes | | DELETE | `/community/comments/:id` | Delete own comment | Yes | ### 4.8 Bug Report Routes (`/api/bugs`) | Method | Endpoint | Description | Auth Required | |--------|----------|-------------|---------------| | GET | `/bugs` | List bug reports | Yes (Admin) | | GET | `/bugs/:id` | Get bug report details | Yes | | POST | `/bugs` | Submit bug report | Yes | | PUT | `/bugs/:id` | Update bug status (admin) | Yes (Admin) | ### 4.9 Upload Routes (`/api/uploads`) | Method | Endpoint | Description | Auth Required | |--------|----------|-------------|---------------| | POST | `/uploads/video` | Upload video file | Yes (Admin) | | POST | `/uploads/icon` | Upload icon/image | Yes (Admin) | ### 4.10 Static File Serving | Path | Description | |------|-------------| | `/uploads/videos/*` | Video files | | `/uploads/icons/*` | Icon/image files | | `/uploads/documents/*` | Document exports | --- ## 5. UI/UX Specifications (Exact Replication) ### 5.1 Design System (From Screenshots) **Color Palette:** - Primary Blue: `#2563eb` (buttons, active states) - Sidebar Background: `#1e293b` (dark slate) - Card Colors (word difficulty): - Green: `#10b981` (A1-A2) - Yellow: `#f59e0b` (B1-B2) - Orange: `#f97316` (C1-C2) - Background: `#f8fafc` (light gray) - Text Primary: `#0f172a` - Text Secondary: `#64748b` - Border: `#e2e8f0` **Typography:** - Font Family: Inter, system-ui, sans-serif - Headings: 600-700 weight - Body: 400-500 weight - Sizes: 14px (body), 16px (large), 12px (small) **Spacing:** - Base unit: 4px (Tailwind default) - Card padding: 16px - Section gaps: 24px - Grid gap: 16px **Border Radius:** - Cards: 8px - Buttons: 6px - Inputs: 6px ### 5.2 Layout Components **Sidebar (Left Navigation):** - Width: 240px - Fixed position - Dark background (#1e293b) - White text with hover states - Logo at top - Navigation items with icons - Auth section at bottom **Main Content Area:** - Margin-left: 240px (sidebar width) - Padding: 24px - Max-width: 1400px (for large screens) **Filter Bar (Dictionary Page):** - Sticky top position - White background with shadow - Flex layout: inputs on left, actions on right - Input fields: text search, dropdowns for filters - Action buttons: Search (blue), Text toggle, Reset **Word Card Grid:** - CSS Grid: 4-5 columns on desktop, responsive - Gap: 16px - Card structure: - Icon area (top, colored background) - Word label (bottom, white background) - Action buttons (overlay on hover) ### 5.3 Page-Specific Layouts **Home (Početna):** - Hero section with large heading - 2x2 grid of feature cards - Each card: icon, title, description, link **Dictionary (Riječi):** - Filter bar (sticky) - Word card grid - Pagination at bottom - Word detail modal (overlay) **Znakopis (Sentence Builder):** - Two-column layout: - Left: Token tray (current sentence) - Right: Document panel (sentence list, page controls) - Drag-and-drop interface for tokens - Save/Load buttons in header **Video Rečenica (Video Sentence):** - Two-column layout: - Left: Large video player (60% width) - Right: Sentence panel (40% width) - Video controls below player - Highlighted current token in sentence list **Oblak (Cloud):** - Document list with cards - Upload/Create buttons - Document metadata display - Load/Delete actions per document ### 5.4 Component Library (shadcn/ui) Components to install: - `button` - All action buttons - `input` - Text inputs - `select` - Dropdowns - `card` - Word cards, document cards - `dialog` - Modals (word detail, confirmations) - `dropdown-menu` - User menu - `tabs` - Znakopis/Oblak tabs - `toast` - Notifications - `avatar` - User avatar - `badge` - CEFR level badges - `separator` - Dividers - `scroll-area` - Scrollable lists ### 5.5 Croatian Text (Exact Labels) **Navigation:** - Početna (Home) - Riječi (Words/Dictionary) - Znakopis (Sentence Builder) - Video rečenica (Video Sentence) - Oblak (Cloud) - KoriΕ‘tenje aplikacije (Help) - Zajednica (Community) - Komentari (Comments) - Prijavi greΕ‘ku (Bug Report) - Admin Panel (Admin only - User Management) **Dictionary Page:** - Riječ (Word) - search input label - Tip riječi (Word Type) - dropdown label - CEFR razina (CEFR Level) - dropdown label - TraΕΎi (Search) - button - Text - toggle button - Reset - button - Dodaj (Add) - card action - Info - card action **Znakopis Page:** - Popis rečenica (Sentence List) - stranica (page) - Učitajte dokument (Load Document) - Spremi (Save) - Nova rečenica (New Sentence) **Auth:** - Prijavi se (Sign In) - Odjavi se (Sign Out) - Email adresa (Email Address) - Lozinka (Password) **Admin Panel:** - Korisnici (Users) - Dodaj korisnika (Add User) - Uredi korisnika (Edit User) - ObriΕ‘i korisnika (Delete User) - Resetiraj lozinku (Reset Password) - Aktiviraj/Deaktiviraj (Activate/Deactivate) - Uloga (Role) - Aktivan (Active) --- ## 6. Implementation Milestones **Note on Authentication Strategy:** For the initial phases, we will implement a simple admin user with local user management. OAuth integration (Google, Microsoft) will be deferred to later phases (Phase 8+) to focus on core functionality first. The admin user will be able to create and manage local users through an admin panel. ### Phase 0: Project Setup (Week 1) **Goal:** Initialize project structure and development environment **Tasks:** 1. Create monorepo structure with pnpm workspaces 2. Initialize frontend (Vite + React + TypeScript) 3. Initialize backend (Express + TypeScript) 4. Set up Prisma with MySQL 5. Configure Tailwind CSS and shadcn/ui 6. Set up ESLint, Prettier, and TypeScript configs 7. Create `.env.example` files 8. Set up Git repository and `.gitignore` 9. Create initial README with setup instructions **Deliverables:** - βœ… Project compiles and runs (frontend + backend) - βœ… Database connection works - βœ… Basic "Hello World" on both ends --- ### Phase 1: Core Infrastructure (Week 2) **Goal:** Build basic authentication and layout with admin user **Backend Tasks:** 1. Implement Prisma schema (User model with role and isActive fields) 2. Run initial migration 3. Create seed script with default admin user 4. Implement simple session-based authentication 5. Create auth routes (login, logout, me) 6. Create auth middleware (isAuthenticated, isAdmin) 7. Set up CORS and security headers 8. Create admin user management routes (CRUD) **Frontend Tasks:** 1. Create layout components (Sidebar, Header, Layout) 2. Set up React Router with routes 3. Create login page (simple email/password) 4. Implement auth store (Zustand) 5. Create auth API client 6. Implement protected routes 7. Create admin panel page for user management 8. Build user list, create, edit, delete components **Deliverables:** - βœ… Admin can login with default credentials - βœ… Session persists across page reloads - βœ… Sidebar navigation works - βœ… Protected routes redirect to login - βœ… Admin can create/edit/delete local users - βœ… Admin can reset user passwords - βœ… Admin can activate/deactivate users --- ### Phase 2: Dictionary Module (Week 3-4) **Goal:** Complete dictionary browsing and search **Backend Tasks:** 1. Implement Term, TermMedia, TermExample models 2. Run migrations 3. Create seed script with sample terms 4. Implement term routes (list, get, search) 5. Add filtering logic (wordType, cefrLevel, query) 6. Add pagination 7. Implement full-text search 8. Set up file upload for videos/icons 9. Create static file serving **Frontend Tasks:** 1. Create Dictionary page layout 2. Build FilterBar component 3. Build WordCard component 4. Build WordGrid component 5. Implement term API client 6. Add search and filter functionality 7. Create WordDetailModal component 8. Integrate video player (Plyr) 9. Add pagination controls 10. Implement "Text" toggle view **Deliverables:** - βœ… Dictionary page displays word cards - βœ… Search and filters work - βœ… Word detail modal shows video - βœ… Pagination works - βœ… UI matches screenshots exactly --- ### Phase 3: Sentence Builder (Znakopis) (Week 5-6) **Goal:** Build sentence composition workspace **Backend Tasks:** 1. Implement Document, DocumentPage, Sentence, SentenceToken models 2. Run migrations 3. Create document routes (CRUD) 4. Create sentence routes (CRUD) 5. Implement token management (add, remove, reorder) 6. Add document content update endpoint 7. Implement document listing for user **Frontend Tasks:** 1. Create Znakopis page layout 2. Build sentence store (Zustand) 3. Implement "Add to sentence" from Dictionary 4. Create TokenTray component 5. Implement drag-and-drop reordering (@dnd-kit) 6. Create SentenceList component 7. Build DocumentPanel component 8. Implement save/load document 9. Add page management (multi-page documents) 10. Create new sentence functionality **Deliverables:** - βœ… Users can add words from dictionary to sentence - βœ… Tokens can be reordered via drag-and-drop - βœ… Sentences can be saved to documents - βœ… Documents can be loaded from cloud - βœ… Multi-page documents work - βœ… UI matches screenshots exactly --- ### Phase 4: Video Sentence Player (Week 7) **Goal:** Implement video playback of sentences **Backend Tasks:** 1. Create playlist generation endpoint 2. Implement playlist resolver (tokens β†’ videos) 3. Handle missing media gracefully 4. Add video metadata to responses **Frontend Tasks:** 1. Create VideoSentence page layout 2. Build VideoPlayer component with Plyr 3. Implement playlist logic 4. Add playback controls (play, pause, next, prev) 5. Implement token highlighting sync 6. Add speed control 7. Add loop functionality 8. Implement preloading of next video 9. Create sentence panel (right side) **Deliverables:** - βœ… Sentences play as sequential videos - βœ… Playback controls work - βœ… Current token is highlighted - βœ… Videos transition smoothly - βœ… UI matches screenshots exactly --- ### Phase 5: Cloud Documents (Week 8) **Goal:** Complete document management **Backend Tasks:** 1. Implement document visibility settings 2. Add share link generation 3. Create document export functionality 4. Add document search/filtering **Frontend Tasks:** 1. Create Oblak (Cloud) page 2. Build DocumentBrowser component 3. Implement document list with cards 4. Add upload document functionality 5. Create document metadata editor 6. Implement load document into Znakopis 7. Add delete document with confirmation 8. Implement document sharing UI **Deliverables:** - βœ… Users can view all their documents - βœ… Documents can be loaded into editor - βœ… Documents can be deleted - βœ… Share links work - βœ… UI matches screenshots exactly --- ### Phase 6: Community & Support (Week 9) **Goal:** Add community features **Backend Tasks:** 1. Implement Comment and BugReport models 2. Run migrations 3. Create community routes 4. Create bug report routes 5. Add admin endpoints for bug management **Frontend Tasks:** 1. Create Help page (KoriΕ‘tenje aplikacije) 2. Create Community page (Zajednica) 3. Create Comments page (Komentari) 4. Build comment form and list 5. Create BugReport page (Prijavi greΕ‘ku) 6. Build bug report form 7. Add toast notifications **Deliverables:** - βœ… Users can post comments - βœ… Users can submit bug reports - βœ… Help page has documentation - βœ… UI matches screenshots exactly --- ### Phase 7: Polish & Testing (Week 10) **Goal:** Final refinements and quality assurance **Tasks:** 1. Responsive design testing (mobile, tablet, desktop) 2. Cross-browser testing (Chrome, Firefox, Safari, Edge) 3. Accessibility audit (keyboard nav, ARIA labels, screen readers) 4. Performance optimization (lazy loading, code splitting) 5. Error handling improvements 6. Loading states and skeletons 7. Form validation refinements 8. Video loading optimization 9. Database query optimization 10. Security audit 11. Write E2E tests (Playwright) 12. Write unit tests for critical functions 13. Documentation updates 14. Deployment preparation **Deliverables:** - βœ… App works on all major browsers - βœ… Mobile responsive - βœ… Accessible (WCAG 2.1 AA) - βœ… Fast loading times - βœ… No critical bugs - βœ… Tests pass - βœ… Ready for deployment --- ### Phase 8: OAuth Integration (Future Phase) **Goal:** Add OAuth authentication providers (deferred from Phase 1) **Backend Tasks:** 1. Set up Passport.js with Google OAuth strategy 2. Set up Passport.js with Microsoft OAuth strategy 3. Update User model to support authProvider and providerId fields 4. Create OAuth callback routes 5. Implement account linking (OAuth to existing local accounts) 6. Update auth middleware to support OAuth sessions **Frontend Tasks:** 1. Add OAuth buttons to login page 2. Create OAuth callback handling 3. Add account linking UI 4. Update user profile to show auth provider **Deliverables:** - βœ… Users can sign in with Google - βœ… Users can sign in with Microsoft - βœ… OAuth accounts can be linked to existing local accounts - βœ… Session management works with OAuth --- ## 7. Environment Configuration ### Backend `.env` (packages/backend/.env) ```env # Server NODE_ENV=development PORT=3000 FRONTEND_URL=http://localhost:5173 # Database DATABASE_URL="mysql://user:password@localhost:3306/znakovni" # Session SESSION_SECRET=your-super-secret-session-key-change-in-production # Default Admin User (created on first run) ADMIN_EMAIL=admin@znakovni.hr ADMIN_PASSWORD=change-this-password-immediately # File Upload UPLOAD_DIR=./uploads MAX_FILE_SIZE=104857600 # 100MB in bytes # CORS CORS_ORIGIN=http://localhost:5173 ``` ### Frontend `.env` (packages/frontend/.env) ```env VITE_API_URL=http://localhost:3000/api VITE_UPLOADS_URL=http://localhost:3000/uploads ``` --- ## 8. Key Features & User Flows ### CORE FUNCTIONALITY OVERVIEW (Based on Screenshots 1, 2, 3) **The application has THREE main interconnected screens that form the primary workflow:** #### Screen 1: Riječi (Dictionary) - "1 dodaj rijeci.png" **Purpose:** Browse pre-recorded sign language words and add them to your sentence. **Key Elements:** - Grid of word cards with icons/illustrations - Each card has two buttons: - **"Dodaj"** (Add) - Adds the word to the current sentence being built - **"Info"** - Opens a modal/detail view showing the sign video for that word - Filter bar at top with search and CEFR level filters - Words are color-coded by difficulty level (green/yellow/orange) **User Actions:** 1. Browse or search for words 2. Click "Dodaj" to add words to the sentence (words accumulate at the top of the page) 3. Click "Info" to watch the sign video demonstration for any word 4. Continue adding multiple words to build a complete sentence --- #### Screen 2: Znakopis (Sentence Builder) - "2 dodaj rijeci znakopis.png" **Purpose:** Organize the words you've added into a proper sentence order and save as a document. **Key Elements:** - **Top area:** Shows the current sentence with all added words as tokens/chips - **Right panel:** "Popis rečenica" (Sentence List) showing: - All sentences in the current document - Page navigation (e.g., "1 / 2 stranica") - Document management controls - **Main workspace:** Area to reorder words via drag-and-drop - **Action buttons:** - "Spremi" (Save) - Saves the document to cloud storage under your user account - "Učitajte dokument" (Load Document) - Loads a previously saved document - "Nova rečenica" (New Sentence) - Creates a new sentence on a new page **User Actions:** 1. View all words added from the Dictionary screen 2. Drag and drop words to reorder them into the correct sentence structure 3. Remove unwanted words 4. Create multiple sentences across multiple pages 5. Save the complete document to the cloud (stored under user's account) 6. Load previously saved documents for editing --- #### Screen 3: Video Rečenica (Video Sentence Player) - "3 dodaj rijeci recenica.png" **Purpose:** Play all the sign videos for your sentence in sequence, with visual highlighting. **Key Elements:** - **Left side (60%):** Large video player showing the current word's sign video - **Right side (40%):** Sentence panel showing: - Complete sentence with all words listed - Current word being played is **highlighted/marked** - Sentence navigation controls - **Playback controls:** - Play/Pause - Next/Previous word - Speed control - Loop options **User Actions:** 1. Load a sentence from Znakopis or Cloud 2. Click play to start sequential video playback 3. Watch as each word's sign video plays in order 4. See visual highlighting on the current word being signed 5. Control playback (pause, skip, adjust speed) 6. Navigate between different sentences/pages in the document --- ### 8.1 Complete User Workflow (End-to-End) **Step 1: Build a Sentence (Riječi β†’ Znakopis)** 1. User navigates to **"Riječi"** (Dictionary) 2. User searches/browses for words they want to use 3. User clicks **"Dodaj"** on each word card to add it to their sentence - Words accumulate in a sentence builder area (visible at top) 4. User can click **"Info"** on any word to preview its sign video 5. After adding all desired words, user navigates to **"Znakopis"** 6. In Znakopis, user sees all added words as draggable tokens 7. User drags and drops words to arrange them in correct order 8. User can remove unwanted words 9. User can create additional sentences on new pages 10. User clicks **"Spremi"** (Save) to save the document to cloud - Document is stored under the user's account in the portal **Step 2: Review and Play (Znakopis β†’ Video Rečenica)** 1. User navigates to **"Video rečenica"** (Video Sentence) 2. User loads their saved sentence/document 3. Sentence appears in the right panel with all words listed 4. User clicks **Play** 5. Video player shows each word's sign video in sequence 6. Current word is **highlighted** in the sentence list 7. User can control playback, adjust speed, or navigate between sentences **Step 3: Manage Documents (Oblak)** 1. User navigates to **"Oblak"** (Cloud) 2. User sees all their saved documents 3. User can: - Load a document back into Znakopis for editing - Delete documents - Share documents (generate link) - View document metadata (creation date, page count, etc.) --- ### 8.2 Critical Implementation Requirements **Sentence State Management:** - Sentence state must persist across all three screens (Riječi β†’ Znakopis β†’ Video Rečenica) - When user adds words in Riječi, they must appear in Znakopis - When user saves in Znakopis, document must be available in Video Rečenica and Oblak - Use Zustand store to maintain sentence/document state globally **Video Synchronization:** - In Video Rečenica, video playback must be synchronized with word highlighting - When video for word N finishes, automatically start video for word N+1 - Highlight must move to the current word being played - Smooth transitions between videos (preload next video) **Document Structure:** - Documents can have multiple pages - Each page can have multiple sentences - Each sentence is a sequence of word tokens - Tokens maintain reference to original Term (for video lookup) - Token order is preserved and editable **Cloud Storage:** - All saved documents are associated with the logged-in user - Documents persist in database (Document β†’ DocumentPage β†’ Sentence β†’ SentenceToken) - Users can only see/edit their own documents (unless shared) - Documents can be loaded back into Znakopis for editing --- ## 9. Technical Implementation Details ### 9.1 Video Playlist Resolution Algorithm ```typescript // Pseudocode for playlist generation async function generatePlaylist(sentenceId: string) { // 1. Fetch sentence with tokens const sentence = await prisma.sentence.findUnique({ where: { id: sentenceId }, include: { tokens: { include: { term: { include: { media: true } } }, orderBy: { tokenIndex: 'asc' } } } }); // 2. Map tokens to video URLs const playlist = sentence.tokens.map(token => { if (!token.term) { return null; // Skip punctuation or free-text } // Find primary video media const video = token.term.media.find(m => m.kind === 'VIDEO'); if (!video) { console.warn(`No video for term ${token.term.id}`); return null; } return { tokenId: token.id, termId: token.term.id, displayText: token.displayText, videoUrl: video.url, durationMs: video.durationMs, thumbnailUrl: video.url.replace('.mp4', '-thumb.jpg') }; }).filter(Boolean); return { playlist, totalDuration: playlist.reduce((sum, item) => sum + item.durationMs, 0) }; } ``` ### 9.2 Full-Text Search Implementation ```typescript // MySQL full-text search query async function searchTerms(query: string, filters: Filters) { const where = { AND: [ query ? { OR: [ { wordText: { contains: query } }, { normalizedText: { contains: query } } ] } : {}, filters.wordType ? { wordType: filters.wordType } : {}, filters.cefrLevel ? { cefrLevel: filters.cefrLevel } : {} ] }; return await prisma.term.findMany({ where, include: { media: true }, orderBy: { wordText: 'asc' }, skip: (filters.page - 1) * filters.limit, take: filters.limit }); } ``` ### 9.3 Token Reordering Logic ```typescript // Frontend: Drag-and-drop reorder function handleDragEnd(event: DragEndEvent) { const { active, over } = event; if (active.id !== over.id) { const oldIndex = tokens.findIndex(t => t.id === active.id); const newIndex = tokens.findIndex(t => t.id === over.id); const reordered = arrayMove(tokens, oldIndex, newIndex); // Update local state setSentenceTokens(reordered); // Sync to backend await api.put(`/sentences/${sentenceId}/reorder`, { tokenIds: reordered.map(t => t.id) }); } } ``` --- ## 10. Deployment Guide (PoC) ### 10.1 Prerequisites - Ubuntu 20.04+ or similar Linux server - Node.js 20 LTS installed - MySQL 8.0+ installed and running - pnpm installed globally - nginx installed (optional, for reverse proxy) ### 10.2 Deployment Steps **1. Clone and Install** ```bash git clone cd turkshop pnpm install ``` **2. Configure Environment** ```bash # Backend cd packages/backend cp .env.example .env # Edit .env with production values nano .env # Frontend cd ../frontend cp .env.example .env # Edit .env with production API URL nano .env ``` **3. Database Setup** ```bash cd packages/backend npx prisma migrate deploy npx prisma db seed ``` **4. Build Applications** ```bash # From root pnpm build ``` **5. Start with PM2** ```bash # Install PM2 npm install -g pm2 # Start backend cd packages/backend pm2 start dist/server.js --name znakovni-api # Serve frontend (with nginx or serve) cd ../frontend pm2 serve dist 5173 --name znakovni-frontend ``` **6. Configure nginx (Optional)** ```nginx server { listen 80; server_name yourdomain.com; # Frontend location / { root /path/to/turkshop/packages/frontend/dist; try_files $uri $uri/ /index.html; } # Backend API location /api { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } # Uploads location /uploads { proxy_pass http://localhost:3000; } } ``` **7. Set up SSL (Production)** ```bash sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d yourdomain.com ``` --- ## 11. Development Workflow ### 11.1 Initial Setup ```bash # Clone repo git clone cd turkshop # Install dependencies pnpm install # Set up environment files cp packages/backend/.env.example packages/backend/.env cp packages/frontend/.env.example packages/frontend/.env # Edit .env files with your local MySQL credentials and OAuth keys # Run database migrations cd packages/backend npx prisma migrate dev npx prisma db seed # Return to root cd ../.. ``` ### 11.2 Running Development Servers ```bash # From root directory pnpm dev # This runs both frontend (port 5173) and backend (port 3000) concurrently ``` ### 11.3 Database Management ```bash # Create new migration cd packages/backend npx prisma migrate dev --name description_of_change # Reset database (WARNING: deletes all data) npx prisma migrate reset # Open Prisma Studio (GUI for database) npx prisma studio # Generate Prisma Client after schema changes npx prisma generate ``` ### 11.4 Adding shadcn/ui Components ```bash cd packages/frontend npx shadcn-ui@latest add button npx shadcn-ui@latest add input npx shadcn-ui@latest add card # etc. ``` --- ## 12. Success Criteria ### Core Functionality Requirements (CRITICAL) βœ… **Screen 1: Riječi (Dictionary)** - [ ] Dictionary displays grid of word cards with icons - [ ] Each card has "Dodaj" (Add) and "Info" buttons - [ ] Clicking "Dodaj" adds word to current sentence (visible at top of page) - [ ] Clicking "Info" opens modal with sign video that auto-plays - [ ] Search and filter controls work (word type, CEFR level) - [ ] Words are color-coded by difficulty level **Screen 2: Znakopis (Sentence Builder)** - [ ] All words added from Dictionary appear as draggable tokens - [ ] Tokens can be reordered via drag-and-drop - [ ] Tokens can be removed - [ ] Right panel shows "Popis rečenica" (Sentence List) - [ ] Users can create multiple sentences across multiple pages - [ ] "Spremi" (Save) button saves document to cloud under user's account - [ ] "Učitajte dokument" (Load Document) loads previously saved documents - [ ] Page navigation works (e.g., "1 / 2 stranica") **Screen 3: Video Rečenica (Video Player)** - [ ] Left side shows large video player (60% width) - [ ] Right side shows sentence panel with all words (40% width) - [ ] Clicking Play starts sequential video playback - [ ] Videos play in order, one after another - [ ] Current word being signed is **highlighted** in the sentence list - [ ] Highlighting moves automatically as videos progress - [ ] Playback controls work (play, pause, next, previous) - [ ] Speed control and loop options work - [ ] Videos transition smoothly (preload next video) **Cross-Screen Integration** - [ ] Sentence state persists from Riječi β†’ Znakopis β†’ Video Rečenica - [ ] Documents saved in Znakopis appear in Oblak (Cloud) - [ ] Documents loaded from Oblak can be edited in Znakopis - [ ] Documents loaded from Oblak can be played in Video Rečenica **Authentication & User Management** - [ ] Admin can login with credentials - [ ] Admin can create/edit/delete local users - [ ] Admin can reset user passwords - [ ] Admin can activate/deactivate users - [ ] Regular users can login with their credentials - [ ] Documents are associated with logged-in user - [ ] Users can only see their own documents (unless shared) **Cloud Document Management (Oblak)** - [ ] Users can view all their saved documents - [ ] Documents show metadata (title, creation date, page count) - [ ] Users can load documents into Znakopis - [ ] Users can delete documents - [ ] Users can share documents (generate link) **Additional Features** - [ ] Comments and bug reports can be submitted - [ ] Help page displays usage documentation - [ ] Community features work ### Visual Requirements βœ… - [ ] UI matches screenshots pixel-perfect - [ ] Croatian text labels are exact - [ ] Color scheme matches original - [ ] Typography matches original - [ ] Layout and spacing match original - [ ] Icons and graphics match original ### Performance Requirements βœ… - [ ] Dictionary search returns results < 500ms - [ ] Video playback starts < 1s - [ ] Page load time < 2s - [ ] Smooth animations (60fps) ### Quality Requirements βœ… - [ ] TypeScript strict mode with no errors - [ ] All forms have validation - [ ] Error handling on all API calls - [ ] Loading states for async operations - [ ] Responsive design (desktop, tablet, mobile) - [ ] Accessible (keyboard navigation, ARIA labels) --- ## 13. Next Steps 1. **Review and approve this plan** 2. **Prepare MySQL database** (local or cloud) 3. **Begin Phase 0: Project Setup** 4. **Iterate through phases sequentially** 5. **Test continuously during development** 6. **Deploy to production server** 7. **Phase 8+ (Future): Set up OAuth credentials** (Google Cloud Console, Microsoft Azure) when ready to add OAuth --- ## 14. Resources & References ### Documentation - React: https://react.dev - Vite: https://vitejs.dev - TypeScript: https://www.typescriptlang.org - Tailwind CSS: https://tailwindcss.com - shadcn/ui: https://ui.shadcn.com - Prisma: https://www.prisma.io - Express: https://expressjs.com - Passport.js: https://www.passportjs.org - Plyr: https://plyr.io - @dnd-kit: https://dndkit.com ### Original Specifications - `/usr/src/znakovni/original/project1.md` - Detailed functional spec - `/usr/src/znakovni/original/project2.md` - Replication requirements - `/usr/src/znakovni/original/*.png` - UI screenshots --- **END OF PLAN**