Web dashboard v2 step 2: Tailwind design system, theme toggle, reader pages
Replaces the hand-written stylesheet with a Tailwind CSS v4 source (src/web/tailwind.css) whose minified build is committed as src/web/static/app.css and still embedded with include_str!, so the binary and the test suite never need Node. package.json pins tailwindcss and @tailwindcss/cli to 4.3.3 and adds css, css:watch and css:check. Colours are semantic CSS variables swapped per theme and exposed to Tailwind through @theme inline, so templates write bg-paper/text-ink/border-rule once and both themes work. theme.js runs synchronously in <head> to set data-theme before paint; app.js cycles the toggle system -> light -> dark and persists the choice. Newsreader is self-hosted and served from web::static_asset with the same ETag/304 logic as the other assets. Every reader template is restyled: ears row, centred masthead with its rules, tracked section labels, the drop cap on The Brief, "Why it's here" with its accent bar, the segmented rating control, prev/next cards, and the colophon as a two-column definition grid. Dashboard templates are untouched (step 3). Also fixes a .block collision with Tailwind's display utility, the SVG `hidden` property that never toggled the theme icon, and points the dev seed's profile_path at ./dev so previewing cannot rewrite data/profile.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NHyYupFdBiR4VfoUM7NjSM
This commit is contained in:
+23
-5
@@ -502,16 +502,34 @@ async fn static_asset(
|
||||
axum::extract::Path(file): axum::extract::Path<String>,
|
||||
headers: axum::http::HeaderMap,
|
||||
) -> Response {
|
||||
let asset = match file.as_str() {
|
||||
"app.css" => ("text/css; charset=utf-8", include_str!("static/app.css")),
|
||||
let asset: (&str, &'static [u8]) = match file.as_str() {
|
||||
"app.css" => (
|
||||
"text/css; charset=utf-8",
|
||||
include_str!("static/app.css").as_bytes(),
|
||||
),
|
||||
"app.js" => (
|
||||
"application/javascript; charset=utf-8",
|
||||
include_str!("static/app.js"),
|
||||
include_str!("static/app.js").as_bytes(),
|
||||
),
|
||||
"theme.js" => (
|
||||
"application/javascript; charset=utf-8",
|
||||
include_str!("static/theme.js").as_bytes(),
|
||||
),
|
||||
"favicon.svg" => (
|
||||
"image/svg+xml",
|
||||
include_str!("static/favicon.svg").as_bytes(),
|
||||
),
|
||||
"Newsreader.woff2" => (
|
||||
"font/woff2",
|
||||
include_bytes!("static/fonts/Newsreader.woff2"),
|
||||
),
|
||||
"Newsreader-italic.woff2" => (
|
||||
"font/woff2",
|
||||
include_bytes!("static/fonts/Newsreader-italic.woff2"),
|
||||
),
|
||||
"favicon.svg" => ("image/svg+xml", include_str!("static/favicon.svg")),
|
||||
_ => return WebError::NotFound.into_response(),
|
||||
};
|
||||
let etag = format!("\"{}\"", hex::encode(Sha256::digest(asset.1.as_bytes())));
|
||||
let etag = format!("\"{}\"", hex::encode(Sha256::digest(asset.1)));
|
||||
if headers
|
||||
.get(header::IF_NONE_MATCH)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
|
||||
Reference in New Issue
Block a user