jashdoshi77 commited on
Commit
dfac64b
·
1 Parent(s): 0f896f8

Fix auto-training and add dynamic MVP/Championship predictions

Browse files
server.py CHANGED
@@ -375,6 +375,12 @@ def smart_retrain_model():
375
  trainer = AutoTrainer()
376
  result = trainer.run_training_cycle()
377
 
 
 
 
 
 
 
378
  if not result.get("success"):
379
  logger.warning(f"Training failed: {result.get('error', 'Unknown error')}")
380
  # Restore backup
 
375
  trainer = AutoTrainer()
376
  result = trainer.run_training_cycle()
377
 
378
+ # Handle skipped training (no new games) - not an error
379
+ if result.get("skipped"):
380
+ logger.info(f"Training skipped: {result.get('reason', 'No new games')}")
381
+ # Don't restore backup, just return - model is still valid
382
+ return
383
+
384
  if not result.get("success"):
385
  logger.warning(f"Training failed: {result.get('error', 'Unknown error')}")
386
  # Restore backup
src/auto_trainer.py CHANGED
@@ -229,7 +229,7 @@ class AutoTrainer:
229
  Uses ContinuousLearner for the actual training.
230
 
231
  Returns:
232
- Dict with 'success', 'accuracy', and optionally 'error' keys
233
  """
234
  try:
235
  from src.continuous_learner import ContinuousLearner
@@ -240,20 +240,39 @@ class AutoTrainer:
240
 
241
  accuracy = result.get("metrics", {}).get("test_accuracy", 0)
242
  success = result.get("model_retrained", False)
 
243
 
244
  if success:
245
  logger.info(f"AutoTrainer: Training cycle complete. Accuracy: {accuracy:.2%}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  else:
247
- logger.info(f"AutoTrainer: No new data to train on. Games ingested: {result.get('games_ingested', 0)}")
248
-
249
- return {
250
- "success": success,
251
- "accuracy": accuracy,
252
- "games_ingested": result.get("games_ingested", 0)
253
- }
 
 
254
  except Exception as e:
255
  logger.error(f"AutoTrainer: Training cycle failed: {e}")
256
- return {"success": False, "error": str(e), "accuracy": 0}
257
 
258
  def force_retrain(self):
259
  """Force an immediate model retrain."""
 
229
  Uses ContinuousLearner for the actual training.
230
 
231
  Returns:
232
+ Dict with 'success', 'accuracy', 'skipped', and optionally 'error' keys
233
  """
234
  try:
235
  from src.continuous_learner import ContinuousLearner
 
240
 
241
  accuracy = result.get("metrics", {}).get("test_accuracy", 0)
242
  success = result.get("model_retrained", False)
243
+ games_ingested = result.get("games_ingested", 0)
244
 
245
  if success:
246
  logger.info(f"AutoTrainer: Training cycle complete. Accuracy: {accuracy:.2%}")
247
+ return {
248
+ "success": True,
249
+ "skipped": False,
250
+ "accuracy": accuracy,
251
+ "games_ingested": games_ingested
252
+ }
253
+ elif games_ingested == 0:
254
+ # No new data - this is NOT an error, just nothing to train on
255
+ logger.info(f"AutoTrainer: No new games available for training")
256
+ return {
257
+ "success": True, # Not a failure
258
+ "skipped": True, # Just skipped
259
+ "reason": "No new games to train on",
260
+ "accuracy": accuracy,
261
+ "games_ingested": 0
262
+ }
263
  else:
264
+ # Games ingested but training failed
265
+ logger.warning(f"AutoTrainer: Training failed after ingesting {games_ingested} games")
266
+ return {
267
+ "success": False,
268
+ "skipped": False,
269
+ "error": "Training failed - will retry next cycle",
270
+ "accuracy": accuracy,
271
+ "games_ingested": games_ingested
272
+ }
273
  except Exception as e:
274
  logger.error(f"AutoTrainer: Training cycle failed: {e}")
275
+ return {"success": False, "skipped": False, "error": str(e), "accuracy": 0}
276
 
277
  def force_retrain(self):
278
  """Force an immediate model retrain."""
src/continuous_learner.py CHANGED
@@ -56,8 +56,14 @@ class ContinuousLearner:
56
  import json
57
  if self.checkpoint_file.exists():
58
  with open(self.checkpoint_file, 'r') as f:
59
- return json.load(f)
60
- return {"last_game_date": None, "last_game_ids": []}
 
 
 
 
 
 
61
 
62
  def _save_checkpoint(self, checkpoint: Dict):
63
  """Save checkpoint after processing."""
@@ -83,19 +89,13 @@ class ContinuousLearner:
83
 
84
  # Load checkpoint
85
  checkpoint = self._load_checkpoint()
86
- processed_ids = set(checkpoint.get("last_game_ids", []))
87
 
88
- # Filter to new games only
89
- new_games = [g for g in final_games if g["game_id"] not in processed_ids]
90
 
91
- if not new_games:
92
- logger.info("All completed games already processed")
93
- return 0
94
-
95
- logger.info(f"Found {len(new_games)} new completed games")
96
-
97
- # Update prediction tracker with results
98
- for game in new_games:
99
  winner = game["home_team"] if game["home_score"] > game["away_score"] else game["away_team"]
100
  self.prediction_tracker.update_result(
101
  game_id=game["game_id"],
@@ -103,20 +103,59 @@ class ContinuousLearner:
103
  home_score=game["home_score"],
104
  away_score=game["away_score"]
105
  )
106
- processed_ids.add(game["game_id"])
107
 
108
- # Append new games to raw data
109
- self._append_games_to_raw_data(new_games)
 
 
 
110
 
