riazmo Claude Opus 4.6 commited on
Commit
db8862f
·
1 Parent(s): 8d1b9cb

v3.4: UX revamp — Deep Violet theme, progress stepper, tab-based Stage 2

Browse files

Major UI overhaul with 7 changes:
1. Theme: Blue → Deep Violet (#6D28D9) accent — distinctive, not generic
2. Progress stepper: Horizontal ①→②→③→④→⑤ shows workflow state
3. Tab-based Stage 2: 5 tabs (Scores, Benchmarks, Typography, Colors, Spacing)
replaces infinite scrolling — reduces cognitive load
4. Always-visible log: Analysis log stays visible during loading overlay
5. As-Is → To-Be cards: Side-by-side comparison showing current vs recommended
values for type scale, spacing, colors, and shadows
6. Visual benchmark cards: HTML cards with per-category progress bars instead
of markdown table — shows match % for Type/Spacing/Colors/Radius/Shadows
7. Streamlined layout: Compact config, cleaner footer, consistent violet accents

All 113 tests pass. Return tuple expanded to 18 values (added asis_tobe_html).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +850 -709
app.py CHANGED
@@ -1435,11 +1435,11 @@ async def run_stage2_analysis_v2(
1435
  state.log(f" ⚠️ Formatting failed: {str(format_err)[:100]}")
1436
  import traceback
1437
  state.log(traceback.format_exc()[:500])
1438
- # Return minimal results (must match 16 outputs)
1439
  return (
1440
  f"⚠️ Analysis completed with formatting errors: {str(format_err)[:50]}",
1441
  state.get_logs(),
1442
- "*Benchmark comparison unavailable*",
1443
  "<div class='placeholder-msg'>Scores unavailable</div>",
1444
  "<div class='placeholder-msg'>Actions unavailable</div>",
1445
  [],
@@ -1454,6 +1454,7 @@ async def run_stage2_analysis_v2(
1454
  "*Formatting error - radius tokens unavailable*", # radius_md
1455
  "*Formatting error - shadow tokens unavailable*", # shadows_md
1456
  "⚠️ Color preview unavailable due to formatting errors.", # auto_color_preview
 
1457
  )
1458
 
1459
  # Auto-generate color classification preview
@@ -1465,6 +1466,51 @@ async def run_stage2_analysis_v2(
1465
  state.log(f" ⚠️ Auto color preview failed: {str(cp_err)}")
1466
  auto_color_preview = "⚠️ Color preview unavailable — click 'Preview Color Names' button to generate."
1467
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1468
  progress(0.95, desc="✅ Complete!")
1469
 
1470
  # Final log summary
@@ -1496,7 +1542,7 @@ async def run_stage2_analysis_v2(
1496
  state.log(f" 💰 TOTAL COST: ~$0.003")
1497
  state.log(f" ⏱️ COMPLETED: {datetime.now().strftime('%H:%M:%S')}")
1498
  state.log("═" * 60)
1499
-
1500
  return (
1501
  status_md,
1502
  state.get_logs(),
@@ -1515,6 +1561,7 @@ async def run_stage2_analysis_v2(
1515
  radius_md,
1516
  shadows_md,
1517
  auto_color_preview,
 
1518
  )
1519
 
1520
  except Exception as e:
@@ -1625,11 +1672,11 @@ def create_fallback_synthesis(rule_results, benchmark_comparisons, brand_result,
1625
 
1626
 
1627
  def create_stage2_error_response(error_msg: str):
1628
- """Create error response tuple for Stage 2 (must match 17 outputs)."""
1629
  return (
1630
  error_msg,
1631
  state.get_logs(),
1632
- "", # benchmark_md
1633
  f"<div class='placeholder-msg'>{error_msg}</div>", # scores_html
1634
  "", # actions_html
1635
  [], # color_recs_table
@@ -1644,6 +1691,7 @@ def create_stage2_error_response(error_msg: str):
1644
  "*Run analysis to see radius tokens*", # radius_md
1645
  "*Run analysis to see shadow tokens*", # shadows_md
1646
  "", # auto_color_preview
 
1647
  )
1648
 
1649
 
@@ -1686,88 +1734,8 @@ def format_stage2_status_v2(rule_results, final_synthesis, best_practices) -> st
1686
 
1687
 
1688
  def format_benchmark_comparison_v2(benchmark_comparisons, benchmark_advice) -> str:
1689
- """Format benchmark comparison results ALL 6 categories."""
1690
-
1691
- if not benchmark_comparisons:
1692
- return "*No benchmark comparison available*"
1693
-
1694
- lines = []
1695
- lines.append("## 📊 Benchmark Comparison (6 Categories)")
1696
- lines.append("")
1697
-
1698
- # Recommended benchmark
1699
- if benchmark_advice and benchmark_advice.recommended_benchmark_name:
1700
- lines.append(f"### 🏆 Recommended: {benchmark_advice.recommended_benchmark_name}")
1701
- if benchmark_advice.reasoning:
1702
- lines.append(f"*{benchmark_advice.reasoning}*")
1703
- lines.append("")
1704
-
1705
- # Full comparison table with all 6 categories
1706
- lines.append("### 📈 Similarity Ranking")
1707
- lines.append("")
1708
- lines.append("| Rank | Design System | Overall | Type | Spacing | Colors | Radius | Shadows |")
1709
- lines.append("|------|---------------|---------|------|---------|--------|--------|---------|")
1710
-
1711
- medals = ["🥇", "🥈", "🥉"]
1712
- for i, c in enumerate(benchmark_comparisons[:5]):
1713
- medal = medals[i] if i < 3 else str(i+1)
1714
- b = c.benchmark
1715
-
1716
- def pct_icon(pct):
1717
- if pct >= 80: return f"✅ {pct:.0f}%"
1718
- elif pct >= 50: return f"🟡 {pct:.0f}%"
1719
- else: return f"🔴 {pct:.0f}%"
1720
-
1721
- lines.append(
1722
- f"| {medal} | {b.icon} {b.short_name} | **{c.overall_match_pct:.0f}%** | "
1723
- f"{pct_icon(c.type_match_pct)} | {pct_icon(c.spacing_match_pct)} | "
1724
- f"{pct_icon(c.color_match_pct)} | {pct_icon(c.radius_match_pct)} | "
1725
- f"{pct_icon(c.shadow_match_pct)} |"
1726
- )
1727
-
1728
- lines.append("")
1729
-
1730
- # Detailed per-category comparison for top benchmark
1731
- if benchmark_comparisons:
1732
- top = benchmark_comparisons[0]
1733
- b = top.benchmark
1734
- lines.append(f"### 🔍 Detailed: Your Site vs {b.icon} {b.short_name}")
1735
- lines.append("")
1736
- lines.append("| Category | Your Value | Benchmark | Gap | Match |")
1737
- lines.append("|----------|-----------|-----------|-----|-------|")
1738
- lines.append(f"| **Typography** | ratio {top.type_ratio_diff + b.typography.get('scale_ratio', 1.25):.2f} | ratio {b.typography.get('scale_ratio', '?')} | diff {top.type_ratio_diff:.2f} | {top.type_match_pct:.0f}% |")
1739
- lines.append(f"| **Base Size** | {top.base_size_diff + b.typography.get('base_size', 16)}px | {b.typography.get('base_size', '?')}px | diff {top.base_size_diff}px | — |")
1740
- lines.append(f"| **Spacing** | {top.spacing_grid_diff + b.spacing.get('base', 8)}px grid | {b.spacing.get('base', '?')}px grid | diff {top.spacing_grid_diff}px | {top.spacing_match_pct:.0f}% |")
1741
- lines.append(f"| **Colors** | — | {b.colors.get('palette_size', '?')} colors | {top.color_gap or 'N/A'} | {top.color_match_pct:.0f}% |")
1742
- b_radius = b.radius if hasattr(b, 'radius') and b.radius else {}
1743
- b_shadows = b.shadows if hasattr(b, 'shadows') and b.shadows else {}
1744
- lines.append(f"| **Radius** | — | {b_radius.get('tiers', '?')} tiers ({b_radius.get('strategy', '?')}) | {top.radius_gap or 'N/A'} | {top.radius_match_pct:.0f}% |")
1745
- lines.append(f"| **Shadows** | — | {b_shadows.get('levels', '?')} levels | {top.shadow_gap or 'N/A'} | {top.shadow_match_pct:.0f}% |")
1746
-
1747
- lines.append("")
1748
-
1749
- # Alignment changes needed
1750
- if benchmark_advice and benchmark_advice.alignment_changes:
1751
- lines.append("### 🔧 Changes to Align")
1752
- for change in benchmark_advice.alignment_changes[:5]:
1753
- token_type = change.get('token_type', '')
1754
- icon = {"typography": "📐", "spacing": "📏", "colors": "🎨", "radius": "🔘", "shadows": "🌗"}.get(token_type, "🔧")
1755
- lines.append(f"- {icon} **{change.get('change', '?')}**: {change.get('from', '?')} → {change.get('to', '?')} (effort: {change.get('effort', '?')})")
1756
- lines.append("")
1757
-
1758
- # Pros and cons
1759
- if benchmark_advice:
1760
- if benchmark_advice.pros_of_alignment:
1761
- lines.append("**✅ Pros of aligning:**")
1762
- for pro in benchmark_advice.pros_of_alignment[:3]:
1763
- lines.append(f"- {pro}")
1764
- if benchmark_advice.cons_of_alignment:
1765
- lines.append("")
1766
- lines.append("**⚠️ Considerations:**")
1767
- for con in benchmark_advice.cons_of_alignment[:3]:
1768
- lines.append(f"- {con}")
1769
-
1770
- return "\n".join(lines)
1771
 
1772
 
1773
  def format_scores_dashboard_v2(rule_results, final_synthesis, best_practices) -> str:
@@ -3582,6 +3550,161 @@ def export_tokens_json(convention: str = "semantic"):
3582
  return json_str
3583
 
3584
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3585
  # =============================================================================
3586
  # UI BUILDING
3587
  # =============================================================================
@@ -3589,9 +3712,9 @@ def export_tokens_json(convention: str = "semantic"):
3589
  def create_ui():
3590
  """Create the Gradio interface with corporate branding."""
3591
 
3592
- # Corporate theme customization
3593
  corporate_theme = gr.themes.Base(
3594
- primary_hue=gr.themes.colors.blue,
3595
  secondary_hue=gr.themes.colors.slate,
3596
  neutral_hue=gr.themes.colors.slate,
3597
  font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
@@ -3604,72 +3727,157 @@ def create_ui():
3604
  block_background_fill_dark="#1e293b",
3605
  block_border_color="#e2e8f0",
3606
  block_border_color_dark="#334155",
3607
- block_label_background_fill="#f1f5f9",
3608
  block_label_background_fill_dark="#1e293b",
3609
  block_title_text_color="#0f172a",
3610
  block_title_text_color_dark="#f1f5f9",
3611
-
3612
- # Primary button
3613
- button_primary_background_fill="#2563eb",
3614
- button_primary_background_fill_hover="#1d4ed8",
3615
  button_primary_text_color="white",
3616
-
3617
  # Secondary button
3618
- button_secondary_background_fill="#f1f5f9",
3619
- button_secondary_background_fill_hover="#e2e8f0",
3620
  button_secondary_text_color="#1e293b",
3621
-
3622
  # Input fields
3623
  input_background_fill="#ffffff",
3624
  input_background_fill_dark="#1e293b",
3625
  input_border_color="#cbd5e1",
3626
  input_border_color_dark="#475569",
3627
-
3628
  # Shadows and radius
3629
  block_shadow="0 1px 3px rgba(0,0,0,0.1)",
3630
  block_shadow_dark="0 1px 3px rgba(0,0,0,0.3)",
3631
  block_border_width="1px",
3632
  block_radius="8px",
3633
-
3634
  # Text
3635
  body_text_color="#1e293b",
3636
  body_text_color_dark="#e2e8f0",
3637
  body_text_size="14px",
3638
  )
3639
 
3640
- # Custom CSS for additional styling
3641
  custom_css = """
3642
- /* Global styles */
 
 
3643
  .gradio-container {
3644
  max-width: 1400px !important;
3645
  margin: 0 auto !important;
3646
  }
3647
-
3648
- /* Header branding */
 
 
3649
  .app-header {
3650
- background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
3651
- padding: 24px 32px;
3652
  border-radius: 12px;
3653
- margin-bottom: 24px;
3654
  color: white;
 
 
 
 
 
 
 
 
 
 
 
 
3655
  }
3656
  .app-header h1 {
3657
- margin: 0 0 8px 0;
3658
- font-size: 28px;
3659
  font-weight: 700;
 
3660
  }
3661
  .app-header p {
3662
  margin: 0;
3663
- opacity: 0.9;
3664
- font-size: 14px;
3665
  }
3666
-
3667
- /* Stage indicators */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3668
  .stage-header {
3669
- background: linear-gradient(90deg, #f1f5f9 0%, #ffffff 100%);
3670
  padding: 16px 20px;
3671
  border-radius: 8px;
3672
- border-left: 4px solid #2563eb;
3673
  margin-bottom: 16px;
3674
  }
3675
  .stage-header h2 {
@@ -3677,8 +3885,10 @@ def create_ui():
3677
  font-size: 18px;
3678
  color: #1e293b;
3679
  }
3680
-
3681
- /* Log styling */
 
 
3682
  .log-container textarea {
3683
  font-family: 'JetBrains Mono', monospace !important;
3684
  font-size: 12px !important;
@@ -3687,8 +3897,18 @@ def create_ui():
3687
  color: #e2e8f0 !important;
3688
  border-radius: 8px !important;
3689
  }
3690
-
3691
- /* Color swatch */
 
 
 
 
 
 
 
 
 
 
3692
  .color-swatch {
3693
  display: inline-block;
3694
  width: 24px;
@@ -3698,8 +3918,10 @@ def create_ui():
3698
  vertical-align: middle;
3699
  border: 1px solid rgba(0,0,0,0.1);
3700
  }
3701
-
3702
- /* Score badges */
 
 
3703
  .score-badge {
3704
  display: inline-block;
3705
  padding: 4px 12px;
@@ -3710,8 +3932,10 @@ def create_ui():
3710
  .score-badge.high { background: #dcfce7; color: #166534; }
3711
  .score-badge.medium { background: #fef3c7; color: #92400e; }
3712
  .score-badge.low { background: #fee2e2; color: #991b1b; }
3713
-
3714
- /* Benchmark cards */
 
 
3715
  .benchmark-card {
3716
  background: #f8fafc;
3717
  border: 1px solid #e2e8f0;
@@ -3720,11 +3944,141 @@ def create_ui():
3720
  margin-bottom: 12px;
3721
  }
3722
  .benchmark-card.selected {
3723
- border-color: #2563eb;
3724
- background: #eff6ff;
3725
  }
3726
-
3727
- /* Action items */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3728
  .action-item {
3729
  background: white;
3730
  border: 1px solid #e2e8f0;
@@ -3738,8 +4092,10 @@ def create_ui():
3738
  .action-item.medium-priority {
3739
  border-left: 4px solid #f59e0b;
3740
  }
3741
-
3742
- /* Progress indicator */
 
 
3743
  .progress-bar {
3744
  height: 4px;
3745
  background: #e2e8f0;
@@ -3748,22 +4104,19 @@ def create_ui():
3748
  }
3749
  .progress-bar-fill {
3750
  height: 100%;
3751
- background: linear-gradient(90deg, #2563eb, #3b82f6);
3752
  transition: width 0.3s ease;
3753
  }
3754
-
3755
- /* Accordion styling */
3756
- .accordion-header {
3757
- font-weight: 600 !important;
3758
- }
3759
-
3760
- /* Table styling */
3761
  table {
3762
  border-collapse: collapse;
3763
  width: 100%;
3764
  }
3765
  th {
3766
- background: #f1f5f9;
3767
  color: #1e293b;
3768
  padding: 12px;
3769
  text-align: left;
@@ -3776,7 +4129,9 @@ def create_ui():
3776
  border-bottom: 1px solid #e2e8f0;
3777
  }
3778
 
3779
- /* Section descriptions */
 
 
3780
  .section-desc p, .section-desc {
3781
  font-size: 13px !important;
3782
  color: #64748b !important;
@@ -3788,68 +4143,97 @@ def create_ui():
3788
  color: #94a3b8 !important;
3789
  }
3790
 
3791
- /* Success messages */
 
 
3792
  .success-msg { background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 8px; padding: 16px; margin: 8px 0; }
3793
  .success-msg h2 { color: #166534 !important; }
3794
  .dark .success-msg { background: #052e16 !important; border-color: #166534 !important; }
3795
  .dark .success-msg h2 { color: #bbf7d0 !important; }
3796
  .dark .success-msg p { color: #d1d5db !important; }
3797
-
3798
- /* Error messages */
3799
  .error-msg { background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; padding: 16px; margin: 8px 0; }
3800
  .error-msg h2 { color: #991b1b !important; }
3801
  .dark .error-msg { background: #450a0a !important; border-color: #991b1b !important; }
3802
  .dark .error-msg h2 { color: #fecaca !important; }
3803
  .dark .error-msg p { color: #d1d5db !important; }
3804
 
3805
- /* Placeholder messages */
 
 
3806
  .placeholder-msg {
3807
  padding: 20px;
3808
- background: #f5f5f5;
3809
  border-radius: 8px;
3810
- color: #666;
 
 
3811
  }
3812
  .placeholder-msg.placeholder-lg {
3813
  padding: 40px;
3814
- text-align: center;
3815
  }
3816
 
3817
- /* Progress bar */
3818
- .progress-bar {
3819
- background: #e2e8f0;
3820
- }
3821
 
3822
- /* Dark mode adjustments */
3823
- .dark .stage-header {
3824
- background: linear-gradient(90deg, #1e293b 0%, #0f172a 100%);
3825
- border-left-color: #3b82f6;
3826
- }
3827
- .dark .stage-header h2 {
3828
- color: #f1f5f9;
3829
- }
3830
- .dark .stage-header-subtitle,
3831
- .dark .tip-text {
3832
- color: #94a3b8 !important;
3833
- }
3834
- .dark .benchmark-card {
3835
  background: #1e293b;
3836
  border-color: #334155;
3837
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3838
  .dark .action-item {
3839
  background: #1e293b;
3840
  border-color: #475569;
3841
  color: #e2e8f0;
3842
  }
3843
- /* Dark mode: Placeholder messages */
 
 
 
3844
  .dark .placeholder-msg {
3845
- background: #1e293b !important;
3846
- color: #94a3b8 !important;
3847
- }
3848
- /* Dark mode: Progress bar */
3849
- .dark .progress-bar {
3850
- background: #334155 !important;
3851
  }
3852
- /* Dark mode: Gradio Dataframe tables */
 
3853
  .dark table th {
3854
  background: #1e293b !important;
3855
  color: #e2e8f0 !important;
@@ -3859,238 +4243,89 @@ def create_ui():
3859
  color: #e2e8f0 !important;
3860
  border-bottom-color: #334155 !important;
3861
  }
3862
- .dark table tr {
3863
- background: #0f172a !important;
3864
- }
3865
- .dark table tr:nth-child(even) {
3866
- background: #1e293b !important;
3867
- }
3868
- /* Dark mode: HTML preview tables (typography, benchmarks) */
3869
- .dark .typography-preview {
3870
- background: #1e293b !important;
3871
- }
3872
- .dark .typography-preview th {
3873
- background: #334155 !important;
3874
- color: #e2e8f0 !important;
3875
- border-bottom-color: #475569 !important;
3876
- }
3877
- .dark .typography-preview td {
3878
- color: #e2e8f0 !important;
3879
- }
3880
- .dark .typography-preview .meta-row {
3881
- background: #1e293b !important;
3882
- border-top-color: #334155 !important;
3883
- }
3884
  .dark .typography-preview .scale-name,
3885
- .dark .typography-preview .scale-label {
3886
- color: #f1f5f9 !important;
3887
- background: #475569 !important;
3888
- }
3889
- .dark .typography-preview .meta {
3890
- color: #cbd5e1 !important;
3891
- }
3892
- .dark .typography-preview .preview-cell {
3893
- background: #0f172a !important;
3894
- border-bottom-color: #334155 !important;
3895
- }
3896
- .dark .typography-preview .preview-text {
3897
- color: #f1f5f9 !important;
3898
- }
3899
- .dark .typography-preview tr:hover .preview-cell {
3900
- background: #1e293b !important;
3901
- }
3902
-
3903
- /* Dark mode: Colors AS-IS preview */
3904
- .dark .colors-asis-header {
3905
- color: #e2e8f0 !important;
3906
- background: #1e293b !important;
3907
- }
3908
- .dark .colors-asis-preview {
3909
- background: #0f172a !important;
3910
- }
3911
- .dark .color-row-asis {
3912
- background: #1e293b !important;
3913
- border-color: #475569 !important;
3914
- }
3915
- .dark .color-name-asis {
3916
- color: #f1f5f9 !important;
3917
- }
3918
- .dark .frequency {
3919
- color: #cbd5e1 !important;
3920
- }
3921
- .dark .color-meta-asis .aa-pass {
3922
- color: #22c55e !important;
3923
- background: #14532d !important;
3924
- }
3925
- .dark .color-meta-asis .aa-fail {
3926
- color: #f87171 !important;
3927
- background: #450a0a !important;
3928
- }
3929
- .dark .context-badge {
3930
- background: #334155 !important;
3931
- color: #e2e8f0 !important;
3932
- }
 
 
 
 
 
 
 
 
3933
 
3934
- /* Dark mode: Color ramps preview */
3935
- .dark .color-ramps-preview {
3936
- background: #0f172a !important;
3937
- }
3938
- .dark .ramps-header-info {
3939
- color: #e2e8f0 !important;
3940
- background: #1e293b !important;
3941
- }
3942
- .dark .ramp-header {
3943
- background: #1e293b !important;
3944
- }
3945
- .dark .ramp-header-label {
3946
- color: #cbd5e1 !important;
3947
- }
3948
- .dark .color-row {
3949
- background: #1e293b !important;
3950
- border-color: #475569 !important;
3951
- }
3952
- .dark .color-name {
3953
- color: #f1f5f9 !important;
3954
- background: #475569 !important;
3955
- }
3956
- .dark .color-hex {
3957
- color: #cbd5e1 !important;
3958
- }
3959
-
3960
- /* Dark mode: Spacing preview */
3961
- .dark .spacing-asis-preview {
3962
- background: #0f172a !important;
3963
- }
3964
- .dark .spacing-row-asis {
3965
- background: #1e293b !important;
3966
- }
3967
- .dark .spacing-label {
3968
- color: #f1f5f9 !important;
3969
- }
3970
-
3971
- /* Dark mode: Radius preview */
3972
- .dark .radius-asis-preview {
3973
- background: #0f172a !important;
3974
- }
3975
- .dark .radius-item {
3976
- background: #1e293b !important;
3977
- }
3978
- .dark .radius-label {
3979
- color: #f1f5f9 !important;
3980
- }
3981
-
3982
- /* Dark mode: Shadows preview */
3983
- .dark .shadows-asis-preview {
3984
- background: #0f172a !important;
3985
- }
3986
- .dark .shadow-item {
3987
- background: #1e293b !important;
3988
- }
3989
- .dark .shadow-box {
3990
- background: #334155 !important;
3991
- }
3992
- .dark .shadow-label {
3993
- color: #f1f5f9 !important;
3994
- }
3995
- .dark .shadow-value {
3996
- color: #94a3b8 !important;
3997
- }
3998
-
3999
- /* Dark mode: Semantic color ramps */
4000
- .dark .sem-ramps-preview {
4001
- background: #0f172a !important;
4002
- }
4003
- .dark .sem-category {
4004
- background: #1e293b !important;
4005
- border-color: #475569 !important;
4006
- }
4007
- .dark .sem-cat-title {
4008
- color: #f1f5f9 !important;
4009
- border-bottom-color: #475569 !important;
4010
- }
4011
- .dark .sem-color-row {
4012
- background: #0f172a !important;
4013
- border-color: #334155 !important;
4014
- }
4015
- .dark .sem-role {
4016
- color: #f1f5f9 !important;
4017
- }
4018
- .dark .sem-hex {
4019
- color: #cbd5e1 !important;
4020
- }
4021
- .dark .llm-rec {
4022
- background: #422006 !important;
4023
- border-color: #b45309 !important;
4024
- }
4025
- .dark .rec-label {
4026
- color: #fbbf24 !important;
4027
- }
4028
- .dark .rec-issue {
4029
- color: #fde68a !important;
4030
- }
4031
- .dark .rec-arrow {
4032
- color: #fbbf24 !important;
4033
- }
4034
- .dark .llm-summary {
4035
- background: #1e3a5f !important;
4036
- border-color: #3b82f6 !important;
4037
- }
4038
- .dark .llm-summary h4 {
4039
- color: #93c5fd !important;
4040
- }
4041
- .dark .llm-summary ul,
4042
- .dark .llm-summary li {
4043
- color: #bfdbfe !important;
4044
- }
4045
-
4046
- /* Dark mode: Score badges */
4047
  .dark .score-badge.high { background: #14532d; color: #86efac; }
4048
  .dark .score-badge.medium { background: #422006; color: #fde68a; }
4049
  .dark .score-badge.low { background: #450a0a; color: #fca5a5; }
4050
 
4051
- /* Dark mode: Benchmark & action cards */
4052
- .dark .benchmark-card.selected {
4053
- border-color: #3b82f6;
4054
- background: #1e3a5f;
4055
- }
4056
- .dark .action-item.high-priority {
4057
- border-left-color: #ef4444;
4058
- }
4059
- .dark .action-item.medium-priority {
4060
- border-left-color: #f59e0b;
4061
- }
4062
-
4063
- /* Dark mode: Gradio markdown rendered tables */
4064
- .dark .prose table th,
4065
- .dark .markdown-text table th {
4066
- background: #1e293b !important;
4067
- color: #e2e8f0 !important;
4068
- border-color: #475569 !important;
4069
- }
4070
- .dark .prose table td,
4071
- .dark .markdown-text table td {
4072
- color: #e2e8f0 !important;
4073
- border-color: #334155 !important;
4074
- }
4075
- .dark .prose table tr,
4076
- .dark .markdown-text table tr {
4077
- background: #0f172a !important;
4078
- }
4079
- .dark .prose table tr:nth-child(even),
4080
- .dark .markdown-text table tr:nth-child(even) {
4081
- background: #1e293b !important;
4082
- }
4083
 
4084
- /* Dark mode: Generic text in HTML components */
4085
- .dark .gradio-html p,
4086
- .dark .gradio-html span,
4087
- .dark .gradio-html div {
4088
- color: #e2e8f0;
4089
- }
4090
  """
4091
 
4092
  with gr.Blocks(
4093
- title="Design System Extractor v2",
4094
  theme=corporate_theme,
4095
  css=custom_css
4096
  ) as app:
@@ -4098,14 +4333,16 @@ def create_ui():
4098
  # Header with branding
4099
  gr.HTML("""
4100
  <div class="app-header">
4101
- <h1>🎨 Design System Extractor v2</h1>
4102
  <p>Reverse-engineer design systems from live websites • AI-powered analysis • Figma-ready export</p>
4103
  </div>
4104
  """)
4105
- gr.Markdown("This tool works in **3 stages**: (1) Discover & extract design tokens from a live website, "
4106
- "(2) Run AI-powered analysis to benchmark and improve your tokens, "
4107
- "(3) Export Figma-ready JSON. Start by entering a URL below.",
4108
- elem_classes=["section-desc"])
 
 
4109
 
4110
  # =================================================================
4111
  # CONFIGURATION
@@ -4286,7 +4523,7 @@ def create_ui():
4286
  # =================================================================
4287
 
4288
  with gr.Accordion("🧠 Stage 2: AI-Powered Analysis", open=False) as stage2_accordion:
4289
-
4290
  # Stage header
4291
  gr.HTML("""
4292
  <div class="stage-header">
@@ -4294,317 +4531,217 @@ def create_ui():
4294
  <p class="stage-header-subtitle" style="color: #64748b; margin-top: 4px;">Rule Engine + Benchmark Research + LLM Agents</p>
4295
  </div>
4296
  """)
4297
-
4298
  stage2_status = gr.Markdown("Click **'Run Analysis'** below to start AI-powered design system analysis. "
4299
  "This runs a 4-layer pipeline: Rule Engine → Benchmark Research → LLM Agents → Head Synthesizer.")
4300
 
4301
- # =============================================================
4302
- # NEW ARCHITECTURE CONFIGURATION
4303
- # =============================================================
4304
- with gr.Accordion("⚙️ Analysis Configuration", open=True):
4305
-
4306
- # Architecture explanation
4307
- gr.Markdown("""
4308
- ### 🏗️ New Analysis Architecture
4309
-
4310
- | Layer | Type | What It Does | Cost |
4311
- |-------|------|--------------|------|
4312
- | **Layer 1** | Rule Engine | Type scale, AA check, spacing grid, color stats | FREE |
4313
- | **Layer 2** | Benchmark Research | Fetch live specs via Firecrawl (24h cache) | ~$0.001 |
4314
- | **Layer 3** | LLM Agents | Brand ID, Benchmark Advisor, Best Practices | ~$0.002 |
4315
- | **Layer 4** | HEAD Synthesizer | Combine all → Final recommendations | ~$0.001 |
4316
-
4317
- **Total Cost:** ~$0.003-0.004 per analysis
4318
- """)
4319
-
4320
- gr.Markdown("---")
4321
-
4322
- # Benchmark selection
4323
- gr.Markdown("### 📊 Select Design Systems to Compare Against")
4324
- gr.Markdown("*Choose which design systems to benchmark your tokens against:*")
4325
-
4326
- benchmark_checkboxes = gr.CheckboxGroup(
4327
- choices=[
4328
- ("🟢 Material Design 3 (Google)", "material_design_3"),
4329
- ("🍎 Apple HIG", "apple_hig"),
4330
- ("🛒 Shopify Polaris", "shopify_polaris"),
4331
- ("🔵 Atlassian Design System", "atlassian_design"),
4332
- ("🔷 IBM Carbon", "ibm_carbon"),
4333
- ("🌊 Tailwind CSS", "tailwind_css"),
4334
- ("🐜 Ant Design", "ant_design"),
4335
- ("⚡ Chakra UI", "chakra_ui"),
4336
- ],
4337
- value=["material_design_3", "shopify_polaris", "atlassian_design"],
4338
- label="Benchmarks",
4339
- )
4340
-
4341
- gr.Markdown("""
4342
- <small class="tip-text" style="color: #64748b;">
4343
- 💡 <b>Tip:</b> Select 2-4 benchmarks for best results. More benchmarks = longer analysis time.
4344
- <br>
4345
- 📦 Results are cached for 24 hours to speed up subsequent analyses.
4346
- </small>
4347
- """)
4348
-
4349
- gr.Markdown("**Run Analysis** triggers the 4-layer architecture: Rule Engine (free) "
4350
- "then AURORA + ATLAS + SENTINEL in parallel, then NEXUS compiles. "
4351
- "Review scores, recommendations, and visual previews below, then apply your chosen upgrades.",
4352
  elem_classes=["section-desc"])
4353
 
4354
- # Analyze button
4355
- with gr.Row():
4356
- analyze_btn_v2 = gr.Button(
4357
- "🚀 Run Analysis",
4358
- variant="primary",
4359
- size="lg",
4360
- scale=2
4361
- )
4362
-
4363
- # =============================================================
4364
- # ANALYSIS LOG
4365
- # =============================================================
4366
- with gr.Accordion("📋 Analysis Log", open=True):
4367
- gr.Markdown("*Real-time log of the analysis pipeline. Each layer reports its progress, results, and any errors. "
4368
- "Scroll through to see detailed statistics and individual agent outputs.*",
4369
- elem_classes=["section-desc"])
4370
  stage2_log = gr.Textbox(
4371
- label="📋 Analysis Log (full step-by-step reasoning)",
4372
- lines=30,
4373
  interactive=False,
4374
  elem_classes=["log-container"]
4375
  )
4376
-
4377
- # =============================================================
4378
- # SCORES DASHBOARD
4379
- # =============================================================
4380
- gr.Markdown("---")
4381
- gr.Markdown("## 📊 Analysis Results")
4382
- gr.Markdown("*Overall scores for your design system across accessibility, consistency, brand alignment, and best practices. "
4383
- "Each score is out of 100 — aim for 70+ in all categories. Priority actions below show the highest-impact fixes.*",
4384
- elem_classes=["section-desc"])
4385
 
4386
- scores_dashboard = gr.HTML(
4387
- value="<div class='placeholder-msg placeholder-lg'>Scores will appear after analysis...</div>",
4388
- label="Scores"
4389
- )
4390
-
4391
- # =============================================================
4392
- # PRIORITY ACTIONS
4393
- # =============================================================
4394
- priority_actions_html = gr.HTML(
4395
- value="<div class='placeholder-msg'>Priority actions will appear after analysis...</div>",
4396
- label="Priority Actions"
4397
- )
4398
-
4399
- # =============================================================
4400
- # BENCHMARK COMPARISON
4401
- # =============================================================
4402
- gr.Markdown("---")
4403
- gr.Markdown("## 📊 Benchmark Comparison")
4404
- gr.Markdown("*Your design tokens compared against industry-leading design systems (Material Design 3, Shopify Polaris, etc.). "
4405
- "Shows how closely your type scale, spacing grid, and color palette align with each benchmark. "
4406
- "Helps you decide which system to adopt or draw inspiration from.*",
4407
- elem_classes=["section-desc"])
4408
- benchmark_comparison_md = gr.Markdown("*Benchmark comparison will appear after analysis*")
4409
-
4410
- # =============================================================
4411
- # COLOR RECOMMENDATIONS
4412
- # =============================================================
4413
- gr.Markdown("---")
4414
- gr.Markdown("## 🎨 Color Recommendations")
4415
- gr.Markdown("*AI-suggested color changes based on WCAG AA compliance, brand consistency, and industry best practices. "
4416
- "Each recommendation shows the current color, the issue found, and a suggested replacement. "
4417
- "Use the checkboxes to accept or reject individual changes before exporting.*",
4418
- elem_classes=["section-desc"])
4419
-
4420
- # =============================================================
4421
- # TYPOGRAPHY SECTION
4422
- # =============================================================
4423
- gr.Markdown("---")
4424
- gr.Markdown("## 📐 Typography")
4425
- gr.Markdown("*Your detected type scale compared against standard ratios (Minor Third 1.2, Major Third 1.25, Perfect Fourth 1.333). "
4426
- "The visual preview shows how text will look at each scale. Desktop and mobile sizes are shown separately — "
4427
- "choose a scale below to apply to your exported tokens.*",
4428
- elem_classes=["section-desc"])
4429
 
4430
- with gr.Accordion("👁️ Typography Visual Preview", open=True):
4431
- stage2_typography_preview = gr.HTML(
4432
- value="<div class='placeholder-msg'>Typography preview will appear after analysis...</div>",
4433
- label="Typography Preview"
4434
- )
4435
-
4436
- with gr.Row():
4437
- with gr.Column(scale=2):
4438
- gr.Markdown("### 🖥️ Desktop (1440px)")
4439
- typography_desktop = gr.Dataframe(
4440
- headers=["Token", "Current", "Scale 1.2", "Scale 1.25 ⭐", "Scale 1.333", "Keep"],
4441
- datatype=["str", "str", "str", "str", "str", "str"],
4442
- label="Desktop Typography",
4443
- interactive=False,
4444
  )
4445
-
4446
- with gr.Column(scale=2):
4447
- gr.Markdown("### 📱 Mobile (375px)")
4448
- typography_mobile = gr.Dataframe(
4449
- headers=["Token", "Current", "Scale 1.2", "Scale 1.25 ⭐", "Scale 1.333", "Keep"],
4450
- datatype=["str", "str", "str", "str", "str", "str"],
4451
- label="Mobile Typography",
4452
- interactive=False,
4453
  )
4454
-
4455
- with gr.Row():
4456
- with gr.Column():
4457
- gr.Markdown("### Select Type Scale Option")
4458
- type_scale_radio = gr.Radio(
4459
- choices=["Keep Current", "Scale 1.2 (Minor Third)", "Scale 1.25 (Major Third) ⭐", "Scale 1.333 (Perfect Fourth)"],
4460
- value="Scale 1.25 (Major Third) ⭐",
4461
- label="Type Scale",
4462
- interactive=True,
4463
  )
4464
- gr.Markdown("*Font family will be preserved. Sizes rounded to even numbers.*")
4465
-
4466
- # =============================================================
4467
- # COLORS SECTION - Base Colors + Ramps + LLM Recommendations
4468
- # =============================================================
4469
- gr.Markdown("---")
4470
- gr.Markdown("## 🎨 Colors")
4471
- gr.Markdown("*Complete color analysis: base colors extracted from your site, AI-generated semantic color ramps (50–950 shades), "
4472
- "and LLM-powered recommendations for accessibility fixes. The visual preview groups colors by semantic role "
4473
- "(brand, text, background, border, feedback).*",
4474
- elem_classes=["section-desc"])
4475
 
4476
- # ── Color Naming Convention Preview (visible BEFORE export) ──
4477
- with gr.Accordion("🏷️ Color Naming Convention — Preview Before Export", open=True):
4478
- gr.Markdown("**Choose how colors are named in your export.** Preview the classification to verify names before exporting. "
4479
- "100% rule-based — no LLM involved. Change convention anytime and re-preview.",
4480
- elem_classes=["section-desc"])
4481
- with gr.Row():
4482
- naming_convention_stage2 = gr.Dropdown(
4483
- choices=["semantic", "tailwind", "material"],
4484
- value="semantic",
4485
- label="🎨 Naming Convention",
4486
- info="semantic = color.brand.primary | tailwind = brand-primary | material = color.brand.primary",
4487
- scale=2,
4488
  )
4489
- preview_colors_btn_stage2 = gr.Button("👁️ Preview Color Names", variant="secondary", scale=1)
4490
- color_preview_output_stage2 = gr.Textbox(
4491
- label="Color Classification Preview (Rule-Based — No LLM)",
4492
- lines=18,
4493
- max_lines=40,
4494
- interactive=False,
4495
- placeholder="Click 'Preview Color Names' above to see how colors will be named in the export. "
4496
- "This runs AFTER extraction (Stage 1). No LLM cost.",
4497
- )
4498
 
4499
- # LLM Recommendations Section (NEW)
4500
- with gr.Accordion("🤖 LLM Color Recommendations", open=True):
4501
- gr.Markdown("*Four AI agents analyzed your colors: **Brand Identifier** (detects primary/secondary brand colors), "
4502
- "**Benchmark Advisor** (compares to design system standards), **Best Practices Auditor** (WCAG, contrast, naming), "
4503
- "and **Head Synthesizer** (combines all findings into actionable suggestions). Use the table to accept or reject each change.*",
4504
- elem_classes=["section-desc"])
4505
-
4506
- llm_color_recommendations = gr.HTML(
4507
- value="<div class='placeholder-msg'>LLM recommendations will appear after analysis...</div>",
4508
- label="LLM Recommendations"
4509
- )
4510
-
4511
- # Accept/Reject table for color recommendations
4512
- color_recommendations_table = gr.Dataframe(
4513
- headers=["Accept", "Role", "Current", "Issue", "Suggested", "Contrast"],
4514
- datatype=["bool", "str", "str", "str", "str", "str"],
4515
- label="Color Recommendations",
4516
- interactive=True,
4517
- col_count=(6, "fixed"),
4518
- )
4519
-
4520
- # Visual Preview
4521
- with gr.Accordion("👁️ Color Ramps Visual Preview (Semantic Groups)", open=True):
4522
- gr.Markdown("*AI-generated color ramps expanding each base color into a 50–950 shade scale (similar to Tailwind CSS). "
4523
- "Colors are grouped by semantic role. These ramps will be included in your final export if the checkbox below is enabled.*",
4524
- elem_classes=["section-desc"])
4525
- stage2_color_ramps_preview = gr.HTML(
4526
- value="<div class='placeholder-msg'>Color ramps preview will appear after analysis...</div>",
4527
- label="Color Ramps Preview"
4528
- )
4529
 
4530
- gr.Markdown("**Base Colors** Primary colors extracted from your site, organized by frequency and semantic role:",
4531
- elem_classes=["section-desc"])
4532
- base_colors_display = gr.Markdown("*Base colors will appear after analysis*")
 
 
4533
 
4534
- gr.Markdown("---")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4535
 
4536
- gr.Markdown("**Color Ramps** Full shade tables (50–950) generated from each base color:",
4537
- elem_classes=["section-desc"])
4538
- color_ramps_display = gr.Markdown("*Color ramps will appear after analysis*")
4539
-
4540
- color_ramps_checkbox = gr.Checkbox(
4541
- label="✓ Generate color ramps (keeps base colors, adds 50-950 shades)",
4542
- value=True,
4543
- )
4544
-
4545
- # =============================================================
4546
- # SPACING SECTION
4547
- # =============================================================
4548
- gr.Markdown("---")
4549
- gr.Markdown("## 📏 Spacing (Rule-Based)")
4550
- gr.Markdown("*Your detected spacing values compared against standard 8px and 4px grid systems. "
4551
- "Consistent spacing creates visual rhythm and alignment. The 8px grid (8, 16, 24, 32...) is the industry standard — "
4552
- "select your preferred system below to normalize spacing in the export.*",
4553
- elem_classes=["section-desc"])
4554
 
4555
- with gr.Row():
4556
- with gr.Column(scale=2):
4557
- spacing_comparison = gr.Dataframe(
4558
- headers=["Current", "8px Grid", "4px Grid"],
4559
- datatype=["str", "str", "str"],
4560
- label="Spacing Comparison",
4561
- interactive=False,
4562
- )
4563
-
4564
- with gr.Column(scale=1):
4565
- spacing_radio = gr.Radio(
4566
- choices=["Keep Current", "8px Base Grid ⭐", "4px Base Grid"],
4567
- value="8px Base Grid ⭐",
4568
- label="Spacing System",
4569
- interactive=True,
 
 
 
 
 
 
 
 
 
 
 
 
 
4570
  )
4571
-
4572
- # =============================================================
4573
- # RADIUS SECTION
4574
- # =============================================================
4575
- gr.Markdown("---")
4576
- gr.Markdown("## 🔘 Border Radius (Rule-Based)")
4577
- gr.Markdown("*Border radius values detected from your site, mapped to standard design tokens (radius.none → radius.full). "
4578
- "Consistent radius tokens ensure buttons, cards, and modals share a cohesive visual language. "
4579
- "Values are sorted from sharp corners to fully rounded.*",
4580
- elem_classes=["section-desc"])
4581
 
4582
- radius_display = gr.Markdown("*Radius tokens will appear after analysis*")
4583
-
4584
- # =============================================================
4585
- # SHADOWS SECTION
4586
- # =============================================================
4587
- gr.Markdown("---")
4588
- gr.Markdown("## 🌫️ Shadows (Rule-Based)")
4589
- gr.Markdown("*Box shadow values detected from your site, organized into elevation tokens (shadow.xs → shadow.2xl). "
4590
- "A well-defined shadow scale creates depth hierarchy — subtle shadows for cards, deeper shadows for modals and popovers. "
4591
- "Exported tokens are ready for Figma elevation styles.*",
4592
- elem_classes=["section-desc"])
4593
 
4594
- shadows_display = gr.Markdown("*Shadow tokens will appear after analysis*")
4595
-
4596
- # =============================================================
4597
- # APPLY SECTION
4598
- # =============================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4599
  gr.Markdown("---")
4600
- gr.Markdown("**Apply** saves your chosen type scale, spacing grid, color ramp, and LLM recommendation selections. "
4601
- "These choices will be baked into your Stage 3 export. **Reset** reverts all selections back to the original extracted values.",
4602
  elem_classes=["section-desc"])
4603
-
4604
  with gr.Row():
4605
  apply_upgrades_btn = gr.Button("✨ Apply Selected Upgrades", variant="primary", scale=2)
4606
  reset_btn = gr.Button("↩️ Reset to Original", variant="secondary", scale=1)
4607
-
4608
  apply_status = gr.Markdown("", elem_classes=["apply-status-box"])
4609
 
4610
  # =================================================================
@@ -4685,26 +4822,27 @@ def create_ui():
4685
  # =================================================================
4686
  # EVENT HANDLERS
4687
  # =================================================================
4688
-
4689
  # Store data for viewport toggle
4690
  desktop_data = gr.State({})
4691
  mobile_data = gr.State({})
4692
-
4693
- # Discover pages
4694
  discover_btn.click(
4695
  fn=discover_pages,
4696
  inputs=[url_input],
4697
  outputs=[discover_status, log_output, pages_table],
4698
  ).then(
4699
- fn=lambda: (gr.update(visible=True), gr.update(visible=True)),
4700
- outputs=[pages_table, extract_btn],
 
4701
  )
4702
-
4703
- # Extract tokens
4704
  extract_btn.click(
4705
  fn=extract_tokens,
4706
  inputs=[pages_table],
4707
- outputs=[extraction_status, log_output, desktop_data, mobile_data,
4708
  stage1_typography_preview, stage1_colors_preview,
4709
  stage1_semantic_preview,
4710
  stage1_spacing_preview, stage1_radius_preview, stage1_shadows_preview],
@@ -4713,18 +4851,19 @@ def create_ui():
4713
  inputs=[desktop_data],
4714
  outputs=[colors_table, typography_table, spacing_table, radius_table],
4715
  ).then(
4716
- fn=lambda: (gr.update(open=True), gr.update(open=True)),
4717
- outputs=[stage1_accordion, stage2_accordion],
 
4718
  )
4719
 
4720
- # Viewport toggle
4721
  viewport_toggle.change(
4722
  fn=switch_viewport,
4723
  inputs=[viewport_toggle],
4724
  outputs=[colors_table, typography_table, spacing_table, radius_table],
4725
  )
4726
-
4727
- # Stage 2: NEW Architecture Analyze
4728
  analyze_btn_v2.click(
4729
  fn=run_stage2_analysis_v2,
4730
  inputs=[benchmark_checkboxes],
@@ -4746,35 +4885,38 @@ def create_ui():
4746
  radius_display,
4747
  shadows_display,
4748
  color_preview_output_stage2,
 
4749
  ],
4750
  ).then(
4751
- fn=lambda: gr.update(open=True),
4752
- outputs=[stage3_accordion],
 
4753
  )
4754
 
4755
- # Stage 2: Apply upgrades
4756
  apply_upgrades_btn.click(
4757
  fn=apply_selected_upgrades,
4758
  inputs=[type_scale_radio, spacing_radio, color_ramps_checkbox, color_recommendations_table],
4759
  outputs=[apply_status, stage2_log],
4760
  ).then(
4761
- fn=lambda: gr.update(open=True),
4762
- outputs=[stage3_accordion],
 
4763
  )
4764
 
4765
- # Stage 2: Reset to original
4766
  reset_btn.click(
4767
  fn=reset_to_original,
4768
  outputs=[type_scale_radio, spacing_radio, color_ramps_checkbox, apply_status, stage2_log],
4769
  )
4770
-
4771
- # Stage 1: Download JSON
4772
  download_stage1_btn.click(
4773
  fn=export_stage1_json,
4774
  outputs=[export_output],
4775
  )
4776
-
4777
- # Proceed to Stage 2 button
4778
  proceed_stage2_btn.click(
4779
  fn=lambda: gr.update(open=True),
4780
  outputs=[stage2_accordion],
@@ -4786,11 +4928,10 @@ def create_ui():
4786
 
4787
  gr.Markdown("""
4788
  ---
4789
- **Design System Extractor v3** | Built with Playwright + Firecrawl + HuggingFace
4790
-
4791
- *A multi-agent co-pilot for design system recovery and modernization.*
4792
-
4793
- **Architecture:** Rule Engine (FREE) + Benchmark Research + ReAct LLM Agents (AURORA | ATLAS | SENTINEL | NEXUS)
4794
  """)
4795
 
4796
  return app
 
1435
  state.log(f" ⚠️ Formatting failed: {str(format_err)[:100]}")
1436
  import traceback
1437
  state.log(traceback.format_exc()[:500])
1438
+ # Return minimal results (must match 18 outputs)
1439
  return (
1440
  f"⚠️ Analysis completed with formatting errors: {str(format_err)[:50]}",
1441
  state.get_logs(),
1442
+ "", # benchmark_html
1443
  "<div class='placeholder-msg'>Scores unavailable</div>",
1444
  "<div class='placeholder-msg'>Actions unavailable</div>",
1445
  [],
 
1454
  "*Formatting error - radius tokens unavailable*", # radius_md
1455
  "*Formatting error - shadow tokens unavailable*", # shadows_md
1456
  "⚠️ Color preview unavailable due to formatting errors.", # auto_color_preview
1457
+ "", # asis_tobe_html
1458
  )
1459
 
1460
  # Auto-generate color classification preview
 
1466
  state.log(f" ⚠️ Auto color preview failed: {str(cp_err)}")
1467
  auto_color_preview = "⚠️ Color preview unavailable — click 'Preview Color Names' button to generate."
1468
 
1469
+ # Build As-Is → To-Be transformation summary
1470
+ asis_tobe_html = ""
1471
+ try:
1472
+ cards = []
1473
+ # Type Scale
1474
+ detected_ratio = f"{rule_results.typography.detected_ratio:.2f}" if rule_results else "?"
1475
+ rec_ratio = "1.25"
1476
+ rec_name = "Major Third"
1477
+ if final_synthesis and final_synthesis.type_scale_recommendation:
1478
+ rec_ratio = str(final_synthesis.type_scale_recommendation.get("recommended_ratio", "1.25"))
1479
+ rec_name = final_synthesis.type_scale_recommendation.get("name", "Major Third")
1480
+ cards.append(_render_as_is_to_be(
1481
+ "Type Scale", detected_ratio,
1482
+ f"{rule_results.typography.scale_name if rule_results else '?'} • Variance: {rule_results.typography.variance:.2f}" if rule_results else "",
1483
+ rec_ratio, rec_name, icon="📐"
1484
+ ))
1485
+ # Spacing
1486
+ detected_base = f"{rule_results.spacing.detected_base}px" if rule_results else "?"
1487
+ alignment = f"{rule_results.spacing.alignment_percentage:.0f}% aligned" if rule_results else ""
1488
+ cards.append(_render_as_is_to_be(
1489
+ "Spacing Grid", detected_base, alignment,
1490
+ "8px", "Industry standard (Material, Tailwind)", icon="📏"
1491
+ ))
1492
+ # Colors
1493
+ color_count = str(rule_results.color_stats.unique_count) if rule_results else "?"
1494
+ aa_fails = rule_results.aa_failures if rule_results else 0
1495
+ cards.append(_render_as_is_to_be(
1496
+ "Colors", f"{color_count} unique",
1497
+ f"{aa_fails} fail AA compliance" if aa_fails else "All pass AA",
1498
+ f"~15 semantic" if int(color_count) > 20 else color_count,
1499
+ "0 AA failures" if aa_fails else "All pass ✓", icon="🎨"
1500
+ ))
1501
+ # Shadows
1502
+ shadow_count = 0
1503
+ if state.desktop_normalized:
1504
+ shadow_count = len(getattr(state.desktop_normalized, 'shadows', {}))
1505
+ cards.append(_render_as_is_to_be(
1506
+ "Shadows", f"{shadow_count} levels",
1507
+ "Elevation tokens" if shadow_count > 0 else "No shadows found",
1508
+ "5 levels", "xs → sm → md → lg → xl", icon="🌫️"
1509
+ ))
1510
+ asis_tobe_html = "".join(cards)
1511
+ except Exception:
1512
+ asis_tobe_html = ""
1513
+
1514
  progress(0.95, desc="✅ Complete!")
1515
 
1516
  # Final log summary
 
1542
  state.log(f" 💰 TOTAL COST: ~$0.003")
1543
  state.log(f" ⏱️ COMPLETED: {datetime.now().strftime('%H:%M:%S')}")
1544
  state.log("═" * 60)
1545
+
1546
  return (
1547
  status_md,
1548
  state.get_logs(),
 
1561
  radius_md,
1562
  shadows_md,
1563
  auto_color_preview,
1564
+ asis_tobe_html,
1565
  )
1566
 
1567
  except Exception as e:
 
1672
 
1673
 
1674
  def create_stage2_error_response(error_msg: str):
1675
+ """Create error response tuple for Stage 2 (must match 18 outputs)."""
1676
  return (
1677
  error_msg,
1678
  state.get_logs(),
1679
+ "", # benchmark_html
1680
  f"<div class='placeholder-msg'>{error_msg}</div>", # scores_html
1681
  "", # actions_html
1682
  [], # color_recs_table
 
1691
  "*Run analysis to see radius tokens*", # radius_md
1692
  "*Run analysis to see shadow tokens*", # shadows_md
1693
  "", # auto_color_preview
1694
+ "", # asis_tobe_html
1695
  )
1696
 
1697
 
 
1734
 
1735
 
1736
  def format_benchmark_comparison_v2(benchmark_comparisons, benchmark_advice) -> str:
1737
+ """Format benchmark comparison as visual HTML cards with progress bars."""
1738
+ return _render_benchmark_cards(benchmark_comparisons, benchmark_advice)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1739
 
1740
 
1741
  def format_scores_dashboard_v2(rule_results, final_synthesis, best_practices) -> str:
 
3550
  return json_str
3551
 
3552
 
3553
+ # =============================================================================
3554
+ # UI HELPERS
3555
+ # =============================================================================
3556
+
3557
+ def _render_stepper(active: int = 1, completed: list = None) -> str:
3558
+ """Render the horizontal progress stepper HTML.
3559
+
3560
+ Args:
3561
+ active: Current active step (1-5)
3562
+ completed: List of completed step numbers
3563
+ """
3564
+ if completed is None:
3565
+ completed = []
3566
+
3567
+ steps = [
3568
+ ("1", "Discover"),
3569
+ ("2", "Extract"),
3570
+ ("3", "Analyze"),
3571
+ ("4", "Review"),
3572
+ ("5", "Export"),
3573
+ ]
3574
+
3575
+ parts = []
3576
+ for i, (num, label) in enumerate(steps):
3577
+ step_num = i + 1
3578
+ if step_num in completed:
3579
+ cls = "completed"
3580
+ icon = "✓"
3581
+ elif step_num == active:
3582
+ cls = "active"
3583
+ icon = num
3584
+ else:
3585
+ cls = ""
3586
+ icon = num
3587
+
3588
+ parts.append(f'<div class="step-item {cls}"><span class="step-num">{icon}</span>{label}</div>')
3589
+
3590
+ if i < len(steps) - 1:
3591
+ conn_cls = "done" if step_num in completed else ""
3592
+ parts.append(f'<div class="step-connector {conn_cls}"></div>')
3593
+
3594
+ return f'<div class="progress-stepper">{"".join(parts)}</div>'
3595
+
3596
+
3597
+ def _render_benchmark_cards(benchmark_comparisons, benchmark_advice) -> str:
3598
+ """Render visual benchmark cards with progress bars instead of markdown table."""
3599
+ if not benchmark_comparisons:
3600
+ return "<div class='placeholder-msg'>No benchmark comparison available</div>"
3601
+
3602
+ medals = ["🥇", "🥈", "🥉"]
3603
+ cards_html = []
3604
+
3605
+ # Recommendation banner
3606
+ rec_html = ""
3607
+ if benchmark_advice and benchmark_advice.recommended_benchmark_name:
3608
+ rec_html = f"""
3609
+ <div style="background: #f5f3ff; border: 1px solid #c4b5fd; border-radius: 8px;
3610
+ padding: 14px 18px; margin-bottom: 16px; display: flex; align-items: center; gap: 10px;">
3611
+ <span style="font-size: 20px;">🏆</span>
3612
+ <div>
3613
+ <div style="font-weight: 600; color: #4C1D95; font-size: 14px;">
3614
+ Recommended: {benchmark_advice.recommended_benchmark_name}
3615
+ </div>
3616
+ <div style="font-size: 12px; color: #6D28D9; margin-top: 2px;">
3617
+ {benchmark_advice.reasoning or ''}
3618
+ </div>
3619
+ </div>
3620
+ </div>"""
3621
+
3622
+ for i, c in enumerate(benchmark_comparisons[:5]):
3623
+ b = c.benchmark
3624
+ medal = medals[i] if i < 3 else f"#{i+1}"
3625
+
3626
+ def bar_color(pct):
3627
+ if pct >= 80: return "#10b981"
3628
+ elif pct >= 50: return "#f59e0b"
3629
+ else: return "#ef4444"
3630
+
3631
+ categories = [
3632
+ ("Type", c.type_match_pct),
3633
+ ("Spacing", c.spacing_match_pct),
3634
+ ("Colors", c.color_match_pct),
3635
+ ("Radius", c.radius_match_pct),
3636
+ ("Shadows", c.shadow_match_pct),
3637
+ ]
3638
+
3639
+ bars = ""
3640
+ for cat_name, pct in categories:
3641
+ color = bar_color(pct)
3642
+ bars += f"""
3643
+ <div class="bm-bar-row">
3644
+ <div class="bm-bar-label">{cat_name}</div>
3645
+ <div class="bm-bar-track">
3646
+ <div class="bm-bar-fill" style="width: {pct:.0f}%; background: {color};"></div>
3647
+ </div>
3648
+ <div class="bm-bar-value" style="color: {color};">{pct:.0f}%</div>
3649
+ </div>"""
3650
+
3651
+ cards_html.append(f"""
3652
+ <div class="bm-card">
3653
+ <div class="bm-card-header">
3654
+ <div style="display: flex; align-items: center;">
3655
+ <span class="bm-medal">{medal}</span>
3656
+ <span class="bm-card-name">{b.icon} {b.short_name}</span>
3657
+ </div>
3658
+ <div class="bm-card-pct">{c.overall_match_pct:.0f}%</div>
3659
+ </div>
3660
+ {bars}
3661
+ </div>""")
3662
+
3663
+ # Alignment changes
3664
+ changes_html = ""
3665
+ if benchmark_advice and benchmark_advice.alignment_changes:
3666
+ items = []
3667
+ for change in benchmark_advice.alignment_changes[:4]:
3668
+ token_type = change.get('token_type', '')
3669
+ icon = {"typography": "📐", "spacing": "📏", "colors": "🎨", "radius": "🔘", "shadows": "🌗"}.get(token_type, "🔧")
3670
+ items.append(f"<li>{icon} <strong>{change.get('change', '?')}</strong>: {change.get('from', '?')} → {change.get('to', '?')}</li>")
3671
+ changes_html = f"""
3672
+ <div style="margin-top: 16px; padding: 14px 18px; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px;">
3673
+ <div style="font-weight: 600; font-size: 14px; margin-bottom: 8px; color: #1e293b;">🔧 To Align with Top Match</div>
3674
+ <ul style="margin: 0; padding-left: 20px; font-size: 13px; color: #475569; line-height: 1.8;">
3675
+ {''.join(items)}
3676
+ </ul>
3677
+ </div>"""
3678
+
3679
+ return f"""
3680
+ {rec_html}
3681
+ <div class="bm-card-grid">
3682
+ {''.join(cards_html)}
3683
+ </div>
3684
+ {changes_html}
3685
+ """
3686
+
3687
+
3688
+ def _render_as_is_to_be(category: str, as_is_value: str, as_is_detail: str,
3689
+ to_be_value: str, to_be_detail: str, icon: str = "📐") -> str:
3690
+ """Render an As-Is → To-Be comparison card for a token category."""
3691
+ return f"""
3692
+ <div class="comparison-grid">
3693
+ <div class="comparison-card as-is">
3694
+ <div class="comparison-label as-is-label">{icon} {category} — AS-IS</div>
3695
+ <div class="comparison-value">{as_is_value}</div>
3696
+ <div class="comparison-detail">{as_is_detail}</div>
3697
+ </div>
3698
+ <div class="comparison-arrow">→</div>
3699
+ <div class="comparison-card to-be">
3700
+ <div class="comparison-label to-be-label">{icon} {category} — TO-BE</div>
3701
+ <div class="comparison-value">{to_be_value}</div>
3702
+ <div class="comparison-detail">{to_be_detail}</div>
3703
+ </div>
3704
+ </div>
3705
+ """
3706
+
3707
+
3708
  # =============================================================================
3709
  # UI BUILDING
3710
  # =============================================================================
 
3712
  def create_ui():
3713
  """Create the Gradio interface with corporate branding."""
3714
 
3715
+ # Corporate theme — Deep Violet accent (distinctive, not blue)
3716
  corporate_theme = gr.themes.Base(
3717
+ primary_hue=gr.themes.colors.violet,
3718
  secondary_hue=gr.themes.colors.slate,
3719
  neutral_hue=gr.themes.colors.slate,
3720
  font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
 
3727
  block_background_fill_dark="#1e293b",
3728
  block_border_color="#e2e8f0",
3729
  block_border_color_dark="#334155",
3730
+ block_label_background_fill="#f5f3ff",
3731
  block_label_background_fill_dark="#1e293b",
3732
  block_title_text_color="#0f172a",
3733
  block_title_text_color_dark="#f1f5f9",
3734
+
3735
+ # Primary button — Deep Violet
3736
+ button_primary_background_fill="#6D28D9",
3737
+ button_primary_background_fill_hover="#5B21B6",
3738
  button_primary_text_color="white",
3739
+
3740
  # Secondary button
3741
+ button_secondary_background_fill="#f5f3ff",
3742
+ button_secondary_background_fill_hover="#ede9fe",
3743
  button_secondary_text_color="#1e293b",
3744
+
3745
  # Input fields
3746
  input_background_fill="#ffffff",
3747
  input_background_fill_dark="#1e293b",
3748
  input_border_color="#cbd5e1",
3749
  input_border_color_dark="#475569",
3750
+
3751
  # Shadows and radius
3752
  block_shadow="0 1px 3px rgba(0,0,0,0.1)",
3753
  block_shadow_dark="0 1px 3px rgba(0,0,0,0.3)",
3754
  block_border_width="1px",
3755
  block_radius="8px",
3756
+
3757
  # Text
3758
  body_text_color="#1e293b",
3759
  body_text_color_dark="#e2e8f0",
3760
  body_text_size="14px",
3761
  )
3762
 
3763
+ # Custom CSS Deep Violet theme + Progress Stepper + Always-visible log
3764
  custom_css = """
3765
+ /* ═══════════════════════════════════════════════════════════════
3766
+ GLOBAL
3767
+ ═══════════════════════════════════════════════════════════════ */
3768
  .gradio-container {
3769
  max-width: 1400px !important;
3770
  margin: 0 auto !important;
3771
  }
3772
+
3773
+ /* ═══════════════════════════════════════════════════════════════
3774
+ HEADER — Deep Violet gradient
3775
+ ═══════════════════════════════════════════════════════════════ */
3776
  .app-header {
3777
+ background: linear-gradient(135deg, #4C1D95 0%, #7C3AED 50%, #8B5CF6 100%);
3778
+ padding: 28px 32px;
3779
  border-radius: 12px;
3780
+ margin-bottom: 8px;
3781
  color: white;
3782
+ position: relative;
3783
+ overflow: hidden;
3784
+ }
3785
+ .app-header::before {
3786
+ content: '';
3787
+ position: absolute;
3788
+ top: -50%;
3789
+ right: -30%;
3790
+ width: 60%;
3791
+ height: 200%;
3792
+ background: radial-gradient(ellipse, rgba(255,255,255,0.08) 0%, transparent 70%);
3793
+ pointer-events: none;
3794
  }
3795
  .app-header h1 {
3796
+ margin: 0 0 6px 0;
3797
+ font-size: 26px;
3798
  font-weight: 700;
3799
+ letter-spacing: -0.3px;
3800
  }
3801
  .app-header p {
3802
  margin: 0;
3803
+ opacity: 0.85;
3804
+ font-size: 13px;
3805
  }
3806
+
3807
+ /* ═══════════════════════════════════════════════════════════════
3808
+ PROGRESS STEPPER — horizontal workflow indicator
3809
+ ═══════════════════════════════════════════════════════════════ */
3810
+ .progress-stepper {
3811
+ display: flex;
3812
+ align-items: center;
3813
+ justify-content: center;
3814
+ padding: 16px 20px;
3815
+ background: white;
3816
+ border: 1px solid #e2e8f0;
3817
+ border-radius: 10px;
3818
+ margin-bottom: 20px;
3819
+ gap: 0;
3820
+ }
3821
+ .step-item {
3822
+ display: flex;
3823
+ align-items: center;
3824
+ gap: 8px;
3825
+ padding: 6px 14px;
3826
+ border-radius: 20px;
3827
+ font-size: 13px;
3828
+ font-weight: 500;
3829
+ color: #94a3b8;
3830
+ transition: all 0.3s ease;
3831
+ white-space: nowrap;
3832
+ }
3833
+ .step-item.active {
3834
+ background: #f5f3ff;
3835
+ color: #6D28D9;
3836
+ font-weight: 600;
3837
+ }
3838
+ .step-item.completed {
3839
+ color: #10b981;
3840
+ font-weight: 500;
3841
+ }
3842
+ .step-num {
3843
+ width: 24px;
3844
+ height: 24px;
3845
+ border-radius: 50%;
3846
+ display: flex;
3847
+ align-items: center;
3848
+ justify-content: center;
3849
+ font-size: 12px;
3850
+ font-weight: 700;
3851
+ background: #e2e8f0;
3852
+ color: #94a3b8;
3853
+ flex-shrink: 0;
3854
+ }
3855
+ .step-item.active .step-num {
3856
+ background: #6D28D9;
3857
+ color: white;
3858
+ }
3859
+ .step-item.completed .step-num {
3860
+ background: #10b981;
3861
+ color: white;
3862
+ }
3863
+ .step-connector {
3864
+ width: 32px;
3865
+ height: 2px;
3866
+ background: #e2e8f0;
3867
+ flex-shrink: 0;
3868
+ }
3869
+ .step-connector.done {
3870
+ background: #10b981;
3871
+ }
3872
+
3873
+ /* ═══════════════════════════════════════════════════════════════
3874
+ STAGE HEADERS — Violet accent
3875
+ ═══════════════════════════════════════════════════════════════ */
3876
  .stage-header {
3877
+ background: linear-gradient(90deg, #f5f3ff 0%, #ffffff 100%);
3878
  padding: 16px 20px;
3879
  border-radius: 8px;
3880
+ border-left: 4px solid #6D28D9;
3881
  margin-bottom: 16px;
3882
  }
3883
  .stage-header h2 {
 
3885
  font-size: 18px;
3886
  color: #1e293b;
3887
  }
3888
+
3889
+ /* ═══════════════════════════════════════════════════════════════
3890
+ LOG — Always visible during loading (z-index trick)
3891
+ ═══════════════════════════════════════════════════════════════ */
3892
  .log-container textarea {
3893
  font-family: 'JetBrains Mono', monospace !important;
3894
  font-size: 12px !important;
 
3897
  color: #e2e8f0 !important;
3898
  border-radius: 8px !important;
3899
  }
3900
+ /* Keep log visible above Gradio loading overlay */
3901
+ .always-visible-log {
3902
+ position: relative;
3903
+ z-index: 1001;
3904
+ }
3905
+ .always-visible-log .wrap {
3906
+ opacity: 1 !important;
3907
+ }
3908
+
3909
+ /* ═══════════════════════════════════════════════════════════════
3910
+ COLOR SWATCH
3911
+ ═══════════════════════════════════════════════════════════════ */
3912
  .color-swatch {
3913
  display: inline-block;
3914
  width: 24px;
 
3918
  vertical-align: middle;
3919
  border: 1px solid rgba(0,0,0,0.1);
3920
  }
3921
+
3922
+ /* ═══════════════════════════════════════════════════════════════
3923
+ SCORE BADGES
3924
+ ═══════════════════════════════════════════════════════════════ */
3925
  .score-badge {
3926
  display: inline-block;
3927
  padding: 4px 12px;
 
3932
  .score-badge.high { background: #dcfce7; color: #166534; }
3933
  .score-badge.medium { background: #fef3c7; color: #92400e; }
3934
  .score-badge.low { background: #fee2e2; color: #991b1b; }
3935
+
3936
+ /* ═══════════════════════════════════════════════════════════════
3937
+ BENCHMARK CARDS — Visual cards with progress bars
3938
+ ═══════════════════════════════════════════════════════════════ */
3939
  .benchmark-card {
3940
  background: #f8fafc;
3941
  border: 1px solid #e2e8f0;
 
3944
  margin-bottom: 12px;
3945
  }
3946
  .benchmark-card.selected {
3947
+ border-color: #6D28D9;
3948
+ background: #f5f3ff;
3949
  }
3950
+ .bm-card-grid {
3951
+ display: grid;
3952
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
3953
+ gap: 16px;
3954
+ margin: 16px 0;
3955
+ }
3956
+ .bm-card {
3957
+ background: white;
3958
+ border: 1px solid #e2e8f0;
3959
+ border-radius: 10px;
3960
+ padding: 18px;
3961
+ transition: all 0.2s ease;
3962
+ }
3963
+ .bm-card:first-child {
3964
+ border: 2px solid #6D28D9;
3965
+ box-shadow: 0 0 0 3px rgba(109,40,217,0.1);
3966
+ }
3967
+ .bm-card-header {
3968
+ display: flex;
3969
+ justify-content: space-between;
3970
+ align-items: center;
3971
+ margin-bottom: 14px;
3972
+ }
3973
+ .bm-card-name {
3974
+ font-weight: 600;
3975
+ font-size: 15px;
3976
+ color: #1e293b;
3977
+ }
3978
+ .bm-card-pct {
3979
+ font-weight: 700;
3980
+ font-size: 20px;
3981
+ color: #6D28D9;
3982
+ }
3983
+ .bm-card:first-child .bm-card-pct {
3984
+ color: #6D28D9;
3985
+ }
3986
+ .bm-bar-row {
3987
+ display: flex;
3988
+ align-items: center;
3989
+ gap: 8px;
3990
+ margin-bottom: 8px;
3991
+ }
3992
+ .bm-bar-label {
3993
+ width: 70px;
3994
+ font-size: 11px;
3995
+ color: #64748b;
3996
+ text-align: right;
3997
+ flex-shrink: 0;
3998
+ }
3999
+ .bm-bar-track {
4000
+ flex: 1;
4001
+ height: 6px;
4002
+ background: #e2e8f0;
4003
+ border-radius: 3px;
4004
+ overflow: hidden;
4005
+ }
4006
+ .bm-bar-fill {
4007
+ height: 100%;
4008
+ border-radius: 3px;
4009
+ transition: width 0.5s ease;
4010
+ }
4011
+ .bm-bar-value {
4012
+ width: 40px;
4013
+ font-size: 11px;
4014
+ color: #475569;
4015
+ font-weight: 600;
4016
+ }
4017
+ .bm-medal {
4018
+ display: inline-flex;
4019
+ align-items: center;
4020
+ justify-content: center;
4021
+ width: 26px;
4022
+ height: 26px;
4023
+ border-radius: 50%;
4024
+ font-size: 14px;
4025
+ margin-right: 6px;
4026
+ }
4027
+
4028
+ /* ═══════════════════════════════════════════════════════════════
4029
+ AS-IS vs TO-BE COMPARISON CARDS
4030
+ ═══════════════════════════════════════════════════════════════ */
4031
+ .comparison-grid {
4032
+ display: grid;
4033
+ grid-template-columns: 1fr auto 1fr;
4034
+ gap: 0;
4035
+ margin: 16px 0;
4036
+ align-items: stretch;
4037
+ }
4038
+ .comparison-card {
4039
+ background: white;
4040
+ border: 1px solid #e2e8f0;
4041
+ border-radius: 10px;
4042
+ padding: 18px;
4043
+ }
4044
+ .comparison-card.as-is {
4045
+ border-left: 4px solid #94a3b8;
4046
+ }
4047
+ .comparison-card.to-be {
4048
+ border-left: 4px solid #6D28D9;
4049
+ background: #faf5ff;
4050
+ }
4051
+ .comparison-arrow {
4052
+ display: flex;
4053
+ align-items: center;
4054
+ justify-content: center;
4055
+ padding: 0 12px;
4056
+ font-size: 24px;
4057
+ color: #6D28D9;
4058
+ }
4059
+ .comparison-label {
4060
+ font-size: 11px;
4061
+ font-weight: 700;
4062
+ text-transform: uppercase;
4063
+ letter-spacing: 0.5px;
4064
+ margin-bottom: 10px;
4065
+ }
4066
+ .comparison-label.as-is-label { color: #94a3b8; }
4067
+ .comparison-label.to-be-label { color: #6D28D9; }
4068
+ .comparison-value {
4069
+ font-size: 22px;
4070
+ font-weight: 700;
4071
+ color: #1e293b;
4072
+ margin-bottom: 4px;
4073
+ }
4074
+ .comparison-detail {
4075
+ font-size: 12px;
4076
+ color: #64748b;
4077
+ }
4078
+
4079
+ /* ══════════════════════════════���════════════════════════════════
4080
+ ACTION ITEMS
4081
+ ═══════════════════════════════════════════════════════════════ */
4082
  .action-item {
4083
  background: white;
4084
  border: 1px solid #e2e8f0;
 
4092
  .action-item.medium-priority {
4093
  border-left: 4px solid #f59e0b;
4094
  }
4095
+
4096
+ /* ═══════════════════════════════════════════════════════════════
4097
+ PROGRESS BAR (generic)
4098
+ ═══════════════════════════════════════════════════════════════ */
4099
  .progress-bar {
4100
  height: 4px;
4101
  background: #e2e8f0;
 
4104
  }
4105
  .progress-bar-fill {
4106
  height: 100%;
4107
+ background: linear-gradient(90deg, #6D28D9, #8B5CF6);
4108
  transition: width 0.3s ease;
4109
  }
4110
+
4111
+ /* ═══════════════════════════════════════════════════════════════
4112
+ TABLES
4113
+ ═══════════════════════════════════════════════════════════════ */
 
 
 
4114
  table {
4115
  border-collapse: collapse;
4116
  width: 100%;
4117
  }
4118
  th {
4119
+ background: #f5f3ff;
4120
  color: #1e293b;
4121
  padding: 12px;
4122
  text-align: left;
 
4129
  border-bottom: 1px solid #e2e8f0;
4130
  }
4131
 
4132
+ /* ═══════════════════════════════════════════════════════════════
4133
+ SECTION DESCRIPTIONS
4134
+ ═══════════════════════════════════════════════════════════════ */
4135
  .section-desc p, .section-desc {
4136
  font-size: 13px !important;
4137
  color: #64748b !important;
 
4143
  color: #94a3b8 !important;
4144
  }
4145
 
4146
+ /* ═══════════════════════════════════════════════════════════════
4147
+ SUCCESS / ERROR MESSAGES
4148
+ ═══════════════════════════════════════════════════════════════ */
4149
  .success-msg { background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 8px; padding: 16px; margin: 8px 0; }
4150
  .success-msg h2 { color: #166534 !important; }
4151
  .dark .success-msg { background: #052e16 !important; border-color: #166534 !important; }
4152
  .dark .success-msg h2 { color: #bbf7d0 !important; }
4153
  .dark .success-msg p { color: #d1d5db !important; }
 
 
4154
  .error-msg { background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; padding: 16px; margin: 8px 0; }
4155
  .error-msg h2 { color: #991b1b !important; }
4156
  .dark .error-msg { background: #450a0a !important; border-color: #991b1b !important; }
4157
  .dark .error-msg h2 { color: #fecaca !important; }
4158
  .dark .error-msg p { color: #d1d5db !important; }
4159
 
4160
+ /* ═══════════════════════════════════════════════════════════════
4161
+ PLACEHOLDER MESSAGES
4162
+ ═══════════════════════════════════════════════════════════════ */
4163
  .placeholder-msg {
4164
  padding: 20px;
4165
+ background: #f5f3ff;
4166
  border-radius: 8px;
4167
+ color: #6D28D9;
4168
+ border: 1px dashed #c4b5fd;
4169
+ text-align: center;
4170
  }
4171
  .placeholder-msg.placeholder-lg {
4172
  padding: 40px;
 
4173
  }
4174
 
4175
+ /* ═══════════════════════════════════════════════════════════════
4176
+ DARK MODE
4177
+ ═══════════════════════════════════════════════════════════════ */
 
4178
 
4179
+ /* Stepper */
4180
+ .dark .progress-stepper {
 
 
 
 
 
 
 
 
 
 
 
4181
  background: #1e293b;
4182
  border-color: #334155;
4183
  }
4184
+ .dark .step-item { color: #64748b; }
4185
+ .dark .step-item.active { background: #2e1065; color: #c4b5fd; }
4186
+ .dark .step-item.completed { color: #34d399; }
4187
+ .dark .step-num { background: #334155; color: #64748b; }
4188
+ .dark .step-item.active .step-num { background: #7C3AED; color: white; }
4189
+ .dark .step-item.completed .step-num { background: #10b981; color: white; }
4190
+ .dark .step-connector { background: #334155; }
4191
+ .dark .step-connector.done { background: #10b981; }
4192
+
4193
+ /* Stage header */
4194
+ .dark .stage-header {
4195
+ background: linear-gradient(90deg, #1e293b 0%, #0f172a 100%);
4196
+ border-left-color: #7C3AED;
4197
+ }
4198
+ .dark .stage-header h2 { color: #f1f5f9; }
4199
+ .dark .stage-header-subtitle, .dark .tip-text { color: #94a3b8 !important; }
4200
+
4201
+ /* Benchmark cards */
4202
+ .dark .benchmark-card { background: #1e293b; border-color: #334155; }
4203
+ .dark .benchmark-card.selected { border-color: #7C3AED; background: #2e1065; }
4204
+ .dark .bm-card { background: #1e293b; border-color: #334155; }
4205
+ .dark .bm-card:first-child { border-color: #7C3AED; box-shadow: 0 0 0 3px rgba(124,58,237,0.15); }
4206
+ .dark .bm-card-name { color: #f1f5f9; }
4207
+ .dark .bm-card-pct { color: #c4b5fd; }
4208
+ .dark .bm-bar-label { color: #94a3b8; }
4209
+ .dark .bm-bar-track { background: #334155; }
4210
+ .dark .bm-bar-value { color: #cbd5e1; }
4211
+
4212
+ /* Comparison cards */
4213
+ .dark .comparison-card { background: #1e293b; border-color: #334155; }
4214
+ .dark .comparison-card.to-be { background: #2e1065; border-left-color: #7C3AED; }
4215
+ .dark .comparison-arrow { color: #c4b5fd; }
4216
+ .dark .comparison-label.to-be-label { color: #c4b5fd; }
4217
+ .dark .comparison-value { color: #f1f5f9; }
4218
+ .dark .comparison-detail { color: #94a3b8; }
4219
+
4220
+ /* Action items */
4221
  .dark .action-item {
4222
  background: #1e293b;
4223
  border-color: #475569;
4224
  color: #e2e8f0;
4225
  }
4226
+ .dark .action-item.high-priority { border-left-color: #ef4444; }
4227
+ .dark .action-item.medium-priority { border-left-color: #f59e0b; }
4228
+
4229
+ /* Placeholder */
4230
  .dark .placeholder-msg {
4231
+ background: #1e1b2e !important;
4232
+ color: #a78bfa !important;
4233
+ border-color: #4c1d95 !important;
 
 
 
4234
  }
4235
+
4236
+ /* Tables */
4237
  .dark table th {
4238
  background: #1e293b !important;
4239
  color: #e2e8f0 !important;
 
4243
  color: #e2e8f0 !important;
4244
  border-bottom-color: #334155 !important;
4245
  }
4246
+ .dark table tr { background: #0f172a !important; }
4247
+ .dark table tr:nth-child(even) { background: #1e293b !important; }
4248
+
4249
+ /* Typography preview */
4250
+ .dark .typography-preview { background: #1e293b !important; }
4251
+ .dark .typography-preview th { background: #334155 !important; color: #e2e8f0 !important; border-bottom-color: #475569 !important; }
4252
+ .dark .typography-preview td { color: #e2e8f0 !important; }
4253
+ .dark .typography-preview .meta-row { background: #1e293b !important; border-top-color: #334155 !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4254
  .dark .typography-preview .scale-name,
4255
+ .dark .typography-preview .scale-label { color: #f1f5f9 !important; background: #475569 !important; }
4256
+ .dark .typography-preview .meta { color: #cbd5e1 !important; }
4257
+ .dark .typography-preview .preview-cell { background: #0f172a !important; border-bottom-color: #334155 !important; }
4258
+ .dark .typography-preview .preview-text { color: #f1f5f9 !important; }
4259
+ .dark .typography-preview tr:hover .preview-cell { background: #1e293b !important; }
4260
+
4261
+ /* Colors AS-IS preview */
4262
+ .dark .colors-asis-header { color: #e2e8f0 !important; background: #1e293b !important; }
4263
+ .dark .colors-asis-preview { background: #0f172a !important; }
4264
+ .dark .color-row-asis { background: #1e293b !important; border-color: #475569 !important; }
4265
+ .dark .color-name-asis { color: #f1f5f9 !important; }
4266
+ .dark .frequency { color: #cbd5e1 !important; }
4267
+ .dark .color-meta-asis .aa-pass { color: #22c55e !important; background: #14532d !important; }
4268
+ .dark .color-meta-asis .aa-fail { color: #f87171 !important; background: #450a0a !important; }
4269
+ .dark .context-badge { background: #334155 !important; color: #e2e8f0 !important; }
4270
+
4271
+ /* Color ramps preview */
4272
+ .dark .color-ramps-preview { background: #0f172a !important; }
4273
+ .dark .ramps-header-info { color: #e2e8f0 !important; background: #1e293b !important; }
4274
+ .dark .ramp-header { background: #1e293b !important; }
4275
+ .dark .ramp-header-label { color: #cbd5e1 !important; }
4276
+ .dark .color-row { background: #1e293b !important; border-color: #475569 !important; }
4277
+ .dark .color-name { color: #f1f5f9 !important; background: #475569 !important; }
4278
+ .dark .color-hex { color: #cbd5e1 !important; }
4279
+
4280
+ /* Spacing preview */
4281
+ .dark .spacing-asis-preview { background: #0f172a !important; }
4282
+ .dark .spacing-row-asis { background: #1e293b !important; }
4283
+ .dark .spacing-label { color: #f1f5f9 !important; }
4284
+
4285
+ /* Radius preview */
4286
+ .dark .radius-asis-preview { background: #0f172a !important; }
4287
+ .dark .radius-item { background: #1e293b !important; }
4288
+ .dark .radius-label { color: #f1f5f9 !important; }
4289
+
4290
+ /* Shadows preview */
4291
+ .dark .shadows-asis-preview { background: #0f172a !important; }
4292
+ .dark .shadow-item { background: #1e293b !important; }
4293
+ .dark .shadow-box { background: #334155 !important; }
4294
+ .dark .shadow-label { color: #f1f5f9 !important; }
4295
+ .dark .shadow-value { color: #94a3b8 !important; }
4296
+
4297
+ /* Semantic color ramps */
4298
+ .dark .sem-ramps-preview { background: #0f172a !important; }
4299
+ .dark .sem-category { background: #1e293b !important; border-color: #475569 !important; }
4300
+ .dark .sem-cat-title { color: #f1f5f9 !important; border-bottom-color: #475569 !important; }
4301
+ .dark .sem-color-row { background: #0f172a !important; border-color: #334155 !important; }
4302
+ .dark .sem-role { color: #f1f5f9 !important; }
4303
+ .dark .sem-hex { color: #cbd5e1 !important; }
4304
+ .dark .llm-rec { background: #422006 !important; border-color: #b45309 !important; }
4305
+ .dark .rec-label { color: #fbbf24 !important; }
4306
+ .dark .rec-issue { color: #fde68a !important; }
4307
+ .dark .rec-arrow { color: #fbbf24 !important; }
4308
+ .dark .llm-summary { background: #2e1065 !important; border-color: #7C3AED !important; }
4309
+ .dark .llm-summary h4 { color: #c4b5fd !important; }
4310
+ .dark .llm-summary ul, .dark .llm-summary li { color: #ddd6fe !important; }
4311
 
4312
+ /* Score badges */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4313
  .dark .score-badge.high { background: #14532d; color: #86efac; }
4314
  .dark .score-badge.medium { background: #422006; color: #fde68a; }
4315
  .dark .score-badge.low { background: #450a0a; color: #fca5a5; }
4316
 
4317
+ /* Markdown tables */
4318
+ .dark .prose table th, .dark .markdown-text table th { background: #1e293b !important; color: #e2e8f0 !important; border-color: #475569 !important; }
4319
+ .dark .prose table td, .dark .markdown-text table td { color: #e2e8f0 !important; border-color: #334155 !important; }
4320
+ .dark .prose table tr, .dark .markdown-text table tr { background: #0f172a !important; }
4321
+ .dark .prose table tr:nth-child(even), .dark .markdown-text table tr:nth-child(even) { background: #1e293b !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4322
 
4323
+ /* Generic dark HTML text */
4324
+ .dark .gradio-html p, .dark .gradio-html span, .dark .gradio-html div { color: #e2e8f0; }
 
 
 
 
4325
  """
4326
 
4327
  with gr.Blocks(
4328
+ title="Design System Extractor v3",
4329
  theme=corporate_theme,
4330
  css=custom_css
4331
  ) as app:
 
4333
  # Header with branding
4334
  gr.HTML("""
4335
  <div class="app-header">
4336
+ <h1>🎨 Design System Extractor</h1>
4337
  <p>Reverse-engineer design systems from live websites • AI-powered analysis • Figma-ready export</p>
4338
  </div>
4339
  """)
4340
+
4341
+ # Progress stepper always visible, updated by JS
4342
+ progress_stepper = gr.HTML(
4343
+ value=_render_stepper(active=1),
4344
+ elem_classes=["progress-stepper-wrap"]
4345
+ )
4346
 
4347
  # =================================================================
4348
  # CONFIGURATION
 
4523
  # =================================================================
4524
 
4525
  with gr.Accordion("🧠 Stage 2: AI-Powered Analysis", open=False) as stage2_accordion:
4526
+
4527
  # Stage header
4528
  gr.HTML("""
4529
  <div class="stage-header">
 
4531
  <p class="stage-header-subtitle" style="color: #64748b; margin-top: 4px;">Rule Engine + Benchmark Research + LLM Agents</p>
4532
  </div>
4533
  """)
4534
+
4535
  stage2_status = gr.Markdown("Click **'Run Analysis'** below to start AI-powered design system analysis. "
4536
  "This runs a 4-layer pipeline: Rule Engine → Benchmark Research → LLM Agents → Head Synthesizer.")
4537
 
4538
+ # ── Config + Run button ──
4539
+ with gr.Row():
4540
+ with gr.Column(scale=3):
4541
+ benchmark_checkboxes = gr.CheckboxGroup(
4542
+ choices=[
4543
+ ("🟢 Material Design 3", "material_design_3"),
4544
+ ("🍎 Apple HIG", "apple_hig"),
4545
+ ("🛒 Shopify Polaris", "shopify_polaris"),
4546
+ ("🔵 Atlassian", "atlassian_design"),
4547
+ ("🔷 IBM Carbon", "ibm_carbon"),
4548
+ ("🌊 Tailwind CSS", "tailwind_css"),
4549
+ ("🐜 Ant Design", "ant_design"),
4550
+ ("⚡ Chakra UI", "chakra_ui"),
4551
+ ],
4552
+ value=["material_design_3", "shopify_polaris", "atlassian_design"],
4553
+ label="📊 Benchmarks to Compare Against",
4554
+ )
4555
+ with gr.Column(scale=1):
4556
+ analyze_btn_v2 = gr.Button(
4557
+ "🚀 Run Analysis",
4558
+ variant="primary",
4559
+ size="lg",
4560
+ )
4561
+ gr.Markdown(
4562
+ "<small style='color: #64748b;'>Cost: ~$0.003 per run</small>",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4563
  elem_classes=["section-desc"])
4564
 
4565
+ # ── Always-visible log panel ──
4566
+ with gr.Group(elem_classes=["always-visible-log"]):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4567
  stage2_log = gr.Textbox(
4568
+ label="📋 Live Analysis Log",
4569
+ lines=20,
4570
  interactive=False,
4571
  elem_classes=["log-container"]
4572
  )
 
 
 
 
 
 
 
 
 
4573
 
4574
+ # ═══════════════════════════════════════════════
4575
+ # TAB-BASED RESULTS (reduce scrolling)
4576
+ # ═══════════════════════════════════════════════
4577
+ with gr.Tabs():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4578
 
4579
+ # ── Tab 1: Scores & Actions ──
4580
+ with gr.Tab("📊 Scores & Actions"):
4581
+ gr.Markdown("*Overall scores for your design system. Each score is 0–100. "
4582
+ "Priority actions show the highest-impact fixes.*",
4583
+ elem_classes=["section-desc"])
4584
+
4585
+ scores_dashboard = gr.HTML(
4586
+ value="<div class='placeholder-msg placeholder-lg'>Scores will appear after analysis...</div>",
4587
+ label="Scores"
 
 
 
 
 
4588
  )
4589
+
4590
+ priority_actions_html = gr.HTML(
4591
+ value="<div class='placeholder-msg'>Priority actions will appear after analysis...</div>",
4592
+ label="Priority Actions"
 
 
 
 
4593
  )
4594
+
4595
+ # As-Is vs To-Be summary cards
4596
+ stage2_asis_tobe = gr.HTML(
4597
+ value="<div class='placeholder-msg'>As-Is To-Be transformation summary will appear after analysis...</div>",
4598
+ label="Transformation Summary"
 
 
 
 
4599
  )
 
 
 
 
 
 
 
 
 
 
 
4600
 
4601
+ # ── Tab 2: Benchmarks ──
4602
+ with gr.Tab("📈 Benchmarks"):
4603
+ gr.Markdown("*Your tokens compared against industry design systems. Visual cards show per-category match.*",
4604
+ elem_classes=["section-desc"])
4605
+ benchmark_comparison_md = gr.HTML(
4606
+ value="<div class='placeholder-msg'>Benchmark comparison will appear after analysis...</div>",
4607
+ label="Benchmarks"
 
 
 
 
 
4608
  )
 
 
 
 
 
 
 
 
 
4609
 
4610
+ # ── Tab 3: Typography ──
4611
+ with gr.Tab("📐 Typography"):
4612
+ gr.Markdown("*Type scale analysis with standard ratio comparisons. Choose a scale to apply.*",
4613
+ elem_classes=["section-desc"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4614
 
4615
+ with gr.Accordion("👁️ Typography Visual Preview", open=True):
4616
+ stage2_typography_preview = gr.HTML(
4617
+ value="<div class='placeholder-msg'>Typography preview will appear after analysis...</div>",
4618
+ label="Typography Preview"
4619
+ )
4620
 
4621
+ with gr.Row():
4622
+ with gr.Column(scale=2):
4623
+ gr.Markdown("### 🖥️ Desktop (1440px)")
4624
+ typography_desktop = gr.Dataframe(
4625
+ headers=["Token", "Current", "Scale 1.2", "Scale 1.25 ⭐", "Scale 1.333", "Keep"],
4626
+ datatype=["str", "str", "str", "str", "str", "str"],
4627
+ label="Desktop Typography",
4628
+ interactive=False,
4629
+ )
4630
+ with gr.Column(scale=2):
4631
+ gr.Markdown("### 📱 Mobile (375px)")
4632
+ typography_mobile = gr.Dataframe(
4633
+ headers=["Token", "Current", "Scale 1.2", "Scale 1.25 ⭐", "Scale 1.333", "Keep"],
4634
+ datatype=["str", "str", "str", "str", "str", "str"],
4635
+ label="Mobile Typography",
4636
+ interactive=False,
4637
+ )
4638
+
4639
+ with gr.Row():
4640
+ with gr.Column():
4641
+ type_scale_radio = gr.Radio(
4642
+ choices=["Keep Current", "Scale 1.2 (Minor Third)", "Scale 1.25 (Major Third) ⭐", "Scale 1.333 (Perfect Fourth)"],
4643
+ value="Scale 1.25 (Major Third) ⭐",
4644
+ label="Select Type Scale",
4645
+ interactive=True,
4646
+ )
4647
+ gr.Markdown("*Font family preserved. Sizes rounded to even numbers.*",
4648
+ elem_classes=["section-desc"])
4649
+
4650
+ # ── Tab 4: Colors ──
4651
+ with gr.Tab("🎨 Colors"):
4652
+ gr.Markdown("*Complete color analysis: base colors, AI ramps (50–950), and recommendations.*",
4653
+ elem_classes=["section-desc"])
4654
 
4655
+ # Color Naming Convention Preview
4656
+ with gr.Accordion("🏷️ Naming Convention — Preview Before Export", open=True):
4657
+ gr.Markdown("**Choose how colors are named.** 100% rule-based — no LLM.",
4658
+ elem_classes=["section-desc"])
4659
+ with gr.Row():
4660
+ naming_convention_stage2 = gr.Dropdown(
4661
+ choices=["semantic", "tailwind", "material"],
4662
+ value="semantic",
4663
+ label="🎨 Naming Convention",
4664
+ info="semantic = color.brand.primary | tailwind = brand-primary | material = color.brand.primary",
4665
+ scale=2,
4666
+ )
4667
+ preview_colors_btn_stage2 = gr.Button("👁️ Preview", variant="secondary", scale=1)
4668
+ color_preview_output_stage2 = gr.Textbox(
4669
+ label="Color Classification Preview (Rule-Based)",
4670
+ lines=18, max_lines=40, interactive=False,
4671
+ placeholder="Click 'Preview' to see how colors will be named in the export.",
4672
+ )
4673
 
4674
+ # LLM Recommendations
4675
+ with gr.Accordion("🤖 LLM Color Recommendations", open=True):
4676
+ gr.Markdown("*AI-suggested accessibility fixes and improvements.*",
4677
+ elem_classes=["section-desc"])
4678
+ llm_color_recommendations = gr.HTML(
4679
+ value="<div class='placeholder-msg'>LLM recommendations will appear after analysis...</div>",
4680
+ label="LLM Recommendations"
4681
+ )
4682
+ color_recommendations_table = gr.Dataframe(
4683
+ headers=["Accept", "Role", "Current", "Issue", "Suggested", "Contrast"],
4684
+ datatype=["bool", "str", "str", "str", "str", "str"],
4685
+ label="Color Recommendations",
4686
+ interactive=True,
4687
+ col_count=(6, "fixed"),
4688
+ )
4689
+
4690
+ # Color Ramps
4691
+ with gr.Accordion("👁️ Color Ramps (Semantic Groups)", open=True):
4692
+ stage2_color_ramps_preview = gr.HTML(
4693
+ value="<div class='placeholder-msg'>Color ramps preview will appear after analysis...</div>",
4694
+ label="Color Ramps Preview"
4695
+ )
4696
+
4697
+ base_colors_display = gr.Markdown("*Base colors will appear after analysis*")
4698
+ color_ramps_display = gr.Markdown("*Color ramps will appear after analysis*")
4699
+ color_ramps_checkbox = gr.Checkbox(
4700
+ label="✓ Generate color ramps (keeps base colors, adds 50-950 shades)",
4701
+ value=True,
4702
  )
 
 
 
 
 
 
 
 
 
 
4703
 
4704
+ # ── Tab 5: Spacing / Radius / Shadows ──
4705
+ with gr.Tab("📏 Spacing · Radius · Shadows"):
 
 
 
 
 
 
 
 
 
4706
 
4707
+ gr.Markdown("## 📏 Spacing")
4708
+ gr.Markdown("*Spacing values compared against 8px and 4px grids.*",
4709
+ elem_classes=["section-desc"])
4710
+ with gr.Row():
4711
+ with gr.Column(scale=2):
4712
+ spacing_comparison = gr.Dataframe(
4713
+ headers=["Current", "8px Grid", "4px Grid"],
4714
+ datatype=["str", "str", "str"],
4715
+ label="Spacing Comparison",
4716
+ interactive=False,
4717
+ )
4718
+ with gr.Column(scale=1):
4719
+ spacing_radio = gr.Radio(
4720
+ choices=["Keep Current", "8px Base Grid ⭐", "4px Base Grid"],
4721
+ value="8px Base Grid ⭐",
4722
+ label="Spacing System",
4723
+ interactive=True,
4724
+ )
4725
+
4726
+ gr.Markdown("---")
4727
+ gr.Markdown("## 🔘 Border Radius")
4728
+ gr.Markdown("*Radius tokens mapped to standard scale (none → full).*",
4729
+ elem_classes=["section-desc"])
4730
+ radius_display = gr.Markdown("*Radius tokens will appear after analysis*")
4731
+
4732
+ gr.Markdown("---")
4733
+ gr.Markdown("## 🌫️ Shadows")
4734
+ gr.Markdown("*Elevation tokens (shadow.xs → shadow.2xl).*",
4735
+ elem_classes=["section-desc"])
4736
+ shadows_display = gr.Markdown("*Shadow tokens will appear after analysis*")
4737
+
4738
+ # ── Apply / Reset ──
4739
  gr.Markdown("---")
4740
+ gr.Markdown("**Apply** saves your choices to the export. **Reset** reverts to extracted values.",
 
4741
  elem_classes=["section-desc"])
 
4742
  with gr.Row():
4743
  apply_upgrades_btn = gr.Button("✨ Apply Selected Upgrades", variant="primary", scale=2)
4744
  reset_btn = gr.Button("↩️ Reset to Original", variant="secondary", scale=1)
 
4745
  apply_status = gr.Markdown("", elem_classes=["apply-status-box"])
4746
 
4747
  # =================================================================
 
4822
  # =================================================================
4823
  # EVENT HANDLERS
4824
  # =================================================================
4825
+
4826
  # Store data for viewport toggle
4827
  desktop_data = gr.State({})
4828
  mobile_data = gr.State({})
4829
+
4830
+ # ── Discover pages ──
4831
  discover_btn.click(
4832
  fn=discover_pages,
4833
  inputs=[url_input],
4834
  outputs=[discover_status, log_output, pages_table],
4835
  ).then(
4836
+ fn=lambda: (gr.update(visible=True), gr.update(visible=True),
4837
+ _render_stepper(active=2, completed=[1])),
4838
+ outputs=[pages_table, extract_btn, progress_stepper],
4839
  )
4840
+
4841
+ # ── Extract tokens ──
4842
  extract_btn.click(
4843
  fn=extract_tokens,
4844
  inputs=[pages_table],
4845
+ outputs=[extraction_status, log_output, desktop_data, mobile_data,
4846
  stage1_typography_preview, stage1_colors_preview,
4847
  stage1_semantic_preview,
4848
  stage1_spacing_preview, stage1_radius_preview, stage1_shadows_preview],
 
4851
  inputs=[desktop_data],
4852
  outputs=[colors_table, typography_table, spacing_table, radius_table],
4853
  ).then(
4854
+ fn=lambda: (gr.update(open=True), gr.update(open=True),
4855
+ _render_stepper(active=3, completed=[1, 2])),
4856
+ outputs=[stage1_accordion, stage2_accordion, progress_stepper],
4857
  )
4858
 
4859
+ # ── Viewport toggle ──
4860
  viewport_toggle.change(
4861
  fn=switch_viewport,
4862
  inputs=[viewport_toggle],
4863
  outputs=[colors_table, typography_table, spacing_table, radius_table],
4864
  )
4865
+
4866
+ # ── Stage 2: Analyze ──
4867
  analyze_btn_v2.click(
4868
  fn=run_stage2_analysis_v2,
4869
  inputs=[benchmark_checkboxes],
 
4885
  radius_display,
4886
  shadows_display,
4887
  color_preview_output_stage2,
4888
+ stage2_asis_tobe,
4889
  ],
4890
  ).then(
4891
+ fn=lambda: (gr.update(open=True),
4892
+ _render_stepper(active=4, completed=[1, 2, 3])),
4893
+ outputs=[stage3_accordion, progress_stepper],
4894
  )
4895
 
4896
+ # ── Stage 2: Apply upgrades ──
4897
  apply_upgrades_btn.click(
4898
  fn=apply_selected_upgrades,
4899
  inputs=[type_scale_radio, spacing_radio, color_ramps_checkbox, color_recommendations_table],
4900
  outputs=[apply_status, stage2_log],
4901
  ).then(
4902
+ fn=lambda: (gr.update(open=True),
4903
+ _render_stepper(active=5, completed=[1, 2, 3, 4])),
4904
+ outputs=[stage3_accordion, progress_stepper],
4905
  )
4906
 
4907
+ # ── Stage 2: Reset to original ──
4908
  reset_btn.click(
4909
  fn=reset_to_original,
4910
  outputs=[type_scale_radio, spacing_radio, color_ramps_checkbox, apply_status, stage2_log],
4911
  )
4912
+
4913
+ # ── Stage 1: Download JSON ──
4914
  download_stage1_btn.click(
4915
  fn=export_stage1_json,
4916
  outputs=[export_output],
4917
  )
4918
+
4919
+ # ── Proceed to Stage 2 ──
4920
  proceed_stage2_btn.click(
4921
  fn=lambda: gr.update(open=True),
4922
  outputs=[stage2_accordion],
 
4928
 
4929
  gr.Markdown("""
4930
  ---
4931
+ <div style="text-align: center; color: #94a3b8; font-size: 12px; padding: 12px 0;">
4932
+ <strong>Design System Extractor v3</strong> · Playwright + Firecrawl + HuggingFace<br/>
4933
+ Rule Engine (FREE) + ReAct LLM Agents (AURORA · ATLAS · SENTINEL · NEXUS)
4934
+ </div>
 
4935
  """)
4936
 
4937
  return app