Mohibullah commited on
Commit
ba74320
·
1 Parent(s): e775096

UI/UX Overhaul: Premium cyber-medical dark theme, glassmorphic styles, custom SVG metric icons, active pipeline stepper, and Plotly gauge sync

Browse files
Files changed (1) hide show
  1. gradio_pharmacopilot_demo.py +579 -178
gradio_pharmacopilot_demo.py CHANGED
@@ -474,7 +474,6 @@ def load_kpi_metrics(searches: int = 0) -> str:
474
  elif fallback_path.exists():
475
  text = fallback_path.read_text(encoding="utf-8", errors="ignore")
476
  if "ocr_accuracy" in text:
477
- # Keep a conservative fallback tied to the checked-in report values.
478
  ocr_accuracy = 0.37888446215139443
479
  retrieval_accuracy = 0.6055776892430279
480
 
@@ -487,10 +486,42 @@ def load_kpi_metrics(searches: int = 0) -> str:
487
  return f"""
488
  <div class="app-shell">
489
  <div class="metric-row">
490
- <div class="metric"><span>Session Searches</span><strong>{searches}</strong></div>
491
- <div class="metric"><span>Retrieval Recovery</span><strong>{retrieval_text}</strong></div>
492
- <div class="metric"><span>Medicines Indexed</span><strong>{indexed}</strong></div>
493
- <div class="metric"><span>Rx Samples Tested</span><strong>2,510</strong></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
494
  </div>
495
  </div>
496
  """
@@ -503,21 +534,27 @@ def confidence_gauge(confidence: int = 97):
503
  go.Indicator(
504
  mode="gauge+number",
505
  value=confidence,
506
- number={"suffix": "%", "font": {"size": 34}},
507
  gauge={
508
- "axis": {"range": [0, 100], "tickwidth": 1},
509
- "bar": {"color": "#0f9f6e"},
510
- "bgcolor": "white",
511
- "borderwidth": 0,
 
512
  "steps": [
513
- {"range": [0, 60], "color": "#fee2e2"},
514
- {"range": [60, 85], "color": "#fef3c7"},
515
- {"range": [85, 100], "color": "#dcfce7"},
516
  ],
517
  },
518
  )
519
  )
520
- fig.update_layout(height=230, margin=dict(l=20, r=20, t=20, b=8), paper_bgcolor="rgba(0,0,0,0)")
 
 
 
 
 
521
  return fig
522
 
523
 
@@ -538,20 +575,34 @@ def pipeline_html(stage: int = 0, validation_status: str = "waiting") -> str:
538
  cards = []
539
  logs = []
540
  for i, (title, status) in enumerate(steps, start=1):
541
- active = i <= stage
 
 
 
 
 
 
542
  cards.append(
543
  f"""
544
- <div class="flow-step {'done' if active else ''}">
545
  <span>{i}</span>
546
  <strong>{title}</strong>
547
  </div>
548
  """
549
  )
550
- if active:
551
- logs.append(f"<li>{title} {status}</li>")
 
 
 
 
 
552
  return f"""
553
  <div class="pipeline">
554
- <div class="pipeline-title">Actual Run Trace</div>
 
 
 
555
  <div class="flow">{''.join(cards)}</div>
556
  <ul class="logs">{''.join(logs)}</ul>
557
  </div>
@@ -581,22 +632,22 @@ def medicine_details_html(
581
  )
582
  return f"""
583
  <div class="result-card">
584
- <h3>Prescription Details</h3>
585
  <dl class="details">
586
  <dt>Medicine</dt><dd>{medicine_label}</dd>
587
- <dt>Generic</dt><dd>{generic_label}</dd>
588
  <dt>Strength</dt><dd>{strength_label}</dd>
589
  <dt>Manufacturer</dt><dd>{manufacturer_label}</dd>
590
- <dt>Confidence</dt><dd>{confidence}%</dd>
591
- <dt>Category</dt><dd>{category_label}</dd>
592
  <dt>Price</dt><dd>{price_label}</dd>
593
  </dl>
594
  <div class="explain">
595
- <h4>AI Explanation</h4>
596
  <p><b>OCR detected:</b> "{ocr_text}"</p>
597
- <p><b>Retrieved:</b> {display_name} ({medicine.get('name', 'Unknown')})</p>
598
- <p><b>Validation:</b> {validation_label}</p>
599
- <p><b>Inventory:</b> {inventory_label}</p>
600
  </div>
601
  </div>
602
  """
@@ -614,15 +665,19 @@ def translated_prescription_html(plan: dict[str, Any]) -> str:
614
  ("Instructions", plan.get("instructions") or "Pharmacist review required"),
615
  ]
616
  row_html = "".join(f"<dt>{label}</dt><dd>{value}</dd>" for label, value in rows)
617
- status = plan.get("status", "needs_review").replace("_", " ").title()
 
 
 
 
618
  return f"""
619
  <div class="translated-card">
620
  <div class="translated-head">
621
- <h3>Translated Prescription</h3>
622
- <span class="status-pill">{status}</span>
623
  </div>
624
  <dl class="details translated-details">{row_html}</dl>
625
- <p class="fine-print">Generated from OCR text and retrieval candidates. Confirm before dispensing.</p>
626
  </div>
627
  """
628
 
@@ -631,18 +686,19 @@ def package_status_html(inventory: dict[str, Any], accepted: bool = True) -> str
631
  if not accepted:
632
  return """
633
  <div class="stock-card">
634
- <div><span>Available Stock</span><strong>-</strong></div>
635
  <div><span>Status</span><strong class="bad">Needs Review</strong></div>
636
- <div><span>Shelf</span><strong>Hidden</strong></div>
637
  </div>
638
  """
639
  status = "In Stock" if inventory.get("quantity", 0) > 0 else "Out of Stock"
640
  dot_class = "ok" if inventory.get("quantity", 0) > 0 else "bad"
 
641
  return f"""
642
  <div class="stock-card">
643
  <div><span>Available Stock</span><strong>{inventory.get('quantity', 0)}</strong></div>
644
  <div><span>Status</span><strong class="{dot_class}">{status}</strong></div>
645
- <div><span>Shelf</span><strong>{inventory.get('shelf', '-')} · Row {inventory.get('row', '-')}</strong></div>
646
  </div>
