GitHub Actions
deploy snapshot (223e474)
2770560
Raw History Blame Contribute Delete
4.35 kB
import base64
from functools import lru_cache
from pathlib import Path
CSS_PATH = Path(__file__).resolve().parent.parent / "ui"/"assets" / "style.css"
# Decorative app-wide background. A downscaled WebP keeps the inlined payload
# small (~300 KB) while the original PNG stays as the master asset.
FONDO_PATH = (
Path(__file__).resolve().parent.parent / "ui" / "assets" / "datathon_fondo_bg.webp"
)
FONDO_OPACITY = 0.3
# Palette tokens — the colours themselves live in style.css (:root). Keeping
# the token names here lets the inline status banners follow the theme.
COLOR_ERROR = "var(--color-danger-bg)"
COLOR_SUCCESS = "var(--color-success-bg)"
COLOR_ON_ACCENT = "var(--color-white)"
def is_error_message(message: str) -> bool:
"""True when a UI message is an error, whatever its display language.
Participant lifecycle messages are produced through the i18n dictionary,
whose ES errors start with "Error" and PT errors with "Erro".
"""
return message.startswith(("Error", "Erro"))
@lru_cache(maxsize=1)
def get_fondo_css() -> str:
"""CSS del fondo decorativo global (``datathon_fondo``).
La imagen se embebe como data URI base64 (mismo criterio que el logo del
sidebar) para no depender de una ruta servida estáticamente. Un
pseudo-elemento ``::before`` porta la opacidad, ya que CSS no permite fijar
la opacidad de un ``background-image`` directamente. El contenedor raíz de
Gradio establece su propio contexto de apilamiento (``isolation``) para que
el fondo quede por detrás del contenido.
"""
try:
data = base64.b64encode(FONDO_PATH.read_bytes()).decode("ascii")
except OSError:
return ""
return (
".gradio-container{position:relative;isolation:isolate;}"
# position:fixed (not background-attachment:fixed) so the background is
# anchored to the viewport and cover is computed against it; works on
# mobile browsers, where background-attachment:fixed is unreliable.
".gradio-container::before{"
"content:'';position:fixed;inset:0;z-index:-1;"
"background:url('data:image/webp;base64,"
f"{data}"
"') center/cover no-repeat;"
f"opacity:{FONDO_OPACITY};pointer-events:none;"
"}"
)
def get_css() -> str:
"""Devuelve el CSS global para inyectarlo en el <head> de la app.
Pasar esta cadena a ``gr.mount_gradio_app(css=...)`` hace que Gradio la
aplique antes del primer render, evitando el flash de contenido sin estilo.
"""
base = CSS_PATH.read_text(encoding="utf-8") if CSS_PATH.exists() else ""
return base + get_fondo_css()
# Inria Sans is the brand typeface for the UI (see style.css, which maps the
# theme's `--font` variable to it). The stylesheet is injected in the document
# head through `gr.mount_gradio_app(head=...)`; the preconnect hints shave the
# TLS handshake off the font request.
#
# `display=optional` (not `swap`): the web font is fetched asynchronously, so a
# late arrival under `swap` re-wraps text after first paint. In the HF Space
# iframe that made the header row (banner + login/language controls) reflow and
# the login button jump; a manual resize recomputed the stale layout. With
# `optional` the layout never waits for the font and never swaps after paint
# (fallback on the first uncached load, Inria Sans on later cached loads), which
# removes the layout shift.
INRIA_SANS_URL = (
"https://fonts.googleapis.com/css2"
"?family=Inria+Sans:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700"
"&display=optional"
)
def get_head() -> str:
"""Devuelve el HTML extra para el <head> (Google Fonts de Inria Sans)."""
return (
'<link rel="preconnect" href="https://fonts.googleapis.com">'
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>'
f'<link rel="stylesheet" href="{INRIA_SANS_URL}">'
)
def status_banner_html(message: str, is_error: bool) -> str:
"""Envuelve un mensaje de estado. Errores con fondo rojo, éxito con verde.
La caja es cuadrada (sin radio) con borde negro de 1px.
"""
bg = COLOR_ERROR if is_error else COLOR_SUCCESS
return (
f"<div style='background-color:{bg};color:{COLOR_ON_ACCENT};"
f"padding:8px;border:1px solid #000000;border-radius:0;'>"
f"{message}</div>"
)