Skip to main content

orbital_theme/baseline/
mod.rs

1//! First-paint theme baseline CSS — fonts and scoped CSS variables for SSR.
2//!
3//! Baseline covers default light theme tokens only. Dark mode, custom brand colors,
4//! and route-specific branding remain runtime responsibilities via
5//! [`OrbitalThemeProvider`](crate::OrbitalThemeProvider) and `inject_dynamic_style`.
6//!
7//! ## Scope ID contract
8//!
9//! The outermost [`OrbitalThemeProvider`] in an app tree (e.g. via [`OrbitalTemplate`])
10//! must be the first provider rendered so it receives [`ROOT_THEME_SCOPE_ID`] (`"0"`).
11//! Baseline CSS targets:
12//!
13//! ```css
14//! .orbital-theme-provider[data-orbital-theme-id="0"] { /* vars */ }
15//! ```
16//!
17//! Nested providers (`"1"`, …) are never baselined.
18
19mod injection;
20
21#[cfg(test)]
22mod tests;
23
24pub use injection::{
25    baseline_active_in_document, is_baseline_theme, should_skip_root_baseline_injection,
26};
27
28use crate::context::scoped_css;
29use crate::fonts::{font_faces_css, font_faces_css_with_asset_prefix};
30use crate::Theme;
31
32/// Stable scope id for the root shell [`OrbitalThemeProvider`].
33pub const ROOT_THEME_SCOPE_ID: &str = "0";
34
35/// Filename served from the app static assets directory.
36pub const BASELINE_STYLESHEET_FILENAME: &str = "orbital-theme-baseline.css";
37
38/// Default theme for anonymous first visit (light + product token defaults).
39pub fn default_first_paint_theme() -> Theme {
40    Theme::light()
41}
42
43/// Scoped CSS variables block for a theme scope (no `@font-face` rules).
44pub fn theme_scoped_vars_css(scope_id: &str, theme: &Theme) -> String {
45    let mut css_vars = String::new();
46    theme.write_css_vars(&mut css_vars);
47    scoped_css(scope_id, &css_vars)
48}
49
50/// Full first-paint baseline: `@font-face` rules plus scoped theme variables.
51pub fn theme_baseline_css(scope_id: &str, theme: &Theme) -> String {
52    let mut out = font_faces_css();
53    out.push('\n');
54    out.push_str(&theme_scoped_vars_css(scope_id, theme));
55    out
56}
57
58/// Full first-paint baseline with an explicit font asset prefix (see
59/// [`font_faces_css_with_asset_prefix`](crate::fonts::font_faces_css_with_asset_prefix)).
60pub fn theme_baseline_css_with_font_prefix(
61    scope_id: &str,
62    theme: &Theme,
63    font_prefix: &str,
64) -> String {
65    let mut out = font_faces_css_with_asset_prefix(font_prefix);
66    out.push('\n');
67    out.push_str(&theme_scoped_vars_css(scope_id, theme));
68    out
69}
70
71/// Convenience: [`default_first_paint_theme`] baseline for the given scope id.
72pub fn default_first_paint_baseline_css(scope_id: &str) -> String {
73    theme_baseline_css(scope_id, &default_first_paint_theme())
74}
75
76/// Default first-paint baseline with an explicit font asset prefix.
77///
78/// Used by `orbital`'s `build.rs` so `LEPTOS_BASE_PATH` is applied at build-script
79/// runtime rather than via a nested `cargo` invocation.
80pub fn default_first_paint_baseline_css_with_font_prefix(
81    scope_id: &str,
82    font_prefix: &str,
83) -> String {
84    theme_baseline_css_with_font_prefix(scope_id, &default_first_paint_theme(), font_prefix)
85}
86
87/// Baseline stylesheet filename (see [`BASELINE_STYLESHEET_FILENAME`]).
88pub fn baseline_stylesheet_filename() -> &'static str {
89    BASELINE_STYLESHEET_FILENAME
90}
91
92/// Base-path-aware URL for the baseline stylesheet at site root.
93pub fn baseline_asset_path(base_path: &str) -> String {
94    let base = base_path.trim_end_matches('/');
95    let file = BASELINE_STYLESHEET_FILENAME;
96    if base.is_empty() {
97        format!("/{file}")
98    } else {
99        format!("{base}/{file}")
100    }
101}
102
103/// Base-path-aware URL for the primary UI font preload.
104pub fn baseline_primary_font_preload_path(base_path: &str) -> String {
105    let base = base_path.trim_end_matches('/');
106    let path = "fonts/league-spartan/LeagueSpartan-VF.woff2";
107    if base.is_empty() {
108        format!("/{path}")
109    } else {
110        format!("{base}/{path}")
111    }
112}