647
  """
648
 
@@ -651,12 +707,13 @@ def candidates_html(candidates: list[dict[str, Any]]) -> str:
651
  rows = []
652
  for rank, item in enumerate(candidates, start=1):
653
  score = round(item["score"] * 100)
 
654
  rows.append(
655
- f"<tr><td>{rank}</td><td>{item['label']}</td><td>{item['medicine']['name']}</td><td>{score}%</td></tr>"
656
  )
657
  return f"""
658
  <table class="candidate-table">
659
- <thead><tr><th>#</th><th>Candidate</th><th>Canonical</th><th>Score</th></tr></thead>
660
  <tbody>{''.join(rows)}</tbody>
661
  </table>
662
  """
@@ -669,12 +726,25 @@ def ocr_compare_html(
669
  confidence: int,
670
  plan: dict[str, Any],
671
  ) -> str:
672
- corrected = display_name if plan.get("status") == "validated" else f"Needs review: {display_name}"
 
 
 
 
673
  return f"""
674
  <div class="compare-grid">
675
- <div><span>OCR Output</span><strong>{ocr_text}</strong></div>
676
- <div><span>AI Corrected</span><strong>{corrected}</strong></div>
677
- <div><span>Canonical</span><strong>{medicine['name'] if plan.get('status') == 'validated' else 'Not confirmed'}</strong></div>
 
 
 
 
 
 
 
 
 
678
  </div>
679
  """
680
 
@@ -828,233 +898,564 @@ def locate_on_shelf(shelf_image, state: dict[str, Any] | None):
828
 
829
 
830
  CSS = """
 
 
831
  :root {
832
- --green: #0f9f6e;
833
- --ink: #14213d;
834
- --muted: #5f6b7a;
835
- --line: #d9e2ec;
836
- --soft: #f4f8f7;
837
- --blue: #1769aa;
 
 
 
 
 
 
 
838
  }
 
 