111
- # Update checkpoint
112
- checkpoint = {
113
- "last_game_date": datetime.now().isoformat(),
114
- "last_game_ids": list(processed_ids)[-100:] # Keep last 100 IDs
115
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  self._save_checkpoint(checkpoint)
117
 
118
- logger.info(f"Ingested {len(new_games)} new games")
119
- return len(new_games)
120
 
121
  def _append_games_to_raw_data(self, games: List[Dict]):
122
  """Append new game data to raw parquet files."""
@@ -262,6 +301,13 @@ class ContinuousLearner:
262
  metrics = self.retrain_model(incremental=True)
263
  results["model_retrained"] = "error" not in metrics
264
  results["metrics"] = metrics
 
 
 
 
 
 
 
265
 
266
  logger.info("Update cycle complete")
267
  return results
 
56
  import json
57
  if self.checkpoint_file.exists():
58
  with open(self.checkpoint_file, 'r') as f:
59
+ checkpoint = json.load(f)
60
+ # Ensure both fields exist for backward compatibility
61
+ if "trained_game_ids" not in checkpoint:
62
+ checkpoint["trained_game_ids"] = []
63
+ if "prediction_game_ids" not in checkpoint:
64
+ checkpoint["prediction_game_ids"] = checkpoint.get("last_game_ids", [])
65
+ return checkpoint
66
+ return {"last_game_date": None, "last_game_ids": [], "trained_game_ids": [], "prediction_game_ids": []}
67
 
68
  def _save_checkpoint(self, checkpoint: Dict):
69
  """Save checkpoint after processing."""
 
89
 
90
  # Load checkpoint
91
  checkpoint = self._load_checkpoint()
92
+ prediction_ids = set(checkpoint.get("prediction_game_ids", checkpoint.get("last_game_ids", [])))
93
 
94
+ # Filter to games not yet tracked for predictions
95
+ new_for_predictions = [g for g in final_games if g["game_id"] not in prediction_ids]
96
 
97
+ # Update prediction tracker with results (for ALL new games)
98
+ for game in new_for_predictions:
 
 
 
 
 
 
99
  winner = game["home_team"] if game["home_score"] > game["away_score"] else game["away_team"]
100
  self.prediction_tracker.update_result(
101
  game_id=game["game_id"],
 
103
  home_score=game["home_score"],
104
  away_score=game["away_score"]
105
  )
106
+ prediction_ids.add(game["game_id"])
107
 
108
+ # Update prediction checkpoint immediately (tracks which games have been processed for accuracy)
109
+ checkpoint["prediction_game_ids"] = list(prediction_ids)[-100:]
110
+ checkpoint["last_game_ids"] = checkpoint["prediction_game_ids"] # Backward compat
111
+ checkpoint["last_game_date"] = datetime.now().isoformat()
112
+ self._save_checkpoint(checkpoint)
113
 
114
+ if new_for_predictions:
115
+ logger.info(f"Updated predictions for {len(new_for_predictions)} games")
116
+
117
+ # For TRAINING, check against trained_game_ids (different from prediction tracking)
118
+ trained_ids = set(checkpoint.get("trained_game_ids", []))
119
+ new_for_training = [g for g in final_games if g["game_id"] not in trained_ids]
120
+
121
+ if not new_for_training:
122
+ logger.info("All completed games already trained on")
123
+ return 0
124
+
125
+ logger.info(f"Found {len(new_for_training)} new games for training")
126
+
127
+ # Append new games to raw data for training
128
+ self._append_games_to_raw_data(new_for_training)
129
+
130
+ # Store the game IDs to be marked as trained AFTER successful training
131
+ # This is stored temporarily; mark_games_as_trained() will persist them
132
+ self._pending_training_games = [g["game_id"] for g in new_for_training]
133
+
134
+ logger.info(f"Ingested {len(new_for_training)} new games for training")
135
+ return len(new_for_training)
136
+
137
+ def mark_games_as_trained(self, game_ids: List[str] = None):
138
+ """
139
+ Mark games as successfully trained on. Only call after training succeeds.
140
+
141
+ Args:
142
+ game_ids: List of game IDs to mark as trained. If None, uses pending games.
143
+ """
144
+ if game_ids is None:
145
+ game_ids = getattr(self, '_pending_training_games', [])
146
+
147
+ if not game_ids:
148
+ return
149
+
150
+ checkpoint = self._load_checkpoint()
151
+ trained_ids = set(checkpoint.get("trained_game_ids", []))
152
+ trained_ids.update(game_ids)
153
+
154
+ checkpoint["trained_game_ids"] = list(trained_ids)[-100:] # Keep last 100
155
  self._save_checkpoint(checkpoint)
156
 
157
+ logger.info(f"Marked {len(game_ids)} games as trained")
158
+ self._pending_training_games = []
159
 
160
  def _append_games_to_raw_data(self, games: List[Dict]):
161
  """Append new game data to raw parquet files."""
 
301
  metrics = self.retrain_model(incremental=True)
302
  results["model_retrained"] = "error" not in metrics
303
  results["metrics"] = metrics
304
+
305
+ # Only mark games as trained AFTER successful training
306
+ if results["model_retrained"]:
307
+ self.mark_games_as_trained()
308
+ logger.info("Training successful - games marked as trained")
309
+ else:
310
+ logger.warning("Training failed - games will be retried next cycle")
311
 
312
  logger.info("Update cycle complete")
313
  return results
src/prediction_pipeline.py CHANGED
@@ -712,14 +712,69 @@ class PredictionPipeline:
712
  })
713
 
714
  def get_championship_odds(self, team_df: pd.DataFrame = None) -> pd.DataFrame:
715
- """Get current championship odds."""
716
  if team_df is None:
717
- # Use mock data if no real data
718
- team_df = pd.DataFrame({
719
- "TEAM_ABBREVIATION": ["OKC", "CLE", "BOS", "DEN", "MEM", "HOU", "NYK", "GSW"],
720
- "W_PCT": [0.70, 0.68, 0.65, 0.62, 0.60, 0.58, 0.55, 0.52],
721
- "NET_RATING": [9.5, 8.2, 7.5, 6.0, 5.5, 4.0, 3.5, 2.0]
722
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
723
 
724
  return self.champ_model.get_top_contenders(team_df)
725
 
 
712
  })
713
 
714
  def get_championship_odds(self, team_df: pd.DataFrame = None) -> pd.DataFrame:
715
+ """Get current championship odds using LIVE standings data from NBA API."""
716
  if team_df is None:
717
+ # Fetch real current season standings from NBA API
718
+ max_retries = 1 # Fail fast and use fallback
719
+
720
+ for attempt in range(max_retries):
721
+ try:
722
+ from nba_api.stats.endpoints import leaguestandings
723
+ import time
724
+
725
+ time.sleep(0.5)
726
+
727
+ standings = leaguestandings.LeagueStandings(
728
+ season='2025-26',
729
+ timeout=30
730
+ )
731
+ df = standings.get_data_frames()[0]
732
+
733
+ if df.empty:
734
+ logger.warning("NBA API returned empty standings data")
735
+ continue
736
+
737
+ logger.info(f"Got standings for {len(df)} teams from NBA API")
738
+
739
+ # Build team DataFrame with required columns
740
+ team_df = pd.DataFrame({
741
+ 'TEAM_ABBREVIATION': df['TeamCity'].apply(lambda x: NBA_TEAMS.get(
742
+ next((tid for tid, abbr in NBA_TEAMS.items()
743
+ if x.lower() in abbr.lower() or abbr.lower() in x.lower()), 0),
744
+ 'UNK'
745
+ )),
746
+ 'W_PCT': df['WinPCT'].fillna(0.5),
747
+ 'NET_RATING': df['NetRating'].fillna(0) if 'NetRating' in df.columns else 0,
748
+ })
749
+
750
+ # If team abbreviations didn't map well, try using TeamAbbreviation directly if available
751
+ if 'TeamAbbreviation' in df.columns:
752
+ team_df['TEAM_ABBREVIATION'] = df['TeamAbbreviation']
753
+
754
+ # Add ELO ratings from our feature generator
755
+ elo_ratings = {}
756
+ for team_id, abbrev in NBA_TEAMS.items():
757
+ elo_ratings[abbrev] = self.feature_gen.elo.get_rating(team_id)
758
+
759
+ team_df['ELO'] = team_df['TEAM_ABBREVIATION'].map(elo_ratings).fillna(1500)
760
+
761
+ logger.info(f"Successfully built championship data for {len(team_df)} teams")
762
+ break
763
+
764
+ except Exception as e:
765
+ logger.warning(f"Championship standings fetch attempt {attempt + 1} failed: {e}")
766
+ continue
767
+ else:
768
+ # All retries failed - use fallback mock data
769
+ logger.warning("Using fallback championship odds data")
770
+ team_df = pd.DataFrame({
771
+ "TEAM_ABBREVIATION": ["OKC", "CLE", "BOS", "DEN", "MEM", "HOU", "NYK", "GSW",
772
+ "MIN", "LAL", "MIL", "PHX", "DAL", "MIA", "SAC", "IND"],
773
+ "W_PCT": [0.74, 0.70, 0.66, 0.62, 0.60, 0.58, 0.56, 0.54,
774
+ 0.52, 0.50, 0.48, 0.46, 0.44, 0.42, 0.40, 0.38],
775
+ "NET_RATING": [10.5, 8.2, 7.5, 6.0, 5.5, 4.5, 4.0, 3.5,
776
+ 3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0, -0.5]
777
+ })
778
 
779
  return self.champ_model.get_top_contenders(team_df)
780
 
