"""Patch app.py CSS block only — no imports, pure file I/O.""" import re, sys, pathlib TARGET = pathlib.Path(__file__).parent.parent / 'app.py' NEW_CSS = ( "\n" " @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700" "&family=Plus+Jakarta+Sans:wght@500;700;800&display=swap');\n" "\n" " :root {\n" " --bg: #0f1117;\n" " --bg-card: #181c27;\n" " --bg-inset: #11141d;\n" " --fg: #e8eaf0;\n" " --fg-soft: #9ea3b5;\n" " --muted: #636880;\n" " --accent: #7c6fff;\n" " --accent-light: #a89eff;\n" " --accent-glow: rgba(124, 111, 255, 0.28);\n" " --border: rgba(255, 255, 255, 0.07);\n" " --border-accent: rgba(124, 111, 255, 0.35);\n" " --shadow-card: 0 4px 32px rgba(0, 0, 0, 0.45), 0 1px 0 rgba(255,255,255,0.05) inset;\n" " --shadow-inset: inset 0 2px 8px rgba(0, 0, 0, 0.55);\n" " --shadow-btn: 0 4px 20px rgba(124, 111, 255, 0.28);\n" " --radius-card: 20px;\n" " --radius-base: 12px;\n" " --radius-sm: 8px;\n" " }\n" "\n" " html { scroll-behavior: smooth; }\n" "\n" " *, *::before, *::after { box-sizing: border-box; }\n" "\n" " body,\n" " .gradio-container,\n" " .gradio-container > .main,\n" " .gradio-container > .main > .wrap,\n" " footer {\n" " background: var(--bg) !important;\n" " color: var(--fg) !important;\n" " font-family: 'DM Sans', sans-serif !important;\n" " border: none !important;\n" " }\n" "\n" " .gradio-container {\n" " max-width: 1300px !important;\n" " padding: 28px 20px 56px !important;\n" " }\n" "\n" " h1, h2, h3, h4 {\n" " font-family: 'Plus Jakarta Sans', sans-serif !important;\n" " color: var(--fg) !important;\n" " letter-spacing: -0.025em;\n" " }\n" "\n" " /* === Hero === */\n" " .hero-shell {\n" " position: relative;\n" " overflow: hidden;\n" " padding: 40px 44px 36px;\n" " margin-bottom: 20px;\n" " background: var(--bg-card);\n" " border: 1px solid var(--border);\n" " border-radius: var(--radius-card);\n" " box-shadow: var(--shadow-card);\n" " }\n" "\n" " .hero-shell::before {\n" " content: '';\n" " position: absolute;\n" " right: -80px;\n" " top: -80px;\n" " width: 300px;\n" " height: 300px;\n" " border-radius: 9999px;\n" " background: radial-gradient(circle, rgba(124,111,255,0.25) 0%, transparent 70%);\n" " animation: orb-drift 6s ease-in-out infinite;\n" " pointer-events: none;\n" " }\n" "\n" " .hero-shell::after {\n" " content: '';\n" " position: absolute;\n" " left: 50%;\n" " bottom: -60px;\n" " width: 200px;\n" " height: 200px;\n" " border-radius: 9999px;\n" " background: radial-gradient(circle, rgba(99,200,200,0.12) 0%, transparent 70%);\n" " animation: orb-drift 8s ease-in-out infinite reverse;\n" " pointer-events: none;\n" " }\n" "\n" " .hero-kicker {\n" " display: inline-block;\n" " text-transform: uppercase;\n" " letter-spacing: 0.18em;\n" " font-size: 11px;\n" " color: var(--accent-light);\n" " font-weight: 700;\n" " background: rgba(124,111,255,0.12);\n" " border: 1px solid var(--border-accent);\n" " padding: 4px 12px;\n" " border-radius: 99px;\n" " margin-bottom: 16px;\n" " }\n" "\n" " .hero-title {\n" " margin: 0 0 14px;\n" " font-size: clamp(2.4rem, 4.5vw, 3.8rem);\n" " line-height: 1.0;\n" " font-weight: 800;\n" " background: linear-gradient(135deg, #ffffff 30%, var(--accent-light));\n" " -webkit-background-clip: text;\n" " -webkit-text-fill-color: transparent;\n" " background-clip: text;\n" " }\n" "\n" " .hero-copy {\n" " max-width: 680px;\n" " color: var(--fg-soft);\n" " font-size: 0.97rem;\n" " line-height: 1.75;\n" " margin: 0;\n" " }\n" "\n" " .hero-grid {\n" " display: grid;\n" " grid-template-columns: repeat(3, minmax(0, 1fr));\n" " gap: 12px;\n" " margin-top: 28px;\n" " }\n" "\n" " .hero-pill {\n" " border-radius: var(--radius-base);\n" " padding: 14px 16px;\n" " background: var(--bg-inset);\n" " border: 1px solid var(--border);\n" " min-height: 82px;\n" " transition: border-color 0.2s;\n" " }\n" " .hero-pill:hover { border-color: var(--border-accent); }\n" "\n" " .hero-pill-label {\n" " display: block;\n" " text-transform: uppercase;\n" " letter-spacing: 0.14em;\n" " color: var(--accent-light);\n" " font-size: 10px;\n" " margin-bottom: 6px;\n" " font-weight: 700;\n" " }\n" "\n" " /* === Status bar === */\n" " .soft-status {\n" " border-radius: var(--radius-base);\n" " padding: 10px 18px;\n" " margin-bottom: 18px;\n" " background: var(--bg-card) !important;\n" " border: 1px solid var(--border) !important;\n" " box-shadow: none !important;\n" " font-size: 0.88rem;\n" " color: var(--fg-soft) !important;\n" " }\n" "\n" " /* === Cards === */\n" " .soft-card {\n" " padding: 24px;\n" " background: var(--bg-card) !important;\n" " border: 1px solid var(--border) !important;\n" " border-radius: var(--radius-card) !important;\n" " box-shadow: var(--shadow-card) !important;\n" " }\n" "\n" " .section-title {\n" " margin: 0 0 6px;\n" " font-size: 1.35rem;\n" " font-weight: 800;\n" " color: var(--fg) !important;\n" " }\n" "\n" " .section-copy {\n" " margin: 0 0 18px;\n" " color: var(--muted);\n" " line-height: 1.6;\n" " font-size: 0.9rem;\n" " }\n" "\n" " /* === Image well === */\n" " .well {\n" " border-radius: var(--radius-base);\n" " box-shadow: var(--shadow-inset);\n" " padding: 10px;\n" " background: var(--bg-inset) !important;\n" " border: 1px solid var(--border) !important;\n" " }\n" "\n" " /* === Output boxes === */\n" " .summary-box {\n" " margin-top: 14px;\n" " border-radius: var(--radius-base);\n" " padding: 16px;\n" " background: var(--bg-inset) !important;\n" " border: 1px solid var(--border-accent) !important;\n" " box-shadow: 0 0 20px rgba(124,111,255,0.18) !important;\n" " }\n" "\n" " .detail-box {\n" " margin-top: 10px;\n" " border-radius: var(--radius-base);\n" " padding: 4px;\n" " background: var(--bg-inset) !important;\n" " border: 1px solid var(--border) !important;\n" " box-shadow: none !important;\n" " transition: border-color 0.2s;\n" " }\n" " .detail-box:hover { border-color: var(--border-accent) !important; }\n" "\n" " .download-box {\n" " margin-top: 12px;\n" " border-radius: var(--radius-base);\n" " padding: 8px;\n" " background: var(--bg-inset) !important;\n" " border: 1px dashed var(--border) !important;\n" " box-shadow: none !important;\n" " }\n" "\n" " .soft-footer {\n" " padding: 16px 20px;\n" " margin-top: 16px;\n" " background: transparent !important;\n" " border: none !important;\n" " box-shadow: none !important;\n" " }\n" "\n" " /* === Analyse button === */\n" " #analyze-btn {\n" " min-height: 52px;\n" " border: none !important;\n" " border-radius: var(--radius-base) !important;\n" " background: linear-gradient(135deg, var(--accent-light), var(--accent)) !important;\n" " color: white !important;\n" " font-weight: 700 !important;\n" " font-size: 1rem !important;\n" " letter-spacing: 0.02em !important;\n" " box-shadow: var(--shadow-btn) !important;\n" " transition: transform 260ms ease, box-shadow 260ms ease !important;\n" " }\n" " #analyze-btn:hover {\n" " transform: translateY(-2px) !important;\n" " box-shadow: 0 8px 30px rgba(124,111,255,0.4) !important;\n" " }\n" " #analyze-btn:active {\n" " transform: translateY(0) !important;\n" " box-shadow: none !important;\n" " }\n" "\n" " /* === Gradio structural overrides: kill conflicting defaults === */\n" " .gradio-container .block,\n" " .gradio-container .gr-box,\n" " .gradio-container .gr-group,\n" " .gradio-container .gr-form,\n" " .gradio-container .gr-panel,\n" " .gradio-container .gap-4,\n" " .gradio-container .row,\n" " .gradio-container [data-testid='block'] {\n" " background: transparent !important;\n" " border: none !important;\n" " box-shadow: none !important;\n" " color: var(--fg) !important;\n" " }\n" "\n" " /* Accordions */\n" " .gradio-container .gr-accordion {\n" " background: var(--bg-inset) !important;\n" " border: 1px solid var(--border) !important;\n" " border-radius: var(--radius-base) !important;\n" " box-shadow: none !important;\n" " overflow: hidden;\n" " }\n" " .gradio-container .gr-accordion > button {\n" " background: var(--bg-inset) !important;\n" " color: var(--fg) !important;\n" " font-weight: 600 !important;\n" " min-height: 46px;\n" " padding: 0 16px !important;\n" " border: none !important;\n" " }\n" "\n" " /* Text inputs */\n" " .gradio-container textarea,\n" " .gradio-container input[type='text'],\n" " .gradio-container input[type='number'] {\n" " background: var(--bg-inset) !important;\n" " border: 1px solid var(--border) !important;\n" " border-radius: var(--radius-sm) !important;\n" " color: var(--fg) !important;\n" " box-shadow: var(--shadow-inset) !important;\n" " }\n" "\n" " /* Image/file uploader zones */\n" " .gradio-container .wrap,\n" " .gradio-container .image-container,\n" " .gradio-container .upload-container,\n" " .gradio-container .empty,\n" " .gradio-container .file-preview,\n" " .gradio-container .file-wrap,\n" " .gradio-container [data-testid='image'],\n" " .gradio-container .gr-image,\n" " .gradio-container .gr-file {\n" " background: var(--bg-inset) !important;\n" " border: 1px dashed rgba(124,111,255,0.25) !important;\n" " border-radius: var(--radius-base) !important;\n" " box-shadow: none !important;\n" " color: var(--fg-soft) !important;\n" " }\n" "\n" " /* Markdown */\n" " .gradio-container .gradio-markdown,\n" " .gradio-container .prose {\n" " color: var(--fg) !important;\n" " background: transparent !important;\n" " }\n" " .gradio-container .gradio-markdown p,\n" " .gradio-container .gradio-markdown li,\n" " .gradio-container .gradio-markdown strong {\n" " color: var(--fg) !important;\n" " }\n" " .gradio-container .gradio-markdown h1,\n" " .gradio-container .gradio-markdown h2,\n" " .gradio-container .gradio-markdown h3 {\n" " color: var(--fg) !important;\n" " font-family: 'Plus Jakarta Sans', sans-serif !important;\n" " }\n" " .gradio-container .gradio-markdown blockquote {\n" " border-left: 3px solid var(--accent);\n" " padding-left: 12px;\n" " color: var(--fg-soft) !important;\n" " }\n" " .gradio-container .gradio-markdown code {\n" " background: rgba(124,111,255,0.12) !important;\n" " color: var(--accent-light) !important;\n" " border-radius: 4px;\n" " padding: 1px 5px;\n" " }\n" "\n" " /* Component labels */\n" " .gradio-container label > span,\n" " .gradio-container .label-wrap > span {\n" " color: var(--muted) !important;\n" " font-size: 0.75rem !important;\n" " font-weight: 700 !important;\n" " text-transform: uppercase !important;\n" " letter-spacing: 0.1em !important;\n" " }\n" "\n" " /* SVG icons */\n" " .gradio-container svg {\n" " color: var(--muted) !important;\n" " stroke: var(--muted) !important;\n" " }\n" "\n" " /* Secondary buttons */\n" " .gradio-container button:not(#analyze-btn) {\n" " background: var(--bg-card) !important;\n" " color: var(--fg) !important;\n" " border: 1px solid var(--border) !important;\n" " border-radius: var(--radius-sm) !important;\n" " }\n" "\n" " /* Focus rings */\n" " .gradio-container button:focus-visible,\n" " .gradio-container input:focus-visible,\n" " .gradio-container textarea:focus-visible {\n" " outline: none !important;\n" " box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent) !important;\n" " }\n" "\n" " /* Custom scrollbar */\n" " ::-webkit-scrollbar { width: 6px; height: 6px; }\n" " ::-webkit-scrollbar-track { background: var(--bg); }\n" " ::-webkit-scrollbar-thumb { background: var(--muted); border-radius: 99px; }\n" "\n" " @keyframes orb-drift {\n" " 0%, 100% { transform: translate(0, 0) scale(1); }\n" " 50% { transform: translate(-20px, 14px) scale(1.08); }\n" " }\n" "\n" " @media (max-width: 900px) {\n" " .hero-grid { grid-template-columns: 1fr; }\n" " .hero-title { font-size: 2.6rem; }\n" " .hero-shell { padding: 28px 24px; }\n" " }\n" " " ) # Read and normalise to LF so markers work regardless of CRLF raw = TARGET.read_bytes() src = raw.decode('utf-8').replace('\r\n', '\n') # Find the css = """...""" block start_marker = ' css = """\n' end_marker = '\n """\n' s = src.find(start_marker) if s == -1: print('ERROR: start marker not found') sys.exit(1) e = src.find(end_marker, s + len(start_marker)) if e == -1: print('ERROR: end marker not found') sys.exit(1) new_src = src[:s] + start_marker + NEW_CSS + src[e:] # Write back with LF endings TARGET.write_bytes(new_src.encode('utf-8')) print(f'OK - patched {TARGET} ({len(src)} -> {len(new_src)} bytes)')