diff --git a/crates/aster-webui/assets/nyan-cat.gif b/crates/aster-webui/assets/nyan-cat.gif new file mode 100644 index 0000000..91ce4a2 Binary files /dev/null and b/crates/aster-webui/assets/nyan-cat.gif differ diff --git a/crates/aster-webui/src/main.rs b/crates/aster-webui/src/main.rs index 32e351c..5c53039 100644 --- a/crates/aster-webui/src/main.rs +++ b/crates/aster-webui/src/main.rs @@ -34,6 +34,7 @@ use tracing::{error, info, warn}; const DISPLAY_WIDTH: u32 = 960; const DISPLAY_HEIGHT: u32 = 376; const SYSTEM_FRAME_NAME: &str = "System Specs"; +const NYAN_CAT_GIF: &[u8] = include_bytes!("../assets/nyan-cat.gif"); #[derive(Parser, Debug)] #[command(author, version, about)] @@ -231,6 +232,7 @@ async fn main() -> Result<()> { .route("/api/panels/activate", post(api_activate)) .route("/api/panels/disable", post(api_disable)) .route("/api/images/delete", post(api_delete)) + .route("/api/assets/nyan-cat.gif", get(api_nyan_cat)) .route("/api/images/{name}", get(api_image)) .layer(DefaultBodyLimit::max(64 * 1024 * 1024)) .with_state(state); @@ -527,6 +529,19 @@ async fn api_image( response } +async fn api_nyan_cat() -> Response { + let mut response = Response::new(Body::from(NYAN_CAT_GIF)); + response.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("image/gif"), + ); + response.headers_mut().insert( + header::CACHE_CONTROL, + HeaderValue::from_static("public, max-age=86400"), + ); + response +} + async fn build_state_response(state: &AppState) -> Result { let monitor = load_monitor_json(state).await?; let setup = monitor @@ -1413,247 +1428,23 @@ const INDEX_HTML: &str = r##" position: fixed; right: 24px; bottom: 18px; - width: 248px; - height: 108px; + width: 260px; pointer-events: none; z-index: 30; - opacity: 0.96; + opacity: 0.98; filter: drop-shadow(0 12px 24px rgba(0, 0, 0, 0.35)); - image-rendering: pixelated; } - - .nyan-space { - position: absolute; - inset: 0; - } - - .nyan-star { - position: absolute; - width: 6px; - height: 6px; - background: #f8fafc; - box-shadow: - 6px 0 0 #f8fafc, - 0 6px 0 #f8fafc, - 6px 6px 0 #f8fafc; - animation: nyan-star 1.2s steps(2) infinite; - } - - .nyan-star.s1 { left: 10px; top: 16px; transform: scale(0.8); } - .nyan-star.s2 { left: 48px; top: 72px; transform: scale(0.6); animation-delay: 0.25s; } - .nyan-star.s3 { left: 168px; top: 8px; transform: scale(0.7); animation-delay: 0.55s; } - - .nyan-rainbow { - position: absolute; - left: 14px; - top: 37px; - width: 126px; - height: 30px; - display: grid; - grid-template-rows: repeat(6, 1fr); - gap: 1px; - animation: nyan-rainbow 0.55s steps(2) infinite; - } - - .nyan-rainbow .stripe { - border-radius: 0; - } - - .nyan-rainbow .s1 { background: #ff4d6d; } - .nyan-rainbow .s2 { background: #ff8c42; } - .nyan-rainbow .s3 { background: #ffd93d; } - .nyan-rainbow .s4 { background: #51cf66; } - .nyan-rainbow .s5 { background: #339af0; } - .nyan-rainbow .s6 { background: #845ef7; } - - .nyan-cat { - position: absolute; - right: 0; - top: 18px; - width: 124px; - height: 76px; - animation: nyan-float 0.55s steps(2) infinite; - } - - .nyan-cat::before, - .nyan-cat::after { - content: ""; - position: absolute; - left: 0; - width: 18px; - height: 8px; - background: #9ca3af; - border: 3px solid #111827; - border-right: none; - box-sizing: border-box; - } - - .nyan-cat::before { - top: 25px; - animation: nyan-tail 0.35s steps(2) infinite; - } - - .nyan-cat::after { - top: 37px; - width: 12px; - } - - .nyan-body { - position: absolute; - left: 20px; - top: 14px; - width: 56px; - height: 42px; - background: #d4a574; - border: 3px solid #111827; - box-sizing: border-box; - } - - .nyan-icing { - position: absolute; - inset: 6px; - background: #f6a5c0; - border: 3px solid #111827; - box-sizing: border-box; - } - - .nyan-icing .sprinkle { - position: absolute; - width: 4px; - height: 4px; - } - - .nyan-icing .sp1 { left: 8px; top: 8px; background: #f8fafc; } - .nyan-icing .sp2 { left: 24px; top: 10px; background: #fde047; } - .nyan-icing .sp3 { left: 38px; top: 8px; background: #60a5fa; } - .nyan-icing .sp4 { left: 14px; top: 20px; background: #fb7185; } - .nyan-icing .sp5 { left: 32px; top: 22px; background: #4ade80; } - .nyan-icing .sp6 { left: 42px; top: 18px; background: #f8fafc; } - - .nyan-head { - position: absolute; - left: 72px; - top: 8px; - width: 36px; - height: 30px; - background: #9ca3af; - border: 3px solid #111827; - box-sizing: border-box; - } - - .nyan-head::before, - .nyan-head::after { - content: ""; - position: absolute; - top: -10px; - width: 0; - height: 0; - border-left: 7px solid transparent; - border-right: 7px solid transparent; - border-bottom: 12px solid #111827; - } - - .nyan-head::before { left: 1px; } - .nyan-head::after { right: 1px; } - - .nyan-head .face { - position: absolute; - inset: 3px; - background: #b8bec7; - } - - .nyan-head .eye, - .nyan-head .blush { - position: absolute; + .nyan-wrap img { display: block; - } - - .nyan-head .eye { - top: 9px; - width: 4px; - height: 4px; - background: #0f172a; - } - - .nyan-head .e1 { left: 7px; } - .nyan-head .e2 { right: 7px; } - - .nyan-head .blush { - top: 16px; - width: 6px; - height: 3px; - background: #ff8fab; - } - - .nyan-head .b1 { left: 2px; } - .nyan-head .b2 { right: 2px; } - - .nyan-head .nose { - position: absolute; - left: 13px; - top: 14px; - width: 4px; - height: 4px; - background: #0f172a; - } - - .nyan-head .mouth { - position: absolute; - left: 10px; - top: 19px; - width: 10px; - height: 4px; - border-bottom: 3px solid #0f172a; - box-sizing: border-box; - } - - .nyan-leg { - position: absolute; - width: 8px; - height: 12px; - background: #9ca3af; - border: 3px solid #111827; - border-top: none; - box-sizing: border-box; - animation: nyan-legs 0.35s steps(2) infinite; - } - - .nyan-leg.l1 { left: 30px; top: 56px; } - .nyan-leg.l2 { left: 48px; top: 56px; animation-delay: 0.12s; } - .nyan-leg.l3 { left: 82px; top: 44px; } - .nyan-leg.l4 { left: 98px; top: 44px; animation-delay: 0.12s; } - - @keyframes nyan-float { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-3px); } - } - - @keyframes nyan-rainbow { - 0%, 100% { transform: translateX(0); opacity: 1; } - 50% { transform: translateX(-8px); opacity: 0.96; } - } - - @keyframes nyan-tail { - 0%, 100% { transform: translateY(0) scaleX(1); } - 50% { transform: translateY(-2px) scaleX(0.88); } - } - - @keyframes nyan-legs { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(2px); } - } - - @keyframes nyan-star { - 0%, 100% { opacity: 0.35; transform: translateX(0) scale(1); } - 50% { opacity: 1; transform: translateX(-4px) scale(1.1); } + width: 100%; + height: auto; } @media (max-width: 900px) { .nyan-wrap { right: 14px; bottom: 12px; - transform: scale(0.78); - transform-origin: bottom right; + width: 180px; } } @@ -1716,44 +1507,7 @@ const INDEX_HTML: &str = r##"