Spaces:
Build error
Build error
| """ | |
| وظائف تخصيص واجهة المستخدم (UI utilities) | |
| """ | |
| import streamlit as st | |
| from typing import Dict, List, Optional | |
| import config | |
| def load_custom_css(): | |
| """تحميل CSS مخصص""" | |
| try: | |
| with open("assets/css/style.css", "r", encoding="utf-8") as f: | |
| st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True) | |
| except: | |
| # CSS افتراضي في حالة عدم وجود الملف | |
| st.markdown(""" | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700&display=swap'); | |
| * { | |
| font-family: 'Tajawal', sans-serif !important; | |
| } | |
| .main { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| background-attachment: fixed; | |
| } | |
| .block-container { | |
| background: rgba(255, 255, 255, 0.95); | |
| border-radius: 20px; | |
| padding: 2rem; | |
| margin-top: 2rem; | |
| box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); | |
| } | |
| h1, h2, h3 { | |
| color: #667eea; | |
| } | |
| .stButton > button { | |
| background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border: none; | |
| border-radius: 10px; | |
| padding: 0.75rem 2rem; | |
| font-size: 1.1rem; | |
| font-weight: 600; | |
| transition: all 0.3s ease; | |
| width: 100%; | |
| } | |
| .stButton > button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4); | |
| } | |
| .success-box { | |
| background: linear-gradient(135deg, #48bb78 0%, #38a169 100%); | |
| color: white; | |
| padding: 1rem; | |
| border-radius: 10px; | |
| margin: 1rem 0; | |
| } | |
| .info-box { | |
| background: linear-gradient(135deg, #4299e1 0%, #3182ce 100%); | |
| color: white; | |
| padding: 1rem; | |
| border-radius: 10px; | |
| margin: 1rem 0; | |
| } | |
| .warning-box { | |
| background: linear-gradient(135deg, #ed8936 0%, #dd6b20 100%); | |
| color: white; | |
| padding: 1rem; | |
| border-radius: 10px; | |
| margin: 1rem 0; | |
| } | |
| .card { | |
| background: white; | |
| border-radius: 15px; | |
| padding: 1.5rem; | |
| margin: 1rem 0; | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); | |
| transition: all 0.3s ease; | |
| } | |
| .card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); | |
| } | |
| .progress-bar { | |
| background: #e2e8f0; | |
| border-radius: 10px; | |
| height: 25px; | |
| overflow: hidden; | |
| margin: 1rem 0; | |
| } | |
| .progress-fill { | |
| background: linear-gradient(90deg, #48bb78 0%, #38a169 100%); | |
| height: 100%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: white; | |
| font-weight: 600; | |
| transition: width 0.5s ease; | |
| } | |
| .badge { | |
| display: inline-block; | |
| padding: 0.35rem 0.75rem; | |
| border-radius: 20px; | |
| font-size: 0.875rem; | |
| font-weight: 600; | |
| margin: 0.25rem; | |
| } | |
| .badge-primary { | |
| background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| } | |
| .badge-success { | |
| background: #48bb78; | |
| color: white; | |
| } | |
| .badge-warning { | |
| background: #ed8936; | |
| color: white; | |
| } | |
| .badge-info { | |
| background: #4299e1; | |
| color: white; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| def show_hero_section(title: str, description: str): | |
| """عرض قسم Hero جميل""" | |
| st.markdown(f""" | |
| <div style='text-align: center; padding: 3rem 1rem;'> | |
| <h1 style='font-size: 3em; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); | |
| -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 0.5rem;'> | |
| {title} | |
| </h1> | |
| <p style='font-size: 1.3rem; color: #4a5568; margin-top: 0;'> | |
| {description} | |
| </p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| def show_card(title: str, content: str, icon: str = "📚"): | |
| """عرض بطاقة جميلة""" | |
| st.markdown(f""" | |
| <div class='card'> | |
| <h3 style='margin-top: 0;'>{icon} {title}</h3> | |
| <p style='color: #4a5568;'>{content}</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| def show_feature_cards(features: List[Dict]): | |
| """عرض بطاقات الميزات في أعمدة""" | |
| cols = st.columns(len(features)) | |
| for col, feature in zip(cols, features): | |
| with col: | |
| st.markdown(f""" | |
| <div class='card' style='text-align: center;'> | |
| <div style='font-size: 3rem; margin-bottom: 0.5rem;'>{feature.get('icon', '✨')}</div> | |
| <h3 style='margin: 0.5rem 0;'>{feature.get('title', '')}</h3> | |
| <p style='color: #718096; font-size: 0.95rem;'>{feature.get('description', '')}</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| def show_progress_bar(percentage: float, label: str = ""): | |
| """عرض شريط تقدم جميل""" | |
| st.markdown(f""" | |
| <div class='progress-bar'> | |
| <div class='progress-fill' style='width: {percentage}%;'> | |
| {percentage:.1f}% | |
| </div> | |
| </div> | |
| {f'<p style="text-align: center; color: #4a5568; margin-top: 0.5rem;">{label}</p>' if label else ''} | |
| """, unsafe_allow_html=True) | |
| def show_badge(text: str, badge_type: str = "primary"): | |
| """عرض شارة ملونة""" | |
| return f"<span class='badge badge-{badge_type}'>{text}</span>" | |
| def show_success_message(message: str): | |
| """رسالة نجاح جميلة""" | |
| st.markdown(f""" | |
| <div class='success-box'> | |
| <strong>✅ {message}</strong> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| def show_info_message(message: str): | |
| """رسالة معلومات جميلة""" | |
| st.markdown(f""" | |
| <div class='info-box'> | |
| <strong>ℹ️ {message}</strong> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| def show_warning_message(message: str): | |
| """رسالة تحذير جميلة""" | |
| st.markdown(f""" | |
| <div class='warning-box'> | |
| <strong>⚠️ {message}</strong> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| def create_stat_card(label: str, value: str, icon: str = "📊"): | |
| """إنشاء بطاقة إحصائية""" | |
| return f""" | |
| <div class='card' style='text-align: center;'> | |
| <div style='font-size: 2.5rem;'>{icon}</div> | |
| <h2 style='margin: 0.5rem 0; color: #667eea;'>{value}</h2> | |
| <p style='color: #718096; margin: 0;'>{label}</p> | |
| </div> | |
| """ | |
| def show_timeline(events: List[Dict]): | |
| """عرض خط زمني للأحداث""" | |
| for event in events: | |
| status_icon = "✅" if event.get("completed") else "⏳" | |
| st.markdown(f""" | |
| <div class='card' style='border-right: 4px solid {"#48bb78" if event.get("completed") else "#cbd5e0"};'> | |
| <div style='display: flex; align-items: center; gap: 1rem;'> | |
| <div style='font-size: 2rem;'>{status_icon}</div> | |
| <div style='flex: 1;'> | |
| <h4 style='margin: 0; color: #2d3748;'>{event.get('title', '')}</h4> | |
| <p style='margin: 0.25rem 0 0 0; color: #718096; font-size: 0.9rem;'> | |
| {event.get('date', '')} | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| def show_hub_selector(hubs: Dict) -> Optional[str]: | |
| """عرض محدد المحاور التعليمية""" | |
| st.markdown("### 🎯 اختر محورك التعليمي") | |
| selected_hub = None | |
| cols = st.columns(2) | |
| hub_items = list(hubs.items()) | |
| for idx, (hub_id, hub_data) in enumerate(hub_items): | |
| col = cols[idx % 2] | |
| with col: | |
| if st.button( | |
| f"{hub_data['icon']} {hub_data['name']}\n\n{hub_data['description']}", | |
| key=f"hub_{hub_id}", | |
| use_container_width=True | |
| ): | |
| selected_hub = hub_id | |
| return selected_hub | |
| def init_session_state(defaults: Dict): | |
| """تهيئة session state بقيم افتراضية""" | |
| for key, value in defaults.items(): | |
| if key not in st.session_state: | |
| st.session_state[key] = value | |
| def show_lesson_card(lesson: Dict, lesson_id: str, is_completed: bool = False): | |
| """عرض بطاقة درس""" | |
| status_color = "#48bb78" if is_completed else "#cbd5e0" | |
| status_text = "مكتمل ✅" if is_completed else "قيد الانتظار ⏳" | |
| st.markdown(f""" | |
| <div class='card' style='border-right: 4px solid {status_color};'> | |
| <div style='display: flex; justify-content: space-between; align-items: start;'> | |
| <div style='flex: 1;'> | |
| <h4 style='margin: 0 0 0.5rem 0; color: #2d3748;'>{lesson.get('title', '')}</h4> | |
| <p style='color: #718096; margin: 0.25rem 0;'>{lesson.get('description', '')}</p> | |
| <div style='margin-top: 0.75rem;'> | |
| <span class='badge badge-info'>⏱️ {lesson.get('duration_mins', 0)} دقيقة</span> | |
| <span class='badge badge-primary'>{lesson.get('type', 'درس')}</span> | |
| </div> | |
| </div> | |
| <div style='text-align: left; min-width: 100px;'> | |
| <span class='badge badge-{"success" if is_completed else "warning"}'>{status_text}</span> | |
| </div> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |