{# Theme head block: anti-FOUC script, CSS, and optional JS include #} {# Safety: autoescape off is safe here — all variables (css_block, direction, etc.) #} {# are framework-generated in theme_tags.py, not user input. #} {# cookie_prefix_js and resolved_mode_js are JSON-encoded strings, framework-generated. #} {# nonce_attr is built with format_html in build_theme_head_context, so it is already escaped. #} {# nonce_js is json.dumps with <, > and & escaped, so it is a safe JS string literal. #} {% load static %} {% autoescape off %} (function() { window.__djust_theme_cookie_prefix = {{ cookie_prefix_js }}; {% if nonce_js %}/* CSP nonce, read by theme.js for the style element it creates on a live preset switch. */ window.__djust_theme_nonce = {{ nonce_js }}; {% endif %} /* Single source of truth for the default mode, read by BOTH this script and theme.js (via getMode()). theme.js loads `defer`, so it runs after this one and re-applies the mode — when it resolved its own fallback the two could disagree, and the later one won. */ window.__djust_theme_default_mode = {{ resolved_mode_js|default:'"system"' }}; {% if loading_class %}document.documentElement.classList.add('loading'); {% endif %}var storageKey = 'djust-theme-mode'; var storedMode = null; try { storedMode = localStorage.getItem(storageKey); } catch (e) { /* Storage can throw outright (private windows, blocked site data). An explicit visitor choice is a nicety; the theme itself is not, so fall through to the server-resolved mode rather than aborting the script and leaving the page unstyled. */ } /* Fallback is the mode the SERVER resolved — config default, session, or cookie — not a hardcoded 'system'. A project configuring default_mode: 'dark' used to come up light on any machine whose OS preferred light. */ var mode = storedMode || window.__djust_theme_default_mode || 'system'; var resolvedMode = mode; if (mode === 'system') { resolvedMode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } document.documentElement.setAttribute('data-theme', resolvedMode); document.documentElement.setAttribute('data-theme-mode', mode); document.documentElement.setAttribute('dir', '{{ direction }}'); })(); {{ css_block }} {{ deferred_css_block }} {% comment %} `?v={{ asset_version }}` on each of these, because none of them had a cache-buster and Django's static server sends no `Cache-Control` — so a browser applied heuristic freshness to `components.js` / `components.css` and kept the copy it already had. An edit to either was invisible on the page it was made for. The token is derived from the assets' newest mtime (see `_theme_asset_version`), so it moves on an edit rather than on a release. {% endcomment %} {% if include_component_link %}{% endif %} {% if include_components_app_link %}{% endif %} {{ component_css_block }} {% if include_js %} {% endif %} {% endautoescape %}