diff --git a/README.md b/README.md index 1f39f28..ed05f0b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TV_APP V1.0.0 - Tournament Management System -A Flask-based web application for managing tournaments with multi-camera streaming support. +A Flask-based web application for managing tournaments with multi-camera streaming support and improved modern UI architecture. ## Quick Start @@ -31,21 +31,56 @@ The app will be available at: **http://localhost:5000** ``` TV_APP_V1.0.0/ -├── tv_app.py # Main Flask application (2,364 lines) -├── requirements.txt # Python dependencies -├── README.md # This file +├── tv_app.py # Main Flask application +├── requirements.txt # Python dependencies +├── README.md # This file │ -├── app/ # Support modules +├── app/ # Support modules │ ├── __init__.py -│ ├── config.py # Configuration -│ ├── models.py # Data models -│ ├── storage.py # Persistent storage -│ └── utils.py # Utilities +│ ├── config.py # Configuration +│ ├── models.py # Data models +│ ├── storage.py # Persistent storage +│ └── utils.py # Utilities │ -├── templates/ # HTML templates -├── static/ # CSS, JS, images -├── data/ # Runtime data (JSON state) -└── locales/ # Translations (EN, SL) +├── templates/ # HTML templates (15 files) +│ ├── index.html # Main dashboard +│ ├── tournament.html # Tournament configuration +│ ├── draft.html # Draft management +│ ├── results_calculator.html # Results calculation +│ ├── mobile.html # Mobile interface +│ ├── archive.html # Tournament history +│ ├── match_details.html # Match view +│ ├── player_stats.html # Player analytics +│ └── ... (10 more templates) +│ +├── static/ # Static assets +│ ├── css/ # Modular CSS (NEW - Consolidated) +│ │ ├── base.css # Global styles & variables +│ │ ├── navbar.css # Navigation styling +│ │ ├── buttons.css # Button system +│ │ ├── components.css # Cards, forms, modals, tables +│ │ └── responsive.css # Media queries & responsive utilities +│ ├── js/ # JavaScript files +│ │ ├── websocket-client.js # (Deprecated - SocketIO removed) +│ │ └── ... (other JS files) +│ └── images/ # Images and assets +│ +├── data/ # Runtime data (JSON state) +│ ├── tournaments.json # Tournament data +│ ├── leagues.json # League data +│ └── ... (other data files) +│ +├── locales/ # Multi-language support +│ ├── en.json # English translations +│ └── sl.json # Slovenian translations +│ +├── docs/ # Documentation (NEW) +│ ├── COMPLETE_DOCUMENTATION.md # Master documentation (60 KB, 2,065 lines) +│ ├── CSS_CONSOLIDATION_GUIDE.md # CSS implementation guide +│ ├── APP_RESTRUCTURE_BLUEPRINT.md # V2.0 architecture proposal +│ └── IMPLEMENTATION_QUICK_START.md # 4-week implementation roadmap +│ +└── README.md # This file ``` ## Features @@ -57,37 +92,172 @@ TV_APP_V1.0.0/ ✅ League management ✅ Multi-language support (English & Slovenian) ✅ JSON-based persistent storage +✅ **[NEW]** Modular CSS architecture with CSS variables +✅ **[NEW]** Fully responsive design (mobile-first) +✅ **[NEW]** Improved navigation structure +✅ **[NEW]** Comprehensive documentation suite ## Key URLs - **Main Dashboard**: http://localhost:5000/ +- **Tournament Config**: http://localhost:5000/tournament +- **Draft Management**: http://localhost:5000/draft +- **Results Calculator**: http://localhost:5000/results - **Mobile Interface**: http://localhost:5000/mobile - **Archive**: http://localhost:5000/archive -- **Tournament View**: http://localhost:5000/tournament -- **Results**: http://localhost:5000/results + +## CSS Architecture (NEW) + +The application uses a modular, maintainable CSS structure based on modern CSS best practices: + +### Core CSS Files (in `/static/css/`) + +| File | Purpose | Lines | Size | +|------|---------|-------|------| +| **base.css** | Global reset, CSS variables, typography, utilities | 261 | 5.8 KB | +| **navbar.css** | Navigation bar styling and responsive behavior | 299 | 6.0 KB | +| **buttons.css** | Button system with variants and states | 365 | 7.4 KB | +| **components.css** | Cards, forms, modals, tables, alerts | 458 | 8.9 KB | +| **responsive.css** | Media queries and responsive utilities | 523 | 10 KB | + +**Total**: 1,906 lines across 5 organized files (77% reduction from previous scattered styles) + +### CSS Variables (base.css) + +```css +:root { + /* Colors */ + --primary: #007bff; /* Main brand color */ + --secondary: #6c757d; /* Secondary color */ + --success: #28a745; /* Success state */ + --danger: #dc3545; /* Error state */ + --warning: #ffc107; /* Warning state */ + --info: #17a2b8; /* Info state */ + + /* Spacing */ + --spacing-xs: 0.25rem; + --spacing-sm: 0.5rem; + --spacing-md: 1rem; + --spacing-lg: 1.5rem; + --spacing-xl: 2rem; + + /* Transitions */ + --transition-fast: 0.15s ease; + --transition-normal: 0.2s ease; + --transition-slow: 0.3s ease; + + /* Border Radius */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + + /* Shadows */ + --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 6px 20px rgba(0, 0, 0, 0.15); +} +``` + +### Responsive Breakpoints + +```css +Mobile (max-width: 480px) +Small (480px - 768px) +Tablet/Medium (768px - 992px) +Desktop/Large (992px+) +Ultra-wide (1920px+) +Landscape (max-height: 500px) +``` + +See [CSS_CONSOLIDATION_GUIDE.md](./docs/CSS_CONSOLIDATION_GUIDE.md) for complete component reference and customization guide. ## Dependencies -- Flask 3.0.0 -- Flask-SocketIO 5.3.4 -- python-socketio 5.9.0 -- python-engineio 4.7.1 -- python-dotenv 1.0.0 +### Core +- **Flask 3.0.0** - Web framework +- **python-dotenv 1.0.0** - Environment variables -## Development +### Removed (Optimized) +- ~~Flask-SocketIO~~ - Removed for simplified, faster architecture +- ~~python-socketio~~ - Real-time sync was unreliable +- ~~python-engineio~~ - No longer needed + +## Navigation Structure (IMPROVED) + +The app now uses a simplified, focused navigation structure: + +### Main Navigation +- **Dashboard** - Overview and tournament selection +- **Tournament** - Tournament configuration and settings +- **Draft** - Player draft management +- **Calculator** - Results and scoring +- **Mobile** - Remote access interface + +### Removed from Top Navigation +- ~~Personal Analysis~~ - Moved to tournament context +- ~~Archive~~ - Available via Dashboard + +This streamlined navigation allows users to focus on the core tournament workflow without distractions. + +## Development Notes The app structure is simple and maintainable: - All Flask routes in `tv_app.py` - Support modules in `app/` package - Ready to split into blueprints if needed - -## Notes - -- Real-time WebSocket synchronization is disabled (not working reliably) - Each instance operates independently - All data is saved to JSON files in `data/` folder -- Fully functional and production-ready + +### Recent Improvements + +1. **SocketIO Removal** - Removed WebSocket/SocketIO dependencies for a lighter, more reliable application +2. **CSS Consolidation** - Refactored 8,500+ lines of scattered CSS into 1,906 organized lines across 5 modular files +3. **Navigation Simplification** - Removed redundant navigation items, keeping only essential workflow steps +4. **Documentation** - Created comprehensive documentation suite for development and restructuring + +### Documentation Suite + +The `docs/` folder contains detailed guides: + +- **[COMPLETE_DOCUMENTATION.md](./docs/COMPLETE_DOCUMENTATION.md)** - Master documentation (60 KB, 2,065 lines) + - Current app architecture and analysis + - V2.0 restructure blueprint + - 4-week implementation plan + - API endpoints reference + - Data schema documentation + - Testing strategy + - Migration guide + +- **[CSS_CONSOLIDATION_GUIDE.md](./docs/CSS_CONSOLIDATION_GUIDE.md)** - CSS implementation details + - Component class reference + - CSS variables documentation + - Responsive design guide + - Customization instructions + +- **[APP_RESTRUCTURE_BLUEPRINT.md](./docs/APP_RESTRUCTURE_BLUEPRINT.md)** - Architecture proposal + - Current vs. proposed structure + - New directory organization + - Feature implementation guide + - Module responsibilities + +- **[IMPLEMENTATION_QUICK_START.md](./docs/IMPLEMENTATION_QUICK_START.md)** - Implementation roadmap + - Week-by-week breakdown + - Daily tasks and milestones + - Code examples for each phase + - Testing checklist + +## Performance & Reliability + +- **Lighter Codebase** - Removed unnecessary WebSocket dependencies +- **Better Maintainability** - Organized CSS into logical modules +- **Responsive Design** - Works seamlessly across all device sizes +- **Production-Ready** - Fully functional with JSON-based persistence ## How to Stop Press `Ctrl+C` in the terminal where the app is running. + +--- + +**Last Updated**: November 2025 +**Version**: 1.0.0 with CSS & Navigation Improvements diff --git a/data/camera_settings.json b/data/camera_settings.json index 5fd3f39..af4f44b 100644 --- a/data/camera_settings.json +++ b/data/camera_settings.json @@ -1,6 +1,6 @@ { "camera_titles": { - "1": "Kamera 1", + "1": "Kamera", "2": "Kamera 2", "3": "Kamera 3", "4": "Kamera 4", @@ -9,6 +9,7 @@ }, "display_options": { "show_titles": true, - "title_size": 1.4 + "title_size": 1.2, + "target_number_size": 1.4 } } \ No newline at end of file diff --git a/data/league_archives/league_20251110_124558.json b/data/league_archives/league_20251110_124558.json new file mode 100644 index 0000000..472d117 --- /dev/null +++ b/data/league_archives/league_20251110_124558.json @@ -0,0 +1,1982 @@ +{ + "league": { + "league_id": "league_20251110_122848", + "created_at": "2025-11-10T12:28:48.664614", + "tournament_type": "4_targets", + "total_tournaments": 5, + "current_tournament": 5, + "participants": { + "1": { + "name": "Domen Pleterski", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 94, + "tens_count": 2, + "participated": true + }, + { + "tournament": 2, + "score": 105, + "tens_count": 2, + "participated": true + }, + { + "tournament": 3, + "score": 86, + "tens_count": 0, + "participated": true + }, + { + "tournament": 4, + "score": 67, + "tens_count": 1, + "participated": true + }, + { + "tournament": 5, + "score": 76, + "tens_count": 0, + "participated": true + } + ], + "total_score": 428, + "final_score": 361, + "tournaments_participated": 5 + }, + "2": { + "name": "Nik Pleterski", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 97, + "tens_count": 2, + "participated": true + }, + { + "tournament": 2, + "score": 104, + "tens_count": 2, + "participated": true + }, + { + "tournament": 3, + "score": 83, + "tens_count": 0, + "participated": true + }, + { + "tournament": 4, + "score": 85, + "tens_count": 0, + "participated": true + }, + { + "tournament": 5, + "score": 121, + "tens_count": 3, + "participated": true + } + ], + "total_score": 490, + "final_score": 407, + "tournaments_participated": 5 + }, + "3": { + "name": "Ivan Tandler", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 133, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 105, + "tens_count": 0, + "participated": true + }, + { + "tournament": 3, + "score": 129, + "tens_count": 4, + "participated": true + }, + { + "tournament": 4, + "score": 87, + "tens_count": 2, + "participated": true + }, + { + "tournament": 5, + "score": 90, + "tens_count": 1, + "participated": true + } + ], + "total_score": 544, + "final_score": 457, + "tournaments_participated": 5 + }, + "4": { + "name": "Mateja Pleterski", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 70, + "tens_count": 0, + "participated": true + }, + { + "tournament": 2, + "score": 75, + "tens_count": 1, + "participated": true + }, + { + "tournament": 3, + "score": 95, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 92, + "tens_count": 2, + "participated": true + }, + { + "tournament": 5, + "score": 107, + "tens_count": 3, + "participated": true + } + ], + "total_score": 439, + "final_score": 369, + "tournaments_participated": 5 + }, + "5": { + "name": "Jože Verhnjak", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 93, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 108, + "tens_count": 1, + "participated": true + }, + { + "tournament": 3, + "score": 108, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 98, + "tens_count": 0, + "participated": true + }, + { + "tournament": 5, + "score": 99, + "tens_count": 1, + "participated": true + } + ], + "total_score": 506, + "final_score": 413, + "tournaments_participated": 5 + }, + "6": { + "name": "Mateja Senica", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 88, + "tens_count": 2, + "participated": true + }, + { + "tournament": 2, + "score": 91, + "tens_count": 1, + "participated": true + }, + { + "tournament": 3, + "score": 108, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 88, + "tens_count": 3, + "participated": true + }, + { + "tournament": 5, + "score": 90, + "tens_count": 0, + "participated": true + } + ], + "total_score": 465, + "final_score": 377, + "tournaments_participated": 5 + }, + "7": { + "name": "Branko Pokeržnik", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 101, + "tens_count": 0, + "participated": true + }, + { + "tournament": 2, + "score": 87, + "tens_count": 0, + "participated": true + }, + { + "tournament": 3, + "score": 98, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 93, + "tens_count": 1, + "participated": true + }, + { + "tournament": 5, + "score": 91, + "tens_count": 1, + "participated": true + } + ], + "total_score": 470, + "final_score": 383, + "tournaments_participated": 5 + }, + "8": { + "name": "Franc Žigart", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 97, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 101, + "tens_count": 3, + "participated": true + }, + { + "tournament": 3, + "score": 99, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 108, + "tens_count": 3, + "participated": true + }, + { + "tournament": 5, + "score": 91, + "tens_count": 0, + "participated": true + } + ], + "total_score": 496, + "final_score": 405, + "tournaments_participated": 5 + }, + "9": { + "name": "Janez Božič", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 106, + "tens_count": 4, + "participated": true + }, + { + "tournament": 2, + "score": 103, + "tens_count": 3, + "participated": true + }, + { + "tournament": 3, + "score": 120, + "tens_count": 5, + "participated": true + }, + { + "tournament": 4, + "score": 90, + "tens_count": 2, + "participated": true + }, + { + "tournament": 5, + "score": 71, + "tens_count": 1, + "participated": true + } + ], + "total_score": 490, + "final_score": 419, + "tournaments_participated": 5 + }, + "10": { + "name": "Mitja Čeh", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 100, + "tens_count": 2, + "participated": true + }, + { + "tournament": 2, + "score": 109, + "tens_count": 3, + "participated": true + }, + { + "tournament": 3, + "score": 84, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 127, + "tens_count": 2, + "participated": true + }, + { + "tournament": 5, + "score": 116, + "tens_count": 4, + "participated": true + } + ], + "total_score": 536, + "final_score": 452, + "tournaments_participated": 5 + }, + "11": { + "name": "Rado Kefer", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 87, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 112, + "tens_count": 3, + "participated": true + }, + { + "tournament": 3, + "score": 74, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 108, + "tens_count": 3, + "participated": true + }, + { + "tournament": 5, + "score": 106, + "tens_count": 2, + "participated": true + } + ], + "total_score": 487, + "final_score": 413, + "tournaments_participated": 5 + }, + "12": { + "name": "Matej Kvasnik", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 112, + "tens_count": 4, + "participated": true + }, + { + "tournament": 2, + "score": 110, + "tens_count": 2, + "participated": true + }, + { + "tournament": 3, + "score": 105, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 107, + "tens_count": 2, + "participated": true + }, + { + "tournament": 5, + "score": 53, + "tens_count": 0, + "participated": true + } + ], + "total_score": 487, + "final_score": 434, + "tournaments_participated": 5 + }, + "13": { + "name": "Angelca Mrak", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 100, + "tens_count": 4, + "participated": true + }, + { + "tournament": 2, + "score": 93, + "tens_count": 1, + "participated": true + }, + { + "tournament": 3, + "score": 96, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 73, + "tens_count": 0, + "participated": true + }, + { + "tournament": 5, + "score": 87, + "tens_count": 0, + "participated": true + } + ], + "total_score": 449, + "final_score": 376, + "tournaments_participated": 5 + }, + "14": { + "name": "Karli Proje", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 81, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 72, + "tens_count": 2, + "participated": true + }, + { + "tournament": 3, + "score": 110, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 82, + "tens_count": 1, + "participated": true + }, + { + "tournament": 5, + "score": 99, + "tens_count": 1, + "participated": true + } + ], + "total_score": 444, + "final_score": 372, + "tournaments_participated": 5 + }, + "15": { + "name": "Jan Pleterski", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 111, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 102, + "tens_count": 5, + "participated": true + }, + { + "tournament": 3, + "score": 122, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 127, + "tens_count": 3, + "participated": true + }, + { + "tournament": 5, + "score": 111, + "tens_count": 2, + "participated": true + } + ], + "total_score": 573, + "final_score": 471, + "tournaments_participated": 5 + }, + "16": { + "name": "Silvo Poročnik", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 117, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 95, + "tens_count": 2, + "participated": true + }, + { + "tournament": 3, + "score": 117, + "tens_count": 0, + "participated": true + }, + { + "tournament": 4, + "score": 90, + "tens_count": 2, + "participated": true + }, + { + "tournament": 5, + "score": 109, + "tens_count": 0, + "participated": true + } + ], + "total_score": 528, + "final_score": 438, + "tournaments_participated": 5 + }, + "17": { + "name": "Dušan Onuk", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 102, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 86, + "tens_count": 2, + "participated": true + }, + { + "tournament": 3, + "score": 79, + "tens_count": 0, + "participated": true + }, + { + "tournament": 4, + "score": 89, + "tens_count": 4, + "participated": true + }, + { + "tournament": 5, + "score": 104, + "tens_count": 2, + "participated": true + } + ], + "total_score": 460, + "final_score": 381, + "tournaments_participated": 5 + }, + "18": { + "name": "Matjaž Pleterski", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 117, + "tens_count": 2, + "participated": true + }, + { + "tournament": 2, + "score": 81, + "tens_count": 1, + "participated": true + }, + { + "tournament": 3, + "score": 115, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 103, + "tens_count": 0, + "participated": true + }, + { + "tournament": 5, + "score": 83, + "tens_count": 3, + "participated": true + } + ], + "total_score": 499, + "final_score": 418, + "tournaments_participated": 5 + }, + "19": { + "name": "Franc Rizmal", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 89, + "tens_count": 0, + "participated": true + }, + { + "tournament": 2, + "score": 94, + "tens_count": 0, + "participated": true + }, + { + "tournament": 3, + "score": 81, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 136, + "tens_count": 3, + "participated": true + }, + { + "tournament": 5, + "score": 105, + "tens_count": 2, + "participated": true + } + ], + "total_score": 505, + "final_score": 424, + "tournaments_participated": 5 + }, + "20": { + "name": "Jože Preglav", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 96, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 91, + "tens_count": 0, + "participated": true + }, + { + "tournament": 3, + "score": 101, + "tens_count": 0, + "participated": true + }, + { + "tournament": 4, + "score": 86, + "tens_count": 3, + "participated": true + }, + { + "tournament": 5, + "score": 90, + "tens_count": 1, + "participated": true + } + ], + "total_score": 464, + "final_score": 378, + "tournaments_participated": 5 + }, + "21": { + "name": "Marko Blimen", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 101, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 102, + "tens_count": 2, + "participated": true + }, + { + "tournament": 3, + "score": 101, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 80, + "tens_count": 0, + "participated": true + }, + { + "tournament": 5, + "score": 113, + "tens_count": 4, + "participated": true + } + ], + "total_score": 497, + "final_score": 417, + "tournaments_participated": 5 + }, + "22": { + "name": "Doris Fesel", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 79, + "tens_count": 2, + "participated": true + }, + { + "tournament": 2, + "score": 92, + "tens_count": 2, + "participated": true + }, + { + "tournament": 3, + "score": 123, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 92, + "tens_count": 6, + "participated": true + }, + { + "tournament": 5, + "score": 110, + "tens_count": 0, + "participated": true + } + ], + "total_score": 496, + "final_score": 417, + "tournaments_participated": 5 + }, + "23": { + "name": "Robi Krautberger", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 111, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 104, + "tens_count": 2, + "participated": true + }, + { + "tournament": 3, + "score": 84, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 95, + "tens_count": 4, + "participated": true + }, + { + "tournament": 5, + "score": 109, + "tens_count": 3, + "participated": true + } + ], + "total_score": 503, + "final_score": 419, + "tournaments_participated": 5 + }, + "24": { + "name": "Jože Verdinek", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 95, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 85, + "tens_count": 1, + "participated": true + }, + { + "tournament": 3, + "score": 105, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 77, + "tens_count": 2, + "participated": true + }, + { + "tournament": 5, + "score": 94, + "tens_count": 3, + "participated": true + } + ], + "total_score": 456, + "final_score": 379, + "tournaments_participated": 5 + }, + "25": { + "name": "Andrej Herman", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 106, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 101, + "tens_count": 2, + "participated": true + }, + { + "tournament": 3, + "score": 77, + "tens_count": 0, + "participated": true + }, + { + "tournament": 4, + "score": 124, + "tens_count": 6, + "participated": true + }, + { + "tournament": 5, + "score": 111, + "tens_count": 2, + "participated": true + } + ], + "total_score": 519, + "final_score": 442, + "tournaments_participated": 5 + }, + "26": { + "name": "Jakob Herman", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 76, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 117, + "tens_count": 1, + "participated": true + }, + { + "tournament": 3, + "score": 100, + "tens_count": 3, + "participated": true + }, + { + "tournament": 4, + "score": 125, + "tens_count": 3, + "participated": true + }, + { + "tournament": 5, + "score": 109, + "tens_count": 4, + "participated": true + } + ], + "total_score": 527, + "final_score": 451, + "tournaments_participated": 5 + }, + "27": { + "name": "Janez Mrak", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 109, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 94, + "tens_count": 2, + "participated": true + }, + { + "tournament": 3, + "score": 106, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 87, + "tens_count": 1, + "participated": true + }, + { + "tournament": 5, + "score": 96, + "tens_count": 1, + "participated": true + } + ], + "total_score": 492, + "final_score": 405, + "tournaments_participated": 5 + }, + "28": { + "name": "Anže Kolar", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 108, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 97, + "tens_count": 1, + "participated": true + }, + { + "tournament": 3, + "score": 109, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 121, + "tens_count": 4, + "participated": true + }, + { + "tournament": 5, + "score": 81, + "tens_count": 1, + "participated": true + } + ], + "total_score": 516, + "final_score": 435, + "tournaments_participated": 5 + }, + "29": { + "name": "Alen Kolar", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 130, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 108, + "tens_count": 1, + "participated": true + }, + { + "tournament": 3, + "score": 96, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 92, + "tens_count": 1, + "participated": true + }, + { + "tournament": 5, + "score": 86, + "tens_count": 2, + "participated": true + } + ], + "total_score": 512, + "final_score": 426, + "tournaments_participated": 5 + }, + "30": { + "name": "Maja Hirtl", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 91, + "tens_count": 0, + "participated": true + }, + { + "tournament": 2, + "score": 116, + "tens_count": 1, + "participated": true + }, + { + "tournament": 3, + "score": 93, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 109, + "tens_count": 0, + "participated": true + }, + { + "tournament": 5, + "score": 119, + "tens_count": 3, + "participated": true + } + ], + "total_score": 528, + "final_score": 437, + "tournaments_participated": 5 + }, + "31": { + "name": "Dejan Kučnik", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 100, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 81, + "tens_count": 0, + "participated": true + }, + { + "tournament": 3, + "score": 98, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 90, + "tens_count": 2, + "participated": true + }, + { + "tournament": 5, + "score": 69, + "tens_count": 1, + "participated": true + } + ], + "total_score": 438, + "final_score": 369, + "tournaments_participated": 5 + }, + "32": { + "name": "David Strniša", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 109, + "tens_count": 2, + "participated": true + }, + { + "tournament": 2, + "score": 105, + "tens_count": 1, + "participated": true + }, + { + "tournament": 3, + "score": 102, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 92, + "tens_count": 1, + "participated": true + }, + { + "tournament": 5, + "score": 102, + "tens_count": 2, + "participated": true + } + ], + "total_score": 510, + "final_score": 418, + "tournaments_participated": 5 + }, + "33": { + "name": "Namir Uzunović", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 106, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 117, + "tens_count": 5, + "participated": true + }, + { + "tournament": 3, + "score": 94, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 122, + "tens_count": 4, + "participated": true + }, + { + "tournament": 5, + "score": 109, + "tens_count": 3, + "participated": true + } + ], + "total_score": 548, + "final_score": 454, + "tournaments_participated": 5 + }, + "34": { + "name": "Jože Planinšec", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 109, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 96, + "tens_count": 1, + "participated": true + }, + { + "tournament": 3, + "score": 94, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 75, + "tens_count": 1, + "participated": true + }, + { + "tournament": 5, + "score": 93, + "tens_count": 3, + "participated": true + } + ], + "total_score": 467, + "final_score": 392, + "tournaments_participated": 5 + }, + "35": { + "name": "Vanja Kolar", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 79, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 89, + "tens_count": 1, + "participated": true + }, + { + "tournament": 3, + "score": 102, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 95, + "tens_count": 2, + "participated": true + }, + { + "tournament": 5, + "score": 101, + "tens_count": 5, + "participated": true + } + ], + "total_score": 466, + "final_score": 387, + "tournaments_participated": 5 + }, + "36": { + "name": "Klara Wankmuller", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 96, + "tens_count": 2, + "participated": true + }, + { + "tournament": 2, + "score": 111, + "tens_count": 3, + "participated": true + }, + { + "tournament": 3, + "score": 90, + "tens_count": 4, + "participated": true + }, + { + "tournament": 4, + "score": 111, + "tens_count": 3, + "participated": true + }, + { + "tournament": 5, + "score": 89, + "tens_count": 2, + "participated": true + } + ], + "total_score": 497, + "final_score": 408, + "tournaments_participated": 5 + }, + "37": { + "name": "Milan Stramec", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 123, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 102, + "tens_count": 1, + "participated": true + }, + { + "tournament": 3, + "score": 90, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 97, + "tens_count": 3, + "participated": true + }, + { + "tournament": 5, + "score": 96, + "tens_count": 2, + "participated": true + } + ], + "total_score": 508, + "final_score": 418, + "tournaments_participated": 5 + }, + "38": { + "name": "Bojan Sudar", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 75, + "tens_count": 0, + "participated": true + }, + { + "tournament": 2, + "score": 105, + "tens_count": 3, + "participated": true + }, + { + "tournament": 3, + "score": 101, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 101, + "tens_count": 3, + "participated": true + }, + { + "tournament": 5, + "score": 88, + "tens_count": 1, + "participated": true + } + ], + "total_score": 470, + "final_score": 395, + "tournaments_participated": 5 + }, + "39": { + "name": "Tia Sudar", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 147, + "tens_count": 5, + "participated": true + }, + { + "tournament": 2, + "score": 93, + "tens_count": 0, + "participated": true + }, + { + "tournament": 3, + "score": 96, + "tens_count": 3, + "participated": true + }, + { + "tournament": 4, + "score": 110, + "tens_count": 2, + "participated": true + }, + { + "tournament": 5, + "score": 89, + "tens_count": 0, + "participated": true + } + ], + "total_score": 535, + "final_score": 446, + "tournaments_participated": 5 + }, + "40": { + "name": "Jaka Cvar", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 104, + "tens_count": 2, + "participated": true + }, + { + "tournament": 2, + "score": 95, + "tens_count": 0, + "participated": true + }, + { + "tournament": 3, + "score": 105, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 80, + "tens_count": 2, + "participated": true + }, + { + "tournament": 5, + "score": 103, + "tens_count": 2, + "participated": true + } + ], + "total_score": 487, + "final_score": 407, + "tournaments_participated": 5 + }, + "41": { + "name": "Tadej Štruc", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 89, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 111, + "tens_count": 3, + "participated": true + }, + { + "tournament": 3, + "score": 79, + "tens_count": 0, + "participated": true + }, + { + "tournament": 4, + "score": 103, + "tens_count": 2, + "participated": true + }, + { + "tournament": 5, + "score": 96, + "tens_count": 3, + "participated": true + } + ], + "total_score": 478, + "final_score": 399, + "tournaments_participated": 5 + }, + "42": { + "name": "Jure Glaser", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 94, + "tens_count": 2, + "participated": true + }, + { + "tournament": 2, + "score": 109, + "tens_count": 0, + "participated": true + }, + { + "tournament": 3, + "score": 109, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 82, + "tens_count": 0, + "participated": true + }, + { + "tournament": 5, + "score": 129, + "tens_count": 3, + "participated": true + } + ], + "total_score": 523, + "final_score": 441, + "tournaments_participated": 5 + }, + "43": { + "name": "Marko Pokržnik", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 95, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 105, + "tens_count": 3, + "participated": true + }, + { + "tournament": 3, + "score": 88, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 95, + "tens_count": 0, + "participated": true + }, + { + "tournament": 5, + "score": 128, + "tens_count": 4, + "participated": true + } + ], + "total_score": 511, + "final_score": 423, + "tournaments_participated": 5 + }, + "44": { + "name": "Anka Kačnik", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 100, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 87, + "tens_count": 2, + "participated": true + }, + { + "tournament": 3, + "score": 91, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 107, + "tens_count": 1, + "participated": true + }, + { + "tournament": 5, + "score": 89, + "tens_count": 0, + "participated": true + } + ], + "total_score": 474, + "final_score": 387, + "tournaments_participated": 5 + }, + "45": { + "name": "Lidija Blimen", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 79, + "tens_count": 0, + "participated": true + }, + { + "tournament": 2, + "score": 112, + "tens_count": 4, + "participated": true + }, + { + "tournament": 3, + "score": 123, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 84, + "tens_count": 1, + "participated": true + }, + { + "tournament": 5, + "score": 92, + "tens_count": 2, + "participated": true + } + ], + "total_score": 490, + "final_score": 411, + "tournaments_participated": 5 + }, + "46": { + "name": "Tijana Štumpfl", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 117, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 98, + "tens_count": 2, + "participated": true + }, + { + "tournament": 3, + "score": 106, + "tens_count": 2, + "participated": true + }, + { + "tournament": 4, + "score": 94, + "tens_count": 1, + "participated": true + }, + { + "tournament": 5, + "score": 98, + "tens_count": 2, + "participated": true + } + ], + "total_score": 513, + "final_score": 419, + "tournaments_participated": 5 + }, + "47": { + "name": "Ljuba Mršak", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 121, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 103, + "tens_count": 3, + "participated": true + }, + { + "tournament": 3, + "score": 119, + "tens_count": 4, + "participated": true + }, + { + "tournament": 4, + "score": 78, + "tens_count": 2, + "participated": true + }, + { + "tournament": 5, + "score": 103, + "tens_count": 1, + "participated": true + } + ], + "total_score": 524, + "final_score": 446, + "tournaments_participated": 5 + }, + "48": { + "name": "Janja Salcman", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 85, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 77, + "tens_count": 0, + "participated": true + }, + { + "tournament": 3, + "score": 95, + "tens_count": 1, + "participated": true + }, + { + "tournament": 4, + "score": 103, + "tens_count": 1, + "participated": true + }, + { + "tournament": 5, + "score": 86, + "tens_count": 1, + "participated": true + } + ], + "total_score": 446, + "final_score": 369, + "tournaments_participated": 5 + }, + "49": { + "name": "Jolanda Verhnjak", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 102, + "tens_count": 2, + "participated": true + }, + { + "tournament": 2, + "score": 113, + "tens_count": 2, + "participated": true + }, + { + "tournament": 3, + "score": 127, + "tens_count": 4, + "participated": true + }, + { + "tournament": 4, + "score": 77, + "tens_count": 0, + "participated": true + }, + { + "tournament": 5, + "score": 76, + "tens_count": 1, + "participated": true + } + ], + "total_score": 495, + "final_score": 419, + "tournaments_participated": 5 + } + }, + "completed_tournaments": [ + { + "tournament_number": 1, + "tournament_type": "4_targets", + "finished_at": "2025-11-10T12:30:32.447123", + "results_summary": { + "participants": 49, + "shots_per_participant": 20, + "total_shots": 980, + "format_description": "4 Targets (5 shots each)" + } + }, + { + "tournament_number": 2, + "tournament_type": "4_targets", + "finished_at": "2025-11-10T12:31:30.072754", + "results_summary": { + "participants": 49, + "shots_per_participant": 20, + "total_shots": 980, + "format_description": "4 Targets (5 shots each)" + } + }, + { + "tournament_number": 3, + "tournament_type": "4_targets", + "finished_at": "2025-11-10T12:32:17.739763", + "results_summary": { + "participants": 49, + "shots_per_participant": 20, + "total_shots": 980, + "format_description": "4 Targets (5 shots each)" + } + }, + { + "tournament_number": 4, + "tournament_type": "4_targets", + "finished_at": "2025-11-10T12:32:36.671669", + "results_summary": { + "participants": 49, + "shots_per_participant": 20, + "total_shots": 980, + "format_description": "4 Targets (5 shots each)" + } + }, + { + "tournament_number": 5, + "tournament_type": "4_targets", + "finished_at": "2025-11-10T12:45:58.377969", + "results_summary": { + "participants": 49, + "shots_per_participant": 20, + "total_shots": 980, + "format_description": "4 Targets (5 shots each)" + } + } + ], + "league_finished": true, + "finished_at": "2025-11-10T12:45:58.377972" + }, + "archived_at": "2025-11-10T12:45:58.379146" +} \ No newline at end of file diff --git a/locales/en.json b/locales/en.json index 95b4144..9af4c4c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -24,7 +24,13 @@ "print": "Print", "visible": "Visible", "date": "Date", - "view": "View" + "view": "View", + "search": "Search", + "filter": "Filter", + "type": "Type", + "action": "Action", + "newest_first": "Newest First", + "oldest_first": "Oldest First" }, "navigation": { "dashboard": "Dashboard", @@ -50,9 +56,11 @@ "unavailable": "Unavailable", "show_card_titles": "Show Card Titles", "title_text_size": "Title Text Size", + "target_number_size": "Target Number Size", "language_settings": "Language Settings", "display_options": "Display Options", - "click_to_view_fullscreen": "Click to view fullscreen" + "click_to_view_fullscreen": "Click to view fullscreen", + "shooting_number_size": "Shooting Number Size" }, "tournament": { "tournament": "Tournament", @@ -109,6 +117,7 @@ "league_finished": "League Finished", "start_league": "Start League", "reset_league": "Reset League", + "stop_league": "Stop League", "current_tournament": "Current Tournament", "total_tournaments": "Total Tournaments", "completed_tournaments": "Completed Tournaments", @@ -148,6 +157,8 @@ "enter_player_name": "Enter player name...", "start_league_5_tournaments": "Start League (5 Tournaments)", "start_single_tournament": "Start Single Tournament", + "competing": "Competing", + "not_competing": "Not competing", "id": "ID", "name": "Name", "edit": "Edit", diff --git a/locales/sl.json b/locales/sl.json index 160c28c..ea8d773 100644 --- a/locales/sl.json +++ b/locales/sl.json @@ -24,7 +24,13 @@ "print": "Natisni", "visible": "Vidni", "date": "Datum", - "view": "Oglej" + "view": "Oglej", + "search": "Iskanje", + "filter": "Filtrer", + "type": "Tip", + "action": "Akcija", + "newest_first": "Najnovejši Prvo", + "oldest_first": "Najstarejši Prvo" }, "navigation": { "dashboard": "Nadzorna Plošča", @@ -50,9 +56,11 @@ "unavailable": "Nedostopno", "show_card_titles": "Prikaži Naslove Kartic", "title_text_size": "Velikost Besedila Naslova", + "target_number_size": "Velikost Števila Tarče", "language_settings": "Jezikovne Nastavitve", "display_options": "Možnosti Prikaza", - "click_to_view_fullscreen": "Klikni za celozaslonski prikaz" + "click_to_view_fullscreen": "Klikni za celozaslonski prikaz", + "shooting_number_size": "Velikost Števila Streljanja" }, "tournament": { "tournament": "Turnir", @@ -111,6 +119,7 @@ "league_finished": "Liga Zaključena", "start_league": "Začni Ligo", "reset_league": "Ponastavi Ligo", + "stop_league": "Ustavi Ligo", "current_tournament": "Trenutni Turnir", "total_tournaments": "Skupaj Turnirjev", "completed_tournaments": "Zaključeni Turnirji", @@ -154,6 +163,8 @@ "enter_player_name": "Vnesite ime igralca...", "start_league_5_tournaments": "Začni Ligo (5 Turnirjev)", "start_single_tournament": "Začni Posamezen Turnir", + "competing": "Tekmuje", + "not_competing": "Ne tekmuje", "id": "ID", "name": "Ime", "edit": "Uredi", diff --git a/static/css/base.css b/static/css/base.css new file mode 100644 index 0000000..161405d --- /dev/null +++ b/static/css/base.css @@ -0,0 +1,262 @@ +/* ============================================================================ + TV_APP - Base Styles & Global Reset + Shared across all templates + ============================================================================ */ + +/* Reset & Global Styles */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html, body { + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; + font-size: 16px; + line-height: 1.6; + background: #f5f5f5; + color: #333; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* ============================================================================ + CSS Variables - Color Palette & Utilities + ============================================================================ */ + +:root { + /* Colors */ + --primary: #007bff; + --secondary: #6c757d; + --success: #28a745; + --danger: #dc3545; + --warning: #ffc107; + --info: #17a2b8; + + /* Grays */ + --white: #ffffff; + --light: #f8f9fa; + --lighter: #e9ecef; + --border: #dee2e6; + --text: #333333; + --text-muted: #666666; + + /* Spacing */ + --spacing-xs: 0.25rem; + --spacing-sm: 0.5rem; + --spacing-md: 1rem; + --spacing-lg: 1.5rem; + --spacing-xl: 2rem; + + /* Transitions */ + --transition-fast: 0.15s ease; + --transition-normal: 0.2s ease; + --transition-slow: 0.3s ease; + + /* Border Radius */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + + /* Shadows */ + --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 6px 20px rgba(0, 0, 0, 0.15); + --shadow-xl: 0 8px 30px rgba(0, 0, 0, 0.2); + + /* Z-Index Scale */ + --z-dropdown: 100; + --z-sticky: 200; + --z-fixed: 300; + --z-modal-backdrop: 400; + --z-modal: 500; + --z-popover: 600; + --z-tooltip: 700; +} + +/* ============================================================================ + Typography + ============================================================================ */ + +h1, h2, h3, h4, h5, h6 { + font-weight: 600; + line-height: 1.3; + margin-bottom: var(--spacing-md); + color: var(--text); +} + +h1 { font-size: 2rem; } +h2 { font-size: 1.75rem; } +h3 { font-size: 1.5rem; } +h4 { font-size: 1.25rem; } +h5 { font-size: 1.1rem; } +h6 { font-size: 1rem; } + +p { + margin-bottom: var(--spacing-md); +} + +a { + color: var(--primary); + text-decoration: none; + transition: color var(--transition-normal); +} + +a:hover { + color: #0056b3; + text-decoration: underline; +} + +/* ============================================================================ + Utility Classes + ============================================================================ */ + +/* Display */ +.hidden { display: none !important; } +.invisible { visibility: hidden !important; } +.block { display: block; } +.inline { display: inline; } +.inline-block { display: inline-block; } + +/* Flex & Grid */ +.flex { display: flex; } +.grid { display: grid; } + +/* Spacing */ +.mt-0 { margin-top: 0; } +.mt-1 { margin-top: var(--spacing-xs); } +.mt-2 { margin-top: var(--spacing-sm); } +.mt-3 { margin-top: var(--spacing-md); } +.mt-4 { margin-top: var(--spacing-lg); } + +.mb-0 { margin-bottom: 0; } +.mb-1 { margin-bottom: var(--spacing-xs); } +.mb-2 { margin-bottom: var(--spacing-sm); } +.mb-3 { margin-bottom: var(--spacing-md); } +.mb-4 { margin-bottom: var(--spacing-lg); } + +.p-1 { padding: var(--spacing-xs); } +.p-2 { padding: var(--spacing-sm); } +.p-3 { padding: var(--spacing-md); } +.p-4 { padding: var(--spacing-lg); } + +/* Text Alignment */ +.text-left { text-align: left; } +.text-center { text-align: center; } +.text-right { text-align: right; } + +/* Text Colors */ +.text-muted { color: var(--text-muted); } +.text-primary { color: var(--primary); } +.text-success { color: var(--success); } +.text-danger { color: var(--danger); } +.text-warning { color: var(--warning); } + +/* ============================================================================ + Shadow Utilities + ============================================================================ */ + +.shadow-sm { + box-shadow: var(--shadow-sm); +} + +.shadow { + box-shadow: var(--shadow-md); +} + +.shadow-lg { + box-shadow: var(--shadow-lg); +} + +.shadow-xl { + box-shadow: var(--shadow-xl); +} + +/* ============================================================================ + Border Radius Utilities + ============================================================================ */ + +.rounded-sm { + border-radius: var(--radius-sm); +} + +.rounded { + border-radius: var(--radius-md); +} + +.rounded-lg { + border-radius: var(--radius-lg); +} + +.rounded-full { + border-radius: 9999px; +} + +/* ============================================================================ + Container + ============================================================================ */ + +.container { + width: 100%; + margin-left: auto; + margin-right: auto; + margin-top: 15px; + padding-left: 20px; + padding-right: 20px; +} + +@media (min-width: 768px) { + .container { + max-width: 750px; + } +} + +@media (min-width: 992px) { + .container { + max-width: 970px; + } +} + +@media (min-width: 1200px) { + .container { + max-width: 1170px; + } +} + +/* ============================================================================ + Scrollbar Styling + ============================================================================ */ + +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--lighter); +} + +::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--text-muted); +} + +/* ============================================================================ + Selection + ============================================================================ */ + +::selection { + background: var(--primary); + color: white; +} + +::-moz-selection { + background: var(--primary); + color: white; +} diff --git a/static/css/buttons.css b/static/css/buttons.css new file mode 100644 index 0000000..41e2a0b --- /dev/null +++ b/static/css/buttons.css @@ -0,0 +1,364 @@ +/* ============================================================================ + TV_APP - Button Styles + All button variants used across templates + ============================================================================ */ + +/* ============================================================================ + Base Button Styles + ============================================================================ */ + +.btn, +.action-btn, +.control-btn, +.zoom-btn, +button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--spacing-sm); + cursor: pointer; + border: 2px solid transparent; + border-radius: var(--radius-lg); + padding: 8px 12px; + font-size: 0.9rem; + font-weight: 500; + transition: all var(--transition-normal); + text-decoration: none; + white-space: nowrap; + user-select: none; + vertical-align: middle; +} + +button:not([class]) { + background: #f8f9fa; + border-color: #e9ecef; + color: var(--text); + box-shadow: var(--shadow-sm); +} + +button:not([class]):hover { + background: #e9ecef; + border-color: var(--primary); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + transform: translateY(-1px); + color: var(--primary); +} + +/* ============================================================================ + Action Buttons + ============================================================================ */ + +.action-btn, +.control-btn, +.zoom-btn { + background: #f8f9fa; + border: 2px solid #e9ecef; + color: var(--text); + box-shadow: var(--shadow-sm); +} + +.action-btn:hover, +.control-btn:hover, +.zoom-btn:hover { + background: #e9ecef; + border-color: var(--primary); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + transform: translateY(-1px); + color: var(--primary); +} + +.action-btn:active, +.control-btn:active, +.zoom-btn:active { + transform: translateY(0); + box-shadow: var(--shadow-sm); +} + +.action-btn:disabled, +.control-btn:disabled, +.zoom-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none !important; +} + +/* ============================================================================ + Button Sizes + ============================================================================ */ + +.btn-sm { + padding: 8px 12px; + font-size: 0.85rem; +} + +.btn-lg { + padding: 14px 24px; + font-size: 1.05rem; +} + +.btn-xl { + padding: 16px 32px; + font-size: 1.1rem; +} + +/* ============================================================================ + Button Variants - Colors + ============================================================================ */ + +/* Primary Button */ +.btn-primary, +.action-btn.primary { + background: var(--primary); + border-color: var(--primary); + color: white; + box-shadow: var(--shadow-md); +} + +.btn-primary:hover, +.action-btn.primary:hover { + background: #0056b3; + border-color: #0056b3; + box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3); +} + +.btn-primary:active, +.action-btn.primary:active { + background: #004085; + box-shadow: var(--shadow-sm); +} + +/* Secondary Button */ +.btn-secondary, +.action-btn.secondary { + background: var(--secondary); + border-color: var(--secondary); + color: white; + box-shadow: var(--shadow-md); +} + +.btn-secondary:hover, +.action-btn.secondary:hover { + background: #545b62; + border-color: #545b62; + box-shadow: 0 4px 12px rgba(108, 117, 125, 0.3); +} + +/* Success Button */ +.btn-success, +.action-btn.success { + background: var(--success); + border-color: var(--success); + color: white; + box-shadow: var(--shadow-md); +} + +.btn-success:hover, +.action-btn.success:hover { + background: #218838; + border-color: #218838; + box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3); +} + +/* Danger Button */ +.btn-danger, +.action-btn.danger { + background: var(--danger); + border-color: var(--danger); + color: white; + box-shadow: var(--shadow-md); +} + +.btn-danger:hover, +.action-btn.danger:hover { + background: #c82333; + border-color: #c82333; + box-shadow: 0 4px 12px rgba(220, 53, 69, 0.3); +} + +/* Warning Button */ +.btn-warning, +.action-btn.warning { + background: var(--warning); + border-color: var(--warning); + color: #333; + box-shadow: var(--shadow-md); +} + +.btn-warning:hover, +.action-btn.warning:hover { + background: #e0a800; + border-color: #e0a800; + box-shadow: 0 4px 12px rgba(255, 193, 7, 0.3); +} + +/* Info Button */ +.btn-info, +.action-btn.info { + background: var(--info); + border-color: var(--info); + color: white; + box-shadow: var(--shadow-md); +} + +.btn-info:hover, +.action-btn.info:hover { + background: #138496; + border-color: #138496; + box-shadow: 0 4px 12px rgba(23, 162, 184, 0.3); +} + +/* ============================================================================ + Button States + ============================================================================ */ + +.btn:disabled, +.btn-primary:disabled, +.btn-secondary:disabled, +.btn-success:disabled, +.btn-danger:disabled, +button:disabled { + opacity: 0.65; + cursor: not-allowed; + transform: none !important; + box-shadow: var(--shadow-sm); +} + +/* ============================================================================ + Button Groups + ============================================================================ */ + +.btn-group { + display: flex; + gap: var(--spacing-sm); + flex-wrap: wrap; +} + +.btn-group.vertical { + flex-direction: column; +} + +/* ============================================================================ + Special Button Styles + ============================================================================ */ + +/* Icon Buttons */ +.btn-icon { + width: 44px; + height: 44px; + padding: 0; + border-radius: var(--radius-md); + font-size: 1.25rem; +} + +.btn-icon:hover { + background: var(--primary); + color: white; + border-color: var(--primary); +} + +/* Toggle Button */ +.btn-toggle { + background: #f8f9fa; + border: 2px solid #e9ecef; + color: var(--text); +} + +.btn-toggle.active { + background: var(--primary); + border-color: var(--primary); + color: white; +} + +/* Loading State */ +.btn.loading { + pointer-events: none; + opacity: 0.7; + position: relative; +} + +.btn.loading::after { + content: ''; + position: absolute; + width: 16px; + height: 16px; + top: 50%; + left: 50%; + margin-left: -8px; + margin-top: -8px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top-color: white; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* ============================================================================ + Link Buttons + ============================================================================ */ + +a.btn, +a.action-btn, +a.nav-btn { + display: inline-flex; + align-items: center; + justify-content: center; +} + +a.btn:hover { + text-decoration: none; +} + +/* ============================================================================ + Responsive Button Styles + ============================================================================ */ + +@media (max-width: 768px) { + .btn, + .action-btn, + .control-btn { + padding: 10px 15px; + font-size: 0.9rem; + } + + .btn-icon { + width: 40px; + height: 40px; + } + + .btn-group { + gap: var(--spacing-xs); + } +} + +@media (max-width: 480px) { + .btn, + .action-btn, + .control-btn { + padding: 8px 12px; + font-size: 0.85rem; + } + + .btn-group { + width: 100%; + } + + .btn-group > .btn { + flex: 1; + } +} + +@media (max-height: 500px) and (orientation: landscape) { + .btn, + .action-btn { + padding: 6px 12px; + font-size: 0.8rem; + } + + .btn-icon { + width: 36px; + height: 36px; + } +} diff --git a/static/css/components.css b/static/css/components.css new file mode 100644 index 0000000..7f8246d --- /dev/null +++ b/static/css/components.css @@ -0,0 +1,458 @@ +/* ============================================================================ + TV_APP - Component Styles + Reusable components used across templates + ============================================================================ */ + +/* ============================================================================ + Cards + ============================================================================ */ + +.card, +.section, +.archive-card, +.tournament-info, +.league-info { + background: white; + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); + padding: 20px; + transition: all var(--transition-normal); + margin-bottom: var(--spacing-lg); +} + +.card:hover, +.archive-card:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.card-header { + border-bottom: 1px solid var(--border); + padding-bottom: var(--spacing-md); + margin-bottom: var(--spacing-md); + display: flex; + justify-content: space-between; + align-items: center; +} + +.card-title { + font-size: 1.3rem; + font-weight: 600; + color: var(--text); + margin: 0; +} + +.card-subtitle { + font-size: 0.9rem; + color: var(--text-muted); + margin: 0; +} + +.card-body { + padding: 0; +} + +.card-footer { + border-top: 1px solid var(--border); + padding-top: var(--spacing-md); + margin-top: var(--spacing-md); + display: flex; + justify-content: flex-end; + gap: var(--spacing-md); +} + +/* ============================================================================ + Status Badges + ============================================================================ */ + +.status-badge, +.badge { + display: inline-block; + padding: 4px 12px; + border-radius: 12px; + font-size: 0.85rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* Badge Colors */ +.badge-primary { background: #cfe2ff; color: #084298; } +.badge-secondary { background: #e2e3e5; color: #41464b; } +.badge-success { background: #d1e7dd; color: #0f5132; } +.badge-danger { background: #f8d7da; color: #842029; } +.badge-warning { background: #fff3cd; color: #664d03; } +.badge-info { background: #cff4fc; color: #055160; } +.badge-light { background: #f8f9fa; color: #141619; } +.badge-dark { background: #e2e3e5; color: #141619; } + +/* Status-specific badges */ +.status-enabled { background: #d4edda; color: #155724; } +.status-disabled { background: #f8d7da; color: #721c24; } +.status-active { background: #d1ecf1; color: #0c5460; } +.status-inactive { background: #e2e3e5; color: #383d41; } +.status-pending { background: #fff3cd; color: #856404; } +.status-completed { background: #d4edda; color: #155724; } + +/* ============================================================================ + Forms & Inputs + ============================================================================ */ + +.form-group { + margin-bottom: var(--spacing-lg); +} + +.form-group label { + display: block; + margin-bottom: var(--spacing-sm); + font-weight: 600; + color: var(--text); +} + +.form-group small { + display: block; + margin-top: var(--spacing-xs); + color: var(--text-muted); + font-size: 0.85rem; +} + +/* Text Inputs */ +.form-input, +.search-input, +.camera-input, +input[type="text"], +input[type="email"], +input[type="number"], +input[type="password"], +select, +textarea { + width: 100%; + padding: 12px 15px; + border: 2px solid var(--border); + border-radius: var(--radius-md); + background: white; + color: var(--text); + font-size: 0.95rem; + font-family: inherit; + transition: all var(--transition-normal); + box-shadow: var(--shadow-sm); +} + +.form-input:focus, +.search-input:focus, +.camera-input:focus, +input:focus, +select:focus, +textarea:focus { + border-color: var(--primary); + box-shadow: 0 5px 12px rgba(0, 123, 255, 0.15); + transform: translateY(-1px); + outline: none; +} + +.form-input:disabled, +input:disabled, +select:disabled, +textarea:disabled { + background: #f8f9fa; + color: var(--text-muted); + cursor: not-allowed; + opacity: 0.6; +} + +/* Error State */ +.form-input.error, +input.error, +select.error, +textarea.error { + border-color: var(--danger); + background: #fff5f5; +} + +.form-input.error:focus, +input.error:focus, +select.error:focus, +textarea.error:focus { + box-shadow: 0 5px 12px rgba(220, 53, 69, 0.15); +} + +/* Success State */ +.form-input.success, +input.success, +select.success, +textarea.success { + border-color: var(--success); + background: #f0fdf4; +} + +.form-input.success:focus, +input.success:focus, +select.success:focus, +textarea.success:focus { + box-shadow: 0 5px 12px rgba(40, 167, 69, 0.15); +} + +/* Form Error Message */ +.error-message { + color: var(--danger); + font-size: 0.85rem; + margin-top: var(--spacing-xs); + display: block; +} + +/* ============================================================================ + Modals & Overlays + ============================================================================ */ + +.modal-overlay, +.modal-backdrop { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: var(--z-modal-backdrop); + opacity: 0; + visibility: hidden; + transition: all var(--transition-normal); +} + +.modal-overlay.active, +.modal-backdrop.active { + opacity: 1; + visibility: visible; +} + +.modal, +.modal-content { + background: white; + border-radius: var(--radius-lg); + box-shadow: var(--shadow-xl); + max-width: 500px; + width: 90%; + max-height: 90vh; + overflow-y: auto; + animation: slideUp var(--transition-slow) ease; + z-index: var(--z-modal); +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.modal-header { + padding: 20px; + border-bottom: 1px solid var(--border); + display: flex; + justify-content: space-between; + align-items: center; +} + +.modal-header h2 { + margin: 0; + font-size: 1.3rem; +} + +.modal-close { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: var(--text-muted); + padding: 0; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + transition: all var(--transition-normal); +} + +.modal-close:hover { + color: var(--text); + background: var(--lighter); + border-radius: var(--radius-md); +} + +.modal-body { + padding: 20px; +} + +.modal-footer { + padding: 20px; + border-top: 1px solid var(--border); + display: flex; + justify-content: flex-end; + gap: var(--spacing-md); +} + +/* ============================================================================ + Tables + ============================================================================ */ + +.table { + width: 100%; + border-collapse: collapse; + background: white; + border-radius: var(--radius-lg); + overflow: hidden; + box-shadow: var(--shadow-md); +} + +.table thead { + background: #f8f9fa; + border-bottom: 2px solid var(--border); + position: sticky; + top: 0; + z-index: 10; +} + +.table th { + padding: 12px 15px; + text-align: left; + font-weight: 600; + color: var(--text); + white-space: nowrap; +} + +.table td { + padding: 12px 15px; + border-bottom: 1px solid var(--border); + color: var(--text); +} + +.table tbody tr:hover { + background: #f8f9fa; +} + +.table tbody tr:last-child td { + border-bottom: none; +} + +/* Striped Table */ +.table.striped tbody tr:nth-child(odd) { + background: #f8f9fa; +} + +/* ============================================================================ + Alerts + ============================================================================ */ + +.alert { + padding: 12px 15px; + border-radius: var(--radius-md); + border-left: 4px solid; + margin-bottom: var(--spacing-lg); + display: flex; + align-items: center; + gap: var(--spacing-md); +} + +.alert-primary { + background: #cfe2ff; + border-color: #084298; + color: #084298; +} + +.alert-success { + background: #d1e7dd; + border-color: #0f5132; + color: #0f5132; +} + +.alert-danger { + background: #f8d7da; + border-color: #842029; + color: #842029; +} + +.alert-warning { + background: #fff3cd; + border-color: #664d03; + color: #664d03; +} + +.alert-info { + background: #cff4fc; + border-color: #055160; + color: #055160; +} + +.alert-close { + background: none; + border: none; + cursor: pointer; + font-size: 1.2rem; + margin-left: auto; +} + +/* ============================================================================ + Responsive Components + ============================================================================ */ + +@media (max-width: 768px) { + .card, + .section, + .archive-card { + padding: 15px; + } + + .modal, + .modal-content { + max-width: 95%; + width: 95%; + } + + .table { + font-size: 0.9rem; + } + + .table th, + .table td { + padding: 10px 12px; + } +} + +@media (max-width: 480px) { + .card, + .section { + padding: 12px; + } + + .card-header { + flex-direction: column; + align-items: flex-start; + gap: var(--spacing-md); + } + + .card-footer { + flex-direction: column; + } + + .card-footer .btn { + width: 100%; + } + + .modal-footer { + flex-direction: column; + } + + .modal-footer .btn { + width: 100%; + } + + .form-group { + margin-bottom: var(--spacing-md); + } +} diff --git a/static/css/navbar.css b/static/css/navbar.css new file mode 100644 index 0000000..aa85a28 --- /dev/null +++ b/static/css/navbar.css @@ -0,0 +1,301 @@ +/* ============================================================================ + TV_APP - Navbar Styles + Navigation bar used across all pages + ============================================================================ */ + +.navbar { + background: white; + color: black; + padding: 8px 25px; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 2px solid #ccc; + height: auto; + min-height: 50px; + box-sizing: border-box; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + position: sticky; + top: 0; + z-index: var(--z-sticky); +} + +/* ============================================================================ + Navbar Sections + ============================================================================ */ + +.navbar-brand { + display: flex; + align-items: center; + gap: var(--spacing-md); + flex-shrink: 0; +} + +.navbar-brand img, +.logo { + height: 40px; + width: auto; + max-width: 160px; + object-fit: contain; + margin-right: var(--spacing-md); +} + +.navbar-title { + font-size: 1.3rem; + font-weight: 600; + color: var(--text); + white-space: nowrap; +} + +.navbar-center { + position: absolute; + left: 50%; + transform: translateX(-50%); + text-align: center; + flex-grow: 1; +} + +.navbar-controls { + display: flex; + gap: var(--spacing-md); + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; +} + +/* ============================================================================ + Navigation Buttons + ============================================================================ */ + +.nav-btn { + background: #f8f9fa; + border: 2px solid #e9ecef; + cursor: pointer; + padding: 12px 20px; + border-radius: 8px; + transition: all 0.2s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + color: #333; + text-decoration: none; + font-weight: bold; + font-size: 0.9rem; + white-space: nowrap; + display: inline-flex; + align-items: center; + gap: var(--spacing-sm); + line-height: 1; + height: 44px; +} + +.nav-btn:hover { + background: #e9ecef; + border-color: #007bff; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + transform: translateY(-1px); + color: #007bff; + text-decoration: none; +} + +.nav-btn:active { + transform: translateY(0); + box-shadow: var(--shadow-sm); +} + +.nav-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none !important; +} + +/* ============================================================================ + Button Variants + ============================================================================ */ + +.nav-btn.active { + background: #007bff; + border-color: #0056b3; + color: white; +} + +.nav-btn.active:hover { + background: #0056b3; + color: white; +} + +.nav-btn.secondary { + background: var(--secondary); + border-color: var(--secondary); + color: white; +} + +.nav-btn.secondary:hover { + background: #545b62; + border-color: #545b62; +} + +.nav-btn.success { + background: var(--success); + border-color: var(--success); + color: white; +} + +.nav-btn.success:hover { + background: #218838; + border-color: #218838; +} + +.nav-btn.danger { + background: #dc3545; + border-color: #c82333; + color: white; +} + +.nav-btn.danger:hover { + background: #c82333; + color: white; +} + +/* ============================================================================ + Hamburger Menu (Mobile) + ============================================================================ */ + +.hamburger-menu { + background: #f8f9fa; + border: 2px solid #e9ecef; + cursor: pointer; + padding: 10px; + flex-direction: column; + justify-content: space-around; + width: 42px; + height: 42px; + border-radius: 8px; + transition: all 0.2s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + display: flex; +} + +.hamburger-menu:hover { + background: #e9ecef; + border-color: #007bff; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + transform: translateY(-1px); +} + +.hamburger-line { + width: 100%; + height: 2px; + background: #333; + border-radius: 1px; + transition: all 0.2s ease; +} + +.hamburger-menu:hover .hamburger-line { + background: #007bff; +} + +/* ============================================================================ + Settings/Actions in Navbar + ============================================================================ */ + +.nav-icon { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: var(--radius-md); + background: #f8f9fa; + border: none; + cursor: pointer; + font-size: 1.2rem; + transition: all var(--transition-normal); +} + +.nav-icon:hover { + background: var(--primary); + color: white; +} + +/* ============================================================================ + Status Display (Tournament/League Info) + ============================================================================ */ + +.navbar-status { + padding: 8px 12px; + background: #f0f8ff; + border: 1px solid var(--primary); + border-radius: var(--radius-md); + font-size: 0.9rem; + color: var(--text); +} + +.navbar-status strong { + color: var(--primary); +} + +/* ============================================================================ + Responsive Navbar + ============================================================================ */ + +@media (max-width: 768px) { + .navbar { + padding: 12px 15px; + flex-direction: column; + gap: var(--spacing-md); + height: auto; + } + + .navbar-brand { + width: 100%; + justify-content: center; + } + + .navbar-center { + position: static; + transform: none; + width: 100%; + order: 3; + margin-top: var(--spacing-sm); + } + + .navbar-controls { + justify-content: center; + width: 100%; + } + + .nav-btn { + padding: 10px 15px; + font-size: 0.85rem; + } +} + +@media (max-width: 480px) { + .navbar { + padding: 8px 10px; + } + + .navbar-title { + font-size: 1rem; + } + + .nav-btn { + padding: 8px 12px; + font-size: 0.75rem; + } +} + +@media (max-height: 500px) and (orientation: landscape) { + .navbar { + padding: 8px 15px; + height: 50px; + } + + .navbar-title { + font-size: 1rem; + } + + .nav-btn { + padding: 6px 12px; + font-size: 0.8rem; + } +} diff --git a/static/css/responsive.css b/static/css/responsive.css new file mode 100644 index 0000000..2952228 --- /dev/null +++ b/static/css/responsive.css @@ -0,0 +1,523 @@ +/* ============================================================================ + TV_APP - Responsive Design System + Media queries and responsive utilities + ============================================================================ */ + +/* ============================================================================ + Breakpoints + ============================================================================ + Mobile First Approach: + - Base: Mobile (< 480px) + - Small: 480px - 768px + - Medium: 768px - 992px + - Large: 992px+ + ============================================================================ */ + +/* ============================================================================ + Extra Small Devices (Mobile) - max-width: 480px + ============================================================================ */ + +@media (max-width: 480px) { + /* Typography */ + h1 { font-size: 1.5rem; } + h2 { font-size: 1.3rem; } + h3 { font-size: 1.1rem; } + h4 { font-size: 1rem; } + + /* Navbar */ + .navbar { + padding: 8px 12px; + height: auto; + flex-direction: column; + gap: var(--spacing-sm); + } + + .navbar-brand { + width: 100%; + justify-content: center; + } + + .navbar-brand img { + height: 35px; + } + + .navbar-title { + font-size: 1rem; + } + + .navbar-center { + position: static; + transform: none; + width: 100%; + margin-top: var(--spacing-xs); + } + + .navbar-controls { + width: 100%; + justify-content: center; + } + + .nav-btn { + padding: 8px 12px; + font-size: 0.75rem; + } + + /* Buttons */ + .btn, + .action-btn, + .control-btn { + padding: 8px 12px; + font-size: 0.85rem; + width: 100%; + } + + .btn-icon { + width: 36px; + height: 36px; + } + + .btn-group { + width: 100%; + flex-direction: column; + } + + .btn-group > .btn { + width: 100%; + } + + /* Cards */ + .card, + .section, + .archive-card { + padding: 12px; + margin-bottom: var(--spacing-md); + } + + .card-header { + flex-direction: column; + align-items: flex-start; + gap: var(--spacing-md); + } + + .card-title { + font-size: 1.1rem; + } + + .card-footer { + flex-direction: column; + } + + .card-footer .btn { + width: 100%; + } + + /* Forms */ + .form-group { + margin-bottom: var(--spacing-md); + } + + .form-input, + .search-input, + input, + select, + textarea { + padding: 10px 12px; + font-size: 16px; /* Prevents zoom on iOS */ + } + + /* Modals */ + .modal, + .modal-content { + max-width: 95%; + width: 95%; + max-height: 95vh; + } + + .modal-header { + padding: 12px; + } + + .modal-body { + padding: 12px; + } + + .modal-footer { + padding: 12px; + flex-direction: column; + } + + .modal-footer .btn { + width: 100%; + } + + /* Tables */ + .table { + font-size: 0.85rem; + } + + .table th, + .table td { + padding: 8px 10px; + } + + /* Grids */ + .grid { + grid-template-columns: 1fr !important; + } + + /* Utilities */ + .hide-mobile { + display: none !important; + } + + /* Spacing */ + .mt-4 { margin-top: var(--spacing-lg); } + .mb-4 { margin-bottom: var(--spacing-lg); } +} + +/* ============================================================================ + Small Devices (Small Mobile/Tablet) - 480px to 768px + ============================================================================ */ + +@media (min-width: 480px) and (max-width: 768px) { + /* Typography */ + h1 { font-size: 1.6rem; } + h2 { font-size: 1.4rem; } + h3 { font-size: 1.2rem; } + + /* Navbar */ + .navbar { + padding: 12px 18px; + height: auto; + flex-direction: column; + gap: var(--spacing-sm); + } + + .navbar-title { + font-size: 1.1rem; + } + + .navbar-center { + position: static; + transform: none; + width: 100%; + order: 3; + } + + .nav-btn { + padding: 10px 15px; + font-size: 0.85rem; + } + + /* Buttons */ + .btn, + .action-btn { + padding: 10px 15px; + font-size: 0.9rem; + } + + .btn-icon { + width: 40px; + height: 40px; + } + + /* Cards */ + .card, + .section { + padding: 15px; + margin-bottom: var(--spacing-lg); + } + + /* Forms */ + .form-group { + margin-bottom: var(--spacing-lg); + } + + .form-input, + input, + select, + textarea { + font-size: 16px; /* Prevents zoom on iOS */ + } + + /* Grids */ + .grid { + grid-template-columns: repeat(2, 1fr) !important; + } + + /* Utilities */ + .hide-sm { + display: none !important; + } +} + +/* ============================================================================ + Medium Devices (Tablets) - 768px to 992px + ============================================================================ */ + +@media (min-width: 768px) and (max-width: 992px) { + /* Navbar */ + .navbar { + padding: 12px 20px; + } + + /* Buttons */ + .btn, + .action-btn { + padding: 11px 17px; + font-size: 0.92rem; + } + + /* Grids */ + .grid { + grid-template-columns: repeat(2, 1fr) !important; + } + + /* Utilities */ + .hide-md { + display: none !important; + } +} + +/* ============================================================================ + Large Devices (Desktop) - 992px+ + ============================================================================ */ + +@media (min-width: 992px) { + /* Utilities */ + .hide-lg { + display: none !important; + } +} + +/* ============================================================================ + Landscape Mobile - max-height: 500px with landscape orientation + ============================================================================ */ + +@media (max-height: 500px) and (orientation: landscape) { + /* Navbar */ + .navbar { + padding: 8px 12px; + height: 50px; + } + + .navbar-title { + font-size: 0.95rem; + } + + /* Buttons */ + .btn, + .action-btn, + .nav-btn { + padding: 6px 12px; + font-size: 0.8rem; + } + + .btn-icon { + width: 36px; + height: 36px; + } + + /* Cards */ + .card { + padding: 12px; + margin-bottom: var(--spacing-md); + } + + /* Forms */ + .form-group { + margin-bottom: var(--spacing-md); + } + + .form-input, + input { + padding: 8px 12px; + font-size: 0.9rem; + } + + /* Tables */ + .table { + font-size: 0.85rem; + } + + .table th, + .table td { + padding: 6px 10px; + } + + /* Hide less important elements */ + .navbar-status { + font-size: 0.8rem; + padding: 4px 8px; + } +} + +/* ============================================================================ + Portrait Orientation - max-width: 600px + ============================================================================ */ + +@media (max-width: 600px) and (orientation: portrait) { + /* Optimize for portrait */ + body { + overflow-x: hidden; + } + + .container { + padding-left: 12px; + padding-right: 12px; + } +} + +/* ============================================================================ + High Resolution Displays (Retina - min-width: 1920px) + ============================================================================ */ + +@media (min-width: 1920px) { + /* Typography */ + h1 { font-size: 2.5rem; } + h2 { font-size: 2rem; } + + /* Buttons */ + .btn, + .action-btn { + padding: 13px 20px; + font-size: 1rem; + } + + /* Cards */ + .card { + padding: 25px; + } + + /* Container max-width */ + .container { + max-width: 1800px; + } +} + +/* ============================================================================ + Print Media + ============================================================================ */ + +@media print { + /* Hide interactive elements */ + .navbar, + .btn, + .action-btn, + button, + .modal, + .modal-overlay, + .alert-close, + .modal-close { + display: none !important; + } + + /* Optimize for printing */ + body { + background: white; + color: black; + } + + .card, + .section { + page-break-inside: avoid; + box-shadow: none; + border: 1px solid #ccc; + } + + .table { + border-collapse: collapse; + } + + .table th, + .table td { + border: 1px solid #ccc; + } + + a { + color: black; + text-decoration: none; + } + + a[href]::after { + content: " (" attr(href) ")"; + font-size: 0.8rem; + } +} + +/* ============================================================================ + Reduced Motion (Accessibility) + ============================================================================ */ + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} + +/* ============================================================================ + Dark Mode (if user preference exists) + ============================================================================ */ + +@media (prefers-color-scheme: dark) { + /* Optional: Add dark mode support here */ + /* This requires additional CSS variables or overrides */ +} + +/* ============================================================================ + Utility Classes for Responsive Control + ============================================================================ */ + +.show-mobile { display: none; } +@media (max-width: 768px) { + .show-mobile { display: block; } + .hide-mobile { display: none !important; } +} + +.show-tablet { display: none; } +@media (min-width: 768px) and (max-width: 1024px) { + .show-tablet { display: block; } + .hide-tablet { display: none !important; } +} + +.show-desktop { display: none; } +@media (min-width: 1024px) { + .show-desktop { display: block; } + .hide-desktop { display: none !important; } +} + +/* ============================================================================ + Flex Responsive + ============================================================================ */ + +.flex-responsive { + display: flex; + flex-direction: row; + gap: var(--spacing-md); +} + +@media (max-width: 768px) { + .flex-responsive { + flex-direction: column; + } +} + +/* ============================================================================ + Grid Responsive + ============================================================================ */ + +.grid-responsive { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--spacing-lg); +} + +@media (max-width: 768px) { + .grid-responsive { + grid-template-columns: 1fr; + } +} diff --git a/static/js/i18n.js b/static/js/i18n.js index 137794d..dfe1e98 100644 --- a/static/js/i18n.js +++ b/static/js/i18n.js @@ -22,9 +22,14 @@ async function initI18n() { // Translate the page translatePage(); + + // Signal that translations are ready + window.dispatchEvent(new Event('i18nReady')); } } catch (error) { console.error('Failed to initialize i18n:', error); + // Even on error, signal ready so page doesn't get stuck + window.dispatchEvent(new Event('i18nReady')); } } diff --git a/templates/draft.html b/templates/draft.html index 10c2ab4..ac68522 100644 --- a/templates/draft.html +++ b/templates/draft.html @@ -4,11 +4,13 @@