kaveh commited on
Commit
8e23320
·
1 Parent(s): 0fd41bf

added dark theme support

Browse files
S2FApp/.streamlit/config.toml CHANGED
@@ -6,12 +6,36 @@ enableXsrfProtection = false
6
  # [client]
7
  # toolbarMode = "minimal"
8
 
 
 
 
9
  [theme]
10
  primaryColor = "#0d9488"
 
 
 
11
  backgroundColor = "#ffffff"
12
  secondaryBackgroundColor = "#f8fafc"
13
  textColor = "#1e293b"
14
- font = "sans serif"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  [logger]
17
  level = "error"
 
6
  # [client]
7
  # toolbarMode = "minimal"
8
 
9
+ # Shared accent. Light and dark palettes live in [theme.light] / [theme.dark]
10
+ # (Streamlit ≥1.51). With both set, the app follows the system setting and
11
+ # can still be overridden from the app menu (Settings).
12
  [theme]
13
  primaryColor = "#0d9488"
14
+ font = "sans serif"
15
+
16
+ [theme.light]
17
  backgroundColor = "#ffffff"
18
  secondaryBackgroundColor = "#f8fafc"
19
  textColor = "#1e293b"
20
+ borderColor = "#e2e8f0"
21
+
22
+ [theme.light.sidebar]
23
+ backgroundColor = "#f8fafc"
24
+ secondaryBackgroundColor = "#ffffff"
25
+ textColor = "#1e293b"
26
+
27
+ [theme.dark]
28
+ backgroundColor = "#0b1220"
29
+ secondaryBackgroundColor = "#111827"
30
+ textColor = "#e2e8f0"
31
+ borderColor = "#334155"
32
+ primaryColor = "#2dd4bf"
33
+
34
+ [theme.dark.sidebar]
35
+ backgroundColor = "#111827"
36
+ secondaryBackgroundColor = "#0b1220"
37
+ textColor = "#e2e8f0"
38
+ primaryColor = "#2dd4bf"
39
 
40
  [logger]
41
  level = "error"
S2FApp/requirements-docker.txt CHANGED
@@ -4,7 +4,7 @@ numpy>=1.20.0,<2.0
4
  opencv-python>=4.5.0
5
  scipy>=1.7.0
6
  scikit-image>=0.19.0,<0.26
7
- streamlit>=1.29.0
8
  streamlit-drawable-canvas-fix>=0.9.8
9
  matplotlib>=3.5.0
10
  Pillow>=9.0.0
 
4
  opencv-python>=4.5.0
5
  scipy>=1.7.0
6
  scikit-image>=0.19.0,<0.26
7
+ streamlit>=1.51.0
8
  streamlit-drawable-canvas-fix>=0.9.8
9
  matplotlib>=3.5.0
10
  Pillow>=9.0.0
S2FApp/requirements.txt CHANGED
@@ -5,7 +5,7 @@ numpy>=1.20.0
5
  opencv-python>=4.5.0
6
  scipy>=1.7.0
7
  scikit-image>=0.19.0
8
- streamlit>=1.29.0
9
  streamlit-drawable-canvas-fix>=0.9.8
10
  matplotlib>=3.5.0
11
  Pillow>=9.0.0
 
5
  opencv-python>=4.5.0
6
  scipy>=1.7.0
7
  scikit-image>=0.19.0
8
+ streamlit>=1.51.0
9
  streamlit-drawable-canvas-fix>=0.9.8
10
  matplotlib>=3.5.0
11
  Pillow>=9.0.0
S2FApp/static/s2f_styles.css CHANGED
@@ -6,13 +6,43 @@
6
  --s2f-primary-rgb: 13, 148, 136;
7
  /* Space for Streamlit’s fixed top toolbar (Deploy / menu). Set when header exists (see below). */
8
  --s2f-streamlit-header-offset: 0px;
9
- /* Sidebar grouped blocks: faint surface, no heavy chrome */
10
- --s2f-sidebar-panel-bg: rgba(255, 255, 255, 0.42);
11
- --s2f-sidebar-panel-border: rgba(203, 213, 225, 0.34);
12
  /* Match Streamlit’s sidebar horizontal padding so panels can full-bleed the track */
13
  --s2f-sidebar-inline-inset: 1rem;
14
  --s2f-sidebar-width: 360px;
15
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  /* Streamlit renders a fixed header; without this offset, the sidebar and first main block sit underneath it. */
17
  .stApp:has(header[data-testid="stHeader"]) {
18
  --s2f-streamlit-header-offset: 2rem;
@@ -42,9 +72,9 @@ html, body {
42
  overflow: hidden !important;
43
  display: flex !important;
44
  flex-direction: column !important;
45
- background-color: #fafbfc !important;
46
  background-image:
47
- radial-gradient(circle at 1px 1px, rgba(148, 163, 184, 0.12) 1px, transparent 0);
48
  background-size: 28px 28px !important;
49
  background-position: 0 0, 0 0 !important;
50
  }
@@ -64,8 +94,8 @@ html, body {
64
  right: 0;
65
  bottom: 0;
66
  background:
67
- radial-gradient(ellipse 80% 50% at 70% -20%, rgba(var(--s2f-primary-rgb), 0.06) 0%, transparent 50%),
68
- radial-gradient(ellipse 60% 40% at 0% 100%, rgba(var(--s2f-primary-rgb), 0.04) 0%, transparent 50%);
69
  pointer-events: none;
70
  z-index: 0;
71
  }
@@ -143,16 +173,16 @@ section[data-testid="stSidebar"] {
143
  overflow-y: auto !important;
144
  scrollbar-gutter: stable !important;
145
  background:
146
- linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%),
147
  /* Subtle vertical rhythm */
148
  repeating-linear-gradient(
149
  90deg,
150
  transparent,
151
  transparent 89px,
152
- rgba(203, 213, 225, 0.25) 89px,
153
- rgba(203, 213, 225, 0.25) 90px
154
  ) !important;
155
- border-right: 1px solid #e2e8f0 !important;
156
  }
