spjasper commited on
Commit
393f433
·
1 Parent(s): 623afec

mejora diseño

Browse files
src/pages/comparacion.py CHANGED
@@ -3,14 +3,13 @@ import streamlit as st
3
  from src.config import PARES_ATAQUE_LOCAL, PARES_ATAQUE_VISITANTE
4
  from src.statistics import construir_comparacion, mostrar_comparacion
5
  from src.charts import grafico_radar
6
- from src.utils import sec
7
 
8
  st.session_state["_current_page"] = "comparacion"
9
 
10
  ctx = st.session_state.get("_ctx", {})
11
  if not ctx:
12
- st.warning("Selecciona un partido en la página de Inicio.")
13
- st.stop()
14
 
15
  eq_l = ctx["eq_l"]
16
  eq_v = ctx["eq_v"]
 
3
  from src.config import PARES_ATAQUE_LOCAL, PARES_ATAQUE_VISITANTE
4
  from src.statistics import construir_comparacion, mostrar_comparacion
5
  from src.charts import grafico_radar
6
+ from src.utils import sec, no_match_page
7
 
8
  st.session_state["_current_page"] = "comparacion"
9
 
10
  ctx = st.session_state.get("_ctx", {})
11
  if not ctx:
12
+ no_match_page()
 
13
 
14
  eq_l = ctx["eq_l"]
15
  eq_v = ctx["eq_v"]
src/pages/cuotas_kelly.py CHANGED
@@ -7,14 +7,13 @@ from src.config import (
7
  KELLY_MIN_RECOMENDADO, BANK_MIN, BANK_DEFAULT, BANK_STEP,
8
  )
9
  from src.charts import _THEME as _CHART_THEME
10
- from src.utils import sec
11
 
12
  st.session_state["_current_page"] = "cuotas_kelly"
13
 
14
  ctx = st.session_state.get("_ctx", {})
15
  if not ctx:
16
- st.warning("Selecciona un partido en la página de Inicio.")
17
- st.stop()
18
 
19
  eq_l = ctx["eq_l"]
20
  eq_v = ctx["eq_v"]
@@ -161,20 +160,20 @@ def _color_kelly(v) -> str:
161
  try:
162
  n = float(v)
163
  if n > KELLY_MIN_RECOMENDADO:
164
- return "background-color:#0D9E6E;color:white;font-weight:bold"
165
  elif n > 0:
166
- return "background-color:#4CAF7D;color:white"
167
  else:
168
- return "background-color:#D93025;color:white"
169
  except Exception:
170
  return ""
171
 
172
  def _color_ve(v) -> str:
173
  try:
174
  return (
175
- "background-color:#0D6E4E;color:white;font-weight:bold"
176
  if float(v) > 0
177
- else "background-color:#B71C1C;color:white"
178
  )
179
  except Exception:
180
  return ""
 
7
  KELLY_MIN_RECOMENDADO, BANK_MIN, BANK_DEFAULT, BANK_STEP,
8
  )
9
  from src.charts import _THEME as _CHART_THEME
10
+ from src.utils import sec, no_match_page
11
 
12
  st.session_state["_current_page"] = "cuotas_kelly"
13
 
14
  ctx = st.session_state.get("_ctx", {})
15
  if not ctx:
16
+ no_match_page()
 
17
 
18
  eq_l = ctx["eq_l"]
19
  eq_v = ctx["eq_v"]
 
160
  try:
161
  n = float(v)
162
  if n > KELLY_MIN_RECOMENDADO:
163
+ return "background-color:#D1FAE5;color:#065F46;font-weight:700"
164
  elif n > 0:
165
+ return "background-color:#ECFDF5;color:#047857;font-weight:600"
166
  else:
167
+ return "background-color:#FEE2E2;color:#991B1B;font-weight:600"
168
  except Exception:
169
  return ""
170
 
171
  def _color_ve(v) -> str:
172
  try:
173
  return (
174
+ "background-color:#D1FAE5;color:#065F46;font-weight:700"
175
  if float(v) > 0
176
+ else "background-color:#FEE2E2;color:#991B1B;font-weight:600"
177
  )
178
  except Exception:
179
  return ""
src/pages/estadisticas.py CHANGED
@@ -4,14 +4,13 @@ import pandas as pd
4
  from src.config import KELLY_MIN_RECOMENDADO
5
  from src.data_loader import ultimos_n
6
  from src.statistics import mostrar_tabla_partidos
7
- from src.utils import sec, med
8
 
9
  st.session_state["_current_page"] = "estadisticas"
10
 
11
  ctx = st.session_state.get("_ctx", {})
12
  if not ctx:
13
- st.warning("Selecciona un partido en la página de Inicio.")
14
- st.stop()
15
 
16
  eq_l = ctx["eq_l"]
17
  eq_v = ctx["eq_v"]
@@ -217,10 +216,10 @@ def _center_compare(df_l: pd.DataFrame, col_l: str,
217
  f'<div style="margin-bottom:8px">'
218
  f'<div style="font-size:10px;color:var(--text-muted);margin-bottom:3px">🏠 {eq_l}</div>'
219
  f'<div style="height:8px;border-radius:4px;background:var(--bg-hover);overflow:hidden">'
220
- f'<div style="width:{pct_l:.0f}%;height:100%;background:#2570D4;border-radius:4px"></div></div>'
221
  f'<div style="font-size:10px;color:var(--text-muted);margin:5px 0 3px">✈️ {eq_v}</div>'
222
  f'<div style="height:8px;border-radius:4px;background:var(--bg-hover);overflow:hidden">'
223
- f'<div style="width:{pct_v:.0f}%;height:100%;background:#D93025;border-radius:4px"></div></div>'
224
  f'</div>',
225
  unsafe_allow_html=True,
226
  )
 
4
  from src.config import KELLY_MIN_RECOMENDADO
5
  from src.data_loader import ultimos_n
6
  from src.statistics import mostrar_tabla_partidos
7
+ from src.utils import sec, med, no_match_page
8
 
9
  st.session_state["_current_page"] = "estadisticas"
10
 
11
  ctx = st.session_state.get("_ctx", {})
12
  if not ctx:
13
+ no_match_page()
 
14
 
15
  eq_l = ctx["eq_l"]
16
  eq_v = ctx["eq_v"]
 
216
  f'<div style="margin-bottom:8px">'
217
  f'<div style="font-size:10px;color:var(--text-muted);margin-bottom:3px">🏠 {eq_l}</div>'
218
  f'<div style="height:8px;border-radius:4px;background:var(--bg-hover);overflow:hidden">'
219
+ f'<div style="width:{pct_l:.0f}%;height:100%;background:var(--blue);border-radius:4px"></div></div>'
220
  f'<div style="font-size:10px;color:var(--text-muted);margin:5px 0 3px">✈️ {eq_v}</div>'
221
  f'<div style="height:8px;border-radius:4px;background:var(--bg-hover);overflow:hidden">'
222
+ f'<div style="width:{pct_v:.0f}%;height:100%;background:var(--red);border-radius:4px"></div></div>'
223
  f'</div>',
224
  unsafe_allow_html=True,
225
  )
src/pages/inicio.py CHANGED
@@ -1,4 +1,4 @@
1
- from datetime import date
2
 
3
  import streamlit as st
4
 
@@ -12,7 +12,28 @@ if df_proximos is None:
12
  st.stop()
13
 
14
  today = date.today()
15
- df_up = df_proximos[df_proximos["Date"].dt.date >= today].copy()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  sort_cols = ["Date", "Time_local"] if "Time_local" in df_up.columns else ["Date"]
18
  df_up = df_up.sort_values(sort_cols, na_position="last").reset_index(drop=True)
 
1
+ from datetime import date, datetime as _dt
2
 
3
  import streamlit as st
4
 
 
12
  st.stop()
13
 
14
  today = date.today()
15
+ _now = _dt.now()
16
+
17
+
18
+ def _es_proximo(row) -> bool:
19
+ """True si el partido aún no ha comenzado (fecha futura, o hoy y hora no pasada)."""
20
+ d = row["Date"].date()
21
+ if d > today:
22
+ return True
23
+ if d < today:
24
+ return False
25
+ # Mismo día: comparar hora local de inicio
26
+ t = str(row.get("Time_local", "") or "")
27
+ if not t:
28
+ return True # sin hora → mantener por precaución
29
+ try:
30
+ h, m = map(int, t.split(":"))
31
+ return (h, m) > (_now.hour, _now.minute)
32
+ except Exception:
33
+ return True
34
+
35
+
36
+ df_up = df_proximos[df_proximos.apply(_es_proximo, axis=1)].copy()
37
 
38
  sort_cols = ["Date", "Time_local"] if "Time_local" in df_up.columns else ["Date"]
39
  df_up = df_up.sort_values(sort_cols, na_position="last").reset_index(drop=True)
src/pages/prediccion_rf.py CHANGED
@@ -14,7 +14,7 @@ from src.config import (
14
  )
15
  from src.charts import _THEME as _CHART_THEME
16
  from src.models import mostrar_rf_equipo
17
- from src.utils import sec, get_pred
18
 
19
  _IMG_COLORS = {
20
  "BG": "#0F1117",
@@ -193,8 +193,7 @@ st.session_state["_current_page"] = "prediccion_rf"
193
 
194
  ctx = st.session_state.get("_ctx", {})
195
  if not ctx:
196
- st.warning("Selecciona un partido en la página de Inicio.")
197
- st.stop()
198
 
199
  eq_l = ctx["eq_l"]
200
  eq_v = ctx["eq_v"]
 
14
  )
15
  from src.charts import _THEME as _CHART_THEME
16
  from src.models import mostrar_rf_equipo
17
+ from src.utils import sec, get_pred, no_match_page
18
 
19
  _IMG_COLORS = {
20
  "BG": "#0F1117",
 
193
 
194
  ctx = st.session_state.get("_ctx", {})
195
  if not ctx:
196
+ no_match_page()
 
197
 
198
  eq_l = ctx["eq_l"]
199
  eq_v = ctx["eq_v"]
src/src/__pycache__/config.cpython-313.pyc CHANGED
Binary files a/src/src/__pycache__/config.cpython-313.pyc and b/src/src/__pycache__/config.cpython-313.pyc differ
 
src/src/__pycache__/statistics.cpython-313.pyc CHANGED
Binary files a/src/src/__pycache__/statistics.cpython-313.pyc and b/src/src/__pycache__/statistics.cpython-313.pyc differ
 
src/src/__pycache__/styles.cpython-313.pyc CHANGED
Binary files a/src/src/__pycache__/styles.cpython-313.pyc and b/src/src/__pycache__/styles.cpython-313.pyc differ
 
src/src/__pycache__/utils.cpython-313.pyc CHANGED
Binary files a/src/src/__pycache__/utils.cpython-313.pyc and b/src/src/__pycache__/utils.cpython-313.pyc differ
 
src/src/config.py CHANGED
@@ -15,16 +15,16 @@ PAGE_LAYOUT = "wide"
15
  # --- Colores ---
16
  COLOR_LOCAL = "#2570D4"
17
  COLOR_VISITANTE = "#7C4DCC"
18
- COLOR_EMPATE = "#9BA3AE"
19
  COLOR_POSITIVO = "#0D9E6E"
20
  COLOR_NEGATIVO = "#D93025"
21
- COLOR_NEUTRO = "#5A6270"
22
  COLOR_MEDIA = "#B07D0E"
23
- COLOR_ADVERTENCIA = "#f9a825"
24
- COLOR_LOCAL_ALPHA = "rgba(37,112,212,0.3)"
25
- COLOR_VISITANTE_ALPHA = "rgba(124,77,204,0.3)"
26
- COLOR_LOCAL_HT = "#7DAAED"
27
- COLOR_VISITANTE_HT = "#B599E0"
28
 
29
  # --- Hiperparámetros del modelo Random Forest ---
30
  RF_N_ESTIMATORS = 100
 
15
  # --- Colores ---
16
  COLOR_LOCAL = "#2570D4"
17
  COLOR_VISITANTE = "#7C4DCC"
18
+ COLOR_EMPATE = "#94A3B8"
19
  COLOR_POSITIVO = "#0D9E6E"
20
  COLOR_NEGATIVO = "#D93025"
21
+ COLOR_NEUTRO = "#64748B"
22
  COLOR_MEDIA = "#B07D0E"
23
+ COLOR_ADVERTENCIA = "#F59E0B"
24
+ COLOR_LOCAL_ALPHA = "rgba(37,112,212,0.2)"
25
+ COLOR_VISITANTE_ALPHA = "rgba(124,77,204,0.2)"
26
+ COLOR_LOCAL_HT = "#93C5FD"
27
+ COLOR_VISITANTE_HT = "#C4B5FD"
28
 
29
  # --- Hiperparámetros del modelo Random Forest ---
30
  RF_N_ESTIMATORS = 100
src/src/statistics.py CHANGED
@@ -74,11 +74,11 @@ def _colorear_diferencia_serie(series: pd.Series) -> list[str]:
74
  if pd.isna(v):
75
  result.append("")
76
  elif abs(v) <= umbral:
77
- result.append(f"background-color: {COLOR_ADVERTENCIA}; color: black; font-weight: bold")
78
  elif v > 0:
79
- result.append(f"background-color: {COLOR_POSITIVO}; color: white; font-weight: bold")
80
  else:
81
- result.append(f"background-color: {COLOR_NEGATIVO}; color: white; font-weight: bold")
82
  return result
83
 
84
 
@@ -93,11 +93,11 @@ def _colorear_desv(series: pd.Series) -> list[str]:
93
  if pd.isna(v):
94
  result.append("")
95
  elif v <= q33:
96
- result.append(f"background-color: {COLOR_POSITIVO}; color: white; font-weight: bold")
97
  elif v <= q66:
98
- result.append(f"background-color: {COLOR_ADVERTENCIA}; color: black; font-weight: bold")
99
  else:
100
- result.append(f"background-color: {COLOR_NEGATIVO}; color: white; font-weight: bold")
101
  return result
102
 
103
 
 
74
  if pd.isna(v):
75
  result.append("")
76
  elif abs(v) <= umbral:
77
+ result.append("background-color: #FEF3C7; color: #92400E; font-weight: 600")
78
  elif v > 0:
79
+ result.append("background-color: #D1FAE5; color: #065F46; font-weight: 600")
80
  else:
81
+ result.append("background-color: #FEE2E2; color: #991B1B; font-weight: 600")
82
  return result
83
 
84
 
 
93
  if pd.isna(v):
94
  result.append("")
95
  elif v <= q33:
96
+ result.append("background-color: #D1FAE5; color: #065F46; font-weight: 600")
97
  elif v <= q66:
98
+ result.append("background-color: #FEF3C7; color: #92400E; font-weight: 600")
99
  else:
100
+ result.append("background-color: #FEE2E2; color: #991B1B; font-weight: 600")
101
  return result
102
 
103
 
src/src/styles.py CHANGED
@@ -1,586 +1,792 @@
1
  import streamlit as st
2
 
3
  _CSS = """<style>
 
 
 
4
  :root {
5
- --bg-primary: #F6F8FA;
 
6
  --bg-secondary: #FFFFFF;
7
  --bg-card: #FFFFFF;
8
- --bg-hover: #F0F2F5;
9
- --bg-active: #EBF2FF;
10
- --border: #E2E6EA;
11
- --border-light: #EAECEF;
12
- --text-primary: #1A1F2E;
13
- --text-secondary:#5A6270;
14
- --text-muted: #9BA3AE;
15
- --green: #0D9E6E;
16
- --green-dim: rgba(13,158,110,0.08);
 
 
 
 
17
  --blue: #2570D4;
18
- --blue-dim: rgba(37,112,212,0.08);
19
- --yellow: #B07D0E;
20
- --yellow-dim: rgba(176,125,14,0.08);
21
  --red: #D93025;
22
- --red-dim: rgba(217,48,37,0.08);
 
 
 
 
23
  --purple: #7C4DCC;
24
- --purple-dim: rgba(124,77,204,0.08);
25
- --radius-sm: 6px;
26
- --radius-md: 10px;
27
- --radius-lg: 14px;
28
- --shadow: 0 2px 12px rgba(0,0,0,0.06);
29
- --shadow-md: 0 4px 20px rgba(0,0,0,0.1);
30
- --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 
 
 
 
 
 
 
 
31
  }
32
 
33
- /* ─── App shell ─── */
34
- .stApp { background:var(--bg-primary) !important; font-family:var(--font) !important; }
 
 
 
 
 
35
  .main .block-container {
36
- background:var(--bg-primary) !important;
37
- padding:24px 28px 60px !important;
38
- max-width:1400px !important;
39
  }
40
 
41
  /* ─── Hide Sidebar ─── */
42
  [data-testid="stSidebar"],
43
  [data-testid="stSidebarCollapsedControl"],
44
  [data-testid="stSidebarNav"],
45
- section[data-testid="stSidebar"] {
46
- display:none !important;
47
- }
48
- .main { margin-left:0 !important; }
49
 
50
  /* ─── Typography ─── */
51
- h1,h2,h3,h4,h5 { font-family:var(--font) !important; color:var(--text-primary) !important; }
 
 
 
 
 
 
 
 
52
 
53
  /* ─── Metrics ─── */
54
  [data-testid="stMetric"] {
55
- background:var(--bg-card) !important;
56
- border:1px solid var(--border) !important;
57
- border-radius:var(--radius-md) !important;
58
- padding:14px 16px !important;
59
- box-shadow:var(--shadow) !important;
60
- }
 
 
61
  [data-testid="stMetricValue"] {
62
- color:var(--green) !important; font-size:24px !important; font-weight:700 !important;
 
 
 
63
  }
64
  [data-testid="stMetricLabel"] {
65
- color:var(--text-muted) !important; font-size:10px !important;
66
- font-weight:600 !important; text-transform:uppercase !important; letter-spacing:0.5px !important;
 
 
 
67
  }
68
- [data-testid="stMetricDelta"] { font-size:11px !important; }
69
 
70
  /* ─── Dataframes ─── */
71
  [data-testid="stDataFrame"] {
72
- border:1px solid var(--border) !important;
73
- border-radius:var(--radius-md) !important;
74
- overflow:hidden !important;
 
75
  }
76
 
77
  /* ─── Expanders ─── */
78
  [data-testid="stExpander"] {
79
- background:var(--bg-card) !important;
80
- border:1px solid var(--border) !important;
81
- border-radius:var(--radius-md) !important;
82
- overflow:hidden !important;
83
- box-shadow:none !important;
84
  }
85
  [data-testid="stExpander"] details summary {
86
- background:var(--bg-card) !important;
87
- color:var(--text-secondary) !important;
88
- font-size:13px !important; font-weight:500 !important; padding:12px 16px !important;
 
 
 
 
 
 
89
  }
90
- [data-testid="stExpander"] details summary:hover { background:var(--bg-hover) !important; }
91
 
92
  /* ─── Select boxes ─── */
93
- [data-testid="stSelectbox"]>div>div {
94
- background:var(--bg-card) !important;
95
- border:1px solid var(--border) !important;
96
- border-radius:var(--radius-md) !important;
97
- color:var(--text-primary) !important; font-size:13px !important;
 
 
98
  }
99
  [data-testid="stSelectbox"] label {
100
- color:var(--text-secondary) !important; font-size:11px !important;
101
- font-weight:600 !important; text-transform:uppercase !important; letter-spacing:0.4px !important;
 
 
 
102
  }
103
 
104
  /* ─── Buttons ─── */
105
- .stButton>button {
106
- background:var(--green) !important; color:#fff !important;
107
- border:none !important; border-radius:var(--radius-md) !important;
108
- font-weight:600 !important; font-size:13px !important;
109
- padding:9px 22px !important; transition:opacity 0.15s !important; box-shadow:none !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  }
111
- .stButton>button:hover { opacity:0.88 !important; border:none !important; }
112
- .stButton>button[kind="primary"] { background:var(--blue) !important; }
113
 
114
  /* ─── Alerts ─── */
115
- [data-testid="stAlert"] { border-radius:var(--radius-md) !important; font-size:13px !important; }
116
 
117
  /* ─── Number input ─── */
118
  [data-testid="stNumberInput"] input {
119
- background:var(--bg-card) !important; border:1px solid var(--border) !important;
120
- border-radius:var(--radius-md) !important; color:var(--text-primary) !important;
 
 
121
  }
122
 
123
  /* ─── Slider ─── */
124
  [data-testid="stSlider"] label {
125
- color:var(--text-secondary) !important; font-size:11px !important;
126
- font-weight:600 !important; text-transform:uppercase !important; letter-spacing:0.4px !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  }
128
 
129
  /* ─── Caption ─── */
130
- [data-testid="stCaptionContainer"] { color:var(--text-muted) !important; font-size:11px !important; }
 
 
 
131
 
132
  /* ─── Tabs ─── */
133
  .stTabs [data-testid="stTabBar"] {
134
- background:var(--bg-card) !important;
135
- border:1px solid var(--border) !important;
136
- border-radius:var(--radius-md) !important;
137
- padding:4px !important; gap:2px !important;
138
- margin-bottom:20px !important;
 
 
139
  }
140
  .stTabs [data-testid="stTabBar"] button {
141
- border-radius:var(--radius-sm) !important;
142
- font-size:12px !important; font-weight:500 !important;
143
- color:var(--text-secondary) !important;
144
- background:transparent !important; border:none !important;
145
- padding:8px 16px !important; white-space:nowrap !important;
146
- transition:all 0.15s !important;
147
- }
148
- .stTabs [data-testid="stTabBar"] button:hover { color:var(--text-primary) !important; }
 
 
 
 
 
 
149
  .stTabs [data-testid="stTabBar"] button[aria-selected="true"] {
150
- background:var(--bg-secondary) !important;
151
- color:var(--text-primary) !important;
152
- font-weight:600 !important;
153
- box-shadow:0 1px 4px rgba(0,0,0,0.1) !important;
154
  }
155
- [data-testid="stTabPanel"] { padding:0 !important; }
 
 
 
156
 
157
  /* ─── Hide branding ─── */
158
- #MainMenu { visibility:hidden; }
159
- footer { visibility:hidden; }
160
- [data-testid="stToolbar"] { display:none !important; }
161
- [data-testid="stDecoration"] { display:none !important; }
162
 
163
- /* ══════════════════════════════════
164
  CUSTOM COMPONENTS
165
- ══════════════════════════════════ */
166
 
167
- /* Sidebar header */
168
- .fs-sb-header {
169
- display:flex; align-items:center; gap:10px;
170
- padding:16px 0 14px;
171
- border-bottom:1px solid var(--border);
172
- margin-bottom:16px;
173
- }
174
- .fs-sb-logo {
175
- width:34px; height:34px; background:var(--green); border-radius:9px;
176
- display:flex; align-items:center; justify-content:center;
177
- font-size:17px; flex-shrink:0;
 
 
 
178
  }
179
- .fs-sb-title { font-size:14px; font-weight:700; color:var(--text-primary); line-height:1.2; }
180
- .fs-sb-sub { font-size:10px; color:var(--text-muted); }
181
-
182
- /* Sidebar section label */
183
- .fs-sb-sec {
184
- font-size:10px; text-transform:uppercase; letter-spacing:0.8px;
185
- color:var(--text-muted); font-weight:600; margin:14px 0 6px;
186
  }
187
-
188
- /* Sidebar match mini card */
189
- .fs-match-mini {
190
- background:var(--bg-active); border:1px solid rgba(37,112,212,0.3);
191
- border-radius:var(--radius-md); padding:10px 12px; margin:10px 0;
192
  }
193
- .fs-match-mini .home { color:var(--blue); font-weight:700; font-size:12px; }
194
- .fs-match-mini .away { color:var(--red); font-weight:700; font-size:12px; }
195
- .fs-match-mini .vs { color:var(--text-muted); font-size:11px; padding:0 5px; }
196
- .fs-match-mini .meta { font-size:10px; color:var(--text-muted); margin-top:5px; }
197
 
198
- /* Sidebar stat pill */
199
- .fs-sb-pills {
200
- display:flex; gap:6px; flex-wrap:wrap; margin-top:8px;
201
- }
202
- .fs-sb-pill {
203
- display:inline-flex; align-items:center; gap:3px;
204
- background:var(--bg-hover); border:1px solid var(--border);
205
- border-radius:20px; padding:3px 9px;
206
- font-size:11px; color:var(--text-secondary);
 
207
  }
208
- .fs-sb-pill span { font-weight:700; color:var(--text-primary); }
209
-
210
- /* Page header */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  .fs-header {
212
- display:flex; align-items:center; gap:14px;
213
- padding-bottom:18px; border-bottom:1px solid var(--border); margin-bottom:20px;
 
 
 
 
214
  }
215
  .fs-header h1 {
216
- font-size:22px !important; font-weight:700 !important;
217
- color:var(--text-primary) !important; margin:0 !important;
218
- padding:0 !important; line-height:1.2 !important;
219
- }
220
- .fs-header .sub { font-size:12px; color:var(--text-muted); margin-top:3px; }
221
-
222
- /* Section title */
 
 
 
 
 
 
 
 
 
223
  .fs-sec {
224
- display:flex; align-items:center; gap:8px;
225
- font-size:11px; text-transform:uppercase; letter-spacing:0.8px;
226
- color:var(--text-muted); font-weight:600; margin:20px 0 12px;
227
- }
228
- .fs-sec::after { content:''; flex:1; height:1px; background:var(--border-light); }
229
-
230
- /* Stats summary bar */
 
 
 
 
 
 
 
 
231
  .fs-stats-bar {
232
- display:flex; align-items:stretch;
233
- background:var(--bg-card); border:1px solid var(--border);
234
- border-radius:var(--radius-lg); overflow:hidden; margin-bottom:20px;
 
 
 
 
235
  }
236
  .fs-stats-bar .si {
237
- flex:1; text-align:center; padding:14px 16px;
238
- border-right:1px solid var(--border-light);
239
- }
240
- .fs-stats-bar .si:last-child { border-right:none; }
241
- .fs-stats-bar .sn { font-size:24px; font-weight:700; color:var(--green); line-height:1; }
242
- .fs-stats-bar .sn.blue { color:var(--blue); }
243
- .fs-stats-bar .sn.red { color:var(--red); }
244
- .fs-stats-bar .sn.yellow { color:var(--yellow); }
 
 
 
 
 
 
 
 
 
245
  .fs-stats-bar .sl {
246
- font-size:10px; color:var(--text-muted);
247
- text-transform:uppercase; letter-spacing:0.5px; margin-top:4px;
 
 
 
 
248
  }
249
 
250
- /* Team analysis panels */
251
  .fs-panel-home {
252
- background:var(--bg-card);
253
- border:1px solid var(--border);
254
- border-top:3px solid var(--blue);
255
- border-radius:var(--radius-lg);
256
- padding:16px 16px 4px;
257
- margin-bottom:12px;
 
258
  }
259
  .fs-panel-away {
260
- background:var(--bg-card);
261
- border:1px solid var(--border);
262
- border-top:3px solid var(--red);
263
- border-radius:var(--radius-lg);
264
- padding:16px 16px 4px;
265
- margin-bottom:12px;
 
266
  }
267
  .fs-panel-title {
268
- font-size:14px; font-weight:700; margin-bottom:12px;
269
- display:flex; align-items:center; gap:8px;
270
- }
271
- .fs-panel-title.home { color:var(--blue); }
272
- .fs-panel-title.away { color:var(--red); }
273
-
274
- /* Probability bar */
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  .fs-prob {
276
- background:var(--bg-card); border:1px solid var(--border);
277
- border-radius:var(--radius-md); padding:16px; margin-bottom:14px;
 
 
 
 
278
  }
279
  .fs-prob .labels {
280
- display:flex; justify-content:space-between;
281
- font-size:12px; color:var(--text-secondary); margin-bottom:10px; font-weight:500;
 
 
 
 
282
  }
283
  .fs-prob .bar {
284
- height:10px; border-radius:5px; background:var(--bg-hover); overflow:hidden; display:flex;
285
- }
286
- .fs-prob .seg { height:100%; transition:width 0.5s; }
287
- .fs-prob .seg.h { background:#2570D4; }
288
- .fs-prob .seg.d { background:#C0C6CE; }
289
- .fs-prob .seg.a { background:#D93025; }
 
 
 
 
 
290
  .fs-prob .vals {
291
- display:flex; justify-content:space-between;
292
- margin-top:10px; font-size:14px; font-weight:700;
293
- }
294
- .fs-prob .vh { color:#2570D4; }
295
- .fs-prob .vd { color:var(--text-secondary); }
296
- .fs-prob .va { color:#D93025; }
297
-
298
- /* Odds cards (1X2) */
299
- .fs-odds { display:grid; grid-template-columns:1fr 1fr 1fr; gap:10px; margin-bottom:16px; }
 
 
 
 
300
  .fs-odds .oc {
301
- background:var(--bg-card); border:1px solid var(--border);
302
- border-radius:var(--radius-md); padding:16px 12px; text-align:center;
303
- transition:border-color 0.15s;
304
- }
305
- .fs-odds .oc.best { border-color:var(--green); background:var(--green-dim); }
306
- .fs-odds .oc .on { font-size:11px; color:var(--text-muted); margin-bottom:6px; font-weight:500; }
307
- .fs-odds .oc .ov { font-size:26px; font-weight:700; color:var(--text-primary); line-height:1; }
308
- .fs-odds .oc.best .ov { color:var(--green); }
309
- .fs-odds .oc .op { font-size:11px; color:var(--text-secondary); margin-top:6px; }
310
- .fs-odds .oc .ove { font-size:11px; margin-top:3px; font-weight:600; }
311
- .fs-odds .oc .ove.pos { color:var(--green); }
312
- .fs-odds .oc .ove.neg { color:var(--red); }
313
-
314
- /* Kelly result cards */
315
- .fs-kelly-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; margin-bottom:20px; }
 
 
 
 
 
 
 
 
 
316
  .fs-kelly-card {
317
- background:var(--bg-card); border:1px solid var(--border);
318
- border-top:4px solid var(--border);
319
- border-radius:var(--radius-lg); padding:20px 14px; text-align:center;
320
- }
321
- .fs-kelly-card.positive { border-top-color:var(--green); background:var(--green-dim); border-color:rgba(13,158,110,0.2); }
322
- .fs-kelly-card.yellow { border-top-color:var(--yellow); background:var(--yellow-dim); border-color:rgba(176,125,14,0.2); }
323
- .fs-kelly-card.negative { border-top-color:var(--red); }
324
- .fs-kelly-card .kc-result { font-size:10px; text-transform:uppercase; letter-spacing:0.5px; color:var(--text-muted); font-weight:600; margin-bottom:4px; }
325
- .fs-kelly-card .kc-team { font-size:13px; font-weight:700; color:var(--text-primary); margin-bottom:14px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
326
- .fs-kelly-card .kc-odd { font-size:32px; font-weight:700; color:var(--text-primary); line-height:1; margin-bottom:10px; }
327
- .fs-kelly-card.positive .kc-odd { color:var(--green); }
328
- .fs-kelly-card .kc-sep { width:32px; height:2px; background:var(--border); margin:0 auto 10px; border-radius:2px; }
329
- .fs-kelly-card .kc-pct { font-size:20px; font-weight:700; line-height:1; }
330
- .fs-kelly-card.positive .kc-pct { color:var(--green); }
331
- .fs-kelly-card.yellow .kc-pct { color:var(--yellow); }
332
- .fs-kelly-card.negative .kc-pct { color:var(--red); }
333
- .fs-kelly-card .kc-label { font-size:10px; color:var(--text-muted); margin-top:3px; }
334
- .fs-kelly-card .kc-meta { margin-top:10px; padding-top:10px; border-top:1px solid var(--border-light); font-size:11px; color:var(--text-secondary); }
335
- .fs-kelly-card .kc-meta .pos { color:var(--green); font-weight:700; }
336
- .fs-kelly-card .kc-meta .neg { color:var(--red); font-weight:700; }
337
-
338
- /* Recommendation banner */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  .fs-recommend {
340
- display:flex; align-items:center; gap:14px;
341
- background:var(--green-dim); border:1px solid rgba(13,158,110,0.25);
342
- border-radius:var(--radius-md); padding:14px 18px; margin-top:4px;
343
- }
344
- .fs-recommend .rec-icon { font-size:24px; flex-shrink:0; }
345
- .fs-recommend .rec-title { font-size:13px; font-weight:700; color:var(--green); }
346
- .fs-recommend .rec-detail { font-size:12px; color:var(--text-secondary); margin-top:2px; }
347
- .fs-no-bet {
348
- display:flex; align-items:center; gap:14px;
349
- background:var(--red-dim); border:1px solid rgba(217,48,37,0.2);
350
- border-radius:var(--radius-md); padding:14px 18px; margin-top:4px;
351
- }
352
- .fs-no-bet .nb-icon { font-size:22px; flex-shrink:0; }
353
- .fs-no-bet .nb-text { font-size:13px; font-weight:600; color:var(--red); }
354
 
355
- /* AI panel */
356
- .fs-ai {
357
- background:linear-gradient(135deg,rgba(37,112,212,0.05),rgba(217,48,37,0.05));
358
- border:1px solid rgba(37,112,212,0.2);
359
- border-radius:var(--radius-md); padding:16px;
360
- }
361
- .fs-ai .ai-hdr { display:flex; align-items:center; gap:10px; margin-bottom:4px; }
362
- .fs-ai .ai-ico {
363
- width:32px; height:32px; background:var(--blue-dim); border:1px solid var(--blue);
364
- border-radius:var(--radius-sm); display:flex; align-items:center; justify-content:center; font-size:16px;
365
- }
366
- .fs-ai .ai-title { font-size:14px; font-weight:700; color:var(--blue); }
367
- .fs-ai .ai-sub { font-size:10px; color:var(--text-muted); }
368
-
369
- /* Empty state */
370
  .fs-empty {
371
- text-align:center; padding:40px 20px;
372
- color:var(--text-muted); font-size:13px;
 
 
373
  }
374
- .fs-empty .em-icon { font-size:32px; margin-bottom:8px; }
375
 
376
- /* ── Match selection cards ── */
377
  .fs-mc {
378
- background:#FFFFFF;
379
- border:1px solid #DDE3EA;
380
- border-radius:var(--radius-lg);
381
- overflow:hidden;
382
- transition:box-shadow 0.15s, border-color 0.15s;
383
- margin-bottom:4px;
384
- box-shadow:0 1px 4px rgba(0,0,0,0.05);
385
  }
386
  .fs-mc:hover {
387
- border-color:#B8C6DC;
388
- box-shadow:0 4px 14px rgba(37,112,212,0.1);
 
389
  }
390
  .fs-mc.selected {
391
- border-color:rgba(13,158,110,0.45);
392
- box-shadow:0 0 0 2px rgba(13,158,110,0.18), 0 4px 14px rgba(13,158,110,0.1);
 
393
  }
394
 
395
- /* top strip: liga + time */
396
  .fs-mc .mc-top {
397
- display:flex; align-items:center; justify-content:space-between;
398
- padding:7px 12px 6px;
399
- background:#F4F7FB;
400
- border-bottom:1px solid #E8EEF5;
 
 
401
  }
402
  .fs-mc.selected .mc-top {
403
- background:#EBF7F2;
404
- border-bottom-color:rgba(13,158,110,0.2);
405
- }
406
- .fs-mc .mc-league {
407
- font-size:10px; font-weight:700; color:#5A6270;
408
- text-transform:uppercase; letter-spacing:0.5px;
409
- }
410
- .fs-mc .mc-time {
411
- font-size:11px; font-weight:600; color:#7C4DCC;
412
- background:#F0EBF9; border-radius:4px; padding:1px 7px;
413
  }
 
 
414
 
415
  /* teams row */
416
- .fs-mc .mc-teams {
417
- display:flex; align-items:stretch;
418
- min-height:72px;
419
- }
420
- .fs-mc .mc-team {
421
- flex:1; display:flex; flex-direction:column;
422
- align-items:center; justify-content:center;
423
- padding:12px 8px;
424
- }
425
- .fs-mc .mc-team.home { background:#EEF4FD; }
426
- .fs-mc .mc-team.away { background:#FDF0EF; }
427
- .fs-mc .mc-name {
428
- font-size:13px; font-weight:700; text-align:center; line-height:1.25;
429
- }
430
- .fs-mc .mc-team.home .mc-name { color:#1B5EC0; }
431
- .fs-mc .mc-team.away .mc-name { color:#B52A1E; }
432
- .fs-mc .mc-role {
433
- font-size:9px; text-transform:uppercase; letter-spacing:0.5px;
434
- margin-top:4px; font-weight:600;
435
- }
436
- .fs-mc .mc-team.home .mc-role { color:#6A9FDE; }
437
- .fs-mc .mc-team.away .mc-role { color:#DC7068; }
438
 
439
  /* VS badge */
440
- .fs-mc .mc-vs-wrap {
441
- display:flex; align-items:center; justify-content:center;
442
- padding:0 10px; background:#FFFFFF; flex-shrink:0;
443
- }
444
- .fs-mc .mc-vs {
445
- font-size:10px; font-weight:800; color:#C0C6CE;
446
- letter-spacing:1px;
447
- background:#F4F7FB; border:1px solid #DDE3EA;
448
- border-radius:5px; padding:3px 7px;
449
- }
450
 
451
  /* odds strip */
452
  .fs-mc .mc-odds {
453
- display:flex; justify-content:space-around; align-items:center;
454
- padding:6px 12px;
455
- background:#FAFBFD;
456
- border-top:1px solid #E8EEF5;
457
- }
458
- .fs-mc .mc-odd {
459
- font-size:11px; font-weight:700;
460
- border-radius:4px; padding:2px 8px;
461
- }
462
- .fs-mc .mc-odd.home { color:#1B5EC0; background:#EEF4FD; }
463
- .fs-mc .mc-odd.draw { color:#5A6270; background:#F4F7FB; }
464
- .fs-mc .mc-odd.away { color:#B52A1E; background:#FDF0EF; }
465
-
466
- /* selected tint override for teams */
467
- .fs-mc.selected .mc-team.home { background:#D8EFDA; }
468
- .fs-mc.selected .mc-team.home .mc-name { color:#0A6B42; }
469
- .fs-mc.selected .mc-team.home .mc-role { color:#4CAF7D; }
470
- .fs-mc.selected .mc-team.away { background:#D8EFDA; }
471
- .fs-mc.selected .mc-team.away .mc-name { color:#0A6B42; }
472
- .fs-mc.selected .mc-team.away .mc-role { color:#4CAF7D; }
473
- .fs-mc.selected .mc-vs { background:#EBF7F2; border-color:rgba(13,158,110,0.3); color:#4CAF7D; }
474
- .fs-mc.selected .mc-odds { background:#EBF7F2; border-top-color:rgba(13,158,110,0.2); }
475
- .fs-mc.selected .mc-odd.home { background:#C8EBCF; color:#0A6B42; }
476
- .fs-mc.selected .mc-odd.draw { background:#D8F0DD; color:#3A7A52; }
477
- .fs-mc.selected .mc-odd.away { background:#C8EBCF; color:#0A6B42; }
478
 
479
  /* date group label */
480
  .fs-date-label {
481
- font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:0.6px;
482
- color:var(--text-muted); padding:14px 0 8px; border-bottom:1px solid var(--border-light);
483
- margin-bottom:10px;
 
 
 
 
 
484
  }
485
 
486
- /* Sidebar no-match state */
487
- .fs-sb-welcome {
488
- text-align:center; padding:20px 8px;
489
- color:var(--text-muted); font-size:12px;
490
- }
491
- .fs-sb-welcome .sw-icon { font-size:28px; margin-bottom:8px; }
492
- .fs-sb-welcome .sw-text { line-height:1.5; }
493
-
494
- /* ─── Page navigation links ─── */
495
- [data-testid="stPageLink"] {
496
- margin:2px 0 !important;
497
- }
498
- [data-testid="stPageLink"] a {
499
- border-radius:var(--radius-sm) !important;
500
- padding:6px 10px !important;
501
- font-size:12px !important;
502
- color:var(--text-secondary) !important;
503
- display:flex !important; align-items:center !important; gap:6px !important;
504
- transition:all 0.15s !important;
505
- text-decoration:none !important;
506
- font-weight:500 !important;
507
- white-space:nowrap !important;
508
- }
509
- [data-testid="stPageLink"] a:hover {
510
- background:var(--bg-hover) !important;
511
- color:var(--text-primary) !important;
512
- }
513
- [data-testid="stPageLink"] a[aria-current="page"] {
514
- background:var(--bg-active) !important;
515
- color:var(--blue) !important;
516
- font-weight:600 !important;
517
- }
518
-
519
- /* ─── Topbar ─── */
520
- .fs-topbar {
521
- display:flex; align-items:center; gap:10px;
522
- padding:8px 0 12px; border-bottom:1px solid var(--border); margin-bottom:16px;
523
- }
524
- .fs-topbar-logo {
525
- display:flex; align-items:center; gap:8px; flex-shrink:0;
526
- font-size:15px; font-weight:700; color:var(--text-primary);
527
- }
528
- .fs-topbar-logo .tl-icon {
529
- width:30px; height:30px; background:var(--green); border-radius:8px;
530
- display:flex; align-items:center; justify-content:center; font-size:15px;
531
- }
532
-
533
- /* ─── Match context bar (shown when match is selected) ─── */
534
- .fs-match-bar {
535
- display:flex; align-items:center; gap:6px; flex-wrap:wrap;
536
- background:var(--bg-active); border:1px solid rgba(37,112,212,0.2);
537
- border-radius:var(--radius-md); padding:8px 14px; margin-bottom:14px;
538
- font-size:13px;
539
- }
540
- .fs-match-bar .home { color:var(--blue); font-weight:700; }
541
- .fs-match-bar .away { color:var(--red); font-weight:700; }
542
- .fs-match-bar .vs { color:var(--text-muted); padding:0 4px; font-size:11px; }
543
- .fs-match-bar .meta { color:var(--text-muted); font-size:11px; margin-left:4px; }
544
-
545
- /* ─── Result pills (last X results) ─── */
546
- .fs-result-pills {
547
- display:flex; flex-wrap:wrap; gap:5px; margin-top:8px;
548
- }
549
  .rp {
550
- min-width:28px; height:28px; border-radius:6px;
551
- display:flex; align-items:center; justify-content:center;
552
- font-size:12px; font-weight:700; color:#fff;
553
- padding:0 5px;
554
- }
555
-
556
- /* ─── Category stats header ─── */
 
 
 
 
 
 
 
557
  .fs-cat-label {
558
- font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:0.6px;
559
- color:var(--text-muted); margin-bottom:6px;
 
 
 
 
560
  }
561
 
562
- /* ── Compact comparison cell ── */
563
- .fs-compare-cell {
564
- background:var(--bg-card); border:1px solid var(--border);
565
- border-radius:var(--radius-md); padding:14px 12px;
566
- text-align:center; margin-bottom:10px;
567
- }
568
- .fs-compare-cell .cc-val { font-size:24px; font-weight:700; line-height:1; }
569
- .fs-compare-cell .cc-lbl { font-size:10px; color:var(--text-muted); margin-top:4px; text-transform:uppercase; letter-spacing:0.4px; }
570
- .fs-compare-cell .cc-delta { font-size:12px; font-weight:600; margin-top:6px; }
571
- .fs-compare-cell .cc-delta.pos { color:var(--green); }
572
- .fs-compare-cell .cc-delta.neg { color:var(--red); }
573
- .fs-compare-cell .cc-delta.neu { color:var(--text-muted); }
574
-
575
- /* ─── Date filter bar ─── */
576
- .fs-date-filter {
577
- display:flex; align-items:center; justify-content:flex-end; gap:10px;
578
- margin-bottom:8px;
579
- }
580
- .fs-date-filter label {
581
- font-size:11px; font-weight:600; text-transform:uppercase;
582
- letter-spacing:0.5px; color:var(--text-muted);
583
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
584
  </style>"""
585
 
586
 
 
1
  import streamlit as st
2
 
3
  _CSS = """<style>
4
+ /* ── Google Inter font ── */
5
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
6
+
7
  :root {
8
+ /* ── Surfaces ── */
9
+ --bg-primary: #F8FAFC;
10
  --bg-secondary: #FFFFFF;
11
  --bg-card: #FFFFFF;
12
+ --bg-hover: #F1F5F9;
13
+ --bg-active: #EFF6FF;
14
+
15
+ /* ── Borders ── */
16
+ --border: #E2E8F0;
17
+ --border-light: #F1F5F9;
18
+
19
+ /* ── Text ── */
20
+ --text-primary: #0F172A;
21
+ --text-secondary:#475569;
22
+ --text-muted: #94A3B8;
23
+
24
+ /* ── Brand (kept identical to config.py constants) ── */
25
  --blue: #2570D4;
26
+ --blue-dim: rgba(37,112,212,0.09);
27
+ --blue-mid: rgba(37,112,212,0.18);
 
28
  --red: #D93025;
29
+ --red-dim: rgba(217,48,37,0.09);
30
+ --green: #0D9E6E;
31
+ --green-dim: rgba(13,158,110,0.09);
32
+ --yellow: #B07D0E;
33
+ --yellow-dim: rgba(176,125,14,0.09);
34
  --purple: #7C4DCC;
35
+ --purple-dim: rgba(124,77,204,0.09);
36
+
37
+ /* ── Radius ── */
38
+ --radius-sm: 6px;
39
+ --radius-md: 10px;
40
+ --radius-lg: 16px;
41
+ --radius-xl: 24px;
42
+
43
+ /* ── Shadows ── */
44
+ --shadow-xs: 0 1px 3px rgba(15,23,42,0.06);
45
+ --shadow: 0 2px 10px rgba(15,23,42,0.07), 0 1px 3px rgba(15,23,42,0.04);
46
+ --shadow-md: 0 6px 20px rgba(15,23,42,0.09), 0 2px 6px rgba(15,23,42,0.05);
47
+
48
+ /* ── Font ── */
49
+ --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
50
  }
51
 
52
+ /* ════════════════════════════════════
53
+ APP SHELL
54
+ ════════════════════════════════════ */
55
+ .stApp {
56
+ background: var(--bg-primary) !important;
57
+ font-family: var(--font) !important;
58
+ }
59
  .main .block-container {
60
+ background: var(--bg-primary) !important;
61
+ padding: 20px 28px 60px !important;
62
+ max-width: 1400px !important;
63
  }
64
 
65
  /* ─── Hide Sidebar ─── */
66
  [data-testid="stSidebar"],
67
  [data-testid="stSidebarCollapsedControl"],
68
  [data-testid="stSidebarNav"],
69
+ section[data-testid="stSidebar"] { display: none !important; }
70
+ .main { margin-left: 0 !important; }
 
 
71
 
72
  /* ─── Typography ─── */
73
+ h1,h2,h3,h4,h5 {
74
+ font-family: var(--font) !important;
75
+ color: var(--text-primary) !important;
76
+ letter-spacing: -0.02em !important;
77
+ }
78
+
79
+ /* ════════════════════════════════════
80
+ STREAMLIT NATIVE COMPONENTS
81
+ ════════════════════════════════════ */
82
 
83
  /* ─── Metrics ─── */
84
  [data-testid="stMetric"] {
85
+ background: var(--bg-card) !important;
86
+ border: 1px solid var(--border) !important;
87
+ border-radius: var(--radius-md) !important;
88
+ padding: 14px 16px !important;
89
+ box-shadow: var(--shadow-xs) !important;
90
+ transition: box-shadow 0.15s !important;
91
+ }
92
+ [data-testid="stMetric"]:hover { box-shadow: var(--shadow) !important; }
93
  [data-testid="stMetricValue"] {
94
+ color: var(--text-primary) !important;
95
+ font-size: 22px !important;
96
+ font-weight: 700 !important;
97
+ letter-spacing: -0.03em !important;
98
  }
99
  [data-testid="stMetricLabel"] {
100
+ color: var(--text-muted) !important;
101
+ font-size: 10px !important;
102
+ font-weight: 600 !important;
103
+ text-transform: uppercase !important;
104
+ letter-spacing: 0.6px !important;
105
  }
106
+ [data-testid="stMetricDelta"] { font-size: 11px !important; font-weight: 500 !important; }
107
 
108
  /* ─── Dataframes ─── */
109
  [data-testid="stDataFrame"] {
110
+ border: 1px solid var(--border) !important;
111
+ border-radius: var(--radius-md) !important;
112
+ overflow: hidden !important;
113
+ box-shadow: var(--shadow-xs) !important;
114
  }
115
 
116
  /* ─── Expanders ─── */
117
  [data-testid="stExpander"] {
118
+ background: var(--bg-card) !important;
119
+ border: 1px solid var(--border) !important;
120
+ border-radius: var(--radius-md) !important;
121
+ overflow: hidden !important;
122
+ box-shadow: var(--shadow-xs) !important;
123
  }
124
  [data-testid="stExpander"] details summary {
125
+ background: var(--bg-card) !important;
126
+ color: var(--text-secondary) !important;
127
+ font-size: 13px !important;
128
+ font-weight: 500 !important;
129
+ padding: 12px 16px !important;
130
+ }
131
+ [data-testid="stExpander"] details summary:hover {
132
+ background: var(--bg-hover) !important;
133
+ color: var(--text-primary) !important;
134
  }
 
135
 
136
  /* ─── Select boxes ─── */
137
+ [data-testid="stSelectbox"] > div > div {
138
+ background: var(--bg-card) !important;
139
+ border: 1px solid var(--border) !important;
140
+ border-radius: var(--radius-md) !important;
141
+ color: var(--text-primary) !important;
142
+ font-size: 13px !important;
143
+ box-shadow: var(--shadow-xs) !important;
144
  }
145
  [data-testid="stSelectbox"] label {
146
+ color: var(--text-muted) !important;
147
+ font-size: 11px !important;
148
+ font-weight: 600 !important;
149
+ text-transform: uppercase !important;
150
+ letter-spacing: 0.5px !important;
151
  }
152
 
153
  /* ─── Buttons ─── */
154
+ .stButton > button {
155
+ background: linear-gradient(135deg, var(--green), #0b8a5f) !important;
156
+ color: #fff !important;
157
+ border: none !important;
158
+ border-radius: var(--radius-md) !important;
159
+ font-weight: 600 !important;
160
+ font-size: 13px !important;
161
+ padding: 9px 22px !important;
162
+ transition: opacity 0.15s, transform 0.1s !important;
163
+ box-shadow: 0 2px 8px rgba(13,158,110,0.25) !important;
164
+ letter-spacing: 0.01em !important;
165
+ }
166
+ .stButton > button:hover {
167
+ opacity: 0.9 !important;
168
+ transform: translateY(-1px) !important;
169
+ box-shadow: 0 4px 12px rgba(13,158,110,0.3) !important;
170
+ }
171
+ .stButton > button[kind="primary"],
172
+ .stButton > button[data-testid="stBaseButton-primary"] {
173
+ background: linear-gradient(135deg, var(--blue), #1a5ec0) !important;
174
+ box-shadow: 0 2px 8px rgba(37,112,212,0.25) !important;
175
+ }
176
+ .stButton > button[kind="primary"]:hover,
177
+ .stButton > button[data-testid="stBaseButton-primary"]:hover {
178
+ box-shadow: 0 4px 12px rgba(37,112,212,0.3) !important;
179
+ }
180
+ .stButton > button[kind="secondary"],
181
+ .stButton > button[data-testid="stBaseButton-secondary"] {
182
+ background: var(--bg-card) !important;
183
+ color: var(--text-secondary) !important;
184
+ border: 1px solid var(--border) !important;
185
+ box-shadow: var(--shadow-xs) !important;
186
+ }
187
+ .stButton > button[kind="secondary"]:hover {
188
+ background: var(--bg-hover) !important;
189
+ color: var(--text-primary) !important;
190
+ box-shadow: var(--shadow) !important;
191
+ transform: translateY(-1px) !important;
192
  }
 
 
193
 
194
  /* ─── Alerts ─── */
195
+ [data-testid="stAlert"] { border-radius: var(--radius-md) !important; font-size: 13px !important; }
196
 
197
  /* ─── Number input ─── */
198
  [data-testid="stNumberInput"] input {
199
+ background: var(--bg-card) !important;
200
+ border: 1px solid var(--border) !important;
201
+ border-radius: var(--radius-md) !important;
202
+ color: var(--text-primary) !important;
203
  }
204
 
205
  /* ─── Slider ─── */
206
  [data-testid="stSlider"] label {
207
+ color: var(--text-secondary) !important;
208
+ font-size: 11px !important;
209
+ font-weight: 600 !important;
210
+ text-transform: uppercase !important;
211
+ letter-spacing: 0.5px !important;
212
+ }
213
+
214
+ /* ─── Text input ─── */
215
+ [data-testid="stTextInput"] input {
216
+ background: var(--bg-card) !important;
217
+ border: 1px solid var(--border) !important;
218
+ border-radius: var(--radius-md) !important;
219
+ color: var(--text-primary) !important;
220
+ font-size: 13px !important;
221
+ padding: 8px 12px !important;
222
+ }
223
+ [data-testid="stTextInput"] input:focus {
224
+ border-color: var(--blue) !important;
225
+ box-shadow: 0 0 0 3px rgba(37,112,212,0.12) !important;
226
  }
227
 
228
  /* ─── Caption ─── */
229
+ [data-testid="stCaptionContainer"] {
230
+ color: var(--text-muted) !important;
231
+ font-size: 11px !important;
232
+ }
233
 
234
  /* ─── Tabs ─── */
235
  .stTabs [data-testid="stTabBar"] {
236
+ background: var(--bg-card) !important;
237
+ border: 1px solid var(--border) !important;
238
+ border-radius: var(--radius-lg) !important;
239
+ padding: 5px !important;
240
+ gap: 2px !important;
241
+ margin-bottom: 20px !important;
242
+ box-shadow: var(--shadow-xs) !important;
243
  }
244
  .stTabs [data-testid="stTabBar"] button {
245
+ border-radius: var(--radius-md) !important;
246
+ font-size: 12px !important;
247
+ font-weight: 500 !important;
248
+ color: var(--text-secondary) !important;
249
+ background: transparent !important;
250
+ border: none !important;
251
+ padding: 8px 16px !important;
252
+ white-space: nowrap !important;
253
+ transition: all 0.15s !important;
254
+ }
255
+ .stTabs [data-testid="stTabBar"] button:hover {
256
+ color: var(--text-primary) !important;
257
+ background: var(--bg-hover) !important;
258
+ }
259
  .stTabs [data-testid="stTabBar"] button[aria-selected="true"] {
260
+ background: var(--blue) !important;
261
+ color: #fff !important;
262
+ font-weight: 600 !important;
263
+ box-shadow: 0 2px 8px rgba(37,112,212,0.3) !important;
264
  }
265
+ [data-testid="stTabPanel"] { padding: 0 !important; }
266
+
267
+ /* ─── Spinner ─── */
268
+ [data-testid="stSpinner"] > div { border-top-color: var(--blue) !important; }
269
 
270
  /* ─── Hide branding ─── */
271
+ #MainMenu { visibility: hidden; }
272
+ footer { visibility: hidden; }
273
+ [data-testid="stToolbar"] { display: none !important; }
274
+ [data-testid="stDecoration"] { display: none !important; }
275
 
276
+ /* ════════════════════════════════════
277
  CUSTOM COMPONENTS
278
+ ════════════════════════════════════ */
279
 
280
+ /* ── Page navigation links ── */
281
+ [data-testid="stPageLink"] { margin: 1px 0 !important; }
282
+ [data-testid="stPageLink"] a {
283
+ border-radius: var(--radius-md) !important;
284
+ padding: 7px 12px !important;
285
+ font-size: 12px !important;
286
+ color: var(--text-secondary) !important;
287
+ display: flex !important;
288
+ align-items: center !important;
289
+ gap: 6px !important;
290
+ transition: all 0.15s !important;
291
+ text-decoration: none !important;
292
+ font-weight: 500 !important;
293
+ white-space: nowrap !important;
294
  }
295
+ [data-testid="stPageLink"] a:hover {
296
+ background: var(--bg-hover) !important;
297
+ color: var(--text-primary) !important;
 
 
 
 
298
  }
299
+ [data-testid="stPageLink"] a[aria-current="page"] {
300
+ background: var(--blue) !important;
301
+ color: #fff !important;
302
+ font-weight: 600 !important;
303
+ box-shadow: 0 2px 8px rgba(37,112,212,0.28) !important;
304
  }
 
 
 
 
305
 
306
+ /* ── Topbar ── */
307
+ .fs-topbar-logo {
308
+ display: flex;
309
+ align-items: center;
310
+ gap: 9px;
311
+ flex-shrink: 0;
312
+ font-size: 15px;
313
+ font-weight: 700;
314
+ color: var(--text-primary);
315
+ letter-spacing: -0.02em;
316
  }
317
+ .fs-topbar-logo .tl-icon {
318
+ width: 32px;
319
+ height: 32px;
320
+ background: linear-gradient(135deg, var(--blue) 0%, var(--purple) 100%);
321
+ border-radius: 9px;
322
+ display: flex;
323
+ align-items: center;
324
+ justify-content: center;
325
+ font-size: 16px;
326
+ box-shadow: 0 2px 8px rgba(37,112,212,0.3);
327
+ }
328
+
329
+ /* ── Match context bar ── */
330
+ .fs-match-bar {
331
+ display: flex;
332
+ align-items: center;
333
+ gap: 8px;
334
+ flex-wrap: wrap;
335
+ background: linear-gradient(135deg, rgba(37,112,212,0.06), rgba(124,77,204,0.04));
336
+ border: 1px solid rgba(37,112,212,0.2);
337
+ border-left: 3px solid var(--blue);
338
+ border-radius: var(--radius-md);
339
+ padding: 9px 14px;
340
+ margin-bottom: 14px;
341
+ font-size: 13px;
342
+ }
343
+ .fs-match-bar .home { color: var(--blue); font-weight: 700; }
344
+ .fs-match-bar .away { color: var(--red); font-weight: 700; }
345
+ .fs-match-bar .vs { color: var(--text-muted); padding: 0 4px; font-size: 11px; }
346
+ .fs-match-bar .meta { color: var(--text-muted); font-size: 11px; margin-left: 2px; }
347
+
348
+ /* ── Page header ── */
349
  .fs-header {
350
+ display: flex;
351
+ align-items: center;
352
+ gap: 14px;
353
+ padding-bottom: 16px;
354
+ border-bottom: 1px solid var(--border);
355
+ margin-bottom: 20px;
356
  }
357
  .fs-header h1 {
358
+ font-size: 24px !important;
359
+ font-weight: 800 !important;
360
+ color: var(--text-primary) !important;
361
+ margin: 0 !important;
362
+ padding: 0 !important;
363
+ line-height: 1.15 !important;
364
+ letter-spacing: -0.03em !important;
365
+ }
366
+ .fs-header .sub {
367
+ font-size: 12px;
368
+ color: var(--text-muted);
369
+ margin-top: 4px;
370
+ font-weight: 400;
371
+ }
372
+
373
+ /* ── Section title ── */
374
  .fs-sec {
375
+ display: flex;
376
+ align-items: center;
377
+ gap: 8px;
378
+ font-size: 10px;
379
+ text-transform: uppercase;
380
+ letter-spacing: 1px;
381
+ color: var(--text-secondary);
382
+ font-weight: 700;
383
+ margin: 28px 0 14px;
384
+ padding-left: 10px;
385
+ border-left: 3px solid var(--blue);
386
+ line-height: 1.4;
387
+ }
388
+
389
+ /* ── Stats summary bar ── */
390
  .fs-stats-bar {
391
+ display: grid;
392
+ grid-template-columns: repeat(4, 1fr);
393
+ background: var(--bg-card);
394
+ border-radius: var(--radius-lg);
395
+ box-shadow: var(--shadow);
396
+ margin-bottom: 22px;
397
+ overflow: hidden;
398
  }
399
  .fs-stats-bar .si {
400
+ text-align: center;
401
+ padding: 18px 16px;
402
+ border-right: 1px solid var(--border-light);
403
+ transition: background 0.15s;
404
+ }
405
+ .fs-stats-bar .si:last-child { border-right: none; }
406
+ .fs-stats-bar .si:hover { background: var(--bg-hover); }
407
+ .fs-stats-bar .sn {
408
+ font-size: 28px;
409
+ font-weight: 800;
410
+ color: var(--green);
411
+ line-height: 1;
412
+ letter-spacing: -0.04em;
413
+ }
414
+ .fs-stats-bar .sn.blue { color: var(--blue); }
415
+ .fs-stats-bar .sn.red { color: var(--red); }
416
+ .fs-stats-bar .sn.yellow { color: var(--yellow); }
417
  .fs-stats-bar .sl {
418
+ font-size: 10px;
419
+ color: var(--text-muted);
420
+ text-transform: uppercase;
421
+ letter-spacing: 0.6px;
422
+ margin-top: 5px;
423
+ font-weight: 500;
424
  }
425
 
426
+ /* ── Team analysis panels ── */
427
  .fs-panel-home {
428
+ background: var(--bg-card);
429
+ border: 1px solid rgba(37,112,212,0.2);
430
+ border-radius: var(--radius-lg);
431
+ padding: 0;
432
+ margin-bottom: 12px;
433
+ overflow: hidden;
434
+ box-shadow: var(--shadow-xs);
435
  }
436
  .fs-panel-away {
437
+ background: var(--bg-card);
438
+ border: 1px solid rgba(217,48,37,0.2);
439
+ border-radius: var(--radius-lg);
440
+ padding: 0;
441
+ margin-bottom: 12px;
442
+ overflow: hidden;
443
+ box-shadow: var(--shadow-xs);
444
  }
445
  .fs-panel-title {
446
+ font-size: 13px;
447
+ font-weight: 700;
448
+ display: flex;
449
+ align-items: center;
450
+ gap: 8px;
451
+ padding: 12px 16px;
452
+ margin-bottom: 0;
453
+ }
454
+ .fs-panel-title.home {
455
+ background: linear-gradient(135deg, rgba(37,112,212,0.1), rgba(37,112,212,0.04));
456
+ color: var(--blue);
457
+ border-bottom: 1px solid rgba(37,112,212,0.12);
458
+ }
459
+ .fs-panel-title.away {
460
+ background: linear-gradient(135deg, rgba(217,48,37,0.1), rgba(217,48,37,0.04));
461
+ color: var(--red);
462
+ border-bottom: 1px solid rgba(217,48,37,0.12);
463
+ }
464
+
465
+ /* ── Probability bar ── */
466
  .fs-prob {
467
+ background: var(--bg-card);
468
+ border: 1px solid var(--border);
469
+ border-radius: var(--radius-lg);
470
+ padding: 18px 20px;
471
+ margin-bottom: 14px;
472
+ box-shadow: var(--shadow-xs);
473
  }
474
  .fs-prob .labels {
475
+ display: flex;
476
+ justify-content: space-between;
477
+ font-size: 12px;
478
+ color: var(--text-secondary);
479
+ margin-bottom: 12px;
480
+ font-weight: 500;
481
  }
482
  .fs-prob .bar {
483
+ height: 14px;
484
+ border-radius: 7px;
485
+ background: var(--border-light);
486
+ overflow: hidden;
487
+ display: flex;
488
+ gap: 2px;
489
+ }
490
+ .fs-prob .seg { height: 100%; transition: width 0.5s ease; }
491
+ .fs-prob .seg.h { background: linear-gradient(90deg, #1a5ec0, var(--blue)); }
492
+ .fs-prob .seg.d { background: #CBD5E1; }
493
+ .fs-prob .seg.a { background: linear-gradient(90deg, var(--red), #b52219); }
494
  .fs-prob .vals {
495
+ display: flex;
496
+ justify-content: space-between;
497
+ margin-top: 12px;
498
+ font-size: 16px;
499
+ font-weight: 800;
500
+ letter-spacing: -0.02em;
501
+ }
502
+ .fs-prob .vh { color: var(--blue); }
503
+ .fs-prob .vd { color: var(--text-secondary); }
504
+ .fs-prob .va { color: var(--red); }
505
+
506
+ /* ── Odds cards (1X2) ── */
507
+ .fs-odds { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; margin-bottom: 18px; }
508
  .fs-odds .oc {
509
+ background: var(--bg-card);
510
+ border: 1px solid var(--border);
511
+ border-radius: var(--radius-lg);
512
+ padding: 18px 14px;
513
+ text-align: center;
514
+ transition: box-shadow 0.15s, transform 0.1s;
515
+ box-shadow: var(--shadow-xs);
516
+ }
517
+ .fs-odds .oc:hover { box-shadow: var(--shadow); transform: translateY(-2px); }
518
+ .fs-odds .oc.best {
519
+ border-color: rgba(13,158,110,0.4);
520
+ background: linear-gradient(180deg, rgba(13,158,110,0.06), #fff);
521
+ box-shadow: 0 4px 16px rgba(13,158,110,0.12);
522
+ }
523
+ .fs-odds .oc .on { font-size: 10px; color: var(--text-muted); margin-bottom: 8px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }
524
+ .fs-odds .oc .ov { font-size: 30px; font-weight: 800; color: var(--text-primary); line-height: 1; letter-spacing: -0.04em; }
525
+ .fs-odds .oc.best .ov { color: var(--green); }
526
+ .fs-odds .oc .op { font-size: 11px; color: var(--text-secondary); margin-top: 8px; }
527
+ .fs-odds .oc .ove { font-size: 12px; margin-top: 4px; font-weight: 700; }
528
+ .fs-odds .oc .ove.pos { color: var(--green); }
529
+ .fs-odds .oc .ove.neg { color: var(--red); }
530
+
531
+ /* ── Kelly result cards ── */
532
+ .fs-kelly-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 14px; margin-bottom: 22px; }
533
  .fs-kelly-card {
534
+ background: var(--bg-card);
535
+ border: 1px solid var(--border);
536
+ border-radius: var(--radius-lg);
537
+ padding: 22px 16px;
538
+ text-align: center;
539
+ box-shadow: var(--shadow-xs);
540
+ transition: box-shadow 0.15s, transform 0.1s;
541
+ }
542
+ .fs-kelly-card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
543
+ .fs-kelly-card.positive {
544
+ border-color: rgba(13,158,110,0.3);
545
+ background: linear-gradient(180deg, rgba(13,158,110,0.07) 0%, #fff 60%);
546
+ box-shadow: 0 4px 20px rgba(13,158,110,0.12);
547
+ }
548
+ .fs-kelly-card.yellow {
549
+ border-color: rgba(176,125,14,0.3);
550
+ background: linear-gradient(180deg, rgba(176,125,14,0.07) 0%, #fff 60%);
551
+ }
552
+ .fs-kelly-card.negative {
553
+ border-color: rgba(217,48,37,0.2);
554
+ background: linear-gradient(180deg, rgba(217,48,37,0.04) 0%, #fff 60%);
555
+ }
556
+ .fs-kelly-card .kc-result { font-size: 10px; text-transform: uppercase; letter-spacing: 0.8px; color: var(--text-muted); font-weight: 700; margin-bottom: 4px; }
557
+ .fs-kelly-card .kc-team { font-size: 14px; font-weight: 700; color: var(--text-primary); margin-bottom: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; letter-spacing: -0.01em; }
558
+ .fs-kelly-card .kc-odd { font-size: 36px; font-weight: 800; color: var(--text-primary); line-height: 1; margin-bottom: 12px; letter-spacing: -0.04em; }
559
+ .fs-kelly-card.positive .kc-odd { color: var(--green); }
560
+ .fs-kelly-card .kc-sep { width: 36px; height: 2px; background: var(--border); margin: 0 auto 12px; border-radius: 2px; }
561
+ .fs-kelly-card .kc-pct { font-size: 24px; font-weight: 800; line-height: 1; letter-spacing: -0.03em; }
562
+ .fs-kelly-card.positive .kc-pct { color: var(--green); }
563
+ .fs-kelly-card.yellow .kc-pct { color: var(--yellow); }
564
+ .fs-kelly-card.negative .kc-pct { color: var(--text-muted); }
565
+ .fs-kelly-card .kc-label { font-size: 10px; color: var(--text-muted); margin-top: 4px; font-weight: 500; }
566
+ .fs-kelly-card .kc-meta { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border-light); font-size: 11px; color: var(--text-secondary); }
567
+ .fs-kelly-card .kc-meta .pos { color: var(--green); font-weight: 700; }
568
+ .fs-kelly-card .kc-meta .neg { color: var(--red); font-weight: 700; }
569
+
570
+ /* ── Recommendation / no-bet banners ── */
571
  .fs-recommend {
572
+ display: flex;
573
+ align-items: center;
574
+ gap: 14px;
575
+ background: linear-gradient(135deg, rgba(13,158,110,0.08), rgba(13,158,110,0.04));
576
+ border: 1px solid rgba(13,158,110,0.3);
577
+ border-left: 4px solid var(--green);
578
+ border-radius: var(--radius-md);
579
+ padding: 14px 18px;
580
+ margin-top: 4px;
581
+ }
582
+ .fs-recommend .rec-icon { font-size: 22px; flex-shrink: 0; }
583
+ .fs-recommend .rec-title { font-size: 13px; font-weight: 700; color: var(--green); }
584
+ .fs-recommend .rec-detail { font-size: 12px; color: var(--text-secondary); margin-top: 3px; }
 
585
 
586
+ .fs-no-bet {
587
+ display: flex;
588
+ align-items: center;
589
+ gap: 14px;
590
+ background: rgba(217,48,37,0.05);
591
+ border: 1px solid rgba(217,48,37,0.2);
592
+ border-left: 4px solid var(--red);
593
+ border-radius: var(--radius-md);
594
+ padding: 14px 18px;
595
+ margin-top: 4px;
596
+ }
597
+ .fs-no-bet .nb-icon { font-size: 20px; flex-shrink: 0; }
598
+ .fs-no-bet .nb-text { font-size: 13px; font-weight: 600; color: var(--red); }
599
+
600
+ /* ── Empty state ── */
601
  .fs-empty {
602
+ text-align: center;
603
+ padding: 48px 20px;
604
+ color: var(--text-muted);
605
+ font-size: 13px;
606
  }
607
+ .fs-empty .em-icon { font-size: 36px; margin-bottom: 10px; opacity: 0.7; }
608
 
609
+ /* ── Match selection cards ── */
610
  .fs-mc {
611
+ background: #FFFFFF;
612
+ border: 1px solid #DDE3EA;
613
+ border-radius: var(--radius-lg);
614
+ overflow: hidden;
615
+ transition: box-shadow 0.18s, border-color 0.18s, transform 0.12s;
616
+ margin-bottom: 4px;
617
+ box-shadow: 0 1px 4px rgba(15,23,42,0.06);
618
  }
619
  .fs-mc:hover {
620
+ border-color: #A8BFDE;
621
+ box-shadow: 0 6px 20px rgba(37,112,212,0.12);
622
+ transform: translateY(-2px);
623
  }
624
  .fs-mc.selected {
625
+ border-color: rgba(13,158,110,0.5);
626
+ box-shadow: 0 0 0 3px rgba(13,158,110,0.12), 0 6px 20px rgba(13,158,110,0.1);
627
+ transform: none;
628
  }
629
 
630
+ /* top strip */
631
  .fs-mc .mc-top {
632
+ display: flex;
633
+ align-items: center;
634
+ justify-content: space-between;
635
+ padding: 7px 12px 6px;
636
+ background: #F4F7FB;
637
+ border-bottom: 1px solid #E8EEF5;
638
  }
639
  .fs-mc.selected .mc-top {
640
+ background: linear-gradient(135deg, #EBF7F2, #F0FBF6);
641
+ border-bottom-color: rgba(13,158,110,0.18);
 
 
 
 
 
 
 
 
642
  }
643
+ .fs-mc .mc-league { font-size: 10px; font-weight: 700; color: #5A6270; text-transform: uppercase; letter-spacing: 0.5px; }
644
+ .fs-mc .mc-time { font-size: 11px; font-weight: 600; color: var(--purple); background: rgba(124,77,204,0.08); border-radius: 5px; padding: 2px 8px; }
645
 
646
  /* teams row */
647
+ .fs-mc .mc-teams { display: flex; align-items: stretch; min-height: 72px; }
648
+ .fs-mc .mc-team { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 12px 8px; }
649
+ .fs-mc .mc-team.home { background: linear-gradient(135deg, #EEF4FD, #F5F9FF); }
650
+ .fs-mc .mc-team.away { background: linear-gradient(135deg, #FDF0EF, #FFF5F5); }
651
+ .fs-mc .mc-name { font-size: 13px; font-weight: 700; text-align: center; line-height: 1.25; }
652
+ .fs-mc .mc-team.home .mc-name { color: #1B5EC0; }
653
+ .fs-mc .mc-team.away .mc-name { color: #B52A1E; }
654
+ .fs-mc .mc-role { font-size: 9px; text-transform: uppercase; letter-spacing: 0.5px; margin-top: 4px; font-weight: 600; }
655
+ .fs-mc .mc-team.home .mc-role { color: #6A9FDE; }
656
+ .fs-mc .mc-team.away .mc-role { color: #DC7068; }
 
 
 
 
 
 
 
 
 
 
 
 
657
 
658
  /* VS badge */
659
+ .fs-mc .mc-vs-wrap { display: flex; align-items: center; justify-content: center; padding: 0 10px; background: #FFFFFF; flex-shrink: 0; }
660
+ .fs-mc .mc-vs { font-size: 9px; font-weight: 800; color: #B0BAC6; letter-spacing: 1px; background: #F4F7FB; border: 1px solid #DDE3EA; border-radius: 5px; padding: 3px 7px; }
 
 
 
 
 
 
 
 
661
 
662
  /* odds strip */
663
  .fs-mc .mc-odds {
664
+ display: flex;
665
+ justify-content: space-around;
666
+ align-items: center;
667
+ padding: 6px 12px;
668
+ background: #FAFBFD;
669
+ border-top: 1px solid #E8EEF5;
670
+ }
671
+ .fs-mc .mc-odd { font-size: 11px; font-weight: 700; border-radius: 5px; padding: 2px 9px; }
672
+ .fs-mc .mc-odd.home { color: #1B5EC0; background: #EEF4FD; }
673
+ .fs-mc .mc-odd.draw { color: #5A6270; background: #F4F7FB; }
674
+ .fs-mc .mc-odd.away { color: #B52A1E; background: #FDF0EF; }
675
+
676
+ /* selected tint */
677
+ .fs-mc.selected .mc-team.home { background: linear-gradient(135deg, #D8EFDA, #E8F7EA); }
678
+ .fs-mc.selected .mc-team.home .mc-name { color: #0A6B42; }
679
+ .fs-mc.selected .mc-team.home .mc-role { color: #4CAF7D; }
680
+ .fs-mc.selected .mc-team.away { background: linear-gradient(135deg, #D8EFDA, #E8F7EA); }
681
+ .fs-mc.selected .mc-team.away .mc-name { color: #0A6B42; }
682
+ .fs-mc.selected .mc-team.away .mc-role { color: #4CAF7D; }
683
+ .fs-mc.selected .mc-vs { background: #EBF7F2; border-color: rgba(13,158,110,0.3); color: #4CAF7D; }
684
+ .fs-mc.selected .mc-odds { background: #EBF7F2; border-top-color: rgba(13,158,110,0.15); }
685
+ .fs-mc.selected .mc-odd.home { background: rgba(13,158,110,0.12); color: #0A6B42; }
686
+ .fs-mc.selected .mc-odd.draw { background: rgba(13,158,110,0.08); color: #3A7A52; }
687
+ .fs-mc.selected .mc-odd.away { background: rgba(13,158,110,0.12); color: #0A6B42; }
 
688
 
689
  /* date group label */
690
  .fs-date-label {
691
+ font-size: 11px;
692
+ font-weight: 700;
693
+ text-transform: uppercase;
694
+ letter-spacing: 0.6px;
695
+ color: var(--text-muted);
696
+ padding: 16px 0 8px;
697
+ border-bottom: 1px solid var(--border-light);
698
+ margin-bottom: 12px;
699
  }
700
 
701
+ /* ── Compact comparison cell ── */
702
+ .fs-compare-cell {
703
+ background: var(--bg-card);
704
+ border: 1px solid var(--border);
705
+ border-radius: var(--radius-md);
706
+ padding: 14px 12px;
707
+ text-align: center;
708
+ margin-bottom: 10px;
709
+ box-shadow: var(--shadow-xs);
710
+ }
711
+ .fs-compare-cell .cc-val { font-size: 22px; font-weight: 800; line-height: 1; letter-spacing: -0.03em; color: var(--text-primary); }
712
+ .fs-compare-cell .cc-lbl { font-size: 10px; color: var(--text-muted); margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; }
713
+ .fs-compare-cell .cc-delta { font-size: 12px; font-weight: 700; margin-top: 8px; }
714
+ .fs-compare-cell .cc-delta.pos { color: var(--green); }
715
+ .fs-compare-cell .cc-delta.neg { color: var(--red); }
716
+ .fs-compare-cell .cc-delta.neu { color: var(--text-muted); }
717
+
718
+ /* ── Result pills ── */
719
+ .fs-result-pills { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; padding: 4px 0; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
720
  .rp {
721
+ min-width: 28px;
722
+ height: 28px;
723
+ border-radius: 7px;
724
+ display: flex;
725
+ align-items: center;
726
+ justify-content: center;
727
+ font-size: 12px;
728
+ font-weight: 700;
729
+ color: #fff;
730
+ padding: 0 5px;
731
+ box-shadow: 0 1px 3px rgba(0,0,0,0.15);
732
+ }
733
+
734
+ /* ── Category stats header ── */
735
  .fs-cat-label {
736
+ font-size: 10px;
737
+ font-weight: 700;
738
+ text-transform: uppercase;
739
+ letter-spacing: 0.6px;
740
+ color: var(--text-muted);
741
+ margin-bottom: 6px;
742
  }
743
 
744
+ /* ── AI panel ── */
745
+ .fs-ai {
746
+ background: linear-gradient(135deg, rgba(37,112,212,0.05), rgba(124,77,204,0.04));
747
+ border: 1px solid rgba(37,112,212,0.2);
748
+ border-radius: var(--radius-md);
749
+ padding: 16px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
750
  }
751
+ .fs-ai .ai-hdr { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
752
+ .fs-ai .ai-ico {
753
+ width: 32px; height: 32px;
754
+ background: var(--blue-dim);
755
+ border: 1px solid rgba(37,112,212,0.3);
756
+ border-radius: var(--radius-sm);
757
+ display: flex; align-items: center; justify-content: center;
758
+ font-size: 16px;
759
+ }
760
+ .fs-ai .ai-title { font-size: 14px; font-weight: 700; color: var(--blue); }
761
+ .fs-ai .ai-sub { font-size: 10px; color: var(--text-muted); }
762
+
763
+ /* ── Sidebar components (kept for compatibility) ── */
764
+ .fs-sb-header { display: flex; align-items: center; gap: 10px; padding: 16px 0 14px; border-bottom: 1px solid var(--border); margin-bottom: 16px; }
765
+ .fs-sb-logo { width: 34px; height: 34px; background: linear-gradient(135deg, var(--blue), var(--purple)); border-radius: 9px; display: flex; align-items: center; justify-content: center; font-size: 17px; flex-shrink: 0; }
766
+ .fs-sb-title { font-size: 14px; font-weight: 700; color: var(--text-primary); line-height: 1.2; }
767
+ .fs-sb-sub { font-size: 10px; color: var(--text-muted); }
768
+ .fs-sb-sec { font-size: 10px; text-transform: uppercase; letter-spacing: 0.8px; color: var(--text-muted); font-weight: 600; margin: 14px 0 6px; }
769
+
770
+ .fs-match-mini { background: var(--bg-active); border: 1px solid rgba(37,112,212,0.25); border-radius: var(--radius-md); padding: 10px 12px; margin: 10px 0; }
771
+ .fs-match-mini .home { color: var(--blue); font-weight: 700; font-size: 12px; }
772
+ .fs-match-mini .away { color: var(--red); font-weight: 700; font-size: 12px; }
773
+ .fs-match-mini .vs { color: var(--text-muted); font-size: 11px; padding: 0 5px; }
774
+ .fs-match-mini .meta { font-size: 10px; color: var(--text-muted); margin-top: 5px; }
775
+
776
+ .fs-sb-pills { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
777
+ .fs-sb-pill { display: inline-flex; align-items: center; gap: 3px; background: var(--bg-hover); border: 1px solid var(--border); border-radius: 20px; padding: 3px 9px; font-size: 11px; color: var(--text-secondary); }
778
+ .fs-sb-pill span { font-weight: 700; color: var(--text-primary); }
779
+
780
+ .fs-sb-welcome { text-align: center; padding: 20px 8px; color: var(--text-muted); font-size: 12px; }
781
+ .fs-sb-welcome .sw-icon { font-size: 28px; margin-bottom: 8px; }
782
+ .fs-sb-welcome .sw-text { line-height: 1.5; }
783
+
784
+ /* ── Date filter bar ── */
785
+ .fs-date-filter { display: flex; align-items: center; justify-content: flex-end; gap: 10px; margin-bottom: 8px; }
786
+ .fs-date-filter label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); }
787
+
788
+ /* ── Divider refinement ── */
789
+ hr { border-color: var(--border-light) !important; }
790
  </style>"""
791
 
792
 
src/src/utils.py CHANGED
@@ -1,6 +1,43 @@
1
  import pandas as pd
2
 
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  def sec(icon: str, title: str) -> None:
5
  import streamlit as st
6
  st.markdown(f'<div class="fs-sec"><span>{icon}</span> {title}</div>', unsafe_allow_html=True)
 
1
  import pandas as pd
2
 
3
 
4
+ def no_match_page() -> None:
5
+ """Estado vacío con CTA cuando no hay partido seleccionado. Llama st.stop()."""
6
+ import streamlit as st
7
+ st.markdown(
8
+ """
9
+ <div style="
10
+ display:flex;flex-direction:column;align-items:center;
11
+ justify-content:center;min-height:42vh;
12
+ text-align:center;padding:60px 20px;
13
+ ">
14
+ <div style="
15
+ width:80px;height:80px;border-radius:20px;
16
+ background:linear-gradient(135deg,rgba(37,112,212,0.12),rgba(124,77,204,0.10));
17
+ border:1px solid rgba(37,112,212,0.2);
18
+ display:flex;align-items:center;justify-content:center;
19
+ font-size:36px;margin-bottom:22px;
20
+ ">⚽</div>
21
+ <h2 style="
22
+ font-size:22px;font-weight:800;color:#0F172A;
23
+ margin:0 0 10px;letter-spacing:-0.03em;
24
+ ">Primero selecciona un partido</h2>
25
+ <p style="
26
+ font-size:14px;color:#94A3B8;max-width:340px;
27
+ margin:0 0 32px;line-height:1.6;
28
+ ">Ve a la página de <strong style="color:#475569">Inicio</strong>
29
+ para elegir un partido y comenzar el análisis.</p>
30
+ </div>
31
+ """,
32
+ unsafe_allow_html=True,
33
+ )
34
+ _, _c, _ = st.columns([3, 2, 3])
35
+ with _c:
36
+ if st.button("🏠 Ir a Inicio", use_container_width=True, type="primary"):
37
+ st.switch_page("pages/inicio.py")
38
+ st.stop()
39
+
40
+
41
  def sec(icon: str, title: str) -> None:
42
  import streamlit as st
43
  st.markdown(f'<div class="fs-sec"><span>{icon}</span> {title}</div>', unsafe_allow_html=True)
src/streamlit_app.py CHANGED
@@ -73,12 +73,13 @@ with _col_logo:
73
  )
