{#- layout_auth.html — full-screen layout for authentication pages. WHY THIS EXISTS (the bite it fixes): Auth pages (login, register, reset) are full-screen but are NOT the public site and NOT the logged-in app — a third archetype. Forcing them to inherit a public/app layout, or to inherit nothing, made every generation re-decide where CSRF, security meta, viewport, and theme go. This layout is the single canonical home for that contract, so a child page NEVER re-decides it. WHAT A CHILD PROVIDES: {% block title %} -> text {% block eyebrow %} -> the mono eyebrow above the heading (optional) {% block heading %} -> the h1 of the card {% block subheading %} -> one line under the heading (optional) {% block auth_body %} -> the form / content of the card {% block auth_foot %} -> below the card (e.g. "No account? Register") (optional) {% block head_extra %} -> page-specific <head> additions (optional) {% block scripts %} -> page-specific JS. MUST carry nonce (see below). THE SECURITY CONTRACT (all wired here, once): - data-theme on <html> is rendered from the `theme` cookie -> no flash. - <meta name="csrf-token"> exposes the token for fabbro.request()/fetch. Forms still submit a hidden csrf_token field (see the example below). - Any inline <script> MUST carry nonce="{{ csp_nonce() }}", because the CSP is script-src 'self' with a per-request nonce (no 'unsafe-inline'). fabbro.js and fabbro.css are same-origin ('self'), so they need no nonce. - Server flashes are handed to fabbro.js as JSON and shown as toasts. CSRF IN A CHILD FORM (copy this pattern): <form method="post" action="{{ url_for('auth.login') }}" data-fbb-loading> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> ...fields... </form> -#} {#- Sprite path defined ONCE here; children reference it as {{ sprite }}#id. Rename the sprite in a single place instead of hunting every <use>. -#} {% set sprite = url_for('static', filename='img/fabbro_icons.svg') %} <!DOCTYPE html> <html lang="{{ lang|default('en') }}" data-theme="{{ request.cookies.get('theme', 'dark') }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <meta name="color-scheme" content="dark light"> {#- CSRF token for JS (fetch/fabbro.request read this). Forms use the hidden field. -#} <meta name="csrf-token" content="{{ csrf_token() }}"> <title>{% block title %}Sign in{% endblock %} · Fabbro {% block head_extra %}{% endblock %} {#- Ambient forge glow, purely decorative, sits behind the card. -#}
{#- Brand lockup: symbol + wordmark, links home. -#} Fabbro {#- The card: the same "forged" surface validated in the design system. -#}
{% block eyebrow %}{% endblock %}

{% block heading %}Welcome back{% endblock %}

{% block subheading %}{% endblock %}
{% block auth_body %}{% endblock %}
{#- Below-card slot: cross-links (register/login/reset), legal, etc. -#}
{% block auth_foot %}{% endblock %}
{#- A minimal mono signature at the very bottom of the viewport. -#}

Fabbro · by Forjelo

{#- Server flashes -> toasts. fabbro.js drains this on load. Render your flashed messages as [{type, message, title?}]. -#} {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% endif %} {% endwith %} {#- Point the sprite at the served URL. Inline => MUST carry the nonce. -#} {% block scripts %}{% endblock %} {#- ── Layout-scoped styles. style-src allows 'unsafe-inline', so a