157
  @media (max-width: 768px) {
158
  section[data-testid="stSidebar"] { width: 100% !important; max-width: 100% !important; }
@@ -165,7 +195,7 @@ section[data-testid="stSidebar"] [data-testid="stWidgetLabel"],
165
  section[data-testid="stSidebar"] [data-testid="stWidgetLabel"] p {
166
  font-size: 0.9rem !important;
167
  font-weight: 500 !important;
168
- color: #334155 !important;
169
  }
170
  .sidebar-brand {
171
  display: flex;
@@ -177,7 +207,7 @@ section[data-testid="stSidebar"] [data-testid="stWidgetLabel"] p {
177
  .sidebar-brand .brand-text {
178
  font-size: 1.1rem;
179
  font-weight: 700;
180
- color: var(--s2f-primary-dark);
181
  letter-spacing: -0.01em;
182
  }
183
 
@@ -213,8 +243,8 @@ section[data-testid="stSidebar"] .st-key-s2f_grp_force > div {
213
 
214
  /* === Metric cards === */
215
  [data-testid="stMetric"] {
216
- background: linear-gradient(145deg, #ffffff 0%, #f8fafc 100%);
217
- border: 1px solid #e2e8f0;
218
  border-radius: 12px;
219
  padding: 0.85rem 1rem !important;
220
  box-shadow: 0 1px 4px rgba(0,0,0,0.06);
@@ -227,14 +257,14 @@ section[data-testid="stSidebar"] .st-key-s2f_grp_force > div {
227
  [data-testid="stMetric"] label {
228
  font-size: 0.75rem !important;
229
  font-weight: 600 !important;
230
- color: #64748b !important;
231
  text-transform: uppercase;
232
  letter-spacing: 0.04em;
233
  }
234
  [data-testid="stMetric"] [data-testid="stMetricValue"] {
235
  font-size: 1.25rem !important;
236
  font-weight: 700 !important;
237
- color: var(--s2f-primary-dark) !important;
238
  }
239
 
240
  /* === Buttons === */
@@ -255,7 +285,7 @@ section[data-testid="stSidebar"] .st-key-s2f_grp_force > div {
255
  .stButton > button:not([kind="primary"]) {
256
  border-radius: 10px !important;
257
  font-weight: 500 !important;
258
- border: 1px solid #cbd5e1 !important;
259
  transition: all 0.2s ease !important;
260
  }
261
  .stButton > button:not([kind="primary"]):hover {
@@ -275,8 +305,8 @@ section[data-testid="stSidebar"] .st-key-s2f_grp_force > div {
275
 
276
  /* === Action buttons row (measure + downloads) === */
277
  div[data-testid="stHorizontalBlock"]:has([data-testid="stDownloadButton"]):has([data-testid="stButton"]) {
278
- background: #f8fafc;
279
- border: 1px solid #e2e8f0;
280
  border-radius: 14px;
281
  padding: 0.65rem 0.6rem !important;
282
  margin-top: 0.5rem;
@@ -307,22 +337,22 @@ div[data-testid="stHorizontalBlock"]:has([data-testid="stDownloadButton"]):has([
307
  transform: translateY(-1px) !important;
308
  }
309
  div[data-testid="stHorizontalBlock"]:has([data-testid="stDownloadButton"]):has([data-testid="stButton"]) [data-testid="stDownloadButton"] button {
310
- background: white !important;
311
- border: 1px solid #e2e8f0 !important;
312
- color: #334155 !important;
313
  box-shadow: 0 1px 2px rgba(0,0,0,0.04) !important;
314
  }
315
  div[data-testid="stHorizontalBlock"]:has([data-testid="stDownloadButton"]):has([data-testid="stButton"]) [data-testid="stDownloadButton"] button:hover {
316
  background: rgba(var(--s2f-primary-rgb), 0.06) !important;
317
  border-color: var(--s2f-primary) !important;
318
- color: var(--s2f-primary-dark) !important;
319
  box-shadow: 0 2px 6px rgba(var(--s2f-primary-rgb), 0.12) !important;
320
  transform: translateY(-1px) !important;
321
  }
322
 
323
  /* === Expanders === */
324
  .stExpander {
325
- border: 1px solid #e2e8f0 !important;
326
  border-radius: 12px !important;
327
  overflow: hidden;
328
  box-shadow: 0 1px 3px rgba(0,0,0,0.04);
@@ -330,7 +360,7 @@ div[data-testid="stHorizontalBlock"]:has([data-testid="stDownloadButton"]):has([
330
 
331
  /* === File uploader === */
332
  [data-testid="stFileUploader"] section {
333
- border: 2px dashed #cbd5e1 !important;
334
  border-radius: 12px !important;
335
  transition: border-color 0.2s ease;
336
  }
@@ -345,7 +375,7 @@ div[data-testid="stHorizontalBlock"]:has([data-testid="stDownloadButton"]):has([
345
  gap: 8px;
346
  font-size: 0.92rem;
347
  font-weight: 600;
348
- color: #334155;
349
  padding: 0.4rem 0;
350
  }
351
  .result-badge {
@@ -356,12 +386,12 @@ div[data-testid="stHorizontalBlock"]:has([data-testid="stDownloadButton"]):has([
356
  letter-spacing: 0.06em;
357
  }
358
  .result-badge.input {
359
- background: #e2e8f0;
360
- color: #475569;
361
  }
362
  .result-badge.output {
363
  background: rgba(var(--s2f-primary-rgb), 0.15);
364
- color: var(--s2f-primary-dark);
365
  }
366
 
367
  /* === Run prediction info bar === */
@@ -371,29 +401,30 @@ div[data-testid="stHorizontalBlock"]:has([data-testid="stDownloadButton"]):has([
371
  gap: 10px;
372
  height: 42px;
373
  font-size: 0.85rem;
374
- color: #64748b;
375
  }
376
  .run-info-tag {
377
  background: rgba(var(--s2f-primary-rgb), 0.1);
378
- color: var(--s2f-primary-dark);
379
  font-weight: 600;
380
  font-size: 0.78rem;
381
  padding: 3px 10px;
382
  border-radius: 6px;
383
  }
384
  .run-info code {
385
- background: #f1f5f9;
386
  padding: 2px 8px;
387
  border-radius: 4px;
388
  font-size: 0.8rem;
389
- color: #475569;
390
  }
391
 
392
  /* === Messages === */
393
  .stSuccess {
394
- background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%) !important;
395
  border-left: 4px solid #10b981 !important;
396
  border-radius: 8px !important;
 
397
  }
398
  .stWarning { border-radius: 8px !important; }
399
  .stInfo { border-radius: 8px !important; }
@@ -402,19 +433,19 @@ div[data-testid="stHorizontalBlock"]:has([data-testid="stDownloadButton"]):has([
402
  [data-testid="stSelectbox"] > div > div,
403
  [data-testid="stSelectbox"] input,
404
  [data-testid="stMultiSelect"] > div > div {
405
- color: #1e293b !important;
406
- background-color: #ffffff !important;
407
  }
408
  /* Multiselect input: transparent so it doesn't show as a white block before the tags */
409
  [data-testid="stMultiSelect"] input {
410
  background-color: transparent !important;
411
- color: #1e293b !important;
412
  min-width: 2ch !important;
413
  }
414
  [data-testid="stSelectbox"] > div > div,
415
  [data-testid="stMultiSelect"] > div > div {
416
  border-radius: 8px !important;
417
- border: 1px solid #cbd5e1 !important;
418
  transition: border-color 0.15s ease, box-shadow 0.15s ease !important;
419
  }
420
  section[data-testid="stSidebar"] [data-testid="stSelectbox"] > div > div:focus-within,
@@ -428,7 +459,7 @@ section[data-testid="stSidebar"] [data-testid="stMultiSelect"] > div > div:focus
428
  }
429
  /* Toggle: on state uses --s2f-primary */
430
  [data-testid="stToggle"] [data-baseweb="toggleTrack"] {
431
- background-color: #cbd5e1 !important;
432
  }
433
  [data-testid="stToggle"] [data-baseweb="toggleTrack"][aria-checked="true"] {
434
  background-color: var(--s2f-primary) !important;
@@ -437,27 +468,27 @@ section[data-testid="stSidebar"] [data-testid="stMultiSelect"] > div > div:focus
437
  background-color: var(--s2f-primary) !important;
438
  }
439
  [data-testid="stToggle"] [data-baseweb="toggleTrack"][data-state="unchecked"] {
440
- background-color: #cbd5e1 !important;
441
  }
442
  /* Newer Streamlit may render a switch button instead of Base Web track */
443
  [data-testid="stToggle"] button[role="switch"][aria-checked="true"] {
444
  background-color: var(--s2f-primary) !important;
445
  }
446
  [data-testid="stToggle"] button[role="switch"][aria-checked="false"] {
447
- background-color: #cbd5e1 !important;
448
  }
449
- /* Dropdown options: ensure visible text on light background */
450
  [role="listbox"] [role="option"],
451
  [data-baseweb="menu"] li,
452
  ul[role="listbox"] li {
453
- color: #1e293b !important;
454
- background-color: #ffffff !important;
455
  }
456
  /* Sidebar / form labels (colormap row, section titles) */
457
  .s2f-form-label {
458
  font-size: 0.95rem;
459
  font-weight: 500;
460
- color: #334155;
461
  line-height: 1.2;
462
  margin: 0;
463
  }
@@ -492,7 +523,7 @@ ul[role="listbox"] li {
492
  padding: 0 1px;
493
  font-size: 0.65rem;
494
  font-weight: 600;
495
- color: #64748b;
496
  letter-spacing: 0.03em;
497
  }
498
  .colorbar-ticks .cb-tick {
@@ -508,7 +539,7 @@ ul[role="listbox"] li {
508
  box-shadow: inset 0 1px 1px rgba(0,0,0,0.05);
509
  }
510
  /* === Divider === */
511
- hr { border-color: #cbd5e1 !important; opacity: 0.7; }
512
 
513
  /* === Plotly chart === */
514
  .stPlotlyChart { border-radius: 12px; overflow: hidden; }
@@ -521,10 +552,10 @@ hr { border-color: #cbd5e1 !important; opacity: 0.7; }
521
  right: 0;
522
  z-index: 999;
523
  padding: 0.5rem 1rem 0.55rem;
524
- background: #f1f5f9;
525
- border-top: 1px solid #e2e8f0;
526
  font-size: 0.7rem;
527
- color: #64748b;
528
  text-align: center;
529
  line-height: 1.45;
530
  }
@@ -561,19 +592,33 @@ section[data-testid="stSidebar"] > div:first-child {
561
  /* Measure tool: value panel (replaces inline styles in measure_tool.py) */
562
  .s2f-measure-vals-heading {
563
  font-weight: 400;
564
- color: #334155;
565
  font-size: 0.95rem;
566
  margin: 0 20px 4px 4px;
567
  }
568
  .s2f-measure-vals-panel {
569
  width: 100%;
570
  box-sizing: border-box;
571
- border: 1px solid #e2e8f0;
572
  border-radius: 10px;
573
  padding: 10px 12px;
574
  margin: 0 10px 20px 10px;
575
- background: linear-gradient(145deg, #f8fafc 0%, #f1f5f9 100%);
576
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
  }
578
  .s2f-measure-vals-grid {
579
  display: flex;
 
6
  --s2f-primary-rgb: 13, 148, 136;
7
  /* Space for Streamlit’s fixed top toolbar (Deploy / menu). Set when header exists (see below). */
8
  --s2f-streamlit-header-offset: 0px;
 
 
 
9
  /* Match Streamlit’s sidebar horizontal padding so panels can full-bleed the track */
10
  --s2f-sidebar-inline-inset: 1rem;
11
  --s2f-sidebar-width: 360px;
12
  }
13
+ /*
14
+ * Surfaces follow the active Streamlit theme. light-dark() uses the color-scheme
15
+ * Streamlit sets from the system setting (or from Settings → Light / Dark).
16
+ */
17
+ .stApp {
18
+ --s2f-page-bg: light-dark(#fafbfc, #0b1220);
19
+ --s2f-page-dot: light-dark(rgba(148, 163, 184, 0.12), rgba(148, 163, 184, 0.18));
20
+ --s2f-glow: light-dark(rgba(13, 148, 136, 0.06), rgba(45, 212, 191, 0.08));
21
+ --s2f-glow-2: light-dark(rgba(13, 148, 136, 0.04), rgba(45, 212, 191, 0.05));
22
+ --s2f-sidebar-start: light-dark(#f8fafc, #111827);
23
+ --s2f-sidebar-end: light-dark(#f1f5f9, #0f172a);
24
+ --s2f-sidebar-rule: light-dark(rgba(203, 213, 225, 0.25), rgba(51, 65, 85, 0.55));
25
+ --s2f-sidebar-panel-bg: light-dark(rgba(255, 255, 255, 0.42), rgba(15, 23, 42, 0.55));
26
+ --s2f-sidebar-panel-border: light-dark(rgba(203, 213, 225, 0.34), rgba(51, 65, 85, 0.75));
27
+ --s2f-text: light-dark(#1e293b, #e2e8f0);
28
+ --s2f-text-secondary: light-dark(#334155, #cbd5e1);
29
+ --s2f-text-muted: light-dark(#64748b, #94a3b8);
30
+ --s2f-text-faint: light-dark(#475569, #94a3b8);
31
+ --s2f-accent-text: light-dark(#0f766e, #5eead4);
32
+ --s2f-surface: light-dark(#ffffff, #111827);
33
+ --s2f-surface-2: light-dark(#f8fafc, #1e293b);
34
+ --s2f-surface-3: light-dark(#f1f5f9, #1e293b);
35
+ --s2f-border: light-dark(#e2e8f0, #334155);
36
+ --s2f-border-strong: light-dark(#cbd5e1, #475569);
37
+ --s2f-badge-bg: light-dark(#e2e8f0, #334155);
38
+ --s2f-code-bg: light-dark(#f1f5f9, #1e293b);
39
+ --s2f-footer-bg: light-dark(#f1f5f9, #0f172a);
40
+ --s2f-success-start: light-dark(#ecfdf5, #064e3b);
41
+ --s2f-success-end: light-dark(#d1fae5, #065f46);
42
+ --s2f-success-text: light-dark(#065f46, #d1fae5);
43
+ --s2f-shadow: light-dark(rgba(0, 0, 0, 0.06), rgba(0, 0, 0, 0.35));
44
+ --s2f-shadow-hover: light-dark(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.45));
45
+ }
46
  /* Streamlit renders a fixed header; without this offset, the sidebar and first main block sit underneath it. */
47
  .stApp:has(header[data-testid="stHeader"]) {
48
  --s2f-streamlit-header-offset: 2rem;
 
72
  overflow: hidden !important;
73
  display: flex !important;
74
  flex-direction: column !important;
75
+ background-color: var(--s2f-page-bg) !important;
76
  background-image:
77
+ radial-gradient(circle at 1px 1px, var(--s2f-page-dot) 1px, transparent 0);
78
  background-size: 28px 28px !important;
79
  background-position: 0 0, 0 0 !important;
80
  }
 
94
  right: 0;
95
  bottom: 0;
96
  background:
97
+ radial-gradient(ellipse 80% 50% at 70% -20%, var(--s2f-glow) 0%, transparent 50%),
98
+ radial-gradient(ellipse 60% 40% at 0% 100%, var(--s2f-glow-2) 0%, transparent 50%);
99
  pointer-events: none;
100
  z-index: 0;
101
  }
 
173
  overflow-y: auto !important;
174
  scrollbar-gutter: stable !important;
175
  background:
176
+ linear-gradient(180deg, var(--s2f-sidebar-start) 0%, var(--s2f-sidebar-end) 100%),
177
  /* Subtle vertical rhythm */
178
  repeating-linear-gradient(
179
  90deg,
180
  transparent,
181
  transparent 89px,
182
+ var(--s2f-sidebar-rule) 89px,
183
+ var(--s2f-sidebar-rule) 90px
184
  ) !important;
185
+ border-right: 1px solid var(--s2f-border) !important;
186
  }
187
  @media (max-width: 768px) {
188
  section[data-testid="stSidebar"] { width: 100% !important; max-width: 100% !important; }
 
195
  section[data-testid="stSidebar"] [data-testid="stWidgetLabel"] p {
196
  font-size: 0.9rem !important;
197
  font-weight: 500 !important;
198
+ color: var(--s2f-text-secondary) !important;
199
  }
200
  .sidebar-brand {
201
  display: flex;
 
207
  .sidebar-brand .brand-text {
208
  font-size: 1.1rem;
209
  font-weight: 700;
210
+ color: var(--s2f-accent-text);
211
  letter-spacing: -0.01em;
212
  }
213
 
 
243
 
244
  /* === Metric cards === */
245
  [data-testid="stMetric"] {
246
+ background: linear-gradient(145deg, var(--s2f-surface) 0%, var(--s2f-surface-2) 100%);
247
+ border: 1px solid var(--s2f-border);
248
  border-radius: 12px;
249
  padding: 0.85rem 1rem !important;
250
  box-shadow: 0 1px 4px rgba(0,0,0,0.06);
 
257
  [data-testid="stMetric"] label {
258
  font-size: 0.75rem !important;
259
  font-weight: 600 !important;
260
+ color: var(--s2f-text-muted) !important;
261
  text-transform: uppercase;
262
  letter-spacing: 0.04em;
263
  }
264
  [data-testid="stMetric"] [data-testid="stMetricValue"] {
265
  font-size: 1.25rem !important;
266
  font-weight: 700 !important;
267
+ color: var(--s2f-accent-text) !important;
268
  }
269
 
270
  /* === Buttons === */
 
285
  .stButton > button:not([kind="primary"]) {
286
  border-radius: 10px !important;
287
  font-weight: 500 !important;
288
+ border: 1px solid var(--s2f-border-strong) !important;
289
  transition: all 0.2s ease !important;
290
  }
291
  .stButton > button:not([kind="primary"]):hover {
 
305
 
306
  /* === Action buttons row (measure + downloads) === */
307
  div[data-testid="stHorizontalBlock"]:has([data-testid="stDownloadButton"]):has([data-testid="stButton"]) {
308
+ background: var(--s2f-surface-2);
309
+ border: 1px solid var(--s2f-border);
310
  border-radius: 14px;
311
  padding: 0.65rem 0.6rem !important;
312
  margin-top: 0.5rem;
 
337
  transform: translateY(-1px) !important;
338
  }
339
  div[data-testid="stHorizontalBlock"]:has([data-testid="stDownloadButton"]):has([data-testid="stButton"]) [data-testid="stDownloadButton"] button {
340
+ background: var(--s2f-surface) !important;
341
+ border: 1px solid var(--s2f-border) !important;
342
+ color: var(--s2f-text-secondary) !important;
343
  box-shadow: 0 1px 2px rgba(0,0,0,0.04) !important;
344
  }
345
  div[data-testid="stHorizontalBlock"]:has([data-testid="stDownloadButton"]):has([data-testid="stButton"]) [data-testid="stDownloadButton"] button:hover {
346
  background: rgba(var(--s2f-primary-rgb), 0.06) !important;
347
  border-color: var(--s2f-primary) !important;
348
+ color: var(--s2f-accent-text) !important;
349
  box-shadow: 0 2px 6px rgba(var(--s2f-primary-rgb), 0.12) !important;
350
  transform: translateY(-1px) !important;
351
  }
352
 
353
  /* === Expanders === */
354
  .stExpander {
355
+ border: 1px solid var(--s2f-border) !important;
356
  border-radius: 12px !important;
357
  overflow: hidden;
358
  box-shadow: 0 1px 3px rgba(0,0,0,0.04);
 
360
 
361
  /* === File uploader === */
362
  [data-testid="stFileUploader"] section {
363
+ border: 2px dashed var(--s2f-border-strong) !important;
364
  border-radius: 12px !important;
365
  transition: border-color 0.2s ease;
366
  }
 
375
  gap: 8px;
376
  font-size: 0.92rem;
377
  font-weight: 600;
378
+ color: var(--s2f-text-secondary);
379
  padding: 0.4rem 0;
380
  }
381
  .result-badge {
 
386
  letter-spacing: 0.06em;
387
  }
388
  .result-badge.input {
389
+ background: var(--s2f-badge-bg);
390
+ color: var(--s2f-text-faint);
391
  }
392
  .result-badge.output {
393
  background: rgba(var(--s2f-primary-rgb), 0.15);
394
+ color: var(--s2f-accent-text);
395
  }
396
 
397
  /* === Run prediction info bar === */
 
401
  gap: 10px;
402
  height: 42px;
403
  font-size: 0.85rem;
404
+ color: var(--s2f-text-muted);
405
  }
406
  .run-info-tag {
407
  background: rgba(var(--s2f-primary-rgb), 0.1);
408
+ color: var(--s2f-accent-text);
409
  font-weight: 600;
410
  font-size: 0.78rem;
411
  padding: 3px 10px;
412
  border-radius: 6px;
413
  }
414
  .run-info code {
415
+ background: var(--s2f-code-bg);
416
  padding: 2px 8px;
417
  border-radius: 4px;
418
  font-size: 0.8rem;
419
+ color: var(--s2f-text-faint);
420
  }
421
 
422
  /* === Messages === */
423
  .stSuccess {
424
+ background: linear-gradient(135deg, var(--s2f-success-start) 0%, var(--s2f-success-end) 100%) !important;
425
  border-left: 4px solid #10b981 !important;
426
  border-radius: 8px !important;
427
+ color: var(--s2f-success-text) !important;
428
  }
429
  .stWarning { border-radius: 8px !important; }
430
  .stInfo { border-radius: 8px !important; }
 
433
  [data-testid="stSelectbox"] > div > div,
434
  [data-testid="stSelectbox"] input,
435
  [data-testid="stMultiSelect"] > div > div {
436
+ color: var(--s2f-text) !important;
437
+ background-color: var(--s2f-surface) !important;
438
  }
439
  /* Multiselect input: transparent so it doesn't show as a white block before the tags */
440
  [data-testid="stMultiSelect"] input {
441
  background-color: transparent !important;
442
+ color: var(--s2f-text) !important;
443
  min-width: 2ch !important;
444
  }
445
  [data-testid="stSelectbox"] > div > div,
446
  [data-testid="stMultiSelect"] > div > div {
447
  border-radius: 8px !important;
448
+ border: 1px solid var(--s2f-border-strong) !important;
449
  transition: border-color 0.15s ease, box-shadow 0.15s ease !important;
450
  }
451
  section[data-testid="stSidebar"] [data-testid="stSelectbox"] > div > div:focus-within,
 
459
  }
460
  /* Toggle: on state uses --s2f-primary */
461
  [data-testid="stToggle"] [data-baseweb="toggleTrack"] {
462
+ background-color: var(--s2f-border-strong) !important;
463
  }
464
  [data-testid="stToggle"] [data-baseweb="toggleTrack"][aria-checked="true"] {
465
  background-color: var(--s2f-primary) !important;
 
468
  background-color: var(--s2f-primary) !important;
469
  }
470
  [data-testid="stToggle"] [data-baseweb="toggleTrack"][data-state="unchecked"] {
471
+ background-color: var(--s2f-border-strong) !important;
472
  }
473
  /* Newer Streamlit may render a switch button instead of Base Web track */
474
  [data-testid="stToggle"] button[role="switch"][aria-checked="true"] {
475
  background-color: var(--s2f-primary) !important;
476
  }
477
  [data-testid="stToggle"] button[role="switch"][aria-checked="false"] {
478
+ background-color: var(--s2f-border-strong) !important;
479
  }
480
+ /* Dropdowns can render outside .stApp, so resolve colors here instead of inherited variables. */
481
  [role="listbox"] [role="option"],
482
  [data-baseweb="menu"] li,
483
  ul[role="listbox"] li {
484
+ color: light-dark(#1e293b, #e2e8f0) !important;
485
+ background-color: light-dark(#ffffff, #111827) !important;
486
  }
487
  /* Sidebar / form labels (colormap row, section titles) */
488
  .s2f-form-label {
489
  font-size: 0.95rem;
490
  font-weight: 500;
491
+ color: var(--s2f-text-secondary);
492
  line-height: 1.2;
493
  margin: 0;
494
  }
 
523
  padding: 0 1px;
524
  font-size: 0.65rem;
525
  font-weight: 600;
526
+ color: var(--s2f-text-muted);
527
  letter-spacing: 0.03em;
528
  }
529
  .colorbar-ticks .cb-tick {
 
539
  box-shadow: inset 0 1px 1px rgba(0,0,0,0.05);
540
  }
541
  /* === Divider === */
542
+ hr { border-color: var(--s2f-border-strong) !important; opacity: 0.7; }
543
 
544
  /* === Plotly chart === */
545
  .stPlotlyChart { border-radius: 12px; overflow: hidden; }
 
552
  right: 0;
553
  z-index: 999;
554
  padding: 0.5rem 1rem 0.55rem;
555
+ background: var(--s2f-footer-bg);
556
+ border-top: 1px solid var(--s2f-border);
557
  font-size: 0.7rem;
558
+ color: var(--s2f-text-muted);
559
  text-align: center;
560
  line-height: 1.45;
561
  }
 
592
  /* Measure tool: value panel (replaces inline styles in measure_tool.py) */
593
  .s2f-measure-vals-heading {
594
  font-weight: 400;
595
+ color: var(--s2f-text-secondary);
596
  font-size: 0.95rem;
597
  margin: 0 20px 4px 4px;
598
  }
599
  .s2f-measure-vals-panel {
600
  width: 100%;
601
  box-sizing: border-box;
602
+ border: 1px solid var(--s2f-border);
603
  border-radius: 10px;
604
  padding: 10px 12px;
605
  margin: 0 10px 20px 10px;
606
+ background: linear-gradient(145deg, var(--s2f-surface-2) 0%, var(--s2f-surface-3) 100%);
607
+ box-shadow: 0 1px 3px var(--s2f-shadow);
608
+ }
609
+ .s2f-region-table {
610
+ border-collapse: collapse;
611
+ width: 100%;
612
+ color: var(--s2f-text);
613
+ }
614
+ .s2f-region-table th,
615
+ .s2f-region-table td {
616
+ border: 1px solid var(--s2f-border);
617
+ padding: 8px;
618
+ text-align: left;
619
+ }
620
+ .s2f-region-table th {
621
+ background: var(--s2f-surface-2);
622
  }
623
  .s2f-measure-vals-grid {
624
  display: flex;
S2FApp/ui/measure_tool.py CHANGED
@@ -155,21 +155,15 @@ def render_region_metrics_and_downloads(metrics_list, masks, heatmap_rgb, input_
155
  # Render as HTML table to avoid Streamlit's default row/column indices
156
  header = table_rows[0]
157
  body = table_rows[1:]
158
- th_cells = "".join(
159
- f'<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">{html.escape(str(h))}</th>'
160
- for h in header
161
- )
162
  rows_html = [
163
  "<tr>"
164
- + "".join(
165
- f'<td style="border: 1px solid #ddd; padding: 8px;">{html.escape(str(c))}</td>'
166
- for c in row
167
- )
168
  + "</tr>"
169
  for row in body
170
  ]
171
  table_html = (
172
- f'<table style="border-collapse: collapse; width: 100%;">'
173
  f"<thead><tr>{th_cells}</tr></thead>"
174
  f"<tbody>{''.join(rows_html)}</tbody></table>"
175
  )
 
155
  # Render as HTML table to avoid Streamlit's default row/column indices
156
  header = table_rows[0]
157
  body = table_rows[1:]
158
+ th_cells = "".join(f"<th>{html.escape(str(h))}</th>" for h in header)
 
 
 
159
  rows_html = [
160
  "<tr>"
161
+ + "".join(f"<td>{html.escape(str(c))}</td>" for c in row)
 
 
 
162
  + "</tr>"
163
  for row in body
164
  ]
165
  table_html = (
166
+ '<table class="s2f-region-table">'
167
  f"<thead><tr>{th_cells}</tr></thead>"
168
  f"<tbody>{''.join(rows_html)}</tbody></table>"
169
  )