839
  .gradio-container {
840
- background: linear-gradient(180deg, #f8fbfb 0%, #edf6f4 100%);
841
- color: var(--ink) !important;
842
- font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
843
- }
844
- .gradio-container h1,
845
- .gradio-container h2,
846
- .gradio-container h3,
847
- .gradio-container h4,
848
- .gradio-container p,
849
- .gradio-container label,
850
- .gradio-container span,
851
- .gradio-container strong,
852
- .gradio-container div {
853
- letter-spacing: 0;
854
  }
 
855
  .app-shell {
856
- width: min(1120px, calc(100vw - 64px));
857
  max-width: 1120px;
858
  margin: 0 auto;
859
  box-sizing: border-box;
860
  }
 
 
861
  .hero {
862
- padding: 24px 28px;
863
  display: flex;
864
  align-items: center;
865
  justify-content: space-between;
866
  gap: 24px;
867
- border: 1px solid var(--line);
868
- border-radius: 8px;
869
- background: #ffffff;
870
- box-shadow: 0 10px 32px rgba(15, 23, 42, 0.06);
 
 
871
  margin-top: 24px;
872
  }
873
- .hero h1 { color: var(--ink) !important; font-size: 34px; line-height: 1; margin: 0; letter-spacing: 0; }
874
- .hero p { margin: 8px 0 0; color: var(--muted) !important; font-size: 16px; }
 
 
 
 
 
 
 
 
 
 
 
 
875
  .powered {
876
- color: var(--blue) !important;
877
- font-weight: 800;
878
  text-align: right;
879
- background: #edf7ff;
880
- border: 1px solid #cfe7fb;
881
- border-radius: 8px;
882
- padding: 10px 12px;
 
 
 
883
  }
 
 
884
  .metric-row {
885
  display: grid;
886
  grid-template-columns: repeat(4, minmax(0, 1fr));
887
- gap: 12px;
888
- width: min(1120px, calc(100vw - 64px));
889
  max-width: 1120px;
890
- margin: 16px auto 24px;
891
  box-sizing: border-box;
892
  }
893
  .metric {
894
- border: 1px solid var(--line);
895
- background: #ffffff;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
896
  border-radius: 8px;
897
- padding: 16px 18px;
898
- min-height: 86px;
899
- box-shadow: 0 10px 26px rgba(15, 23, 42, 0.045);
900
  }
901
- .metric span, .stock-card span, .compare-grid span { color: var(--muted) !important; display: block; font-size: 13px; }
902
- .metric strong { color: var(--ink) !important; display: block; font-size: 27px; margin-top: 6px; }
903
  .capture-card {
904
- border: 1px solid var(--line);
905
- background: #ffffff;
906
- border-radius: 8px;
907
- padding: 28px;
908
- min-height: 132px;
909
- box-shadow: 0 10px 26px rgba(15, 23, 42, 0.045);
 
 
 
 
 
 
910
  }
911
- .capture-card h2 { color: var(--ink) !important; margin: 0 0 10px; font-size: 24px; }
912
- .capture-card p { color: var(--muted) !important; margin: 0; }
 
 
 
 
 
913
  .pipeline {
914
- border: 1px solid var(--line);
915
- background: #ffffff;
916
- border-radius: 8px;
917
- padding: 20px;
918
  margin: 0;
919
- min-height: 132px;
920
- box-shadow: 0 10px 26px rgba(15, 23, 42, 0.045);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
921
  }
922
- .pipeline-title { color: var(--ink) !important; font-weight: 800; margin-bottom: 12px; }
923
- .flow { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; }
924
  .flow-step {
925
- min-height: 86px;
926
- border: 1px solid var(--line);
927
- border-radius: 8px;
928
- background: var(--soft);
929
- padding: 10px;
 
 
 
 
 
 
 
930
  }
931
  .flow-step span {
932
- width: 26px;
933
- height: 26px;
934
  border-radius: 50%;
935
  display: inline-grid;
936
  place-items: center;
937
- background: #d8e7e3;
938
- color: var(--ink);
939
- font-weight: 800;
940
- margin-bottom: 8px;
941
- }
942
- .flow-step.done { border-color: #85d7bd; background: #ebfbf5; }
943
- .flow-step.done span { background: var(--green); color: #ffffff; }
944
- .flow-step strong { color: var(--ink) !important; display: block; font-size: 14px; }
945
- .logs { margin: 14px 0 0; padding-left: 20px; color: #174c3c !important; }
946
- .result-card, .stock-card {
947
- border: 1px solid var(--line);
948
- background: #ffffff;
949
- border-radius: 8px;
950
- padding: 18px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
951
  }
952
- .result-card h3 { color: var(--ink) !important; margin: 0 0 12px; font-size: 20px; }
953
  .details {
954
  display: grid;
955
  grid-template-columns: 140px 1fr;
956
- gap: 8px 14px;
 
 
 
 
 
 
 
 
 
957
  margin: 0;
 
 
958
  }
959
- .details dt { color: var(--muted) !important; }
960
- .details dd { color: var(--ink) !important; margin: 0; font-weight: 750; }
961
  .explain {
962
- margin-top: 16px;
963
- border-top: 1px solid var(--line);
964
- padding-top: 14px;
 
 
 
 
 
 
965
  }
966
- .explain h4 { color: var(--ink) !important; margin: 0 0 8px; }
967
- .explain p { margin: 6px 0; color: #263445 !important; }
 
 
 
 
 
 
 
 
 
968
  .stock-card {
969
  display: grid;
970
  grid-template-columns: repeat(3, minmax(0, 1fr));
971
- gap: 12px;
972
- margin-top: 10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
973
  }
974
- .stock-card strong { color: var(--ink) !important; display: block; font-size: 20px; margin-top: 3px; }
975
- .stock-card .ok { color: var(--green); }
976
- .stock-card .bad { color: #b91c1c; }
977
- .candidate-table { width: 100%; border-collapse: collapse; background: #ffffff; }
978
  .candidate-table th, .candidate-table td {
979
- border-bottom: 1px solid var(--line);
980
- padding: 10px 8px;
981
  text-align: left;
982
- color: var(--ink) !important;
 
983
  }
984
- .candidate-table th { color: var(--muted) !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
985
  .compare-grid {
986
  display: grid;
987
  grid-template-columns: repeat(3, minmax(0, 1fr));
988
- gap: 12px;
 
989
  }
990
  .compare-grid div {
991
- border: 1px solid var(--line);
992
- border-radius: 8px;
993
- padding: 14px;
994
- background: #ffffff;
 
 
 
 
 
 
 
 
 
 
 
 
 
995
  }
996
- .compare-grid strong { color: var(--ink) !important; display: block; margin-top: 6px; font-size: 18px; }
 
997
  .translated-card {
998
- border: 1px solid var(--line);
999
- background: #ffffff;
1000
- border-radius: 8px;
1001
- padding: 18px;
1002
- margin-top: 12px;
 
1003
  }
1004
  .translated-head {
1005
  display: flex;
1006
  justify-content: space-between;
1007
  gap: 12px;
1008
  align-items: center;
1009
- margin-bottom: 12px;
 
 
 
 
 
 
1010
  }
1011
- .translated-head h3 { color: var(--ink) !important; margin: 0; font-size: 20px; }
1012
  .status-pill {
1013
- background: #ebfbf5;
1014
- border: 1px solid #85d7bd;
1015
- color: #075f45;
1016
  border-radius: 999px;
1017
- padding: 5px 10px;
1018
  font-size: 12px;
1019
- font-weight: 800;
 
 
 
 
 
 
 
1020
  }
1021
  .translated-details {
1022
- grid-template-columns: 130px 1fr;
1023
  }
1024
  .fine-print {
1025
- border-top: 1px solid var(--line);
1026
- color: var(--muted) !important;
1027
- margin: 14px 0 0;
1028
- padding-top: 12px;
1029
- font-size: 13px;
 
1030
  }
1031
- .compact { margin-top: 0; }
 
1032
  .gradio-container button.primary,
1033
  .gradio-container button[variant="primary"] {
1034
- background: var(--green) !important;
1035
- border-color: var(--green) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1036
  color: #ffffff !important;
 
 
 
1037
  }
1038
- .gradio-container button.primary:hover,
1039
- .gradio-container button[variant="primary"]:hover {
1040
- background: #0b7f59 !important;
1041
  }
1042
- @media (max-width: 760px) {
1043
- .app-shell { width: min(100% - 28px, 1120px); }
1044
- .hero { display: block; }
1045
- .powered { text-align: left; margin-top: 10px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1046
  .metric-row, .flow, .stock-card, .compare-grid { grid-template-columns: 1fr; }
1047
  .details { grid-template-columns: 1fr; }
1048
- .translated-head { align-items: flex-start; flex-direction: column; }
 
1049
  }
1050
  """
1051
 
1052
 
1053
- APP_THEME = gr.themes.Soft(
1054
  primary_hue="emerald",
1055
- secondary_hue="blue",
1056
  neutral_hue="slate",
1057
- font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
1058
  )
1059
 
1060
 
 
474
  elif fallback_path.exists():
475
  text = fallback_path.read_text(encoding="utf-8", errors="ignore")
476
  if "ocr_accuracy" in text:
 
477
  ocr_accuracy = 0.37888446215139443
478
  retrieval_accuracy = 0.6055776892430279
479
 
 
486
  return f"""
487
  <div class="app-shell">
488
  <div class="metric-row">
489
+ <div class="metric">
490
+ <div class="metric-head">
491
+ <span>Session Searches</span>
492
+ <div class="metric-icon" style="color: var(--glow-cyan); background: rgba(6, 182, 212, 0.12);">
493
+ <svg fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="width: 16px; height: 16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
494
+ </div>
495
+ </div>
496
+ <strong>{searches}</strong>
497
+ </div>
498
+ <div class="metric">
499
+ <div class="metric-head">
500
+ <span>Retrieval Recovery</span>
501
+ <div class="metric-icon" style="color: var(--glow-green); background: rgba(16, 185, 129, 0.12);">
502
+ <svg fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="width: 16px; height: 16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path></svg>
503
+ </div>
504
+ </div>
505
+ <strong>{retrieval_text}</strong>
506
+ </div>
507
+ <div class="metric">
508
+ <div class="metric-head">
509
+ <span>Medicines Indexed</span>
510
+ <div class="metric-icon" style="color: var(--glow-indigo); background: rgba(99, 102, 241, 0.12);">
511
+ <svg fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="width: 16px; height: 16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path></svg>
512
+ </div>
513
+ </div>
514
+ <strong>{indexed}</strong>
515
+ </div>
516
+ <div class="metric">
517
+ <div class="metric-head">
518
+ <span>Rx Samples Tested</span>
519
+ <div class="metric-icon" style="color: #f43f5e; background: rgba(244, 63, 94, 0.12);">
520
+ <svg fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="width: 16px; height: 16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path></svg>
521
+ </div>
522
+ </div>
523
+ <strong>2,510</strong>
524
+ </div>
525
  </div>
526
  </div>
527
  """
 
534
  go.Indicator(
535
  mode="gauge+number",
536
  value=confidence,
537
+ number={"suffix": "%", "font": {"size": 34, "color": "#ffffff"}},
538
  gauge={
539
+ "axis": {"range": [0, 100], "tickwidth": 1, "tickcolor": "#94a3b8", "tickfont": {"color": "#94a3b8"}},
540
+ "bar": {"color": "#10b981"},
541
+ "bgcolor": "rgba(255, 255, 255, 0.05)",
542
+ "borderwidth": 1,
543
+ "bordercolor": "rgba(255, 255, 255, 0.15)",
544
  "steps": [
545
+ {"range": [0, 60], "color": "rgba(239, 68, 68, 0.15)"},
546
+ {"range": [60, 85], "color": "rgba(245, 158, 11, 0.15)"},
547
+ {"range": [85, 100], "color": "rgba(16, 185, 129, 0.15)"},
548
  ],
549
  },
550
  )
551
  )
552
+ fig.update_layout(
553
+ height=230,
554
+ margin=dict(l=20, r=20, t=20, b=8),
555
+ paper_bgcolor="rgba(0,0,0,0)",
556
+ plot_bgcolor="rgba(0,0,0,0)"
557
+ )
558
  return fig
559
 
560
 
 
575
  cards = []
576
  logs = []
577
  for i, (title, status) in enumerate(steps, start=1):
578
+ if i < stage:
579
+ step_class = "done"
580
+ elif i == stage:
581
+ step_class = "current" if stage < 5 else "done"
582
+ else:
583
+ step_class = ""
584
+
585
  cards.append(
586
  f"""
587
+ <div class="flow-step {step_class}">
588
  <span>{i}</span>
589
  <strong>{title}</strong>
590
  </div>
591
  """
592
  )
593
+ if i <= stage:
594
+ logs.append(f"<li>{title} {status}</li>")
595
+
596
+ pulse_indicator = ""
597
+ if stage > 0 and stage < 5:
598
+ pulse_indicator = '<span style="display:inline-block; width:10px; height:10px; background:var(--glow-cyan); border-radius:50%; margin-left:8px; animation: pulse-step 1.5s infinite;"></span>'
599
+
600
  return f"""
601
  <div class="pipeline">
602
+ <div class="pipeline-title" style="display:flex; align-items:center;">
603
+ <span>SYSTEM DIAGNOSTICS TRACE</span>
604
+ {pulse_indicator}
605
+ </div>
606
  <div class="flow">{''.join(cards)}</div>
607
  <ul class="logs">{''.join(logs)}</ul>
608
  </div>
 
632
  )
633
  return f"""
634
  <div class="result-card">
635
+ <h3>Prescription Analysis</h3>
636
  <dl class="details">
637
  <dt>Medicine</dt><dd>{medicine_label}</dd>
638
+ <dt>Generic</dt><dd style="color: var(--glow-cyan) !important;">{generic_label}</dd>
639
  <dt>Strength</dt><dd>{strength_label}</dd>
640
  <dt>Manufacturer</dt><dd>{manufacturer_label}</dd>
641
+ <dt>Confidence</dt><dd style="color: {'var(--text-success)' if accepted else 'var(--text-warning)'} !important;">{confidence}%</dd>
642
+ <dt>Category</dt><dd><span style="padding: 2px 8px; background: rgba(99,102,241,0.15); border-radius: 4px; font-size:12px; color: #a5b4fc;">{category_label}</span></dd>
643
  <dt>Price</dt><dd>{price_label}</dd>
644
  </dl>
645
  <div class="explain">
646
+ <h4>AI Diagnostic Breakdown</h4>
647
  <p><b>OCR detected:</b> "{ocr_text}"</p>
648
+ <p><b>Retrieved Master Match:</b> {display_name} ({medicine.get('name', 'Unknown')})</p>
649
+ <p><b>Nemotron Validation:</b> <span style="color: {'var(--text-success)' if accepted else 'var(--text-warning)'}; font-weight: 600;">{validation_label}</span></p>
650
+ <p><b>Inventory Location:</b> {inventory_label}</p>
651
  </div>
652
  </div>
653
  """
 
665
  ("Instructions", plan.get("instructions") or "Pharmacist review required"),
666
  ]
667
  row_html = "".join(f"<dt>{label}</dt><dd>{value}</dd>" for label, value in rows)
668
+ status_val = plan.get("status", "needs_review")
669
+ status_label = status_val.replace("_", " ").title()
670
+ status_class = "status-pill"
671
+ if status_val == "needs_review":
672
+ status_class += " needs-review"
673
  return f"""
674
  <div class="translated-card">
675
  <div class="translated-head">
676
+ <h3>Translated Instruction Plan</h3>
677
+ <span class="{status_class}">{status_label}</span>
678
  </div>
679
  <dl class="details translated-details">{row_html}</dl>
680
+ <p class="fine-print">Generated via local LLM + OCR validation pipeline. Verify dosage instructions before dispensing.</p>
681
  </div>
682
  """
683
 
 
686
  if not accepted:
687
  return """
688
  <div class="stock-card">
689
+ <div><span>Available Stock</span><strong style="color: var(--text-muted);">-</strong></div>
690
  <div><span>Status</span><strong class="bad">Needs Review</strong></div>
691
+ <div><span>Shelf Location</span><strong style="color: var(--text-muted);">Hidden</strong></div>
692
  </div>
693
  """
694
  status = "In Stock" if inventory.get("quantity", 0) > 0 else "Out of Stock"
695
  dot_class = "ok" if inventory.get("quantity", 0) > 0 else "bad"
696
+ shelf_loc = f"Shelf {inventory.get('shelf', '-')} · Row {inventory.get('row', '-')}"
697
  return f"""
698
  <div class="stock-card">
699
  <div><span>Available Stock</span><strong>{inventory.get('quantity', 0)}</strong></div>
700
  <div><span>Status</span><strong class="{dot_class}">{status}</strong></div>
701
+ <div><span>Shelf Location</span><strong>{shelf_loc}</strong></div>
702
  </div>
703
  """
704
 
 
707
  rows = []
708
  for rank, item in enumerate(candidates, start=1):
709
  score = round(item["score"] * 100)
710
+ score_color = "var(--text-success)" if score > 80 else ("var(--text-warning)" if score > 50 else "var(--text-muted)")
711
  rows.append(
712
+ f"<tr><td style='font-weight:600; color:var(--glow-cyan);'>{rank}</td><td style='font-weight:600;'>{item['label']}</td><td>{item['medicine']['name']}</td><td style='font-weight:700; color:{score_color};'>{score}%</td></tr>"
713
  )
714
  return f"""
715
  <table class="candidate-table">
716
+ <thead><tr><th>Rank</th><th>Candidate Alias</th><th>Canonical Name</th><th>Match Score</th></tr></thead>
717
  <tbody>{''.join(rows)}</tbody>
718
  </table>
719
  """
 
726
  confidence: int,
727
  plan: dict[str, Any],
728
  ) -> str:
729
+ accepted = plan.get("status") == "validated" and confidence >= ACCEPTANCE_THRESHOLD
730
+ corrected = display_name if accepted else f"Needs review: {display_name}"
731
+ canonical = medicine['name'] if accepted else 'Not confirmed'
732
+ corrected_color = "var(--text-success)" if accepted else "var(--text-warning)"
733
+ canonical_color = "var(--glow-indigo)" if accepted else "var(--text-muted)"
734
  return f"""
735
  <div class="compare-grid">
736
+ <div style="border-left: 3px solid var(--text-warning) !important;">
737
+ <span>Raw OCR Output</span>
738
+ <strong style="color: var(--text-warning) !important; font-family: monospace;">"{ocr_text}"</strong>
739
+ </div>
740
+ <div style="border-left: 3px solid {corrected_color} !important;">
741
+ <span>AI Corrected Candidate</span>
742
+ <strong style="color: {corrected_color} !important;">{corrected}</strong>
743
+ </div>
744
+ <div style="border-left: 3px solid {canonical_color} !important;">
745
+ <span>Master Database Canonical</span>
746
+ <strong style="color: {canonical_color} !important;">{canonical}</strong>
747
+ </div>
748
  </div>
749
  """
750
 
 
898
 
899
 
900
  CSS = """
901
+ @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;700&display=swap');
902
+
903
  :root {
904
+ --glow-green: #10b981;
905
+ --glow-green-hover: #059669;
906
+ --glow-cyan: #06b6d4;
907
+ --glow-indigo: #6366f1;
908
+ --bg-dark: #070b13;
909
+ --bg-glass: rgba(13, 20, 38, 0.65);
910
+ --border-glass: rgba(255, 255, 255, 0.08);
911
+ --border-active: rgba(16, 185, 129, 0.3);
912
+ --text-main: #f1f5f9;
913
+ --text-muted: #94a3b8;
914
+ --text-success: #34d399;
915
+ --text-warning: #fbbf24;
916
+ --text-danger: #f87171;
917
  }
918
+
919
+ /* Base Styles & Background Override */
920
  .gradio-container {
921
+ background: radial-gradient(circle at 50% 0%, #0d172e 0%, #060911 100%) !important;
922
+ color: var(--text-main) !important;
923
+ font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif !important;
924
+ min-height: 100vh;
925
+ }
926
+
927
+ /* Title text colors override */
928
+ .gradio-container h1, .gradio-container h2, .gradio-container h3, .gradio-container h4, .gradio-container h5, .gradio-container h6 {
929
+ color: #ffffff !important;
930
+ font-weight: 700 !important;
 
 
 
 
931
  }
932
+
933
  .app-shell {
934
+ width: min(1120px, calc(100vw - 48px));
935
  max-width: 1120px;
936
  margin: 0 auto;
937
  box-sizing: border-box;
938
  }
939
+
940
+ /* Beautiful Hero Header */
941
  .hero {
942
+ padding: 24px 32px;
943
  display: flex;
944
  align-items: center;
945
  justify-content: space-between;
946
  gap: 24px;
947
+ border: 1px solid var(--border-glass) !important;
948
+ border-top: 3px solid var(--glow-green) !important;
949
+ border-radius: 16px !important;
950
+ background: linear-gradient(135deg, rgba(16, 26, 48, 0.8), rgba(9, 14, 27, 0.8)) !important;
951
+ backdrop-filter: blur(16px);
952
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4), 0 0 15px rgba(16, 185, 129, 0.05);
953
  margin-top: 24px;
954
  }
955
+ .hero h1 {
956
+ font-size: 36px;
957
+ line-height: 1.1;
958
+ margin: 0;
959
+ background: linear-gradient(90deg, #ffffff, #06b6d4, #10b981);
960
+ -webkit-background-clip: text;
961
+ -webkit-text-fill-color: transparent;
962
+ letter-spacing: -0.5px;
963
+ }
964
+ .hero p {
965
+ margin: 8px 0 0;
966
+ color: var(--text-muted) !important;
967
+ font-size: 16px;
968
+ }
969
  .powered {
970
+ color: #60a5fa !important;
971
+ font-weight: 700;
972
  text-align: right;
973
+ background: rgba(30, 64, 175, 0.15);
974
+ border: 1px solid rgba(96, 165, 250, 0.25);
975
+ border-radius: 10px;
976
+ padding: 8px 14px;
977
+ font-size: 13px;
978
+ box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.1);
979
+ letter-spacing: 0.5px;
980
  }
981
+
982
+ /* Metric KPI Layout */
983
  .metric-row {
984
  display: grid;
985
  grid-template-columns: repeat(4, minmax(0, 1fr));
986
+ gap: 16px;
987
+ width: min(1120px, calc(100vw - 48px));
988
  max-width: 1120px;
989
+ margin: 20px auto 24px;
990
  box-sizing: border-box;
991
  }
992
  .metric {
993
+ border: 1px solid var(--border-glass) !important;
994
+ background: var(--bg-glass) !important;
995
+ border-radius: 16px !important;
996
+ padding: 20px;
997
+ min-height: 96px;
998
+ backdrop-filter: blur(12px);
999
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
1000
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1001
+ display: flex;
1002
+ flex-direction: column;
1003
+ justify-content: space-between;
1004
+ }
1005
+ .metric:hover {
1006
+ transform: translateY(-4px);
1007
+ border-color: var(--glow-cyan) !important;
1008
+ box-shadow: 0 15px 30px rgba(6, 182, 212, 0.15), 0 0 10px rgba(6, 182, 212, 0.05);
1009
+ }
1010
+ .metric-head {
1011
+ display: flex;
1012
+ align-items: center;
1013
+ justify-content: space-between;
1014
+ margin-bottom: 4px;
1015
+ }
1016
+ .metric span {
1017
+ color: var(--text-muted) !important;
1018
+ font-size: 13px;
1019
+ font-weight: 500;
1020
+ text-transform: uppercase;
1021
+ letter-spacing: 0.5px;
1022
+ }
1023
+ .metric strong {
1024
+ color: #ffffff !important;
1025
+ font-size: 32px;
1026
+ line-height: 1;
1027
+ font-weight: 800;
1028
+ margin-top: 4px;
1029
+ letter-spacing: -0.5px;
1030
+ background: linear-gradient(180deg, #ffffff 0%, #e2e8f0 100%);
1031
+ -webkit-background-clip: text;
1032
+ -webkit-text-fill-color: transparent;
1033
+ }
1034
+ .metric-icon {
1035
+ width: 24px;
1036
+ height: 24px;
1037
+ color: var(--glow-cyan);
1038
+ display: flex;
1039
+ align-items: center;
1040
+ justify-content: center;
1041
+ background: rgba(6, 182, 212, 0.1);
1042
  border-radius: 8px;
1043
+ padding: 4px;
 
 
1044
  }
1045
+
1046
+ /* Step Capture Card & Upload */
1047
  .capture-card {
1048
+ border: 1px solid var(--border-glass) !important;
1049
+ background: var(--bg-glass) !important;
1050
+ border-radius: 16px !important;
1051
+ padding: 24px 28px;
1052
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
1053
+ margin-bottom: 12px;
1054
+ }
1055
+ .capture-card h2 {
1056
+ color: #ffffff !important;
1057
+ margin: 0 0 8px;
1058
+ font-size: 22px;
1059
+ font-weight: 600;
1060
  }
1061
+ .capture-card p {
1062
+ color: var(--text-muted) !important;
1063
+ margin: 0;
1064
+ font-size: 14px;
1065
+ }
1066
+
1067
+ /* Beautiful Stepper Diagnostics Trace */
1068
  .pipeline {
1069
+ border: 1px solid var(--border-glass) !important;
1070
+ background: var(--bg-glass) !important;
1071
+ border-radius: 16px !important;
1072
+ padding: 24px;
1073
  margin: 0;
1074
+ backdrop-filter: blur(12px);
1075
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
1076
+ }
1077
+ .pipeline-title {
1078
+ color: #ffffff !important;
1079
+ font-weight: 700;
1080
+ margin-bottom: 20px;
1081
+ font-size: 16px;
1082
+ text-transform: uppercase;
1083
+ letter-spacing: 0.5px;
1084
+ display: flex;
1085
+ align-items: center;
1086
+ gap: 8px;
1087
+ }
1088
+ .flow {
1089
+ display: grid;
1090
+ grid-template-columns: repeat(5, minmax(0, 1fr));
1091
+ gap: 12px;
1092
+ position: relative;
1093
  }
 
 
1094
  .flow-step {
1095
+ min-height: 96px;
1096
+ border: 1px solid var(--border-glass) !important;
1097
+ border-radius: 12px !important;
1098
+ background: rgba(255, 255, 255, 0.02) !important;
1099
+ padding: 12px;
1100
+ text-align: center;
1101
+ display: flex;
1102
+ flex-direction: column;
1103
+ align-items: center;
1104
+ justify-content: flex-start;
1105
+ transition: all 0.3s ease;
1106
+ position: relative;
1107
  }
1108
  .flow-step span {
1109
+ width: 32px;
1110
+ height: 32px;
1111
  border-radius: 50%;
1112
  display: inline-grid;
1113
  place-items: center;
1114
+ background: rgba(255, 255, 255, 0.05);
1115
+ color: var(--text-muted);
1116
+ font-weight: 700;
1117
+ font-size: 14px;
1118
+ margin-bottom: 12px;
1119
+ border: 1px solid var(--border-glass);
1120
+ }
1121
+ .flow-step.done {
1122
+ border-color: var(--border-active) !important;
1123
+ background: rgba(16, 185, 129, 0.04) !important;
1124
+ box-shadow: 0 0 15px rgba(16, 185, 129, 0.05);
1125
+ }
1126
+ .flow-step.done span {
1127
+ background: var(--glow-green);
1128
+ color: #ffffff;
1129
+ border: none;
1130
+ box-shadow: 0 0 10px rgba(16, 185, 129, 0.4);
1131
+ }
1132
+ .flow-step.current {
1133
+ border-color: rgba(6, 182, 212, 0.5) !important;
1134
+ background: rgba(6, 182, 212, 0.06) !important;
1135
+ box-shadow: 0 0 15px rgba(6, 182, 212, 0.15);
1136
+ animation: pulse-step 2s infinite ease-in-out;
1137
+ }
1138
+ .flow-step.current span {
1139
+ background: var(--glow-cyan);
1140
+ color: #ffffff;
1141
+ border: none;
1142
+ box-shadow: 0 0 10px rgba(6, 182, 212, 0.4);
1143
+ }
1144
+ .flow-step strong {
1145
+ color: #ffffff !important;
1146
+ display: block;
1147
+ font-size: 13px;
1148
+ font-weight: 600;
1149
+ }
1150
+ @keyframes pulse-step {
1151
+ 0% { box-shadow: 0 0 0 0 rgba(6, 182, 212, 0.4); }
1152
+ 70% { box-shadow: 0 0 0 6px rgba(6, 182, 212, 0); }
1153
+ 100% { box-shadow: 0 0 0 0 rgba(6, 182, 212, 0); }
1154
+ }
1155
+
1156
+ /* Monospace Diagnostics Console Logs */
1157
+ .logs {
1158
+ margin: 20px 0 0;
1159
+ padding: 16px !important;
1160
+ background: #05080e !important;
1161
+ border: 1px solid rgba(255, 255, 255, 0.05) !important;
1162
+ border-radius: 10px;
1163
+ font-family: 'JetBrains Mono', 'Courier New', Courier, monospace;
1164
+ color: #10b981 !important;
1165
+ box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.8);
1166
+ font-size: 13px;
1167
+ list-style-type: none;
1168
+ max-height: 120px;
1169
+ overflow-y: auto;
1170
+ }
1171
+ .logs li {
1172
+ margin-bottom: 6px;
1173
+ line-height: 1.5;
1174
+ display: flex;
1175
+ align-items: center;
1176
+ gap: 8px;
1177
+ }
1178
+ .logs li::before {
1179
+ content: ">";
1180
+ color: var(--glow-cyan);
1181
+ font-weight: 700;
1182
+ }
1183
+
1184
+ /* Beautiful Result Card & Medical Details */
1185
+ .result-card {
1186
+ border: 1px solid var(--border-glass) !important;
1187
+ background: var(--bg-glass) !important;
1188
+ border-radius: 16px !important;
1189
+ padding: 24px;
1190
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
1191
+ }
1192
+ .result-card h3 {
1193
+ color: #ffffff !important;
1194
+ margin: 0 0 16px;
1195
+ font-size: 20px;
1196
+ font-weight: 700;
1197
+ border-left: 3px solid var(--glow-green);
1198
+ padding-left: 10px;
1199
  }
 
1200
  .details {
1201
  display: grid;
1202
  grid-template-columns: 140px 1fr;
1203
+ gap: 10px 16px;
1204
+ margin: 0;
1205
+ }
1206
+ .details dt {
1207
+ color: var(--text-muted) !important;
1208
+ font-size: 14px;
1209
+ font-weight: 500;
1210
+ }
1211
+ .details dd {
1212
+ color: #ffffff !important;
1213
  margin: 0;
1214
+ font-weight: 650;
1215
+ font-size: 14px;
1216
  }
 
 
1217
  .explain {
1218
+ margin-top: 20px;
1219
+ border-top: 1px solid var(--border-glass);
1220
+ padding-top: 18px;
1221
+ }
1222
+ .explain h4 {
1223
+ color: #ffffff !important;
1224
+ margin: 0 0 12px;
1225
+ font-size: 16px;
1226
+ font-weight: 600;
1227
  }
1228
+ .explain p {
1229
+ margin: 8px 0;
1230
+ color: var(--text-main) !important;
1231
+ font-size: 13.5px;
1232
+ line-height: 1.6;
1233
+ }
1234
+ .explain p b {
1235
+ color: var(--glow-cyan);
1236
+ }
1237
+
1238
+ /* Stock Indicators */
1239
  .stock-card {
1240
  display: grid;
1241
  grid-template-columns: repeat(3, minmax(0, 1fr));
1242
+ gap: 16px;
1243
+ margin-top: 12px;
1244
+ border: 1px solid var(--border-glass) !important;
1245
+ background: var(--bg-glass) !important;
1246
+ border-radius: 16px !important;
1247
+ padding: 18px;
1248
+ }
1249
+ .stock-card span {
1250
+ color: var(--text-muted) !important;
1251
+ font-size: 12px;
1252
+ font-weight: 500;
1253
+ text-transform: uppercase;
1254
+ }
1255
+ .stock-card strong {
1256
+ color: #ffffff !important;
1257
+ display: block;
1258
+ font-size: 22px;
1259
+ margin-top: 4px;
1260
+ font-weight: 700;
1261
+ }
1262
+ .stock-card .ok {
1263
+ color: var(--text-success) !important;
1264
+ }
1265
+ .stock-card .bad {
1266
+ color: var(--text-danger) !important;
1267
+ }
1268
+
1269
+ /* Table Candidates */
1270
+ .candidate-table {
1271
+ width: 100%;
1272
+ border-collapse: collapse;
1273
+ background: transparent;
1274
  }
 
 
 
 
1275
  .candidate-table th, .candidate-table td {
1276
+ border-bottom: 1px solid var(--border-glass);
1277
+ padding: 12px 14px;
1278
  text-align: left;
1279
+ color: var(--text-main) !important;
1280
+ font-size: 13.5px;
1281
  }
1282
+ .candidate-table th {
1283
+ color: var(--text-muted) !important;
1284
+ font-weight: 600;
1285
+ text-transform: uppercase;
1286
+ font-size: 12px;
1287
+ background: rgba(255, 255, 255, 0.02);
1288
+ }
1289
+ .candidate-table tbody tr {
1290
+ transition: background 0.2s ease;
1291
+ }
1292
+ .candidate-table tbody tr:hover {
1293
+ background: rgba(255, 255, 255, 0.02);
1294
+ }
1295
+
1296
+ /* Compare Grid */
1297
  .compare-grid {
1298
  display: grid;
1299
  grid-template-columns: repeat(3, minmax(0, 1fr));
1300
+ gap: 16px;
1301
+ margin-top: 12px;
1302
  }
1303
  .compare-grid div {
1304
+ border: 1px solid var(--border-glass) !important;
1305
+ border-radius: 12px !important;
1306
+ padding: 16px;
1307
+ background: var(--bg-glass) !important;
1308
+ box-shadow: 0 4px 15px rgba(0,0,0,0.15);
1309
+ transition: all 0.3s ease;
1310
+ }
1311
+ .compare-grid div:hover {
1312
+ transform: translateY(-2px);
1313
+ border-color: rgba(99, 102, 241, 0.3) !important;
1314
+ }
1315
+ .compare-grid strong {
1316
+ color: #ffffff !important;
1317
+ display: block;
1318
+ margin-top: 6px;
1319
+ font-size: 17px;
1320
+ font-weight: 700;
1321
  }
1322
+
1323
+ /* Translated Prescription Output */
1324
  .translated-card {
1325
+ border: 1px solid var(--border-glass) !important;
1326
+ background: var(--bg-glass) !important;
1327
+ border-radius: 16px !important;
1328
+ padding: 24px;
1329
+ margin-top: 16px;
1330
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
1331
  }
1332
  .translated-head {
1333
  display: flex;
1334
  justify-content: space-between;
1335
  gap: 12px;
1336
  align-items: center;
1337
+ margin-bottom: 20px;
1338
+ }
1339
+ .translated-head h3 {
1340
+ color: #ffffff !important;
1341
+ margin: 0;
1342
+ font-size: 20px;
1343
+ font-weight: 700;
1344
  }
 
1345
  .status-pill {
1346
+ background: rgba(16, 185, 129, 0.12);
1347
+ border: 1px solid var(--border-active);
1348
+ color: var(--text-success) !important;
1349
  border-radius: 999px;
1350
+ padding: 5px 12px;
1351
  font-size: 12px;
1352
+ font-weight: 700;
1353
+ text-transform: uppercase;
1354
+ letter-spacing: 0.5px;
1355
+ }
1356
+ .status-pill.needs-review {
1357
+ background: rgba(245, 158, 11, 0.12);
1358
+ border: 1px solid rgba(245, 158, 11, 0.3);
1359
+ color: var(--text-warning) !important;
1360
  }
1361
  .translated-details {
1362
+ grid-template-columns: 140px 1fr;
1363
  }
1364
  .fine-print {
1365
+ border-top: 1px solid var(--border-glass);
1366
+ color: var(--text-muted) !important;
1367
+ margin: 20px 0 0;
1368
+ padding-top: 16px;
1369
+ font-size: 12.5px;
1370
+ line-height: 1.5;
1371
  }
1372
+
1373
+ /* Action Buttons Custom Styling */
1374
  .gradio-container button.primary,
1375
  .gradio-container button[variant="primary"] {
1376
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
1377
+ border: none !important;
1378
+ color: #ffffff !important;
1379
+ font-weight: 700 !important;
1380
+ text-transform: uppercase !important;
1381
+ letter-spacing: 0.5px !important;
1382
+ border-radius: 10px !important;
1383
+ padding: 12px 24px !important;
1384
+ box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3) !important;
1385
+ transition: all 0.2s ease !important;
1386
+ }
1387
+ .gradio-container button.primary:hover {
1388
+ transform: translateY(-2px) !important;
1389
+ box-shadow: 0 6px 20px rgba(16, 185, 129, 0.45), 0 0 12px rgba(16, 185, 129, 0.2) !important;
1390
+ }
1391
+ .gradio-container button.primary:active {
1392
+ transform: translateY(0px) !important;
1393
+ }
1394
+
1395
+ /* Secondary Buttons */
1396
+ .gradio-container button.secondary,
1397
+ .gradio-container button:not(.primary) {
1398
+ background: rgba(255, 255, 255, 0.04) !important;
1399
+ border: 1px solid var(--border-glass) !important;
1400
  color: #ffffff !important;
1401
+ border-radius: 10px !important;
1402
+ font-weight: 600 !important;
1403
+ transition: all 0.2s ease !important;
1404
  }
1405
+ .gradio-container button:not(.primary):hover {
1406
+ background: rgba(255, 255, 255, 0.08) !important;
1407
+ border-color: rgba(255, 255, 255, 0.15) !important;
1408
  }
1409
+
1410
+ /* Custom Accordion overrides */
1411
+ .gradio-container .accordion {
1412
+ border: 1px solid var(--border-glass) !important;
1413
+ background: var(--bg-glass) !important;
1414
+ border-radius: 16px !important;
1415
+ overflow: hidden;
1416
+ box-shadow: 0 4px 15px rgba(0,0,0,0.15);
1417
+ }
1418
+ .gradio-container .accordion .label-wrap {
1419
+ background: rgba(255, 255, 255, 0.02) !important;
1420
+ padding: 14px 18px !important;
1421
+ border-bottom: 1px solid var(--border-glass) !important;
1422
+ }
1423
+ .gradio-container .accordion .label-wrap span {
1424
+ color: #ffffff !important;
1425
+ font-weight: 600 !important;
1426
+ font-size: 15px;
1427
+ }
1428
+
1429
+ /* Gradio input boxes styling to fit theme */
1430
+ .gradio-container input, .gradio-container textarea, .gradio-container select {
1431
+ background: rgba(0, 0, 0, 0.3) !important;
1432
+ border: 1px solid var(--border-glass) !important;
1433
+ color: #ffffff !important;
1434
+ border-radius: 8px !important;
1435
+ }
1436
+ .gradio-container input:focus, .gradio-container textarea:focus {
1437
+ border-color: var(--glow-cyan) !important;
1438
+ box-shadow: 0 0 8px rgba(6, 182, 212, 0.2) !important;
1439
+ }
1440
+
1441
+ /* Fix mobile responsiveness */
1442
+ @media (max-width: 768px) {
1443
+ .app-shell { width: min(100% - 24px, 1120px); }
1444
+ .hero { display: block; padding: 20px; }
1445
+ .powered { text-align: left; margin-top: 12px; display: inline-block; }
1446
  .metric-row, .flow, .stock-card, .compare-grid { grid-template-columns: 1fr; }
1447
  .details { grid-template-columns: 1fr; }
1448
+ .translated-head { align-items: flex-start; flex-direction: column; gap: 8px; }
1449
+ .flow-step { min-height: 80px; }
1450
  }
1451
  """
1452
 
1453
 
1454
+ APP_THEME = gr.themes.Default(
1455
  primary_hue="emerald",
1456
+ secondary_hue="indigo",
1457
  neutral_hue="slate",
1458
+ font=[gr.themes.GoogleFont("Plus Jakarta Sans"), "ui-sans-serif", "system-ui", "sans-serif"],
1459
  )
1460
 
1461