.dockerignore DELETED
@@ -1,10 +0,0 @@
1
- .git
2
- .cursor
3
- __pycache__
4
- *.py[cod]
5
- *.db
6
- *.db-journal
7
- *.db-wal
8
- *.db-shm
9
- .env
10
- .venv
 
 
 
 
 
 
 
 
 
 
 
.gitignore DELETED
@@ -1,10 +0,0 @@
1
- ipl_predictions.db
2
- ipl_predictions.db-*
3
- *.db-journal
4
- *.db-wal
5
- *.db-shm
6
- __pycache__/
7
- .venv/
8
- .env
9
- .cursor/
10
- ipl_image.png
 
 
 
 
 
 
 
 
 
 
 
Dockerfile CHANGED
@@ -1,16 +1,10 @@
1
- FROM python:3.11-slim
2
 
3
  WORKDIR /app
4
 
5
- ENV PYTHONUNBUFFERED=1
6
- ENV PIP_NO_CACHE_DIR=1
7
-
8
- COPY requirements.txt .
9
  RUN pip install -r requirements.txt
10
 
11
  COPY . .
12
 
13
- EXPOSE 7860
14
-
15
- # Hugging Face Spaces sets PORT; default 7860 for local Docker
16
- CMD exec gunicorn --bind "0.0.0.0:${PORT:-7860}" --workers 1 --threads 4 --timeout 120 "app:app"
 
1
+ FROM python:3.9
2
 
3
  WORKDIR /app
4
 
5
+ COPY requirements.txt requirements.txt
 
 
 
6
  RUN pip install -r requirements.txt
7
 
8
  COPY . .
9
 
10
+ CMD ["streamlit", "run", "app.py"]
 
 
 
README.md CHANGED
@@ -1,226 +1,57 @@
1
  ---
2
- title: DIS IPL 2026 — Predictions
3
- emoji: 🏏
4
- colorFrom: blue
5
- colorTo: red
6
- sdk: docker
7
- app_port: 7860
 
8
  pinned: false
9
- license: mit
10
  ---
11
 
12
- # 🏏 DIS IPL 2026 — Team Match Predictions
13
 
14
- A full-featured IPL match prediction app for your team. Built with Python (Flask) + SQLite.
15
 
16
- ---
17
-
18
- ## ⚡ Quick Start
19
-
20
- ```bash
21
- pip install -r requirements.txt
22
- set ADMIN_PASSWORD=your-admin-password-for-results-panel
23
- # Optional: set PORT=7860 to match Hugging Face; default local port is 5000
24
- set SECRET_KEY=long-random-string-for-session-cookies
25
- python app.py
26
- ```
27
-
28
- When you run **`python app.py`**, **live reload is on by default**: editing `app.py`, templates, `static/`, or seed JSON (`users.json`, etc.) restarts the dev server automatically. Set **`FLASK_DEBUG=0`** (or `false` / `off`) to disable. Production / Hugging Face uses **gunicorn** and does not use this dev reloader.
29
 
30
- Open **`http://127.0.0.1:5000/`** when running `python app.py` locally (default port **5000**). On Hugging Face Spaces, use the Space URL. **No passwords** for team members — pick your name on the home page (roster comes from the database after the first sync from `users.json`; new players from Admin appear automatically).
31
- Admins use **Admin login** in the nav and enter `ADMIN_PASSWORD` (legacy: `ADMIN_SECRET` if `ADMIN_PASSWORD` is unset).
32
 
33
- ### Hugging Face Spaces (free tier — persistent state without paid disk)
34
 
35
- The Space container disk is **ephemeral**. The app can save `ipl_predictions.db` to:
 
 
36
 
