{# Layout: menú lateral + contenido. Sin barra superior en escritorio: logo, buscador (Ctrl K), navegación, modo claro/oscuro y cuenta viven en la lateral. En móvil queda una barra mínima (hamburguesa + logo) para abrir la lateral como cajón. La host app define/override: - block title - block logo → logotipo (arriba de la lateral y en la barra móvil) - block sidebar_nav → enlaces (usar core/components/sidebar_link.html) - block breadcrumbs → ruta, en una línea sobre el contenido - block topbar_actions → acciones globales, a la derecha de la ruta - block user_menu_items → enlaces extra del menú de cuenta; por defecto incluye `partials/user_menu.html` si la app lo tiene - block content - block footer → contenido extra del pie legal - block extra_head, block extra_body, block toast_initial Variables esperadas (opcionales): - user.email, user.is_admin, user.name - tokens_override_href → al CSS de tokens del cliente - dg_font_preload → ruta del .woff2 del texto base de la marca (global de Jinja; sin ella no se precarga) - logout_url → acción del formulario de cierre de sesión #} {% from "core/components/icon.html" import icon as _icon %} {% from "core/components/initials.html" import initials as _initials_of %} {# viewport-fit=cover activa env(safe-area-inset-*) en iOS con notch #} {% block title %}Datagrowth{% endblock %} {# FOUC guard: aplica el tema antes del primer paint para evitar el parpadeo light->dark en usuarios con preferencia "dark" guardada. Se ejecuta síncronamente (sin defer) y SOLO toca data-theme; el resto del API (toggle, sync de icons/aria) vive en theme.js. #} {# Las @font-face viven en tokens.css. Precargar la cara del texto base evita esperar a que se parsee el CSS, pero cuál es depende de la marca y el shell no la conoce: la app fija `dg_font_preload` (ruta a un .woff2) y sin ella no se precarga nada, porque una fuente que la página no pinta es una descarga inútil y un aviso de consola. Sin `crossorigin` el navegador la descargaría dos veces (las fuentes se piden en modo CORS). #} {% set _font_preload = dg_font_preload | default('') %} {% if _font_preload %} {% endif %} {# ?v= con la versión de la UI: tras un bump del paquete el navegador no reutiliza el CSS/JS cacheado de la versión anterior. #} {% set _v = dg_ui_version | default('') %} {# Fallback a `request.state.dg_tokens_override_href` para handlers que no pasan `tokens_override_href` en el context Jinja (caso: routers del paquete que renderizan con context propio). La host app puebla el state via `populate_template_state` con la URL al CSS del `BrandDesign` que viste al host. #} {% set _tokens_override_href = tokens_override_href or (request.state.dg_tokens_override_href if request.state else None) %} {% if _tokens_override_href %} {% endif %} {# htmx 1.9.12 y su extensión preload viajan en el paquete (static/js/vendor, 0BSD: LICENSE-htmx.txt): cargarlos de una CDN le daría a un tercero la IP de cada usuario y el dominio de la app, dejaría el shell sin HTMX si la CDN falla y obligaría a abrirle el script-src de la CSP. La extensión va detrás: se registra contra el `htmx` global. #} {# CSRF double-submit wiring. SIN `defer`: el monkey-patch de `fetch` debe estar activo antes de cualquier ` {% block extra_head %}{% endblock %} {% set user = user or (request.state.dg_user if request.state else None) %} {# ── Barra mínima (solo móvil) ───────────────────────────── #}
{{ self.logo() }}
{# Overlay semitransparente detrás del cajón en móvil. Click en él lo cierra. #} {# ── Lateral (escritorio: fija a la izquierda; móvil: cajón) ─ #} {# ── Contenido principal ─────────────────────────────────── #} {# Solo `main` hace scroll: el body tiene alto de viewport y overflow oculto. #}
{% if self.breadcrumbs() | trim or self.topbar_actions() | trim %}
{% if self.breadcrumbs() | trim %} {% endif %}
{% block topbar_actions %}{% endblock %}
{% endif %}
{% block content %}{% endblock %}
{# El titular es el cliente de la marca cuando la app lo conoce (global `dg_branding`), igual que en sus páginas de acceso; el enlace de soporte solo sale si la app publica `dg_support_url`. #} {% set _client_name = (dg_branding | default({})).client_name | default('', true) %} {% set _support_url = dg_support_url | default('', true) %}
{# Región de toasts. `register_error_handlers` inyecta un toast inicial server-side vía el bloque `toast_initial`. El mismo contenedor recibe OOB swaps de HTMX y toasts client-side. #}
{% block toast_initial %}{% endblock %}
{# Command palette global (Ctrl+K). El binding vive en htmx-events.js. #} {% include "core/components/command_palette.html" %} {% block extra_body %}{% endblock %}