{% if not _htmx_partial | default(false) %} {# #958 cycle 5 — haptic opt-in flag the framework JS reads at boot. Only emitted when `[ui] haptic = true` in dazzle.toml; the JS skips navigator.vibrate calls when the meta tag is absent. #} {% if haptic_enabled() %}{% endif %} {% block title %}{% if page_title %}{{ page_title }} — {{ app_name | default("App") }}{% else %}{{ app_name | default("App") }}{% endif %}{% endblock %} {# Inter font — always preconnected (used by every theme as fallback) #} {# v0.61.42 (Phase B Patch 6): theme-declared font_preconnect URLs. Each entry is a Google Fonts URL the theme's CSS references via `--font-sans` / `--font-mono`. Without this preconnect, the browser only discovers the font when it sees the CSS, adding ~150ms to first paint of any heading rendered in the theme font. #} {% if _app_theme_font_preconnect is defined and _app_theme_font_preconnect %} {% for font_url in _app_theme_font_preconnect %} {% endfor %} {% endif %} {# CSS Cascade Layer order — defines priority: base < framework < app < overrides #} {# v0.62 CSS refactor (Phase 4 teardown): Tailwind compiled bundle removed. All UI templates now consume the semantic .dz-* class families from dazzle.css (loaded below) and the legacy framework files it @imports — no more JIT compilation step required at build time. #} {# v0.61.36: optional app-shell theme override. The bundle ships design-system.css's default shadcn-zinc tokens; this loads an alternate :root block (e.g. linear-dark) AFTER the bundle so `@layer overrides` takes precedence. Set [ui] theme = "" in dazzle.toml. v0.61.41 (Phase B Patch 5): URL is now resolved at startup via the theme registry — `_app_theme_url` is set whether the theme is framework-shipped (`/static/css/themes/.css`) or project-local (`/static/themes/.css`). Falls back to the legacy inline path when only `_app_theme` is set (older deployments). v0.61.45 (Phase C Patch 1): if the active theme has an `extends` chain, _app_theme_url_chain holds [parent...child] URLs in cascade order. Emit ALL of them so the parent's tokens load first and the child's @layer overrides wins. Single-parent themes have a length-1 chain and render identically to Phase B. #} {% if _app_theme_url_chain is defined and _app_theme_url_chain %} {% for url in _app_theme_url_chain %} {% endfor %} {% elif _app_theme_url is defined and _app_theme_url %} {% elif _app_theme is defined and _app_theme %} {% endif %} {# v0.61.47 (Phase C Patch 3): theme map for live switching. Inline JSON consumed by dzThemeSwitcher Alpine component. Maps each available theme name to its inheritance-chain CSS URLs (in cascade order). Component swaps the elements without a page reload. #} {% if _app_theme_map is defined and _app_theme_map %} {% endif %} {# Asset loading: bundled `dist/dazzle.min.{js,css}` for mature sites where wire/parse efficiency outweighs per-file live-reload visibility, OR individual scripts for development and granular debugging. Resolved by `dazzle_ui.runtime.asset_bundle.should_bundle_assets()` from `[ui] assets` in dazzle.toml + DAZZLE_ENV + CLI overrides; defaults to individual when `_bundle_assets` is unset. #} {% if _bundle_assets %} {# Bundled mode — one CSS, one JS. Eliminates ~24 round-trips on cold connections; loses per-file live-reload visibility (acceptable once a site reaches maturity). Both files ship in the dazzle-dsl wheel under `dist/`, so Heroku slug builds don't need a Node toolchain. #} {% else %} {# Individual mode (default) — every script is a separate file with `defer` so the parser doesn't block. Trades wire-time efficiency for per-file live-reload during development. #} {# HTMX + extensions (vendored). Deferred so they don't block the HTML parser — extensions still register before DOMContentLoaded because defer scripts run in document order, and htmx's own auto-init waits for DCL anyway. The inline configRequest listener below uses addEventListener on `document`, which is defer-safe — the listener is attached before htmx fires the event regardless of script load order. #} {# htmx-ext-preload.js intentionally NOT loaded — the speculative-fetch pattern logged 403s on persona-rejected and racy URLs (#969 / #967), and the cache-warming benefit is small relative to htmx-boost's full-page-replace speed. Re-enable by adding the script back AND adding `preload` to the body's hx-ext list, but only after a fix that gates speculative fetches by per-link access. #} {# Alpine.js + plugins #} {# Dazzle framework CSS — single entry point that declares the cascade-layer order (reset, vendor, tokens, base, utilities, components, overrides) and @imports every layer + component file. #} {# #977 cycle 1 — Dazzle-native rich-text editor. #} {# #947 cycle 1 — Alpine × HTMX introspection helper. Exposes window.dzDebug for test introspection. Cheap and dormant until called; production deployments that don't need it can omit this script tag without runtime impact. #} {% endif %} {# CSRF token injection for HTMX requests (v1.0.0) #} {# Lucide icons (v0.38.0: nav group icons) — self-hosted vendored copy. The inline `lucide.createIcons()` in app_shell used to run before the deferred script loaded, leaving every `` as an empty . #846: hook createIcons to DOMContentLoaded *and* htmx:afterSettle so icons upgrade on initial load and after every HTMX swap. #} {# Design token CSS custom properties #} {% block head_extra %}{% endblock %} {% endif %} {% block body %} {% endblock %} {# Toast notifications — Alpine dzToast component (UX-033 / UX-013 tokens). v0.62 CSS refactor: tone tinting moved from inline `border-l-[hsl(...)]` ternary to `:data-dz-toast-level` attribute selectors on .dz-toast (components/fragments.css). #} {# #995 — hx-preserve protects the toast container from Alpine.initTree(body) re-walking on hx-boost navigation. Without it, the global htmx:afterSettle handler in dz-alpine.js re-evaluates the x-for child bindings (`t.message`, `t.leaving`) before the x-for scope re-establishes `t`, logging three "t is not defined" Alpine errors per page render. Same bug class as #963/#968/#970. #}
{# Toast container for HTMX OOB-driven toasts (remove-me auto-dismiss) #}
{# Modal slot for server-loaded modals #}
{# Dynamic asset loader for HTMX-swapped widget dependencies #}
{# Screen-reader page announcer for hx-boost navigation #}
{# Server-rendered user preferences for client-side access (v0.38.0) #} {% if user_preferences %} {% endif %} {# Feedback widget — injected when feedback_widget is enabled in DSL #} {% if _feedback_widget_enabled | default(false) %} {% endif %} {# Conditional vendor widget assets — populated by asset_manifest #} {% if required_assets is defined %} {% if "tom-select" in required_assets %} {% endif %} {% if "flatpickr" in required_assets %} {% endif %} {# #976: Pickr removed — `widget=color` now uses native . The conditional asset block + `pickr` key in asset_manifest.py are gone. Adopters who need swatches/alpha can register a custom widget; the default ships zero KB for `widget=color`. #} {# #977 cycle 4: rich-text editor moved to dz-richtext.js (bundled unconditionally with the core JS). No vendor library required. #} {% endif %} {% block scripts_extra %}{% endblock %} {% if not _htmx_partial | default(false) %} {% endif %}