Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import streamlit.components.v1 as components | |
| from model.predict import predict | |
| st.set_page_config(page_title="Mizan ميزان", layout="centered") | |
| # --------------------------------------------------------------------------- | |
| # Theme — parchment, ink, wax seal. Injected once for the whole app. | |
| # --------------------------------------------------------------------------- | |
| st.markdown( | |
| """ | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;0,700;1,500&family=Amiri:wght@400;700&family=Spectral:wght@300;400;500&display=swap'); | |
| :root { | |
| --parchment: #E8DFC8; | |
| --ink: #2B2118; | |
| --ink-soft: #5A4B38; | |
| --wax-red: #8B2E20; | |
| --seal-blue: #1F3A5F; | |
| --gold: #A8843D; | |
| } | |
| .stApp { | |
| background: #E8DFC8; | |
| background-image: | |
| radial-gradient(circle at 20% 15%, rgba(168,132,61,0.06) 0%, transparent 40%), | |
| radial-gradient(circle at 80% 85%, rgba(139,46,32,0.05) 0%, transparent 45%); | |
| } | |
| .miz-masthead { text-align: center; margin: 1rem 0 0.2rem; } | |
| .miz-masthead .ar { font-family: 'Amiri', serif; font-weight: 700; font-size: clamp(3rem, 10vw, 5rem); line-height: 1; color: #2B2118; } | |
| .miz-masthead .en { font-family: 'Cormorant Garamond', serif; font-weight: 600; font-size: clamp(1rem, 3vw, 1.4rem); letter-spacing: 0.5em; text-transform: uppercase; color: #5A4B38; margin-top: 0.3rem; padding-left: 0.5em; } | |
| .miz-rule { width: min(520px, 88vw); margin: 1.1rem auto 0.4rem; display: flex; align-items: center; gap: 0.8rem; color: #A8843D; } | |
| .miz-rule .line { flex: 1; height: 1px; background: linear-gradient(to right, transparent, #A8843D); } | |
| .miz-rule .line.r { background: linear-gradient(to left, transparent, #A8843D); } | |
| .miz-rule .glyph { font-family: 'Amiri', serif; font-size: 1.4rem; opacity: 0.85; } | |
| .miz-tagline { font-family: 'Cormorant Garamond', serif; font-style: italic; font-size: 1.1rem; color: #5A4B38; text-align: center; max-width: 30rem; margin: 0.5rem auto 1.5rem; line-height: 1.5; } | |
| .miz-section { font-family: 'Cormorant Garamond', serif; font-weight: 600; text-transform: uppercase; letter-spacing: 0.28em; font-size: 0.8rem; color: #5A4B38; margin: 0.5rem 0 0.3rem; } | |
| .stTabs [data-baseweb="tab"] { font-family: 'Cormorant Garamond', serif; font-size: 1.05rem; letter-spacing: 0.1em; color: #5A4B38; } | |
| .stTabs [aria-selected="true"] { color: #2B2118; border-bottom-color: #8B2E20 !important; } | |
| .stTextArea textarea { background: rgba(255,253,247,0.6) !important; border: 1px solid rgba(90,75,56,0.3) !important; border-radius: 2px !important; font-family: 'Amiri', serif !important; font-size: 1.3rem !important; line-height: 1.7 !important; color: #2B2118 !important; direction: rtl; } | |
| .stTextArea textarea:focus { border-color: #A8843D !important; box-shadow: 0 0 0 3px rgba(168,132,61,0.15) !important; } | |
| .stButton button { font-family: 'Cormorant Garamond', serif !important; font-weight: 600 !important; letter-spacing: 0.14em; text-transform: uppercase; color: #E8DFC8 !important; background: #2B2118 !important; border: none !important; border-radius: 2px !important; padding: 0.6rem 1.8rem !important; } | |
| .stButton button:hover { background: #1d160f !important; } | |
| #MainMenu, footer, header { visibility: hidden; } | |
| </style> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| # --------------------------------------------------------------------------- | |
| # Masthead | |
| # --------------------------------------------------------------------------- | |
| st.markdown( | |
| """ | |
| <div class="miz-masthead"> | |
| <div class="ar">ميزان</div> | |
| <div class="en">Mizan</div> | |
| </div> | |
| <div class="miz-rule"><span class="line"></span><span class="glyph">۞</span><span class="line r"></span></div> | |
| <p class="miz-tagline">A scale for what we are told. Weigh a claim, and let the seal fall where it may.</p> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| def render_verdict(label, certainty, confidence): | |
| """Render the wax-seal verdict as a self-contained HTML component.""" | |
| is_fake = label == "Fake" | |
| accent = "#8B2E20" if is_fake else "#1F3A5F" | |
| accent_deep = "#6E2014" if is_fake else "#16293f" | |
| accent_light = "#d8a99e" if is_fake else "#9db4cf" | |
| accent_text = "#eccfc8" if is_fake else "#cdd9e8" | |
| mark = "\u2715" if is_fake else "\u2713" | |
| sub = "FALSE" if is_fake else "CREDIBLE" | |
| caption = "Sealed \u00b7 Disputed" if is_fake else "Sealed \u00b7 Authentic" | |
| html = f""" | |
| <!DOCTYPE html><html><head><meta charset="utf-8"> | |
| <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;600;700&family=Amiri:wght@400;700&display=swap" rel="stylesheet"> | |
| <style> | |
| body {{ margin:0; background:transparent; font-family:'Cormorant Garamond',serif; text-align:center; overflow:hidden; }} | |
| .eyebrow {{ text-transform:uppercase; letter-spacing:0.4em; font-size:0.75rem; color:#5A4B38; opacity:0; animation:fade 0.6s ease 0.1s forwards; }} | |
| .word {{ font-weight:700; font-size:clamp(2.4rem,8vw,3.6rem); line-height:1.05; color:#2B2118; opacity:0; animation:fade 0.7s ease 0.25s forwards; }} | |
| .conf {{ font-family:'Amiri',serif; font-size:1.05rem; color:#5A4B38; margin-top:0.4rem; opacity:0; animation:fade 0.7s ease 0.45s forwards; }} | |
| .seal {{ margin:1.6rem auto 0; width:140px; height:140px; transform:scale(2.4) rotate(-18deg); opacity:0; animation:stamp 0.5s cubic-bezier(0.2,1.4,0.4,1) 0.7s forwards; }} | |
| .cap {{ text-transform:uppercase; letter-spacing:0.32em; font-size:0.7rem; color:#5A4B38; margin-top:0.9rem; opacity:0; animation:fade 0.6s ease 1.15s forwards; }} | |
| @keyframes stamp {{ 0%{{transform:scale(2.4) rotate(-18deg);opacity:0;}} 60%{{opacity:1;}} 100%{{transform:scale(1) rotate(-6deg);opacity:1;}} }} | |
| @keyframes fade {{ from{{opacity:0;transform:translateY(10px);}} to{{opacity:1;transform:translateY(0);}} }} | |
| @media (prefers-reduced-motion: reduce) {{ * {{ animation-duration:0.01ms!important; animation-delay:0ms!important; }} }} | |
| </style></head><body> | |
| <div class="eyebrow">The Scale Has Spoken</div> | |
| <div class="word">{label}</div> | |
| <div class="conf">{certainty} \u00b7 Confidence {confidence}%</div> | |
| <div class="seal"> | |
| <svg viewBox="0 0 150 150" xmlns="http://www.w3.org/2000/svg"> | |
| <defs> | |
| <radialGradient id="wax" cx="40%" cy="35%" r="75%"> | |
| <stop offset="0%" stop-color="{accent}"/> | |
| <stop offset="100%" stop-color="{accent_deep}"/> | |
| </radialGradient> | |
| <filter id="rough"><feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" result="n"/><feDisplacementMap in="SourceGraphic" in2="n" scale="3.2"/></filter> | |
| </defs> | |
| <g filter="url(#rough)"> | |
| <circle cx="75" cy="75" r="64" fill="url(#wax)"/> | |
| <circle cx="75" cy="75" r="52" fill="none" stroke="{accent_light}" stroke-width="1.2" opacity="0.5"/> | |
| </g> | |
| <text x="75" y="62" text-anchor="middle" font-family="Amiri,serif" font-size="32" fill="{accent_text}" font-weight="700">{mark}</text> | |
| <text x="75" y="92" text-anchor="middle" font-family="Cormorant Garamond,serif" font-size="14" letter-spacing="2" fill="{accent_text}" font-weight="600">VERDICT</text> | |
| <text x="75" y="107" text-anchor="middle" font-family="Cormorant Garamond,serif" font-size="9" letter-spacing="3" fill="{accent_light}">{sub}</text> | |
| </svg> | |
| </div> | |
| <div class="cap">{caption}</div> | |
| </body></html> | |
| """ | |
| components.html(html, height=360) | |
| # --------------------------------------------------------------------------- | |
| # Tabs | |
| # --------------------------------------------------------------------------- | |
| tab1, tab2 = st.tabs(["Analyse", "Live Headlines"]) | |
| with tab1: | |
| st.markdown('<div class="miz-section">Submit a claim for judgment</div>', unsafe_allow_html=True) | |
| text_input = st.text_area("Paste a claim in Arabic", height=150, label_visibility="collapsed") | |
| if st.button("Render Verdict"): | |
| if text_input.strip() == "": | |
| st.warning("Please enter a claim first.") | |
| else: | |
| results = predict(text_input) | |
| if results["label"] == "Unsupported": | |
| st.warning("Only Arabic is supported in this version.") | |
| else: | |
| render_verdict(results["label"], results["certainty"], results["confidence"]) | |
| st.caption(f"Detected language: {results['language'].upper()}") | |
| with tab2: | |
| st.markdown('<div class="miz-section">Live Headlines</div>', unsafe_allow_html=True) | |
| if st.button("Fetch Headlines"): | |
| from scraper.headlines import get_headlines | |
| with st.spinner("Fetching headlines..."): | |
| headlines = get_headlines() | |
| for h in headlines: | |
| result = predict(h["title"]) | |
| col1, col2 = st.columns([3, 1]) | |
| with col1: | |
| st.markdown(f"**{h['title']}**") | |
| st.caption(f"{h['source']} \u00b7 {h['language'].upper()}") | |
| with col2: | |
| if result["label"] == "Unsupported": | |
| st.info("Arabic only") | |
| elif result["label"] == "Fake": | |
| st.error(f"{result['certainty']} Fake \u00b7 {result['confidence']}%") | |
| else: | |
| st.success(f"{result['certainty']} Credible \u00b7 {result['confidence']}%") | |
| st.divider() |