74
 
75
  with _col_nav:
 
76
  _nc = st.columns(5)
77
  with _nc[0]: st.page_link(_p_inicio, label="Inicio", icon="🏠")
78
- with _nc[1]: st.page_link(_p_stats, label="Estadísticas", icon="📊")
79
- with _nc[2]: st.page_link(_p_comp, label="Comparación", icon="⚔️")
80
- with _nc[3]: st.page_link(_p_rf, label="Predicción RF", icon="🤖")
81
- with _nc[4]: st.page_link(_p_kelly, label="Cuotas & Kelly", icon="💰")
82
 
83
  st.markdown("<hr style='margin:4px 0 16px;border:none;border-top:1px solid var(--border)'>", unsafe_allow_html=True)
84
 
 
73
  )
74
 
75
  with _col_nav:
76
+ _has_sel = bool(st.session_state.get("_selected_match"))
77
  _nc = st.columns(5)
78
  with _nc[0]: st.page_link(_p_inicio, label="Inicio", icon="🏠")
79
+ with _nc[1]: st.page_link(_p_stats, label="Estadísticas", icon="📊" if _has_sel else "🔒")
80
+ with _nc[2]: st.page_link(_p_comp, label="Comparación", icon="⚔️" if _has_sel else "🔒")
81
+ with _nc[3]: st.page_link(_p_rf, label="Predicción RF", icon="🤖" if _has_sel else "🔒")
82
+ with _nc[4]: st.page_link(_p_kelly, label="Cuotas & Kelly", icon="💰" if _has_sel else "🔒")
83
 
84
  st.markdown("<hr style='margin:4px 0 16px;border:none;border-top:1px solid var(--border)'>", unsafe_allow_html=True)
85