/// Landing Page Handler /// /// Generates HTML landing page for the Nostr relay. use crate::config::Config; /// Get the software version string (version + optional git commit) fn get_version() -> String { let version = env!("CARGO_PKG_VERSION"); match option_env!("GIT_COMMIT_SHORT") { Some(commit) if !commit.is_empty() => format!("v{}-{}", version, commit), _ => format!("v{}", version), } } /// Generate the footer JavaScript that sets the domain dynamically fn get_footer_script() -> &'static str { r#""# } /// Generate the theme toggle HTML button fn get_theme_toggle_html() -> &'static str { r##""## } /// Generate the theme toggle JavaScript fn get_theme_script() -> &'static str { r#""# } /// Generate the common base CSS used across all pages fn get_base_css() -> &'static str { r#"/* Dark mode (default) */ :root { --brand: #4434FF; --brand-light: #6b5fff; --bg: #0a0a0f; --surface: #12121a; --border: #1e1e2e; --text: #e4e4eb; --text-muted: #a8a8bd; --error: #ff4444; --success: #22c55e; --logo-bg: #4434FF; --logo-icon: white; } /* Light mode - system preference */ @media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) { --brand: #4434FF; --brand-light: #3525cc; --bg: #f8f9fa; --surface: #ffffff; --border: #e1e4e8; --text: #1a1a2e; --text-muted: #586069; --error: #dc3545; --success: #28a745; --logo-bg: #4434FF; --logo-icon: white; } } /* Manual light mode override */ :root[data-theme="light"] { --brand: #4434FF; --brand-light: #3525cc; --bg: #f8f9fa; --surface: #ffffff; --border: #e1e4e8; --text: #1a1a2e; --text-muted: #586069; --error: #dc3545; --success: #28a745; --logo-bg: #4434FF; --logo-icon: white; } /* Manual dark mode override */ :root[data-theme="dark"] { --brand: #4434FF; --brand-light: #6b5fff; --bg: #0a0a0f; --surface: #12121a; --border: #1e1e2e; --text: #e4e4eb; --text-muted: #a8a8bd; --error: #ff4444; --success: #22c55e; --logo-bg: #4434FF; --logo-icon: white; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', sans-serif; line-height: 1.6; background: var(--bg); color: var(--text); min-height: 100vh; transition: background-color 0.3s ease, color 0.3s ease; } a { color: var(--brand-light); text-decoration: none; } a:hover { text-decoration: underline; } code { background: var(--border); padding: 4px 8px; border-radius: 4px; font-family: 'SF Mono', 'Consolas', monospace; font-size: 0.875rem; color: var(--brand-light); } /* Theme toggle button */ .theme-toggle { position: fixed; top: 16px; right: 16px; z-index: 1000; background: var(--surface); border: 1px solid var(--border); border-radius: 50%; width: 44px; height: 44px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .theme-toggle:hover { transform: scale(1.1); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } .theme-toggle svg { width: 20px; height: 20px; fill: var(--text); transition: fill 0.3s ease; } .theme-toggle .sun-icon { display: none; } .theme-toggle .moon-icon { display: block; } :root[data-theme="light"] .theme-toggle .sun-icon, :root:not([data-theme="dark"]) .theme-toggle .sun-icon { display: block; } :root[data-theme="light"] .theme-toggle .moon-icon, :root:not([data-theme="dark"]) .theme-toggle .moon-icon { display: none; } @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .theme-toggle .sun-icon { display: none; } :root:not([data-theme="light"]) .theme-toggle .moon-icon { display: block; } } .footer { margin-top: 48px; padding-top: 24px; border-top: 1px solid var(--border); text-align: center; color: var(--text-muted); font-size: 0.875rem; } .footer-separator { margin: 0 0.5em; opacity: 0.5; } .software-box { display: flex; align-items: flex-start; gap: 16px; text-align: left; } .software-logo { width: 48px; height: 48px; flex-shrink: 0; } .software-logo rect { fill: var(--logo-bg); } .software-logo path { fill: var(--logo-icon); } .software-content { flex: 1; } .software-heading { font-size: 1.125rem; font-weight: 500; margin-bottom: 8px; color: var(--text-muted); } .software-heading a { color: var(--brand-light); } .software-heading a:hover { text-decoration: underline; } .software-desc { color: var(--text-muted); font-size: 0.9rem; line-height: 1.5; }"# } /// Generate the HTML landing page pub fn get_html(config: &Config) -> String { // Curation matches NIP-11 document - currently None for this relay let curation = "None".to_string(); format!( include_str!("../../templates/landing.html"), base_css = get_base_css(), relay_name = config.relay_name(), relay_description = config.relay_description, version = get_version(), curation = curation, theme_toggle = get_theme_toggle_html(), theme_script = get_theme_script(), ) } /// Generate a generic 404 page for unknown paths /// /// Used for any path that doesn't match a known route pub fn get_generic_404_html(config: &Config, path: &str) -> String { format!( r##"
The page you're looking for doesn't exist.
{path}
This repository doesn't exist on this GRASP server.
{npub}
{identifier}
Git repository hosted using the Grasp Protocol
Browse Repository on GitWorkshop.dev →