37
- - **[Storage Buckets](https://huggingface.co/docs/hub/storage-buckets)** — S3-style object storage (see the [Python buckets guide](https://huggingface.co/docs/huggingface_hub/guides/buckets)).
38
- - **A private Hub dataset** — Git + LFS history per upload.
39
 
40
- You can set **both** `DIS_STATE_BUCKET` and `DIS_STATE_REPO`: every save uploads to **each** configured target (redundant backup). On restore, the app tries the **bucket first**, then the **dataset** if the local file is still missing or too small.
41
 
42
- #### What you need to do (one-time)
 
 
43
 
44
- 1. **Create a Hugging Face account** (free) if you don’t have one.
45
- 2. **Create storage** (one or both):
46
- - **Bucket:** private bucket, e.g. **`Jay-Rajput/dis-ipl-state`** (Hub UI or [`hf buckets create`](https://huggingface.co/docs/huggingface_hub/guides/buckets)).
47
- - **Dataset:** private dataset with the same or another id, e.g. **`Jay-Rajput/dis-ipl-state`** ([new dataset](https://huggingface.co/new-dataset)). Dataset and bucket are different Hub resources; the id string can match on both sides.
48
- 3. **Create an access token** with **read/write** on the bucket and dataset you use: [Settings → Access Tokens](https://huggingface.co/settings/tokens).
49
- 4. **Create a Space**: [New Space](https://huggingface.co/new-space) → SDK **Docker** → connect this repo. **CPU basic** is enough (free).
50
- 5. **Add Space secrets** (Space → **Settings** → **Repository secrets**):
51
- | Secret | Value |
52
- |--------|--------|
53
- | `ADMIN_PASSWORD` | Strong password for the admin panel |
54
- | `SECRET_KEY` | Long random string (keep the same across redeploys so sessions stay valid) |
55
- | `HF_TOKEN` | Token with write access to your bucket and/or dataset |
56
- | `DIS_STATE_BUCKET` | *(optional)* Bucket id, e.g. `Jay-Rajput/dis-ipl-state` |
57
- | `DIS_STATE_REPO` | *(optional)* Dataset id, e.g. `Jay-Rajput/dis-ipl-state` |
58
 
59
- 6. **Rebuild / restart** the Space. Use the app once, then wait **~10–30 seconds** so the background upload finishes before you restart the Space to test restore.
 
 
60
 
61
- #### How it behaves
62
 
63
- - After a **prediction** is saved or **results are settled** (points applied for every player who had a pick), the app **uploads the full database** to **each** configured target (bucket and/or dataset) **before the page finishes loading**, so the file on Hub always includes **all members**’ predictions and scores. Other admin tweaks still use a quick background upload.
64
- - On a **cold start**, if the local DB is missing or very small, it tries **bucket first**, then **dataset**.
65
- - Optional **`DIS_FORCE_HUB_RESTORE=1`** forces a download from Hub (overwrites local DB). Use only when you intend to reset from remote state.
66
 
67
- #### Optional (not required for free hosting)
68
-
69
- - Paid Hugging Face **persistent disk** mounted at `/data`: you may set **`DIS_DATA_DIR=/data`** to keep SQLite on that volume ([Spaces storage docs](https://huggingface.co/docs/hub/spaces-storage)).
70
- - To override bundled JSON at runtime, place `users.json` / `matches.json` / `players.json` under the same directory as the database when using `DIS_DATA_DIR`.
71
-
72
- **Bundled data:** `users.json`, `matches.json`, and `players.json` in the repo seed the DB; the name picker reads **active users from the database**.
73
-
74
- ---
75
 
76
- ## 🎮 How It Works
77
-
78
- ### Points System
79
- | Event | Points |
80
- |---|---|
81
- | ✅ Correct winner | **+bid amount** (e.g. bid 100 → earn 100) |
82
- | ❌ Wrong winner | **−bid amount** (e.g. bid 100 → lose 100) |
83
- | ⭐ Correct MOTM | **+75 bonus** |
84
- | 🚫 Wrong MOTM | **−25 penalty** |
85
- | 🤷 No MOTM predicted | **0** (safe, no risk) |
86
- | 🌧️ Match abandoned | **bid refunded** (no winner/MOTM points) |
87
- | 🚫 No prediction made | **0** (missed opportunity only) |
88
-
89
- ### Starting Points
90
- - Every player starts with **1,000 points**
91
- - Admin can adjust per-player starting points when adding them
92
- - Points can go negative (no floor — creates drama!)
93
-
94
- ### Bid Rules
95
- - Minimum bid: **10 points**
96
- - Maximum bid: **500 points** OR your current balance (whichever is lower)
97
- - Quick-bid buttons: 10%, 25%, 50%, 75%, 100% of your max bid
98
-
99
- ### Prediction Lock
100
- - Predictions lock automatically **at the scheduled match start time**
101
- - Once locked, no new predictions or edits are allowed
102
- - The lock is automatic — no admin action needed
103
 
104
- ---
105
-
106
- ## 👥 Admin Guide
107
-
108
- ### Adding Matches
109
- 1. Go to **Admin → Add Match**
110
- 2. Select Team 1, Team 2, date, and time
111
- 3. Add venue and match number (optional but recommended)
112
- 4. Predictions are allowed **only on the match’s calendar day**, until the scheduled start time, then they lock automatically
113
-
114
- ### Setting Results
115
- 1. Go to **Admin → Set Results**
116
- 2. Select the winner and Man of the Match
117
- 3. Click "Set Result & Settle" — points are calculated automatically
118
- 4. If you made a mistake, check **"Reverse previous settlement & recalculate"** and re-submit
119
-
120
- ### Managing Users
121
- - Add players: **Admin → Users → Add New Player**
122
- - Manually adjust points (for disputes, bonuses, etc.)
123
- - Disable/enable users
124
-
125
- ### Match Statuses
126
- | Status | Meaning |
127
- |---|---|
128
- | `upcoming` | Predictions open |
129
- | `locked` | Auto-locked at scheduled start (no new predictions) |
130
- | `live` | Match in progress (manual — set for drama!) |
131
- | `completed` | Match done, result can be entered |
132
- | `abandoned` | Rain/DLS/etc — bids are automatically refunded |
133
- | `postponed` | Rescheduled — set new date/time by deleting and re-adding |
134
-
135
- ---
136
-
137
- ## 🌍 Real-World Scenarios Handled
138
-
139
- | Scenario | How it's handled |
140
- |---|---|
141
- | **2 matches in a day** | Both appear on dashboard; predict each separately |
142
- | **Match abandoned** | Admin sets status → bids refunded automatically |
143
- | **Wrong result entered** | Admin uses "Recalculate" to reverse & redo |
144
- | **User forgets to predict** | No penalty — just missed opportunity |
145
- | **User has ≤ 10 pts** | Can bid all remaining points (no forced minimum) |
146
- | **Player name variations** (V Kohli vs Virat Kohli) | Fuzzy last-name matching |
147
- | **No MOTM predicted** | Zero impact — neither bonus nor penalty |
148
- | **Admin needs to correct MOTM** | Re-enter result with "Recalculate" checked |
149
- | **New user joins mid-season** | Admin adds with custom starting points |
150
- | **Suspended/banned user** | Admin disables account — they can't log in |
151
-
152
- ---
153
 
154
- ## 🗂️ File Structure
155
-
156
- ```
157
- ipl-predictions/
158
- ├── app.py # Main Flask app (all routes + logic)
159
- ├── requirements.txt
160
- ├── ipl_predictions.db # SQLite DB (auto-created)
161
- └── templates/
162
- ├── base.html # Shared layout, nav, styling
163
- ├── login.html # Login page
164
- ├── dashboard.html # Home — today's matches
165
- ├── matches.html # All matches list
166
- ├── predict.html # Prediction form
167
- ├── leaderboard.html # Full leaderboard + podium
168
- ├── history.html # Personal stats & history
169
- ├── admin.html # Admin panel
170
- └── admin_predictions.html # Per-match prediction viewer
171
- ```
172
-
173
- ---
174
-
175
- ## 🔧 Configuration (in app.py)
176
-
177
- ```python
178
- POINTS_CONFIG = {
179
- 'initial': 1000, # Starting points per player
180
- 'min_bid': 10, # Minimum bid
181
- 'max_bid': 500, # Maximum bid cap
182
- 'correct_winner': 1.0, # Multiplier on bid for correct winner
183
- 'wrong_winner': -1.0, # Multiplier on bid for wrong winner
184
- 'correct_motm': 75, # Flat bonus for correct MOTM
185
- 'wrong_motm': -25, # Flat penalty for wrong MOTM
186
- 'lock_minutes_before': 0, # 0 = at start; set e.g. 30 to lock N minutes before
187
- }
188
- ```
189
-
190
- Change these values and restart the app. Existing settled predictions are unaffected.
191
-
192
- ---
193
-
194
- ## 🚀 Production Deployment
195
-
196
- For production (not just local use):
197
-
198
- ```bash
199
- # Install gunicorn
200
- pip install gunicorn
201
-
202
- # Run with gunicorn
203
- gunicorn -w 4 -b 0.0.0.0:5000 app:app
204
- ```
205
-
206
- Also set a real secret key:
207
- ```bash
208
- export SECRET_KEY="your-very-long-random-secret-key-here"
209
- ```
210
-
211
- ---
212
 
213
- ## 🏏 IPL 2025 Teams
214
-
215
- | Team | Abbr |
216
- |---|---|
217
- | Mumbai Indians | MI |
218
- | Chennai Super Kings | CSK |
219
- | Royal Challengers Bengaluru | RCB |
220
- | Kolkata Knight Riders | KKR |
221
- | Sunrisers Hyderabad | SRH |
222
- | Delhi Capitals | DC |
223
- | Rajasthan Royals | RR |
224
- | Punjab Kings | PBKS |
225
- | Lucknow Super Giants | LSG |
226
- | Gujarat Titans | GT |
 
1
  ---
2
+ title: DIS IPL Predictions
3
+ emoji: 🦀
4
+ colorFrom: red
5
+ colorTo: pink
6
+ sdk: streamlit
7
+ sdk_version: 1.32.2
8
+ app_file: app.py
9
  pinned: false
10
+ license: apache-2.0
11
  ---
12
 
13
+ # DIS IPL Match Predictions App
14
 
15
+ > "Predict, Compete, and Win 🏏 - Where Every Guess Counts! 🏆"
16
 
17
+ Welcome to the DIS IPL Match Predictions App! This app allows you to predict the outcomes of IPL matches and compete with your colleagues to win exciting prizes.
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ ## Getting Started
 
20
 
21
+ ### Prerequisites
22
 
23
+ - Python 3.x installed on your system
24
+ - Git installed on your system
25
+ - Pip package manager
26
 
27
+ ### Installation
 
28
 
29
+ 1. Clone this repository to your local machine using Git:
30
 
31
+ ```bash
32
+ git clone <repository_url>
33
+ ```
34
 
35
+ 2. Navigate to the cloned repository directory:
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
+ ```bash
38
+ cd ipl-match-predictions-app
39
+ ```
40
 
41
+ 3. Install the required Python dependencies using Pip:
42
 
43
+ ```bash
44
+ pip install -r requirements.txt
45
+ ```
46
 
47
+ ### Running The APP
 
 
 
 
 
 
 
48
 
49
+ 1. After installing the dependencies, you can run the Streamlit app using the following command:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
+ ```bash
52
+ streamlit run app.py
53
+ ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
+ 2. The app will start running locally and open in your default web browser.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ Thanks
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
The diff for this file is too large to render. See raw diff
 
ipl_image.png ADDED
leaders/users.json CHANGED
@@ -1,354 +1,354 @@
1
  {
2
- "Arpit": {
3
- "last_5_results": [
4
- "🔴",
5
- "🔴",
6
- "🟢",
7
- "🟢",
8
- "🔴"
9
- ],
10
- "points": 11947,
11
- "redistributed_bonus": 0,
12
- "wildcard": [
13
- 0,
14
- 0,
15
- 0
16
- ]
17
- },
18
- "Ganesh": {
19
- "last_5_results": [
20
- "⚪",
21
- "⚪",
22
- "⚪",
23
- "⚪",
24
- "⚪"
25
- ],
26
- "points": 800,
27
- "redistributed_bonus": 0,
28
- "wildcard": [
29
- 0,
30
- 0,
31
- 0
32
- ]
33
- },
34
- "Haaris": {
35
- "last_5_results": [
36
- "🔴",
37
- "🔴",
38
- "🔴",
39
- "🔴",
40
- "🔴"
41
- ],
42
- "points": 800,
43
- "redistributed_bonus": 0,
44
- "wildcard": [
45
- 0,
46
- 0,
47
- 0
48
- ]
49
- },
50
- "Jay": {
51
- "last_5_results": [
52
- "🔴",
53
- "🟢",
54
- "🟢",
55
- "🟢",
56
- "🔴"
57
- ],
58
- "points": 22942,
59
- "redistributed_bonus": 0,
60
- "wildcard": [
61
- 0,
62
- 0,
63
- 0
64
- ]
65
- },
66
- "Kishore": {
67
- "last_5_results": [
68
- "🔴",
69
- "🟢",
70
- "🟢",
71
- "🟢",
72
- "🟢"
73
- ],
74
- "points": 36600,
75
- "redistributed_bonus": 0,
76
- "wildcard": [
77
- 0,
78
- 0,
79
- 0
80
- ]
81
- },
82
- "Megha": {
83
- "last_5_results": [
84
- "",
85
- "",
86
- "🟢",
87
- "",
88
- "🟢"
89
- ],
90
- "points": 21200,
91
- "redistributed_bonus": 0,
92
- "wildcard": [
93
- 0,
94
- 0,
95
- 0
96
- ]
97
- },
98
- "Naveein": {
99
- "last_5_results": [
100
- "🔴",
101
- "🟢",
102
- "🟢",
103
- "🟢",
104
- "🟢"
105
- ],
106
- "points": 37800,
107
- "redistributed_bonus": 0,
108
- "wildcard": [
109
- 0,
110
- 0,
111
- 0
112
- ]
113
- },
114
- "Neha": {
115
- "last_5_results": [
116
- "",
117
- "⚪",
118
- "⚪",
119
- "⚪",
120
- "⚪"
121
- ],
122
- "points": 800,
123
- "redistributed_bonus": 0,
124
- "wildcard": [
125
- 0,
126
- 0,
127
- 0
128
- ]
129
- },
130
- "Praveen": {
131
- "last_5_results": [
132
- "",
133
- "⚪",
134
- "⚪",
135
- "⚪",
136
- "⚪"
137
- ],
138
- "points": 8448,
139
- "redistributed_bonus": 0,
140
- "wildcard": [
141
- 0,
142
- 0,
143
- 0
144
- ]
145
- },
146
- "Rakesh": {
147
- "last_5_results": [
148
- "🔴",
149
- "🟢",
150
- "🟢",
151
- "🔴",
152
- "🟢"
153
- ],
154
- "points": 21880,
155
- "redistributed_bonus": 0,
156
- "wildcard": [
157
- 0,
158
- 0,
159
- 0
160
- ]
161
- },
162
- "Sai": {
163
- "last_5_results": [
164
- "🟢",
165
- "🔴",
166
- "🟢",
167
- "🟢",
168
- "🔴"
169
- ],
170
- "points": 40908,
171
- "redistributed_bonus": 0,
172
- "wildcard": [
173
- 0,
174
- 0,
175
- 0
176
- ]
177
- },
178
- "Sunil": {
179
- "last_5_results": [
180
- "",
181
- "🔴",
182
- "🔴",
183
- "🔴",
184
- "🔴"
185
- ],
186
- "points": 22485,
187
- "redistributed_bonus": 0,
188
- "wildcard": [
189
- 0,
190
- 0,
191
- 0
192
- ]
193
- },
194
- "Vaibhav": {
195
- "last_5_results": [
196
- "🔴",
197
- "🔴",
198
- "",
199
- "",
200
- "🟢"
201
- ],
202
- "points": 26900,
203
- "redistributed_bonus": 0,
204
- "wildcard": [
205
- 0,
206
- 0,
207
- 0
208
- ]
209
- },
210
- "Vinay": {
211
- "last_5_results": [
212
- "⚪",
213
- "⚪",
214
- "⚪",
215
- "⚪",
216
- "⚪"
217
- ],
218
- "points": 4580,
219
- "redistributed_bonus": 0,
220
- "wildcard": [
221
- 0,
222
- 0,
223
- 0
224
- ]
225
- },
226
- "Anandh": {
227
- "last_5_results": [
228
- "🔴",
229
- "🟢",
230
- "🟢",
231
- "🔴",
232
- "🔴"
233
- ],
234
- "points": 6188,
235
- "redistributed_bonus": 0,
236
- "wildcard": [
237
- 0,
238
- 0,
239
- 0
240
- ]
241
- },
242
- "Archana": {
243
- "last_5_results": [
244
- "⚪",
245
- "⚪",
246
- "⚪",
247
- "⚪",
248
- "⚪"
249
- ],
250
- "points": 5200,
251
- "redistributed_bonus": 0,
252
- "wildcard": [
253
- 0,
254
- 0,
255
- 0
256
- ]
257
- },
258
- "Biswabarenya": {
259
- "last_5_results": [
260
- "",
261
- "⚪",
262
- "⚪",
263
- "🟢",
264
- "🔴"
265
- ],
266
- "points": 6150,
267
- "redistributed_bonus": 0,
268
- "wildcard": [
269
- 0,
270
- 0,
271
- 0
272
- ]
273
- },
274
- "Naitik": {
275
- "last_5_results": [
276
- "🟢",
277
- "🔴",
278
- "🟢",
279
- "🟢",
280
- "🔴"
281
- ],
282
- "points": 43400,
283
- "redistributed_bonus": 0,
284
- "wildcard": [
285
- 0,
286
- 0,
287
- 0
288
- ]
289
- },
290
- "Alekhya": {
291
- "last_5_results": [
292
- "⚪",
293
- "⚪",
294
- "",
295
- "⚪",
296
- "⚪"
297
- ],
298
- "points": 11960,
299
- "redistributed_bonus": 0,
300
- "wildcard": [
301
- 0,
302
- 0,
303
- 0
304
- ]
305
- },
306
- "Siri Gowri": {
307
- "last_5_results": [
308
- "⚪",
309
- "",
310
- "",
311
- "",
312
- "🔴"
313
- ],
314
- "points": 600,
315
- "redistributed_bonus": 0,
316
- "wildcard": [
317
- 0,
318
- 0,
319
- 0
320
- ]
321
- },
322
- "Priyavrat Mohan": {
323
- "last_5_results": [
324
- "🔴",
325
- "🔴",
326
- "🟢",
327
- "🟢",
328
- "🔴"
329
- ],
330
- "points": 31475,
331
- "redistributed_bonus": 0,
332
- "wildcard": [
333
- 0,
334
- 0,
335
- 0
336
- ]
337
- },
338
- "Satish": {
339
- "last_5_results": [
340
- "",
341
- "🟢",
342
- "🔴",
343
- "🔴",
344
- "🔴"
345
- ],
346
- "points": 34600,
347
- "redistributed_bonus": 0,
348
- "wildcard": [
349
- 0,
350
- 0,
351
- 0
352
- ]
353
- }
354
  }
 
1
  {
2
+ "Arpit": {
3
+ "last_5_results": [
4
+ "🟢",
5
+ "",
6
+ "🟢",
7
+ "🔴",
8
+ "🟢"
9
+ ],
10
+ "points": 153927,
11
+ "redistributed_bonus": 2656,
12
+ "wildcard": [
13
+ 0,
14
+ 0,
15
+ 1
16
+ ]
17
+ },
18
+ "Ganesh": {
19
+ "last_5_results": [
20
+ "⚪",
21
+ "⚪",
22
+ "⚪",
23
+ "⚪",
24
+ "⚪"
25
+ ],
26
+ "points": 21356,
27
+ "redistributed_bonus": 0,
28
+ "wildcard": [
29
+ 0,
30
+ 0,
31
+ 0
32
+ ]
33
+ },
34
+ "Haaris": {
35
+ "last_5_results": [
36
+ "🟢",
37
+ "",
38
+ "🟢",
39
+ "",
40
+ ""
41
+ ],
42
+ "points": 92052,
43
+ "redistributed_bonus": 6907,
44
+ "wildcard": [
45
+ 0,
46
+ 0,
47
+ 0
48
+ ]
49
+ },
50
+ "Jay": {
51
+ "last_5_results": [
52
+ "🟢",
53
+ "🟢",
54
+ "🟢",
55
+ "🔴",
56
+ "🔴"
57
+ ],
58
+ "points": 192586,
59
+ "redistributed_bonus": 0,
60
+ "wildcard": [
61
+ 0,
62
+ 0,
63
+ 1
64
+ ]
65
+ },
66
+ "Kishore": {
67
+ "last_5_results": [
68
+ "",
69
+ "🟢",
70
+ "🟢",
71
+ "🟢",
72
+ ""
73
+ ],
74
+ "points": 64863,
75
+ "redistributed_bonus": 0,
76
+ "wildcard": [
77
+ 0,
78
+ 0,
79
+ 0
80
+ ]
81
+ },
82
+ "Megha": {
83
+ "last_5_results": [
84
+ "🟢",
85
+ "🟢",
86
+ "🟢",
87
+ "🔴",
88
+ "🔴"
89
+ ],
90
+ "points": 155444,
91
+ "redistributed_bonus": 2552,
92
+ "wildcard": [
93
+ 1,
94
+ 0,
95
+ 1
96
+ ]
97
+ },
98
+ "Naveein": {
99
+ "last_5_results": [
100
+ "🟢",
101
+ "🟢",
102
+ "🔴",
103
+ "🔴",
104
+ "🔴"
105
+ ],
106
+ "points": 173375,
107
+ "redistributed_bonus": 0,
108
+ "wildcard": [
109
+ 1,
110
+ 0,
111
+ 1
112
+ ]
113
+ },
114
+ "Neha": {
115
+ "last_5_results": [
116
+ "🔴",
117
+ "⚪",
118
+ "⚪",
119
+ "⚪",
120
+ "⚪"
121
+ ],
122
+ "points": 47053,
123
+ "redistributed_bonus": 9999,
124
+ "wildcard": [
125
+ 0,
126
+ 1,
127
+ 0
128
+ ]
129
+ },
130
+ "Praveen": {
131
+ "last_5_results": [
132
+ "🟢",
133
+ "⚪",
134
+ "⚪",
135
+ "⚪",
136
+ "⚪"
137
+ ],
138
+ "points": 148347,
139
+ "redistributed_bonus": 3039,
140
+ "wildcard": [
141
+ 0,
142
+ 1,
143
+ 0
144
+ ]
145
+ },
146
+ "Rakesh": {
147
+ "last_5_results": [
148
+ "🟢",
149
+ "🔴",
150
+ "🔴",
151
+ "🟢",
152
+ "🟢"
153
+ ],
154
+ "points": 84129,
155
+ "redistributed_bonus": 7452,
156
+ "wildcard": [
157
+ 0,
158
+ 0,
159
+ 1
160
+ ]
161
+ },
162
+ "Sai": {
163
+ "last_5_results": [
164
+ "🟢",
165
+ "🔴",
166
+ "",
167
+ "",
168
+ ""
169
+ ],
170
+ "points": 195401,
171
+ "redistributed_bonus": 0,
172
+ "wildcard": [
173
+ 0,
174
+ 0,
175
+ 1
176
+ ]
177
+ },
178
+ "Sunil": {
179
+ "last_5_results": [
180
+ "🔴",
181
+ "🟢",
182
+ "🟢",
183
+ "🟢",
184
+ "🔴"
185
+ ],
186
+ "points": 188091,
187
+ "redistributed_bonus": 0,
188
+ "wildcard": [
189
+ 1,
190
+ 1,
191
+ 0
192
+ ]
193
+ },
194
+ "Vaibhav": {
195
+ "last_5_results": [
196
+ "🟢",
197
+ "🟢",
198
+ "🟢",
199
+ "🔴",
200
+ "🟢"
201
+ ],
202
+ "points": 153919,
203
+ "redistributed_bonus": 2656,
204
+ "wildcard": [
205
+ 0,
206
+ 0,
207
+ 1
208
+ ]
209
+ },
210
+ "Vinay": {
211
+ "last_5_results": [
212
+ "⚪",
213
+ "⚪",
214
+ "⚪",
215
+ "⚪",
216
+ "⚪"
217
+ ],
218
+ "points": 19680,
219
+ "redistributed_bonus": 0,
220
+ "wildcard": [
221
+ 0,
222
+ 0,
223
+ 0
224
+ ]
225
+ },
226
+ "Anandh": {
227
+ "last_5_results": [
228
+ "",
229
+ "🟢",
230
+ "🟢",
231
+ "🔴",
232
+ "🟢"
233
+ ],
234
+ "points": 131506,
235
+ "redistributed_bonus": 0,
236
+ "wildcard": [
237
+ 0,
238
+ 1,
239
+ 0
240
+ ]
241
+ },
242
+ "Archana": {
243
+ "last_5_results": [
244
+ "⚪",
245
+ "⚪",
246
+ "⚪",
247
+ "⚪",
248
+ "⚪"
249
+ ],
250
+ "points": 21310,
251
+ "redistributed_bonus": 0,
252
+ "wildcard": [
253
+ 0,
254
+ 0,
255
+ 0
256
+ ]
257
+ },
258
+ "Biswabarenya": {
259
+ "last_5_results": [
260
+ "🟢",
261
+ "⚪",
262
+ "⚪",
263
+ "🔴",
264
+ ""
265
+ ],
266
+ "points": 102167,
267
+ "redistributed_bonus": 6212,
268
+ "wildcard": [
269
+ 0,
270
+ 1,
271
+ 0
272
+ ]
273
+ },
274
+ "Naitik": {
275
+ "last_5_results": [
276
+ "🟢",
277
+ "🟢",
278
+ "🔴",
279
+ "🔴",
280
+ "🟢"
281
+ ],
282
+ "points": 376072,
283
+ "redistributed_bonus": 0,
284
+ "wildcard": [
285
+ 0,
286
+ 1,
287
+ 1
288
+ ]
289
+ },
290
+ "Alekhya": {
291
+ "last_5_results": [
292
+ "⚪",
293
+ "⚪",
294
+ "🟢",
295
+ "⚪",
296
+ "⚪"
297
+ ],
298
+ "points": 30567,
299
+ "redistributed_bonus": 0,
300
+ "wildcard": [
301
+ 0,
302
+ 0,
303
+ 0
304
+ ]
305
+ },
306
+ "Siri Gowri": {
307
+ "last_5_results": [
308
+ "⚪",
309
+ "🟢",
310
+ "🟢",
311
+ "🔴",
312
+ "🟢"
313
+ ],
314
+ "points": 116836,
315
+ "redistributed_bonus": 0,
316
+ "wildcard": [
317
+ 0,
318
+ 1,
319
+ 0
320
+ ]
321
+ },
322
+ "Priyavrat Mohan": {
323
+ "last_5_results": [
324
+ "🟢",
325
+ "🔴",
326
+ "🟢",
327
+ "🔴",
328
+ "🟢"
329
+ ],
330
+ "points": 143618,
331
+ "redistributed_bonus": 3364,
332
+ "wildcard": [
333
+ 0,
334
+ 1,
335
+ 1
336
+ ]
337
+ },
338
+ "Satish": {
339
+ "last_5_results": [
340
+ "🟢",
341
+ "",
342
+ "🔴",
343
+ "🔴",
344
+ ""
345
+ ],
346
+ "points": 115130,
347
+ "redistributed_bonus": 5322,
348
+ "wildcard": [
349
+ 0,
350
+ 1,
351
+ 0
352
+ ]
353
+ }
354
  }
matches.json CHANGED
@@ -1,702 +1,852 @@
1
  [
2
  {
3
- "match_id": "20260328_1",
4
- "date": "2026-03-28",
5
- "time": "7:30 PM",
6
  "teams": [
7
- "RCB",
8
- "SRH"
9
  ],
10
- "venue": "M. Chinnaswamy Stadium, Bengaluru"
11
  },
12
  {
13
- "match_id": "20260329_1",
14
- "date": "2026-03-29",
 
 
 
 
 
 
 
 
 
 
15
  "time": "7:30 PM",
16
  "teams": [
17
- "MI",
18
- "KKR"
19
  ],
20
- "venue": "Wankhede Stadium, Mumbai"
21
  },
22
  {
23
- "match_id": "20260330_1",
24
- "date": "2026-03-30",
 
 
 
 
 
 
 
 
 
 
25
  "time": "7:30 PM",
26
  "teams": [
27
- "RR",
28
- "CSK"
29
  ],
30
- "venue": "Sawai Mansingh Stadium, Jaipur"
31
  },
32
  {
33
- "match_id": "20260331_1",
34
- "date": "2026-03-31",
35
  "time": "7:30 PM",
36
  "teams": [
37
- "PBKS",
38
- "GT"
39
  ],
40
- "venue": "Maharaja Yadavindra Stadium, Mohali"
41
  },
42
  {
43
- "match_id": "20260401_1",
44
- "date": "2026-04-01",
45
  "time": "7:30 PM",
46
  "teams": [
47
- "LSG",
48
- "DC"
49
  ],
50
- "venue": "BRSABV Ekana Stadium, Lucknow"
51
  },
52
  {
53
- "match_id": "20260402_1",
54
- "date": "2026-04-02",
55
  "time": "7:30 PM",
56
  "teams": [
57
- "KKR",
58
- "SRH"
59
  ],
60
- "venue": "Eden Gardens, Kolkata"
61
  },
62
  {
63
- "match_id": "20260403_1",
64
- "date": "2026-04-03",
65
  "time": "7:30 PM",
66
  "teams": [
67
- "CSK",
68
- "PBKS"
69
  ],
70
- "venue": "M.A. Chidambaram Stadium, Chennai"
71
  },
72
  {
73
- "match_id": "20260404_1",
74
- "date": "2026-04-04",
75
- "time": "3:30 PM",
76
  "teams": [
77
- "DC",
78
- "MI"
79
  ],
80
- "venue": "Arun Jaitley Stadium, Delhi"
81
  },
82
  {
83
- "match_id": "20260404_2",
84
- "date": "2026-04-04",
85
  "time": "7:30 PM",
86
  "teams": [
87
  "GT",
88
- "RR"
89
  ],
90
- "venue": "Narendra Modi Stadium, Ahmedabad"
91
  },
92
  {
93
- "match_id": "20260405_1",
94
- "date": "2026-04-05",
95
  "time": "3:30 PM",
96
  "teams": [
97
- "SRH",
98
- "LSG"
99
  ],
100
- "venue": "Rajiv Gandhi Intl Stadium, Hyderabad"
101
  },
102
  {
103
- "match_id": "20260405_2",
104
- "date": "2026-04-05",
105
  "time": "7:30 PM",
106
  "teams": [
107
- "RCB",
108
  "CSK"
109
  ],
110
- "venue": "M. Chinnaswamy Stadium, Bengaluru"
111
  },
112
  {
113
- "match_id": "20260406_1",
114
- "date": "2026-04-06",
115
  "time": "7:30 PM",
116
  "teams": [
117
- "KKR",
118
- "PBKS"
119
  ],
120
- "venue": "Eden Gardens, Kolkata"
121
  },
122
  {
123
- "match_id": "20260407_1",
124
- "date": "2026-04-07",
125
  "time": "7:30 PM",
126
  "teams": [
127
- "RR",
128
- "MI"
129
  ],
130
- "venue": "Sawai Mansingh Stadium, Jaipur"
131
  },
132
  {
133
- "match_id": "20260408_1",
134
- "date": "2026-04-08",
135
  "time": "7:30 PM",
136
  "teams": [
137
- "DC",
138
  "GT"
139
  ],
140
- "venue": "Arun Jaitley Stadium, Delhi"
141
  },
142
  {
143
- "match_id": "20260409_1",
144
- "date": "2026-04-09",
145
  "time": "7:30 PM",
146
  "teams": [
147
  "KKR",
148
- "LSG"
149
  ],
150
- "venue": "Eden Gardens, Kolkata"
151
  },
152
  {
153
- "match_id": "20260410_1",
154
- "date": "2026-04-10",
155
  "time": "7:30 PM",
156
  "teams": [
157
- "RR",
158
- "RCB"
159
  ],
160
- "venue": "Sawai Mansingh Stadium, Jaipur"
161
  },
162
  {
163
- "match_id": "20260411_1",
164
- "date": "2026-04-11",
165
  "time": "3:30 PM",
166
  "teams": [
167
- "PBKS",
168
- "SRH"
169
  ],
170
- "venue": "Maharaja Yadavindra Stadium, Mohali"
171
  },
172
  {
173
- "match_id": "20260411_2",
174
- "date": "2026-04-11",
175
  "time": "7:30 PM",
176
  "teams": [
177
- "CSK",
178
- "DC"
179
  ],
180
- "venue": "M.A. Chidambaram Stadium, Chennai"
181
  },
182
  {
183
- "match_id": "20260412_1",
184
- "date": "2026-04-12",
185
- "time": "3:30 PM",
186
  "teams": [
187
- "LSG",
188
  "GT"
189
  ],
190
- "venue": "BRSABV Ekana Stadium, Lucknow"
191
  },
192
  {
193
- "match_id": "20260412_2",
194
- "date": "2026-04-12",
195
  "time": "7:30 PM",
196
  "teams": [
197
  "MI",
198
  "RCB"
199
  ],
200
- "venue": "Wankhede Stadium, Mumbai"
201
  },
202
  {
203
- "match_id": "20260413_1",
204
- "date": "2026-04-13",
205
- "time": "7:30 PM",
206
  "teams": [
207
- "SRH",
208
- "RR"
209
  ],
210
- "venue": "Rajiv Gandhi Intl Stadium, Hyderabad"
211
  },
212
  {
213
- "match_id": "20260414_1",
214
- "date": "2026-04-14",
215
  "time": "7:30 PM",
216
  "teams": [
217
- "CSK",
218
- "KKR"
219
  ],
220
- "venue": "M.A. Chidambaram Stadium, Chennai"
221
  },
222
  {
223
- "match_id": "20260415_1",
224
- "date": "2026-04-15",
225
  "time": "7:30 PM",
226
  "teams": [
227
- "RCB",
228
- "LSG"
229
  ],
230
- "venue": "M. Chinnaswamy Stadium, Bengaluru"
231
  },
232
  {
233
- "match_id": "20260416_1",
234
- "date": "2026-04-16",
235
  "time": "7:30 PM",
236
  "teams": [
237
- "MI",
238
- "PBKS"
239
  ],
240
- "venue": "Wankhede Stadium, Mumbai"
241
  },
242
  {
243
- "match_id": "20260417_1",
244
- "date": "2026-04-17",
245
  "time": "7:30 PM",
246
  "teams": [
247
- "GT",
248
  "KKR"
249
  ],
250
- "venue": "Narendra Modi Stadium, Ahmedabad"
251
  },
252
  {
253
- "match_id": "20260418_1",
254
- "date": "2026-04-18",
255
  "time": "3:30 PM",
256
  "teams": [
257
- "RCB",
258
- "DC"
259
  ],
260
- "venue": "M. Chinnaswamy Stadium, Bengaluru"
261
  },
262
  {
263
- "match_id": "20260418_2",
264
- "date": "2026-04-18",
265
  "time": "7:30 PM",
266
  "teams": [
267
  "SRH",
268
- "CSK"
269
  ],
270
- "venue": "Rajiv Gandhi Intl Stadium, Hyderabad"
271
  },
272
  {
273
- "match_id": "20260419_1",
274
- "date": "2026-04-19",
275
  "time": "3:30 PM",
276
  "teams": [
277
- "KKR",
278
- "RR"
279
  ],
280
- "venue": "Eden Gardens, Kolkata"
281
  },
282
  {
283
- "match_id": "20260419_2",
284
- "date": "2026-04-19",
285
  "time": "7:30 PM",
286
  "teams": [
287
- "PBKS",
288
- "LSG"
289
  ],
290
- "venue": "Maharaja Yadavindra Stadium, Mohali"
291
  },
292
  {
293
- "match_id": "20260420_1",
294
- "date": "2026-04-20",
295
  "time": "7:30 PM",
296
  "teams": [
297
- "GT",
298
- "MI"
299
  ],
300
- "venue": "Narendra Modi Stadium, Ahmedabad"
301
  },
302
  {
303
- "match_id": "20260421_1",
304
- "date": "2026-04-21",
305
  "time": "7:30 PM",
306
  "teams": [
307
- "SRH",
308
- "DC"
309
  ],
310
- "venue": "Rajiv Gandhi Intl Stadium, Hyderabad"
311
  },
312
  {
313
- "match_id": "20260422_1",
314
- "date": "2026-04-22",
315
  "time": "7:30 PM",
316
  "teams": [
317
- "LSG",
318
  "RR"
319
  ],
320
- "venue": "BRSABV Ekana Stadium, Lucknow"
321
  },
322
  {
323
- "match_id": "20260423_1",
324
- "date": "2026-04-23",
325
  "time": "7:30 PM",
326
  "teams": [
327
  "MI",
328
- "CSK"
329
  ],
330
- "venue": "Wankhede Stadium, Mumbai"
331
  },
332
  {
333
- "match_id": "20260424_1",
334
- "date": "2026-04-24",
335
- "time": "7:30 PM",
336
  "teams": [
337
  "RCB",
338
- "GT"
339
  ],
340
- "venue": "M. Chinnaswamy Stadium, Bengaluru"
341
  },
342
  {
343
- "match_id": "20260425_1",
344
- "date": "2026-04-25",
345
- "time": "3:30 PM",
346
  "teams": [
347
- "DC",
348
- "PBKS"
349
  ],
350
- "venue": "Arun Jaitley Stadium, Delhi"
351
  },
352
  {
353
- "match_id": "20260425_2",
354
- "date": "2026-04-25",
355
- "time": "7:30 PM",
356
  "teams": [
357
  "RR",
358
- "SRH"
359
  ],
360
- "venue": "Sawai Mansingh Stadium, Jaipur"
361
  },
362
  {
363
- "match_id": "20260426_1",
364
- "date": "2026-04-26",
365
- "time": "3:30 PM",
366
  "teams": [
367
- "GT",
 
 
 
 
 
 
 
 
 
 
368
  "CSK"
369
  ],
370
- "venue": "Narendra Modi Stadium, Ahmedabad"
 
 
 
 
 
 
 
 
 
 
371
  },
372
  {
373
- "match_id": "20260426_2",
374
- "date": "2026-04-26",
375
  "time": "7:30 PM",
376
  "teams": [
377
  "LSG",
378
- "KKR"
379
  ],
380
- "venue": "BRSABV Ekana Stadium, Lucknow"
381
  },
382
  {
383
- "match_id": "20260427_1",
384
- "date": "2026-04-27",
385
  "time": "7:30 PM",
386
  "teams": [
387
- "DC",
388
- "RCB"
389
  ],
390
- "venue": "Arun Jaitley Stadium, Delhi"
391
  },
392
  {
393
- "match_id": "20260428_1",
394
- "date": "2026-04-28",
395
  "time": "7:30 PM",
396
  "teams": [
397
- "PBKS",
398
  "RR"
399
  ],
400
- "venue": "Maharaja Yadavindra Stadium, Mohali"
401
  },
402
  {
403
- "match_id": "20260429_1",
404
- "date": "2026-04-29",
405
  "time": "7:30 PM",
406
  "teams": [
407
- "MI",
408
  "SRH"
409
  ],
410
- "venue": "Wankhede Stadium, Mumbai"
411
  },
412
  {
413
- "match_id": "20260430_1",
414
- "date": "2026-04-30",
415
  "time": "7:30 PM",
416
  "teams": [
417
- "GT",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
  "RCB"
419
  ],
420
- "venue": "Narendra Modi Stadium, Ahmedabad"
421
  },
422
  {
423
- "match_id": "20260501_1",
424
- "date": "2026-05-01",
425
  "time": "7:30 PM",
426
  "teams": [
427
  "RR",
428
- "DC"
429
  ],
430
- "venue": "Sawai Mansingh Stadium, Jaipur"
431
  },
432
  {
433
- "match_id": "20260502_1",
434
- "date": "2026-05-02",
 
 
 
 
 
 
 
 
 
 
435
  "time": "7:30 PM",
436
  "teams": [
437
  "CSK",
 
 
 
 
 
 
 
 
 
 
438
  "MI"
439
  ],
440
- "venue": "M.A. Chidambaram Stadium, Chennai"
 
 
 
 
 
 
 
 
 
 
441
  },
442
  {
443
- "match_id": "20260503_1",
444
- "date": "2026-05-03",
 
 
 
 
 
 
 
 
 
 
445
  "time": "3:30 PM",
446
  "teams": [
447
- "SRH",
448
- "KKR"
449
  ],
450
- "venue": "Rajiv Gandhi Intl Stadium, Hyderabad"
451
  },
452
  {
453
- "match_id": "20260503_2",
454
- "date": "2026-05-03",
455
  "time": "7:30 PM",
456
  "teams": [
457
- "GT",
458
- "PBKS"
 
 
 
 
 
 
 
 
 
 
459
  ],
460
- "venue": "Narendra Modi Stadium, Ahmedabad"
461
  },
462
  {
463
- "match_id": "20260504_1",
464
- "date": "2026-05-04",
465
  "time": "7:30 PM",
466
  "teams": [
467
  "MI",
468
- "LSG"
469
  ],
470
- "venue": "Wankhede Stadium, Mumbai"
471
  },
472
  {
473
- "match_id": "20260505_1",
474
- "date": "2026-05-05",
475
  "time": "7:30 PM",
476
  "teams": [
477
- "DC",
478
  "CSK"
479
  ],
480
- "venue": "Arun Jaitley Stadium, Delhi"
481
  },
482
  {
483
- "match_id": "20260506_1",
484
- "date": "2026-05-06",
485
  "time": "7:30 PM",
486
  "teams": [
487
- "SRH",
488
- "PBKS"
489
  ],
490
- "venue": "Rajiv Gandhi Intl Stadium, Hyderabad"
491
  },
492
  {
493
- "match_id": "20260507_1",
494
- "date": "2026-05-07",
495
  "time": "7:30 PM",
496
  "teams": [
497
  "LSG",
498
  "RCB"
499
  ],
500
- "venue": "BRSABV Ekana Stadium, Lucknow"
501
  },
502
  {
503
- "match_id": "20260508_1",
504
- "date": "2026-05-08",
505
  "time": "7:30 PM",
506
  "teams": [
507
- "DC",
508
  "KKR"
509
  ],
510
- "venue": "Arun Jaitley Stadium, Delhi"
 
 
 
 
 
 
 
 
 
 
511
  },
512
  {
513
- "match_id": "20260509_1",
514
- "date": "2026-05-09",
515
  "time": "7:30 PM",
516
  "teams": [
517
- "RR",
518
  "GT"
519
  ],
520
- "venue": "Sawai Mansingh Stadium, Jaipur"
521
  },
522
  {
523
- "match_id": "20260510_1",
524
- "date": "2026-05-10",
525
  "time": "7:30 PM",
526
  "teams": [
527
  "CSK",
528
- "LSG"
529
  ],
530
- "venue": "M.A. Chidambaram Stadium, Chennai"
531
  },
532
  {
533
- "match_id": "20260510_2",
534
- "date": "2026-05-10",
535
  "time": "7:30 PM",
536
  "teams": [
537
  "RCB",
538
- "MI"
539
  ],
540
- "venue": "M. Chinnaswamy Stadium, Bengaluru"
541
  },
542
  {
543
- "match_id": "20260511_1",
544
- "date": "2026-05-11",
545
  "time": "7:30 PM",
546
  "teams": [
547
- "PBKS",
548
- "DC"
549
  ],
550
- "venue": "Maharaja Yadavindra Stadium, Mohali"
551
  },
552
  {
553
- "match_id": "20260512_1",
554
- "date": "2026-05-12",
555
  "time": "7:30 PM",
556
  "teams": [
557
- "GT",
558
- "SRH"
559
  ],
560
- "venue": "Narendra Modi Stadium, Ahmedabad"
561
  },
562
  {
563
- "match_id": "20260513_1",
564
- "date": "2026-05-13",
565
  "time": "7:30 PM",
566
  "teams": [
567
  "RCB",
568
  "KKR"
569
  ],
570
- "venue": "M. Chinnaswamy Stadium, Bengaluru"
 
 
 
 
 
 
 
 
 
 
571
  },
572
  {
573
- "match_id": "20260514_1",
574
- "date": "2026-05-14",
575
  "time": "7:30 PM",
576
  "teams": [
577
- "PBKS",
578
- "MI"
579
  ],
580
- "venue": "Maharaja Yadavindra Stadium, Mohali"
581
  },
582
  {
583
- "match_id": "20260515_1",
584
- "date": "2026-05-15",
585
  "time": "7:30 PM",
586
  "teams": [
587
  "LSG",
588
- "CSK"
589
  ],
590
- "venue": "BRSABV Ekana Stadium, Lucknow"
591
  },
592
  {
593
- "match_id": "20260516_1",
594
- "date": "2026-05-16",
595
  "time": "7:30 PM",
596
  "teams": [
597
- "KKR",
598
- "GT"
599
  ],
600
- "venue": "Eden Gardens, Kolkata"
601
  },
602
  {
603
- "match_id": "20260517_1",
604
- "date": "2026-05-17",
605
  "time": "7:30 PM",
606
  "teams": [
607
- "PBKS",
608
- "RCB"
609
  ],
610
- "venue": "Maharaja Yadavindra Stadium, Mohali"
611
  },
612
  {
613
- "match_id": "20260517_2",
614
- "date": "2026-05-17",
615
  "time": "7:30 PM",
616
  "teams": [
617
- "DC",
618
- "RR"
619
  ],
620
- "venue": "Arun Jaitley Stadium, Delhi"
621
  },
622
  {
623
- "match_id": "20260518_1",
624
- "date": "2026-05-18",
625
  "time": "7:30 PM",
626
  "teams": [
627
- "CSK",
628
  "SRH"
629
  ],
630
- "venue": "M.A. Chidambaram Stadium, Chennai"
631
  },
632
  {
633
- "match_id": "20260519_1",
634
- "date": "2026-05-19",
635
  "time": "7:30 PM",
636
  "teams": [
637
- "RR",
638
- "LSG"
 
 
 
 
 
 
 
 
 
 
639
  ],
640
- "venue": "Sawai Mansingh Stadium, Jaipur"
641
  },
642
  {
643
- "match_id": "20260520_1",
644
- "date": "2026-05-20",
645
  "time": "7:30 PM",
646
  "teams": [
647
- "KKR",
 
 
 
 
 
 
 
 
 
 
648
  "MI"
649
  ],
650
- "venue": "Eden Gardens, Kolkata"
651
  },
652
  {
653
- "match_id": "20260521_1",
654
- "date": "2026-05-21",
655
  "time": "7:30 PM",
656
  "teams": [
657
- "CSK",
658
- "GT"
659
  ],
660
- "venue": "M.A. Chidambaram Stadium, Chennai"
661
  },
662
  {
663
- "match_id": "20260522_1",
664
- "date": "2026-05-22",
665
  "time": "7:30 PM",
666
  "teams": [
667
- "SRH",
668
  "RCB"
669
  ],
670
- "venue": "Rajiv Gandhi Intl Stadium, Hyderabad"
671
  },
672
  {
673
- "match_id": "20260523_1",
674
- "date": "2026-05-23",
675
  "time": "7:30 PM",
676
  "teams": [
677
- "LSG",
678
- "PBKS"
679
  ],
680
- "venue": "BRSABV Ekana Stadium, Lucknow"
681
  },
682
  {
683
- "match_id": "20260524_1",
684
- "date": "2026-05-24",
685
- "time": "3:30 PM",
686
  "teams": [
687
- "MI",
688
- "RR"
689
  ],
690
- "venue": "Wankhede Stadium, Mumbai"
691
  },
692
  {
693
- "match_id": "20260524_2",
694
- "date": "2026-05-24",
695
  "time": "7:30 PM",
696
  "teams": [
697
- "KKR",
698
- "DC"
699
  ],
700
- "venue": "Eden Gardens, Kolkata"
701
- }
702
- ]
 
1
  [
2
  {
3
+ "match_id": "20250320_1",
4
+ "date": "2025-03-20",
5
+ "time": "4:10 PM",
6
  "teams": [
7
+ "DIS",
8
+ "DIF"
9
  ],
10
+ "venue": "OTH"
11
  },
12
  {
13
+ "match_id": "20250321_1",
14
+ "date": "2025-03-21",
15
+ "time": "12:00 PM",
16
+ "teams": [
17
+ "DIS",
18
+ "DIF"
19
+ ],
20
+ "venue": "OTH"
21
+ },
22
+ {
23
+ "match_id": "20250322_1",
24
+ "date": "2025-03-22",
25
  "time": "7:30 PM",
26
  "teams": [
27
+ "KKR",
28
+ "RCB"
29
  ],
30
+ "venue": "Kolkata"
31
  },
32
  {
33
+ "match_id": "20250323_1",
34
+ "date": "2025-03-23",
35
+ "time": "3:30 PM",
36
+ "teams": [
37
+ "SRH",
38
+ "RR"
39
+ ],
40
+ "venue": "Hyderabad"
41
+ },
42
+ {
43
+ "match_id": "20250323_2",
44
+ "date": "2025-03-23",
45
  "time": "7:30 PM",
46
  "teams": [
47
+ "CSK",
48
+ "MI"
49
  ],
50
+ "venue": "Chennai"
51
  },
52
  {
53
+ "match_id": "20250324_1",
54
+ "date": "2025-03-24",
55
  "time": "7:30 PM",
56
  "teams": [
57
+ "DC",
58
+ "LSG"
59
  ],
60
+ "venue": "Visakhapatnam"
61
  },
62
  {
63
+ "match_id": "20250325_1",
64
+ "date": "2025-03-25",
65
  "time": "7:30 PM",
66
  "teams": [
67
+ "GT",
68
+ "PBKS"
69
  ],
70
+ "venue": "Ahmedabad"
71
  },
72
  {
73
+ "match_id": "20250326_1",
74
+ "date": "2025-03-26",
75
  "time": "7:30 PM",
76
  "teams": [
77
+ "RR",
78
+ "KKR"
79
  ],
80
+ "venue": "Guwahati"
81
  },
82
  {
83
+ "match_id": "20250327_1",
84
+ "date": "2025-03-27",
85
  "time": "7:30 PM",
86
  "teams": [
87
+ "SRH",
88
+ "LSG"
89
  ],
90
+ "venue": "Hyderabad"
91
  },
92
  {
93
+ "match_id": "20250328_1",
94
+ "date": "2025-03-28",
95
+ "time": "7:30 PM",
96
  "teams": [
97
+ "CSK",
98
+ "RCB"
99
  ],
100
+ "venue": "Chennai"
101
  },
102
  {
103
+ "match_id": "20250329_1",
104
+ "date": "2025-03-29",
105
  "time": "7:30 PM",
106
  "teams": [
107
  "GT",
108
+ "MI"
109
  ],
110
+ "venue": "Ahmedabad"
111
  },
112
  {
113
+ "match_id": "20250330_1",
114
+ "date": "2025-03-30",
115
  "time": "3:30 PM",
116
  "teams": [
117
+ "DC",
118
+ "SRH"
119
  ],
120
+ "venue": "Visakhapatnam"
121
  },
122
  {
123
+ "match_id": "20250330_2",
124
+ "date": "2025-03-30",
125
  "time": "7:30 PM",
126
  "teams": [
127
+ "RR",
128
  "CSK"
129
  ],
130
+ "venue": "Guwahati"
131
  },
132
  {
133
+ "match_id": "20250331_1",
134
+ "date": "2025-03-31",
135
  "time": "7:30 PM",
136
  "teams": [
137
+ "MI",
138
+ "KKR"
139
  ],
140
+ "venue": "Mumbai"
141
  },
142
  {
143
+ "match_id": "20250401_1",
144
+ "date": "2025-04-01",
145
  "time": "7:30 PM",
146
  "teams": [
147
+ "LSG",
148
+ "PBKS"
149
  ],
150
+ "venue": "Lucknow"
151
  },
152
  {
153
+ "match_id": "20250402_1",
154
+ "date": "2025-04-02",
155
  "time": "7:30 PM",
156
  "teams": [
157
+ "RCB",
158
  "GT"
159
  ],
160
+ "venue": "Bengaluru"
161
  },
162
  {
163
+ "match_id": "20250403_1",
164
+ "date": "2025-04-03",
165
  "time": "7:30 PM",
166
  "teams": [
167
  "KKR",
168
+ "SRH"
169
  ],
170
+ "venue": "Kolkata"
171
  },
172
  {
173
+ "match_id": "20250404_1",
174
+ "date": "2025-04-04",
175
  "time": "7:30 PM",
176
  "teams": [
177
+ "LSG",
178
+ "MI"
179
  ],
180
+ "venue": "Lucknow"
181
  },
182
  {
183
+ "match_id": "20250405_1",
184
+ "date": "2025-04-05",
185
  "time": "3:30 PM",
186
  "teams": [
187
+ "CSK",
188
+ "DC"
189
  ],
190
+ "venue": "Chennai"
191
  },
192
  {
193
+ "match_id": "20250405_2",
194
+ "date": "2025-04-05",
195
  "time": "7:30 PM",
196
  "teams": [
197
+ "PBKS",
198
+ "RR"
199
  ],
200
+ "venue": "New Chandigarh"
201
  },
202
  {
203
+ "match_id": "20250406_1",
204
+ "date": "2025-04-06",
205
+ "time": "7:30 PM",
206
  "teams": [
207
+ "SRH",
208
  "GT"
209
  ],
210
+ "venue": "Hyderabad"
211
  },
212
  {
213
+ "match_id": "20250407_1",
214
+ "date": "2025-04-07",
215
  "time": "7:30 PM",
216
  "teams": [
217
  "MI",
218
  "RCB"
219
  ],
220
+ "venue": "Mumbai"
221
  },
222
  {
223
+ "match_id": "20250408_1",
224
+ "date": "2025-04-08",
225
+ "time": "3:30 PM",
226
  "teams": [
227
+ "KKR",
228
+ "LSG"
229
  ],
230
+ "venue": "Kolkata"
231
  },
232
  {
233
+ "match_id": "20250408_2",
234
+ "date": "2025-04-08",
235
  "time": "7:30 PM",
236
  "teams": [
237
+ "PBKS",
238
+ "CSK"
239
  ],
240
+ "venue": "New Chandigarh"
241
  },
242
  {
243
+ "match_id": "20250409_1",
244
+ "date": "2025-04-09",
245
  "time": "7:30 PM",
246
  "teams": [
247
+ "GT",
248
+ "RR"
249
  ],
250
+ "venue": "Ahmedabad"
251
  },
252
  {
253
+ "match_id": "20250410_1",
254
+ "date": "2025-04-10",
255
  "time": "7:30 PM",
256
  "teams": [
257
+ "RCB",
258
+ "DC"
259
  ],
260
+ "venue": "Bengaluru"
261
  },
262
  {
263
+ "match_id": "20250411_1",
264
+ "date": "2025-04-11",
265
  "time": "7:30 PM",
266
  "teams": [
267
+ "CSK",
268
  "KKR"
269
  ],
270
+ "venue": "Chennai"
271
  },
272
  {
273
+ "match_id": "20250412_1",
274
+ "date": "2025-04-12",
275
  "time": "3:30 PM",
276
  "teams": [
277
+ "LSG",
278
+ "GT"
279
  ],
280
+ "venue": "Lucknow"
281
  },
282
  {
283
+ "match_id": "20250412_2",
284
+ "date": "2025-04-12",
285
  "time": "7:30 PM",
286
  "teams": [
287
  "SRH",
288
+ "PBKS"
289
  ],
290
+ "venue": "Hyderabad"
291
  },
292
  {
293
+ "match_id": "20250413_1",
294
+ "date": "2025-04-13",
295
  "time": "3:30 PM",
296
  "teams": [
297
+ "RR",
298
+ "RCB"
299
  ],
300
+ "venue": "Jaipur"
301
  },
302
  {
303
+ "match_id": "20250413_2",
304
+ "date": "2025-04-13",
305
  "time": "7:30 PM",
306
  "teams": [
307
+ "DC",
308
+ "MI"
309
  ],
310
+ "venue": "Delhi"
311
  },
312
  {
313
+ "match_id": "20250414_1",
314
+ "date": "2025-04-14",
315
  "time": "7:30 PM",
316
  "teams": [
317
+ "LSG",
318
+ "CSK"
319
  ],
320
+ "venue": "Lucknow"
321
  },
322
  {
323
+ "match_id": "20250415_1",
324
+ "date": "2025-04-15",
325
  "time": "7:30 PM",
326
  "teams": [
327
+ "PBKS",
328
+ "KKR"
329
  ],
330
+ "venue": "New Chandigarh"
331
  },
332
  {
333
+ "match_id": "20250416_1",
334
+ "date": "2025-04-16",
335
  "time": "7:30 PM",
336
  "teams": [
337
+ "DC",
338
  "RR"
339
  ],
340
+ "venue": "Delhi"
341
  },
342
  {
343
+ "match_id": "20250417_1",
344
+ "date": "2025-04-17",
345
  "time": "7:30 PM",
346
  "teams": [
347
  "MI",
348
+ "SRH"
349
  ],
350
+ "venue": "Mumbai"
351
  },
352
  {
353
+ "match_id": "20250418_1",
354
+ "date": "2025-04-18",
355
+ "time": "9:45 PM",
356
  "teams": [
357
  "RCB",
358
+ "PBKS"
359
  ],
360
+ "venue": "Bengaluru"
361
  },
362
  {
363
+ "match_id": "20250419_1",
364
+ "date": "2025-04-19",
365
+ "time": "5:15 PM",
366
  "teams": [
367
+ "GT",
368
+ "DC"
369
  ],
370
+ "venue": "Ahmedabad"
371
  },
372
  {
373
+ "match_id": "20250419_2",
374
+ "date": "2025-04-19",
375
+ "time": "5:30 PM",
376
  "teams": [
377
  "RR",
378
+ "LSG"
379
  ],
380
+ "venue": "Jaipur"
381
  },
382
  {
383
+ "match_id": "20250420_1",
384
+ "date": "2025-04-20",
385
+ "time": "6:00 PM",
386
  "teams": [
387
+ "PBKS",
388
+ "RCB"
389
+ ],
390
+ "venue": "New Chandigarh"
391
+ },
392
+ {
393
+ "match_id": "20250420_2",
394
+ "date": "2025-04-20",
395
+ "time": "10:45 AM",
396
+ "teams": [
397
+ "MI",
398
  "CSK"
399
  ],
400
+ "venue": "Mumbai"
401
+ },
402
+ {
403
+ "match_id": "20250421_1",
404
+ "date": "2025-04-21",
405
+ "time": "7:30 PM",
406
+ "teams": [
407
+ "KKR",
408
+ "GT"
409
+ ],
410
+ "venue": "Kolkata"
411
  },
412
  {
413
+ "match_id": "20250422_1",
414
+ "date": "2025-04-22",
415
  "time": "7:30 PM",
416
  "teams": [
417
  "LSG",
418
+ "DC"
419
  ],
420
+ "venue": "Lucknow"
421
  },
422
  {
423
+ "match_id": "20250423_1",
424
+ "date": "2025-04-23",
425
  "time": "7:30 PM",
426
  "teams": [
427
+ "SRH",
428
+ "MI"
429
  ],
430
+ "venue": "Hyderabad"
431
  },
432
  {
433
+ "match_id": "20250424_1",
434
+ "date": "2025-04-24",
435
  "time": "7:30 PM",
436
  "teams": [
437
+ "RCB",
438
  "RR"
439
  ],
440
+ "venue": "Bengaluru"
441
  },
442
  {
443
+ "match_id": "20250425_1",
444
+ "date": "2025-04-25",
445
  "time": "7:30 PM",
446
  "teams": [
447
+ "CSK",
448
  "SRH"
449
  ],
450
+ "venue": "Chennai"
451
  },
452
  {
453
+ "match_id": "20250426_1",
454
+ "date": "2025-04-26",
455
  "time": "7:30 PM",
456
  "teams": [
457
+ "KKR",
458
+ "PBKS"
459
+ ],
460
+ "venue": "Kolkata"
461
+ },
462
+ {
463
+ "match_id": "20250427_1",
464
+ "date": "2025-04-27",
465
+ "time": "3:30 PM",
466
+ "teams": [
467
+ "MI",
468
+ "LSG"
469
+ ],
470
+ "venue": "Mumbai"
471
+ },
472
+ {
473
+ "match_id": "20250427_2",
474
+ "date": "2025-04-27",
475
+ "time": "7:30 PM",
476
+ "teams": [
477
+ "DC",
478
  "RCB"
479
  ],
480
+ "venue": "Delhi"
481
  },
482
  {
483
+ "match_id": "20250428_1",
484
+ "date": "2025-04-28",
485
  "time": "7:30 PM",
486
  "teams": [
487
  "RR",
488
+ "GT"
489
  ],
490
+ "venue": "Jaipur"
491
  },
492
  {
493
+ "match_id": "20250429_1",
494
+ "date": "2025-04-29",
495
+ "time": "7:30 PM",
496
+ "teams": [
497
+ "DC",
498
+ "KKR"
499
+ ],
500
+ "venue": "Delhi"
501
+ },
502
+ {
503
+ "match_id": "20250430_1",
504
+ "date": "2025-04-30",
505
  "time": "7:30 PM",
506
  "teams": [
507
  "CSK",
508
+ "PBKS"
509
+ ],
510
+ "venue": "Chennai"
511
+ },
512
+ {
513
+ "match_id": "20250501_1",
514
+ "date": "2025-05-01",
515
+ "time": "7:30 PM",
516
+ "teams": [
517
+ "RR",
518
  "MI"
519
  ],
520
+ "venue": "Jaipur"
521
+ },
522
+ {
523
+ "match_id": "20250502_1",
524
+ "date": "2025-05-02",
525
+ "time": "7:30 PM",
526
+ "teams": [
527
+ "GT",
528
+ "SRH"
529
+ ],
530
+ "venue": "Ahmedabad"
531
  },
532
  {
533
+ "match_id": "20250503_1",
534
+ "date": "2025-05-03",
535
+ "time": "7:30 PM",
536
+ "teams": [
537
+ "RCB",
538
+ "CSK"
539
+ ],
540
+ "venue": "Bengaluru"
541
+ },
542
+ {
543
+ "match_id": "20250504_1",
544
+ "date": "2025-05-04",
545
  "time": "3:30 PM",
546
  "teams": [
547
+ "KKR",
548
+ "RR"
549
  ],
550
+ "venue": "Kolkata"
551
  },
552
  {
553
+ "match_id": "20250504_2",
554
+ "date": "2025-05-04",
555
  "time": "7:30 PM",
556
  "teams": [
557
+ "PBKS",
558
+ "LSG"
559
+ ],
560
+ "venue": "Dharamsala"
561
+ },
562
+ {
563
+ "match_id": "20250505_1",
564
+ "date": "2025-05-05",
565
+ "time": "7:30 PM",
566
+ "teams": [
567
+ "SRH",
568
+ "DC"
569
  ],
570
+ "venue": "Hyderabad"
571
  },
572
  {
573
+ "match_id": "20250506_1",
574
+ "date": "2025-05-06",
575
  "time": "7:30 PM",
576
  "teams": [
577
  "MI",
578
+ "GT"
579
  ],
580
+ "venue": "Mumbai"
581
  },
582
  {
583
+ "match_id": "20250507_1",
584
+ "date": "2025-05-07",
585
  "time": "7:30 PM",
586
  "teams": [
587
+ "KKR",
588
  "CSK"
589
  ],
590
+ "venue": "Kolkata"
591
  },
592
  {
593
+ "match_id": "20250508_1",
594
+ "date": "2025-05-08",
595
  "time": "7:30 PM",
596
  "teams": [
597
+ "PBKS",
598
+ "DC"
599
  ],
600
+ "venue": "Dharamsala"
601
  },
602
  {
603
+ "match_id": "20250509_1",
604
+ "date": "2025-05-09",
605
  "time": "7:30 PM",
606
  "teams": [
607
  "LSG",
608
  "RCB"
609
  ],
610
+ "venue": "Lucknow"
611
  },
612
  {
613
+ "match_id": "20250510_1",
614
+ "date": "2025-05-10",
615
  "time": "7:30 PM",
616
  "teams": [
617
+ "SRH",
618
  "KKR"
619
  ],
620
+ "venue": "Hyderabad"
621
+ },
622
+ {
623
+ "match_id": "20250511_1",
624
+ "date": "2025-05-11",
625
+ "time": "3:30 PM",
626
+ "teams": [
627
+ "PBKS",
628
+ "MI"
629
+ ],
630
+ "venue": "Dharamsala"
631
  },
632
  {
633
+ "match_id": "20250511_2",
634
+ "date": "2025-05-11",
635
  "time": "7:30 PM",
636
  "teams": [
637
+ "DC",
638
  "GT"
639
  ],
640
+ "venue": "Delhi"
641
  },
642
  {
643
+ "match_id": "20250512_1",
644
+ "date": "2025-05-12",
645
  "time": "7:30 PM",
646
  "teams": [
647
  "CSK",
648
+ "RR"
649
  ],
650
+ "venue": "Chennai"
651
  },
652
  {
653
+ "match_id": "20250513_1",
654
+ "date": "2025-05-13",
655
  "time": "7:30 PM",
656
  "teams": [
657
  "RCB",
658
+ "SRH"
659
  ],
660
+ "venue": "Bengaluru"
661
  },
662
  {
663
+ "match_id": "20250514_1",
664
+ "date": "2025-05-14",
665
  "time": "7:30 PM",
666
  "teams": [
667
+ "GT",
668
+ "LSG"
669
  ],
670
+ "venue": "Ahmedabad"
671
  },
672
  {
673
+ "match_id": "20250515_1",
674
+ "date": "2025-05-15",
675
  "time": "7:30 PM",
676
  "teams": [
677
+ "MI",
678
+ "DC"
679
  ],
680
+ "venue": "Mumbai"
681
  },
682
  {
683
+ "match_id": "20250517_1",
684
+ "date": "2025-05-17",
685
  "time": "7:30 PM",
686
  "teams": [
687
  "RCB",
688
  "KKR"
689
  ],
690
+ "venue": "Bengaluru"
691
+ },
692
+ {
693
+ "match_id": "20250518_1",
694
+ "date": "2025-05-18",
695
+ "time": "3:30 PM",
696
+ "teams": [
697
+ "RR",
698
+ "PBKS"
699
+ ],
700
+ "venue": "Ahmedabad"
701
  },
702
  {
703
+ "match_id": "20250518_2",
704
+ "date": "2025-05-18",
705
  "time": "7:30 PM",
706
  "teams": [
707
+ "DC",
708
+ "GT"
709
  ],
710
+ "venue": "Lucknow"
711
  },
712
  {
713
+ "match_id": "20250519_1",
714
+ "date": "2025-05-19",
715
  "time": "7:30 PM",
716
  "teams": [
717
  "LSG",
718
+ "SRH"
719
  ],
720
+ "venue": "Lucknow"
721
  },
722
  {
723
+ "match_id": "20250520_1",
724
+ "date": "2025-05-20",
725
  "time": "7:30 PM",
726
  "teams": [
727
+ "CSK",
728
+ "RR"
729
  ],
730
+ "venue": "Lucknow"
731
  },
732
  {
733
+ "match_id": "20250521_1",
734
+ "date": "2025-05-21",
735
  "time": "7:30 PM",
736
  "teams": [
737
+ "MI",
738
+ "DC"
739
  ],
740
+ "venue": "Lucknow"
741
  },
742
  {
743
+ "match_id": "20250522_1",
744
+ "date": "2025-05-22",
745
  "time": "7:30 PM",
746
  "teams": [
747
+ "GT",
748
+ "LSG"
749
  ],
750
+ "venue": "Lucknow"
751
  },
752
  {
753
+ "match_id": "20250523_1",
754
+ "date": "2025-05-23",
755
  "time": "7:30 PM",
756
  "teams": [
757
+ "RCB",
758
  "SRH"
759
  ],
760
+ "venue": "Lucknow"
761
  },
762
  {
763
+ "match_id": "20250524_1",
764
+ "date": "2025-05-24",
765
  "time": "7:30 PM",
766
  "teams": [
767
+ "PBKS",
768
+ "DC"
769
+ ],
770
+ "venue": "Lucknow"
771
+ },
772
+ {
773
+ "match_id": "20250525_1",
774
+ "date": "2025-05-25",
775
+ "time": "3:30 PM",
776
+ "teams": [
777
+ "GT",
778
+ "CSK"
779
  ],
780
+ "venue": "Lucknow"
781
  },
782
  {
783
+ "match_id": "20250525_2",
784
+ "date": "2025-05-25",
785
  "time": "7:30 PM",
786
  "teams": [
787
+ "SRH",
788
+ "KKR"
789
+ ],
790
+ "venue": "Lucknow"
791
+ },
792
+ {
793
+ "match_id": "20250526_1",
794
+ "date": "2025-05-26",
795
+ "time": "7:30 PM",
796
+ "teams": [
797
+ "PBKS",
798
  "MI"
799
  ],
800
+ "venue": "Lucknow"
801
  },
802
  {
803
+ "match_id": "20250527_1",
804
+ "date": "2025-05-27",
805
  "time": "7:30 PM",
806
  "teams": [
807
+ "LSG",
808
+ "RCB"
809
  ],
810
+ "venue": "Lucknow"
811
  },
812
  {
813
+ "match_id": "20250529_1",
814
+ "date": "2025-05-29",
815
  "time": "7:30 PM",
816
  "teams": [
817
+ "PBKS",
818
  "RCB"
819
  ],
820
+ "venue": "Lucknow"
821
  },
822
  {
823
+ "match_id": "20250530_1",
824
+ "date": "2025-05-30",
825
  "time": "7:30 PM",
826
  "teams": [
827
+ "GT",
828
+ "MI"
829
  ],
830
+ "venue": "Lucknow"
831
  },
832
  {
833
+ "match_id": "20250601_1",
834
+ "date": "2025-06-01",
835
+ "time": "7:30 PM",
836
  "teams": [
837
+ "PBKS",
838
+ "MI"
839
  ],
840
+ "venue": "Lucknow"
841
  },
842
  {
843
+ "match_id": "20250603_1",
844
+ "date": "2025-06-03",
845
  "time": "7:30 PM",
846
  "teams": [
847
+ "MI",
848
+ "RCB"
849
  ],
850
+ "venue": "Lucknow"
851
+ }
852
+ ]
players.json CHANGED
@@ -1,268 +1,312 @@
1
  {
2
- "CSK": [
3
- "Akeal Hosein",
4
- "Aman Khan",
5
- "Anshul Kamboj",
6
- "Ayush Mhatre",
7
- "Dewald Brevis",
8
- "Gurjapneet Singh",
9
- "Jamie Overton",
10
- "Kartik Sharma",
11
- "Khaleel Ahmed",
12
- "Matthew Short",
13
- "MS Dhoni",
14
- "Mukesh Choudhary",
15
- "Nathan Ellis",
16
- "Noor Ahmad",
17
- "Prashant Veer",
18
- "Rahul Chahar",
19
- "Ramakrishna Ghosh",
20
- "Ruturaj Gaikwad",
21
- "Sanju Samson",
22
- "Sarfaraz Khan",
23
- "Shivam Dube",
24
- "Shreyas Gopal",
25
- "Urvil Patel"
 
 
26
  ],
27
- "SRH": [
28
- "Abhishek Sharma",
29
- "Amit Kumar",
30
- "Aniket Verma",
31
- "Brydon Carse",
32
- "Eshan Malinga",
33
- "Harsh Dubey",
34
- "Harshal Patel",
35
- "Heinrich Klaasen",
36
- "Ishan Kishan",
37
- "Jack Edwards",
38
- "Jaydev Unadkat",
39
- "Kamindu Mendis",
40
- "Krains Fuletra",
41
- "Liam Livingstone",
42
- "Nitish Reddy",
43
- "Onkar Tarmale",
44
- "Pat Cummins",
45
- "Praful Hinge",
46
- "R Smaran",
47
- "Sakib Hussain",
48
- "Salil Arora",
49
- "Shivam Mavi",
50
- "Shivang Kumar",
51
- "Travis Head",
52
- "Zeeshan Ansari"
53
  ],
54
- "RR": [
55
- "Adam Milne",
56
- "Dhruv Jurel",
57
- "Donovan Ferreira",
58
- "Jofra Archer",
59
- "Kuldeep Sen",
60
- "Kwena Maphaka",
61
- "Lhuan-dre Pretorius",
62
- "Nandre Burger",
63
- "Ravi Bishnoi",
64
- "Ravi Singh",
65
- "Ravindra Jadeja",
66
- "Riyan Parag",
67
- "Sam Curran",
68
- "Sandeep Sharma",
69
- "Shimron Hetmyer",
70
- "Shubham Dubey",
71
- "Sushant Mishra",
72
- "Tushar Deshpande",
73
- "Vaibhav Suryavanshi",
74
- "Vignesh Puthur",
75
- "Yash Raj Punja",
76
- "Yashasvi Jaiswal",
77
- "Yudhvir Charak"
 
 
 
 
 
 
 
 
78
  ],
79
  "DC": [
80
- "Abishek Porel",
81
- "Ajay Mandal",
82
- "Ashutosh Sharma",
83
- "Auqib Dar",
84
- "Axar Patel",
85
- "Ben Duckett",
86
- "David Miller",
87
- "Dushmantha Chameera",
88
- "Karun Nair",
89
- "KL Rahul",
90
- "Kuldeep Yadav",
91
- "Kyle Jamieson",
92
- "Lungi Ngidi",
93
- "Madhav Tiwari",
94
- "Mitchell Starc",
95
- "Mukesh Kumar",
96
- "Nitish Rana",
97
- "Pathum Nissanka",
98
- "Prithvi Shaw",
99
- "Sahil Parakh",
100
- "Sameer Rizvi",
101
- "T Natarajan",
102
- "Tripurana Vijay",
103
- "Tristan Stubbs",
104
- "Vipraj Nigam"
105
- ],
106
- "KKR": [
107
- "Ajinkya Rahane",
108
- "Akash Deep",
109
- "Angkrish Raghuvanshi",
110
- "Anukul Roy",
111
- "Cameron Green",
112
- "Daksh Kamra",
113
- "Finn Allen",
114
- "Harshit Rana",
115
- "Kartik Tyagi",
116
- "Manish Pandey",
117
- "Matheesha Pathirana",
118
- "Mustafizur Rahman",
119
- "Prashant Solanki",
120
- "Rachin Ravindra",
121
- "Rahul Tripathi",
122
- "Ramandeep Singh",
123
- "Rinku Singh",
124
- "Rovman Powell",
125
- "Sarthak Ranjan",
126
- "Sunil Narine",
127
- "Tejasvi Singh",
128
- "Tim Seifert",
129
- "Umran Malik",
130
- "Vaibhav Arora",
131
- "Varun Chakaravarthy"
132
  ],
133
  "GT": [
134
- "Anuj Rawat",
135
- "Arshad Khan",
136
- "Ashok Sharma",
137
- "Glenn Phillips",
138
- "Gurnoor Singh Brar",
139
- "Ishant Sharma",
140
- "Jason Holder",
141
- "Jayant Yadav",
142
- "Jos Buttler",
143
- "Kagiso Rabada",
144
- "Kumar Kushagra",
145
- "Luke Wood",
146
- "Manav Suthar",
147
- "Mohammed Siraj",
148
- "Nishant Sindhu",
149
- "Prasidh Krishna",
150
- "Prithviraj Yarra",
151
- "Rahul Tewatia",
152
- "Rashid Khan",
153
- "Sai Kishore",
154
- "Sai Sudharsan",
155
- "Shahrukh Khan",
156
- "Shubman Gill",
157
- "Tom Banton",
158
- "Washington Sundar"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  ],
160
  "LSG": [
 
 
 
 
 
 
 
161
  "Abdul Samad",
 
 
 
 
 
162
  "Aiden Markram",
163
- "Akash Singh",
164
- "Akshat Raghuwanshi",
165
- "Anrich Nortje",
166
- "Arjun Tendulkar",
167
  "Arshin Kulkarni",
168
- "Avesh Khan",
169
- "Ayush Badoni",
170
  "Digvesh Rathi",
171
- "Himmat Singh",
172
- "Josh Inglis",
173
- "Manimaran Siddharth",
174
- "Matthew Breetzke",
175
- "Mayank Yadav",
176
- "Mitchell Marsh",
177
- "Mohammed Shami",
178
- "Mohsin Khan",
179
- "Mukul Choudhary",
180
- "Naman Tiwari",
181
- "Nicholas Pooran",
182
  "Prince Yadav",
183
- "Rishabh Pant",
184
- "Shahbaz Ahmed",
185
- "Wanindu Hasaranga"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  ],
187
  "PBKS": [
 
188
  "Arshdeep Singh",
189
- "Azmatullah Omarzai",
190
- "Ben Dwarshuis",
191
- "Cooper Connolly",
192
- "Harnoor Pannu",
193
- "Harpreet Brar",
194
- "Lockie Ferguson",
195
- "Marco Jansen",
196
  "Marcus Stoinis",
197
- "Mitchell Owen",
198
- "Musheer Khan",
199
  "Nehal Wadhera",
 
200
  "Prabhsimran Singh",
201
- "Pravin Dubey",
202
  "Priyansh Arya",
203
- "Pyala Avinash",
204
- "Shashank Singh",
205
- "Shreyas Iyer",
206
- "Suryansh Shedge",
207
- "Vishal Nishad",
 
 
208
  "Vishnu Vinod",
209
- "Vyshak Vijaykumar",
210
  "Xavier Bartlett",
211
- "Yash Thakur",
212
- "Yuzvendra Chahal"
 
 
 
213
  ],
214
- "MI": [
215
- "AM Ghazanfar",
216
- "Ashwani Kumar",
217
- "Atharva Ankolekar",
218
- "Corbin Bosch",
219
- "Danish Malewar",
220
- "Deepak Chahar",
221
- "Hardik Pandya",
222
- "Jasprit Bumrah",
223
- "Mayank Markande",
224
- "Mayank Rawat",
225
- "Mitchell Santner",
226
- "Mohammad Izhar",
227
- "Naman Dhir",
228
- "Quinton de Kock",
229
- "Raghu Sharma",
230
- "Raj Bawa",
231
- "Robin Minz",
232
- "Rohit Sharma",
233
- "Ryan Rickelton",
234
- "Shardul Thakur",
235
- "Sherfane Rutherford",
236
- "Suryakumar Yadav",
237
- "Tilak Varma",
238
- "Trent Boult",
239
- "Will Jacks"
240
  ],
241
  "RCB": [
242
- "Abhinandan Singh",
243
- "Bhuvneshwar Kumar",
244
- "Devdutt Padikkal",
245
- "Jacob Bethell",
246
- "Jacob Duffy",
247
- "Jitesh Sharma",
248
- "Jordan Cox",
249
- "Josh Hazlewood",
250
- "Kanishk Chouhan",
251
- "Krunal Pandya",
252
- "Mangesh Yadav",
253
- "Nuwan Thushara",
254
- "Phil Salt",
255
- "Rajat Patidar",
256
- "Rasikh Salam",
257
- "Romario Shepherd",
258
- "Satvik Deswal",
259
- "Suyash Sharma",
260
- "Swapnil Singh",
261
- "Tim David",
262
- "Venkatesh Iyer",
263
- "Vicky Ostwal",
264
- "Vihaan Malhotra",
265
- "Virat Kohli",
266
- "Yash Dayal"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  ]
268
- }
 
1
  {
2
+ "DIS": [
3
+ "MS Dhoni",
4
+ "Devon Conway",
5
+ "Ruturaj Gaikwad",
6
+ "Ajinkya Rahane",
7
+ "Shaik Rasheed",
8
+ "Ravindra Jadeja",
9
+ "Mitchell Santner",
10
+ "Moeen Ali",
11
+ "Shivam Dube",
12
+ "Nishant Sindhu",
13
+ "Ajay Mandal",
14
+ "Rajvardhan Hangargekar",
15
+ "Deepak Chahar",
16
+ "Maheesh Theekshana",
17
+ "Mukesh Chowdhary",
18
+ "Prashant Solanki",
19
+ "Simarjeet Singh",
20
+ "Tushar Deshpande",
21
+ "Matheesha Pathirana",
22
+ "Rachin Ravindra",
23
+ "Shardul Thakur",
24
+ "Daryl Mitchell",
25
+ "Sameer Rizvi",
26
+ "Mustafizur Rahman",
27
+ "Avanish Rao Aravelly"
28
  ],
29
+ "DIF": [
30
+ "Faf du Plessis",
31
+ "Glenn Maxwell",
32
+ "Virat Kohli",
33
+ "Rajat Patidar",
34
+ "Anuj Rawat",
35
+ "Dinesh Karthik",
36
+ "Suyash Prabhudessai",
37
+ "Will Jacks",
38
+ "Mahipal Lomror",
39
+ "Karn Sharma",
40
+ "Manoj Bhandage",
41
+ "Mayank Dagar",
42
+ "Vyshak Vijaykumar",
43
+ "Akash Deep",
44
+ "Mohammed Siraj",
45
+ "Reece Topley",
46
+ "Himanshu Sharma",
47
+ "Rajan Kumar",
48
+ "Cameron Green",
49
+ "Alzarri Joseph",
50
+ "Yash Dayal",
51
+ "Lockie Ferguson",
52
+ "Tom Curran",
53
+ "Swapnil Singh",
54
+ "Saurav Chauhan"
55
  ],
56
+ "CSK": [
57
+ "MS Dhoni",
58
+ "Ruturaj Gaikwad",
59
+ "Devon Conway",
60
+ "Shivam Dube",
61
+ "Ravindra Jadeja",
62
+ "Moeen Ali",
63
+ "Dewald Brevis",
64
+ "Deepak Chahar",
65
+ "Maheesh Theekshana",
66
+ "Matheesha Pathirana",
67
+ "Tushar Deshpande",
68
+ "Ben Stokes",
69
+ "Noor Ahmad",
70
+ "Ravichandran Ashwin",
71
+ "Khaleel Ahmed",
72
+ "Rachin Ravindra",
73
+ "Rahul Tripathi",
74
+ "Anshul Kamboj",
75
+ "Sam Curran",
76
+ "Gurjapneet Singh",
77
+ "Nathan Ellis",
78
+ "Deepak Hooda",
79
+ "Jamie Overton",
80
+ "Vijay Shankar",
81
+ "Vansh Bedi",
82
+ "Shreyas Gopal",
83
+ "C Andre Siddarth",
84
+ "Ramakrishna Ghosh",
85
+ "Shaik Rasheed",
86
+ "Mukesh Choudhary",
87
+ "Kamlesh Nagarkoti"
88
  ],
89
  "DC": [
90
+ "Axar Patel",
91
+ "KL Rahul",
92
+ "Kuldeep Yadav",
93
+ "Mitchell Starc",
94
+ "T Natarajan",
95
+ "Tristan Stubbs",
96
+ "Jake Fraser-McGurk",
97
+ "Mukesh Kumar",
98
+ "Harry Brook",
99
+ "Abishek Porel",
100
+ "Ashutosh Sharma",
101
+ "Mohit Sharma",
102
+ "Faf du Plessis",
103
+ "Sameer Rizvi",
104
+ "Donovan Ferreira",
105
+ "Dushmantha Chameera",
106
+ "Vipraj Nigam",
107
+ "Karun Nair",
108
+ "Madhav Tiwari",
109
+ "Manvanth Kumar",
110
+ "Tripurana Vijay",
111
+ "Darshan Nalkande",
112
+ "Ajay Mandal"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  ],
114
  "GT": [
115
+ "Shubman Gill",
116
+ "Jos Buttler",
117
+ "Sherfane Rutherford",
118
+ "Glenn Phillips",
119
+ "Kumar Kushagra",
120
+ "Anuj Rawat",
121
+ "Rashid Khan",
122
+ "Mohammed Siraj",
123
+ "Kagiso Rabada",
124
+ "Prasidh Krishna",
125
+ "Gerald Coetzee",
126
+ "Gurnoor Brar",
127
+ "Arshad Khan",
128
+ "Ishant Sharma",
129
+ "B Sai Sudharsan",
130
+ "M Shahrukh Khan",
131
+ "Rahul Tewatia",
132
+ "Washington Sundar",
133
+ "Sai Kishore",
134
+ "Mahipal Lomror",
135
+ "Karim Janat",
136
+ "Jayant Yadav",
137
+ "Nishant Sindhu",
138
+ "Manav Suthar",
139
+ "Kulwant Khejroliya"
140
+ ],
141
+ "KKR": [
142
+ "Rinku Singh",
143
+ "Varun Chakaravarthy",
144
+ "Sunil Narine",
145
+ "Andre Russell",
146
+ "Venkatesh Iyer",
147
+ "Quinton de Kock",
148
+ "Anrich Nortje",
149
+ "Rahmanullah Gurbaz",
150
+ "Moeen Ali",
151
+ "Spencer Johnson",
152
+ "Rovman Powell",
153
+ "Ajinkya Rahane",
154
+ "Umran Malik",
155
+ "Manish Pandey",
156
+ "Harshit Rana",
157
+ "Ramandeep Singh",
158
+ "Angkrish Raghuvanshi",
159
+ "Vaibhav Arora",
160
+ "Anukul Roy",
161
+ "Mayank Markande",
162
+ "Luvnith Sisodia"
163
  ],
164
  "LSG": [
165
+ "Rishabh Pant",
166
+ "Nicholas Pooran",
167
+ "Mayank Yadav",
168
+ "Ravi Bishnoi",
169
+ "Avesh Khan",
170
+ "Akash Deep",
171
+ "David Miller",
172
  "Abdul Samad",
173
+ "Ayush Badoni",
174
+ "Shardul Thakur",
175
+ "Mohsin Khan",
176
+ "Mitchell Marsh",
177
+ "Shahbaz Ahmed",
178
  "Aiden Markram",
179
+ "Matthew Breetzke",
180
+ "Shamar Joseph",
181
+ "Manimaran Siddharth",
182
+ "Himmat Singh",
183
  "Arshin Kulkarni",
 
 
184
  "Digvesh Rathi",
 
 
 
 
 
 
 
 
 
 
 
185
  "Prince Yadav",
186
+ "Yuvraj Chaudhary",
187
+ "Akash Singh",
188
+ "Rajvardhan Hangargekar",
189
+ "Aryan Juyal"
190
+ ],
191
+ "MI": [
192
+ "Jasprit Bumrah",
193
+ "Hardik Pandya",
194
+ "Suryakumar Yadav",
195
+ "Rohit Sharma",
196
+ "Trent Boult",
197
+ "Deepak Chahar",
198
+ "Tilak Varma",
199
+ "Naman Dhir",
200
+ "Will Jacks",
201
+ "AM Ghazanfar",
202
+ "Mitchell Santner",
203
+ "Ryan Rickelton",
204
+ "Reece Topley",
205
+ "Lizaad Williams",
206
+ "Robin Minz",
207
+ "Karn Sharma",
208
+ "Vignesh Puthur",
209
+ "Bevon Jacobs",
210
+ "Satyanarayana Raju",
211
+ "Raj Bawa",
212
+ "Ashwani Kumar",
213
+ "Arjun Tendulkar",
214
+ "Krishnan Shrijith",
215
+ "Corbin Bosch"
216
  ],
217
  "PBKS": [
218
+ "Shreyas Iyer",
219
  "Arshdeep Singh",
220
+ "Yuzvendra Chahal",
 
 
 
 
 
 
221
  "Marcus Stoinis",
222
+ "Marco Jansen",
223
+ "Shashank Singh",
224
  "Nehal Wadhera",
225
+ "Glenn Maxwell",
226
  "Prabhsimran Singh",
 
227
  "Priyansh Arya",
228
+ "Josh Inglis",
229
+ "Azmatullah Omarzai",
230
+ "Lockie Ferguson",
231
+ "Vijaykumar Vyshak",
232
+ "Yash Thakur",
233
+ "Harpreet Brar",
234
+ "Aaron Hardie",
235
  "Vishnu Vinod",
236
+ "Kuldeep Sen",
237
  "Xavier Bartlett",
238
+ "Suryansh Shedge",
239
+ "Pyla Avinash",
240
+ "Musheer Khan",
241
+ "Harnoor Singh",
242
+ "Praveen Dubey"
243
  ],
244
+ "RR": [
245
+ "Sanju Samson",
246
+ "Yashasvi Jaiswal",
247
+ "Riyan Parag",
248
+ "Dhruv Jurel",
249
+ "Shimron Hetmyer",
250
+ "Sandeep Sharma",
251
+ "Jofra Archer",
252
+ "Tushar Deshpande",
253
+ "Wanindu Hasaranga",
254
+ "Maheesh Theekshana",
255
+ "Nitish Rana",
256
+ "Fazalhaq Farooqi",
257
+ "Kwena Maphaka",
258
+ "Akash Madhwal",
259
+ "Vaibhav Suryavanshi",
260
+ "Shubham Dubey",
261
+ "Yudhvir Singh",
262
+ "Kunal Singh Rathore",
263
+ "Ashok Sharma",
264
+ "Kumar Kartikeya"
 
 
 
 
 
265
  ],
266
  "RCB": [
267
+ "Virat Kohli",
268
+ "Rajat Patidar",
269
+ "Yash Dayal",
270
+ "Liam Livingstone",
271
+ "Phil Salt",
272
+ "Jitesh Sharma",
273
+ "Josh Hazlewood",
274
+ "Bhuvneshwar Kumar",
275
+ "Krunal Pandya",
276
+ "Tim David",
277
+ "Suyash Sharma",
278
+ "Rasikh Dar",
279
+ "Lungi Ngidi",
280
+ "Jacob Bethell",
281
+ "Romario Shepherd",
282
+ "Nuwan Thushara",
283
+ "Devdutt Padikkal",
284
+ "Swapnil Singh",
285
+ "Swastik Chikara",
286
+ "Manoj Bhandage",
287
+ "Abhinandan Singh",
288
+ "Mohit Rathee"
289
+ ],
290
+ "SRH": [
291
+ "Heinrich Klaasen",
292
+ "Pat Cummins",
293
+ "Abhishek Sharma",
294
+ "Travis Head",
295
+ "Ishan Kishan",
296
+ "Mohammed Shami",
297
+ "Harshal Patel",
298
+ "Nitish Kumar Reddy",
299
+ "Rahul Chahar",
300
+ "Abhinav Manohar",
301
+ "Adam Zampa",
302
+ "Simarjeet Singh",
303
+ "Eshan Malinga",
304
+ "Brydon Carse",
305
+ "Jaydev Unadkat",
306
+ "Kamindu Mendis",
307
+ "Zeeshan Ansari",
308
+ "Aniket Verma",
309
+ "Atharva Taide",
310
+ "Sachin Baby"
311
  ]
312
+ }
requirements.txt CHANGED
@@ -1,3 +1,6 @@
1
- flask>=3.0.0
2
- gunicorn>=22.0.0
3
- huggingface_hub>=1.7.0
 
 
 
 
1
+ datasets
2
+ huggingface_hub
3
+ pandas
4
+ pytz
5
+ requests
6
+ streamlit
scripts/build_ipl2026_data.py DELETED
@@ -1,165 +0,0 @@
1
- """One-off builder: IPL 2026 league schedule (BBC) + squads (Firstpost Dec 2025)."""
2
- import json
3
- import re
4
- from collections import defaultdict
5
- from pathlib import Path
6
-
7
- ROOT = Path(__file__).resolve().parents[1]
8
-
9
- # League only, chronological. (date, early_slot, home_abbr, away_abbr)
10
- # early_slot True => 3:30 PM IST (11:00 BST), else 7:30 PM IST (15:00 BST)
11
- FIXTURES = [
12
- ("2026-03-28", False, "RCB", "SRH"),
13
- ("2026-03-29", False, "MI", "KKR"),
14
- ("2026-03-30", False, "RR", "CSK"),
15
- ("2026-03-31", False, "PBKS", "GT"),
16
- ("2026-04-01", False, "LSG", "DC"),
17
- ("2026-04-02", False, "KKR", "SRH"),
18
- ("2026-04-03", False, "CSK", "PBKS"),
19
- ("2026-04-04", True, "DC", "MI"),
20
- ("2026-04-04", False, "GT", "RR"),
21
- ("2026-04-05", True, "SRH", "LSG"),
22
- ("2026-04-05", False, "RCB", "CSK"),
23
- ("2026-04-06", False, "KKR", "PBKS"),
24
- ("2026-04-07", False, "RR", "MI"),
25
- ("2026-04-08", False, "DC", "GT"),
26
- ("2026-04-09", False, "KKR", "LSG"),
27
- ("2026-04-10", False, "RR", "RCB"),
28
- ("2026-04-11", True, "PBKS", "SRH"),
29
- ("2026-04-11", False, "CSK", "DC"),
30
- ("2026-04-12", True, "LSG", "GT"),
31
- ("2026-04-12", False, "MI", "RCB"),
32
- ("2026-04-13", False, "SRH", "RR"),
33
- ("2026-04-14", False, "CSK", "KKR"),
34
- ("2026-04-15", False, "RCB", "LSG"),
35
- ("2026-04-16", False, "MI", "PBKS"),
36
- ("2026-04-17", False, "GT", "KKR"),
37
- ("2026-04-18", True, "RCB", "DC"),
38
- ("2026-04-18", False, "SRH", "CSK"),
39
- ("2026-04-19", True, "KKR", "RR"),
40
- ("2026-04-19", False, "PBKS", "LSG"),
41
- ("2026-04-20", False, "GT", "MI"),
42
- ("2026-04-21", False, "SRH", "DC"),
43
- ("2026-04-22", False, "LSG", "RR"),
44
- ("2026-04-23", False, "MI", "CSK"),
45
- ("2026-04-24", False, "RCB", "GT"),
46
- ("2026-04-25", True, "DC", "PBKS"),
47
- ("2026-04-25", False, "RR", "SRH"),
48
- ("2026-04-26", True, "GT", "CSK"),
49
- ("2026-04-26", False, "LSG", "KKR"),
50
- ("2026-04-27", False, "DC", "RCB"),
51
- ("2026-04-28", False, "PBKS", "RR"),
52
- ("2026-04-29", False, "MI", "SRH"),
53
- ("2026-04-30", False, "GT", "RCB"),
54
- ("2026-05-01", False, "RR", "DC"),
55
- ("2026-05-02", False, "CSK", "MI"),
56
- ("2026-05-03", True, "SRH", "KKR"),
57
- ("2026-05-03", False, "GT", "PBKS"),
58
- ("2026-05-04", False, "MI", "LSG"),
59
- ("2026-05-05", False, "DC", "CSK"),
60
- ("2026-05-06", False, "SRH", "PBKS"),
61
- ("2026-05-07", False, "LSG", "RCB"),
62
- ("2026-05-08", False, "DC", "KKR"),
63
- ("2026-05-09", False, "RR", "GT"),
64
- ("2026-05-10", False, "CSK", "LSG"),
65
- ("2026-05-10", False, "RCB", "MI"),
66
- ("2026-05-11", False, "PBKS", "DC"),
67
- ("2026-05-12", False, "GT", "SRH"),
68
- ("2026-05-13", False, "RCB", "KKR"),
69
- ("2026-05-14", False, "PBKS", "MI"),
70
- ("2026-05-15", False, "LSG", "CSK"),
71
- ("2026-05-16", False, "KKR", "GT"),
72
- ("2026-05-17", False, "PBKS", "RCB"),
73
- ("2026-05-17", False, "DC", "RR"),
74
- ("2026-05-18", False, "CSK", "SRH"),
75
- ("2026-05-19", False, "RR", "LSG"),
76
- ("2026-05-20", False, "KKR", "MI"),
77
- ("2026-05-21", False, "CSK", "GT"),
78
- ("2026-05-22", False, "SRH", "RCB"),
79
- ("2026-05-23", False, "LSG", "PBKS"),
80
- ("2026-05-24", True, "MI", "RR"),
81
- ("2026-05-24", False, "KKR", "DC"),
82
- ]
83
-
84
- VENUE_HOME = {
85
- "RCB": "M. Chinnaswamy Stadium, Bengaluru",
86
- "SRH": "Rajiv Gandhi Intl Stadium, Hyderabad",
87
- "MI": "Wankhede Stadium, Mumbai",
88
- "KKR": "Eden Gardens, Kolkata",
89
- "RR": "Sawai Mansingh Stadium, Jaipur",
90
- "CSK": "M.A. Chidambaram Stadium, Chennai",
91
- "PBKS": "Maharaja Yadavindra Stadium, Mohali",
92
- "GT": "Narendra Modi Stadium, Ahmedabad",
93
- "LSG": "BRSABV Ekana Stadium, Lucknow",
94
- "DC": "Arun Jaitley Stadium, Delhi",
95
- }
96
-
97
- # Firstpost Dec 2025 — strip roles / trade notes in UI
98
- RAW_SQUADS = {
99
- "CSK": "Ruturaj Gaikwad (c), MS Dhoni (wk), Sanju Samson (traded from RR), Ayush Mhatre, Dewald Brevis, Ramakrishna Ghosh, Shivam Dube, Urvil Patel (wk), Anshul Kamboj, Khaleel Ahmed, Mukesh Choudhary, Nathan Ellis, Noor Ahmad, Shreyas Gopal, Gurjapneet Singh, Jamie Overton, Akeal Hosein, Prashant Veer, Kartik Sharma, Matthew Short, Aman Khan, Sarfaraz Khan, Rahul Chahar",
100
- "SRH": "Travis Head, Abhishek Sharma, Aniket Verma, R Smaran, Ishan Kishan, Heinrich Klaasen, Nitish Reddy, Harsh Dubey, Kamindu Mendis, Harshal Patel, Brydon Carse, Pat Cummins (c), Jaydev Unadkat, Eshan Malinga, Zeeshan Ansari, Liam Livingstone, Shivang Kumar, Salil Arora, Sakib Hussain, Onkar Tarmale, Amit Kumar, Praful Hinge, Krains Fuletra, Jack Edwards, Shivam Mavi",
101
- "RR": "Ravindra Jadeja (traded from CSK), Yashasvi Jaiswal, Vaibhav Suryavanshi, Riyan Parag, Dhruv Jurel, Sam Curran (traded from CSK), Jofra Archer, Nandre Burger, Tushar Deshpande, Kwena Maphaka, Shubham Dubey, Shimron Hetmyer, Lhuan-dre Pretorius, Donovan Ferreira (traded from DC), Sandeep Sharma, Yudhvir Charak, Ravi Bishnoi, Sushant Mishra, Yash Raj Punja, Vignesh Puthur, Ravi Singh, Adam Milne, Kuldeep Sen",
102
- "DC": "Nitish Rana (traded from RR), Kuldeep Yadav, KL Rahul, Mitchell Starc, Tristan Stubbs, Abishek Porel, Ashutosh Sharma, Axar Patel, Dushmantha Chameera, Karun Nair, Sameer Rizvi, T Natarajan, Tripurana Vijay, Vipraj Nigam, Mukesh Kumar, Ajay Mandal, Madhav Tiwari, Auqib Dar, Pathum Nissanka, David Miller, Ben Duckett, Kyle Jamieson, Lungi Ngidi, Prithvi Shaw, Sahil Parakh",
103
- "KKR": "Ajinkya Rahane, Angkrish Raghuvanshi, Anukul Roy, Harshit Rana, Manish Pandey, Ramandeep Singh, Rinku Singh, Rovman Powell, Sunil Narine, Umran Malik, Vaibhav Arora, Varun Chakaravarthy, Cameron Green, Matheesha Pathirana, Mustafizur Rahman, Finn Allen, Tim Seifert, Tejasvi Singh, Kartik Tyagi, Prashant Solanki, Rahul Tripathi, Akash Deep, Rachin Ravindra, Daksh Kamra, Sarthak Ranjan",
104
- "GT": "Shubman Gill (c), Sai Sudharsan, Kumar Kushagra, Anuj Rawat, Jos Buttler, Nishant Sindhu, Washington Sundar, Arshad Khan, Shahrukh Khan, Rahul Tewatia, Kagiso Rabada, Mohammed Siraj, Prasidh Krishna, Ishant Sharma, Gurnoor Singh Brar, Rashid Khan, Manav Suthar, Sai Kishore, Jayant Yadav, Glenn Phillips, Jason Holder, Tom Banton, Ashok Sharma, Luke Wood, Prithviraj Yarra",
105
- "LSG": "Abdul Samad, Ayush Badoni, Aiden Markram, Matthew Breetzke, Himmat Singh, Rishabh Pant (c), Nicholas Pooran, Mitchell Marsh, Shahbaz Ahmed, Arshin Kulkarni, Mayank Yadav, Avesh Khan, Mohsin Khan, Manimaran Siddharth, Digvesh Rathi, Prince Yadav, Akash Singh, Josh Inglis, Wanindu Hasaranga, Anrich Nortje, Mukul Choudhary, Akshat Raghuwanshi, Naman Tiwari, Mohammed Shami (traded from SRH), Arjun Tendulkar (traded from MI)",
106
- "PBKS": "Prabhsimran Singh, Priyansh Arya, Shreyas Iyer, Shashank Singh, Nehal Wadhera, Marcus Stoinis, Azmatullah Omarzai, Marco Jansen, Harpreet Brar, Yuzvendra Chahal, Arshdeep Singh, Musheer Khan, Pyala Avinash, Harnoor Pannu, Suryansh Shedge, Mitchell Owen, Xavier Bartlett, Lockie Ferguson, Vyshak Vijaykumar, Yash Thakur, Vishnu Vinod, Ben Dwarshuis, Cooper Connolly, Vishal Nishad, Pravin Dubey",
107
- "MI": "Rohit Sharma, Suryakumar Yadav, Tilak Varma, Robin Minz, Ryan Rickelton, Hardik Pandya, Naman Dhir, Mitchell Santner, Will Jacks, Corbin Bosch, Raj Bawa, Trent Boult, Jasprit Bumrah, Deepak Chahar, Ashwani Kumar, Raghu Sharma, AM Ghazanfar, Quinton de Kock, Shardul Thakur (traded from LSG), Sherfane Rutherford (traded from GT), Mayank Markande (traded from KKR), Mayank Rawat, Atharva Ankolekar, Mohammad Izhar, Danish Malewar",
108
- "RCB": "Rajat Patidar (c), Virat Kohli, Devdutt Padikkal, Phil Salt, Jitesh Sharma, Krunal Pandya, Swapnil Singh, Tim David, Romario Shepherd, Jacob Bethell, Josh Hazlewood, Yash Dayal, Bhuvneshwar Kumar, Nuwan Thushara, Rasikh Salam, Abhinandan Singh, Suyash Sharma, Venkatesh Iyer, Mangesh Yadav, Jacob Duffy, Jordan Cox, Kanishk Chouhan, Vihaan Malhotra, Vicky Ostwal, Satvik Deswal",
109
- }
110
-
111
-
112
- def clean_player(raw: str) -> str:
113
- s = raw.strip()
114
- s = re.sub(r"\s*\([^)]*\)\s*", "", s).strip()
115
- return s
116
-
117
-
118
- def build_players():
119
- out = {}
120
- for abbr, blob in RAW_SQUADS.items():
121
- names = [clean_player(p) for p in blob.split(",")]
122
- names = [n for n in names if n]
123
- out[abbr] = sorted(set(names), key=str.lower)
124
- return out
125
-
126
-
127
- def build_matches():
128
- per_day = defaultdict(int)
129
- matches = []
130
- for d, early, h, a in FIXTURES:
131
- per_day[d] += 1
132
- n = per_day[d]
133
- match_id = f"{d.replace('-', '')}_{n}"
134
- time_str = "3:30 PM" if early else "7:30 PM"
135
- venue = VENUE_HOME.get(h, "")
136
- matches.append(
137
- {
138
- "match_id": match_id,
139
- "date": d,
140
- "time": time_str,
141
- "teams": [h, a],
142
- "venue": venue,
143
- }
144
- )
145
- return matches
146
-
147
-
148
- def main():
149
- matches = build_matches()
150
- players = build_players()
151
- assert len(matches) == 70, len(matches)
152
- for k in ("CSK", "MI", "RCB", "KKR", "SRH", "DC", "RR", "PBKS", "LSG", "GT"):
153
- assert k in players and len(players[k]) >= 18, (k, len(players.get(k, [])))
154
-
155
- (ROOT / "matches.json").write_text(
156
- json.dumps(matches, indent=4) + "\n", encoding="utf-8"
157
- )
158
- (ROOT / "players.json").write_text(
159
- json.dumps(players, indent=4) + "\n", encoding="utf-8"
160
- )
161
- print("Wrote", len(matches), "matches and", len(players), "team squads.")
162
-
163
-
164
- if __name__ == "__main__":
165
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/admin.html DELETED
@@ -1,365 +0,0 @@
1
- {% extends 'base.html' %}
2
- {% block title %}Admin – {{ app_brand }}{% endblock %}
3
- {% block head %}
4
- <style>
5
- @media (max-width: 860px) {
6
- .admin-result-form {
7
- min-width: 0 !important;
8
- width: 100%;
9
- }
10
- .admin-header-logout {
11
- width: 100%;
12
- }
13
- }
14
- </style>
15
- {% endblock %}
16
- {% block content %}
17
- <div class="page">
18
- <div class="page-header" style="display:flex; flex-wrap:wrap; align-items:flex-start; justify-content:space-between; gap:1rem;">
19
- <div>
20
- <div class="page-title">⚙️ ADMIN PANEL</div>
21
- <div class="page-subtitle">Manage matches, users, results and points</div>
22
- </div>
23
- <a href="{{ url_for('admin_logout') }}" class="btn btn-ghost btn-sm admin-header-logout" style="align-self:center;">🔒 Log out admin</a>
24
- </div>
25
-
26
- <!-- Tab Nav -->
27
- <div style="display:flex; gap:0.5rem; margin-bottom:1.5rem; border-bottom:1px solid var(--border); padding-bottom:0.5rem; flex-wrap:wrap;">
28
- {% for tab in [('matches','📅 Matches'),('add_match','➕ Add Match'),('results','✅ Set Results'),('users','👥 Users')] %}
29
- <button class="btn btn-ghost btn-sm tab-btn" data-tab="{{ tab[0] }}" onclick="showTab('{{ tab[0] }}')">{{ tab[1] }}</button>
30
- {% endfor %}
31
- </div>
32
-
33
- <!-- ── TAB: MATCHES ─────────────────────────────────────────────────────── -->
34
- <div id="tab-matches" class="tab-panel">
35
- <div style="display:flex; justify-content:flex-end; margin-bottom:0.75rem;">
36
- <a href="{{ url_for('team_pool') }}" class="btn btn-secondary btn-sm">🃏 Open team Pool (everyone’s picks)</a>
37
- </div>
38
- <div class="card">
39
- <div class="table-wrap">
40
- <table>
41
- <thead>
42
- <tr>
43
- <th>#</th><th>Teams</th><th>Date & Time</th><th>Venue</th>
44
- <th>Status</th><th>Winner</th><th>Preds</th><th>Actions</th>
45
- </tr>
46
- </thead>
47
- <tbody>
48
- {% for match in matches %}
49
- <tr>
50
- <td style="color:var(--muted);">{{ match.match_number or '—' }}</td>
51
- <td>
52
- <div style="font-weight:700;">
53
- <span style="color:{{ match.team1_color }};">{{ match.team1_abbr }}</span>
54
- <span style="color:var(--muted);"> vs </span>
55
- <span style="color:{{ match.team2_color }};">{{ match.team2_abbr }}</span>
56
- </div>
57
- <div style="font-size:0.75rem; color:var(--muted2);">{{ match.team1 }} vs {{ match.team2 }}</div>
58
- </td>
59
- <td style="font-size:0.85rem; white-space:nowrap;">
60
- {{ match.match_date|format_date }}<br>
61
- <span style="color:var(--muted2);">{{ match.match_time }}</span>
62
- </td>
63
- <td style="font-size:0.8rem; color:var(--muted2);">{{ match.venue or '—' }}</td>
64
- <td>
65
- <span class="badge badge-{{ match.status }}">{{ match.status }}</span>
66
- </td>
67
- <td style="font-size:0.85rem;">
68
- {% if match.winner and match.winner != 'ABANDONED' %}
69
- <div style="color:var(--green);">{{ match.winner }}</div>
70
- {% if match.man_of_match %}<div style="color:var(--muted2); font-size:0.78rem;">⭐ {{ match.man_of_match }}</div>{% endif %}
71
- {% elif match.status == 'abandoned' %}<span style="color:var(--muted);">Abandoned</span>
72
- {% else %}—{% endif %}
73
- </td>
74
- <td>
75
- <a href="{{ url_for('admin_match_predictions', match_id=match.id) }}" class="btn btn-ghost btn-sm">View</a>
76
- </td>
77
- <td>
78
- <div style="display:flex; gap:0.4rem; flex-wrap:wrap;">
79
- <!-- Status update -->
80
- <form method="post" action="{{ url_for('admin_update_status', match_id=match.id) }}" style="display:flex; gap:0.3rem;">
81
- <select name="status" style="width:auto; font-size:0.78rem; padding:0.3rem 0.5rem;">
82
- {% for s in statuses %}
83
- <option value="{{ s }}" {% if match.status == s %}selected{% endif %}>{{ s }}</option>
84
- {% endfor %}
85
- </select>
86
- <button type="submit" class="btn btn-secondary btn-sm" style="padding:0.3rem 0.5rem;">Set</button>
87
- </form>
88
- <!-- Delete -->
89
- <form method="post" action="{{ url_for('admin_delete_match', match_id=match.id) }}"
90
- onsubmit="return confirm('Delete this match? (Only possible if no predictions exist)')">
91
- <button type="submit" class="btn btn-danger btn-sm" style="padding:0.3rem 0.5rem;">🗑</button>
92
- </form>
93
- </div>
94
- </td>
95
- </tr>
96
- {% endfor %}
97
- </tbody>
98
- </table>
99
- </div>
100
- </div>
101
- </div>
102
-
103
- <!-- ── TAB: ADD MATCH ───────────────────────────────────────────────────── -->
104
- <div id="tab-add_match" class="tab-panel" style="display:none;">
105
- <div class="card" style="max-width:700px;">
106
- <div class="card-title">➕ ADD NEW MATCH</div>
107
- <form method="post" action="{{ url_for('admin_add_match') }}">
108
- <div class="form-row cols-2">
109
- <div class="form-group">
110
- <label>Team 1 *</label>
111
- <select name="team1" required>
112
- <option value="">Select team…</option>
113
- {% for t in teams %}<option value="{{ t }}">{{ t }}</option>{% endfor %}
114
- </select>
115
- </div>
116
- <div class="form-group">
117
- <label>Team 2 *</label>
118
- <select name="team2" required>
119
- <option value="">Select team…</option>
120
- {% for t in teams %}<option value="{{ t }}">{{ t }}</option>{% endfor %}
121
- </select>
122
- </div>
123
- </div>
124
- <div class="form-row cols-3">
125
- <div class="form-group">
126
- <label>Match Date *</label>
127
- <input type="date" name="match_date" required value="{{ today }}">
128
- </div>
129
- <div class="form-group">
130
- <label>Start Time *</label>
131
- <input type="time" name="match_time" required value="19:30">
132
- <div class="form-hint">{% if points_config.lock_minutes_before %}Lock auto-triggers {{ points_config.lock_minutes_before }} min before start{% else %}Lock auto-triggers at scheduled match start{% endif %}</div>
133
- </div>
134
- <div class="form-group">
135
- <label>Match Number</label>
136
- <input type="number" name="match_number" placeholder="e.g. 1">
137
- </div>
138
- </div>
139
- <div class="form-row cols-2">
140
- <div class="form-group">
141
- <label>Venue</label>
142
- <input type="text" name="venue" placeholder="e.g. Wankhede Stadium">
143
- </div>
144
- <div class="form-group">
145
- <label>City</label>
146
- <input type="text" name="city" placeholder="e.g. Mumbai">
147
- </div>
148
- </div>
149
- <button type="submit" class="btn btn-primary">➕ Add Match</button>
150
- </form>
151
- </div>
152
-
153
- <!-- Bulk IPL 2025 schedule hint -->
154
- <div class="card" style="max-width:700px; margin-top:1rem; padding:1rem; background:rgba(59,130,246,0.05); border-color:rgba(59,130,246,0.2);">
155
- <div style="font-size:0.875rem; color:var(--muted2);">
156
- <strong style="color:var(--blue);">💡 Tip:</strong>
157
- IPL matches typically kick off at <strong>7:30 PM IST</strong> with afternoon matches at <strong>3:30 PM IST</strong> on weekends.
158
- Add matches before the season starts so your team can predict early!
159
- </div>
160
- </div>
161
- </div>
162
-
163
- <!-- ── TAB: RESULTS ─────────────────────────────────────────────────────── -->
164
- <div id="tab-results" class="tab-panel" style="display:none;">
165
- {% set settable = matches|selectattr('status','in',['completed','live','locked','abandoned'])|list %}
166
- {% if settable %}
167
- <div style="display:grid; gap:1rem;">
168
- {% for match in settable %}
169
- <div class="card">
170
- <div style="display:flex; justify-content:space-between; align-items:flex-start; flex-wrap:wrap; gap:1rem;">
171
- <div>
172
- <div style="font-family:var(--font-display); font-size:1.3rem;">
173
- <span style="color:{{ match.team1_color }};">{{ match.team1_abbr }}</span>
174
- <span style="color:var(--muted);"> vs </span>
175
- <span style="color:{{ match.team2_color }};">{{ match.team2_abbr }}</span>
176
- <span class="badge badge-{{ match.status }}" style="margin-left:0.5rem; vertical-align:middle;">{{ match.status }}</span>
177
- </div>
178
- <div style="font-size:0.85rem; color:var(--muted2);">
179
- Match #{{ match.match_number or '?' }} · {{ match.match_date|format_date }} · {{ match.match_time }}
180
- {% if match.venue %} · {{ match.venue }}{% endif %}
181
- </div>
182
- {% if match.winner %}
183
- <div style="margin-top:0.5rem; font-size:0.85rem; color:var(--green);">
184
- Current: 🏆 {{ match.winner }} {% if match.man_of_match %}· ⭐ {{ match.man_of_match }}{% endif %}
185
- {% if match.is_result_final %}<span class="badge badge-completed" style="margin-left:0.4rem;">Finalised</span>{% endif %}
186
- </div>
187
- {% endif %}
188
- </div>
189
- <form method="post" action="{{ url_for('admin_set_result', match_id=match.id) }}" class="admin-result-form" style="display:flex; flex-direction:column; gap:0.75rem; min-width:320px;">
190
- <div class="form-row cols-2">
191
- <div class="form-group" style="margin:0;">
192
- <label>Winner *</label>
193
- <select name="winner" id="admin-winner-{{ match.id }}" required>
194
- <option value="">Select…</option>
195
- <option value="{{ match.team1 }}" {% if match.winner == match.team1 %}selected{% endif %}>{{ match.team1 }}</option>
196
- <option value="{{ match.team2 }}" {% if match.winner == match.team2 %}selected{% endif %}>{{ match.team2 }}</option>
197
- </select>
198
- </div>
199
- <div class="form-group" style="margin:0;">
200
- <label>Man of the Match</label>
201
- <select name="man_of_match" id="admin-motm-{{ match.id }}" data-initial-motm="{{ (match.man_of_match or '')|e }}">
202
- <option value="">— Pick winner first —</option>
203
- </select>
204
- <div class="form-hint" style="font-size:0.72rem; margin-top:0.25rem;">Squad for the selected winning team only.</div>
205
- </div>
206
- </div>
207
- <div class="form-group" style="margin:0;">
208
- <label>Notes (optional)</label>
209
- <input type="text" name="result_notes" placeholder="e.g. Won by 4 wickets" value="{{ match.result_notes or '' }}">
210
- </div>
211
- {% if match.is_result_final %}
212
- <label style="display:flex; align-items:center; gap:0.5rem; cursor:pointer; color:var(--muted2); font-size:0.85rem;">
213
- <input type="checkbox" name="recalculate" value="1" style="width:auto;">
214
- Reverse previous settlement &amp; recalculate
215
- </label>
216
- {% endif %}
217
- <button type="submit" class="btn btn-success btn-sm">
218
- {% if match.is_result_final %}🔄 Update &amp; Recalculate{% else %}✅ Set Result &amp; Settle{% endif %}
219
- </button>
220
- </form>
221
- </div>
222
- </div>
223
- {% endfor %}
224
- </div>
225
- {% else %}
226
- <div class="empty-state card">
227
- <div class="icon">✅</div>
228
- <div>No matches need results yet. Complete or lock a match first.</div>
229
- </div>
230
- {% endif %}
231
- </div>
232
-
233
- <!-- ── TAB: USERS ───────────────────────────────────────────────────────── -->
234
- <div id="tab-users" class="tab-panel" style="display:none;">
235
- <!-- Add User Form -->
236
- <div class="card" style="max-width:600px; margin-bottom:1.5rem;">
237
- <div class="card-title">👤 ADD NEW PLAYER</div>
238
- <form method="post" action="{{ url_for('admin_add_user') }}">
239
- <div class="form-row cols-2">
240
- <div class="form-group">
241
- <label>Username *</label>
242
- <input type="text" name="username" placeholder="e.g. rahul_ipl" required>
243
- </div>
244
- <div class="form-group">
245
- <label>Display Name</label>
246
- <input type="text" name="display_name" placeholder="e.g. Rahul Sharma">
247
- </div>
248
- </div>
249
- <div class="form-row cols-2">
250
- <div class="form-group">
251
- <label>Password</label>
252
- <input type="password" name="password" placeholder="Leave blank for name-picker sign-in">
253
- </div>
254
- <div class="form-group">
255
- <label>Starting Points</label>
256
- <input type="number" name="initial_points" value="{{ points_config.initial }}" min="100">
257
- </div>
258
- </div>
259
- <button type="submit" class="btn btn-primary">➕ Add Player</button>
260
- </form>
261
- </div>
262
-
263
- <!-- Users List -->
264
- <div class="card">
265
- <div class="table-wrap">
266
- <table>
267
- <thead>
268
- <tr>
269
- <th>Player</th><th>Username</th><th style="text-align:right;">Points</th>
270
- <th>Status</th><th>Actions</th>
271
- </tr>
272
- </thead>
273
- <tbody>
274
- {% for u in users %}
275
- <tr>
276
- <td>
277
- <div style="font-weight:600;">{{ u.display_name or u.username }}</div>
278
- <div style="font-size:0.75rem; color:var(--muted);">Joined {{ u.created_at[:10]|format_date }}</div>
279
- </td>
280
- <td style="font-family:var(--font-mono); color:var(--muted2);">@{{ u.username }}</td>
281
- <td style="text-align:right; font-family:var(--font-mono); font-weight:700;">{{ '%.0f'|format(u.points) }}</td>
282
- <td>
283
- {% if u.is_active %}<span class="badge badge-completed">Active</span>
284
- {% else %}<span class="badge badge-abandoned">Inactive</span>{% endif %}
285
- </td>
286
- <td>
287
- <div style="display:flex; gap:0.4rem; flex-wrap:wrap; align-items:center;">
288
- <!-- Adjust points -->
289
- <form method="post" action="{{ url_for('admin_adjust_points', user_id=u.id) }}" style="display:flex; gap:0.3rem;">
290
- <input type="number" name="amount" placeholder="±pts" style="width:75px; font-size:0.8rem; padding:0.3rem 0.5rem;">
291
- <input type="text" name="reason" placeholder="reason" style="width:100px; font-size:0.8rem; padding:0.3rem 0.5rem;">
292
- <button type="submit" class="btn btn-secondary btn-sm" style="padding:0.3rem 0.5rem;">Adjust</button>
293
- </form>
294
- <!-- Toggle active -->
295
- <form method="post" action="{{ url_for('admin_toggle_user', user_id=u.id) }}">
296
- <button type="submit" class="btn btn-ghost btn-sm">{{ '🚫 Disable' if u.is_active else '✅ Enable' }}</button>
297
- </form>
298
- </div>
299
- </td>
300
- </tr>
301
- {% endfor %}
302
- </tbody>
303
- </table>
304
- </div>
305
- </div>
306
- </div>
307
- </div>
308
-
309
- <script>
310
- const ACTIVE_TAB_KEY = 'admin_tab';
311
- const ADMIN_MOTM_BY_MATCH = {{ squads_by_match_id | tojson }};
312
-
313
- function adminFillMotmForWinner(matchId) {
314
- const w = document.getElementById('admin-winner-' + matchId);
315
- const s = document.getElementById('admin-motm-' + matchId);
316
- if (!w || !s) return;
317
- const key = String(matchId);
318
- const byMatch = ADMIN_MOTM_BY_MATCH[key] || ADMIN_MOTM_BY_MATCH[matchId];
319
- const saved = (s.getAttribute('data-initial-motm') || '').trim();
320
- const team = w.value;
321
- s.innerHTML = '';
322
- const ph = document.createElement('option');
323
- ph.value = '';
324
- if (!team) {
325
- ph.textContent = '— Pick winner first —';
326
- s.appendChild(ph);
327
- return;
328
- }
329
- const players = (byMatch && byMatch[team]) ? byMatch[team] : [];
330
- ph.textContent = players.length ? '— Optional: choose MOTM —' : '— No squad for this team —';
331
- s.appendChild(ph);
332
- for (let i = 0; i < players.length; i++) {
333
- const o = document.createElement('option');
334
- o.value = players[i];
335
- o.textContent = players[i];
336
- s.appendChild(o);
337
- }
338
- if (saved && players.indexOf(saved) !== -1) {
339
- s.value = saved;
340
- }
341
- }
342
-
343
- function showTab(name) {
344
- document.querySelectorAll('.tab-panel').forEach(p => p.style.display = 'none');
345
- document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('btn-secondary'));
346
- document.querySelectorAll('.tab-btn').forEach(b => b.classList.add('btn-ghost'));
347
- const panel = document.getElementById('tab-' + name);
348
- if (panel) panel.style.display = 'block';
349
- const btn = document.querySelector(`[data-tab="${name}"]`);
350
- if (btn) { btn.classList.remove('btn-ghost'); btn.classList.add('btn-secondary'); }
351
- localStorage.setItem(ACTIVE_TAB_KEY, name);
352
- }
353
- // Restore last tab or default
354
- const last = localStorage.getItem(ACTIVE_TAB_KEY) || 'matches';
355
- showTab(last);
356
-
357
- document.querySelectorAll('[id^="admin-winner-"]').forEach(function (el) {
358
- const id = el.id.replace('admin-winner-', '');
359
- el.addEventListener('change', function () { adminFillMotmForWinner(id); });
360
- if (el.value) {
361
- adminFillMotmForWinner(id);
362
- }
363
- });
364
- </script>
365
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/admin_login.html DELETED
@@ -1,19 +0,0 @@
1
- {% extends 'base.html' %}
2
- {% block title %}Admin – {{ app_brand }}{% endblock %}
3
- {% block content %}
4
- <div class="page" style="max-width:440px;">
5
- <div class="card">
6
- <div class="card-title">🔐 ADMIN LOGIN</div>
7
- <form method="post" action="{{ url_for('admin_login', next=next_url) }}">
8
- <div class="form-group">
9
- <label for="password">Password</label>
10
- <input type="password" name="password" id="password" autocomplete="current-password" required autofocus placeholder="Admin password">
11
- </div>
12
- <button type="submit" class="btn btn-primary" style="width:100%; justify-content:center;">Log in</button>
13
- </form>
14
- <p style="margin-top:1rem; font-size:0.8rem; color:var(--muted);">
15
- <a href="{{ url_for('dashboard') }}" style="color:var(--orange);">← Back to dashboard</a>
16
- </p>
17
- </div>
18
- </div>
19
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/admin_predictions.html DELETED
@@ -1,115 +0,0 @@
1
- {% extends 'base.html' %}
2
- {% block title %}Match predictions – Admin – {{ app_brand }}{% endblock %}
3
- {% block content %}
4
- <div class="page">
5
- <div style="margin-bottom:1rem;">
6
- <a href="{{ url_for('admin') }}" style="color:var(--muted2); text-decoration:none; font-size:0.875rem;">← Back to Admin</a>
7
- </div>
8
-
9
- <!-- Match Header -->
10
- <div class="card" style="margin-bottom:1.5rem;">
11
- <div style="display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:1rem;">
12
- <div>
13
- <div style="font-family:var(--font-display); font-size:2rem;">
14
- <span style="color:{{ match.team1_color }};">{{ match.team1_abbr }}</span>
15
- <span style="color:var(--muted);"> vs </span>
16
- <span style="color:{{ match.team2_color }};">{{ match.team2_abbr }}</span>
17
- </div>
18
- <div style="color:var(--muted2); font-size:0.9rem;">{{ match.team1 }} vs {{ match.team2 }}</div>
19
- <div style="color:var(--muted); font-size:0.85rem; margin-top:0.25rem;">
20
- Match #{{ match.match_number or '?' }} · {{ match.match_date }} · {{ match.match_time }}
21
- {% if match.venue %} · {{ match.venue }}{% endif %}
22
- </div>
23
- </div>
24
- <div style="text-align:right;">
25
- <span class="badge badge-{{ match.status }}">{{ match.status }}</span>
26
- {% if match.winner %}
27
- <div style="margin-top:0.5rem; color:var(--green); font-weight:700;">🏆 {{ match.winner }}</div>
28
- {% if match.man_of_match %}<div style="color:var(--muted2); font-size:0.85rem;">⭐ {{ match.man_of_match }}</div>{% endif %}
29
- {% endif %}
30
- </div>
31
- </div>
32
- </div>
33
-
34
- <!-- Summary -->
35
- {% set total = predictions|length %}
36
- {% set team1_picks = predictions|selectattr('predicted_winner','equalto',match.team1)|list|length %}
37
- {% set team2_picks = predictions|selectattr('predicted_winner','equalto',match.team2)|list|length %}
38
- {% set total_bid = predictions|sum(attribute='bid_amount') %}
39
-
40
- <div class="grid grid-4" style="margin-bottom:1.5rem;">
41
- <div class="stat-box">
42
- <div class="stat-value">{{ total }}</div>
43
- <div class="stat-label">Total Predictions</div>
44
- </div>
45
- <div class="stat-box">
46
- <div class="stat-value" style="color:{{ match.team1_color }};">{{ team1_picks }}</div>
47
- <div class="stat-label">{{ match.team1_abbr }} Picks</div>
48
- </div>
49
- <div class="stat-box">
50
- <div class="stat-value" style="color:{{ match.team2_color }};">{{ team2_picks }}</div>
51
- <div class="stat-label">{{ match.team2_abbr }} Picks</div>
52
- </div>
53
- <div class="stat-box">
54
- <div class="stat-value">{{ '%.0f'|format(total_bid) }}</div>
55
- <div class="stat-label">Total Points at Stake</div>
56
- </div>
57
- </div>
58
-
59
- <!-- Predictions table -->
60
- {% if predictions %}
61
- <div class="card">
62
- <div class="table-wrap">
63
- <table>
64
- <thead>
65
- <tr>
66
- <th>Player</th>
67
- <th>Winner Pick</th>
68
- <th>MOTM Pick</th>
69
- <th style="text-align:right;">Bid</th>
70
- <th>Winner?</th>
71
- <th>MOTM?</th>
72
- <th style="text-align:right;">P/L</th>
73
- <th>Submitted</th>
74
- </tr>
75
- </thead>
76
- <tbody>
77
- {% for p in predictions %}
78
- <tr>
79
- <td>
80
- <div style="font-weight:600;">{{ p.display_name or p.username }}</div>
81
- <div style="font-size:0.78rem; color:var(--muted);">@{{ p.username }}</div>
82
- </td>
83
- <td style="font-weight:600; color:var(--orange);">{{ p.predicted_winner }}</td>
84
- <td style="font-size:0.85rem; color:var(--muted2);">{{ p.predicted_motm or '—' }}</td>
85
- <td style="text-align:right; font-family:var(--font-mono); font-weight:700;">{{ '%.0f'|format(p.bid_amount) }}</td>
86
- <td style="text-align:center;">
87
- {% if p.is_settled %}
88
- {{ '✅' if p.winner_correct else '❌' }}
89
- {% else %}<span style="color:var(--muted);">—</span>{% endif %}
90
- </td>
91
- <td style="text-align:center;">
92
- {% if p.is_settled and p.motm_correct is not none %}
93
- {{ '✅' if p.motm_correct else '❌' }}
94
- {% else %}<span style="color:var(--muted);">—</span>{% endif %}
95
- </td>
96
- <td style="text-align:right;">
97
- {% if p.is_settled %}
98
- <span class="{{ p.points_earned|delta_class }}" style="font-family:var(--font-mono); font-weight:700;">{{ p.points_earned|delta_sign }}</span>
99
- {% else %}<span style="color:var(--muted);">Pending</span>{% endif %}
100
- </td>
101
- <td style="font-size:0.78rem; color:var(--muted2);">{{ p.updated_at[:16] }}</td>
102
- </tr>
103
- {% endfor %}
104
- </tbody>
105
- </table>
106
- </div>
107
- </div>
108
- {% else %}
109
- <div class="empty-state card">
110
- <div class="icon">🎯</div>
111
- <div>No predictions for this match yet.</div>
112
- </div>
113
- {% endif %}
114
- </div>
115
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/analytics.html DELETED
@@ -1,397 +0,0 @@
1
- {% extends 'base.html' %}
2
- {% block title %}Analytics – {{ app_brand }}{% endblock %}
3
- {% block head %}
4
- <style>
5
- .ax-hero {
6
- position: relative;
7
- border-radius: var(--radius);
8
- padding: 2rem 1.75rem 1.75rem;
9
- margin-bottom: 2rem;
10
- overflow: hidden;
11
- border: 1px solid rgba(249,115,22,0.22);
12
- background:
13
- radial-gradient(ellipse 120% 80% at 20% -20%, rgba(249,115,22,0.18), transparent 55%),
14
- radial-gradient(ellipse 90% 70% at 100% 50%, rgba(59,130,246,0.12), transparent 50%),
15
- linear-gradient(165deg, var(--card) 0%, #0f1628 100%);
16
- }
17
- .ax-hero::after {
18
- content: '';
19
- position: absolute;
20
- inset: 0;
21
- background: url("data:image/svg+xml,%3Csvg width='60' height='60' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M30 0L60 30L30 60L0 30Z' fill='%23ffffff' fill-opacity='0.02'/%3E%3C/svg%3E");
22
- pointer-events: none;
23
- opacity: 0.9;
24
- }
25
- .ax-hero-inner { position: relative; z-index: 1; }
26
- .ax-kicker {
27
- font-size: 0.72rem;
28
- font-weight: 700;
29
- letter-spacing: 0.14em;
30
- color: var(--orange);
31
- text-transform: uppercase;
32
- margin-bottom: 0.35rem;
33
- }
34
- .ax-title {
35
- font-family: var(--font-display);
36
- font-size: clamp(2rem, 5vw, 2.75rem);
37
- letter-spacing: 3px;
38
- color: var(--white);
39
- line-height: 1.05;
40
- margin-bottom: 0.5rem;
41
- }
42
- .ax-sub { color: var(--muted2); font-size: 0.95rem; max-width: 520px; line-height: 1.5; }
43
- .ax-stat-grid {
44
- display: grid;
45
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
46
- gap: 0.75rem;
47
- margin-top: 1.5rem;
48
- }
49
- .ax-tile {
50
- background: rgba(10,14,26,0.55);
51
- border: 1px solid var(--border);
52
- border-radius: var(--radius-sm);
53
- padding: 0.85rem 1rem;
54
- transition: border-color 0.2s, transform 0.2s;
55
- }
56
- .ax-tile:hover { border-color: rgba(249,115,22,0.35); transform: translateY(-1px); }
57
- .ax-tile-val {
58
- font-family: var(--font-mono);
59
- font-size: 1.35rem;
60
- font-weight: 700;
61
- color: var(--gold);
62
- line-height: 1.2;
63
- }
64
- .ax-tile-lbl {
65
- font-size: 0.68rem;
66
- font-weight: 600;
67
- letter-spacing: 0.06em;
68
- color: var(--muted2);
69
- text-transform: uppercase;
70
- margin-top: 0.25rem;
71
- line-height: 1.35;
72
- }
73
- .ax-bar-row { margin-bottom: 0.65rem; }
74
- .ax-bar-meta {
75
- display: flex;
76
- justify-content: space-between;
77
- align-items: baseline;
78
- gap: 0.5rem;
79
- font-size: 0.82rem;
80
- margin-bottom: 0.25rem;
81
- }
82
- .ax-bar-name { color: var(--text); font-weight: 500; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
83
- .ax-bar-count { font-family: var(--font-mono); color: var(--gold); font-weight: 700; }
84
- .ax-bar-track {
85
- height: 8px;
86
- border-radius: 99px;
87
- background: var(--bg3);
88
- overflow: hidden;
89
- }
90
- .ax-bar-fill {
91
- height: 100%;
92
- border-radius: 99px;
93
- background: linear-gradient(90deg, var(--orange), var(--gold));
94
- min-width: 2px;
95
- transition: width 0.5s ease;
96
- }
97
- .ax-bin-row {
98
- display: flex;
99
- align-items: center;
100
- gap: 0.65rem;
101
- margin-bottom: 0.5rem;
102
- font-size: 0.8rem;
103
- }
104
- .ax-bin-lbl { width: 88px; flex-shrink: 0; color: var(--muted2); }
105
- .ax-mini {
106
- font-size: 0.78rem;
107
- color: var(--muted2);
108
- line-height: 1.5;
109
- margin-top: 0.75rem;
110
- padding-top: 0.75rem;
111
- border-top: 1px solid var(--border);
112
- }
113
- .ax-table { width: 100%; font-size: 0.85rem; border-collapse: collapse; }
114
- .ax-table th {
115
- text-align: left;
116
- font-size: 0.68rem;
117
- letter-spacing: 0.06em;
118
- text-transform: uppercase;
119
- color: var(--muted);
120
- padding: 0.35rem 0 0.5rem;
121
- border-bottom: 1px solid var(--border);
122
- }
123
- .ax-table td { padding: 0.55rem 0; border-bottom: 1px solid rgba(30,45,69,0.6); vertical-align: middle; }
124
- .ax-table tr:last-child td { border-bottom: none; }
125
- .ax-pill {
126
- display: inline-block;
127
- font-family: var(--font-mono);
128
- font-size: 0.72rem;
129
- font-weight: 700;
130
- padding: 0.15rem 0.45rem;
131
- border-radius: 4px;
132
- background: var(--bg3);
133
- color: var(--muted2);
134
- }
135
- .ax-insight {
136
- display: grid;
137
- gap: 0.75rem;
138
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
139
- margin-top: 1rem;
140
- }
141
- .ax-insight-b {
142
- background: var(--bg2);
143
- border-radius: var(--radius-sm);
144
- padding: 1rem 1.1rem;
145
- border: 1px solid var(--border);
146
- }
147
- .ax-insight-b strong { color: var(--white); font-size: 0.92rem; display: block; margin-bottom: 0.25rem; }
148
- .ax-insight-b span { font-size: 0.82rem; color: var(--muted2); line-height: 1.45; }
149
- @media (max-width: 640px) {
150
- .ax-stat-grid { grid-template-columns: repeat(2, 1fr); }
151
- }
152
- </style>
153
- {% endblock %}
154
- {% block content %}
155
- <div class="page">
156
- <div class="ax-hero">
157
- <div class="ax-hero-inner">
158
- <div class="ax-kicker">Squad intelligence</div>
159
- <h1 class="ax-title">POOL ANALYTICS</h1>
160
- <p class="ax-sub">Every number here is aggregated across <strong style="color:var(--text);">all active teammates</strong> — picks, stakes, accuracy, and who’s leading the MOTM and P/L boards.</p>
161
-
162
- <div class="ax-stat-grid">
163
- <div class="ax-tile">
164
- <div class="ax-tile-val">{{ pool.members or 0 }}</div>
165
- <div class="ax-tile-lbl">Active members</div>
166
- </div>
167
- <div class="ax-tile">
168
- <div class="ax-tile-val">{{ pool.preds or 0 }}</div>
169
- <div class="ax-tile-lbl">Total predictions</div>
170
- </div>
171
- <div class="ax-tile">
172
- <div class="ax-tile-val">{{ pool.settled or 0 }}</div>
173
- <div class="ax-tile-lbl">Settled picks</div>
174
- </div>
175
- <div class="ax-tile">
176
- <div class="ax-tile-val">{% if win_acc is not none %}{{ win_acc }}%{% else %}—{% endif %}</div>
177
- <div class="ax-tile-lbl">Pool winner hit rate</div>
178
- </div>
179
- <div class="ax-tile">
180
- <div class="ax-tile-val">{% if motm_acc is not none %}{{ motm_acc }}%{% else %}—{% endif %}</div>
181
- <div class="ax-tile-lbl">Pool MOTM hit rate</div>
182
- </div>
183
- <div class="ax-tile">
184
- <div class="ax-tile-val">{{ '%.0f'|format(pool.total_staked or 0) }}</div>
185
- <div class="ax-tile-lbl">Points staked (all)</div>
186
- </div>
187
- <div class="ax-tile">
188
- <div class="ax-tile-val">{% if pool.avg_bid %}{{ '%.0f'|format(pool.avg_bid) }}{% else %}—{% endif %}</div>
189
- <div class="ax-tile-lbl">Avg bid / pick</div>
190
- </div>
191
- <div class="ax-tile">
192
- <div class="ax-tile-val">{{ '%.0f'|format(pool.avg_pts or 0) }}</div>
193
- <div class="ax-tile-lbl">Avg balance / member</div>
194
- </div>
195
- </div>
196
- </div>
197
- </div>
198
-
199
- <div class="grid grid-2" style="gap:1.25rem; margin-bottom:1.25rem;">
200
- <div class="card">
201
- <div class="card-title">🗓️ FIXTURE COVERAGE</div>
202
- <div class="ax-insight">
203
- <div class="ax-insight-b">
204
- <strong>Schedule</strong>
205
- <span>{{ match_counts.n_done or 0 }} completed · {{ match_counts.n_open or 0 }} still open · {{ match_counts.n_all or 0 }} total in system</span>
206
- </div>
207
- <div class="ax-insight-b">
208
- <strong>Engagement</strong>
209
- <span>{{ matches_touched }} distinct matches have at least one pool pick logged.</span>
210
- </div>
211
- </div>
212
- </div>
213
- <div class="card">
214
- <div class="card-title">🧠 CROWD vs RESULT</div>
215
- {% if crowd.n %}
216
- <p style="font-size:1.15rem; font-weight:700; color:var(--gold); margin-bottom:0.35rem;">{{ crowd.pct }}%</p>
217
- <p style="font-size:0.88rem; color:var(--muted2); line-height:1.5;">
218
- On <strong style="color:var(--text);">{{ crowd.n }}</strong> finished matches with picks, the <strong>plurality crowd favourite</strong> matched the actual winner that many times.
219
- </p>
220
- {% else %}
221
- <p style="color:var(--muted2); font-size:0.9rem;">Not enough completed results with predictions to measure consensus yet.</p>
222
- {% endif %}
223
- </div>
224
- </div>
225
-
226
- <div class="grid grid-2" style="gap:1.25rem; margin-bottom:1.25rem;">
227
- <div class="card">
228
- <div class="card-title">🎯 POPULAR WINNER PICKS</div>
229
- <p style="font-size:0.82rem; color:var(--muted2); margin-bottom:1rem;">Which teams the pool backs most often (by full franchise name).</p>
230
- {% if pick_rows %}
231
- {% for row in pick_rows %}
232
- <div class="ax-bar-row">
233
- <div class="ax-bar-meta">
234
- <span class="ax-bar-name" title="{{ row.predicted_winner }}">{{ team_abbr.get(row.predicted_winner, row.predicted_winner[:3]) }} — {{ row.predicted_winner }}</span>
235
- <span class="ax-bar-count">{{ row.c }}</span>
236
- </div>
237
- <div class="ax-bar-track">
238
- <div class="ax-bar-fill" style="width:{{ (row.c / pick_max * 100)|round(1) }}%;"></div>
239
- </div>
240
- </div>
241
- {% endfor %}
242
- {% else %}
243
- <div class="empty-state" style="padding:1.5rem;">No picks in the pool yet.</div>
244
- {% endif %}
245
- </div>
246
-
247
- <div class="card">
248
- <div class="card-title">💰 BID SIZE MIX</div>
249
- <p style="font-size:0.82rem; color:var(--muted2); margin-bottom:1rem;">How aggressively the squad is staking (points per prediction).</p>
250
- {% set bins = [
251
- ('≤ 50', bid_bins.b_low or 0),
252
- ('51 – 150', bid_bins.b_mid or 0),
253
- ('151 – 300', bid_bins.b_high or 0),
254
- ('300+', bid_bins.b_whale or 0),
255
- ] %}
256
- {% for label, v in bins %}
257
- <div class="ax-bin-row">
258
- <span class="ax-bin-lbl">{{ label }}</span>
259
- <div class="ax-bar-track" style="flex:1;">
260
- <div class="ax-bar-fill" style="width:{{ (v / bin_max * 100)|round(1) if bin_max else 0 }}%; opacity:{% if v %}1{% else %}0.2{% endif %};"></div>
261
- </div>
262
- <span class="mono" style="font-size:0.78rem; color:var(--gold); width:2rem; text-align:right;">{{ v }}</span>
263
- </div>
264
- {% endfor %}
265
- <div class="ax-mini">
266
- Combined outstanding member balances: <strong class="mono" style="color:var(--text);">{{ '%.0f'|format(sum_balances) }}</strong> pts ·
267
- Net P/L summed over all settled picks: <strong class="mono" style="color:var(--text);">{{ '%+.0f'|format(settled_pl_sum) }}</strong> pts
268
- </div>
269
- </div>
270
- </div>
271
-
272
- <div class="grid grid-2" style="gap:1.25rem; margin-bottom:1.25rem;">
273
- <div class="card">
274
- <div class="card-title">⭐ MOTM BOARD</div>
275
- <p style="font-size:0.82rem; color:var(--muted2); margin-bottom:0.75rem;">Correct MOTM calls vs attempts (settled matches only).</p>
276
- {% if motm_board %}
277
- <table class="ax-table">
278
- <thead>
279
- <tr>
280
- <th>Player</th>
281
- <th style="text-align:right;">Hits</th>
282
- <th style="text-align:right;">Att.</th>
283
- <th style="text-align:right;">Rate</th>
284
- </tr>
285
- </thead>
286
- <tbody>
287
- {% for r in motm_board %}
288
- {% set rate = ((r.hits / r.attempts * 100)|round(1)) if r.attempts else 0 %}
289
- <tr>
290
- <td><strong>{{ r.display_name or r.username }}</strong></td>
291
- <td style="text-align:right; font-family:var(--font-mono); color:var(--green);">{{ r.hits }}</td>
292
- <td style="text-align:right; font-family:var(--font-mono); color:var(--muted2);">{{ r.attempts }}</td>
293
- <td style="text-align:right;"><span class="ax-pill">{{ rate }}%</span></td>
294
- </tr>
295
- {% endfor %}
296
- </tbody>
297
- </table>
298
- {% else %}
299
- <div class="empty-state" style="padding:1.5rem;">No MOTM outcomes recorded yet.</div>
300
- {% endif %}
301
- </div>
302
-
303
- <div class="card">
304
- <div class="card-title">🎯 WINNER ACCURACY LEADERS</div>
305
- <p style="font-size:0.82rem; color:var(--muted2); margin-bottom:0.75rem;">Min. 2 settled picks — ranked by hit rate, then volume.</p>
306
- {% if sharp_picks %}
307
- <table class="ax-table">
308
- <thead>
309
- <tr>
310
- <th>Player</th>
311
- <th style="text-align:right;">W / G</th>
312
- <th style="text-align:right;">Rate</th>
313
- </tr>
314
- </thead>
315
- <tbody>
316
- {% for r in sharp_picks %}
317
- {% set rate = ((r.w / r.g * 100)|round(1)) if r.g else 0 %}
318
- <tr>
319
- <td><strong>{{ r.display_name or r.username }}</strong></td>
320
- <td style="text-align:right; font-family:var(--font-mono);">{{ r.w }} / {{ r.g }}</td>
321
- <td style="text-align:right;"><span class="ax-pill">{{ rate }}%</span></td>
322
- </tr>
323
- {% endfor %}
324
- </tbody>
325
- </table>
326
- {% else %}
327
- <div class="empty-state" style="padding:1.5rem;">Need more settled results to rank the squad.</div>
328
- {% endif %}
329
- </div>
330
- </div>
331
-
332
- <div class="grid grid-2" style="gap:1.25rem; margin-bottom:1.25rem;">
333
- <div class="card">
334
- <div class="card-title">📈 NET P&amp;L FROM PICKS</div>
335
- <p style="font-size:0.82rem; color:var(--muted2); margin-bottom:0.75rem;">Net points won or lost on settled predictions (adds up everyone’s match results).</p>
336
- {% if pl_board %}
337
- <table class="ax-table">
338
- <thead>
339
- <tr>
340
- <th>Player</th>
341
- <th style="text-align:right;">Net P/L</th>
342
- <th style="text-align:right;">Balance</th>
343
- </tr>
344
- </thead>
345
- <tbody>
346
- {% for r in pl_board %}
347
- <tr>
348
- <td><strong>{{ r.display_name or r.username }}</strong></td>
349
- <td style="text-align:right; font-family:var(--font-mono); font-weight:700;" class="{{ r.season_pl|delta_class }}">{{ r.season_pl|delta_sign }}</td>
350
- <td style="text-align:right; font-family:var(--font-mono); color:var(--muted2);">{{ '%.0f'|format(r.points) }}</td>
351
- </tr>
352
- {% endfor %}
353
- </tbody>
354
- </table>
355
- {% else %}
356
- <div class="empty-state" style="padding:1.5rem;">No settled P/L yet.</div>
357
- {% endif %}
358
- </div>
359
-
360
- <div class="card">
361
- <div class="card-title">⚡ EXTREMES &amp; MOTM VOLUME</div>
362
- <div class="ax-insight" style="margin-top:0;">
363
- <div class="ax-insight-b">
364
- <strong>Best single match</strong>
365
- <span class="mono" style="color:var(--green); font-size:1rem;">{% if extrema.best_single is not none %}{{ extrema.best_single|delta_sign }}{% else %}—{% endif %}</span>
366
- <span style="display:block; margin-top:0.35rem;">Largest one-off gain logged by anyone in the pool.</span>
367
- </div>
368
- <div class="ax-insight-b">
369
- <strong>Toughest single match</strong>
370
- <span class="mono text-red" style="font-size:1rem;">{% if extrema.worst_single is not none %}{{ extrema.worst_single|delta_sign }}{% else %}—{% endif %}</span>
371
- <span style="display:block; margin-top:0.35rem;">Biggest swing the other way on one result.</span>
372
- </div>
373
- <div class="ax-insight-b">
374
- <strong>MOTM tries</strong>
375
- <span><strong style="color:var(--gold); font-size:1.1rem;">{{ pool.motm_attempts or 0 }}</strong> settled rows with a MOTM name · <span class="text-green">{{ pool.motm_hits or 0 }}</span> correct · <span class="text-red">{{ pool.motm_misses or 0 }}</span> wrong</span>
376
- </div>
377
- <div class="ax-insight-b">
378
- <strong>Avg P/L when settled</strong>
379
- <span class="mono" style="color:var(--text);">{% if extrema.avg_settled_pl is not none %}{{ extrema.avg_settled_pl|delta_sign }}{% else %}—{% endif %}</span>
380
- <span style="display:block; margin-top:0.35rem;">Mean points change per settled prediction, squad-wide.</span>
381
- </div>
382
- </div>
383
- </div>
384
- </div>
385
-
386
- <div class="card" style="border-color:rgba(59,130,246,0.25); margin-bottom:1.25rem;">
387
- <div class="card-title" style="color:var(--blue);">✨ SQUAD TAKEAWAYS</div>
388
- <ul style="color:var(--muted2); font-size:0.9rem; line-height:1.85; margin-left:1.1rem;">
389
- <li><strong style="color:var(--text);">Winner accuracy</strong> blends bold calls and safe picks — the pool rate is the real “house view” on IPL nights.</li>
390
- <li><strong style="color:var(--text);">MOTM</strong> is high variance by design: a few correct calls move the MOTM board fast.</li>
391
- <li><strong style="color:var(--text);">Crowd vs result</strong> shows whether the group’s modal pick tracks reality — ties count if any tied favourite wins.</li>
392
- <li><strong style="color:var(--text);">Bid mix</strong> shows risk appetite: whale stakes (>300) are rare but memorable.</li>
393
- <li><strong style="color:var(--text);">Personal deep dive?</strong> Use <a href="{{ url_for('history') }}" style="color:var(--orange);">My stats</a> for your own history — this page stays squad-first.</li>
394
- </ul>
395
- </div>
396
- </div>
397
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/base.html DELETED
@@ -1,426 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>{% block title %}DIS IPL 2026{% endblock %}</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com">
8
- <link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@500&display=swap" rel="stylesheet">
9
- <style>
10
- :root {
11
- --bg: #0a0e1a;
12
- --bg2: #111827;
13
- --bg3: #1a2234;
14
- --card: #141c2e;
15
- --border: #1e2d45;
16
- --orange: #f97316;
17
- --orange2: #fb923c;
18
- --gold: #fbbf24;
19
- --blue: #3b82f6;
20
- --green: #22c55e;
21
- --red: #ef4444;
22
- --text: #e2e8f0;
23
- --muted: #64748b;
24
- --muted2: #94a3b8;
25
- --white: #ffffff;
26
- --font-display: 'Bebas Neue', sans-serif;
27
- --font-body: 'DM Sans', sans-serif;
28
- --font-mono: 'JetBrains Mono', monospace;
29
- --radius: 12px;
30
- --radius-sm: 8px;
31
- }
32
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
33
- html { scroll-behavior: smooth; }
34
- body {
35
- font-family: var(--font-body);
36
- background: var(--bg);
37
- color: var(--text);
38
- min-height: 100vh;
39
- line-height: 1.6;
40
- overflow-x: hidden;
41
- }
42
-
43
- /* ── SCROLLBAR ──────────────────────────────── */
44
- ::-webkit-scrollbar { width: 6px; }
45
- ::-webkit-scrollbar-track { background: var(--bg2); }
46
- ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
47
-
48
- /* ── NAVBAR ─────────────────────────────────── */
49
- nav {
50
- background: rgba(10,14,26,0.95);
51
- backdrop-filter: blur(12px);
52
- border-bottom: 1px solid var(--border);
53
- position: sticky; top: 0; z-index: 100;
54
- padding: 0 1.5rem;
55
- }
56
- .nav-inner {
57
- max-width: 1200px; margin: 0 auto;
58
- display: flex; align-items: center; gap: 1rem;
59
- height: 60px;
60
- }
61
- .nav-brand {
62
- font-family: var(--font-display);
63
- font-size: 1.6rem;
64
- letter-spacing: 2px;
65
- color: var(--orange);
66
- text-decoration: none;
67
- display: flex; align-items: center; gap: 0.4rem;
68
- }
69
- .nav-brand .brand-ipl { color: var(--white); font-weight: 600; }
70
- .nav-brand .brand-year { color: var(--gold); }
71
- .nav-links {
72
- display: flex; align-items: center; gap: 0.25rem;
73
- margin-left: auto;
74
- }
75
- .nav-link {
76
- color: var(--muted2); text-decoration: none;
77
- font-size: 0.875rem; font-weight: 500;
78
- padding: 0.4rem 0.75rem; border-radius: var(--radius-sm);
79
- transition: all 0.2s; white-space: nowrap;
80
- }
81
- .nav-link:hover, .nav-link.active { color: var(--white); background: var(--bg3); }
82
- .nav-link.active { color: var(--orange); }
83
- .pts-badge {
84
- font-family: var(--font-mono);
85
- background: linear-gradient(135deg, var(--orange), var(--gold));
86
- color: var(--bg);
87
- font-size: 0.8rem; font-weight: 700;
88
- padding: 0.3rem 0.75rem; border-radius: 99px;
89
- margin-left: 0.5rem;
90
- }
91
- .nav-admin-badge {
92
- background: var(--blue); color: white;
93
- font-size: 0.7rem; padding: 0.2rem 0.5rem;
94
- border-radius: 4px; font-weight: 600;
95
- }
96
- .hamburger { display: none; background: none; border: none; color: var(--muted2); cursor: pointer; font-size: 1.5rem; }
97
-
98
- /* ── LAYOUT ─────────────────────────────────── */
99
- .page { max-width: 1200px; margin: 0 auto; padding: 2rem 1.5rem; }
100
- .page-header { margin-bottom: 2rem; }
101
- .page-title {
102
- font-family: var(--font-display);
103
- font-size: 2.5rem; letter-spacing: 2px;
104
- color: var(--white); line-height: 1;
105
- }
106
- .page-subtitle { color: var(--muted2); margin-top: 0.4rem; }
107
-
108
- /* ── CARDS ───────────────────────────────────── */
109
- .card {
110
- background: var(--card);
111
- border: 1px solid var(--border);
112
- border-radius: var(--radius);
113
- padding: 1.5rem;
114
- }
115
- .card-sm { padding: 1rem; }
116
- .card-title {
117
- font-family: var(--font-display);
118
- font-size: 1.2rem; letter-spacing: 1px;
119
- color: var(--orange); margin-bottom: 1rem;
120
- display: flex; align-items: center; gap: 0.5rem;
121
- }
122
-
123
- /* ── BUTTONS ─────────────────────────────────── */
124
- .btn {
125
- display: inline-flex; align-items: center; gap: 0.4rem;
126
- font-family: var(--font-body); font-weight: 600;
127
- font-size: 0.875rem; padding: 0.6rem 1.25rem;
128
- border-radius: var(--radius-sm); border: none;
129
- cursor: pointer; text-decoration: none;
130
- transition: all 0.2s; white-space: nowrap;
131
- justify-content: center;
132
- }
133
- .btn-primary { background: var(--orange); color: var(--bg); }
134
- .btn-primary:hover { background: var(--orange2); transform: translateY(-1px); }
135
- .btn-secondary { background: var(--bg3); color: var(--text); border: 1px solid var(--border); }
136
- .btn-secondary:hover { border-color: var(--muted); }
137
- .btn-danger { background: var(--red); color: white; }
138
- .btn-danger:hover { opacity: 0.85; }
139
- .btn-success { background: var(--green); color: var(--bg); }
140
- .btn-sm { font-size: 0.8rem; padding: 0.4rem 0.9rem; }
141
- .btn-ghost { background: transparent; color: var(--muted2); border: 1px solid var(--border); }
142
- .btn-ghost:hover { color: var(--text); border-color: var(--muted); }
143
- .btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none !important; }
144
-
145
- /* ── FORMS ───────────────────────────────────── */
146
- .form-group { margin-bottom: 1.25rem; }
147
- label { display: block; font-size: 0.875rem; font-weight: 600; color: var(--muted2); margin-bottom: 0.4rem; }
148
- input, select, textarea {
149
- width: 100%; background: var(--bg2); border: 1px solid var(--border);
150
- border-radius: var(--radius-sm); color: var(--text);
151
- font-family: var(--font-body); font-size: 0.9rem;
152
- padding: 0.65rem 1rem; transition: border-color 0.2s;
153
- outline: none;
154
- }
155
- input:focus, select:focus, textarea:focus { border-color: var(--orange); }
156
- select option { background: var(--bg2); }
157
- textarea { resize: vertical; min-height: 80px; }
158
- .form-row { display: grid; gap: 1rem; }
159
- .form-row.cols-2 { grid-template-columns: 1fr 1fr; }
160
- .form-row.cols-3 { grid-template-columns: 1fr 1fr 1fr; }
161
- .form-hint { font-size: 0.8rem; color: var(--muted); margin-top: 0.3rem; }
162
-
163
- /* ── ALERTS / FLASH ──────────────────────────── */
164
- .alerts { max-width: 1200px; margin: 1rem auto; padding: 0 1.5rem; }
165
- .alert {
166
- padding: 0.75rem 1rem; border-radius: var(--radius-sm);
167
- font-size: 0.9rem; border-left: 3px solid;
168
- display: flex; align-items: center; gap: 0.5rem;
169
- animation: slideIn 0.3s ease;
170
- }
171
- @keyframes slideIn { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
172
- .alert-success { background: rgba(34,197,94,0.1); border-color: var(--green); color: #86efac; }
173
- .alert-danger { background: rgba(239,68,68,0.1); border-color: var(--red); color: #fca5a5; }
174
- .alert-warning { background: rgba(251,191,36,0.1); border-color: var(--gold); color: #fde68a; }
175
- .alert-info { background: rgba(59,130,246,0.1); border-color: var(--blue); color: #93c5fd; }
176
-
177
- /* ── BADGES ──────────────────────────────────── */
178
- .badge {
179
- display: inline-flex; align-items: center;
180
- font-size: 0.7rem; font-weight: 700; letter-spacing: 0.5px;
181
- padding: 0.2rem 0.6rem; border-radius: 99px;
182
- text-transform: uppercase;
183
- }
184
- .badge-upcoming { background: rgba(59,130,246,0.2); color: var(--blue); }
185
- .badge-locked { background: rgba(251,191,36,0.2); color: var(--gold); }
186
- .badge-live { background: rgba(239,68,68,0.2); color: var(--red); }
187
- .badge-completed{ background: rgba(34,197,94,0.2); color: var(--green); }
188
- .badge-abandoned{ background: rgba(100,116,139,0.2);color: var(--muted2); }
189
- .badge-postponed{ background: rgba(249,115,22,0.2); color: var(--orange); }
190
-
191
- .status-dot {
192
- width: 7px; height: 7px; border-radius: 50%; display: inline-block; margin-right: 4px;
193
- }
194
- .dot-live { background: var(--red); animation: pulse 1.5s infinite; }
195
- @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
196
-
197
- /* ── TABLES ──────────────────────────────────── */
198
- .table-wrap {
199
- overflow-x: auto;
200
- -webkit-overflow-scrolling: touch;
201
- scrollbar-width: thin;
202
- }
203
- table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
204
- th {
205
- text-align: left; padding: 0.75rem 1rem;
206
- color: var(--muted); font-size: 0.75rem; font-weight: 600;
207
- text-transform: uppercase; letter-spacing: 0.5px;
208
- border-bottom: 1px solid var(--border);
209
- }
210
- td { padding: 0.875rem 1rem; border-bottom: 1px solid rgba(30,45,69,0.5); }
211
- tr:last-child td { border-bottom: none; }
212
- tr:hover td { background: rgba(255,255,255,0.02); }
213
-
214
- /* ── MATCH CARD ──────────────────────────────── */
215
- .match-card {
216
- background: var(--card);
217
- border: 1px solid var(--border);
218
- border-radius: var(--radius);
219
- padding: 1.25rem;
220
- position: relative; overflow: hidden;
221
- transition: border-color 0.2s, transform 0.2s;
222
- }
223
- .match-card:hover { border-color: var(--muted); }
224
- .match-card.predicted { border-color: rgba(249,115,22,0.4); }
225
- .match-vs {
226
- display: flex; align-items: center; gap: 1rem;
227
- justify-content: space-between;
228
- }
229
- .team-block { text-align: center; flex: 1; }
230
- .team-abbr {
231
- font-family: var(--font-display);
232
- font-size: 2rem; letter-spacing: 2px;
233
- }
234
- .team-name { font-size: 0.75rem; color: var(--muted2); margin-top: 0.2rem; }
235
- .vs-divider {
236
- font-family: var(--font-display);
237
- font-size: 1.2rem; color: var(--muted);
238
- letter-spacing: 2px; flex-shrink: 0;
239
- }
240
- .match-meta {
241
- display: flex; gap: 1rem; flex-wrap: wrap;
242
- margin-top: 0.75rem; font-size: 0.8rem; color: var(--muted2);
243
- }
244
- .match-meta span { display: flex; align-items: center; gap: 0.3rem; }
245
-
246
- /* ── GRID HELPERS ────────────────────────────── */
247
- .grid { display: grid; gap: 1rem; }
248
- .grid-2 { grid-template-columns: repeat(2, 1fr); }
249
- .grid-3 { grid-template-columns: repeat(3, 1fr); }
250
- .grid-4 { grid-template-columns: repeat(4, 1fr); }
251
- .grid-auto { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
252
-
253
- /* ── MISC ────────────────────────────────────── */
254
- .text-orange { color: var(--orange); }
255
- .text-gold { color: var(--gold); }
256
- .text-green { color: var(--green); }
257
- .text-red { color: var(--red); }
258
- .text-muted { color: var(--muted2); }
259
- .text-center { text-align: center; }
260
- .text-right { text-align: right; }
261
- .mt-1 { margin-top: 0.5rem; }
262
- .mt-2 { margin-top: 1rem; }
263
- .mt-3 { margin-top: 1.5rem; }
264
- .mb-1 { margin-bottom: 0.5rem; }
265
- .mb-2 { margin-bottom: 1rem; }
266
- .mono { font-family: var(--font-mono); }
267
- .fw-bold { font-weight: 700; }
268
- hr { border: none; border-top: 1px solid var(--border); margin: 1.5rem 0; }
269
- .section-title {
270
- font-family: var(--font-display); font-size: 1.5rem;
271
- letter-spacing: 1.5px; margin-bottom: 1rem; color: var(--white);
272
- display: flex; align-items: center; justify-content: space-between;
273
- }
274
- .empty-state { text-align: center; padding: 3rem; color: var(--muted); }
275
- .empty-state .icon { font-size: 3rem; margin-bottom: 1rem; }
276
-
277
- /* ── STAT BOX ────────────────────────────────── */
278
- .stat-box {
279
- background: var(--card); border: 1px solid var(--border);
280
- border-radius: var(--radius); padding: 1.25rem;
281
- text-align: center;
282
- }
283
- .stat-value {
284
- font-family: var(--font-mono); font-size: 2rem;
285
- font-weight: 700; line-height: 1;
286
- background: linear-gradient(135deg, var(--orange), var(--gold));
287
- -webkit-background-clip: text; -webkit-text-fill-color: transparent;
288
- }
289
- .stat-label { font-size: 0.8rem; color: var(--muted); margin-top: 0.4rem; text-transform: uppercase; letter-spacing: 0.5px; }
290
-
291
- /* ── POINTS METER ────────────────────────────── */
292
- .points-bar-wrap { margin-top: 0.5rem; }
293
- .points-bar {
294
- height: 6px; background: var(--bg3); border-radius: 3px; overflow: hidden;
295
- }
296
- .points-bar-fill {
297
- height: 100%;
298
- background: linear-gradient(90deg, var(--orange), var(--gold));
299
- border-radius: 3px; transition: width 0.5s ease;
300
- }
301
-
302
- /* ── RESPONSIVE ──────────────────────────────── */
303
- @media (max-width: 1100px) {
304
- .page { padding: 1.4rem 1rem; }
305
- .grid-auto { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
306
- }
307
-
308
- @media (max-width: 768px) {
309
- .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
310
- .form-row.cols-2, .form-row.cols-3 { grid-template-columns: 1fr; }
311
- nav { padding: 0 0.8rem; }
312
- .nav-inner { height: 56px; gap: 0.5rem; }
313
- .nav-brand { font-size: 1.35rem; letter-spacing: 1px; }
314
- .nav-links { display: none; flex-direction: column; position: absolute; top: 60px; left: 0; right: 0;
315
- background: var(--bg2); padding: 0.8rem; border-bottom: 1px solid var(--border); gap: 0.45rem;
316
- max-height: calc(100vh - 56px); overflow-y: auto; }
317
- .nav-links { top: 56px; }
318
- .nav-links.open { display: flex; }
319
- .hamburger { display: block; margin-left: auto; }
320
- .nav-link { width: 100%; text-align: left; }
321
- .pts-badge { margin-left: 0; align-self: flex-start; margin-top: 0.3rem; }
322
- .alerts { padding: 0 0.9rem; margin-top: 0.75rem; }
323
- .alert { font-size: 0.84rem; padding: 0.65rem 0.8rem; }
324
- .page { padding: 0.95rem 0.8rem 1.1rem; }
325
- .page-header { margin-bottom: 1.25rem; }
326
- .page-title { font-size: 1.65rem; letter-spacing: 1px; }
327
- .page-subtitle { font-size: 0.9rem; }
328
- .card { padding: 1rem; border-radius: 10px; }
329
- .card-title { font-size: 1.05rem; margin-bottom: 0.75rem; }
330
- .section-title { font-size: 1.22rem; letter-spacing: 1px; margin-bottom: 0.75rem; gap: 0.55rem; }
331
- .btn { font-size: 0.84rem; padding: 0.55rem 0.95rem; }
332
- .btn-sm { font-size: 0.76rem; padding: 0.38rem 0.72rem; }
333
- input, select, textarea { font-size: 0.88rem; padding: 0.58rem 0.85rem; }
334
- .match-card { padding: 1rem; }
335
- .match-vs { gap: 0.55rem; }
336
- .team-abbr { font-size: 1.5rem; letter-spacing: 1px; }
337
- .team-name { font-size: 0.72rem; }
338
- .vs-divider { font-size: 1rem; letter-spacing: 1px; }
339
- .match-meta { gap: 0.55rem; font-size: 0.75rem; margin-top: 0.62rem; }
340
- .stat-box { padding: 0.9rem; }
341
- .stat-value { font-size: 1.55rem; }
342
- .empty-state { padding: 1.5rem 0.9rem; }
343
- .empty-state .icon { font-size: 2.2rem; margin-bottom: 0.7rem; }
344
- .glow { display: none; }
345
- }
346
-
347
- @media (max-width: 560px) {
348
- table { font-size: 0.8rem; }
349
- th { padding: 0.52rem 0.62rem; font-size: 0.67rem; }
350
- td { padding: 0.62rem; }
351
- .btn { width: 100%; }
352
- .btn.btn-sm { width: auto; }
353
- .section-title { flex-wrap: wrap; }
354
- .section-title a { width: auto; }
355
- }
356
-
357
- /* ── ACCENT GLOW ─────────────────────────────── */
358
- .glow {
359
- position: fixed; pointer-events: none; z-index: 0;
360
- width: 600px; height: 600px; border-radius: 50%;
361
- background: radial-gradient(circle, rgba(249,115,22,0.06) 0%, transparent 70%);
362
- top: -200px; right: -200px;
363
- }
364
- </style>
365
- {% block head %}{% endblock %}
366
- </head>
367
- <body>
368
- <div class="glow"></div>
369
-
370
- <nav>
371
- <div class="nav-inner">
372
- <a href="{{ url_for('index') }}" class="nav-brand" title="{{ app_brand }} — pick name / switch user">DIS <span class="brand-ipl">IPL</span> <span class="brand-year">2026</span></a>
373
- <button class="hamburger" onclick="toggleNav()">☰</button>
374
- <div class="nav-links" id="navLinks">
375
- {% if current_user %}
376
- <a href="{{ url_for('dashboard') }}" class="nav-link {% if request.endpoint == 'dashboard' %}active{% endif %}">🏠 Home</a>
377
- <a href="{{ url_for('user_guide') }}" class="nav-link {% if request.endpoint == 'user_guide' %}active{% endif %}">📘 Guide</a>
378
- <a href="{{ url_for('matches') }}" class="nav-link {% if request.endpoint == 'matches' %}active{% endif %}">📅 Matches</a>
379
- <a href="{{ url_for('team_pool') }}" class="nav-link {% if request.endpoint == 'team_pool' %}active{% endif %}">🃏 Pool</a>
380
- <a href="{{ url_for('leaderboard') }}"class="nav-link {% if request.endpoint == 'leaderboard'%}active{% endif %}">🏆 Board</a>
381
- <a href="{{ url_for('analytics') }}" class="nav-link {% if request.endpoint == 'analytics' %}active{% endif %}">📈 Analytics</a>
382
- <a href="{{ url_for('history') }}" class="nav-link {% if request.endpoint == 'history' %}active{% endif %}">📊 My Stats</a>
383
- {% else %}
384
- <a href="{{ url_for('index') }}" class="nav-link {% if request.endpoint in ['index','identify'] %}active{% endif %}">🏠 Home</a>
385
- {% endif %}
386
- {% if admin_login_configured %}
387
- {% if staff_session %}
388
- <a href="{{ url_for('admin') }}" class="nav-link {% if request.endpoint and request.endpoint.startswith('admin') %}active{% endif %}">⚙️ Admin</a>
389
- {% else %}
390
- <a href="{{ url_for('admin_login', next=url_for('admin')) }}" class="nav-link">🔐 Admin</a>
391
- {% endif %}
392
- {% endif %}
393
- {% if current_user %}
394
- <span class="pts-badge">{{ '%.0f'|format(current_user.points) }} pts</span>
395
- {% endif %}
396
- </div>
397
- </div>
398
- </nav>
399
-
400
- <div class="alerts">
401
- {% for cat, msg in get_flashed_messages(with_categories=True) %}
402
- <div class="alert alert-{{ cat }}" role="alert">
403
- {% if cat == 'success' %}✅{% elif cat == 'danger' %}❌{% elif cat == 'warning' %}⚠️{% else %}ℹ️{% endif %}
404
- {{ msg }}
405
- </div>
406
- {% endfor %}
407
- </div>
408
-
409
- {% block content %}{% endblock %}
410
-
411
- <script>
412
- function toggleNav() {
413
- document.getElementById('navLinks').classList.toggle('open');
414
- }
415
- // Auto-dismiss alerts
416
- setTimeout(() => {
417
- document.querySelectorAll('.alert').forEach(a => {
418
- a.style.transition = 'opacity 0.5s';
419
- a.style.opacity = '0';
420
- setTimeout(() => a.remove(), 500);
421
- });
422
- }, 4000);
423
- </script>
424
- {% block scripts %}{% endblock %}
425
- </body>
426
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/dashboard.html DELETED
@@ -1,263 +0,0 @@
1
- {% extends 'base.html' %}
2
- {% block title %}Dashboard – {{ app_brand }}{% endblock %}
3
- {% block content %}
4
- <div class="page">
5
- <!-- Header -->
6
- <div class="page-header">
7
- <div style="display:flex; align-items:flex-start; justify-content:space-between; flex-wrap:wrap; gap:1rem;">
8
- <div>
9
- <div class="page-title">HEY, {{ (current_user.display_name or current_user.username)|upper }} 👋</div>
10
- <div class="page-subtitle">
11
- {{ today|format_date_weekday }} ·
12
- {% if todays_matches %}
13
- {% set n = todays_matches|length %}
14
- {% if n == 1 %}
15
- <span style="color:var(--gold); font-weight:600;">One match</span> on today’s card.
16
- {% else %}
17
- <span style="color:var(--gold); font-weight:600;">{{ n }} matches</span> on today’s card.
18
- {% endif %}
19
- {% else %}
20
- <span style="color:var(--muted2);">No IPL fixtures today — next games are below.</span>
21
- {% endif %}
22
- </div>
23
- </div>
24
- <div style="display:flex; gap:0.75rem; flex-wrap:wrap;">
25
- <div class="stat-box" style="min-width:160px;">
26
- <div class="stat-value">{{ '%.0f'|format(current_user.points) }}</div>
27
- <div class="stat-label">Your Points</div>
28
- <div style="font-size:0.78rem; color:var(--muted2); margin-top:0.4rem;">#{{ rank }} on leaderboard</div>
29
- </div>
30
- <div class="stat-box" style="min-width:160px;">
31
- <div class="stat-value" style="font-size:1.75rem;">{{ my_streak }}🔥</div>
32
- <div class="stat-label">Best win streak</div>
33
- <div style="font-size:0.78rem; color:var(--muted2); margin-top:0.4rem;">Last 5 IPL results</div>
34
- <div style="display:flex; justify-content:center; gap:4px; margin-top:0.5rem;">
35
- {% for c in my_last5 %}
36
- <span title="{% if c=='green' %}Correct{% elif c=='red' %}Wrong{% else %}No pick / pending{% endif %}" style="width:12px;height:12px;border-radius:50%;display:inline-block;border:1px solid var(--border);{% if c=='green' %}background:var(--green);border-color:var(--green);{% elif c=='red' %}background:var(--red);border-color:var(--red);{% else %}background:var(--bg3);{% endif %}"></span>
37
- {% endfor %}
38
- </div>
39
- </div>
40
- </div>
41
- </div>
42
- </div>
43
-
44
- <!-- Today's Matches -->
45
- <div class="section-title">
46
- 🗓️ TODAY'S MATCHES
47
- <a href="{{ url_for('matches') }}" style="font-size:0.85rem; color:var(--orange); font-family:var(--font-body); font-weight:600; letter-spacing:0; text-decoration:none;">View All →</a>
48
- </div>
49
-
50
- {% if todays_matches %}
51
- <div class="grid grid-auto" style="margin-bottom:2rem;">
52
- {% for match in todays_matches %}
53
- {% set pred = user_preds.get(match.id) %}
54
- <div class="match-card {% if pred %}predicted{% endif %}">
55
- <!-- Status + match number -->
56
- <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:0.75rem;">
57
- <span class="badge badge-{{ match.status }}">
58
- {% if match.status == 'live' %}<span class="status-dot dot-live"></span>{% endif %}
59
- {{ match.status|upper }}
60
- </span>
61
- {% if match.match_number %}<span style="font-size:0.8rem; color:var(--muted);">Match #{{ match.match_number }}</span>{% endif %}
62
- </div>
63
-
64
- <!-- Teams -->
65
- <div class="match-vs">
66
- <div class="team-block">
67
- <div class="team-abbr" style="color:{{ match.team1_color }};">{{ match.team1_abbr }}</div>
68
- <div class="team-name">{{ match.team1 }}</div>
69
- </div>
70
- <div class="vs-divider">VS</div>
71
- <div class="team-block">
72
- <div class="team-abbr" style="color:{{ match.team2_color }};">{{ match.team2_abbr }}</div>
73
- <div class="team-name">{{ match.team2 }}</div>
74
- </div>
75
- </div>
76
-
77
- <!-- Meta -->
78
- <div class="match-meta">
79
- <span>🕐 {{ match.match_time }}</span>
80
- {% if match.venue %}<span>📍 {{ match.venue }}{% if match.city %}, {{ match.city }}{% endif %}</span>{% endif %}
81
- </div>
82
-
83
- {% if match.status == 'completed' and match.winner %}
84
- <div style="margin-top:0.75rem; padding:0.5rem 0.75rem; background:rgba(34,197,94,0.08); border-radius:8px; border:1px solid rgba(34,197,94,0.2);">
85
- <div style="font-size:0.78rem; color:var(--muted2);">RESULT</div>
86
- <div style="font-weight:700; color:var(--green);">🏆 {{ match.winner }}</div>
87
- {% if match.man_of_match %}<div style="font-size:0.8rem; color:var(--muted2);">⭐ MOTM: {{ match.man_of_match }}</div>{% endif %}
88
- </div>
89
- {% endif %}
90
-
91
- <!-- User Prediction -->
92
- {% if pred %}
93
- <div style="margin-top:0.75rem; padding:0.5rem 0.75rem; background:rgba(249,115,22,0.08); border-radius:8px; border:1px solid rgba(249,115,22,0.2);">
94
- <div style="font-size:0.78rem; color:var(--muted2);">YOUR PREDICTION</div>
95
- <div style="display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:0.5rem;">
96
- <div>
97
- <span style="font-weight:700; color:var(--orange);">{{ pred.predicted_winner }}</span>
98
- {% if pred.predicted_motm %}<span style="font-size:0.8rem; color:var(--muted2);"> · ⭐ {{ pred.predicted_motm }}</span>{% endif %}
99
- </div>
100
- <div style="font-family:var(--font-mono); font-size:0.85rem;">
101
- Bid: <strong style="color:var(--gold);">{{ '%.0f'|format(pred.bid_amount) }}</strong>
102
- {% if pred.is_settled %}
103
- · <span class="{{ pred.points_earned|delta_class }}">{{ pred.points_earned|delta_sign }} pts</span>
104
- {% endif %}
105
- </div>
106
- </div>
107
- {% if pred.is_settled %}
108
- <div style="display:flex; gap:0.75rem; margin-top:0.3rem; font-size:0.78rem;">
109
- <span>Winner: {% if pred.winner_correct %}✅{% else %}❌{% endif %}</span>
110
- {% if pred.motm_correct is not none %}<span>MOTM: {% if pred.motm_correct %}✅{% else %}❌{% endif %}</span>{% endif %}
111
- </div>
112
- {% endif %}
113
- </div>
114
- {% endif %}
115
-
116
- <!-- CTA -->
117
- <div style="margin-top:0.75rem;">
118
- {% if match.can_predict and not pred %}
119
- <a href="{{ url_for('predict', match_id=match.id) }}" class="btn btn-primary" style="width:100%; justify-content:center;">🎯 Make Prediction</a>
120
- {% elif match.can_predict and pred and not pred.is_settled %}
121
- <a href="{{ url_for('predict', match_id=match.id) }}" class="btn btn-ghost" style="width:100%; justify-content:center;">✏️ Edit Prediction</a>
122
- {% elif match.status in ('upcoming',) and not match.is_match_today %}
123
- <div style="text-align:center; font-size:0.82rem; color:var(--muted); padding:0.5rem;">📆 Opens on {{ match.match_date|format_date }}</div>
124
- {% elif match.locked or match.status == 'locked' %}
125
- <div style="text-align:center; font-size:0.82rem; color:var(--muted); padding:0.5rem;">🔒 Predictions locked</div>
126
- {% endif %}
127
- </div>
128
- </div>
129
- {% endfor %}
130
- </div>
131
- {% else %}
132
- <div class="card" style="margin-bottom:2rem; text-align:center; padding:3rem;">
133
- <div style="font-size:3rem; margin-bottom:1rem;">🏏</div>
134
- <div style="color:var(--muted2);">No matches today. Check the <a href="{{ url_for('matches') }}" style="color:var(--orange);">full schedule</a>.</div>
135
- </div>
136
- {% endif %}
137
-
138
- {% if upcoming_other %}
139
- <div class="section-title" style="font-size:1.1rem; margin-top:0.25rem;">🔜 COMING UP NEXT</div>
140
- <div class="grid grid-auto" style="margin-bottom:2rem;">
141
- {% for m in upcoming_other %}
142
- <a href="{{ url_for('matches') }}?date={{ m.match_date }}" class="match-card" style="text-decoration:none; color:inherit;">
143
- <div style="font-size:0.75rem; color:var(--muted);">{{ m.match_date|format_date }} · {{ m.match_time }}</div>
144
- <div style="display:flex; align-items:center; justify-content:space-between; margin-top:0.5rem; gap:0.5rem;">
145
- <span style="font-family:var(--font-display); font-size:1.25rem; color:{{ m.team1_color }};">{{ m.team1_abbr }}</span>
146
- <span style="color:var(--muted); font-size:0.75rem;">vs</span>
147
- <span style="font-family:var(--font-display); font-size:1.25rem; color:{{ m.team2_color }};">{{ m.team2_abbr }}</span>
148
- </div>
149
- {% if m.venue %}<div style="font-size:0.78rem; color:var(--muted2); margin-top:0.4rem;">📍 {{ m.venue }}</div>{% endif %}
150
- </a>
151
- {% endfor %}
152
- </div>
153
- {% endif %}
154
-
155
- <!-- Bottom Grid -->
156
- <div class="grid grid-2">
157
- <!-- Leaderboard snapshot -->
158
- <div class="card">
159
- <div class="card-title">🏆 TOP PLAYERS</div>
160
- {% for p in leaders %}
161
- <div style="display:flex; align-items:center; gap:0.75rem; padding:0.6rem 0; {% if not loop.last %}border-bottom:1px solid var(--border);{% endif %}">
162
- <div style="font-family:var(--font-display); font-size:1.5rem; color:{% if loop.index==1 %}var(--gold){% elif loop.index==2 %}var(--muted2){% elif loop.index==3 %}#cd7f32{% else %}var(--muted){% endif %}; min-width:28px;">
163
- {{ loop.index }}
164
- </div>
165
- <div style="flex:1;">
166
- <div style="font-weight:600;">{{ p.display_name or p.username }}</div>
167
- </div>
168
- <div style="font-family:var(--font-mono); font-weight:700; color:{% if p.username == current_user.username %}var(--orange){% else %}var(--text){% endif %};">
169
- {{ '%.0f'|format(p.points) }}
170
- </div>
171
- </div>
172
- {% endfor %}
173
- <div style="margin-top:1rem;">
174
- <a href="{{ url_for('leaderboard') }}" class="btn btn-ghost btn-sm" style="width:100%; justify-content:center;">Full Leaderboard →</a>
175
- </div>
176
- </div>
177
-
178
- <!-- Recent activity -->
179
- <div class="card">
180
- <div class="card-title">📊 RECENT ACTIVITY</div>
181
-
182
- <div style="font-size:0.7rem; font-weight:700; letter-spacing:0.08em; color:var(--muted2); margin-bottom:0.35rem;">🎯 PREDICTIONS</div>
183
- {% if recent_predictions %}
184
- {% for r in recent_predictions %}
185
- <div style="padding:0.45rem 0; {% if not loop.last %}border-bottom:1px solid var(--border);{% endif %}">
186
- <div style="display:flex; justify-content:space-between; align-items:flex-start; gap:0.5rem;">
187
- <div style="flex:1; min-width:0;">
188
- <div style="color:var(--muted2); font-size:0.72rem;">{{ r.match_date|format_date }}{% if r.match_number %} · #{{ r.match_number }}{% endif %}</div>
189
- {% set a1 = team_abbr.get(r.team1, r.team1[:3].upper() if r.team1 else '?') %}
190
- {% set a2 = team_abbr.get(r.team2, r.team2[:3].upper() if r.team2 else '?') %}
191
- <div style="font-size:0.82rem; font-weight:600; margin-top:0.15rem;">
192
- <span style="color:{{ team_colors.get(a1, '#888') }};">{{ a1 }}</span>
193
- <span style="color:var(--muted); font-weight:500;"> vs </span>
194
- <span style="color:{{ team_colors.get(a2, '#888') }};">{{ a2 }}</span>
195
- </div>
196
- <div style="font-size:0.78rem; color:var(--muted2); margin-top:0.2rem;">
197
- Pick <strong style="color:var(--orange);">{{ team_abbr.get(r.predicted_winner, r.predicted_winner[:3] if r.predicted_winner else '—') }}</strong>
198
- {% if r.predicted_motm %} · ⭐ {{ r.predicted_motm }}{% endif %}
199
- · Bid {{ '%.0f'|format(r.bid_amount) }}
200
- </div>
201
- </div>
202
- <div style="text-align:right; flex-shrink:0;">
203
- {% if r.is_settled %}
204
- <div style="font-family:var(--font-mono); font-weight:700; font-size:0.85rem;" class="{{ r.points_earned|delta_class }}">{{ r.points_earned|delta_sign }}</div>
205
- <div style="font-size:0.68rem; color:var(--muted); margin-top:0.15rem;">
206
- {% if r.winner_correct %}✅{% else %}❌{% endif %} W
207
- {% if r.motm_correct is not none %} {% if r.motm_correct %}✅{% else %}❌{% endif %} M{% endif %}
208
- </div>
209
- {% else %}
210
- <span class="badge badge-upcoming" style="font-size:0.62rem;">{{ r.match_status|upper }}</span>
211
- {% endif %}
212
- </div>
213
- </div>
214
- </div>
215
- {% endfor %}
216
- <div style="margin:0.65rem 0 1.1rem;">
217
- <a href="{{ url_for('history') }}#prediction-history" class="btn btn-ghost btn-sm" style="width:100%; justify-content:center;">Full prediction history →</a>
218
- </div>
219
- {% else %}
220
- <div style="font-size:0.82rem; color:var(--muted2); padding:0.5rem 0 1rem;">No predictions yet. <a href="{{ url_for('matches') }}" style="color:var(--orange);">Pick a match</a>.</div>
221
- {% endif %}
222
-
223
- <div style="font-size:0.7rem; font-weight:700; letter-spacing:0.08em; color:var(--muted2); margin-bottom:0.35rem;">💰 POINTS</div>
224
- {% if history %}
225
- {% for h in history %}
226
- <div style="display:flex; justify-content:space-between; align-items:center; padding:0.5rem 0; {% if not loop.last %}border-bottom:1px solid var(--border);{% endif %}">
227
- <div style="font-size:0.85rem; flex:1; padding-right:0.5rem; min-width:0;">
228
- <div style="color:var(--muted2); font-size:0.78rem;">{{ h.created_at[:10]|format_date }}{% if h.created_at and h.created_at|string|length > 10 %} · {{ h.created_at[11:16] }}{% endif %}</div>
229
- <div style="line-height:1.35;">{{ h.reason or 'Adjustment' }}</div>
230
- </div>
231
- <div style="text-align:right; flex-shrink:0;">
232
- <div style="font-family:var(--font-mono); font-weight:700; white-space:nowrap;" class="{{ h.change_amount|delta_class }}">
233
- {{ h.change_amount|delta_sign }}
234
- </div>
235
- {% if h.balance_after is not none %}
236
- <div style="font-size:0.7rem; color:var(--muted); font-family:var(--font-mono); margin-top:0.15rem;">bal {{ '%.0f'|format(h.balance_after) }}</div>
237
- {% endif %}
238
- </div>
239
- </div>
240
- {% endfor %}
241
- <div style="margin-top:0.65rem;">
242
- <a href="{{ url_for('history') }}#points-history" class="btn btn-ghost btn-sm" style="width:100%; justify-content:center;">Full points history →</a>
243
- </div>
244
- {% else %}
245
- <div class="empty-state" style="padding:1.25rem;">
246
- <div style="font-size:0.88rem; color:var(--muted2);">No points movements yet.</div>
247
- </div>
248
- {% endif %}
249
- </div>
250
- </div>
251
-
252
- <!-- Points Config Info -->
253
- <div class="card mt-3" style="padding:1rem 1.5rem;">
254
- <div style="font-size:0.8rem; color:var(--muted); display:flex; flex-wrap:wrap; gap:1.5rem;">
255
- <span>🎯 Correct winner: <strong style="color:var(--green);">+bid pts</strong></span>
256
- <span>❌ Wrong winner: <strong style="color:var(--red);">−bid pts</strong></span>
257
- <span>⭐ Correct MOTM: <strong style="color:var(--green);">+{{ points_config.correct_motm }} pts</strong></span>
258
- <span>🚫 Wrong MOTM: <strong style="color:var(--red);">−{{ points_config.wrong_motm|abs }} pts</strong></span>
259
- <span>🔒 Match day only · {% if points_config.lock_minutes_before %}{{ points_config.lock_minutes_before }} min before start{% else %}lock at start{% endif %}</span>
260
- </div>
261
- </div>
262
- </div>
263
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/guide.html DELETED
@@ -1,66 +0,0 @@
1
- {% extends 'base.html' %}
2
- {% block title %}User guide – {{ app_brand }}{% endblock %}
3
- {% block content %}
4
- <div class="page" style="max-width:720px;">
5
- <div class="page-header">
6
- <div class="page-title">📘 USER GUIDE</div>
7
- <div class="page-subtitle">{{ app_brand }} — {{ app_tagline }}</div>
8
- </div>
9
-
10
- <div class="card" style="margin-bottom:1.25rem;">
11
- <div class="card-title">👤 Signing in</div>
12
- <ul style="color:var(--muted2); font-size:0.92rem; line-height:1.75; margin-left:1.2rem;">
13
- <li>Choose <strong>your name</strong> from the team list (no password).</li>
14
- <li>Optional <strong>Remember me</strong> ties this browser/network to your name for next time.</li>
15
- <li>If someone else uses the same phone or computer, tap the <strong>DIS IPL 2026</strong> logo (top left) to return here and pick their name.</li>
16
- </ul>
17
- </div>
18
-
19
- <div class="card" style="margin-bottom:1.25rem;">
20
- <div class="card-title">🎯 Submitting predictions</div>
21
- <ul style="color:var(--muted2); font-size:0.92rem; line-height:1.75; margin-left:1.2rem;">
22
- <li><strong>When</strong>: Predictions are allowed only on the <strong>scheduled match day</strong>, until the <strong>listed start time</strong> (then they lock automatically).</li>
23
- <li><strong>Where</strong>: Use <strong>Home</strong> for today’s fixtures, or <strong>Matches</strong> for the full schedule. Open a match to predict.</li>
24
- <li><strong>Winner</strong>: Pick which team will win.</li>
25
- <li><strong>Man of the Match</strong> (required): After you pick the winner, choose a player from <strong>that team’s squad</strong> only. You get a bonus if correct and a small penalty if wrong.</li>
26
- <li><strong>Bid</strong>: Stake <strong>{{ points_config.min_bid }}</strong>–<strong>{{ points_config.max_bid }}</strong> points (capped by your balance and the app maximum). Quick buttons use % of your allowed max.</li>
27
- <li><strong>One pick per match</strong>: You can <strong>edit</strong> your prediction until the window closes (same match day, before start).</li>
28
- <li><strong>See everyone’s picks</strong>: Open <strong>Pool</strong> in the top nav — all teammates’ winner, MOTM, and stakes are shown match by match (full transparency).</li>
29
- </ul>
30
- </div>
31
-
32
- <div class="card" style="margin-bottom:1.25rem;">
33
- <div class="card-title">📊 Scoring</div>
34
- <ul style="color:var(--muted2); font-size:0.92rem; line-height:1.75; margin-left:1.2rem;">
35
- <li><strong>Starting balance</strong>: {{ points_config.initial }} points when you join.</li>
36
- <li><strong>Correct winner</strong>: you gain points equal to your <strong>bid</strong> (net <span class="mono text-green">+bid</span>).</li>
37
- <li><strong>Wrong winner</strong>: you lose points equal to your <strong>bid</strong> (net <span class="mono text-red">−bid</span>).</li>
38
- <li><strong>Correct MOTM</strong>: <span class="mono text-green">+{{ points_config.correct_motm }}</span> bonus points (on top of winner win/loss).</li>
39
- <li><strong>Wrong MOTM</strong>: <span class="mono text-red">−{{ points_config.wrong_motm|abs }}</span> points (your pick is compared to the official Man of the Match).</li>
40
- <li><strong>Abandoned match</strong>: your bid on that match is <strong>refunded</strong> (no winner/MOTM scoring).</li>
41
- <li><strong>No prediction</strong>: no automatic point loss — you simply miss that match for points.</li>
42
- </ul>
43
- </div>
44
-
45
- <div class="card" style="margin-bottom:1.25rem;">
46
- <div class="card-title">🏆 Leaderboard &amp; form</div>
47
- <ul style="color:var(--muted2); font-size:0.92rem; line-height:1.75; margin-left:1.2rem;">
48
- <li>Ranks use <strong>total points</strong> (starting balance plus all settled match outcomes).</li>
49
- <li><strong>Last 5</strong> dots on the board: <span class="text-green">green</span> = correct winner, <span class="text-red">red</span> = wrong, <span style="color:var(--muted2);">white</span> = no pick for that finished match.</li>
50
- <li><strong>Streak</strong> shows your best run of correct winner calls.</li>
51
- </ul>
52
- </div>
53
-
54
- <div class="card" style="margin-bottom:1.25rem;">
55
- <div class="card-title">📈 Analytics &amp; history</div>
56
- <ul style="color:var(--muted2); font-size:0.92rem; line-height:1.75; margin-left:1.2rem;">
57
- <li><strong>Analytics</strong>: Pool-wide stats, popular picks, MOTM leaders, and your personal numbers.</li>
58
- <li><strong>My stats</strong>: Full prediction and points history.</li>
59
- </ul>
60
- </div>
61
-
62
- <p style="text-align:center; color:var(--muted); font-size:0.9rem; margin-top:0.25rem;">
63
- 🚀 <strong>Compete, strategize, and climb the leaderboard!</strong>
64
- </p>
65
- </div>
66
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/history.html DELETED
@@ -1,151 +0,0 @@
1
- {% extends 'base.html' %}
2
- {% block title %}My stats – {{ app_brand }}{% endblock %}
3
- {% block content %}
4
- <div class="page">
5
- <div class="page-header">
6
- <div class="page-title">MY STATS</div>
7
- <div class="page-subtitle">{{ current_user.display_name or current_user.username }}'s prediction history</div>
8
- </div>
9
-
10
- <!-- Summary stats -->
11
- {% set total = records|length %}
12
- {% set settled = records|selectattr('is_settled','equalto',1)|list|length %}
13
- {% set correct_w = records|selectattr('winner_correct','equalto',1)|list|length %}
14
- {% set correct_m = records|selectattr('motm_correct','equalto',1)|list|length %}
15
- {% set win_pct = (correct_w / settled * 100)|int if settled > 0 else 0 %}
16
-
17
- <div class="grid grid-4" style="margin-bottom:2rem;">
18
- <div class="stat-box">
19
- <div class="stat-value">{{ '%.0f'|format(current_user.points) }}</div>
20
- <div class="stat-label">Current Points</div>
21
- </div>
22
- <div class="stat-box">
23
- <div class="stat-value">{{ total }}</div>
24
- <div class="stat-label">Predictions Made</div>
25
- </div>
26
- <div class="stat-box">
27
- <div class="stat-value">{{ win_pct }}%</div>
28
- <div class="stat-label">Winner Accuracy</div>
29
- </div>
30
- <div class="stat-box">
31
- <div class="stat-value">{{ correct_m }}</div>
32
- <div class="stat-label">MOTM Correct</div>
33
- </div>
34
- </div>
35
-
36
- <!-- Prediction History -->
37
- <div class="section-title" id="prediction-history" style="scroll-margin-top:5rem;">📋 PREDICTION HISTORY</div>
38
- {% if records %}
39
- <div class="card" style="margin-bottom:2rem;">
40
- <div class="table-wrap">
41
- <table>
42
- <thead>
43
- <tr>
44
- <th>#</th>
45
- <th>Match</th>
46
- <th>Date</th>
47
- <th>Your Pick</th>
48
- <th>MOTM Pick</th>
49
- <th>Bid</th>
50
- <th>Result</th>
51
- <th>P/L</th>
52
- </tr>
53
- </thead>
54
- <tbody>
55
- {% for r in records %}
56
- <tr>
57
- <td style="color:var(--muted); font-size:0.8rem;">{{ r.match_number or '—' }}</td>
58
- <td>
59
- <div style="font-weight:600;">
60
- {{ teams_abbr.get(r.team1, r.team1[:3]) }} vs {{ teams_abbr.get(r.team2, r.team2[:3]) }}
61
- </div>
62
- <span class="badge badge-{{ r.match_status }}" style="font-size:0.65rem;">{{ r.match_status }}</span>
63
- </td>
64
- <td style="font-size:0.85rem; color:var(--muted2);">{{ r.match_date|format_date }}</td>
65
- <td>
66
- <div style="font-weight:600; color:var(--orange);">{{ teams_abbr.get(r.predicted_winner, r.predicted_winner) }}</div>
67
- {% if r.is_settled %}
68
- {% if r.winner_correct %}✅{% else %}❌{% endif %}
69
- <span style="font-size:0.78rem; color:var(--muted2);">{{ r.match_winner }}</span>
70
- {% endif %}
71
- </td>
72
- <td style="font-size:0.85rem;">
73
- {% if r.predicted_motm %}
74
- <div>{{ r.predicted_motm }}</div>
75
- {% if r.is_settled and r.motm_correct is not none %}
76
- {% if r.motm_correct %}✅{% else %}❌{% endif %}
77
- <span style="font-size:0.78rem; color:var(--muted2);">{{ r.man_of_match or '?' }}</span>
78
- {% endif %}
79
- {% else %}
80
- <span style="color:var(--muted);">—</span>
81
- {% endif %}
82
- </td>
83
- <td style="font-family:var(--font-mono);">{{ '%.0f'|format(r.bid_amount) }}</td>
84
- <td>
85
- {% if r.is_settled %}
86
- <span class="badge {% if r.winner_correct %}badge-completed{% else %}badge-live{% endif %}">
87
- {{ 'WIN' if r.winner_correct else 'LOSS' }}
88
- </span>
89
- {% elif r.match_status in ('upcoming','locked','live') %}
90
- <span class="badge badge-upcoming">PENDING</span>
91
- {% else %}
92
- <span class="badge badge-abandoned">N/A</span>
93
- {% endif %}
94
- </td>
95
- <td>
96
- {% if r.is_settled %}
97
- <div class="{{ r.points_earned|delta_class }}" style="font-family:var(--font-mono); font-weight:700;">
98
- {{ r.points_earned|delta_sign }}
99
- </div>
100
- {% else %}—{% endif %}
101
- </td>
102
- </tr>
103
- {% endfor %}
104
- </tbody>
105
- </table>
106
- </div>
107
- </div>
108
- {% else %}
109
- <div class="empty-state card">
110
- <div class="icon">🎯</div>
111
- <div>No predictions yet. <a href="{{ url_for('dashboard') }}" style="color:var(--orange);">Start predicting!</a></div>
112
- </div>
113
- {% endif %}
114
-
115
- <!-- Points History -->
116
- <div class="section-title" id="points-history" style="scroll-margin-top:5rem;">💰 POINTS HISTORY</div>
117
- {% if ph %}
118
- <div class="card">
119
- <div class="table-wrap">
120
- <table>
121
- <thead>
122
- <tr>
123
- <th>Date</th>
124
- <th>Description</th>
125
- <th style="text-align:right;">Change</th>
126
- <th style="text-align:right;">Balance</th>
127
- </tr>
128
- </thead>
129
- <tbody>
130
- {% for h in ph %}
131
- <tr>
132
- <td style="font-size:0.8rem; color:var(--muted2); white-space:nowrap;">{{ h.created_at[:16] }}</td>
133
- <td style="font-size:0.875rem;">{{ h.reason or 'Adjustment' }}</td>
134
- <td style="text-align:right; font-family:var(--font-mono); font-weight:700;" class="{{ h.change_amount|delta_class }}">
135
- {{ h.change_amount|delta_sign }}
136
- </td>
137
- <td style="text-align:right; font-family:var(--font-mono);">{{ '%.0f'|format(h.balance_after) }}</td>
138
- </tr>
139
- {% endfor %}
140
- </tbody>
141
- </table>
142
- </div>
143
- </div>
144
- {% else %}
145
- <div class="empty-state card">
146
- <div class="icon">💸</div>
147
- <div>No transactions yet.</div>
148
- </div>
149
- {% endif %}
150
- </div>
151
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/identify.html DELETED
@@ -1,115 +0,0 @@
1
- {% extends 'base.html' %}
2
- {% block title %}Home – {{ app_brand }}{% endblock %}
3
- {% block head %}
4
- <style>
5
- .home-wrap { max-width: 620px; }
6
- .home-hero {
7
- position: relative;
8
- overflow: hidden;
9
- border-color: rgba(249,115,22,0.28);
10
- background:
11
- radial-gradient(circle at 10% 10%, rgba(249,115,22,0.18), transparent 48%),
12
- radial-gradient(circle at 90% 20%, rgba(59,130,246,0.14), transparent 44%),
13
- linear-gradient(165deg, var(--card), #0f182b);
14
- }
15
- .home-hero:after {
16
- content: "";
17
- position: absolute;
18
- inset: 0;
19
- pointer-events: none;
20
- background: linear-gradient(120deg, transparent 0%, rgba(255,255,255,0.03) 45%, transparent 100%);
21
- }
22
- .home-hero-inner { position: relative; z-index: 1; text-align: center; }
23
- .home-title { font-size: 2rem; letter-spacing: 3px; }
24
- .home-sub {
25
- font-size: 0.95rem;
26
- color: var(--muted2);
27
- margin-top: 0.75rem;
28
- line-height: 1.45;
29
- }
30
- .home-chip-row {
31
- display: flex;
32
- justify-content: center;
33
- gap: 0.5rem;
34
- flex-wrap: wrap;
35
- margin-top: 1rem;
36
- }
37
- .home-chip {
38
- font-size: 0.72rem;
39
- letter-spacing: 0.05em;
40
- padding: 0.3rem 0.55rem;
41
- border-radius: 999px;
42
- border: 1px solid var(--border);
43
- background: rgba(10,14,26,0.45);
44
- color: var(--muted2);
45
- }
46
- .identity-panel { border-color: rgba(148,163,184,0.28); }
47
- .current-user-panel {
48
- text-align: center;
49
- padding: 1rem;
50
- border: 1px solid rgba(34,197,94,0.28);
51
- border-radius: var(--radius-sm);
52
- background: rgba(34,197,94,0.06);
53
- margin-bottom: 1rem;
54
- }
55
- </style>
56
- {% endblock %}
57
- {% block content %}
58
- <div class="page home-wrap">
59
- <div class="card home-hero" style="margin-bottom:1.25rem;">
60
- <div class="home-hero-inner">
61
- <div style="font-size:2.4rem; margin-bottom:0.35rem;">🏏</div>
62
- <div class="page-title home-title">
63
- DIS <span style="color:var(--white);">IPL</span> <span style="color:var(--gold);">2026</span>
64
- </div>
65
- <div style="font-size:1.02rem; color:var(--orange); font-weight:700; margin-top:0.35rem;">DIS IPL Match Predictions</div>
66
- <div class="home-sub">🏆 {{ app_tagline }} 🏏</div>
67
- <div class="home-chip-row">
68
- <span class="home-chip">Team pool</span>
69
- <span class="home-chip">Match-day picks</span>
70
- <span class="home-chip">Live leaderboard</span>
71
- </div>
72
- </div>
73
- </div>
74
-
75
- <div class="card identity-panel">
76
- <div class="card-title">WHO ARE YOU?</div>
77
- {% if current_user %}
78
- <div class="current-user-panel">
79
- <div style="font-size:0.82rem; color:var(--muted2);">You’re signed in as</div>
80
- <div style="font-size:1.28rem; font-weight:700; color:var(--orange); margin-top:0.25rem;">{{ current_user.display_name or current_user.username }}</div>
81
- <a href="{{ url_for('dashboard') }}" class="btn btn-primary" style="margin-top:0.9rem; width:100%; max-width:310px; justify-content:center;">Open app — today’s matches</a>
82
- <div style="font-size:0.8rem; color:var(--muted); margin-top:0.8rem;">Switch user? Select another teammate below.</div>
83
- </div>
84
- {% endif %}
85
-
86
- <form method="post" action="{{ url_for('index') }}">
87
- <input type="hidden" name="remember" value="1">
88
- <div class="form-group">
89
- <label for="member_key">Team member</label>
90
- <select name="member_key" id="member_key" required>
91
- <option value="">Select your name…</option>
92
- {% for m in members %}
93
- <option value="{{ m.key }}"
94
- {% if suggested and suggested.member_key == m.key %}selected{% endif %}>
95
- {{ m.display_name or m.key }}
96
- </option>
97
- {% endfor %}
98
- </select>
99
- </div>
100
- <button type="submit" class="btn btn-primary" style="width:100%; justify-content:center; padding:0.9rem;">Let’s go →</button>
101
- </form>
102
-
103
- <p style="margin-top:1rem; font-size:0.8rem; color:var(--muted); text-align:center;">
104
- New teammate? Ask your admin to add you to the team roster.
105
- </p>
106
- <p style="margin-top:0.5rem; font-size:0.8rem; text-align:center;">
107
- {% if admin_login_configured %}
108
- <a href="{{ url_for('admin_login', next=url_for('admin')) }}" style="color:var(--orange);">🔐 Admin login</a>
109
- {% else %}
110
- <span style="color:var(--muted);">Admin login is not enabled yet.</span>
111
- {% endif %}
112
- </p>
113
- </div>
114
- </div>
115
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/leaderboard.html DELETED
@@ -1,240 +0,0 @@
1
- {% extends 'base.html' %}
2
- {% block title %}Leaderboard – {{ app_brand }}{% endblock %}
3
- {% block head %}
4
- <style>
5
- .leaderboard-table-wrap {
6
- max-height: min(70vh, 640px);
7
- overflow: auto;
8
- -webkit-overflow-scrolling: touch;
9
- }
10
- .leaderboard-table-wrap table {
11
- min-width: 880px;
12
- }
13
- .leaderboard-table-wrap thead th {
14
- position: sticky;
15
- top: 0;
16
- z-index: 2;
17
- background: var(--card);
18
- box-shadow: 0 1px 0 var(--border);
19
- }
20
- .podium-wrap {
21
- display: flex;
22
- align-items: flex-end;
23
- justify-content: center;
24
- gap: 1rem;
25
- margin-bottom: 2.5rem;
26
- flex-wrap: wrap;
27
- }
28
- @media (max-width: 760px) {
29
- .podium-wrap {
30
- display: flex;
31
- align-items: flex-end;
32
- justify-content: center;
33
- gap: 0.45rem;
34
- margin-bottom: 1.2rem;
35
- flex-wrap: nowrap;
36
- }
37
- .podium-wrap > div { min-width: 0; flex: 1 1 0 !important; }
38
- .podium-wrap .podium-rank-1 { order: 2; }
39
- .podium-wrap .podium-rank-2 { order: 1; }
40
- .podium-wrap .podium-rank-3 { order: 3; }
41
- .podium-wrap .podium-name { font-size: 0.95rem !important; }
42
- .podium-wrap .podium-pts { font-size: 1.15rem !important; }
43
- .podium-wrap .podium-col { height: 46px !important; }
44
- .leaderboard-table-wrap table {
45
- min-width: 760px;
46
- font-size: 0.84rem;
47
- }
48
- .leaderboard-table-wrap thead th:nth-child(1),
49
- .leaderboard-table-wrap tbody td:nth-child(1) {
50
- position: sticky;
51
- left: 0;
52
- z-index: 3;
53
- background: var(--card);
54
- box-shadow: 1px 0 0 var(--border);
55
- }
56
- .leaderboard-table-wrap thead th:nth-child(2),
57
- .leaderboard-table-wrap tbody td:nth-child(2) {
58
- position: sticky;
59
- left: 52px;
60
- z-index: 3;
61
- background: var(--card);
62
- box-shadow: 1px 0 0 var(--border);
63
- min-width: 140px;
64
- }
65
- .leaderboard-table-wrap tbody td { padding-top: 0.5rem; padding-bottom: 0.5rem; }
66
- }
67
- @media (max-width: 560px) {
68
- .podium-wrap { gap: 0.35rem; margin-bottom: 1rem; }
69
- .podium-wrap .podium-name { font-size: 0.84rem !important; }
70
- .podium-wrap .podium-pts { font-size: 1rem !important; }
71
- .podium-wrap .podium-col { height: 34px !important; }
72
- .leaderboard-table-wrap thead th:nth-child(4),
73
- .leaderboard-table-wrap tbody td:nth-child(4),
74
- .leaderboard-table-wrap thead th:nth-child(6),
75
- .leaderboard-table-wrap tbody td:nth-child(6),
76
- .leaderboard-table-wrap thead th:nth-child(8),
77
- .leaderboard-table-wrap tbody td:nth-child(8) {
78
- display: none;
79
- }
80
- .leaderboard-table-wrap table {
81
- min-width: 640px;
82
- font-size: 0.8rem;
83
- }
84
- }
85
- </style>
86
- {% endblock %}
87
- {% block content %}
88
- <div class="page">
89
- <div class="page-header">
90
- <div class="page-title">🏆 LEADERBOARD</div>
91
- <div class="page-subtitle">Who's dominating the predictions game?</div>
92
- </div>
93
-
94
- <!-- Podium (top 3) -->
95
- {% if players|length >= 3 %}
96
- <div class="podium-wrap">
97
- <!-- 2nd -->
98
- <div class="podium-rank-2" style="text-align:center; flex:0 0 180px;">
99
- <div style="font-size:2rem;">🥈</div>
100
- <div style="background:var(--card); border:1px solid var(--border); border-radius:12px 12px 0 0; padding:1rem 0.75rem; border-bottom:none;">
101
- <div class="podium-name" style="font-family:var(--font-display); font-size:1.1rem; color:var(--muted2);">{{ players[1].display_name or players[1].username }}</div>
102
- <div class="podium-pts" style="font-family:var(--font-mono); font-size:1.5rem; color:var(--muted2); font-weight:700;">{{ '%.0f'|format(players[1].points) }}</div>
103
- <div style="font-size:0.75rem; color:var(--muted);">pts</div>
104
- {% if players[1].settled_count > 0 %}
105
- <div style="font-size:0.75rem; color:var(--muted); margin-top:0.5rem;">{{ players[1].correct_winners }}/{{ players[1].settled_count }} wins</div>
106
- {% endif %}
107
- </div>
108
- <div class="podium-col" style="background:var(--bg3); border:1px solid var(--border); height:80px; border-radius:0 0 8px 8px;"></div>
109
- </div>
110
- <!-- 1st -->
111
- <div class="podium-rank-1" style="text-align:center; flex:0 0 200px;">
112
- <div style="font-size:2.5rem;">🥇</div>
113
- <div style="background:linear-gradient(135deg,var(--card),rgba(251,191,36,0.08)); border:1px solid rgba(251,191,36,0.3); border-radius:12px 12px 0 0; padding:1.25rem 0.75rem; border-bottom:none;">
114
- <div class="podium-name" style="font-family:var(--font-display); font-size:1.3rem; color:var(--gold);">{{ players[0].display_name or players[0].username }}</div>
115
- <div class="podium-pts" style="font-family:var(--font-mono); font-size:2rem; color:var(--gold); font-weight:700;">{{ '%.0f'|format(players[0].points) }}</div>
116
- <div style="font-size:0.75rem; color:var(--muted);">pts</div>
117
- {% if players[0].settled_count > 0 %}
118
- <div style="font-size:0.75rem; color:var(--muted); margin-top:0.5rem;">{{ players[0].correct_winners }}/{{ players[0].settled_count }} wins</div>
119
- {% endif %}
120
- </div>
121
- <div class="podium-col" style="background:linear-gradient(to bottom, rgba(251,191,36,0.1), var(--bg3)); border:1px solid var(--border); height:110px; border-radius:0 0 8px 8px;"></div>
122
- </div>
123
- <!-- 3rd -->
124
- <div class="podium-rank-3" style="text-align:center; flex:0 0 180px;">
125
- <div style="font-size:2rem;">🥉</div>
126
- <div style="background:var(--card); border:1px solid var(--border); border-radius:12px 12px 0 0; padding:1rem 0.75rem; border-bottom:none;">
127
- <div class="podium-name" style="font-family:var(--font-display); font-size:1.1rem; color:#cd7f32;">{{ players[2].display_name or players[2].username }}</div>
128
- <div class="podium-pts" style="font-family:var(--font-mono); font-size:1.5rem; color:#cd7f32; font-weight:700;">{{ '%.0f'|format(players[2].points) }}</div>
129
- <div style="font-size:0.75rem; color:var(--muted);">pts</div>
130
- {% if players[2].settled_count > 0 %}
131
- <div style="font-size:0.75rem; color:var(--muted); margin-top:0.5rem;">{{ players[2].correct_winners }}/{{ players[2].settled_count }} wins</div>
132
- {% endif %}
133
- </div>
134
- <div class="podium-col" style="background:var(--bg3); border:1px solid var(--border); height:55px; border-radius:0 0 8px 8px;"></div>
135
- </div>
136
- </div>
137
- {% endif %}
138
-
139
- <!-- Full Table -->
140
- <div class="card">
141
- <div class="table-wrap leaderboard-table-wrap">
142
- <table>
143
- <thead>
144
- <tr>
145
- <th>Rank</th>
146
- <th>Player</th>
147
- <th style="text-align:right;">Points</th>
148
- <th style="text-align:center;">Predictions</th>
149
- <th style="text-align:center;">Winner %</th>
150
- <th style="text-align:center;">MOTM Hits</th>
151
- <th style="text-align:center;" title="Last 5 completed IPL matches: green=correct, red=wrong, white=no pick">Form</th>
152
- <th style="text-align:center;">Streak</th>
153
- <th style="text-align:right;">P/L</th>
154
- </tr>
155
- </thead>
156
- <tbody>
157
- {% for p in players %}
158
- {% set is_me = p.username == current_user.username %}
159
- <tr style="{% if is_me %}background:rgba(249,115,22,0.05);{% endif %}">
160
- <td>
161
- <span style="font-family:var(--font-display); font-size:1.4rem; color:
162
- {% if loop.index == 1 %}var(--gold)
163
- {% elif loop.index == 2 %}var(--muted2)
164
- {% elif loop.index == 3 %}#cd7f32
165
- {% else %}var(--muted){% endif %};">
166
- {{ loop.index }}
167
- </span>
168
- </td>
169
- <td>
170
- <div style="font-weight:{% if is_me %}700{% else %}600{% endif %}; color:{% if is_me %}var(--orange){% else %}var(--text){% endif %};">
171
- {{ p.display_name or p.username }}
172
- {% if is_me %}<span style="font-size:0.75rem; color:var(--muted2); margin-left:0.3rem;">(you)</span>{% endif %}
173
- </div>
174
- </td>
175
- <td style="text-align:right;">
176
- <div style="font-family:var(--font-mono); font-weight:700; font-size:1.1rem; color:{% if is_me %}var(--orange){% else %}var(--text){% endif %};">
177
- {{ '%.0f'|format(p.points) }}
178
- </div>
179
- {% set delta = p.points - initial_points %}
180
- <div style="font-size:0.78rem;" class="{{ delta|delta_class }}">{{ delta|delta_sign }}</div>
181
- </td>
182
- <td style="text-align:center; font-family:var(--font-mono);">
183
- {{ p.total_predictions }}
184
- </td>
185
- <td style="text-align:center;">
186
- {% if p.settled_count > 0 %}
187
- {% set pct = (p.correct_winners / p.settled_count * 100)|int %}
188
- <div style="font-family:var(--font-mono);">{{ pct }}%</div>
189
- <div style="font-size:0.75rem; color:var(--muted);">{{ p.correct_winners }}/{{ p.settled_count }}</div>
190
- {% else %}—{% endif %}
191
- </td>
192
- <td style="text-align:center; font-family:var(--font-mono);">
193
- {{ p.correct_motms or 0 }}
194
- </td>
195
- <td style="text-align:center;">
196
- <div style="display:flex; justify-content:center; gap:3px; flex-wrap:nowrap;">
197
- {% for c in p.last5 %}
198
- <span title="{% if c=='green' %}Correct{% elif c=='red' %}Wrong{% else %}No pick{% endif %}" style="width:11px;height:11px;border-radius:50%;display:inline-block;border:1px solid var(--border);{% if c=='green' %}background:var(--green);border-color:var(--green);{% elif c=='red' %}background:var(--red);border-color:var(--red);{% else %}background:var(--bg3);{% endif %}"></span>
199
- {% endfor %}
200
- </div>
201
- </td>
202
- <td style="text-align:center; font-family:var(--font-mono); font-size:0.9rem;">
203
- {{ p.best_streak }}🔥
204
- </td>
205
- <td style="text-align:right;">
206
- {% set earned = p.total_earned %}
207
- <div style="font-family:var(--font-mono); font-weight:700;" class="{{ earned|delta_class }}">{{ earned|delta_sign }}</div>
208
- </td>
209
- </tr>
210
- {% endfor %}
211
- </tbody>
212
- </table>
213
- </div>
214
- </div>
215
-
216
- <!-- Recent Results -->
217
- {% if recent_results %}
218
- <div style="margin-top:2rem;">
219
- <div class="section-title" style="font-size:1.2rem;">📋 RECENT RESULTS</div>
220
- <div class="grid grid-auto">
221
- {% for m in recent_results %}
222
- <div class="card card-sm">
223
- <div style="display:flex; justify-content:space-between; margin-bottom:0.5rem;">
224
- <span style="font-size:0.78rem; color:var(--muted);">Match #{{ m.match_number or '?' }}</span>
225
- <span style="font-size:0.78rem; color:var(--muted);">{{ m.match_date|format_date }}</span>
226
- </div>
227
- <div style="display:flex; align-items:center; gap:0.75rem; justify-content:center;">
228
- <span style="font-family:var(--font-display); font-size:1.2rem; color:{{ m.team1_color }}; {% if m.winner == m.team1 %}text-shadow:0 0 12px {{ m.team1_color }};{% endif %}">{{ m.team1_abbr }}</span>
229
- <span style="color:var(--muted); font-size:0.8rem;">vs</span>
230
- <span style="font-family:var(--font-display); font-size:1.2rem; color:{{ m.team2_color }}; {% if m.winner == m.team2 %}text-shadow:0 0 12px {{ m.team2_color }};{% endif %}">{{ m.team2_abbr }}</span>
231
- </div>
232
- <div style="text-align:center; margin-top:0.5rem; font-size:0.85rem; color:var(--green); font-weight:700;">🏆 {{ m.winner }}</div>
233
- {% if m.man_of_match %}<div style="text-align:center; font-size:0.78rem; color:var(--muted2);">⭐ {{ m.man_of_match }}</div>{% endif %}
234
- </div>
235
- {% endfor %}
236
- </div>
237
- </div>
238
- {% endif %}
239
- </div>
240
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/matches.html DELETED
@@ -1,118 +0,0 @@
1
- {% extends 'base.html' %}
2
- {% block title %}Matches – {{ app_brand }}{% endblock %}
3
- {% block content %}
4
- <div class="page">
5
- <div class="page-header">
6
- <div class="page-title">ALL MATCHES</div>
7
- <div class="page-subtitle">Full schedule &amp; your predictions — {{ app_brand }}</div>
8
- </div>
9
-
10
- <!-- Filters -->
11
- <form method="get" style="display:flex; gap:0.75rem; flex-wrap:wrap; margin-bottom:1.5rem; align-items:flex-end;">
12
- <div>
13
- <label style="font-size:0.8rem; color:var(--muted2); display:block; margin-bottom:0.3rem;">Status</label>
14
- <select name="status" style="width:auto; padding:0.5rem 0.75rem;">
15
- <option value="">All</option>
16
- {% for s in statuses %}
17
- <option value="{{ s }}" {% if filter_status == s %}selected{% endif %}>{{ s|capitalize }}</option>
18
- {% endfor %}
19
- </select>
20
- </div>
21
- <div>
22
- <label style="font-size:0.8rem; color:var(--muted2); display:block; margin-bottom:0.3rem;">Date</label>
23
- <input type="date" name="date" value="{{ filter_date }}" style="width:auto; padding:0.5rem 0.75rem;">
24
- </div>
25
- <button type="submit" class="btn btn-secondary btn-sm">Filter</button>
26
- <a href="{{ url_for('matches') }}" class="btn btn-ghost btn-sm">Clear</a>
27
- </form>
28
-
29
- {% if matches %}
30
- <div class="card">
31
- <div class="table-wrap">
32
- <table>
33
- <thead>
34
- <tr>
35
- <th>#</th>
36
- <th>Teams</th>
37
- <th>Date & Time</th>
38
- <th>Venue</th>
39
- <th>Status</th>
40
- <th>Result</th>
41
- <th>Your Prediction</th>
42
- <th>P/L</th>
43
- </tr>
44
- </thead>
45
- <tbody>
46
- {% for match in matches %}
47
- {% set pred = pred_map.get(match.id) %}
48
- <tr>
49
- <td style="color:var(--muted); font-family:var(--font-mono);">
50
- {{ match.match_number or '—' }}
51
- </td>
52
- <td>
53
- <div style="font-weight:700;">
54
- <span style="color:{{ match.team1_color }};">{{ match.team1_abbr }}</span>
55
- <span style="color:var(--muted);"> vs </span>
56
- <span style="color:{{ match.team2_color }};">{{ match.team2_abbr }}</span>
57
- </div>
58
- <div style="font-size:0.75rem; color:var(--muted2);">{{ match.team1 }} vs {{ match.team2 }}</div>
59
- </td>
60
- <td style="white-space:nowrap;">
61
- <div>{{ match.match_date|format_date }}</div>
62
- <div style="font-size:0.8rem; color:var(--muted2);">{{ match.match_time }}</div>
63
- </td>
64
- <td style="font-size:0.85rem; color:var(--muted2);">
65
- {{ match.venue or '—' }}{% if match.city and match.venue %}<br>{% endif %}
66
- {% if match.city %}<span style="font-size:0.78rem;">{{ match.city }}</span>{% endif %}
67
- </td>
68
- <td><span class="badge badge-{{ match.status }}">{{ match.status }}</span></td>
69
- <td>
70
- {% if match.winner and match.winner != 'ABANDONED' %}
71
- <div style="font-weight:700; color:var(--green);">🏆 {{ match.winner }}</div>
72
- {% if match.man_of_match %}<div style="font-size:0.78rem; color:var(--muted2);">⭐ {{ match.man_of_match }}</div>{% endif %}
73
- {% elif match.status == 'abandoned' %}
74
- <span style="color:var(--muted);">Abandoned</span>
75
- {% else %}—{% endif %}
76
- </td>
77
- <td>
78
- {% if pred %}
79
- <div style="font-weight:600; color:var(--orange);">
80
- {{ team_abbr.get(pred.predicted_winner, pred.predicted_winner) }}
81
- </div>
82
- {% if pred.predicted_motm %}<div style="font-size:0.78rem; color:var(--muted2);">⭐ {{ pred.predicted_motm }}</div>{% endif %}
83
- <div style="font-size:0.78rem; font-family:var(--font-mono);">Bid: {{ '%.0f'|format(pred.bid_amount) }}</div>
84
- {% elif match.can_predict %}
85
- <a href="{{ url_for('predict', match_id=match.id) }}" class="btn btn-primary btn-sm">Predict</a>
86
- {% elif match.status == 'upcoming' %}
87
- <a href="{{ url_for('predict', match_id=match.id) }}" class="btn btn-ghost btn-sm" title="{% if not match.is_match_today %}Opens on match day{% else %}View{% endif %}">{% if not match.is_match_today %}📆 {{ match.match_date|format_date }}{% elif match.locked %}🔒 Locked{% else %}View{% endif %}</a>
88
- {% else %}
89
- <span style="color:var(--muted); font-size:0.85rem;">—</span>
90
- {% endif %}
91
- </td>
92
- <td>
93
- {% if pred and pred.is_settled %}
94
- <div class="{{ pred.points_earned|delta_class }}" style="font-family:var(--font-mono); font-weight:700;">
95
- {{ pred.points_earned|delta_sign }}
96
- </div>
97
- <div style="font-size:0.75rem; color:var(--muted);">
98
- W:{% if pred.winner_correct %}✅{% else %}❌{% endif %}
99
- {% if pred.motm_correct is not none %}M:{% if pred.motm_correct %}✅{% else %}❌{% endif %}{% endif %}
100
- </div>
101
- {% elif pred and not pred.is_settled %}
102
- <span style="color:var(--muted); font-size:0.8rem;">Pending</span>
103
- {% else %}—{% endif %}
104
- </td>
105
- </tr>
106
- {% endfor %}
107
- </tbody>
108
- </table>
109
- </div>
110
- </div>
111
- {% else %}
112
- <div class="empty-state card">
113
- <div class="icon">📅</div>
114
- <div>No matches found for the selected filters.</div>
115
- </div>
116
- {% endif %}
117
- </div>
118
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/predict.html DELETED
@@ -1,355 +0,0 @@
1
- {% extends 'base.html' %}
2
- {% block title %}Predict – {{ match.team1 }} vs {{ match.team2 }} – {{ app_brand }}{% endblock %}
3
- {% block head %}
4
- <style>
5
- .motm-lead { font-size: 0.88rem; color: var(--muted2); margin: 0.35rem 0 0.65rem; line-height: 1.45; }
6
- #predicted_motm:disabled {
7
- opacity: 0.65; cursor: not-allowed;
8
- }
9
- </style>
10
- {% endblock %}
11
- {% block content %}
12
- <div class="page" style="max-width:680px;">
13
- <div style="margin-bottom:1rem;">
14
- <a href="{{ url_for('dashboard') }}" style="color:var(--muted2); text-decoration:none; font-size:0.875rem;">← Back to Dashboard</a>
15
- </div>
16
-
17
- <!-- Match Banner -->
18
- <div class="card" style="margin-bottom:1.5rem; background:linear-gradient(135deg, var(--card) 0%, rgba(249,115,22,0.05) 100%); border-color:rgba(249,115,22,0.2);">
19
- <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem;">
20
- <span class="badge badge-{{ match.status }}">
21
- {% if match.status == 'live' %}<span class="status-dot dot-live"></span>{% endif %}
22
- {{ match.status|upper }}
23
- </span>
24
- {% if match.match_number %}<span style="font-size:0.8rem; color:var(--muted);">Match #{{ match.match_number }}</span>{% endif %}
25
- </div>
26
- <div class="match-vs">
27
- <div class="team-block">
28
- <div class="team-abbr" style="font-size:3rem; color:{{ match.team1_color }};">{{ match.team1_abbr }}</div>
29
- <div class="team-name" style="font-size:0.9rem;">{{ match.team1 }}</div>
30
- </div>
31
- <div class="vs-divider" style="font-size:1.8rem;">VS</div>
32
- <div class="team-block">
33
- <div class="team-abbr" style="font-size:3rem; color:{{ match.team2_color }};">{{ match.team2_abbr }}</div>
34
- <div class="team-name" style="font-size:0.9rem;">{{ match.team2 }}</div>
35
- </div>
36
- </div>
37
- <div class="match-meta" style="margin-top:1rem; justify-content:center;">
38
- <span>🗓️ {{ match.match_time_display }}</span>
39
- {% if match.venue %}<span>📍 {{ match.venue }}{% if match.city %}, {{ match.city }}{% endif %}</span>{% endif %}
40
- </div>
41
- <div style="text-align:center; margin-top:0.75rem; font-size:0.82rem; color:var(--muted);">
42
- 📅 Open on <strong>match day only</strong> · locks at scheduled start ({{ match.match_time_display }})
43
- </div>
44
- </div>
45
-
46
- {% if match.status == 'completed' %}
47
- <!-- Result Display -->
48
- <div class="card" style="border-color:rgba(34,197,94,0.3); background:rgba(34,197,94,0.05);">
49
- <div class="card-title" style="color:var(--green);">🏆 MATCH RESULT</div>
50
- <div style="font-size:1.5rem; font-weight:700;">{{ match.winner }}</div>
51
- {% if match.man_of_match %}<div style="color:var(--muted2); margin-top:0.5rem;">⭐ Man of the Match: <strong>{{ match.man_of_match }}</strong></div>{% endif %}
52
- {% if match.result_notes %}<div style="color:var(--muted2); font-size:0.85rem; margin-top:0.5rem;">{{ match.result_notes }}</div>{% endif %}
53
- {% if existing %}
54
- <hr>
55
- <div style="display:flex; gap:1.5rem; flex-wrap:wrap;">
56
- <div>
57
- <div style="font-size:0.8rem; color:var(--muted2);">YOUR PICK</div>
58
- <div style="font-weight:700;">{{ existing.predicted_winner }}</div>
59
- {% if existing.predicted_motm %}<div style="font-size:0.85rem; color:var(--muted2);">⭐ {{ existing.predicted_motm }}</div>{% endif %}
60
- </div>
61
- <div>
62
- <div style="font-size:0.8rem; color:var(--muted2);">BID</div>
63
- <div style="font-weight:700; font-family:var(--font-mono);">{{ '%.0f'|format(existing.bid_amount) }} pts</div>
64
- </div>
65
- {% if existing.is_settled %}
66
- <div>
67
- <div style="font-size:0.8rem; color:var(--muted2);">RESULT</div>
68
- <div style="font-weight:700;" class="{{ existing.points_earned|delta_class }}">{{ existing.points_earned|delta_sign }} pts</div>
69
- <div style="font-size:0.78rem; color:var(--muted);">Winner: {% if existing.winner_correct %}✅{% else %}❌{% endif %} {% if existing.motm_correct is not none %}· MOTM: {% if existing.motm_correct %}✅{% else %}❌{% endif %}{% endif %}</div>
70
- </div>
71
- {% endif %}
72
- </div>
73
- {% endif %}
74
- </div>
75
-
76
- {% elif match.can_predict %}
77
- <!-- Prediction Form -->
78
- <div class="card">
79
- <div class="card-title">{% if existing %}✏️ EDIT PREDICTION{% else %}🎯 MAKE YOUR PREDICTION{% endif %}</div>
80
-
81
- <!-- Current points display -->
82
- <div style="display:flex; justify-content:space-between; align-items:center; padding:0.75rem 1rem; background:var(--bg3); border-radius:8px; margin-bottom:1.5rem;">
83
- <div style="font-size:0.875rem; color:var(--muted2);">Your current balance</div>
84
- <div style="font-family:var(--font-mono); font-size:1.25rem; font-weight:700;">
85
- <span style="background:linear-gradient(135deg,var(--orange),var(--gold)); -webkit-background-clip:text; -webkit-text-fill-color:transparent;">
86
- {{ '%.0f'|format(current_user.points) }}
87
- </span>
88
- <span style="color:var(--muted); font-size:0.8rem;"> pts</span>
89
- </div>
90
- </div>
91
-
92
- <form method="post" id="predictForm">
93
- <!-- Winner selection -->
94
- <div class="form-group">
95
- <label>🏆 Who will WIN? <span style="color:var(--red);">*</span></label>
96
- <div style="display:grid; grid-template-columns:1fr 1fr; gap:0.75rem; margin-top:0.5rem;">
97
- {% for team in [match.team1, match.team2] %}
98
- {% set abbr = team_abbr.get(team, team[:3].upper()) %}
99
- {% set color = team_colors.get(abbr, '#555') %}
100
- <label class="team-pick-label" style="cursor:pointer; margin:0;">
101
- <input type="radio" name="predicted_winner" value="{{ team }}" required
102
- {% if existing and existing.predicted_winner == team %}checked{% endif %}
103
- style="display:none;" onchange="onWinnerChange(this)">
104
- <div class="team-pick-card" id="pick-{{ abbr }}" style="border:2px solid var(--border); border-radius:10px; padding:1rem; text-align:center; transition:all 0.2s; cursor:pointer; {% if existing and existing.predicted_winner == team %}border-color:{{ color }}; background:{{ color }}22;{% endif %}">
105
- <div style="font-family:var(--font-display); font-size:2rem; color:{{ color }};">{{ abbr }}</div>
106
- <div style="font-size:0.78rem; color:var(--muted2); margin-top:0.2rem;">{{ team }}</div>
107
- </div>
108
- </label>
109
- {% endfor %}
110
- </div>
111
- </div>
112
-
113
- <!-- MOTM: required; options = squad of predicted winner only -->
114
- <div class="form-group">
115
- <label for="predicted_motm">⭐ Man of the Match <span style="color:var(--red);">*</span></label>
116
- <p class="motm-lead">
117
- Choose from the <strong>squad of the team you picked to win</strong>.
118
- <strong style="color:var(--green);">+{{ points_config.correct_motm }}</strong> if it matches the official call ·
119
- <strong style="color:var(--red);">−{{ points_config.wrong_motm|abs }}</strong> if it doesn’t.
120
- </p>
121
- <select name="predicted_motm" id="predicted_motm" required disabled aria-label="Man of the Match">
122
- <option value="">— Pick winning team first —</option>
123
- </select>
124
- <p id="motmEmptyHint" class="form-hint" style="display:none; margin-top:0.5rem;">
125
- No players are listed for that team’s squad — you can’t submit until the roster is updated.
126
- </p>
127
- </div>
128
-
129
- <!-- Bid -->
130
- <div class="form-group">
131
- <label>💰 Bid Amount <span style="color:var(--red);">*</span></label>
132
- <div style="position:relative;">
133
- <input type="number" name="bid_amount" id="bidInput"
134
- min="{{ points_config.min_bid }}"
135
- max="{{ [points_config.max_bid, current_user.points|int]|min }}"
136
- step="5"
137
- value="{{ existing.bid_amount|int if existing else points_config.min_bid }}"
138
- required oninput="updateBidDisplay(this.value)">
139
- <span style="position:absolute; right:1rem; top:50%; transform:translateY(-50%); color:var(--muted); font-size:0.85rem;">pts</span>
140
- </div>
141
- <div class="form-hint">
142
- Min: <strong>{{ points_config.min_bid }}</strong> · Max: <strong>{{ [points_config.max_bid, current_user.points|int]|min }}</strong>
143
- · Win: <strong class="text-green" id="winAmount">+{{ (existing.bid_amount|int if existing else points_config.min_bid) }}</strong>
144
- · Lose: <strong class="text-red" id="loseAmount">−{{ (existing.bid_amount|int if existing else points_config.min_bid) }}</strong>
145
- </div>
146
-
147
- <!-- Quick bid buttons -->
148
- <div style="display:flex; gap:0.5rem; margin-top:0.75rem; flex-wrap:wrap;">
149
- {% set pts = current_user.points|int %}
150
- {% set max_b = [points_config.max_bid, pts]|min %}
151
- {% for pct in [10, 25, 50, 75, 100] %}
152
- {% set amt = (max_b * pct / 100)|int %}
153
- {% if amt >= points_config.min_bid %}
154
- <button type="button" class="btn btn-ghost btn-sm" onclick="setBid({{ amt }})">{{ pct }}% ({{ amt }})</button>
155
- {% endif %}
156
- {% endfor %}
157
- </div>
158
- </div>
159
-
160
- <!-- Potential outcome preview -->
161
- <div id="outcomePreview" style="padding:1rem; background:var(--bg3); border-radius:8px; margin-bottom:1.5rem; font-size:0.85rem;">
162
- <div style="color:var(--muted2); margin-bottom:0.5rem; font-size:0.78rem; text-transform:uppercase; letter-spacing:0.5px;">Potential Outcomes</div>
163
- <div style="display:grid; grid-template-columns:1fr 1fr; gap:0.5rem;">
164
- <div>✅ Win + correct MOTM: <strong class="text-green" id="bestCase">—</strong></div>
165
- <div>✅ Win + wrong MOTM: <strong class="text-green" id="winWrongMotm">—</strong></div>
166
- <div>❌ Lose + correct MOTM: <strong id="loseCorrectMotm">—</strong></div>
167
- <div>❌ Lose + wrong MOTM: <strong class="text-red" id="loseWrongMotm">—</strong></div>
168
- </div>
169
- </div>
170
-
171
- <button type="submit" class="btn btn-primary" style="width:100%; justify-content:center; font-size:1rem; padding:0.85rem;">
172
- {% if existing %}✏️ UPDATE PREDICTION{% else %}🚀 SUBMIT PREDICTION{% endif %}
173
- </button>
174
- {% if existing %}
175
- <div style="text-align:center; margin-top:0.75rem; font-size:0.8rem; color:var(--muted);">
176
- Last updated: {{ existing.updated_at[:16] }}
177
- </div>
178
- {% endif %}
179
- </form>
180
- </div>
181
-
182
- {% elif match.status == 'upcoming' and not match.is_match_today %}
183
- <div class="card" style="border-color:rgba(59,130,246,0.25); text-align:center; padding:2rem;">
184
- <div style="font-size:2.5rem; margin-bottom:1rem;">📆</div>
185
- <div style="font-size:1.15rem; font-weight:700; color:var(--blue);">Predictions open on match day</div>
186
- <div style="color:var(--muted2); margin-top:0.6rem; font-size:0.92rem;">This fixture is on <strong>{{ match.match_date|format_date }}</strong>. Come back that day to submit or edit your pick (until the scheduled start).</div>
187
- {% if existing %}
188
- <div style="margin-top:1.5rem; padding:1rem; background:var(--bg3); border-radius:8px; text-align:left;">
189
- <div style="font-size:0.8rem; color:var(--muted2); margin-bottom:0.5rem;">YOUR SAVED PICK (from match day)</div>
190
- <div style="font-weight:700; color:var(--orange);">{{ existing.predicted_winner }}</div>
191
- {% if existing.predicted_motm %}<div style="font-size:0.85rem; color:var(--muted2);">⭐ {{ existing.predicted_motm }}</div>{% endif %}
192
- <div style="font-size:0.85rem; color:var(--muted2); margin-top:0.3rem;">Bid: <strong style="color:var(--gold);">{{ '%.0f'|format(existing.bid_amount) }}</strong> pts</div>
193
- </div>
194
- {% endif %}
195
- </div>
196
-
197
- {% elif match.locked or match.status == 'locked' %}
198
- <div class="card" style="border-color:rgba(251,191,36,0.3); text-align:center; padding:2rem;">
199
- <div style="font-size:3rem; margin-bottom:1rem;">🔒</div>
200
- <div style="font-size:1.2rem; font-weight:700; color:var(--gold);">Predictions Locked</div>
201
- <div style="color:var(--muted2); margin-top:0.5rem; font-size:0.9rem;">The prediction window for this match is closed.</div>
202
- {% if existing %}
203
- <div style="margin-top:1.5rem; padding:1rem; background:var(--bg3); border-radius:8px; text-align:left;">
204
- <div style="font-size:0.8rem; color:var(--muted2); margin-bottom:0.5rem;">YOUR PREDICTION</div>
205
- <div style="font-weight:700; color:var(--orange);">{{ existing.predicted_winner }}</div>
206
- {% if existing.predicted_motm %}<div style="font-size:0.85rem; color:var(--muted2);">⭐ {{ existing.predicted_motm }}</div>{% endif %}
207
- <div style="font-size:0.85rem; color:var(--muted2); margin-top:0.3rem;">Bid: <strong style="color:var(--gold);">{{ '%.0f'|format(existing.bid_amount) }}</strong> pts</div>
208
- </div>
209
- {% else %}
210
- <div style="margin-top:1rem; color:var(--muted); font-size:0.9rem;">You didn't submit a prediction for this match.</div>
211
- {% endif %}
212
- </div>
213
-
214
- {% else %}
215
- <div class="card" style="text-align:center; padding:2rem; color:var(--muted2);">
216
- <div style="font-size:2rem; margin-bottom:0.75rem;">🏏</div>
217
- <div>Predictions aren't available for this match state (<span class="mono">{{ match.status }}</span>).</div>
218
- </div>
219
- {% endif %}
220
- </div>
221
-
222
- {% if match.can_predict %}
223
- <script>
224
- const CORRECT_MOTM = {{ points_config.correct_motm }};
225
- const WRONG_MOTM = {{ points_config.wrong_motm }};
226
- const MOTM_BY_WINNER = {{ { match.team1: squads.team1, match.team2: squads.team2 } | tojson }};
227
- const INITIAL_MOTM = {{ (existing.predicted_motm if existing and existing.predicted_motm else '') | tojson }};
228
-
229
- function fillMotmOptionsForWinner(teamName, restoreSavedMotm) {
230
- const sel = document.getElementById('predicted_motm');
231
- const hint = document.getElementById('motmEmptyHint');
232
- if (!sel) return;
233
- const players = (MOTM_BY_WINNER && MOTM_BY_WINNER[teamName]) ? MOTM_BY_WINNER[teamName] : [];
234
- let keepValue = '';
235
- if (restoreSavedMotm && INITIAL_MOTM && players.indexOf(INITIAL_MOTM) !== -1) {
236
- keepValue = INITIAL_MOTM;
237
- }
238
-
239
- sel.innerHTML = '';
240
- const ph = document.createElement('option');
241
- ph.value = '';
242
- ph.textContent = players.length ? '— Choose Man of the Match —' : '— No squad for this team —';
243
- sel.appendChild(ph);
244
-
245
- for (let i = 0; i < players.length; i++) {
246
- const o = document.createElement('option');
247
- o.value = players[i];
248
- o.textContent = players[i];
249
- sel.appendChild(o);
250
- }
251
-
252
- if (hint) hint.style.display = players.length ? 'none' : 'block';
253
-
254
- if (players.length) {
255
- sel.disabled = false;
256
- sel.required = true;
257
- if (keepValue) sel.value = keepValue;
258
- } else {
259
- sel.disabled = true;
260
- sel.required = false;
261
- sel.value = '';
262
- }
263
- updateBidDisplay(document.getElementById('bidInput')?.value || 0);
264
- }
265
-
266
- function onWinnerChange(radio) {
267
- styleTeamPickCard(radio);
268
- fillMotmOptionsForWinner(radio.value, false);
269
- }
270
-
271
- function styleTeamPickCard(radio) {
272
- document.querySelectorAll('.team-pick-card').forEach(el => {
273
- el.style.borderColor = 'var(--border)';
274
- el.style.background = '';
275
- });
276
- const label = radio.closest('.team-pick-label');
277
- if (label) {
278
- const card = label.querySelector('.team-pick-card');
279
- if (card) {
280
- const inner = card.querySelector('[style*="font-family"]');
281
- const color = (inner && inner.style.color) ? inner.style.color : 'var(--orange)';
282
- card.style.borderColor = color;
283
- if (color.indexOf('rgb(') === 0) {
284
- card.style.background = color.replace('rgb', 'rgba').replace(')', ', 0.1)');
285
- } else if (color.charAt(0) === '#') {
286
- card.style.background = color.length === 7 ? color + '22' : color;
287
- } else {
288
- card.style.background = 'rgba(249,115,22,0.1)';
289
- }
290
- }
291
- }
292
- }
293
-
294
- (function initMotmFromWinner() {
295
- const checked = document.querySelector('input[name="predicted_winner"]:checked');
296
- if (checked) {
297
- styleTeamPickCard(checked);
298
- fillMotmOptionsForWinner(checked.value, true);
299
- }
300
- document.querySelectorAll('input[name="predicted_winner"]').forEach((r) => {
301
- r.addEventListener('change', function () { onWinnerChange(this); });
302
- });
303
- const motmSel = document.getElementById('predicted_motm');
304
- motmSel?.addEventListener('change', () => {
305
- updateBidDisplay(document.getElementById('bidInput')?.value || 0);
306
- });
307
- })();
308
-
309
- function setBid(val) {
310
- const input = document.getElementById('bidInput');
311
- input.value = val;
312
- updateBidDisplay(val);
313
- }
314
-
315
- function updateBidDisplay(val) {
316
- const bid = parseInt(val) || 0;
317
- document.getElementById('winAmount').textContent = '+' + bid;
318
- document.getElementById('loseAmount').textContent = '−' + bid;
319
-
320
- const mt = document.getElementById('predicted_motm');
321
- const hasMOTM = mt && !mt.disabled && (mt.value || '').trim();
322
- const dash = '—';
323
-
324
- const bestEl = document.getElementById('bestCase');
325
- const winWrongEl = document.getElementById('winWrongMotm');
326
- const loseCorrEl = document.getElementById('loseCorrectMotm');
327
- const loseWrongEl = document.getElementById('loseWrongMotm');
328
- if (!hasMOTM) {
329
- if (bestEl) bestEl.textContent = dash;
330
- if (winWrongEl) winWrongEl.textContent = dash;
331
- if (loseCorrEl) loseCorrEl.textContent = dash;
332
- if (loseWrongEl) loseWrongEl.textContent = dash;
333
- return;
334
- }
335
-
336
- const fmt = (n) => (n >= 0 ? '+' : '') + n + ' pts';
337
- if (bestEl) bestEl.textContent = fmt(bid + CORRECT_MOTM);
338
- if (winWrongEl) {
339
- const w = bid + WRONG_MOTM;
340
- winWrongEl.textContent = fmt(w);
341
- winWrongEl.className = w >= 0 ? 'text-green' : 'text-red';
342
- }
343
- if (loseCorrEl) {
344
- const x = -bid + CORRECT_MOTM;
345
- loseCorrEl.textContent = fmt(x);
346
- loseCorrEl.style.color = x >= 0 ? 'var(--green)' : 'var(--red)';
347
- }
348
- if (loseWrongEl) loseWrongEl.textContent = fmt(-bid + WRONG_MOTM);
349
- }
350
-
351
- // Init
352
- updateBidDisplay(document.getElementById('bidInput')?.value || 0);
353
- </script>
354
- {% endif %}
355
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/team_pool.html DELETED
@@ -1,374 +0,0 @@
1
- {% extends 'base.html' %}
2
- {% block title %}The Pool — everyone’s picks — {{ app_brand }}{% endblock %}
3
- {% block head %}
4
- <style>
5
- .pool-hero {
6
- position: relative;
7
- padding: 2rem 1.5rem 1.5rem;
8
- margin: -0.5rem -0.5rem 1.75rem;
9
- border-radius: var(--radius);
10
- background:
11
- linear-gradient(135deg, rgba(249,115,22,0.12) 0%, transparent 45%),
12
- linear-gradient(225deg, rgba(59,130,246,0.08) 0%, transparent 40%),
13
- var(--card);
14
- border: 1px solid var(--border);
15
- overflow: hidden;
16
- }
17
- .pool-hero::before {
18
- content: '';
19
- position: absolute; inset: 0;
20
- background: repeating-linear-gradient(
21
- -18deg,
22
- transparent,
23
- transparent 12px,
24
- rgba(255,255,255,0.02) 12px,
25
- rgba(255,255,255,0.02) 13px
26
- );
27
- pointer-events: none;
28
- }
29
- .pool-hero-inner { position: relative; z-index: 1; }
30
- .pool-kicker {
31
- font-size: 0.75rem; font-weight: 700; letter-spacing: 0.2em;
32
- color: var(--orange); text-transform: uppercase;
33
- }
34
- .pool-title {
35
- font-family: var(--font-display);
36
- font-size: clamp(2.2rem, 5vw, 3.25rem);
37
- letter-spacing: 3px;
38
- color: var(--white);
39
- line-height: 1.05;
40
- margin-top: 0.35rem;
41
- }
42
- .pool-tagline { color: var(--muted2); font-size: 0.95rem; max-width: 36rem; margin-top: 0.5rem; }
43
-
44
- .pool-stats {
45
- display: grid;
46
- grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
47
- gap: 0.75rem;
48
- margin-top: 1.25rem;
49
- }
50
- .pool-stat {
51
- background: rgba(0,0,0,0.25);
52
- border: 1px solid var(--border);
53
- border-radius: var(--radius-sm);
54
- padding: 0.75rem 1rem;
55
- text-align: center;
56
- }
57
- .pool-stat-val {
58
- font-family: var(--font-mono);
59
- font-size: 1.35rem;
60
- font-weight: 700;
61
- color: var(--gold);
62
- }
63
- .pool-stat-lbl { font-size: 0.72rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; }
64
-
65
- .pool-toolbar {
66
- display: flex; flex-wrap: wrap; gap: 0.75rem;
67
- align-items: flex-end;
68
- margin-bottom: 1.5rem;
69
- padding: 1rem 1.25rem;
70
- background: var(--bg2);
71
- border: 1px solid var(--border);
72
- border-radius: var(--radius);
73
- }
74
- .pool-toolbar .form-group { margin: 0; flex: 1; min-width: 200px; }
75
- .pool-toolbar label { font-size: 0.72rem; }
76
-
77
- .pool-match {
78
- margin-bottom: 2rem;
79
- border-radius: var(--radius);
80
- border: 1px solid var(--border);
81
- background: linear-gradient(180deg, var(--card) 0%, rgba(20,28,46,0.97) 100%);
82
- overflow: hidden;
83
- box-shadow: 0 24px 48px rgba(0,0,0,0.35);
84
- }
85
- .pool-match-head {
86
- display: grid;
87
- grid-template-columns: 1fr auto 1fr;
88
- gap: 0.5rem;
89
- align-items: center;
90
- padding: 1.25rem 1rem;
91
- border-bottom: 1px solid var(--border);
92
- background: rgba(0,0,0,0.2);
93
- }
94
- @media (max-width: 720px) {
95
- .pool-match-head { grid-template-columns: 1fr; text-align: center; }
96
- }
97
- .pool-team-tag {
98
- font-family: var(--font-display);
99
- font-size: clamp(1.4rem, 4vw, 2rem);
100
- letter-spacing: 1px;
101
- }
102
- .pool-vs {
103
- font-family: var(--font-display);
104
- font-size: 1.5rem;
105
- color: var(--muted);
106
- letter-spacing: 4px;
107
- }
108
- .pool-meta {
109
- font-size: 0.8rem;
110
- color: var(--muted2);
111
- margin-top: 0.35rem;
112
- }
113
-
114
- .pool-bar-wrap {
115
- height: 10px;
116
- border-radius: 99px;
117
- overflow: hidden;
118
- display: flex;
119
- margin: 0 1.25rem 1rem;
120
- background: var(--bg);
121
- }
122
- .pool-bar-a { height: 100%; transition: width 0.6s ease; }
123
- .pool-bar-b { height: 100%; flex: 1; min-width: 0; transition: width 0.6s ease; }
124
-
125
- .pool-split {
126
- display: grid;
127
- grid-template-columns: 1fr 1fr;
128
- gap: 0;
129
- min-height: 120px;
130
- }
131
- @media (max-width: 640px) { .pool-split { grid-template-columns: 1fr; } }
132
-
133
- .pool-side {
134
- padding: 1rem 1.1rem 1.35rem;
135
- border-top: 3px solid transparent;
136
- }
137
- .pool-side-a { border-top-color: var(--c1, var(--orange)); background: linear-gradient(180deg, rgba(249,115,22,0.06), transparent); }
138
- .pool-side-b { border-top-color: var(--c2, var(--blue)); background: linear-gradient(180deg, rgba(59,130,246,0.06), transparent); }
139
- @media (min-width: 641px) {
140
- .pool-side-a { border-right: 1px solid var(--border); }
141
- }
142
-
143
- .pool-side-h {
144
- display: flex; justify-content: space-between; align-items: baseline;
145
- margin-bottom: 0.75rem;
146
- flex-wrap: wrap; gap: 0.35rem;
147
- }
148
- .pool-side-label { font-size: 0.7rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted); }
149
- .pool-side-sum { font-family: var(--font-mono); font-size: 0.85rem; color: var(--gold); }
150
-
151
- .pool-picks { display: flex; flex-direction: column; gap: 0.5rem; }
152
-
153
- .pool-card {
154
- display: flex; align-items: center; gap: 0.65rem;
155
- padding: 0.55rem 0.65rem;
156
- border-radius: var(--radius-sm);
157
- background: var(--bg2);
158
- border: 1px solid var(--border);
159
- transition: transform 0.15s, border-color 0.15s;
160
- }
161
- .pool-card:hover { border-color: var(--muted); transform: translateX(2px); }
162
- .pool-card.me {
163
- border-color: rgba(251,191,36,0.45);
164
- box-shadow: 0 0 0 1px rgba(251,191,36,0.15);
165
- }
166
-
167
- .pool-av {
168
- width: 2.35rem; height: 2.35rem; border-radius: 10px;
169
- display: flex; align-items: center; justify-content: center;
170
- font-family: var(--font-mono); font-size: 0.72rem; font-weight: 700;
171
- flex-shrink: 0;
172
- background: linear-gradient(135deg, var(--bg3), var(--border));
173
- color: var(--orange2);
174
- }
175
-
176
- .pool-card-body { flex: 1; min-width: 0; }
177
- .pool-name { font-weight: 700; font-size: 0.9rem; color: var(--text); }
178
- .pool-you { font-size: 0.65rem; font-weight: 700; color: var(--gold); margin-left: 0.35rem; letter-spacing: 0.05em; }
179
- .pool-sub { font-size: 0.75rem; color: var(--muted2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
180
- .pool-bid {
181
- font-family: var(--font-mono); font-weight: 700; font-size: 0.85rem;
182
- color: var(--orange); flex-shrink: 0;
183
- }
184
- .pool-out {
185
- font-size: 0.75rem;
186
- margin-top: 0.15rem;
187
- display: flex; align-items: center; gap: 0.35rem; flex-wrap: wrap;
188
- }
189
-
190
- .pool-empty-side {
191
- color: var(--muted);
192
- font-size: 0.85rem;
193
- font-style: italic;
194
- padding: 0.5rem 0;
195
- }
196
- </style>
197
- {% endblock %}
198
-
199
- {% block content %}
200
- <div class="page">
201
- <div class="pool-hero">
202
- <div class="pool-hero-inner">
203
- <div class="pool-kicker">Cards on the table</div>
204
- <h1 class="pool-title">THE POOL</h1>
205
- <p class="pool-tagline">Every active member’s winner pick, MOTM call, and stake — match by match.</p>
206
- <div class="pool-stats">
207
- <div class="pool-stat">
208
- <div class="pool-stat-val">{{ totals.n or 0 }}</div>
209
- <div class="pool-stat-lbl">Picks logged</div>
210
- </div>
211
- <div class="pool-stat">
212
- <div class="pool-stat-val">{{ totals.members or 0 }}</div>
213
- <div class="pool-stat-lbl">Players in pool</div>
214
- </div>
215
- <div class="pool-stat">
216
- <div class="pool-stat-val">{{ totals.matches_touched or 0 }}</div>
217
- <div class="pool-stat-lbl">Matches with picks</div>
218
- </div>
219
- </div>
220
- </div>
221
- </div>
222
-
223
- <form class="pool-toolbar" method="get" action="{{ url_for('team_pool') }}">
224
- <div class="form-group">
225
- <label for="match_id">Match</label>
226
- <select name="match_id" id="match_id" onchange="this.form.submit()">
227
- <option value="">All matches with predictions</option>
228
- {% for sm in selector_matches %}
229
- <option value="{{ sm.id }}" {% if match_filter == sm.id %}selected{% endif %}>
230
- #{{ sm.match_number or '?' }} · {{ sm.team1_abbr }} vs {{ sm.team2_abbr }} · {{ sm.match_date|format_date }} · {{ sm.status }}
231
- </option>
232
- {% endfor %}
233
- </select>
234
- </div>
235
- <div class="form-group" style="min-width:200px;">
236
- <label for="member_id">Find a teammate</label>
237
- <select name="member_id" id="member_id" onchange="this.form.submit()">
238
- <option value="">All teammates</option>
239
- {% for tm in pool_teammates %}
240
- <option value="{{ tm.id }}" {% if member_filter == tm.id %}selected{% endif %}>
241
- {{ tm.display_name or tm.username }}
242
- </option>
243
- {% endfor %}
244
- </select>
245
- </div>
246
- <button type="submit" class="btn btn-primary btn-sm" style="margin-bottom:2px;">Apply</button>
247
- {% if member_filter or match_filter %}
248
- <a href="{{ url_for('team_pool') }}" class="btn btn-ghost btn-sm" style="margin-bottom:2px;">Reset</a>
249
- {% endif %}
250
- </form>
251
-
252
- {% if not pool_rows %}
253
- <div class="card" style="text-align:center; padding:3rem 1.5rem;">
254
- <div style="font-size:2.5rem; margin-bottom:0.75rem;">🎴</div>
255
- <div style="font-weight:700; color:var(--white); margin-bottom:0.5rem;">No picks to show yet</div>
256
- <p style="color:var(--muted2); max-width:420px; margin:0 auto 1.25rem;">
257
- {% if member_filter %}No picks from that teammate for these filters — try another match or reset.
258
- {% else %}Once teammates submit predictions, they’ll show up here — match by match, side by side.
259
- {% endif %}
260
- </p>
261
- <a href="{{ url_for('dashboard') }}" class="btn btn-primary">Go predict a match</a>
262
- </div>
263
- {% endif %}
264
-
265
- {% for row in pool_rows %}
266
- {% set m = row.match %}
267
- <article class="pool-match">
268
- <header class="pool-match-head">
269
- <div style="text-align:right;">
270
- <div class="pool-team-tag" style="color:{{ m.team1_color }};">{{ m.team1_abbr }}</div>
271
- <div class="pool-meta">{{ m.team1 }}</div>
272
- </div>
273
- <div style="text-align:center;">
274
- <div class="pool-vs">VS</div>
275
- <div class="pool-meta">#{{ m.match_number or '?' }} · {{ m.match_date|format_date }} {{ m.match_time }}</div>
276
- <div style="margin-top:0.4rem;">
277
- <span class="badge badge-{{ m.status }}">{{ m.status }}</span>
278
- {% if m.winner and m.winner != 'ABANDONED' %}
279
- <span style="margin-left:0.35rem; color:var(--green); font-size:0.8rem; font-weight:600;">🏆 {{ m.winner }}</span>
280
- {% endif %}
281
- </div>
282
- </div>
283
- <div style="text-align:left;">
284
- <div class="pool-team-tag" style="color:{{ m.team2_color }};">{{ m.team2_abbr }}</div>
285
- <div class="pool-meta">{{ m.team2 }}</div>
286
- </div>
287
- </header>
288
-
289
- <div class="pool-bar-wrap" title="Share of picks (by headcount)">
290
- <div class="pool-bar-a" style="width:{{ row.pct1 }}%; background:linear-gradient(90deg,{{ m.team1_color }},{{ m.team1_color }}cc);"></div>
291
- <div class="pool-bar-b" style="background:linear-gradient(90deg,{{ m.team2_color }}99,{{ m.team2_color }}55);"></div>
292
- </div>
293
- <div style="display:flex; justify-content:space-between; margin:-0.35rem 1.25rem 0.75rem; font-size:0.72rem; color:var(--muted);">
294
- <span>{{ row.pct1 }}% picks · {{ '%.0f'|format(row.bid1) }} pts staked</span>
295
- <span>{{ 100 - row.pct1 }}% picks · {{ '%.0f'|format(row.bid2) }} pts staked</span>
296
- </div>
297
-
298
- <div class="pool-split">
299
- <div class="pool-side pool-side-a" style="--c1:{{ m.team1_color }};">
300
- <div class="pool-side-h">
301
- <span class="pool-side-label" style="color:{{ m.team1_color }};">Backing {{ m.team1_abbr }}</span>
302
- <span class="pool-side-sum">{{ row.side1|length }} · {{ '%.0f'|format(row.bid1) }} pts</span>
303
- </div>
304
- <div class="pool-picks">
305
- {% for p in row.side1 %}
306
- <div class="pool-card{% if p.user_uid == user.id %} me{% endif %}">
307
- <div class="pool-av">{{ (p.display_name or p.username)|initials }}</div>
308
- <div class="pool-card-body">
309
- <div class="pool-name">{{ p.display_name or p.username }}{% if p.user_uid == user.id %}<span class="pool-you">YOU</span>{% endif %}</div>
310
- <div class="pool-sub">MOTM: {{ p.predicted_motm or '—' }}</div>
311
- {% if p.is_settled %}
312
- <div class="pool-out">
313
- {% if p.winner_correct == 1 %}<span style="color:var(--green);">Winner ✓</span>{% elif p.winner_correct == 0 %}<span style="color:var(--red);">Winner ✗</span>{% endif %}
314
- {% if p.predicted_motm %}
315
- {% if p.motm_correct == 1 %}<span style="color:var(--green);">MOTM ✓</span>{% elif p.motm_correct == 0 %}<span style="color:var(--red);">MOTM ✗</span>{% endif %}
316
- {% endif %}
317
- <span class="{{ p.points_earned|delta_class }}" style="font-family:var(--font-mono); font-weight:700;">{{ p.points_earned|delta_sign }}</span>
318
- </div>
319
- {% endif %}
320
- </div>
321
- <div class="pool-bid">{{ '%.0f'|format(p.bid_amount) }}</div>
322
- </div>
323
- {% else %}
324
- <div class="pool-empty-side">Nobody’s on this side yet.</div>
325
- {% endfor %}
326
- </div>
327
- </div>
328
-
329
- <div class="pool-side pool-side-b" style="--c2:{{ m.team2_color }};">
330
- <div class="pool-side-h">
331
- <span class="pool-side-label" style="color:{{ m.team2_color }};">Backing {{ m.team2_abbr }}</span>
332
- <span class="pool-side-sum">{{ row.side2|length }} · {{ '%.0f'|format(row.bid2) }} pts</span>
333
- </div>
334
- <div class="pool-picks">
335
- {% for p in row.side2 %}
336
- <div class="pool-card{% if p.user_uid == user.id %} me{% endif %}">
337
- <div class="pool-av">{{ (p.display_name or p.username)|initials }}</div>
338
- <div class="pool-card-body">
339
- <div class="pool-name">{{ p.display_name or p.username }}{% if p.user_uid == user.id %}<span class="pool-you">YOU</span>{% endif %}</div>
340
- <div class="pool-sub">MOTM: {{ p.predicted_motm or '—' }}</div>
341
- {% if p.is_settled %}
342
- <div class="pool-out">
343
- {% if p.winner_correct == 1 %}<span style="color:var(--green);">Winner ✓</span>{% elif p.winner_correct == 0 %}<span style="color:var(--red);">Winner ✗</span>{% endif %}
344
- {% if p.predicted_motm %}
345
- {% if p.motm_correct == 1 %}<span style="color:var(--green);">MOTM ✓</span>{% elif p.motm_correct == 0 %}<span style="color:var(--red);">MOTM ✗</span>{% endif %}
346
- {% endif %}
347
- <span class="{{ p.points_earned|delta_class }}" style="font-family:var(--font-mono); font-weight:700;">{{ p.points_earned|delta_sign }}</span>
348
- </div>
349
- {% endif %}
350
- </div>
351
- <div class="pool-bid">{{ '%.0f'|format(p.bid_amount) }}</div>
352
- </div>
353
- {% else %}
354
- <div class="pool-empty-side">Nobody’s on this side yet.</div>
355
- {% endfor %}
356
- </div>
357
- </div>
358
- </div>
359
-
360
- {% if row.other %}
361
- <div style="padding:0 1.25rem 1.25rem;">
362
- <div class="card-sm" style="background:rgba(239,68,68,0.08); border-color:rgba(239,68,68,0.25);">
363
- <div style="font-size:0.75rem; font-weight:700; color:var(--red); margin-bottom:0.5rem;">Unusual picks (data check)</div>
364
- {% for p in row.other %}
365
- <div style="font-size:0.85rem; color:var(--muted2);">{{ p.display_name or p.username }} → {{ p.predicted_winner }}</div>
366
- {% endfor %}
367
- </div>
368
- </div>
369
- {% endif %}
370
- </article>
371
- {% endfor %}
372
-
373
- </div>
374
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
users.json CHANGED
@@ -1,25 +1,332 @@
1
  {
2
- "members": [
3
- {"key": "arpit", "display_name": "Arpit"},
4
- {"key": "ganesh", "display_name": "Ganesh"},
5
- {"key": "haaris", "display_name": "Haaris"},
6
- {"key": "jay", "display_name": "Jay"},
7
- {"key": "kishore", "display_name": "Kishore"},
8
- {"key": "megha", "display_name": "Megha"},
9
- {"key": "neha", "display_name": "Neha"},
10
- {"key": "praveen", "display_name": "Praveen"},
11
- {"key": "rakesh", "display_name": "Rakesh"},
12
- {"key": "sai", "display_name": "Sai"},
13
- {"key": "sunil", "display_name": "Sunil"},
14
- {"key": "vaibhav", "display_name": "Vaibhav"},
15
- {"key": "vinay", "display_name": "Vinay"},
16
- {"key": "anandh", "display_name": "Anandh"},
17
- {"key": "archana", "display_name": "Archana"},
18
- {"key": "biswabarenya", "display_name": "Biswabarenya"},
19
- {"key": "naitik", "display_name": "Naitik"},
20
- {"key": "alekhya", "display_name": "Alekhya"},
21
- {"key": "siri_gowri", "display_name": "Siri Gowri"},
22
- {"key": "priyavrat_mohan", "display_name": "Priyavrat Mohan"},
23
- {"key": "satish", "display_name": "Satish"}
24
- ]
25
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  {
2
+ "Arpit": {
3
+ "last_5_results": [
4
+ "",
5
+ "🔴",
6
+ "🔴",
7
+ "🔴",
8
+ "🔴"
9
+ ],
10
+ "points": 2322,
11
+ "wildcard": [
12
+ 0,
13
+ 0,
14
+ 0
15
+ ]
16
+ },
17
+ "Ganesh": {
18
+ "last_5_results": [
19
+ "",
20
+ "",
21
+ "",
22
+ "",
23
+ ""
24
+ ],
25
+ "points": 1600,
26
+ "wildcard": [
27
+ 0,
28
+ 0,
29
+ 0
30
+ ]
31
+ },
32
+ "Haaris": {
33
+ "last_5_results": [
34
+ "⚪",
35
+ "⚪",
36
+ "⚪",
37
+ "⚪",
38
+ "⚪"
39
+ ],
40
+ "points": 1300,
41
+ "wildcard": [
42
+ 0,
43
+ 0,
44
+ 0
45
+ ]
46
+ },
47
+ "Jay": {
48
+ "last_5_results": [
49
+ "⚪",
50
+ "🔴",
51
+ "🔴",
52
+ "⚪",
53
+ "🔴"
54
+ ],
55
+ "points": 5640,
56
+ "wildcard": [
57
+ 0,
58
+ 0,
59
+ 0
60
+ ]
61
+ },
62
+ "Kishore": {
63
+ "last_5_results": [
64
+ "⚪",
65
+ "🔴",
66
+ "🔴",
67
+ "⚪",
68
+ "🟢"
69
+ ],
70
+ "points": 15100,
71
+ "wildcard": [
72
+ 0,
73
+ 0,
74
+ 0
75
+ ]
76
+ },
77
+ "Megha": {
78
+ "last_5_results": [
79
+ "🔴",
80
+ "⚪",
81
+ "🔴",
82
+ "⚪",
83
+ "🔴"
84
+ ],
85
+ "points": 8200,
86
+ "wildcard": [
87
+ 0,
88
+ 0,
89
+ 0
90
+ ]
91
+ },
92
+ "Naveein": {
93
+ "last_5_results": [
94
+ "⚪",
95
+ "🔴",
96
+ "🔴",
97
+ "⚪",
98
+ "🟢"
99
+ ],
100
+ "points": 8100,
101
+ "wildcard": [
102
+ 0,
103
+ 0,
104
+ 0
105
+ ]
106
+ },
107
+ "Neha": {
108
+ "last_5_results": [
109
+ "⚪",
110
+ "⚪",
111
+ "🔴",
112
+ "⚪",
113
+ "⚪"
114
+ ],
115
+ "points": 1240,
116
+ "wildcard": [
117
+ 0,
118
+ 0,
119
+ 0
120
+ ]
121
+ },
122
+ "Praveen": {
123
+ "last_5_results": [
124
+ "🔴",
125
+ "⚪",
126
+ "🔴",
127
+ "⚪",
128
+ "🔴"
129
+ ],
130
+ "points": 8300,
131
+ "wildcard": [
132
+ 0,
133
+ 0,
134
+ 0
135
+ ]
136
+ },
137
+ "Rakesh": {
138
+ "last_5_results": [
139
+ "🔴",
140
+ "⚪",
141
+ "⚪",
142
+ "⚪",
143
+ "⚪"
144
+ ],
145
+ "points": 1400,
146
+ "wildcard": [
147
+ 0,
148
+ 0,
149
+ 0
150
+ ]
151
+ },
152
+ "Sai": {
153
+ "last_5_results": [
154
+ "🔴",
155
+ "🟢",
156
+ "🔴",
157
+ "🔴",
158
+ "🔴"
159
+ ],
160
+ "points": 9708,
161
+ "wildcard": [
162
+ 0,
163
+ 0,
164
+ 0
165
+ ]
166
+ },
167
+ "Sunil": {
168
+ "last_5_results": [
169
+ "⚪",
170
+ "⚪",
171
+ "🟢",
172
+ "🟢",
173
+ "🔴"
174
+ ],
175
+ "points": 17500,
176
+ "wildcard": [
177
+ 0,
178
+ 0,
179
+ 0
180
+ ]
181
+ },
182
+ "Vaibhav": {
183
+ "last_5_results": [
184
+ "🔴",
185
+ "🟢",
186
+ "🔴",
187
+ "⚪",
188
+ "⚪"
189
+ ],
190
+ "points": 12720,
191
+ "wildcard": [
192
+ 0,
193
+ 0,
194
+ 0
195
+ ]
196
+ },
197
+ "Vinay": {
198
+ "last_5_results": [
199
+ "⚪",
200
+ "🔴",
201
+ "🔴",
202
+ "⚪",
203
+ "🔴"
204
+ ],
205
+ "points": 8000,
206
+ "wildcard": [
207
+ 0,
208
+ 0,
209
+ 0
210
+ ]
211
+ },
212
+ "Anandh": {
213
+ "last_5_results": [
214
+ "🔴",
215
+ "⚪",
216
+ "⚪",
217
+ "🟢",
218
+ "🔴"
219
+ ],
220
+ "points": 5100,
221
+ "wildcard": [
222
+ 0,
223
+ 0,
224
+ 0
225
+ ]
226
+ },
227
+ "Archana": {
228
+ "last_5_results": [
229
+ "⚪",
230
+ "⚪",
231
+ "⚪",
232
+ "⚪",
233
+ "⚪"
234
+ ],
235
+ "points": 1000,
236
+ "wildcard": [
237
+ 0,
238
+ 0,
239
+ 0
240
+ ]
241
+ },
242
+ "Biswabarenya": {
243
+ "last_5_results": [
244
+ "⚪",
245
+ "⚪",
246
+ "⚪",
247
+ "⚪",
248
+ "⚪"
249
+ ],
250
+ "points": 1000,
251
+ "wildcard": [
252
+ 0,
253
+ 0,
254
+ 0
255
+ ]
256
+ },
257
+ "Naitik": {
258
+ "last_5_results": [
259
+ "🟢",
260
+ "🔴",
261
+ "⚪",
262
+ "🔴",
263
+ "🔴"
264
+ ],
265
+ "points": 11400,
266
+ "wildcard": [
267
+ 0,
268
+ 0,
269
+ 0
270
+ ]
271
+ },
272
+ "Alekhya": {
273
+ "last_5_results": [
274
+ "⚪",
275
+ "🔴",
276
+ "⚪",
277
+ "⚪",
278
+ "⚪"
279
+ ],
280
+ "points": 1600,
281
+ "wildcard": [
282
+ 0,
283
+ 0,
284
+ 0
285
+ ]
286
+ },
287
+ "Siri Gowri": {
288
+ "last_5_results": [
289
+ "⚪",
290
+ "⚪",
291
+ "⚪",
292
+ "⚪",
293
+ "⚪"
294
+ ],
295
+ "points": 1000,
296
+ "wildcard": [
297
+ 0,
298
+ 0,
299
+ 0
300
+ ]
301
+ },
302
+ "Priyavrat Mohan": {
303
+ "last_5_results": [
304
+ "⚪",
305
+ "🔴",
306
+ "🔴",
307
+ "🟢",
308
+ "🔴"
309
+ ],
310
+ "points": 6550,
311
+ "wildcard": [
312
+ 0,
313
+ 0,
314
+ 0
315
+ ]
316
+ },
317
+ "Satish": {
318
+ "last_5_results": [
319
+ "🔴",
320
+ "🟢",
321
+ "🔴",
322
+ "🔴",
323
+ "🟢"
324
+ ],
325
+ "points": 7600,
326
+ "wildcard": [
327
+ 0,
328
+ 0,
329
+ 0
330
+ ]
331
+ }
332
+ }