diff --git a/src/main.rs b/src/main.rs index 15a68d8..bf89ddf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,8 +15,27 @@ use axum::{ Router, }; use std::net::SocketAddr; +use std::sync::OnceLock; use tower_http::{compression::CompressionLayer, trace::TraceLayer}; +/// Short content hash of the bundled CSS, used as a `?v=` cache-buster: a changed +/// stylesheet gets a new URL (fetched fresh), an unchanged one stays cached. +fn asset_ver() -> &'static str { + static V: OnceLock = OnceLock::new(); + V.get_or_init(|| { + let mut h: u64 = 0xcbf29ce484222325; + for b in include_str!("../static/style.css") + .bytes() + .chain(include_str!("../static/docs.css").bytes()) + { + h ^= b as u64; + h = h.wrapping_mul(0x100000001b3); + } + format!("{h:x}") + }) + .as_str() +} + /// Display-ready snapshot of the live network state for templates. struct Status { online: bool, @@ -48,6 +67,7 @@ struct AppState { #[template(path = "index.html")] struct IndexTemplate { active: &'static str, + css_v: &'static str, status: Status, commits: Vec, } @@ -56,18 +76,21 @@ struct IndexTemplate { #[template(path = "features.html")] struct FeaturesTemplate { active: &'static str, + css_v: &'static str, } #[derive(Template)] #[template(path = "connect.html")] struct ConnectTemplate { active: &'static str, + css_v: &'static str, } #[derive(Template)] #[template(path = "services.html")] struct ServicesTemplate { active: &'static str, + css_v: &'static str, } struct SidePage { @@ -84,6 +107,7 @@ struct SideSection { #[template(path = "docs.html")] struct DocsTemplate { active: &'static str, + css_v: &'static str, page_title: &'static str, body: &'static str, toc: &'static [docs::Heading], @@ -104,16 +128,16 @@ fn page(t: T) -> Response { async fn index(State(st): State) -> Response { let commits = st.commits.read().ok().map(|g| g.clone()).unwrap_or_default(); - page(IndexTemplate { active: "home", status: view(&st.stats), commits }) + page(IndexTemplate { active: "home", css_v: asset_ver(), status: view(&st.stats), commits }) } async fn features() -> Response { - page(FeaturesTemplate { active: "features" }) + page(FeaturesTemplate { active: "features", css_v: asset_ver() }) } async fn connect() -> Response { - page(ConnectTemplate { active: "connect" }) + page(ConnectTemplate { active: "connect", css_v: asset_ver() }) } async fn services() -> Response { - page(ServicesTemplate { active: "services" }) + page(ServicesTemplate { active: "services", css_v: asset_ver() }) } async fn docs_index() -> Response { @@ -137,6 +161,7 @@ async fn docs_page(Path(slug): Path) -> Response { let (prev, next) = docs::neighbours(&slug); page(DocsTemplate { active: "docs", + css_v: asset_ver(), page_title: p.title, body: &p.html, toc: &p.toc, @@ -154,13 +179,19 @@ async fn docs_search() -> impl IntoResponse { async fn style() -> impl IntoResponse { ( - [(header::CONTENT_TYPE, "text/css; charset=utf-8")], + [ + (header::CONTENT_TYPE, "text/css; charset=utf-8"), + (header::CACHE_CONTROL, "public, max-age=31536000, immutable"), + ], include_str!("../static/style.css"), ) } async fn docs_css() -> impl IntoResponse { ( - [(header::CONTENT_TYPE, "text/css; charset=utf-8")], + [ + (header::CONTENT_TYPE, "text/css; charset=utf-8"), + (header::CACHE_CONTROL, "public, max-age=31536000, immutable"), + ], include_str!("../static/docs.css"), ) } diff --git a/static/style.css b/static/style.css index 8ee78cd..e2e7553 100644 --- a/static/style.css +++ b/static/style.css @@ -1,6 +1,6 @@ /* echoIRCd — teal is echo's own colour. Light by default; dark via [data-theme]. */ :root{ - --teal:#12a594; --teal-d:#0e8577; --teal-dd:#0a6a5f; --teal-t:#e7f6f3; + --teal:#12a594; --teal-d:#0e8577; --teal-dd:#0a6a5f; --teal-t:#e7f6f3; --teal-edge:#0a6a5f; --ink:#1b2733; --muted:#5c6773; --faint:#8b95a0; --line:#e4e8eb; --line2:#d8dee2; --bg:#f5f6f7; --card:#ffffff; --panel:#fbfcfd; --code-bg:#132430; --code-fg:#e7edf0; --cmd:#6fe0cf; @@ -12,7 +12,7 @@ --wrap:1080px; } :root[data-theme="dark"]{ - --teal:#15b6a3; --teal-d:#48d8c4; --teal-dd:#7de9db; --teal-t:#0f2d29; + --teal:#15b6a3; --teal-d:#48d8c4; --teal-dd:#7de9db; --teal-t:#0f2d29; --teal-edge:#0b8474; --ink:#e3ece8; --muted:#93a8a1; --faint:#66807a; --line:#1b2e29; --line2:#26403a; --bg:#0b1614; --card:#132622; --panel:#0f1e1b; --purple-line:#4f5a7a; --purple-fg:#bfc7ee; --purple-bg:#182034; --bq-fg:#8fe6d8; @@ -36,12 +36,14 @@ code{font-family:var(--mono);font-size:.9em;background:rgba(18,165,148,.09);colo .mono-line{font-family:var(--mono);color:var(--muted)} /* buttons */ -.btn{display:inline-block;font-weight:600;font-size:.95rem;padding:.6rem 1.25rem;border-radius:6px;border:1.5px solid transparent;transition:background .14s ease,border-color .14s ease,color .14s ease} +.btn{display:inline-flex;align-items:center;gap:.45rem;font-weight:600;font-size:.95rem;letter-spacing:.005em;padding:.58rem 1.25rem;border-radius:5px;border:1.5px solid transparent;cursor:pointer;transition:background .14s ease,border-color .14s ease,color .14s ease,box-shadow .09s ease,transform .09s ease} .btn:hover{text-decoration:none} -.btn-primary{background:var(--teal);border-color:var(--teal);color:#fff} +.btn-primary{background:var(--teal);border-color:var(--teal);color:#fff;box-shadow:0 3px 0 var(--teal-edge),inset 0 1px 0 rgba(255,255,255,.2)} .btn-primary:hover{background:var(--teal-d);border-color:var(--teal-d)} -.btn-ghost{border-color:var(--line2);color:var(--ink);background:transparent} -.btn-ghost:hover{border-color:var(--teal);color:var(--teal-d);background:var(--teal-t)} +.btn-primary:active{transform:translateY(2px);box-shadow:0 1px 0 var(--teal-edge),inset 0 1px 0 rgba(255,255,255,.15)} +.btn-ghost{border-color:var(--teal);color:var(--teal-d);background:transparent} +.btn-ghost:hover{background:var(--teal-t);color:var(--teal-dd)} +.btn-ghost:active{transform:translateY(1px)} /* top bar (teal in both themes) */ .topbar{background:var(--teal);color:#fff;position:sticky;top:0;z-index:20;box-shadow:0 2px 12px -6px rgba(0,0,0,.35)} diff --git a/templates/base.html b/templates/base.html index 365f58e..c3bef3a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -14,7 +14,7 @@ - + {% block head %}{% endblock %} diff --git a/templates/docs.html b/templates/docs.html index b61fbbf..1da68a4 100644 --- a/templates/docs.html +++ b/templates/docs.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% block title %}{{ page_title }} — echoIRCd Docs{% endblock %} {% block head %} - + {% endblock %} {% block content %}