Esvanth commited on
Commit
fd80df9
·
1 Parent(s): a7a00a1

Complete UI overhaul — professional dashboard design

Browse files
Files changed (1) hide show
  1. app.py +249 -109
app.py CHANGED
@@ -133,71 +133,185 @@ def _layout(h=420):
133
 
134
  st.markdown("""
135
  <style>
136
- [data-testid="stAppViewContainer"] { background:#f1f5f9; }
137
- .block-container { padding:1.2rem 1.8rem 3rem; }
138
 
139
- /* Tabs */
 
 
 
 
 
 
 
 
 
140
  .stTabs [data-baseweb="tab-list"] {
141
- background:#fff; border-radius:10px; padding:3px;
142
- box-shadow:0 1px 4px rgba(0,0,0,.06); border:1px solid #e2e8f0;
143
  }
144
  .stTabs [data-baseweb="tab"] {
145
- border-radius:7px; font-size:.83rem; font-weight:600; padding:7px 15px; color:#64748b;
 
 
 
 
 
146
  }
147
- .stTabs [aria-selected="true"] { background:#0f172a !important; color:#fff !important; }
148
 
149
- /* Cards */
150
- .card {
151
- background:#fff; border-radius:10px; padding:16px 20px;
152
- border:1px solid #f1f5f9; box-shadow:0 1px 4px rgba(0,0,0,.05); margin-bottom:12px;
 
153
  }
154
- .card-blue { border-left:4px solid #2563eb; }
155
- .card-amber { border-left:4px solid #d97706; }
156
- .card-green { border-left:4px solid #059669; }
157
- .card-navy { border-left:4px solid #0f172a; }
158
-
159
- /* Terminal */
160
- .term { border-radius:10px; overflow:hidden; border:1px solid #1e293b; margin:6px 0; }
161
- .term-top { background:#1e293b; padding:7px 12px; display:flex; gap:5px; align-items:center; }
162
- .dot { width:9px; height:9px; border-radius:50%; }
163
- .term-body {
164
- background:#0f172a; padding:12px 16px;
165
- font-family:'Courier New',monospace; font-size:.78rem;
166
- color:#94a3b8; white-space:pre-wrap; line-height:1.6;
167
- max-height:300px; overflow-y:auto;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
- /* Insight boxes */
171
  .insight {
172
- background:#ecfdf5; border-left:4px solid #059669;
173
- border-radius:0 8px 8px 0; padding:12px 16px;
174
- color:#064e3b; font-size:.85rem; line-height:1.6; margin:8px 0;
 
 
 
 
 
 
 
175
  }
 
 
176
  .warn-box {
177
- background:#fffbeb; border-left:4px solid #d97706;
178
- border-radius:0 8px 8px 0; padding:12px 16px;
179
- color:#78350f; font-size:.85rem; line-height:1.6; margin:8px 0;
 
 
 
 
 
 
 
180
  }
181
 
182
- /* Legend row */
183
- .leg { display:flex; gap:14px; flex-wrap:wrap; margin:6px 0; }
184
- .li { display:flex; align-items:center; gap:5px; font-size:.78rem; color:#475569; }
185
- .ld { width:11px; height:11px; border-radius:50%; flex-shrink:0; }
 
 
 
 
 
 
186
 
187
- /* Section label */
 
 
 
 
 
188
  .slabel {
189
- font-size:.8rem; font-weight:700; color:#64748b;
190
- text-transform:uppercase; letter-spacing:.05em; margin-bottom:6px;
 
 
 
 
 
 
 
191
  }
192
 
193
- /* Metrics */
194
  div[data-testid="metric-container"] {
195
- background:#fff; border-radius:8px; padding:12px 16px;
196
- border:1px solid #e2e8f0; box-shadow:0 1px 3px rgba(0,0,0,.04);
197
  }
 
 
198
 
199
- /* Sidebar */
200
- [data-testid="stSidebar"] { background:#fff; border-right:1px solid #e2e8f0; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  </style>
202
  """, unsafe_allow_html=True)
203
 
@@ -206,45 +320,50 @@ div[data-testid="metric-container"] {
206
  # ══════════════════════════════════════════════════════════════════════════════
207
  with st.sidebar:
208
  st.markdown("""
209
- <div style='text-align:center;padding:8px 0 16px;'>
210
- <div style='font-size:2rem;'>🛒</div>
211
- <div style='font-weight:800;font-size:1.05rem;color:#0f172a;'>EcoCart AI</div>
 
212
  </div>""", unsafe_allow_html=True)
213
- st.divider()
214
-
215
- st.markdown("**How to navigate**")
216
- for n, t in [("1","Pick a task tab above"),("2","Tasks 2, 3 and 5 — press Run first"),
217
- ("3","Tasks 1 and 3 — press Play to animate"),("4","Task 6 — drag sliders to explore")]:
218
- st.markdown(f"""<div style='display:flex;gap:8px;align-items:center;margin:5px 0;'>
219
- <div style='background:#0f172a;color:#fff;width:18px;height:18px;border-radius:50%;
220
- font-size:.68rem;font-weight:800;display:flex;align-items:center;
221
- justify-content:center;flex-shrink:0;'>{n}</div>
222
- <span style='font-size:.82rem;color:#1e293b;'>{t}</span></div>""",
223
- unsafe_allow_html=True)
224
- st.divider()
225
 
 
 
 
 
 
 
 
 
 
 
226
  t2_done = st.session_state.get("t2_done", False)
227
  t3_done = st.session_state.get("t3_done", False)
228
  t5_done = st.session_state.get("t5_done", False)
229
- st.markdown("**Task status**")
230
- for lbl, done in [("Task 2 — Bias", t2_done),
231
- ("Task 3 — Routes", t3_done),
232
- ("Task 5 — Forecast",t5_done)]:
233
- icon = "✅" if done else "○"
234
- col = GREEN if done else "#94a3b8"
235
- st.markdown(f"<div style='color:{col};font-size:.82rem;margin:3px 0;'>{icon} {lbl}</div>",
 
236
  unsafe_allow_html=True)
237
 
238
- st.divider()
239
- st.caption("Every number and chart comes from running the actual Python scripts — nothing is hardcoded except Task 4 benchmark tables and Task 6 estimates.")
240
 
241
  # ── header ────────────────────────────────────────────────────────────────────
242
  st.markdown("""
243
- <div style='background:linear-gradient(135deg,#0f172a,#1e3a5f);border-radius:12px;
244
- padding:20px 26px;margin-bottom:18px;'>
245
- <div style='color:#f8fafc;font-size:1.5rem;font-weight:800;margin-bottom:2px;'>EcoCart AI System</div>
246
- <div style='color:#94a3b8;font-size:.85rem;'>
247
- Six AI tasks, one logistics problem &nbsp;·&nbsp; Every result runs from real Python scripts
 
 
 
 
 
248
  </div>
249
  </div>""", unsafe_allow_html=True)
250
 
@@ -262,11 +381,15 @@ T1, T2, T3, T4, T5, T6 = st.tabs([
262
  # ══════════════════════════════════════════════════════════════════════════════
263
  with T1:
264
  st.markdown("""
265
- <div class='card card-navy'>
266
- Three AI agents tackle the same 9-stop delivery problem — but each thinks completely differently.
267
- The <b>Reactive</b> agent rushes to the nearest stop. The <b>Goal-Based</b> agent plans the
268
- whole route before leaving. The <b>Utility-Based</b> agent chases high-priority stops first.
269
- Same map, same stops very different outcomes. Press <b>Play</b> to watch or drag the slider to step through.
 
 
 
 
270
  </div>""", unsafe_allow_html=True)
271
 
272
  # ── route data ────────────────────────────────────────────────────────────
@@ -499,11 +622,15 @@ with T1:
499
  # ══════════════════════════════════════════════════════════════════════════════
500
  with T2:
501
  st.markdown("""
502
- <div class='card card-amber'>
503
- The K-Means model was quietly being unfair — not one rural customer made it to High Value.
504
- Zero. This task figures out why that happened (the data was biased from the start) and applies
505
- a three-step fix. The fairness test used is <b>Disparate Impact</b>: if rural customers are
506
- less than 80% as likely as urban ones to reach High Value, the model fails. Press <b>Run</b> to see the before and after.
 
 
 
 
507
  </div>""", unsafe_allow_html=True)
508
 
509
  run_t2 = st.button("▶ Run Task 2 — Segmentation & Bias Fix",
@@ -556,11 +683,15 @@ with T2:
556
  # ══════════════════════════════════════════════════════════════════════════════
557
  with T3:
558
  st.markdown("""
559
- <div class='card card-blue'>
560
- Four algorithms, one problem: find the best delivery route on a custom 20-node map (10 urban, 10 rural stops).
561
- Each algorithm searches differently — some are optimal, one is not, and the best one does it
562
- with the fewest steps. Press <b>Run</b> to see the benchmark results, then use the
563
- <b>interactive replay</b> below to watch any algorithm explore the network node by node.
 
 
 
 
564
  </div>""", unsafe_allow_html=True)
565
 
566
  run_t3 = st.button("▶ Run Task 3 — Route Optimisation",
@@ -612,10 +743,8 @@ with T3:
612
 
613
  # ── interactive route replay ──────────────────────────────────────────────
614
  st.markdown("<br>", unsafe_allow_html=True)
615
- st.markdown("""
616
- <div style='font-weight:700;font-size:1rem;color:#0f172a;margin-bottom:10px;'>
617
- Try it yourself — pick any start, end, and algorithm, then replay the search step by step
618
- </div>""", unsafe_allow_html=True)
619
 
620
  NODES_R = {
621
  "U1":(1.0,1.0,"urban"), "U2":(2.0,1.5,"urban"), "U3":(3.0,1.0,"urban"),
@@ -854,12 +983,15 @@ with T3:
854
  # ═══════════════════════════════════════════��══════════════════════════════════
855
  with T4:
856
  st.markdown("""
857
- <div class='card card-navy'>
858
- A* and IDA* always find the same shortest path — the question is <i>how</i> they get there.
859
- A* keeps a record of every node it has visited (fast, but memory grows). IDA* forgets everything
860
- and re-searches from scratch each pass, tightening its cost limit each time (slower, but uses
861
- almost no memory). These benchmarks run <b>10 routes × 20 timing runs</b> to see which wins on
862
- EcoCart's network and when IDA* would be the better choice.
 
 
 
863
  </div>""", unsafe_allow_html=True)
864
 
865
  urban_data=[
@@ -918,11 +1050,15 @@ with T4:
918
  # ══════════════════════════════════════════════════════════════════════════════
919
  with T5:
920
  st.markdown("""
921
- <div class='card card-green'>
922
- Can a simple model beat a complex one? Two models are trained on <b>730 days</b> of EcoCart
923
- sales history — Linear Regression (transparent, fast) and Random Forest (200 decision trees,
924
- captures non-linear patterns). Both are then tested on <b>140 days they have never seen</b>.
925
- Press <b>Run</b> to find out which wins, and why the result might surprise you.
 
 
 
 
926
  </div>""", unsafe_allow_html=True)
927
 
928
  run_t5 = st.button("▶ Run Task 5 — Demand Forecasting",
@@ -981,12 +1117,16 @@ with T5:
981
  # ══════════════════════════════════════════════════════════════════════════════
982
  with T6:
983
  st.markdown("""
984
- <div class='card card-amber'>
985
- What does all of this actually save EcoCart? This tab turns the technical results into
986
- a financial model — the savings from A* routing, the rural revenue unlocked by fixing the
987
- segmentation bias, and the CO₂ avoided. <b>None of these numbers are real</b> — they are
988
- estimates based on typical fleet operations. Use the sliders on the left to plug in
989
- EcoCart's actual numbers and see how the ROI changes.
 
 
 
 
990
  </div>""", unsafe_allow_html=True)
991
 
992
  ctrl, main = st.columns([1, 3])
 
133
 
134
  st.markdown("""
135
  <style>
136
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
 
137
 
138
+ *, *::before, *::after { font-family: 'Inter', sans-serif !important; box-sizing: border-box; }
139
+
140
+ [data-testid="stAppViewContainer"] {
141
+ background: linear-gradient(160deg, #eef2ff 0%, #f8fafc 40%, #f0fdf4 100%) !important;
142
+ min-height: 100vh;
143
+ }
144
+ .block-container { padding: 1rem 1.4rem 3rem !important; max-width: 1200px; }
145
+ #MainMenu, footer { visibility: hidden; }
146
+
147
+ /* ── Tabs ── */
148
  .stTabs [data-baseweb="tab-list"] {
149
+ background: #fff; border-radius: 14px; padding: 5px;
150
+ box-shadow: 0 2px 16px rgba(0,0,0,.07); border: 1px solid #e8edf5; gap: 3px;
151
  }
152
  .stTabs [data-baseweb="tab"] {
153
+ border-radius: 10px; font-size: .78rem; font-weight: 600;
154
+ padding: 8px 13px; color: #64748b; transition: all .18s ease;
155
+ }
156
+ .stTabs [aria-selected="true"] {
157
+ background: linear-gradient(135deg, #0f172a 0%, #1e3a5f 100%) !important;
158
+ color: #fff !important; box-shadow: 0 3px 10px rgba(15,23,42,.25);
159
  }
 
160
 
161
+ /* ── Hero ── */
162
+ .hero {
163
+ background: linear-gradient(135deg, #0f172a 0%, #162244 50%, #0f1f3d 100%);
164
+ border-radius: 18px; padding: 26px 30px 24px; margin-bottom: 18px;
165
+ position: relative; overflow: hidden;
166
  }
167
+ .hero::before {
168
+ content: ''; position: absolute; top: -60px; right: -40px;
169
+ width: 280px; height: 280px;
170
+ background: radial-gradient(circle, rgba(99,102,241,.18) 0%, transparent 68%);
171
+ border-radius: 50%;
172
+ }
173
+ .hero::after {
174
+ content: ''; position: absolute; bottom: -60px; left: 30%;
175
+ width: 220px; height: 220px;
176
+ background: radial-gradient(circle, rgba(16,185,129,.12) 0%, transparent 68%);
177
+ border-radius: 50%;
178
+ }
179
+ .hero-badge {
180
+ display: inline-flex; align-items: center; gap: 6px;
181
+ background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.12);
182
+ color: #94a3b8; font-size: .68rem; font-weight: 700;
183
+ padding: 4px 12px; border-radius: 20px; margin-bottom: 14px;
184
+ letter-spacing: .06em; text-transform: uppercase;
185
+ }
186
+ .hero-title {
187
+ color: #f1f5f9; font-size: 1.75rem; font-weight: 900;
188
+ margin: 0 0 5px; letter-spacing: -.03em; line-height: 1.15;
189
+ }
190
+ .hero-sub { color: #64748b; font-size: .84rem; margin-bottom: 22px; line-height: 1.5; }
191
+ .hero-stats { display: flex; gap: 0; flex-wrap: wrap; }
192
+ .hero-stat {
193
+ padding: 10px 20px; border-right: 1px solid rgba(255,255,255,.07);
194
+ text-align: center;
195
  }
196
+ .hero-stat:first-child { padding-left: 0; }
197
+ .hero-stat:last-child { border-right: none; }
198
+ .hero-stat-num {
199
+ color: #fff; font-size: 1.35rem; font-weight: 900;
200
+ letter-spacing: -.02em; line-height: 1; display: block;
201
+ }
202
+ .hero-stat-lbl {
203
+ color: #475569; font-size: .65rem; font-weight: 600;
204
+ text-transform: uppercase; letter-spacing: .07em; margin-top: 4px; display: block;
205
+ }
206
+
207
+ /* ── Task intro cards ── */
208
+ .task-card {
209
+ background: #fff; border-radius: 14px; padding: 18px 20px;
210
+ margin-bottom: 16px; border: 1px solid #e8edf5;
211
+ box-shadow: 0 2px 14px rgba(0,0,0,.045);
212
+ display: flex; gap: 16px; align-items: flex-start;
213
+ }
214
+ .task-icon {
215
+ width: 46px; height: 46px; border-radius: 12px; flex-shrink: 0;
216
+ display: flex; align-items: center; justify-content: center; font-size: 1.4rem;
217
+ }
218
+ .task-title { font-size: .92rem; font-weight: 700; color: #0f172a; margin-bottom: 5px; }
219
+ .task-desc { font-size: .82rem; color: #475569; line-height: 1.65; }
220
 
221
+ /* ── Insight ── */
222
  .insight {
223
+ background: linear-gradient(135deg, #f0fdf4, #ecfdf5);
224
+ border: 1px solid #bbf7d0; border-radius: 12px;
225
+ padding: 14px 16px 14px 52px; position: relative;
226
+ color: #14532d; font-size: .83rem; line-height: 1.7; margin: 10px 0;
227
+ }
228
+ .insight::before {
229
+ content: '✓'; position: absolute; left: 14px; top: 14px;
230
+ width: 26px; height: 26px; background: #059669; border-radius: 8px;
231
+ color: #fff; font-size: .8rem; font-weight: 800;
232
+ display: flex; align-items: center; justify-content: center;
233
  }
234
+
235
+ /* ── Warn ── */
236
  .warn-box {
237
+ background: linear-gradient(135deg, #fffbeb, #fef9c3);
238
+ border: 1px solid #fde68a; border-radius: 12px;
239
+ padding: 14px 16px 14px 52px; position: relative;
240
+ color: #78350f; font-size: .83rem; line-height: 1.7; margin: 10px 0;
241
+ }
242
+ .warn-box::before {
243
+ content: '!'; position: absolute; left: 14px; top: 14px;
244
+ width: 26px; height: 26px; background: #d97706; border-radius: 8px;
245
+ color: #fff; font-size: .9rem; font-weight: 900;
246
+ display: flex; align-items: center; justify-content: center;
247
  }
248
 
249
+ /* ── Terminal ── */
250
+ .term { border-radius: 12px; overflow: hidden; border: 1px solid #1e293b; margin: 8px 0; }
251
+ .term-top { background: #1e293b; padding: 8px 14px; display: flex; gap: 6px; align-items: center; }
252
+ .dot { width: 10px; height: 10px; border-radius: 50%; }
253
+ .term-body {
254
+ background: #0f172a; padding: 14px 18px;
255
+ font-family: 'Courier New', monospace; font-size: .77rem;
256
+ color: #94a3b8; white-space: pre-wrap; line-height: 1.7;
257
+ max-height: 280px; overflow-y: auto;
258
+ }
259
 
260
+ /* ── Legend ── */
261
+ .leg { display: flex; gap: 12px; flex-wrap: wrap; margin: 8px 0; }
262
+ .li { display: flex; align-items: center; gap: 5px; font-size: .76rem; color: #475569; font-weight: 500; }
263
+ .ld { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }
264
+
265
+ /* ── Section label ── */
266
  .slabel {
267
+ font-size: .7rem; font-weight: 700; color: #94a3b8;
268
+ text-transform: uppercase; letter-spacing: .08em; margin-bottom: 8px;
269
+ }
270
+
271
+ /* ── Divider heading ── */
272
+ .sec-head {
273
+ font-size: .92rem; font-weight: 700; color: #0f172a;
274
+ margin: 18px 0 10px; padding-bottom: 8px;
275
+ border-bottom: 2px solid #e8edf5; letter-spacing: -.01em;
276
  }
277
 
278
+ /* ── Metrics ── */
279
  div[data-testid="metric-container"] {
280
+ background: #fff; border-radius: 12px; padding: 14px 18px;
281
+ border: 1px solid #e8edf5; box-shadow: 0 2px 8px rgba(0,0,0,.04);
282
  }
283
+ div[data-testid="metric-container"] label { font-size: .72rem !important; font-weight: 600 !important; color: #64748b !important; }
284
+ div[data-testid="metric-container"] [data-testid="stMetricValue"] { font-size: 1.25rem !important; font-weight: 800 !important; color: #0f172a !important; }
285
 
286
+ /* ── Sidebar ── */
287
+ [data-testid="stSidebar"] {
288
+ background: #fff !important; border-right: 1px solid #e8edf5 !important;
289
+ }
290
+ .sb-brand {
291
+ text-align: center; padding: 4px 0 18px;
292
+ border-bottom: 1px solid #f1f5f9; margin-bottom: 18px;
293
+ }
294
+ .sb-icon { font-size: 2.2rem; line-height: 1; margin-bottom: 6px; }
295
+ .sb-name { font-weight: 900; font-size: 1rem; color: #0f172a; }
296
+ .sb-sub { font-size: .7rem; color: #94a3b8; margin-top: 2px; font-weight: 500; }
297
+ .sb-section { font-size: .68rem; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: .08em; margin: 14px 0 8px; }
298
+ .sb-step {
299
+ display: flex; align-items: center; gap: 10px;
300
+ padding: 8px 10px; border-radius: 10px; margin-bottom: 4px;
301
+ background: #f8fafc; border: 1px solid #f1f5f9;
302
+ }
303
+ .sb-num {
304
+ width: 22px; height: 22px; background: #0f172a; color: #fff;
305
+ border-radius: 6px; font-size: .65rem; font-weight: 800;
306
+ display: flex; align-items: center; justify-content: center; flex-shrink: 0;
307
+ }
308
+ .sb-step-txt { font-size: .8rem; color: #334155; font-weight: 500; }
309
+ .sb-status-row {
310
+ display: flex; align-items: center; gap: 8px;
311
+ padding: 7px 10px; border-radius: 10px; margin-bottom: 4px; font-size: .8rem; font-weight: 600;
312
+ }
313
+ .sb-done { background: #f0fdf4; color: #166534; border: 1px solid #bbf7d0; }
314
+ .sb-pending { background: #f8fafc; color: #94a3b8; border: 1px solid #e8edf5; }
315
  </style>
316
  """, unsafe_allow_html=True)
317
 
 
320
  # ══════════════════════════════════════════════════════════════════════════════
321
  with st.sidebar:
322
  st.markdown("""
323
+ <div class='sb-brand'>
324
+ <div class='sb-icon'>🛒</div>
325
+ <div class='sb-name'>EcoCart AI</div>
326
+ <div class='sb-sub'>Esvanth Mohankumar · 24311073</div>
327
  </div>""", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
328
 
329
+ st.markdown("<div class='sb-section'>How to use</div>", unsafe_allow_html=True)
330
+ for n, t in [("1","Pick a task tab above"),
331
+ ("2","Tasks 2, 3, 5 — press Run"),
332
+ ("3","Tasks 1 & 3 — press Play"),
333
+ ("4","Task 6 — adjust the sliders")]:
334
+ st.markdown(f"""<div class='sb-step'>
335
+ <div class='sb-num'>{n}</div>
336
+ <span class='sb-step-txt'>{t}</span></div>""", unsafe_allow_html=True)
337
+
338
+ st.markdown("<div class='sb-section'>Task progress</div>", unsafe_allow_html=True)
339
  t2_done = st.session_state.get("t2_done", False)
340
  t3_done = st.session_state.get("t3_done", False)
341
  t5_done = st.session_state.get("t5_done", False)
342
+ for lbl, icon, done in [
343
+ ("Task 2 — Bias", "⚖️", t2_done),
344
+ ("Task 3 — Routes", "🗺️", t3_done),
345
+ ("Task 5 — Forecast", "📈", t5_done),
346
+ ]:
347
+ cls = "sb-done" if done else "sb-pending"
348
+ mark = "✓" if done else "·"
349
+ st.markdown(f"<div class='sb-status-row {cls}'>{icon} {lbl} <span style='margin-left:auto'>{mark}</span></div>",
350
  unsafe_allow_html=True)
351
 
352
+ st.markdown("<br>", unsafe_allow_html=True)
353
+ st.caption("NCI · MSc AI · Foundations of AI 2026")
354
 
355
  # ── header ────────────────────────────────────────────────────────────────────
356
  st.markdown("""
357
+ <div class='hero'>
358
+ <div class='hero-badge'>🎓 NCI &nbsp;·&nbsp; MSc Artificial Intelligence &nbsp;·&nbsp; Foundations of AI 2026</div>
359
+ <div class='hero-title'>EcoCart AI System</div>
360
+ <div class='hero-sub'>Six AI tasks built to solve one real logistics problem — every chart and number runs from actual Python scripts</div>
361
+ <div class='hero-stats'>
362
+ <div class='hero-stat'><span class='hero-stat-num'>6</span><span class='hero-stat-lbl'>Tasks</span></div>
363
+ <div class='hero-stat'><span class='hero-stat-num'>4</span><span class='hero-stat-lbl'>Algorithms</span></div>
364
+ <div class='hero-stat'><span class='hero-stat-num'>730</span><span class='hero-stat-lbl'>Days Data</span></div>
365
+ <div class='hero-stat'><span class='hero-stat-num'>20</span><span class='hero-stat-lbl'>Node Network</span></div>
366
+ <div class='hero-stat'><span class='hero-stat-num'>0.847</span><span class='hero-stat-lbl'>DI Score</span></div>
367
  </div>
368
  </div>""", unsafe_allow_html=True)
369
 
 
381
  # ══════════════════════════════════════════════════════════════════════════════
382
  with T1:
383
  st.markdown("""
384
+ <div class='task-card'>
385
+ <div class='task-icon' style='background:#eef2ff;'>🤖</div>
386
+ <div>
387
+ <div class='task-title'>Three agents, one delivery map completely different decisions</div>
388
+ <div class='task-desc'>Reactive rushes to the nearest stop. Goal-Based plans the full route before
389
+ leaving using 2-opt optimisation. Utility-Based scores stops by urgency ÷ distance and chases
390
+ high-priority ones first. Same 9-stop map, very different outcomes.
391
+ Press <b>Play</b> to animate or drag the slider to step through stop by stop.</div>
392
+ </div>
393
  </div>""", unsafe_allow_html=True)
394
 
395
  # ── route data ────────────────────────────────────────────────────────────
 
622
  # ══════════════════════════════════════════════════════════════════════════════
623
  with T2:
624
  st.markdown("""
625
+ <div class='task-card'>
626
+ <div class='task-icon' style='background:#fffbeb;'>⚖️</div>
627
+ <div>
628
+ <div class='task-title'>The model was being unfair and nobody noticed until now</div>
629
+ <div class='task-desc'>Not one rural customer made it to High Value. Zero. The K-Means clustering
630
+ was biased from the start because EcoCart launched in cities first. This task measures the bias
631
+ using <b>Disparate Impact</b> (threshold ≥ 0.80) and applies a three-step fix: oversample rural
632
+ customers, adjust for delivery costs, correct for order batching. Press <b>Run</b> to see before and after.</div>
633
+ </div>
634
  </div>""", unsafe_allow_html=True)
635
 
636
  run_t2 = st.button("▶ Run Task 2 — Segmentation & Bias Fix",
 
683
  # ══════════════════════════════════════════════════════════════════════════════
684
  with T3:
685
  st.markdown("""
686
+ <div class='task-card'>
687
+ <div class='task-icon' style='background:#eff6ff;'>🗺️</div>
688
+ <div>
689
+ <div class='task-title'>Four algorithms, one delivery network which one wins?</div>
690
+ <div class='task-desc'>BFS, DFS, A*, and IDA* all search for the shortest route on a
691
+ custom-built 20-node urban/rural network. Some find the optimal path, one doesn't.
692
+ The best does it with the fewest node expansions. Press <b>Run</b> for full results,
693
+ then use the <b>live replay</b> below to watch any algorithm search the network step by step.</div>
694
+ </div>
695
  </div>""", unsafe_allow_html=True)
696
 
697
  run_t3 = st.button("▶ Run Task 3 — Route Optimisation",
 
743
 
744
  # ── interactive route replay ──────────────────────────────────────────────
745
  st.markdown("<br>", unsafe_allow_html=True)
746
+ st.markdown("<div class='sec-head'>Live search replay — pick start, end and algorithm, watch it think</div>",
747
+ unsafe_allow_html=True)
 
 
748
 
749
  NODES_R = {
750
  "U1":(1.0,1.0,"urban"), "U2":(2.0,1.5,"urban"), "U3":(3.0,1.0,"urban"),
 
983
  # ═══════════════════════════════════════════��══════════════════════════════════
984
  with T4:
985
  st.markdown("""
986
+ <div class='task-card'>
987
+ <div class='task-icon' style='background:#f0f4ff;'>📊</div>
988
+ <div>
989
+ <div class='task-title'>Same shortest path, completely different strategies</div>
990
+ <div class='task-desc'>A* remembers every node it visits fast, but memory grows with the network.
991
+ IDA* forgets and re-searches from scratch each pass, tightening its cost bound each time — slower
992
+ but uses almost no memory. This benchmark runs <b>10 routes × 20 timing runs</b> across urban
993
+ and rural pairs to find out which algorithm is right for EcoCart — and at what scale that answer changes.</div>
994
+ </div>
995
  </div>""", unsafe_allow_html=True)
996
 
997
  urban_data=[
 
1050
  # ══════════════════════════════════════════════════════════════════════════════
1051
  with T5:
1052
  st.markdown("""
1053
+ <div class='task-card'>
1054
+ <div class='task-icon' style='background:#f0fdf4;'>📈</div>
1055
+ <div>
1056
+ <div class='task-title'>Can a simple model beat 200 decision trees?</div>
1057
+ <div class='task-desc'>Linear Regression (fast, transparent) goes head-to-head against
1058
+ Random Forest (200 trees, non-linear patterns). Both train on <b>730 days</b> of EcoCart
1059
+ sales history and are tested blind on <b>140 days they have never seen</b>.
1060
+ Press <b>Run</b> to see which model wins on MAE, RMSE, R², and MAPE — and why the result is surprising.</div>
1061
+ </div>
1062
  </div>""", unsafe_allow_html=True)
1063
 
1064
  run_t5 = st.button("▶ Run Task 5 — Demand Forecasting",
 
1117
  # ══════════════════════════════════════════════════════════════════════════════
1118
  with T6:
1119
  st.markdown("""
1120
+ <div class='task-card'>
1121
+ <div class='task-icon' style='background:#fffbeb;'>💼</div>
1122
+ <div>
1123
+ <div class='task-title'>What does all of this actually save the business?</div>
1124
+ <div class='task-desc'>This tab turns the technical results into a live financial model —
1125
+ savings from A* route optimisation, revenue unlocked by fixing the segmentation bias, and
1126
+ CO₂ avoided. <b>All numbers are estimates</b> based on assumed fleet inputs.
1127
+ Use the sliders on the left to model EcoCart's real fleet size, fuel costs, and wages —
1128
+ the ROI and payback period update instantly.</div>
1129
+ </div>
1130
  </div>""", unsafe_allow_html=True)
1131
 
1132
  ctrl, main = st.columns([1, 3])