v0.34.9_css_refactor
This commit is contained in:
1 parent
2b7c67d755
commit
66ff9dfaa2
28 files changed
+11518
-13023
No files matched your search
@@ -79,7 +79,11 @@ static ASSETS: LazyLock<HashMap<&'static str, Asset>> = LazyLock::new(|| {
|
||||
("/assets/streams.js", JS, include_str!("web/streams.js")),
|
||||
("/assets/redirects.js", JS, include_str!("web/redirects.js")),
|
||||
("/assets/host404.js", JS, include_str!("web/host404.js")),
|
||||
("/assets/style.css", CSS, include_str!("web/style.css")),
|
||||
(
|
||||
"/assets/style.css",
|
||||
CSS,
|
||||
include_str!(concat!(env!("OUT_DIR"), "/panel-style.css")),
|
||||
),
|
||||
("/favicon.svg", SVG, include_str!("web/favicon.svg")),
|
||||
];
|
||||
entries
|
||||
|
||||
@@ -2966,6 +2966,69 @@ mod tests {
|
||||
}
|
||||
store.close().await;
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn modular_panel_css_is_one_embedded_public_asset_with_existing_compression() {
|
||||
use std::io::Read;
|
||||
|
||||
let (router, store, _dir, secret, _) = fixture().await;
|
||||
let cookie = format!("{}={secret}", auth::COOKIE_NAME);
|
||||
let css = include_str!(concat!(env!("OUT_DIR"), "/panel-style.css"));
|
||||
let path = format!("/assets/style_{}.css", super::asset_build_id());
|
||||
for authenticated in [false, true] {
|
||||
let mut req = request(&path);
|
||||
if authenticated {
|
||||
req = req.header("cookie", &cookie);
|
||||
}
|
||||
let response = router
|
||||
.clone()
|
||||
.oneshot(req.body(Body::empty()).unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert_eq!(response.headers()["content-type"], "text/css; charset=utf-8");
|
||||
assert_eq!(response.headers()["vary"], "Accept-Encoding");
|
||||
let bytes = axum::body::to_bytes(response.into_body(), css.len() + 1)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(bytes.as_ref(), css.as_bytes());
|
||||
}
|
||||
let response = router
|
||||
.clone()
|
||||
.oneshot(
|
||||
request(&path)
|
||||
.header("accept-encoding", "gzip")
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert_eq!(response.headers()["content-encoding"], "gzip");
|
||||
let compressed = axum::body::to_bytes(response.into_body(), css.len() + 1024)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut decoded = Vec::new();
|
||||
flate2::read::GzDecoder::new(compressed.as_ref())
|
||||
.read_to_end(&mut decoded)
|
||||
.unwrap();
|
||||
assert_eq!(decoded.as_slice(), css.as_bytes());
|
||||
|
||||
for path in ["/assets/css/manifest.json", "/assets/css/base.css"] {
|
||||
let response = router
|
||||
.clone()
|
||||
.oneshot(
|
||||
request(path)
|
||||
.header("cookie", &cookie)
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::NOT_FOUND, "{path}");
|
||||
}
|
||||
store.close().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn public_document_and_discovery_contain_only_login_information() {
|
||||
let (router, store, _dir, _, _) = fixture().await;
|
||||
|
||||
@@ -0,0 +1,518 @@
|
||||
/* Theme palettes and appearance tokens. Runtime preference variables remain writable by JavaScript.
|
||||
* Order is defined in manifest.json; responsive rules are centralized in 70-responsive.css.
|
||||
*/
|
||||
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--bg: #131615;
|
||||
--panel: #1b1f1d;
|
||||
--panel2: #202622;
|
||||
--line: #323a34;
|
||||
--muted: #a0aaa0;
|
||||
--text: #edf0e9;
|
||||
--accent: #f69b74;
|
||||
--green: #bdd293;
|
||||
--red: #ffaaa2;
|
||||
--sidebar-bg: #101310;
|
||||
--brand-secondary: #cfe0ac;
|
||||
--nav-active-bg: #242b23;
|
||||
--nav-hover-bg: #20251f;
|
||||
--text-subtle: #879082;
|
||||
--separator: #596454;
|
||||
--border-hover: #89917c;
|
||||
--field-bg: #131813;
|
||||
--field-line: #47523f;
|
||||
--placeholder: #778070;
|
||||
--row-line: #2b332c;
|
||||
--outline-line: #717b67;
|
||||
--chart-grid: #333c33;
|
||||
--code-bg: #101810;
|
||||
--code-alt-bg: #101922;
|
||||
--code-alt-text: #d3e4ec;
|
||||
--nav-selected-bg: #34412a;
|
||||
--accent-ink: #221810;
|
||||
--accent-hover: #ffb090;
|
||||
--selected-ink: #10251e;
|
||||
--notice-text: #c9b99c;
|
||||
--notice-line: #554833;
|
||||
--notice-bg: #29251c;
|
||||
--pill-line: #6d5a45;
|
||||
--pill-live-line: #576847;
|
||||
--toast-bg: #d0dfb4;
|
||||
--toast-text: #1c2818;
|
||||
--toast-line: #ebf2de;
|
||||
--toast-error-bg: #ffd2bd;
|
||||
--toast-error-line: #ff9f7a;
|
||||
--toast-error-text: #42241b;
|
||||
--danger-line: #644a47;
|
||||
--danger-line-strong: #79514e;
|
||||
--warn-line: #786647;
|
||||
--blue: #8ebcff;
|
||||
--transform-chart: #d7adff;
|
||||
--backdrop: rgba(0, 0, 0, .68);
|
||||
--shadow-mid: #0006;
|
||||
--shadow-strong: #0009;
|
||||
--neutral-soft: hsla(0, 0%, 49.8%, .05);
|
||||
--scrollbar-track: #151a16;
|
||||
--scrollbar-thumb: #4f5d50;
|
||||
--scrollbar-thumb-hover: #6b7b69;
|
||||
--chart-accent: var(--accent);
|
||||
--chart-secondary: var(--green);
|
||||
--chart-tertiary: var(--blue);
|
||||
--chart-quaternary: var(--transform-chart);
|
||||
--chart-quinary: var(--red);
|
||||
--chart-senary: var(--notice-text);
|
||||
--chart-septenary: var(--border-hover);
|
||||
--chart-octonary: var(--separator);
|
||||
--chart-surface: var(--panel2);
|
||||
--chart-surface-alt: var(--panel);
|
||||
--chart-line-width: 2;
|
||||
--chart-linecap: round;
|
||||
--chart-linejoin: round;
|
||||
--chart-grid-opacity: .28;
|
||||
--chart-grid-dash: none;
|
||||
--chart-svg-filter: none;
|
||||
--rp-progress-fill: var(--chart-accent);
|
||||
--rp-progress-track: var(--line);
|
||||
--rp-progress-radius: 3px;
|
||||
--rp-nav-active-color: var(--brand-secondary);
|
||||
--rp-nav-active-bg: var(--nav-active-bg);
|
||||
--rp-nav-hover-bg: var(--nav-hover-bg);
|
||||
--traffic-h1: var(--chart-accent);
|
||||
--traffic-h2: var(--chart-secondary);
|
||||
--traffic-h3: var(--chart-tertiary);
|
||||
--ui-scale: 1;
|
||||
--ui-field-font-size: 12px;
|
||||
--ui-field-pad-y: 6px;
|
||||
--ui-field-pad-x: 8px;
|
||||
--ui-field-min-height: 27px;
|
||||
--ui-check-size: 15px;
|
||||
--ui-range-height: 18px;
|
||||
--mono: ui-monospace, SFMono-Regular, Consolas, monospace;
|
||||
--sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
}
|
||||
|
||||
html[data-theme=light] {
|
||||
color-scheme: light;
|
||||
--bg: #f4f6f2;
|
||||
--panel: #fff;
|
||||
--panel2: #edf1eb;
|
||||
--line: #cbd3c7;
|
||||
--muted: #5e685f;
|
||||
--text: #1d241f;
|
||||
--accent: #b85b38;
|
||||
--green: #5f783f;
|
||||
--red: #b64a43;
|
||||
--sidebar-bg: #eef2ec;
|
||||
--brand-secondary: #6b8249;
|
||||
--nav-active-bg: #e1e8dc;
|
||||
--nav-hover-bg: #e8ede6;
|
||||
--text-subtle: #687369;
|
||||
--separator: #9ba69b;
|
||||
--border-hover: #839083;
|
||||
--field-bg: #fff;
|
||||
--field-line: #b7c1b5;
|
||||
--placeholder: #7a867b;
|
||||
--row-line: #e0e5dd;
|
||||
--outline-line: #98a497;
|
||||
--chart-grid: #d4dbd1;
|
||||
--code-bg: #edf3e9;
|
||||
--code-alt-bg: #eef3f6;
|
||||
--code-alt-text: #2d4049;
|
||||
--nav-selected-bg: #e1e9db;
|
||||
--accent-ink: #fff;
|
||||
--accent-hover: #a84f30;
|
||||
--selected-ink: #fff;
|
||||
--notice-text: #685b45;
|
||||
--notice-line: #d4c2a5;
|
||||
--notice-bg: #fff9ef;
|
||||
--pill-line: #cbb28f;
|
||||
--pill-live-line: #a8ba8d;
|
||||
--danger-line: #d7a29d;
|
||||
--danger-line-strong: #c98f89;
|
||||
--warn-line: #c6b27e;
|
||||
--blue: #4f77a9;
|
||||
--transform-chart: #7d4fa3;
|
||||
--shadow-mid: rgba(31, 39, 33, .16);
|
||||
--shadow-strong: rgba(31, 39, 33, .26);
|
||||
--neutral-soft: rgba(70, 78, 72, .05);
|
||||
--scrollbar-track: #e7ece4;
|
||||
--scrollbar-thumb: #a9b5a6;
|
||||
--scrollbar-thumb-hover: #879684;
|
||||
}
|
||||
|
||||
html:is([data-theme=light], [data-theme=cream]) {
|
||||
--rp-nav-active-color: color-mix(in srgb, var(--brand-secondary) 45%, var(--text) 55%);
|
||||
}
|
||||
|
||||
html[data-theme=ultra_dark] {
|
||||
color-scheme: dark;
|
||||
--bg: #030404;
|
||||
--panel: #0b0d0e;
|
||||
--panel2: #121516;
|
||||
--line: #292f30;
|
||||
--muted: #a4afb0;
|
||||
--text: #f1f4f2;
|
||||
--accent: #93dfc8;
|
||||
--logo-accent: #f69b74;
|
||||
--logo-secondary: #cfe0ac;
|
||||
--green: #b1dcb0;
|
||||
--red: #ff9899;
|
||||
--sidebar-bg: #000;
|
||||
--brand-secondary: #a9cfb5;
|
||||
--nav-active-bg: #172422;
|
||||
--nav-hover-bg: #121819;
|
||||
--text-subtle: #879695;
|
||||
--separator: #526261;
|
||||
--border-hover: #7c9f96;
|
||||
--field-bg: #090b0c;
|
||||
--field-line: #445352;
|
||||
--placeholder: #879593;
|
||||
--row-line: #202627;
|
||||
--outline-line: #79968f;
|
||||
--chart-grid: #303839;
|
||||
--code-bg: #0e1313;
|
||||
--code-alt-bg: #101a1d;
|
||||
--code-alt-text: #d5eae9;
|
||||
--nav-selected-bg: #213730;
|
||||
--accent-ink: #071814;
|
||||
--accent-hover: #b6f2dd;
|
||||
--selected-ink: #071814;
|
||||
--notice-text: #e6c99c;
|
||||
--notice-line: #66533a;
|
||||
--notice-bg: #201a11;
|
||||
--pill-line: #625544;
|
||||
--pill-live-line: #506c59;
|
||||
--toast-bg: #bcf3da;
|
||||
--toast-text: #10221a;
|
||||
--toast-line: #d8ffe8;
|
||||
--toast-error-bg: #ffd5d0;
|
||||
--toast-error-line: #ff9c96;
|
||||
--toast-error-text: #472625;
|
||||
--danger-line: #754245;
|
||||
--danger-line-strong: #965257;
|
||||
--warn-line: #89754c;
|
||||
--blue: #8bbdfb;
|
||||
--transform-chart: #cfaeff;
|
||||
--backdrop: rgba(0, 0, 0, .86);
|
||||
--shadow-mid: rgba(0, 0, 0, .64);
|
||||
--shadow-strong: rgba(0, 0, 0, .84);
|
||||
--neutral-soft: rgba(132, 171, 158, .045);
|
||||
--scrollbar-track: #060808;
|
||||
--scrollbar-thumb: #364243;
|
||||
--scrollbar-thumb-hover: #617373;
|
||||
}
|
||||
|
||||
html[data-theme=cream] {
|
||||
color-scheme: light;
|
||||
--bg: #f2ede3;
|
||||
--panel: #fcf9f2;
|
||||
--panel2: #eae3d6;
|
||||
--line: #d5cabb;
|
||||
--muted: #6b6257;
|
||||
--text: #302c27;
|
||||
--accent: #8c5942;
|
||||
--green: #5e7655;
|
||||
--red: #a54645;
|
||||
--sidebar-bg: #eae2d5;
|
||||
--brand-secondary: #668367;
|
||||
--nav-active-bg: #e0d3c0;
|
||||
--nav-hover-bg: #e6dccc;
|
||||
--text-subtle: #786e61;
|
||||
--separator: #afa494;
|
||||
--border-hover: #968776;
|
||||
--field-bg: #fffcf7;
|
||||
--field-line: #baad9a;
|
||||
--placeholder: #807466;
|
||||
--row-line: #e2d9cb;
|
||||
--outline-line: #9c8d7c;
|
||||
--chart-grid: #ddd3c4;
|
||||
--code-bg: #f0e8da;
|
||||
--code-alt-bg: #e7ebe9;
|
||||
--code-alt-text: #364f4e;
|
||||
--nav-selected-bg: #d9cbb7;
|
||||
--accent-ink: #fff;
|
||||
--accent-hover: #774632;
|
||||
--selected-ink: #fff;
|
||||
--notice-text: #765e40;
|
||||
--notice-line: #d9c3a1;
|
||||
--notice-bg: #f8efdf;
|
||||
--pill-line: #c2a78d;
|
||||
--pill-live-line: #b0bd9d;
|
||||
--toast-bg: #d6ead2;
|
||||
--toast-text: #263d29;
|
||||
--toast-line: #b8d5b6;
|
||||
--toast-error-bg: #f8ded7;
|
||||
--toast-error-line: #dca99c;
|
||||
--toast-error-text: #60352f;
|
||||
--danger-line: #d2a29a;
|
||||
--danger-line-strong: #b97870;
|
||||
--warn-line: #b49c6b;
|
||||
--blue: #4d7292;
|
||||
--transform-chart: #83659a;
|
||||
--backdrop: rgba(37, 27, 16, .44);
|
||||
--shadow-mid: rgba(64, 49, 34, .13);
|
||||
--shadow-strong: rgba(64, 49, 34, .24);
|
||||
--neutral-soft: rgba(104, 91, 69, .045);
|
||||
--scrollbar-track: #ece4d7;
|
||||
--scrollbar-thumb: #b5a997;
|
||||
--scrollbar-thumb-hover: #978a76;
|
||||
}
|
||||
|
||||
body[data-density=dense] {
|
||||
--ui-gap: 4px;
|
||||
--ui-card: 7px;
|
||||
--ui-font: 11.5px;
|
||||
--ui-control: 25px;
|
||||
}
|
||||
|
||||
body[data-density=comfortable] {
|
||||
--ui-gap: 13px;
|
||||
--ui-card: 17px;
|
||||
--ui-font: 14px;
|
||||
--ui-control: 36px;
|
||||
}
|
||||
|
||||
html[data-button-style=compact] body {
|
||||
--button-padding: 3px 7px;
|
||||
--button-font-size: 10px;
|
||||
--button-min-height: 22px;
|
||||
}
|
||||
|
||||
.chart-style-choice[data-chart-preset=default] {
|
||||
--sw1: var(--accent);
|
||||
--sw2: var(--green);
|
||||
--sw3: var(--blue);
|
||||
--sw4: var(--transform-chart);
|
||||
}
|
||||
|
||||
.chart-style-choice[data-chart-preset=soft] {
|
||||
--sw1: #e8a998;
|
||||
--sw2: #95c293;
|
||||
--sw3: #8daef6;
|
||||
--sw4: #b49de6;
|
||||
}
|
||||
|
||||
.chart-style-choice[data-chart-preset=technical] {
|
||||
--sw1: #79c9ff;
|
||||
--sw2: #9adf90;
|
||||
--sw3: #ffd56e;
|
||||
--sw4: #c39cff;
|
||||
}
|
||||
|
||||
.chart-style-choice[data-chart-preset=high_contrast] {
|
||||
--sw1: #ffd54f;
|
||||
--sw2: #8dff80;
|
||||
--sw3: #6fd9ff;
|
||||
--sw4: #ff78d7;
|
||||
}
|
||||
|
||||
.chart-style-choice[data-chart-preset=neon] {
|
||||
--sw1: #2cf6ff;
|
||||
--sw2: #7eff85;
|
||||
--sw3: #ff5cd0;
|
||||
--sw4: #8b7cff;
|
||||
}
|
||||
|
||||
.chart-style-choice[data-chart-preset=monochrome] {
|
||||
--sw1: #f2f2f2;
|
||||
--sw2: #b8b8b8;
|
||||
--sw3: #858585;
|
||||
--sw4: #595959;
|
||||
}
|
||||
|
||||
.chart-style-choice[data-chart-preset=warm] {
|
||||
--sw1: #ff9c5c;
|
||||
--sw2: #f4c46b;
|
||||
--sw3: #ff7b72;
|
||||
--sw4: #f4a261;
|
||||
}
|
||||
|
||||
.chart-style-choice[data-chart-preset=cool] {
|
||||
--sw1: #5fb9ff;
|
||||
--sw2: #45d3c3;
|
||||
--sw3: #7d8fff;
|
||||
--sw4: #70d7ff;
|
||||
}
|
||||
|
||||
html[data-chart-style=default] {
|
||||
--chart-accent: var(--accent);
|
||||
--chart-secondary: var(--green);
|
||||
--chart-tertiary: var(--blue);
|
||||
--chart-quaternary: var(--transform-chart);
|
||||
--chart-quinary: var(--red);
|
||||
--chart-senary: var(--notice-text);
|
||||
--chart-septenary: var(--border-hover);
|
||||
--chart-octonary: var(--separator);
|
||||
--chart-surface: var(--panel2);
|
||||
--chart-surface-alt: var(--panel);
|
||||
--chart-line-width: 2;
|
||||
--chart-linecap: round;
|
||||
--chart-linejoin: round;
|
||||
--chart-grid-opacity: .28;
|
||||
--chart-grid-dash: 2 5;
|
||||
--chart-svg-filter: none;
|
||||
}
|
||||
|
||||
html[data-chart-style=soft] {
|
||||
--chart-accent: #e8a998;
|
||||
--chart-secondary: #95c293;
|
||||
--chart-tertiary: #8daef6;
|
||||
--chart-quaternary: #b49de6;
|
||||
--chart-quinary: #d99490;
|
||||
--chart-senary: #d0b78e;
|
||||
--chart-septenary: #99a790;
|
||||
--chart-octonary: #728070;
|
||||
--chart-surface: color-mix(in srgb, var(--panel2) 88%, #fff 12%);
|
||||
--chart-surface-alt: color-mix(in srgb, var(--panel) 92%, #fff 8%);
|
||||
--chart-line-width: 2.8;
|
||||
--chart-linecap: round;
|
||||
--chart-linejoin: round;
|
||||
--chart-grid-opacity: .42;
|
||||
--chart-grid-dash: 1 0;
|
||||
--chart-svg-filter: saturate(.88) brightness(1.04);
|
||||
}
|
||||
|
||||
html[data-chart-style=technical] {
|
||||
--chart-accent: #79c9ff;
|
||||
--chart-secondary: #9adf90;
|
||||
--chart-tertiary: #ffd56e;
|
||||
--chart-quaternary: #c39cff;
|
||||
--chart-quinary: #ff8c7b;
|
||||
--chart-senary: #b8d0d7;
|
||||
--chart-septenary: #7e9aac;
|
||||
--chart-octonary: #6a7884;
|
||||
--chart-surface: color-mix(in srgb, var(--panel2) 90%, #0b1a27 10%);
|
||||
--chart-surface-alt: color-mix(in srgb, var(--panel) 90%, #09151f 10%);
|
||||
--chart-line-width: 1.35;
|
||||
--chart-linecap: square;
|
||||
--chart-linejoin: miter;
|
||||
--chart-grid-opacity: .82;
|
||||
--chart-grid-dash: 3 4;
|
||||
--chart-svg-filter: saturate(.76) contrast(1.08);
|
||||
}
|
||||
|
||||
html[data-chart-style=high_contrast] {
|
||||
--chart-accent: #ffd54f;
|
||||
--chart-secondary: #8dff80;
|
||||
--chart-tertiary: #6fd9ff;
|
||||
--chart-quaternary: #ff78d7;
|
||||
--chart-quinary: #ff8b6f;
|
||||
--chart-senary: #f2e48f;
|
||||
--chart-septenary: #d7f0d9;
|
||||
--chart-octonary: #cfcfcf;
|
||||
--chart-surface: color-mix(in srgb, var(--panel2) 94%, #000 6%);
|
||||
--chart-surface-alt: color-mix(in srgb, var(--panel) 94%, #000 6%);
|
||||
--chart-line-width: 3;
|
||||
--chart-grid-opacity: .95;
|
||||
--chart-grid-dash: 1 0;
|
||||
--chart-svg-filter: saturate(1.6) contrast(1.22) brightness(1.08);
|
||||
--chart-linecap: round;
|
||||
--chart-linejoin: round;
|
||||
}
|
||||
|
||||
html[data-chart-style=cool] {
|
||||
--chart-linecap: round;
|
||||
--chart-linejoin: round;
|
||||
--chart-accent: #5fb9ff;
|
||||
--chart-secondary: #45d3c3;
|
||||
--chart-tertiary: #7d8fff;
|
||||
--chart-quaternary: #70d7ff;
|
||||
--chart-quinary: #54a0ff;
|
||||
--chart-senary: #8bcbe0;
|
||||
--chart-septenary: #6ea6bf;
|
||||
--chart-octonary: #7d91a9;
|
||||
--chart-surface: color-mix(in srgb, var(--panel2) 88%, #0d2740 12%);
|
||||
--chart-surface-alt: color-mix(in srgb, var(--panel) 90%, #0d2033 10%);
|
||||
--chart-line-width: 2.2;
|
||||
--chart-grid-opacity: .46;
|
||||
--chart-grid-dash: 2 4;
|
||||
--chart-svg-filter: saturate(1.08) brightness(1.03);
|
||||
}
|
||||
|
||||
html[data-chart-style=monochrome] {
|
||||
--chart-linecap: round;
|
||||
--chart-linejoin: round;
|
||||
--chart-accent: color-mix(in srgb, var(--text) 92%, #fff 8%);
|
||||
--chart-secondary: color-mix(in srgb, var(--muted) 80%, var(--text) 20%);
|
||||
--chart-tertiary: color-mix(in srgb, var(--separator) 72%, var(--text) 28%);
|
||||
--chart-quaternary: color-mix(in srgb, var(--line) 45%, var(--text) 55%);
|
||||
--chart-quinary: color-mix(in srgb, var(--muted) 55%, #fff 45%);
|
||||
--chart-senary: color-mix(in srgb, var(--outline-line) 58%, var(--text) 42%);
|
||||
--chart-septenary: color-mix(in srgb, var(--field-line) 70%, var(--text) 30%);
|
||||
--chart-octonary: color-mix(in srgb, var(--scrollbar-thumb) 75%, var(--text) 25%);
|
||||
--chart-surface: color-mix(in srgb, var(--panel2) 96%, var(--text) 4%);
|
||||
--chart-surface-alt: color-mix(in srgb, var(--panel) 96%, var(--text) 4%);
|
||||
--chart-line-width: 2.1;
|
||||
--chart-grid-opacity: .4;
|
||||
--chart-grid-dash: 2 3;
|
||||
--chart-svg-filter: saturate(0) contrast(1.05);
|
||||
}
|
||||
|
||||
html[data-chart-style=neon] {
|
||||
--chart-linecap: round;
|
||||
--chart-linejoin: round;
|
||||
--chart-accent: #2cf6ff;
|
||||
--chart-secondary: #7eff85;
|
||||
--chart-tertiary: #ff5cd0;
|
||||
--chart-quaternary: #8b7cff;
|
||||
--chart-quinary: #ffb454;
|
||||
--chart-senary: #ffe66b;
|
||||
--chart-septenary: #7edcff;
|
||||
--chart-octonary: #d8b3ff;
|
||||
--chart-surface: #0b1117;
|
||||
--chart-surface-alt: #101720;
|
||||
--chart-line-width: 2.45;
|
||||
--chart-grid-opacity: .62;
|
||||
--chart-grid-dash: 2 4;
|
||||
--chart-svg-filter: saturate(1.25) brightness(1.12);
|
||||
}
|
||||
|
||||
html[data-chart-style=warm] {
|
||||
--chart-linecap: round;
|
||||
--chart-linejoin: round;
|
||||
--chart-accent: #ff9c5c;
|
||||
--chart-secondary: #f4c46b;
|
||||
--chart-tertiary: #ff7b72;
|
||||
--chart-quaternary: #f4a261;
|
||||
--chart-quinary: #e76f51;
|
||||
--chart-senary: #d9a15e;
|
||||
--chart-septenary: #c9895c;
|
||||
--chart-octonary: #b37258;
|
||||
--chart-surface: color-mix(in srgb, var(--panel2) 88%, #3d1f10 12%);
|
||||
--chart-surface-alt: color-mix(in srgb, var(--panel) 90%, #32180d 10%);
|
||||
--chart-line-width: 2.4;
|
||||
--chart-grid-opacity: .36;
|
||||
--chart-grid-dash: 1 0;
|
||||
--chart-svg-filter: saturate(1.1) brightness(1.04);
|
||||
}
|
||||
|
||||
html[data-chart-style] {
|
||||
--traffic-h1: var(--chart-accent);
|
||||
--traffic-h2: var(--chart-secondary);
|
||||
--traffic-h3: var(--chart-tertiary);
|
||||
}
|
||||
|
||||
html[data-corner-style=square] {
|
||||
--rp-radius-surface: 0px;
|
||||
--rp-radius-control: 0px;
|
||||
--rp-progress-radius: 0px;
|
||||
}
|
||||
|
||||
html[data-corner-style=subtle] {
|
||||
--rp-radius-surface: 5px;
|
||||
--rp-radius-control: 4px;
|
||||
--rp-progress-radius: 2px;
|
||||
}
|
||||
|
||||
html[data-corner-style=rounded] {
|
||||
--rp-radius-surface: 12px;
|
||||
--rp-radius-control: 8px;
|
||||
--rp-progress-radius: 4px;
|
||||
}
|
||||
|
||||
html[data-corner-style=extra] {
|
||||
--rp-radius-surface: 18px;
|
||||
--rp-radius-control: 12px;
|
||||
--rp-progress-radius: 999px;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,773 @@
|
||||
/* Application startup, authentication, navigation, page chrome and draft dock.
|
||||
* Order is defined in manifest.json; responsive rules are centralized in 70-responsive.css.
|
||||
*/
|
||||
|
||||
html.app-loading .shell {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.app-startup {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10000;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 24px;
|
||||
box-sizing: border-box;
|
||||
background: var(--bg, #10151b);
|
||||
color: var(--text, #edf2f7);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-source[hidden],
|
||||
#toast[popover]::backdrop,
|
||||
.operation-message:empty,
|
||||
#certificate-api-cancel-edit[hidden],
|
||||
#certificate-api-edit-note[hidden],
|
||||
#certificate-api-token-field[hidden],
|
||||
html:not(.app-loading) .app-startup,
|
||||
html.app-startup-failed .app-startup-message,
|
||||
html.app-startup-failed .app-startup-spinner,
|
||||
.login-section #auth-message:empty,
|
||||
.mobile-menu-toggle,
|
||||
.draft-dock-list[hidden],
|
||||
#settings-page-appearance .dashboard-widget-setting .overview-widget-type,
|
||||
.dashboard-appearance-grid .dashboard-widget-list.is-expanded~.dashboard-widget-scroll-hint,
|
||||
.vh-group[hidden],
|
||||
.stream-socket-editor input[hidden],
|
||||
.vh-cache-options[hidden],
|
||||
.acl-policy-custom-controls[hidden],
|
||||
.listener-protocol-settings-actions button[hidden],
|
||||
.logs-dialog .logs-card>.card-heading>div:first-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-startup-card {
|
||||
width: min(100%, 360px);
|
||||
}
|
||||
|
||||
.app-startup-brand {
|
||||
font: 700 29px var(--mono, monospace);
|
||||
letter-spacing: -2px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.app-startup-brand span {
|
||||
color: var(--logo-secondary, var(--accent, #65cfa2));
|
||||
}
|
||||
|
||||
.app-startup-spinner {
|
||||
box-sizing: border-box;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: 0 auto 22px;
|
||||
border: 3px solid var(--line, #374352);
|
||||
border-top-color: var(--accent, #65cfa2);
|
||||
border-radius: 50%;
|
||||
animation: app-startup-spin .8s linear infinite;
|
||||
}
|
||||
|
||||
.app-startup h2 {
|
||||
margin: 0;
|
||||
font-size: 19px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.app-startup-message,
|
||||
.app-startup-recovery p {
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: var(--muted, #b5c0cc);
|
||||
}
|
||||
|
||||
.app-startup-recovery {
|
||||
visibility: hidden;
|
||||
animation: app-startup-reveal 0s linear 12s forwards;
|
||||
}
|
||||
|
||||
.app-startup-recovery .outline:not([hidden]) {
|
||||
margin-top: 10px;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
html.app-startup-failed .app-startup-recovery {
|
||||
visibility: visible;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.shell {
|
||||
display: grid;
|
||||
min-height: calc(100vh / var(--ui-scale, 1));
|
||||
grid-template-columns: 182px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: calc(100vh / var(--ui-scale, 1));
|
||||
background: var(--sidebar-bg);
|
||||
border-right: 1px solid var(--line);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px 13px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 780;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -.6px;
|
||||
font-size: 17px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
font: 700 33px var(--mono);
|
||||
border: 1px solid var(--logo-accent, var(--accent));
|
||||
color: var(--logo-accent, var(--accent));
|
||||
place-content: center;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding-right: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
letter-spacing: -3px;
|
||||
flex-shrink: 0;
|
||||
width: 47px;
|
||||
height: 47px;
|
||||
font-size: 29px;
|
||||
}
|
||||
|
||||
.brand-mark span {
|
||||
font-size: 22px;
|
||||
left: 29px;
|
||||
top: 19px;
|
||||
color: var(--logo-secondary, var(--brand-secondary));
|
||||
position: static;
|
||||
}
|
||||
|
||||
.edition {
|
||||
font: 9px/1.6 var(--mono);
|
||||
color: var(--muted);
|
||||
letter-spacing: 1px;
|
||||
margin: 17px 0 28px;
|
||||
}
|
||||
|
||||
.edition span {
|
||||
display: block;
|
||||
color: var(--accent);
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
nav {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.local-tag {
|
||||
display: flex;
|
||||
font: 9px var(--mono);
|
||||
color: var(--green);
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 13px;
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
border-left: 2px solid transparent;
|
||||
border-radius: var(--rp-radius-control, 0);
|
||||
padding: 9px 10px;
|
||||
}
|
||||
|
||||
.nav span {
|
||||
font: 11px var(--mono);
|
||||
opacity: .65;
|
||||
}
|
||||
|
||||
.nav b {
|
||||
font-weight: 530;
|
||||
}
|
||||
|
||||
.nav.active {
|
||||
background: var(--rp-nav-active-bg);
|
||||
color: var(--rp-nav-active-color);
|
||||
border-left-color: var(--rp-nav-active-color);
|
||||
}
|
||||
|
||||
.nav:not(.active):hover {
|
||||
background: var(--rp-nav-hover-bg);
|
||||
}
|
||||
|
||||
.sidebar .main-navigation .nav:focus-visible {
|
||||
outline: 2px solid var(--rp-nav-active-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.sidebar-bottom {
|
||||
margin-top: auto;
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
.sidebar-bottom p {
|
||||
font-size: 11px;
|
||||
line-height: 1.9;
|
||||
color: var(--text-subtle);
|
||||
margin: 13px 0 26px;
|
||||
}
|
||||
|
||||
main {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
padding: 0 16px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
border-bottom: 1px solid var(--line);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
font: 10px var(--mono);
|
||||
color: var(--muted);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.breadcrumb span {
|
||||
margin: 0 13px;
|
||||
color: var(--separator);
|
||||
}
|
||||
|
||||
.breadcrumb b {
|
||||
font-weight: 400;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.top-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.system-card h2 small {
|
||||
font: 10px var(--mono);
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
font: 10px var(--mono);
|
||||
letter-spacing: 2px;
|
||||
color: var(--green);
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.lead {
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
color: var(--muted);
|
||||
margin-top: 15px;
|
||||
max-width: 690px;
|
||||
}
|
||||
|
||||
.page-intro {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 13px 0 11px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.login-card label {
|
||||
display: block;
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
margin: 16px 0 7px;
|
||||
}
|
||||
|
||||
.page-footer .footer-update {
|
||||
color: var(--green);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.login-section {
|
||||
padding: 62px 0 40px;
|
||||
max-width: 720px;
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
.login-section h1 {
|
||||
font-size: calc(clamp(42px, 5vw, 68px));
|
||||
letter-spacing: -3px;
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
.login-section .lead {
|
||||
font-size: 15px;
|
||||
max-width: 570px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
padding: 27px;
|
||||
max-width: 510px;
|
||||
margin-top: 23px;
|
||||
}
|
||||
|
||||
.login-card>p {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.login-card .primary {
|
||||
width: 100%;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.login-card .fine {
|
||||
font-size: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
font: 9px var(--mono);
|
||||
letter-spacing: 1.7px;
|
||||
color: var(--muted);
|
||||
margin-top: 30px;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.login-footer span {
|
||||
margin: 0 11px;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.page-intro .eyebrow {
|
||||
font-size: 9px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
[dir=rtl] .nav {
|
||||
text-align: right;
|
||||
border-left: 0;
|
||||
border-right: 2px solid transparent;
|
||||
}
|
||||
|
||||
[dir=rtl] .nav.active {
|
||||
border-right-color: var(--rp-nav-active-color);
|
||||
}
|
||||
|
||||
.app-confirm-message {
|
||||
overflow-wrap: anywhere;
|
||||
margin-top: 14px;
|
||||
color: var(--text);
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.sidebar nav {
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.page-intro h1 {
|
||||
font-size: 25px;
|
||||
letter-spacing: -.7px;
|
||||
}
|
||||
|
||||
.page-intro .lead {
|
||||
font-size: 11px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.app-confirm-dialog {
|
||||
width: calc(min(520px, 100vw - 28px));
|
||||
max-width: none;
|
||||
padding: 0;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--rp-radius-surface, 4px);
|
||||
background: var(--panel);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.app-confirm-dialog::backdrop {
|
||||
background: var(--backdrop);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.app-confirm-card {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.app-confirm-actions {
|
||||
justify-content: flex-end;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.app-confirm-danger {
|
||||
border-color: var(--red);
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.app-confirm-danger:hover {
|
||||
border-color: var(--red);
|
||||
background: rgba(255, 170, 162, .08);
|
||||
}
|
||||
|
||||
.auth-page.auth-loading .auth-main,
|
||||
.auth-page.auth-loading .auth-top {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.auth-page {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.auth-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 18px 24px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.auth-main {
|
||||
max-width: 560px;
|
||||
margin: auto;
|
||||
padding: 28px 20px;
|
||||
}
|
||||
|
||||
.auth-main .login-section {
|
||||
padding: 0;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.auth-main h1 {
|
||||
font-size: 1.65rem;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.login-section #auth-message {
|
||||
margin-top: 14px;
|
||||
padding: 11px 12px;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--panel2);
|
||||
color: var(--text);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.auth-advanced>summary,
|
||||
.vh-backend-advanced>summary {
|
||||
cursor: pointer;
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 4px 0 8px;
|
||||
}
|
||||
|
||||
.auth-subcard {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--rp-radius-surface, 6px);
|
||||
padding: 10px;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.auth-subcard>h3 {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.auth-advanced {
|
||||
border-top: 1px solid var(--line);
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
body[data-density=compact] .shell {
|
||||
grid-template-columns: 164px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
body[data-density=compact] .sidebar {
|
||||
padding: 14px 9px;
|
||||
}
|
||||
|
||||
body[data-density=compact] .nav,
|
||||
body[data-density=compact] .stat {
|
||||
padding: 6px 7px;
|
||||
}
|
||||
|
||||
body[data-density=compact] .topbar {
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
body[data-density=compact] .page-intro {
|
||||
padding: 7px 0 6px;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
body[data-density=compact] .page-intro h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
body[data-density=compact] .page-intro .lead {
|
||||
font-size: 10px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.top-log-menu>summary,
|
||||
.backend-column-picker>summary,
|
||||
.file-log-search-help>summary {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.top-log-menu-items {
|
||||
position: absolute;
|
||||
z-index: 120;
|
||||
top: calc(100% + 6px);
|
||||
inset-inline-end: 0;
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
min-width: 170px;
|
||||
padding: 6px;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--panel);
|
||||
box-shadow: 0 12px 28px var(--shadow-strong);
|
||||
}
|
||||
|
||||
.top-log-menu-items .quiet {
|
||||
width: 100%;
|
||||
text-align: start;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.draft-dock {
|
||||
position: fixed;
|
||||
right: 14px;
|
||||
bottom: 14px;
|
||||
z-index: 900;
|
||||
width: calc(min(330px, 100vw - 28px));
|
||||
padding: 0;
|
||||
border: 1px solid var(--border-hover);
|
||||
border-radius: var(--rp-radius-surface, 0);
|
||||
background: var(--panel);
|
||||
box-shadow: 0 12px 32px var(--shadow-strong);
|
||||
}
|
||||
|
||||
.draft-dock-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
min-height: 34px;
|
||||
padding: 7px 9px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
background: var(--panel2);
|
||||
}
|
||||
|
||||
.draft-dock.is-collapsed .draft-dock-heading,
|
||||
.draft-dock-item:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.draft-dock-heading>strong {
|
||||
font: 700 10px var(--mono);
|
||||
letter-spacing: .12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.draft-dock-heading .pill {
|
||||
min-width: 24px;
|
||||
padding: 2px 6px;
|
||||
text-align: center;
|
||||
border-radius: var(--rp-radius-control, 0);
|
||||
}
|
||||
|
||||
.draft-dock-heading-actions,
|
||||
.traffic-protocol-legend span,
|
||||
.stat-chart .stat-chart-legend span,
|
||||
.function-live-chart-legend span,
|
||||
.connection-legend span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.draft-dock-toggle {
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
width: 26px;
|
||||
min-width: 26px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--rp-radius-control, 0);
|
||||
background: var(--panel);
|
||||
color: var(--text);
|
||||
font: 700 14px var(--mono);
|
||||
}
|
||||
|
||||
.draft-dock-toggle:hover,
|
||||
.chart-style-choice:hover {
|
||||
border-color: var(--border-hover);
|
||||
background: var(--nav-hover-bg);
|
||||
}
|
||||
|
||||
.draft-dock-list {
|
||||
display: grid;
|
||||
gap: 0;
|
||||
max-height: calc(min(42vh, 360px));
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.draft-dock-item {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 32px;
|
||||
gap: 0;
|
||||
align-items: stretch;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.draft-dock-open {
|
||||
min-height: 42px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
min-width: 0;
|
||||
padding: 6px 9px;
|
||||
text-align: left;
|
||||
border-right: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.draft-dock-remove {
|
||||
min-height: 42px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
font-size: 18px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.draft-dock-open:hover {
|
||||
background: var(--nav-hover-bg);
|
||||
color: var(--text);
|
||||
box-shadow: inset 3px 0 0 var(--accent);
|
||||
}
|
||||
|
||||
.draft-dock-remove:hover {
|
||||
background: var(--nav-hover-bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.draft-dock-identity {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.draft-dock-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.draft-dock-identity small {
|
||||
color: var(--muted);
|
||||
font: 9px var(--mono);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .08em;
|
||||
}
|
||||
|
||||
.draft-dock-identity time {
|
||||
flex: 0 0 auto;
|
||||
color: var(--text-subtle);
|
||||
font: 9px var(--mono);
|
||||
}
|
||||
|
||||
.draft-dock-identity b {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.page-intro-copy,
|
||||
.acl-picker,
|
||||
.vh-acl-profile-picker,
|
||||
.docker-health-grid input:not([type=checkbox]),
|
||||
.docker-health-grid select,
|
||||
.docker-route-grid input:not([type=checkbox]),
|
||||
.docker-route-grid select {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-intro-title-row {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.page-intro-title-row h1 {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.page-intro .page-intro-meta-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px 16px;
|
||||
min-width: 0;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.page-intro .page-intro-meta-row .eyebrow {
|
||||
margin-bottom: 0;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
@keyframes app-startup-spin {
|
||||
to {
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes app-startup-reveal {
|
||||
to {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,99 @@
|
||||
# Administration stylesheet
|
||||
|
||||
This directory is the source of the shared dashboard and login stylesheet.
|
||||
`manifest.json` defines its complete cascade order. A normal Cargo build runs
|
||||
`scripts/build/panel_css.py`, compacts these sources conservatively and embeds
|
||||
one `panel-style.css` from Cargo's `OUT_DIR` into the binary.
|
||||
|
||||
The browser continues to request `/assets/style_<build-id>.css` once. The
|
||||
existing fingerprint, login access rules and cached gzip/Brotli/Zstandard
|
||||
responses remain in use. Source modules are not separate HTTP assets. The
|
||||
documentation Reader/API page keeps its independent `../docs.css` stylesheet.
|
||||
|
||||
## Module ownership and order
|
||||
|
||||
| Order | File | Responsibility |
|
||||
|---|---|---|
|
||||
| 1 | `00-tokens.css` | Theme palettes, appearance tokens and custom properties shared with JavaScript preferences |
|
||||
| 2 | `10-base.css` | Reset, typography, buttons, form fields, tables, shared cards, dialogs and overlays |
|
||||
| 3 | `20-shell.css` | Startup, login, navigation, page structure and editor draft dock |
|
||||
| 4 | `30-overview.css` | Shared charts/legends and Overview metrics, density, widget layout controls and dashboard presets |
|
||||
| 5 | `40-editors.css` | Vhosts and backends, Web Server, Streams, redirects, 404 hosts and ACL controls |
|
||||
| 6 | `50-settings.css` | Settings, certificates, integrations and Docker service/agent management |
|
||||
| 7 | `60-monitoring.css` | Traffic, cache, transformations, live/stored logs and configuration history |
|
||||
| 8 | `70-responsive.css` | All viewport/container queries and reduced-motion adjustments, in their required cascade order |
|
||||
|
||||
## Editing rules
|
||||
|
||||
- Update the rule in its owning module. Keep one occurrence of a selector in
|
||||
each conditional scope; group selectors when they deliberately share one
|
||||
declaration block and the move preserves cascade behavior.
|
||||
- Reuse the existing theme and appearance variables. JavaScript also writes
|
||||
live values such as chart geometry, widget layout and preferences; retain
|
||||
those variable and class names when reorganizing CSS.
|
||||
- Place a responsive adjustment inside the existing matching condition in
|
||||
`70-responsive.css`. A container name needs whitespace before its condition,
|
||||
for example `@container overview-canvas (max-width: 700px)`.
|
||||
- Preserve the manifest order and the order of overlapping conditions. The
|
||||
query order is part of the cascade; do not sort breakpoints mechanically.
|
||||
In particular, the `760px` condition follows `620px` to preserve the later
|
||||
Transform and file-log adjustments. Container queries follow viewport rules.
|
||||
- Explicit dashboard spans set `--overview-widget-placement` on the widget;
|
||||
one common rule applies it to `grid-column`. Container queries change that
|
||||
variable for supported span values. Keep the generic no-span defaults
|
||||
separate so the legacy row-start and custom-layout contracts remain intact.
|
||||
- Add a new module only when it has a distinct owner and register it once in
|
||||
the manifest. Missing, repeated and unlisted CSS files fail the build.
|
||||
`@import`, `@charset`, path traversal and symlinked modules are rejected.
|
||||
- Group identical declaration bodies within each compatible selector family.
|
||||
Moving them across intervening rules requires checking specificity and source
|
||||
order, including combinations of state classes. Keep vendor pseudo-elements
|
||||
in separate compatible lists; a WebKit/Gecko selector mix can invalidate the
|
||||
whole rule in another engine.
|
||||
|
||||
The builder removes formatting whitespace beside braces/semicolons and normal
|
||||
comments. It preserves selector whitespace, escaped tokens, strings, functions
|
||||
and declaration order. It does not sort, merge or otherwise optimize rules at
|
||||
build time. No Node.js/npm or runtime stylesheet compiler is required.
|
||||
|
||||
## Verification
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
python3 scripts/build/panel_css.py --check
|
||||
python3 tools/dev/test_panel_css.py
|
||||
python3 scripts/preflight.py
|
||||
python3 scripts/audit_css_usage.py --strict
|
||||
bash dev.sh check
|
||||
bash dev.sh test
|
||||
```
|
||||
|
||||
The first three commands require only Python 3.10+. The optional CSS audit
|
||||
requires `tinycss2` and `cssselect2`; install them in a development environment,
|
||||
not on the deployed server. `--structure-json PATH` records repeated selectors,
|
||||
conditional blocks, properties and identical declaration blocks. `--json PATH`
|
||||
records selector references. The audit follows JavaScript embedded by Rust and
|
||||
flags scripts outside those entry points for review. Missing literal class
|
||||
names still need manual review because JavaScript can construct them.
|
||||
Strict mode also rejects identical declaration bodies within the same compatible
|
||||
selector family. Its only body-repetition exception is a single rule per vendor
|
||||
family when merging would create an incompatible selector list.
|
||||
|
||||
Use browser comparisons for layout changes: cover login and every workspace,
|
||||
open dialogs and expanded rows, themes, corner styles, dashboard density/width,
|
||||
hidden/disabled states, viewport breakpoints, container widths and reduced
|
||||
motion. A source audit alone does not establish visual or runtime equivalence.
|
||||
Rebuild the Rust binary after every stylesheet change.
|
||||
|
||||
When replacing a previously built checkout with a source ZIP, include the root
|
||||
`build.rs`. If Cargo reports that `OUT_DIR/panel-style.css` is missing, follow
|
||||
the [generated-CSS recovery steps](../../../../scripts/README.md#missing-generated-css-during-rust-compilation).
|
||||
Source preflight does not generate Cargo outputs. The build tracks the generated
|
||||
bundle as well as its inputs, and rejects a missing or empty output.
|
||||
|
||||
The optional [dashboard browser regression runner](../../../../tools/dev/README-dashboard-css.md)
|
||||
assembles the actual HTML and JavaScript against synthetic API fixtures and
|
||||
compares a supplied baseline stylesheet with the built candidate. It includes
|
||||
breakpoint/container boundaries and dynamic editor states. Its dependencies are
|
||||
development-only; they are not part of the application build or deployment.
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"files": [
|
||||
"00-tokens.css",
|
||||
"10-base.css",
|
||||
"20-shell.css",
|
||||
"30-overview.css",
|
||||
"40-editors.css",
|
||||
"50-settings.css",
|
||||
"60-monitoring.css",
|
||||
"70-responsive.css"
|
||||
]
|
||||
}
|
||||
-12666
File diff suppressed because it is too large.
Load diff
@@ -423,7 +423,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn css_minifier_preserves_dashboard_corner_selector() {
|
||||
let input = include_str!("../admin/web/style.css");
|
||||
let input = include_str!(concat!(env!("OUT_DIR"), "/panel-style.css"));
|
||||
let result = css::minify(input.as_bytes()).expect("minify panel stylesheet");
|
||||
let output = std::str::from_utf8(&result).expect("UTF-8 CSS");
|
||||
assert!(output.contains("html:is([data-corner-style=square],[data-corner-style=subtle],[data-corner-style=rounded],[data-corner-style=extra]) :is("), "lost descendant pseudo-class combinator");
|
||||
|
||||
Reference in new issue
Block a user