static/{index-UMhzWs-z.css → assets/index-DJ5mbnNj.css} RENAMED
@@ -1 +1 @@
1
- *,*:before,*:after{box-sizing:border-box;margin:0;padding:0}:root{--bg-primary: #000000;--bg-secondary: #0d0d0d;--bg-tertiary: #141414;--bg-card: #0a0a0a;--bg-card-hover: #111111;--bg-elevated: #1a1a1a;--text-primary: #ffffff;--text-secondary: #b3b3b3;--text-muted: #666666;--text-dim: #444444;--accent-primary: #00c8ff;--accent-secondary: #ff6b35;--accent-success: #00d26a;--accent-warning: #ffb800;--accent-danger: #ff3b3b;--gradient-primary: linear-gradient(135deg, #00c8ff 0%, #0099cc 100%);--gradient-secondary: linear-gradient(135deg, #ff6b35 0%, #cc5529 100%);--gradient-subtle: linear-gradient(180deg, rgba(255, 255, 255, .03) 0%, rgba(255, 255, 255, 0) 100%);--border-subtle: 1px solid rgba(255, 255, 255, .06);--border-medium: 1px solid rgba(255, 255, 255, .1);--border-focus: 1px solid var(--accent-primary);--shadow-sm: 0 2px 8px rgba(0, 0, 0, .3);--shadow-md: 0 4px 16px rgba(0, 0, 0, .4);--shadow-lg: 0 8px 32px rgba(0, 0, 0, .5);--shadow-glow: 0 0 20px rgba(0, 200, 255, .15);--radius-xs: 4px;--radius-sm: 6px;--radius-md: 8px;--radius-lg: 12px;--radius-xl: 16px;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-8: 32px;--space-10: 40px;--space-12: 48px;--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono: "SF Mono", "Monaco", "Inconsolata", monospace;--transition-fast: .1s ease;--transition-base: .2s ease;--transition-slow: .3s ease}html{font-size:14px}body{font-family:var(--font-sans);background:var(--bg-primary);color:var(--text-primary);line-height:1.5;min-height:100vh;overflow-x:hidden;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#root{min-height:100vh}h1,h2,h3,h4,h5,h6{font-weight:600;line-height:1.25;letter-spacing:-.025em;color:var(--text-primary)}h1{font-size:2rem;font-weight:700}h2{font-size:1.5rem}h3{font-size:1.125rem}h4{font-size:1rem}.text-gradient{background:var(--gradient-primary);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.text-muted{color:var(--text-muted)}.text-secondary{color:var(--text-secondary)}.app-layout{display:flex;min-height:100vh;transition:all var(--transition-slow)}.app-layout.sidebar-collapsed .main-content{margin-left:72px}.sidebar{width:240px;background:var(--bg-secondary);border-right:var(--border-subtle);position:fixed;top:0;left:0;height:100vh;z-index:100;display:flex;flex-direction:column;padding:var(--space-5);transition:all .3s cubic-bezier(.4,0,.2,1);overflow:hidden}.sidebar.collapsed{width:72px;padding:var(--space-4) var(--space-3)}.sidebar.collapsed.hovered{width:240px;padding:var(--space-5);background:#0d0d0df2;backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);box-shadow:0 8px 32px #00000080,0 0 0 1px #ffffff14;border-right:1px solid rgba(255,255,255,.1)}.sidebar-header{padding-bottom:var(--space-6);border-bottom:var(--border-subtle);margin-bottom:var(--space-4);display:flex;align-items:center;justify-content:space-between;gap:var(--space-2)}.sidebar-logo{display:flex;align-items:center;gap:var(--space-3);min-width:0;flex:1}.sidebar-logo-icon{width:32px;height:32px;min-width:32px;background:var(--gradient-primary);border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;transition:transform .3s ease,box-shadow .3s ease}.sidebar:hover .sidebar-logo-icon{transform:scale(1.05);box-shadow:0 0 20px #00c8ff4d}.sidebar-logo-text{font-size:1.125rem;font-weight:700;letter-spacing:-.02em;white-space:nowrap;opacity:1;transition:opacity .2s ease .1s,transform .2s ease}.sidebar.collapsed .sidebar-logo-text{opacity:0;transform:translate(-10px);transition:opacity .1s ease,transform .1s ease}.sidebar.collapsed.hovered .sidebar-logo-text{opacity:1;transform:translate(0);transition:opacity .2s ease .1s,transform .2s ease .1s}.sidebar-toggle{width:28px;height:28px;min-width:28px;border-radius:var(--radius-sm);background:var(--bg-tertiary);border:var(--border-subtle);color:var(--text-secondary);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;opacity:0;transform:scale(.8)}.sidebar:hover .sidebar-toggle{opacity:1;transform:scale(1)}.sidebar-toggle:hover{background:var(--bg-elevated);color:var(--accent-primary);border-color:var(--accent-primary);box-shadow:0 0 12px #00c8ff33}.sidebar-toggle svg{width:16px;height:16px;transition:transform .3s ease}.sidebar-toggle:hover svg{transform:scale(1.1)}.sidebar-nav{flex:1;display:flex;flex-direction:column;gap:var(--space-1);overflow-y:auto;overflow-x:hidden}.nav-section{margin-bottom:var(--space-4)}.nav-section-title{font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-dim);padding:var(--space-2) var(--space-3);margin-bottom:var(--space-1);white-space:nowrap;opacity:1;transition:opacity .2s ease}.sidebar.collapsed .nav-section-title{opacity:0;height:0;padding:0;margin:0;overflow:hidden}.sidebar.collapsed.hovered .nav-section-title{opacity:1;height:auto;padding:var(--space-2) var(--space-3);margin-bottom:var(--space-1)}.nav-item{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3);border-radius:var(--radius-md);color:var(--text-secondary);cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);font-weight:500;font-size:.875rem;position:relative;overflow:hidden}.nav-item:before{content:"";position:absolute;inset:0;background:radial-gradient(circle at left center,rgba(0,200,255,.1) 0%,transparent 70%);opacity:0;transition:opacity .3s ease}.nav-item:hover:before{opacity:1}.nav-item:hover{background:var(--bg-tertiary);color:var(--text-primary);transform:translate(4px)}.nav-item.active{background:#00c8ff1f;color:var(--accent-primary)}.nav-item.active:after{content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);width:3px;height:20px;background:var(--accent-primary);border-radius:0 2px 2px 0;box-shadow:0 0 10px #00c8ff80}.nav-icon{width:20px;height:20px;min-width:20px;opacity:.7;transition:all .2s ease}.nav-item:hover .nav-icon,.nav-item.active .nav-icon{opacity:1;transform:scale(1.1)}.nav-item.active .nav-icon{filter:drop-shadow(0 0 4px rgba(0,200,255,.5))}.nav-text{white-space:nowrap;opacity:1;transition:opacity .2s ease .1s}.sidebar.collapsed .nav-text{opacity:0;width:0;overflow:hidden;transition:opacity .1s ease}.sidebar.collapsed.hovered .nav-text{opacity:1;width:auto;transition:opacity .2s ease .15s}.sidebar.collapsed .nav-item{justify-content:center;padding:var(--space-3)}.sidebar.collapsed.hovered .nav-item{justify-content:flex-start}.sidebar-footer{margin-top:auto;padding-top:var(--space-4);border-top:var(--border-subtle)}.sidebar-footer .btn{justify-content:flex-start}.sidebar.collapsed .sidebar-footer .btn{justify-content:center;padding:var(--space-3)}.sidebar.collapsed.hovered .sidebar-footer .btn{justify-content:flex-start;padding:var(--space-3) var(--space-5)}.sidebar-version{margin-top:var(--space-3);font-size:.6875rem;color:var(--text-dim);text-align:center;opacity:1;transition:opacity .2s ease}.sidebar.collapsed .sidebar-version{opacity:0}.sidebar.collapsed.hovered .sidebar-version{opacity:1}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.spinning{animation:spin 1s linear infinite}.main-content{flex:1;margin-left:240px;padding:var(--space-8);min-height:100vh;background:var(--bg-primary);transition:margin-left .3s cubic-bezier(.4,0,.2,1)}.sidebar-collapsed .main-content{margin-left:72px}.card{background:var(--bg-card);border:var(--border-subtle);border-radius:var(--radius-lg);padding:var(--space-5);transition:all var(--transition-base)}.card:hover{background:var(--bg-card-hover);border-color:#ffffff1a}.card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-4)}.card-title{font-size:.875rem;font-weight:600;color:var(--text-secondary)}.team-logo{width:48px;height:48px;object-fit:contain}.team-logo-sm{width:32px;height:32px}.team-logo-lg{width:64px;height:64px}.team-logo-xl{width:80px;height:80px}.game-card{background:var(--bg-card);border:var(--border-subtle);border-radius:var(--radius-lg);padding:var(--space-5);margin-bottom:var(--space-4);transition:all var(--transition-base)}.game-card:hover{border-color:#ffffff1f;box-shadow:var(--shadow-sm)}.game-card.live{border-color:var(--accent-danger);box-shadow:0 0 0 1px var(--accent-danger)}.game-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-4);padding-bottom:var(--space-3);border-bottom:var(--border-subtle)}.game-status{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em}.game-status.live{color:var(--accent-danger);display:flex;align-items:center;gap:var(--space-2)}.live-dot{width:6px;height:6px;background:var(--accent-danger);border-radius:50%;animation:pulse-dot 2s infinite}@keyframes pulse-dot{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.8)}}.game-time{color:var(--text-muted);font-size:.75rem}.game-matchup{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:var(--space-6)}.team-block{display:flex;flex-direction:column;align-items:center;text-align:center}.team-block.away{align-items:flex-start;text-align:left}.team-block.home{align-items:flex-end;text-align:right}.team-info{display:flex;align-items:center;gap:var(--space-3)}.team-block.home .team-info{flex-direction:row-reverse}.team-details{display:flex;flex-direction:column}.team-name{font-size:1rem;font-weight:600;color:var(--text-primary)}.team-record{font-size:.75rem;color:var(--text-muted)}.team-score{font-size:2rem;font-weight:700;color:var(--text-primary);margin-top:var(--space-2)}.team-probability{font-size:1.5rem;font-weight:700;color:var(--accent-primary);margin-top:var(--space-2)}.game-center{display:flex;flex-direction:column;align-items:center;gap:var(--space-2)}.vs-divider{font-size:.875rem;color:var(--text-dim);font-weight:500}.prediction-indicator{background:var(--bg-elevated);border:var(--border-subtle);border-radius:var(--radius-md);padding:var(--space-3) var(--space-4);text-align:center}.prediction-label{font-size:.625rem;font-weight:600;text-transform:uppercase;letter-spacing:.1em;color:var(--text-muted);margin-bottom:var(--space-1)}.prediction-pick{font-size:1rem;font-weight:700;color:var(--accent-primary)}.confidence-meter-container{margin-top:var(--space-2)}.confidence-meter{position:relative;display:flex;flex-direction:column;align-items:center}.confidence-meter svg{display:block}.confidence-value{position:absolute;top:28px;font-size:1.25rem;font-weight:700;text-shadow:0 0 10px currentColor}.confidence-label{font-size:.625rem;font-weight:600;text-transform:uppercase;letter-spacing:.1em;margin-top:-4px}.stats-comparison{display:flex;flex-direction:column;gap:var(--space-3)}.stat-comparison-row{display:grid;grid-template-columns:1fr auto 1fr;gap:var(--space-3);align-items:center}.stat-bar-container{display:flex;align-items:center;gap:var(--space-2);height:24px}.stat-bar-container.right{justify-content:flex-end}.stat-bar{height:8px;border-radius:4px;transition:width .3s ease}.stat-bar.left{margin-left:auto}.stat-value-label{font-size:.875rem;font-weight:600;min-width:40px}.stat-value-label.left{text-align:right}.stat-value-label.right{text-align:left}.stat-label-center{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);min-width:70px;text-align:center}.recent-form{display:flex;justify-content:center}.form-badges{display:flex;gap:var(--space-1)}.form-badge{width:22px;height:22px;display:flex;align-items:center;justify-content:center;font-size:.625rem;font-weight:700;border-radius:4px}.form-badge.win{background:#00d26a26;color:var(--accent-success)}.form-badge.loss{background:#ff3b3b26;color:var(--accent-danger)}.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--space-4);margin-bottom:var(--space-6)}.stat-card{background:var(--bg-card);border:var(--border-subtle);border-radius:var(--radius-lg);padding:var(--space-5);display:flex;flex-direction:column}.stat-label{font-size:.75rem;font-weight:500;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:var(--space-2)}.stat-value{font-size:2rem;font-weight:700;color:var(--text-primary);line-height:1}.stat-value.accent{color:var(--accent-primary)}.stat-change{font-size:.75rem;margin-top:var(--space-2);display:flex;align-items:center;gap:var(--space-1)}.stat-change.positive{color:var(--accent-success)}.stat-change.negative{color:var(--accent-danger)}.form-group{margin-bottom:var(--space-4)}.form-label{display:block;font-size:.75rem;font-weight:500;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:var(--space-2)}.form-select{width:100%;padding:var(--space-3) var(--space-4);background:var(--bg-elevated);border:var(--border-subtle);border-radius:var(--radius-md);color:var(--text-primary);font-size:.875rem;font-weight:500;cursor:pointer;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right var(--space-3) center;transition:all var(--transition-fast)}.form-select:hover{border-color:#ffffff26}.form-select:focus{outline:none;border-color:var(--accent-primary);box-shadow:0 0 0 2px #00c8ff1a}.form-input{width:100%;padding:var(--space-3) var(--space-4);background:var(--bg-elevated);border:var(--border-subtle);border-radius:var(--radius-md);color:var(--text-primary);font-size:.875rem;transition:all var(--transition-fast)}.form-input:focus{outline:none;border-color:var(--accent-primary);box-shadow:0 0 0 2px #00c8ff1a}.form-input::placeholder{color:var(--text-dim)}.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);padding:var(--space-3) var(--space-5);border-radius:var(--radius-md);font-size:.875rem;font-weight:600;cursor:pointer;border:none;transition:all var(--transition-fast)}.btn-primary{background:var(--gradient-primary);color:#000}.btn-primary:hover{opacity:.9;box-shadow:var(--shadow-glow)}.btn-secondary{background:var(--bg-elevated);border:var(--border-subtle);color:var(--text-primary)}.btn-secondary:hover{background:var(--bg-tertiary);border-color:#ffffff26}.btn-ghost{background:transparent;color:var(--text-secondary)}.btn-ghost:hover{background:var(--bg-tertiary);color:var(--text-primary)}.btn-icon{width:36px;height:36px;padding:0;border-radius:var(--radius-md)}.btn-lg{padding:var(--space-4) var(--space-6);font-size:1rem}.btn-block{width:100%}.badge{display:inline-flex;align-items:center;padding:var(--space-1) var(--space-2);border-radius:var(--radius-xs);font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em}.badge-success{background:#00d26a26;color:var(--accent-success)}.badge-warning{background:#ffb80026;color:var(--accent-warning)}.badge-danger{background:#ff3b3b26;color:var(--accent-danger)}.badge-neutral{background:#ffffff14;color:var(--text-secondary)}.confidence-high{background:#00d26a26;color:var(--accent-success)}.confidence-medium{background:#ffb80026;color:var(--accent-warning)}.confidence-low{background:#ff3b3b26;color:var(--accent-danger)}.table-container{background:var(--bg-card);border:var(--border-subtle);border-radius:var(--radius-lg);overflow:hidden}.data-table{width:100%;border-collapse:collapse}.data-table th{padding:var(--space-3) var(--space-4);text-align:left;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);background:var(--bg-secondary);border-bottom:var(--border-subtle)}.data-table td{padding:var(--space-4);border-bottom:var(--border-subtle);font-size:.875rem}.data-table tr:last-child td{border-bottom:none}.data-table tr:hover{background:var(--bg-card-hover)}.table-team{display:flex;align-items:center;gap:var(--space-3)}.loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--space-12)}.spinner{width:32px;height:32px;border:2px solid var(--bg-tertiary);border-top-color:var(--accent-primary);border-radius:50%;animation:spin .8s linear infinite}.loading-text{margin-top:var(--space-4);color:var(--text-muted);font-size:.875rem}@keyframes spin{to{transform:rotate(360deg)}}.skeleton{background:linear-gradient(90deg,var(--bg-tertiary) 25%,var(--bg-elevated) 50%,var(--bg-tertiary) 75%);background-size:200% 100%;animation:shimmer 1.5s infinite;border-radius:var(--radius-sm)}@keyframes shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.empty-state{text-align:center;padding:var(--space-12);color:var(--text-muted)}.empty-state-icon{width:48px;height:48px;margin:0 auto var(--space-4);opacity:.3}.empty-state-title{font-size:1rem;font-weight:600;color:var(--text-secondary);margin-bottom:var(--space-2)}.empty-state-text{font-size:.875rem;color:var(--text-muted)}.page-header{margin-bottom:var(--space-8)}.page-title{font-size:1.75rem;font-weight:700;margin-bottom:var(--space-2)}.page-description{color:var(--text-secondary);font-size:.875rem}.probability-bar-container{margin-top:var(--space-4)}.probability-bar{height:4px;background:var(--bg-tertiary);border-radius:2px;overflow:hidden;display:flex}.probability-fill-away{height:100%;background:var(--accent-secondary);transition:width var(--transition-slow)}.probability-fill-home{height:100%;background:var(--accent-primary);transition:width var(--transition-slow)}.probability-labels{display:flex;justify-content:space-between;margin-top:var(--space-2);font-size:.75rem;color:var(--text-muted)}.section{margin-bottom:var(--space-8)}.section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-4)}.section-title{font-size:.875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-secondary)}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.animate-fadeIn{animation:fadeIn .3s ease}.animate-slideUp{animation:slideUp .3s ease}.stagger>*:nth-child(1){animation-delay:.05s}.stagger>*:nth-child(2){animation-delay:.1s}.stagger>*:nth-child(3){animation-delay:.15s}.stagger>*:nth-child(4){animation-delay:.2s}.stagger>*:nth-child(5){animation-delay:.25s}@media(max-width:1024px){.sidebar{width:64px;padding:var(--space-3)}.sidebar-logo-text,.nav-section-title,.nav-item span:not(.nav-icon){display:none}.main-content{margin-left:64px;padding:var(--space-5)}}@media(max-width:768px){.sidebar{position:fixed;inset:auto 0 0;width:100%;height:64px;flex-direction:row;padding:var(--space-2);border-right:none;border-top:var(--border-subtle)}.sidebar-header{display:none}.sidebar-nav{flex-direction:row;justify-content:space-around;gap:0;width:100%}.nav-section{display:flex;margin:0}.nav-section-title{display:none}.main-content{margin-left:0;margin-bottom:64px;padding:var(--space-4)}.game-matchup{grid-template-columns:1fr;gap:var(--space-4)}.team-block.away,.team-block.home{align-items:center;text-align:center}.team-block.home .team-info{flex-direction:row}}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--bg-tertiary);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--text-dim)}.flex{display:flex}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-2{gap:var(--space-2)}.gap-3{gap:var(--space-3)}.gap-4{gap:var(--space-4)}.mt-4{margin-top:var(--space-4)}.mb-4{margin-bottom:var(--space-4)}.text-right{text-align:right}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
 
1
+ *,*:before,*:after{box-sizing:border-box;margin:0;padding:0}:root{--bg-primary: #000000;--bg-secondary: #0d0d0d;--bg-tertiary: #141414;--bg-card: #0a0a0a;--bg-card-hover: #111111;--bg-elevated: #1a1a1a;--text-primary: #ffffff;--text-secondary: #b3b3b3;--text-muted: #666666;--text-dim: #444444;--accent-primary: #00c8ff;--accent-secondary: #ff6b35;--accent-success: #00d26a;--accent-warning: #ffb800;--accent-danger: #ff3b3b;--gradient-primary: linear-gradient(135deg, #00c8ff 0%, #0099cc 100%);--gradient-secondary: linear-gradient(135deg, #ff6b35 0%, #cc5529 100%);--gradient-subtle: linear-gradient(180deg, rgba(255, 255, 255, .03) 0%, rgba(255, 255, 255, 0) 100%);--border-subtle: 1px solid rgba(255, 255, 255, .06);--border-medium: 1px solid rgba(255, 255, 255, .1);--border-focus: 1px solid var(--accent-primary);--shadow-sm: 0 2px 8px rgba(0, 0, 0, .3);--shadow-md: 0 4px 16px rgba(0, 0, 0, .4);--shadow-lg: 0 8px 32px rgba(0, 0, 0, .5);--shadow-glow: 0 0 20px rgba(0, 200, 255, .15);--radius-xs: 4px;--radius-sm: 6px;--radius-md: 8px;--radius-lg: 12px;--radius-xl: 16px;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-8: 32px;--space-10: 40px;--space-12: 48px;--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono: "SF Mono", "Monaco", "Inconsolata", monospace;--transition-fast: .1s ease;--transition-base: .2s ease;--transition-slow: .3s ease}html{font-size:14px}body{font-family:var(--font-sans);background:var(--bg-primary);color:var(--text-primary);line-height:1.5;min-height:100vh;overflow-x:hidden;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#root{min-height:100vh}h1,h2,h3,h4,h5,h6{font-weight:600;line-height:1.25;letter-spacing:-.025em;color:var(--text-primary)}h1{font-size:2rem;font-weight:700}h2{font-size:1.5rem}h3{font-size:1.125rem}h4{font-size:1rem}.text-gradient{background:var(--gradient-primary);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.text-muted{color:var(--text-muted)}.text-secondary{color:var(--text-secondary)}.app-layout{display:flex;min-height:100vh;transition:all var(--transition-slow)}.app-layout.sidebar-collapsed .main-content{margin-left:72px}.sidebar{width:240px;background:var(--bg-secondary);border-right:var(--border-subtle);position:fixed;top:0;left:0;height:100vh;z-index:100;display:flex;flex-direction:column;padding:var(--space-5);transition:all .3s cubic-bezier(.4,0,.2,1);overflow:hidden}.sidebar.collapsed{width:72px;padding:var(--space-4) var(--space-3)}.sidebar.collapsed.hovered{width:240px;padding:var(--space-5);background:#0d0d0df2;backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);box-shadow:0 8px 32px #00000080,0 0 0 1px #ffffff14;border-right:1px solid rgba(255,255,255,.1)}.sidebar-header{padding-bottom:var(--space-6);border-bottom:var(--border-subtle);margin-bottom:var(--space-4);display:flex;align-items:center;justify-content:space-between;gap:var(--space-2)}.sidebar-logo{display:flex;align-items:center;gap:var(--space-3);min-width:0;flex:1}.sidebar-logo-icon{width:32px;height:32px;min-width:32px;background:var(--gradient-primary);border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;transition:transform .3s ease,box-shadow .3s ease}.sidebar:hover .sidebar-logo-icon{transform:scale(1.05);box-shadow:0 0 20px #00c8ff4d}.sidebar-logo-text{font-size:1.125rem;font-weight:700;letter-spacing:-.02em;white-space:nowrap;opacity:1;transition:opacity .2s ease .1s,transform .2s ease}.sidebar.collapsed .sidebar-logo-text{opacity:0;transform:translate(-10px);transition:opacity .1s ease,transform .1s ease}.sidebar.collapsed.hovered .sidebar-logo-text{opacity:1;transform:translate(0);transition:opacity .2s ease .1s,transform .2s ease .1s}.sidebar-toggle{width:28px;height:28px;min-width:28px;border-radius:var(--radius-sm);background:var(--bg-tertiary);border:var(--border-subtle);color:var(--text-secondary);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;opacity:0;transform:scale(.8)}.sidebar:hover .sidebar-toggle{opacity:1;transform:scale(1)}.sidebar-toggle:hover{background:var(--bg-elevated);color:var(--accent-primary);border-color:var(--accent-primary);box-shadow:0 0 12px #00c8ff33}.sidebar-toggle svg{width:16px;height:16px;transition:transform .3s ease}.sidebar-toggle:hover svg{transform:scale(1.1)}.sidebar-nav{flex:1;display:flex;flex-direction:column;gap:var(--space-1);overflow-y:auto;overflow-x:hidden}.nav-section{margin-bottom:var(--space-4)}.nav-section-title{font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-dim);padding:var(--space-2) var(--space-3);margin-bottom:var(--space-1);white-space:nowrap;opacity:1;transition:opacity .2s ease}.sidebar.collapsed .nav-section-title{opacity:0;height:0;padding:0;margin:0;overflow:hidden}.sidebar.collapsed.hovered .nav-section-title{opacity:1;height:auto;padding:var(--space-2) var(--space-3);margin-bottom:var(--space-1)}.nav-item{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3);border-radius:var(--radius-md);color:var(--text-secondary);cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);font-weight:500;font-size:.875rem;position:relative;overflow:hidden}.nav-item:before{content:"";position:absolute;inset:0;background:radial-gradient(circle at left center,rgba(0,200,255,.1) 0%,transparent 70%);opacity:0;transition:opacity .3s ease}.nav-item:hover:before{opacity:1}.nav-item:hover{background:var(--bg-tertiary);color:var(--text-primary);transform:translate(4px)}.nav-item.active{background:#00c8ff1f;color:var(--accent-primary)}.nav-item.active:after{content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);width:3px;height:20px;background:var(--accent-primary);border-radius:0 2px 2px 0;box-shadow:0 0 10px #00c8ff80}.nav-icon{width:20px;height:20px;min-width:20px;opacity:.7;transition:all .2s ease}.nav-item:hover .nav-icon,.nav-item.active .nav-icon{opacity:1;transform:scale(1.1)}.nav-item.active .nav-icon{filter:drop-shadow(0 0 4px rgba(0,200,255,.5))}.nav-text{white-space:nowrap;opacity:1;transition:opacity .2s ease .1s}.sidebar.collapsed .nav-text{opacity:0;width:0;overflow:hidden;transition:opacity .1s ease}.sidebar.collapsed.hovered .nav-text{opacity:1;width:auto;transition:opacity .2s ease .15s}.sidebar.collapsed .nav-item{justify-content:center;padding:var(--space-3)}.sidebar.collapsed.hovered .nav-item{justify-content:flex-start}.sidebar-footer{margin-top:auto;padding-top:var(--space-4);border-top:var(--border-subtle)}.sidebar-footer .btn{justify-content:flex-start}.sidebar.collapsed .sidebar-footer .btn{justify-content:center;padding:var(--space-3)}.sidebar.collapsed.hovered .sidebar-footer .btn{justify-content:flex-start;padding:var(--space-3) var(--space-5)}.sidebar-version{margin-top:var(--space-3);font-size:.6875rem;color:var(--text-dim);text-align:center;opacity:1;transition:opacity .2s ease}.sidebar.collapsed .sidebar-version{opacity:0}.sidebar.collapsed.hovered .sidebar-version{opacity:1}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.spinning{animation:spin 1s linear infinite}.main-content{flex:1;margin-left:240px;padding:var(--space-8);min-height:100vh;background:var(--bg-primary);transition:margin-left .3s cubic-bezier(.4,0,.2,1)}.sidebar-collapsed .main-content{margin-left:72px}.card{background:var(--bg-card);border:var(--border-subtle);border-radius:var(--radius-lg);padding:var(--space-5);transition:all var(--transition-base)}.card:hover{background:var(--bg-card-hover);border-color:#ffffff1a}.card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-4)}.card-title{font-size:.875rem;font-weight:600;color:var(--text-secondary)}.team-logo{width:48px;height:48px;object-fit:contain}.team-logo-sm{width:32px;height:32px}.team-logo-lg{width:64px;height:64px}.team-logo-xl{width:80px;height:80px}.game-card{background:var(--bg-card);border:var(--border-subtle);border-radius:var(--radius-lg);padding:var(--space-5);margin-bottom:var(--space-4);transition:all var(--transition-base)}.game-card:hover{border-color:#ffffff1f;box-shadow:var(--shadow-sm)}.game-card.live{border-color:var(--accent-danger);box-shadow:0 0 0 1px var(--accent-danger)}.game-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-4);padding-bottom:var(--space-3);border-bottom:var(--border-subtle)}.game-status{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em}.game-status.live{color:var(--accent-danger);display:flex;align-items:center;gap:var(--space-2)}.live-dot{width:6px;height:6px;background:var(--accent-danger);border-radius:50%;animation:pulse-dot 2s infinite}@keyframes pulse-dot{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.8)}}.game-time{color:var(--text-muted);font-size:.75rem}.game-matchup{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:var(--space-6)}.team-block{display:flex;flex-direction:column;align-items:center;text-align:center}.team-block.away{align-items:flex-start;text-align:left}.team-block.home{align-items:flex-end;text-align:right}.team-info{display:flex;align-items:center;gap:var(--space-3)}.team-block.home .team-info{flex-direction:row-reverse}.team-details{display:flex;flex-direction:column}.team-name{font-size:1rem;font-weight:600;color:var(--text-primary)}.team-record{font-size:.75rem;color:var(--text-muted)}.team-score{font-size:2rem;font-weight:700;color:var(--text-primary);margin-top:var(--space-2)}.team-probability{font-size:1.5rem;font-weight:700;color:var(--accent-primary);margin-top:var(--space-2)}.game-center{display:flex;flex-direction:column;align-items:center;gap:var(--space-2)}.vs-divider{font-size:.875rem;color:var(--text-dim);font-weight:500}.prediction-indicator{background:var(--bg-elevated);border:var(--border-subtle);border-radius:var(--radius-md);padding:var(--space-3) var(--space-4);text-align:center}.prediction-label{font-size:.625rem;font-weight:600;text-transform:uppercase;letter-spacing:.1em;color:var(--text-muted);margin-bottom:var(--space-1)}.prediction-pick{font-size:1rem;font-weight:700;color:var(--accent-primary)}.confidence-meter-container{margin-top:var(--space-2)}.confidence-meter{position:relative;display:flex;flex-direction:column;align-items:center}.confidence-meter svg{display:block}.confidence-value{position:absolute;top:28px;font-size:1.25rem;font-weight:700;text-shadow:0 0 10px currentColor}.confidence-label{font-size:.625rem;font-weight:600;text-transform:uppercase;letter-spacing:.1em;margin-top:-4px}.stats-comparison{display:flex;flex-direction:column;gap:var(--space-3)}.stat-comparison-row{display:grid;grid-template-columns:1fr auto 1fr;gap:var(--space-3);align-items:center}.stat-bar-container{display:flex;align-items:center;gap:var(--space-2);height:24px}.stat-bar-container.right{justify-content:flex-end}.stat-bar{height:8px;border-radius:4px;transition:width .3s ease}.stat-bar.left{margin-left:auto}.stat-value-label{font-size:.875rem;font-weight:600;min-width:40px}.stat-value-label.left{text-align:right}.stat-value-label.right{text-align:left}.stat-label-center{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);min-width:70px;text-align:center}.recent-form{display:flex;justify-content:center}.form-badges{display:flex;gap:var(--space-1)}.form-badge{width:22px;height:22px;display:flex;align-items:center;justify-content:center;font-size:.625rem;font-weight:700;border-radius:4px}.form-badge.win{background:#00d26a26;color:var(--accent-success)}.form-badge.loss{background:#ff3b3b26;color:var(--accent-danger)}.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--space-4);margin-bottom:var(--space-6)}.stat-card{background:var(--bg-card);border:var(--border-subtle);border-radius:var(--radius-lg);padding:var(--space-5);display:flex;flex-direction:column}.stat-label{font-size:.75rem;font-weight:500;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:var(--space-2)}.stat-value{font-size:2rem;font-weight:700;color:var(--text-primary);line-height:1}.stat-value.accent{color:var(--accent-primary)}.stat-change{font-size:.75rem;margin-top:var(--space-2);display:flex;align-items:center;gap:var(--space-1)}.stat-change.positive{color:var(--accent-success)}.stat-change.negative{color:var(--accent-danger)}.form-group{margin-bottom:var(--space-4)}.form-label{display:block;font-size:.75rem;font-weight:500;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:var(--space-2)}.form-select{width:100%;padding:var(--space-3) var(--space-4);background:var(--bg-elevated);border:var(--border-subtle);border-radius:var(--radius-md);color:var(--text-primary);font-size:.875rem;font-weight:500;cursor:pointer;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right var(--space-3) center;transition:all var(--transition-fast)}.form-select:hover{border-color:#ffffff26}.form-select:focus{outline:none;border-color:var(--accent-primary);box-shadow:0 0 0 2px #00c8ff1a}.form-input{width:100%;padding:var(--space-3) var(--space-4);background:var(--bg-elevated);border:var(--border-subtle);border-radius:var(--radius-md);color:var(--text-primary);font-size:.875rem;transition:all var(--transition-fast)}.form-input:focus{outline:none;border-color:var(--accent-primary);box-shadow:0 0 0 2px #00c8ff1a}.form-input::placeholder{color:var(--text-dim)}.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);padding:var(--space-3) var(--space-5);border-radius:var(--radius-md);font-size:.875rem;font-weight:600;cursor:pointer;border:none;transition:all var(--transition-fast)}.btn-primary{background:var(--gradient-primary);color:#000}.btn-primary:hover{opacity:.9;box-shadow:var(--shadow-glow)}.btn-secondary{background:var(--bg-elevated);border:var(--border-subtle);color:var(--text-primary)}.btn-secondary:hover{background:var(--bg-tertiary);border-color:#ffffff26}.btn-ghost{background:transparent;color:var(--text-secondary)}.btn-ghost:hover{background:var(--bg-tertiary);color:var(--text-primary)}.btn-icon{width:36px;height:36px;padding:0;border-radius:var(--radius-md)}.btn-lg{padding:var(--space-4) var(--space-6);font-size:1rem}.btn-block{width:100%}.badge{display:inline-flex;align-items:center;padding:var(--space-1) var(--space-2);border-radius:var(--radius-xs);font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em}.badge-success{background:#00d26a26;color:var(--accent-success)}.badge-warning{background:#ffb80026;color:var(--accent-warning)}.badge-danger{background:#ff3b3b26;color:var(--accent-danger)}.badge-neutral{background:#ffffff14;color:var(--text-secondary)}.confidence-high{background:#00d26a26;color:var(--accent-success)}.confidence-medium{background:#ffb80026;color:var(--accent-warning)}.confidence-low{background:#ff3b3b26;color:var(--accent-danger)}.table-container{background:var(--bg-card);border:var(--border-subtle);border-radius:var(--radius-lg);overflow:hidden}.data-table{width:100%;border-collapse:collapse}.data-table th{padding:var(--space-3) var(--space-4);text-align:left;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);background:var(--bg-secondary);border-bottom:var(--border-subtle)}.data-table td{padding:var(--space-4);border-bottom:var(--border-subtle);font-size:.875rem}.data-table tr:last-child td{border-bottom:none}.data-table tr:hover{background:var(--bg-card-hover)}.table-team{display:flex;align-items:center;gap:var(--space-3)}.loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--space-12)}.spinner{width:32px;height:32px;border:2px solid var(--bg-tertiary);border-top-color:var(--accent-primary);border-radius:50%;animation:spin .8s linear infinite}.loading-text{margin-top:var(--space-4);color:var(--text-muted);font-size:.875rem}@keyframes spin{to{transform:rotate(360deg)}}.skeleton{background:linear-gradient(90deg,var(--bg-tertiary) 25%,var(--bg-elevated) 50%,var(--bg-tertiary) 75%);background-size:200% 100%;animation:shimmer 1.5s infinite;border-radius:var(--radius-sm)}@keyframes shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.empty-state{text-align:center;padding:var(--space-12);color:var(--text-muted)}.empty-state-icon{width:48px;height:48px;margin:0 auto var(--space-4);opacity:.3}.empty-state-title{font-size:1rem;font-weight:600;color:var(--text-secondary);margin-bottom:var(--space-2)}.empty-state-text{font-size:.875rem;color:var(--text-muted)}.page-header{margin-bottom:var(--space-8)}.page-title{font-size:1.75rem;font-weight:700;margin-bottom:var(--space-2)}.page-description{color:var(--text-secondary);font-size:.875rem}.probability-bar-container{margin-top:var(--space-4)}.probability-bar{height:4px;background:var(--bg-tertiary);border-radius:2px;overflow:hidden;display:flex}.probability-fill-away{height:100%;background:var(--accent-secondary);transition:width var(--transition-slow)}.probability-fill-home{height:100%;background:var(--accent-primary);transition:width var(--transition-slow)}.probability-labels{display:flex;justify-content:space-between;margin-top:var(--space-2);font-size:.75rem;color:var(--text-muted)}.section{margin-bottom:var(--space-8)}.section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-4)}.section-title{font-size:.875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-secondary)}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.animate-fadeIn{animation:fadeIn .3s ease}.animate-slideUp{animation:slideUp .3s ease}.stagger>*:nth-child(1){animation-delay:.05s}.stagger>*:nth-child(2){animation-delay:.1s}.stagger>*:nth-child(3){animation-delay:.15s}.stagger>*:nth-child(4){animation-delay:.2s}.stagger>*:nth-child(5){animation-delay:.25s}@media(max-width:1024px){.sidebar{width:64px;padding:var(--space-3)}.sidebar-logo-text,.nav-section-title,.nav-item span:not(.nav-icon){display:none}.main-content{margin-left:64px;padding:var(--space-5)}}@media(max-width:768px){.sidebar{position:fixed;inset:auto 0 0;width:100%;height:64px;flex-direction:row;padding:var(--space-2);border-right:none;border-top:var(--border-subtle)}.sidebar-header{display:none}.sidebar-nav{flex-direction:row;justify-content:space-around;gap:0;width:100%}.nav-section{display:flex;margin:0}.nav-section-title{display:none}.main-content{margin-left:0;margin-bottom:64px;padding:var(--space-4)}.game-matchup{grid-template-columns:1fr;gap:var(--space-4)}.team-block.away,.team-block.home{align-items:center;text-align:center}.team-block.home .team-info{flex-direction:row}}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--bg-tertiary);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--text-dim)}.flex{display:flex}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-2{gap:var(--space-2)}.gap-3{gap:var(--space-3)}.gap-4{gap:var(--space-4)}.mt-4{margin-top:var(--space-4)}.mb-4{margin-bottom:var(--space-4)}.text-right{text-align:right}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.analytics-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:60vh;gap:var(--space-6)}.loading-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}.loading-cell{width:20px;height:20px;background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));border-radius:4px;animation:loadingPulse 1.5s ease-in-out infinite}@keyframes loadingPulse{0%,to{opacity:.2;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}.analytics-header{position:relative;padding:var(--space-8) var(--space-6);margin-bottom:var(--space-6);border-radius:var(--radius-xl);overflow:hidden;background:linear-gradient(135deg,#00c8ff1a,#a78bfa1a);border:1px solid rgba(255,255,255,.1)}.header-background{position:absolute;inset:0;overflow:hidden}.neural-grid{position:absolute;inset:0}.neural-node{position:absolute;width:4px;height:4px;background:var(--accent-primary);border-radius:50%;opacity:.3;animation:neuralPulse 3s ease-in-out infinite}@keyframes neuralPulse{0%,to{opacity:.1;transform:scale(1)}50%{opacity:.6;transform:scale(1.5)}}.header-content{position:relative;z-index:1}.analytics-title{display:flex;align-items:center;gap:var(--space-3);font-size:2rem;font-weight:700;margin-bottom:var(--space-2)}.title-icon{font-size:1.5rem}.title-badge{font-size:.65rem;font-weight:600;padding:4px 10px;background:linear-gradient(135deg,#00c8ff,#a78bfa);border-radius:20px;text-transform:uppercase;letter-spacing:.1em}.analytics-subtitle{color:var(--text-secondary);font-size:1rem}.analytics-tabs{display:flex;gap:var(--space-2);margin-bottom:var(--space-6);padding:var(--space-2);background:#0000004d;border-radius:var(--radius-lg);border:1px solid rgba(255,255,255,.05)}.analytics-tab{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-3) var(--space-5);background:transparent;border:none;border-radius:var(--radius-md);color:var(--text-secondary);font-size:.875rem;font-weight:500;cursor:pointer;transition:all .3s ease}.analytics-tab:hover{background:#ffffff0d;color:var(--text-primary)}.analytics-tab.active{background:linear-gradient(135deg,#00c8ff33,#a78bfa33);color:var(--text-primary);box-shadow:0 0 20px #00c8ff33}.hero-stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--space-4);margin-bottom:var(--space-6)}.analytics-stat-card{position:relative;padding:var(--space-5);background:#0006;border:1px solid rgba(255,255,255,.1);border-radius:var(--radius-lg);overflow:hidden;opacity:0;transform:translateY(20px);transition:all .5s ease}.analytics-stat-card.visible{opacity:1;transform:translateY(0)}.analytics-stat-card:hover{border-color:#fff3;transform:translateY(-2px)}.stat-glow{position:absolute;top:0;left:0;width:100%;height:3px;opacity:.8}.stat-icon{font-size:1.5rem;margin-bottom:var(--space-2)}.analytics-stat-card .stat-value{font-size:2rem;font-weight:700;margin-bottom:var(--space-1);text-shadow:0 0 30px currentColor}.analytics-stat-card .stat-label{font-size:.75rem;color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em}.stat-pulse{position:absolute;bottom:10px;right:10px;width:8px;height:8px;border-radius:50%;animation:statPulse 2s ease-in-out infinite}@keyframes statPulse{0%,to{opacity:.3;transform:scale(1)}50%{opacity:.8;transform:scale(1.2)}}.charts-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(400px,1fr));gap:var(--space-6);margin-bottom:var(--space-6)}.chart-card{background:#0006;border:1px solid rgba(255,255,255,.08);border-radius:var(--radius-xl);padding:var(--space-5);transition:all .3s ease}.chart-card:hover{border-color:#ffffff26;box-shadow:0 8px 32px #0000004d}.chart-card.full-width{grid-column:1 / -1}.glass-effect{backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);background:#0a0a0a99}.chart-header{display:flex;align-items:center;gap:var(--space-3);margin-bottom:var(--space-4);padding-bottom:var(--space-3);border-bottom:1px solid rgba(255,255,255,.05)}.chart-icon{font-size:1.25rem}.chart-header h3{flex:1;font-size:1rem;font-weight:600;color:var(--text-primary)}.chart-subtitle{font-size:.75rem;color:var(--text-muted)}.chart-badge{font-size:.625rem;font-weight:600;padding:3px 8px;border-radius:10px;background:#ffffff1a;text-transform:uppercase;letter-spacing:.05em}.chart-badge.live{background:#00d26a33;color:#00d26a;animation:livePulse 2s ease-in-out infinite}@keyframes livePulse{0%,to{opacity:.7}50%{opacity:1}}.chart-legend{display:flex;gap:var(--space-4);font-size:.75rem}.legend-item{display:flex;align-items:center;gap:var(--space-2);color:var(--text-muted)}.legend-item span:first-child{width:12px;height:3px;border-radius:2px}.chart-body{position:relative}.gauges-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--space-4);padding:var(--space-4)}.gauge-container{display:flex;flex-direction:column;align-items:center}.gauge-svg{width:120px;height:70px}.gauge-label{font-size:.75rem;color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em;margin-top:var(--space-1)}.streak-display{padding:var(--space-4)}.current-streak{display:flex;justify-content:center;margin-bottom:var(--space-5)}.streak-badge{display:flex;flex-direction:column;align-items:center;padding:var(--space-5) var(--space-8);border-radius:var(--radius-xl);background:linear-gradient(135deg,#00d26a33,#00d26a0d);border:2px solid rgba(0,210,106,.3)}.streak-badge.loss{background:linear-gradient(135deg,#ff3b3b33,#ff3b3b0d);border-color:#ff3b3b4d}.streak-count{font-size:3rem;font-weight:700;line-height:1}.streak-badge.win .streak-count{color:#00d26a}.streak-badge.loss .streak-count{color:#ff3b3b}.streak-type{font-size:1rem;font-weight:600;margin:var(--space-1) 0}.streak-badge.win .streak-type{color:#00d26a}.streak-badge.loss .streak-type{color:#ff3b3b}.streak-label{font-size:.65rem;color:var(--text-muted);text-transform:uppercase;letter-spacing:.1em}.streak-records{display:flex;justify-content:center;gap:var(--space-8);margin-bottom:var(--space-5)}.streak-record{text-align:center}.record-value{display:block;font-size:1.5rem;font-weight:700}.streak-record.win .record-value{color:#00d26a}.streak-record.loss .record-value{color:#ff3b3b}.record-label{font-size:.65rem;color:var(--text-muted);text-transform:uppercase}.streak-history{display:flex;align-items:flex-end;justify-content:center;gap:4px;height:70px;padding-top:var(--space-2)}.streak-bar{width:12px;border-radius:4px 4px 0 0;transition:all .3s ease}.streak-bar.win{background:linear-gradient(to top,#00d26a,#4ade80)}.streak-bar.loss{background:linear-gradient(to top,#ff3b3b,#ff6b6b)}.streak-bar:hover{transform:scaleY(1.1);filter:brightness(1.2)}.custom-tooltip{background:#000000e6;border:1px solid rgba(255,255,255,.2);border-radius:var(--radius-md);padding:var(--space-3) var(--space-4);box-shadow:0 8px 32px #00000080}.tooltip-label{font-weight:600;margin-bottom:var(--space-2);color:var(--text-primary)}.custom-tooltip p{font-size:.825rem;margin:2px 0}.quick-stats-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--space-4);padding:var(--space-4)}.hexagon-stat{display:flex;justify-content:center}.hexagon-svg{width:100px;height:115px}.team-stats-table{overflow-x:auto}.team-stats-table table{width:100%;border-collapse:collapse}.team-stats-table th,.team-stats-table td{padding:var(--space-3) var(--space-4);text-align:left;border-bottom:1px solid rgba(255,255,255,.05)}.team-stats-table th{font-size:.7rem;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em}.team-stats-table td{font-size:.875rem}.team-stats-table tbody tr:hover{background:#ffffff08}.team-name-cell{display:flex;align-items:center;gap:var(--space-3)}.team-rank{width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:#ffffff1a;border-radius:50%;font-size:.75rem;font-weight:600}.team-abbrev{font-weight:600}.accuracy-badge{display:inline-block;padding:2px 8px;border-radius:4px;font-weight:600;font-size:.8rem}.accuracy-badge.high{background:#00d26a33;color:#00d26a}.accuracy-badge.medium{background:#ffb80033;color:#ffb800}.accuracy-badge.low{background:#ff3b3b33;color:#ff3b3b}.heatmap-container{padding:var(--space-4)}.heatmap-header,.heatmap-row{display:grid;grid-template-columns:60px repeat(7,1fr);gap:4px;margin-bottom:4px}.heatmap-day-label,.heatmap-team-label{display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--text-muted);text-transform:uppercase}.heatmap-label{width:60px}.heatmap-cell{aspect-ratio:1;display:flex;align-items:center;justify-content:center;border-radius:4px;font-size:.7rem;font-weight:600;color:#000c;transition:all .2s ease}.heatmap-cell:hover{transform:scale(1.1);z-index:1}.heatmap-legend{display:flex;align-items:center;justify-content:center;gap:var(--space-3);margin-top:var(--space-4);padding-top:var(--space-4);border-top:1px solid rgba(255,255,255,.05)}.heatmap-legend .legend-label{font-size:.7rem;color:var(--text-muted)}.legend-gradient{width:120px;height:12px;background:linear-gradient(to right,#ff3b3b,#ffb800,#00d26a);border-radius:6px}.predictions-table{overflow-x:auto}.predictions-table table{width:100%;border-collapse:collapse}.predictions-table th,.predictions-table td{padding:var(--space-3) var(--space-4);text-align:left}.predictions-table th{font-size:.7rem;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em;border-bottom:1px solid rgba(255,255,255,.1)}.predictions-table td{font-size:.875rem;border-bottom:1px solid rgba(255,255,255,.03)}.predictions-table tbody tr{transition:all .2s ease}.predictions-table tbody tr:hover{background:#ffffff08}.predictions-table tbody tr.correct{border-left:3px solid #00d26a}.predictions-table tbody tr.incorrect{border-left:3px solid #ff3b3b}.date-cell{color:var(--text-muted);font-size:.8rem}.matchup-cell{font-weight:500}.prediction-badge{display:inline-block;padding:2px 10px;background:#00c8ff26;border:1px solid rgba(0,200,255,.3);border-radius:var(--radius-sm);font-weight:600;font-size:.8rem;color:var(--accent-primary)}.confidence-cell{display:flex;align-items:center;gap:var(--space-2)}.confidence-bar-mini{width:60px;height:6px;background:#ffffff1a;border-radius:3px;overflow:hidden}.confidence-fill{height:100%;border-radius:3px;transition:width .5s ease}.result-badge{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;font-weight:700}.result-badge.win{background:#00d26a33;color:#00d26a}.result-badge.loss{background:#ff3b3b33;color:#ff3b3b}@media(max-width:768px){.analytics-header{padding:var(--space-5)}.analytics-title{font-size:1.5rem}.analytics-tabs{overflow-x:auto;-webkit-overflow-scrolling:touch}.analytics-tab{padding:var(--space-2) var(--space-3);font-size:.8rem;white-space:nowrap}.hero-stats-grid{grid-template-columns:repeat(2,1fr)}.charts-row{grid-template-columns:1fr}.gauges-grid{grid-template-columns:repeat(2,1fr)}.heatmap-container{overflow-x:auto}}
static/assets/index-pWNzHy8O.js ADDED
The diff for this file is too large to render. See raw diff
 
static/index-Cqd4wFSK.js DELETED
The diff for this file is too large to render. See raw diff
 
static/index.html CHANGED
@@ -8,8 +8,8 @@
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
11
- <script type="module" crossorigin src="/assets/index-Cqd4wFSK.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-UMhzWs-z.css">
13
  </head>
14
  <body>
15
  <div id="root"></div>
 
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
11
+ <script type="module" crossorigin src="/assets/index-pWNzHy8O.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-DJ5mbnNj.css">
13
  </head>
14
  <body>
15
  <div id="root"></div>