Spaces:
Runtime error
v3.4: UX revamp — Deep Violet theme, progress stepper, tab-based Stage 2
Browse filesMajor 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>
|
@@ -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
|
| 1439 |
return (
|
| 1440 |
f"⚠️ Analysis completed with formatting errors: {str(format_err)[:50]}",
|
| 1441 |
state.get_logs(),
|
| 1442 |
-
"
|
| 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
|
| 1629 |
return (
|
| 1630 |
error_msg,
|
| 1631 |
state.get_logs(),
|
| 1632 |
-
"", #
|
| 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
|
| 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
|
| 3593 |
corporate_theme = gr.themes.Base(
|
| 3594 |
-
primary_hue=gr.themes.colors.
|
| 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="#
|
| 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="#
|
| 3614 |
-
button_primary_background_fill_hover="#
|
| 3615 |
button_primary_text_color="white",
|
| 3616 |
-
|
| 3617 |
# Secondary button
|
| 3618 |
-
button_secondary_background_fill="#
|
| 3619 |
-
button_secondary_background_fill_hover="#
|
| 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
|
| 3641 |
custom_css = """
|
| 3642 |
-
/*
|
|
|
|
|
|
|
| 3643 |
.gradio-container {
|
| 3644 |
max-width: 1400px !important;
|
| 3645 |
margin: 0 auto !important;
|
| 3646 |
}
|
| 3647 |
-
|
| 3648 |
-
/*
|
|
|
|
|
|
|
| 3649 |
.app-header {
|
| 3650 |
-
background: linear-gradient(135deg, #
|
| 3651 |
-
padding:
|
| 3652 |
border-radius: 12px;
|
| 3653 |
-
margin-bottom:
|
| 3654 |
color: white;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3655 |
}
|
| 3656 |
.app-header h1 {
|
| 3657 |
-
margin: 0 0
|
| 3658 |
-
font-size:
|
| 3659 |
font-weight: 700;
|
|
|
|
| 3660 |
}
|
| 3661 |
.app-header p {
|
| 3662 |
margin: 0;
|
| 3663 |
-
opacity: 0.
|
| 3664 |
-
font-size:
|
| 3665 |
}
|
| 3666 |
-
|
| 3667 |
-
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3668 |
.stage-header {
|
| 3669 |
-
background: linear-gradient(90deg, #
|
| 3670 |
padding: 16px 20px;
|
| 3671 |
border-radius: 8px;
|
| 3672 |
-
border-left: 4px solid #
|
| 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 |
-
/*
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
/*
|
|
|
|
|
|
|
| 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 |
-
/*
|
|
|
|
|
|
|
| 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: #
|
| 3724 |
-
background: #
|
| 3725 |
}
|
| 3726 |
-
|
| 3727 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
/*
|
|
|
|
|
|
|
| 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, #
|
| 3752 |
transition: width 0.3s ease;
|
| 3753 |
}
|
| 3754 |
-
|
| 3755 |
-
/*
|
| 3756 |
-
|
| 3757 |
-
|
| 3758 |
-
}
|
| 3759 |
-
|
| 3760 |
-
/* Table styling */
|
| 3761 |
table {
|
| 3762 |
border-collapse: collapse;
|
| 3763 |
width: 100%;
|
| 3764 |
}
|
| 3765 |
th {
|
| 3766 |
-
background: #
|
| 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 |
-
/*
|
|
|
|
|
|
|
| 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 |
-
/*
|
|
|
|
|
|
|
| 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 |
-
/*
|
|
|
|
|
|
|
| 3806 |
.placeholder-msg {
|
| 3807 |
padding: 20px;
|
| 3808 |
-
background: #
|
| 3809 |
border-radius: 8px;
|
| 3810 |
-
color: #
|
|
|
|
|
|
|
| 3811 |
}
|
| 3812 |
.placeholder-msg.placeholder-lg {
|
| 3813 |
padding: 40px;
|
| 3814 |
-
text-align: center;
|
| 3815 |
}
|
| 3816 |
|
| 3817 |
-
/*
|
| 3818 |
-
|
| 3819 |
-
|
| 3820 |
-
}
|
| 3821 |
|
| 3822 |
-
/*
|
| 3823 |
-
.dark .
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 3844 |
.dark .placeholder-msg {
|
| 3845 |
-
background: #
|
| 3846 |
-
color: #
|
| 3847 |
-
|
| 3848 |
-
/* Dark mode: Progress bar */
|
| 3849 |
-
.dark .progress-bar {
|
| 3850 |
-
background: #334155 !important;
|
| 3851 |
}
|
| 3852 |
-
|
|
|
|
| 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 |
-
|
| 3864 |
-
|
| 3865 |
-
|
| 3866 |
-
|
| 3867 |
-
}
|
| 3868 |
-
|
| 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 |
-
|
| 3887 |
-
|
| 3888 |
-
}
|
| 3889 |
-
.dark .typography-preview .
|
| 3890 |
-
|
| 3891 |
-
|
| 3892 |
-
.dark .
|
| 3893 |
-
|
| 3894 |
-
|
| 3895 |
-
}
|
| 3896 |
-
.dark .
|
| 3897 |
-
|
| 3898 |
-
}
|
| 3899 |
-
.dark .
|
| 3900 |
-
|
| 3901 |
-
|
| 3902 |
-
|
| 3903 |
-
|
| 3904 |
-
.dark .
|
| 3905 |
-
|
| 3906 |
-
|
| 3907 |
-
}
|
| 3908 |
-
.dark .
|
| 3909 |
-
|
| 3910 |
-
|
| 3911 |
-
.dark .
|
| 3912 |
-
|
| 3913 |
-
|
| 3914 |
-
|
| 3915 |
-
|
| 3916 |
-
|
| 3917 |
-
}
|
| 3918 |
-
.dark .
|
| 3919 |
-
|
| 3920 |
-
|
| 3921 |
-
.dark .
|
| 3922 |
-
|
| 3923 |
-
|
| 3924 |
-
}
|
| 3925 |
-
.dark .
|
| 3926 |
-
|
| 3927 |
-
|
| 3928 |
-
}
|
| 3929 |
-
.dark .
|
| 3930 |
-
|
| 3931 |
-
|
| 3932 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3933 |
|
| 3934 |
-
/*
|
| 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 |
-
/*
|
| 4052 |
-
.dark .
|
| 4053 |
-
|
| 4054 |
-
|
| 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 |
-
/*
|
| 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
|
| 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
|
| 4102 |
<p>Reverse-engineer design systems from live websites • AI-powered analysis • Figma-ready export</p>
|
| 4103 |
</div>
|
| 4104 |
""")
|
| 4105 |
-
|
| 4106 |
-
|
| 4107 |
-
|
| 4108 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 4303 |
-
|
| 4304 |
-
|
| 4305 |
-
|
| 4306 |
-
|
| 4307 |
-
|
| 4308 |
-
|
| 4309 |
-
|
| 4310 |
-
|
| 4311 |
-
|
| 4312 |
-
|
| 4313 |
-
|
| 4314 |
-
|
| 4315 |
-
|
| 4316 |
-
|
| 4317 |
-
|
| 4318 |
-
|
| 4319 |
-
|
| 4320 |
-
|
| 4321 |
-
|
| 4322 |
-
|
| 4323 |
-
|
| 4324 |
-
|
| 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 |
-
#
|
| 4355 |
-
with gr.
|
| 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
|
| 4372 |
-
lines=
|
| 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 |
-
|
| 4387 |
-
|
| 4388 |
-
|
| 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 |
-
|
| 4431 |
-
|
| 4432 |
-
|
| 4433 |
-
|
| 4434 |
-
|
| 4435 |
-
|
| 4436 |
-
|
| 4437 |
-
|
| 4438 |
-
|
| 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 |
-
|
| 4447 |
-
|
| 4448 |
-
|
| 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 |
-
|
| 4456 |
-
|
| 4457 |
-
|
| 4458 |
-
|
| 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 |
-
|
| 4477 |
-
|
| 4478 |
-
|
| 4479 |
-
|
| 4480 |
-
|
| 4481 |
-
|
| 4482 |
-
|
| 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 |
-
|
| 4500 |
-
|
| 4501 |
-
|
| 4502 |
-
|
| 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 |
-
|
| 4531 |
-
|
| 4532 |
-
|
|
|
|
|
|
|
| 4533 |
|
| 4534 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4535 |
|
| 4536 |
-
|
| 4537 |
-
|
| 4538 |
-
|
| 4539 |
-
|
| 4540 |
-
|
| 4541 |
-
|
| 4542 |
-
|
| 4543 |
-
|
| 4544 |
-
|
| 4545 |
-
|
| 4546 |
-
|
| 4547 |
-
|
| 4548 |
-
|
| 4549 |
-
|
| 4550 |
-
|
| 4551 |
-
|
| 4552 |
-
|
| 4553 |
-
|
| 4554 |
|
| 4555 |
-
|
| 4556 |
-
|
| 4557 |
-
|
| 4558 |
-
|
| 4559 |
-
|
| 4560 |
-
|
| 4561 |
-
|
| 4562 |
-
|
| 4563 |
-
|
| 4564 |
-
|
| 4565 |
-
|
| 4566 |
-
|
| 4567 |
-
|
| 4568 |
-
|
| 4569 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 4595 |
-
|
| 4596 |
-
|
| 4597 |
-
|
| 4598 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4599 |
gr.Markdown("---")
|
| 4600 |
-
gr.Markdown("**Apply** saves your
|
| 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 |
-
|
|
|
|
| 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 |
-
|
|
|
|
| 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:
|
| 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 |
-
|
|
|
|
| 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 |
-
|
|
|
|
| 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
|
| 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 |
-
|
| 4790 |
-
|
| 4791 |
-
|
| 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
|